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 ![Flow chart to describe the chain of events for Dynamic 2021 to take](https://user-images.githubusercontent.com/35135081/109071468-9cab7e00-76a8-11eb-8f9f-2b920c602ef4.png) -`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 += "Lurking in the darkness, the Bloodsuckers were:
" - 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 += "
\[[ReturnFullName(TRUE)]\]" + 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 += "Their Vassals were..." - 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 += "The Vassals brought back into the fold were..." + 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]"} - return dat - -/obj/item/spellbook/attack_self(mob/user) - if(!owner) - to_chat(user, span_notice("You bind the spellbook to yourself.")) - owner = user - return - if(user != owner) - to_chat(user, span_warning("The [name] does not recognize you as its owner and refuses to open!")) - return - user.set_machine(src) - var/dat = "" - - dat += "" - - var/datum/spellbook_entry/E - for(var/i=1,i<=entries.len,i++) - var/spell_info = "" - E = entries[i] - spell_info += E.GetInfo() - if(E.CanBuy(user,src)) - spell_info+= "[E.buy_word]
" - else - spell_info+= "Can't [E.buy_word]
" - if(E.CanRefund(user,src)) - spell_info+= "Refund
" - spell_info += "
" - if(cat_dat[E.category]) - cat_dat[E.category] += spell_info - - for(var/category in categories) - dat += "
" - dat += GetCategoryHeader(category) - dat += cat_dat[category] - dat += "
" - - user << browse(wrap(dat), "window=spellbook;size=700x500") - onclose(user, "spellbook") - return - -/obj/item/spellbook/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return TRUE - - if(H.mind.special_role == "apprentice") - temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." - return - - var/datum/spellbook_entry/E = null - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["buy"]) - E = entries[text2num(href_list["buy"])] - if(E && E.CanBuy(H,src)) - if(E.Buy(H,src)) - if(E.limit) - E.limit-- - uses -= E.cost - else if(href_list["refund"]) - E = entries[text2num(href_list["refund"])] - if(E && E.refundable) - var/result = E.Refund(H,src) - if(result > 0) - if(!isnull(E.limit)) - E.limit += result - uses += result - else if(href_list["page"]) - tab = sanitize(href_list["page"]) - attack_self(H) - return diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm new file mode 100644 index 000000000000..07b5a9e52fd6 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm @@ -0,0 +1,232 @@ +/** + * ## Spellbook entries + * + * Wizard spellbooks are automatically populated with + * a list of every spellbook entry subtype when they're made. + * + * Wizards can then buy entries from the book to learn magic, + * invoke rituals, or summon items. + */ +/datum/spellbook_entry + /// The name of the entry + var/name + /// The description of the entry + var/desc + /// The type of spell that the entry grants (typepath) + var/datum/action/cooldown/spell/spell_type + /// What category the entry falls in + var/category + /// How many book charges does the spell take + var/cost = 2 + /// How many times has the spell been purchased. Compared against limit. + var/times = 0 + /// The limit on number of purchases from this entry in a given spellbook. If null, infinite are allowed. + var/limit + /// Is this refundable? + var/refundable = TRUE + /// Flavor. Verb used in saying how the spell is aquired. Ex "[Learn] Fireball" or "[Summon] Ghosts" + var/buy_word = "Learn" + /// The cooldown of the spell + var/cooldown + /// Whether the spell requires wizard garb or not + var/requires_wizard_garb = FALSE + /// Used so you can't have specific spells together + var/list/no_coexistance_typecache + +/datum/spellbook_entry/New() + no_coexistance_typecache = typecacheof(no_coexistance_typecache) + + if(ispath(spell_type)) + if(isnull(limit)) + limit = initial(spell_type.spell_max_level) + if(initial(spell_type.spell_requirements) & SPELL_REQUIRES_WIZARD_GARB) + requires_wizard_garb = TRUE + +/** + * Determines if this entry can be purchased from a spellbook + * Used for configs / round related restrictions. + * + * Return FALSE to prevent the entry from being added to wizard spellbooks, TRUE otherwise + */ +/datum/spellbook_entry/proc/can_be_purchased() + if(!name || !desc || !category) // Erroneously set or abstract + return FALSE + return TRUE + +/** + * Checks if the user, with the supplied spellbook, can purchase the given entry. + * + * Arguments + * * user - the mob who's buying the spell + * * book - what book they're buying the spell from + * + * Return TRUE if it can be bought, FALSE otherwise + */ +/datum/spellbook_entry/proc/can_buy(mob/living/carbon/human/user, obj/item/spellbook/book) + if(book.uses < cost) + return FALSE + if(!isnull(limit) && times >= limit) + return FALSE + for(var/spell in user.actions) + if(is_type_in_typecache(spell, no_coexistance_typecache)) + return FALSE + return TRUE + +/** + * Actually buy the entry for the user + * + * Arguments + * * user - the mob who's bought the spell + * * book - what book they've bought the spell from + * + * Return TRUE if the purchase was successful, FALSE otherwise + */ +/datum/spellbook_entry/proc/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/datum/action/cooldown/spell/existing = locate(spell_type) in user.actions + if(existing) + var/before_name = existing.name + if(!existing.level_spell()) + to_chat(user, span_warning("This spell cannot be improved further!")) + return FALSE + + to_chat(user, span_notice("You have improved [before_name] into [existing.name].")) + name = existing.name + + //we'll need to update the cooldowns for the spellbook + set_spell_info() + log_spellbook("[key_name(user)] improved their knowledge of [initial(existing.name)] to level [existing.spell_level] for [cost] points") + SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[existing.spell_level]")) + log_purchase(user.key) + return TRUE + + //No same spell found - just learn it + var/datum/action/cooldown/spell/new_spell = new spell_type(user.mind || user) + new_spell.Grant(user) + to_chat(user, span_notice("You have learned [new_spell.name].")) + + log_spellbook("[key_name(user)] learned [new_spell] for [cost] points") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + log_purchase(user.key) + return TRUE + +/datum/spellbook_entry/proc/log_purchase(key) + if(!islist(GLOB.wizard_spellbook_purchases_by_key[key])) + GLOB.wizard_spellbook_purchases_by_key[key] = list() + + for(var/list/log as anything in GLOB.wizard_spellbook_purchases_by_key[key]) + if(log[LOG_SPELL_TYPE] == type) + log[LOG_SPELL_AMOUNT]++ + return + + var/list/to_log = list( + LOG_SPELL_TYPE = type, + LOG_SPELL_AMOUNT = 1, + ) + GLOB.wizard_spellbook_purchases_by_key[key] += list(to_log) + +/** + * Checks if the user, with the supplied spellbook, can refund the entry + * + * Arguments + * * user - the mob who's refunding the spell + * * book - what book they're refunding the spell from + * + * Return TRUE if it can refunded, FALSE otherwise + */ +/datum/spellbook_entry/proc/can_refund(mob/living/carbon/human/user, obj/item/spellbook/book) + if(!refundable) + return FALSE + if(!book.refunds_allowed) + return FALSE + + for(var/datum/action/cooldown/spell/other_spell in user.actions) + if(initial(spell_type.name) == initial(other_spell.name)) + return TRUE + + return FALSE + +/** + * Actually refund the entry for the user + * + * Arguments + * * user - the mob who's refunded the spell + * * book - what book they're refunding the spell from + * + * Return -1 on failure, or return the point value of the refund on success + */ +/datum/spellbook_entry/proc/refund_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/area/wizard_station/wizard_home = GLOB.areas_by_type[/area/wizard_station] + if(get_area(user) != wizard_home) + to_chat(user, span_warning("You can only refund spells at the wizard lair!")) + return -1 + + for(var/datum/action/cooldown/spell/to_refund in user.actions) + if(initial(spell_type.name) != initial(to_refund.name)) + continue + + var/amount_to_refund = to_refund.spell_level * cost + if(amount_to_refund <= 0) + return -1 + + qdel(to_refund) + name = initial(name) + log_spellbook("[key_name(user)] refunded [src] for [amount_to_refund] points") + return amount_to_refund + + return -1 + +/** + * Set any of the spell info saved on our entry + * after something has occured + * + * For example, updating the cooldown after upgrading it + */ +/datum/spellbook_entry/proc/set_spell_info() + if(!spell_type) + return + + cooldown = (initial(spell_type.cooldown_time) / 10) + +/// Item summons, they give you an item. +/datum/spellbook_entry/item + refundable = FALSE + buy_word = "Summon" + /// Typepath of what item we create when purchased + var/obj/item/item_path + +/datum/spellbook_entry/item/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/atom/spawned_path = new item_path(get_turf(user)) + log_spellbook("[key_name(user)] bought [src] for [cost] points") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + try_equip_item(user, spawned_path) + log_purchase(user.key) + return spawned_path + +/// Attempts to give the item to the buyer on purchase. +/datum/spellbook_entry/item/proc/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/was_put_in_hands = user.put_in_hands(to_equip) + to_chat(user, span_notice("\A [to_equip.name] has been summoned [was_put_in_hands ? "in your hands" : "at your feet"].")) + +/// Ritual, these cause station wide effects and are (pretty much) a blank slate to implement stuff in +/datum/spellbook_entry/summon + category = "Rituals" + limit = 1 + refundable = FALSE + buy_word = "Cast" + +/datum/spellbook_entry/summon/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + log_spellbook("[key_name(user)] cast [src] for [cost] points") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + log_purchase(user.key) + return TRUE + +/// Non-purchasable flavor spells to populate the spell book with, for style. +/datum/spellbook_entry/challenge + name = "Take the Challenge" + category = "Challenges" + refundable = FALSE + buy_word = "Accept" + +// See, non-purchasable. +/datum/spellbook_entry/challenge/can_buy(mob/living/carbon/human/user, obj/item/spellbook/book) + return FALSE diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm new file mode 100644 index 000000000000..a44663463a7c --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm @@ -0,0 +1,105 @@ +// Wizard spells that assist the caster in some way +/datum/spellbook_entry/summonitem + name = "Summon Item" + desc = "Recalls a previously marked item to your hand from anywhere in the universe." + spell_type = /datum/action/cooldown/spell/summonitem + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/charge + name = "Charge" + desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user." + spell_type = /datum/action/cooldown/spell/charge + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/shapeshift + name = "Wild Shapeshift" + desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed." + spell_type = /datum/action/cooldown/spell/shapeshift/wizard + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/tap + name = "Soul Tap" + desc = "Fuel your spells using your own soul!" + spell_type = /datum/action/cooldown/spell/tap + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/disguise + name = "Mimicry" + spell_type = /datum/action/cooldown/spell/disguise + category = "Assistance" + cost = 1 + +/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/soulstones + name = "Soulstone Shard Kit" + 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/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE) + to_chat(user, span_notice("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"].")) + +/datum/spellbook_entry/item/soulstones/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + . =..() + if(!.) + return + + var/datum/action/cooldown/spell/conjure/construct/bonus_spell = new(user.mind || user) + bonus_spell.Grant(user) + +/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/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" + refundable = TRUE + +/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/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" + refundable = TRUE + +/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" + refundable = TRUE diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm new file mode 100644 index 000000000000..a65793ecfd81 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm @@ -0,0 +1,10 @@ +// THESE ARE NOT PURCHASABLE SPELLS! +// They're flavor references to old spells that got removed + +// shit that sounds stupid but fun so we can painfully lock behind a dimmer +/datum/spellbook_entry/challenge/multiverse + name = "Multiverse Sword" + desc = "The Station gets a multiverse sword to stop you. Can you withstand the hordes of multiverse realities?" + +/datum/spellbook_entry/challenge/antiwizard + name = "Friendly Wizard Scum" + desc = "A \"Friendly\" Wizard will protect the station, and try to kill you. They get a spellbook much like you, but will use it for \"GOOD\"." \ No newline at end of file diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm new file mode 100644 index 000000000000..414e52b49cdb --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm @@ -0,0 +1,133 @@ +// Defensive wizard spells +/datum/spellbook_entry/magicm + name = "Magic Missile" + desc = "Fires several, slow moving, magic projectiles at nearby targets." + spell_type = /datum/action/cooldown/spell/aoe/magic_missile + category = "Defensive" + +/datum/spellbook_entry/disabletech + name = "Disable Tech" + desc = "Disables all weapons, cameras and most other technology in range." + spell_type = /datum/action/cooldown/spell/emp/disable_tech + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/repulse + name = "Repulse" + desc = "Throws everything around the user away." + spell_type = /datum/action/cooldown/spell/aoe/repulse/wizard + category = "Defensive" + +/datum/spellbook_entry/lightning_packet + name = "Thrown Lightning" + desc = "Forged from eldrich energies, a packet of pure power, \ + known as a spell packet will appear in your hand, that when thrown will stun the target." + spell_type = /datum/action/cooldown/spell/conjure_item/spellpacket + category = "Defensive" + +/datum/spellbook_entry/timestop + name = "Time Stop" + desc = "Stops time for everyone except for you, allowing you to move freely \ + while your enemies and even projectiles are frozen." + spell_type = /datum/action/cooldown/spell/timestop + category = "Defensive" + +/datum/spellbook_entry/smoke + name = "Smoke" + desc = "Spawns a cloud of choking smoke at your location." + spell_type = /datum/action/cooldown/spell/smoke + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/forcewall + name = "Force Wall" + desc = "Create a magical barrier that only you can pass through." + spell_type = /datum/action/cooldown/spell/forcewall + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/lichdom + name = "Bind Soul" + desc = "A dark necromantic pact that can forever bind your soul to an item of your choosing, \ + turning you into an immortal Lich. So long as the item remains intact, you will revive from death, \ + no matter the circumstances. Be wary - with each revival, your body will become weaker, and \ + it will become easier for others to find your item of power." + spell_type = /datum/action/cooldown/spell/lichdom + category = "Defensive" + +/datum/spellbook_entry/spacetime_dist + name = "Spacetime Distortion" + desc = "Entangle the strings of space-time in an area around you, \ + randomizing the layout and making proper movement impossible. The strings vibrate..." + spell_type = /datum/action/cooldown/spell/spacetime_dist + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/the_traps + name = "The Traps!" + desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." + spell_type = /datum/action/cooldown/spell/conjure/the_traps + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/bees + name = "Lesser Summon Bees" + desc = "This spell magically kicks a transdimensional beehive, \ + instantly summoning a swarm of bees to your location. These bees are NOT friendly to anyone." + spell_type = /datum/action/cooldown/spell/conjure/bee + category = "Defensive" + +/datum/spellbook_entry/duffelbag + name = "Bestow Cursed Duffel Bag" + desc = "A curse that firmly attaches a demonic duffel bag to the target's back. \ + The duffel bag will make the person it's attached to take periodical damage \ + if it is not fed regularly, and regardless of whether or not it's been fed, \ + it will slow the person wearing it down significantly." + spell_type = /datum/action/cooldown/spell/touch/duffelbag + category = "Defensive" + cost = 1 + +/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/scryingorb + name = "Scrying Orb" + desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." + item_path = /obj/item/scrying + category = "Defensive" + +/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/wands/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE) + to_chat(user, span_notice("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"].")) + +/datum/spellbook_entry/item/armor + name = "Mastercrafted Armor Set" + desc = "An artefact suit of armor that allows you to cast spells \ + while providing more protection against attacks and the void of space. \ + Also grants a battlemage shield." + item_path = /obj/item/clothing/suit/space/hardsuit/wizard + category = "Defensive" + +/datum/spellbook_entry/item/battlemage_charge + name = "Battlemage Armour Charges" + desc = "A powerful defensive rune, it will grant eight additional charges to a battlemage shield." + item_path = /obj/item/wizard_armour_charge + category = "Defensive" + cost = 1 diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm new file mode 100644 index 000000000000..13400fd8c3a4 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm @@ -0,0 +1,45 @@ +// Wizard spells that aid mobiilty(or stealth?) +/datum/spellbook_entry/mindswap + name = "Mindswap" + desc = "Allows you to switch bodies with a target next to you. You will both fall asleep when this happens, and it will be quite obvious that you are the target's body if someone watches you do it." + spell_type = /datum/action/cooldown/spell/pointed/mind_transfer + category = "Mobility" + +/datum/spellbook_entry/knock + name = "Knock" + desc = "Opens nearby doors and closets." + spell_type = /datum/action/cooldown/spell/aoe/knock + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/blink + name = "Blink" + desc = "Randomly teleports you a short distance." + spell_type = /datum/action/cooldown/spell/teleport/radius_turf/blink + category = "Mobility" + +/datum/spellbook_entry/teleport + name = "Teleport" + desc = "Teleports you to an area of your selection." + spell_type = /datum/action/cooldown/spell/teleport/area_teleport/wizard + category = "Mobility" + +/datum/spellbook_entry/jaunt + name = "Ethereal Jaunt" + desc = "Turns your form ethereal, temporarily making you invisible and able to pass through walls." + spell_type = /datum/action/cooldown/spell/jaunt/ethereal_jaunt + category = "Mobility" + +/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/warp_whistle + category = "Mobility" + cost = 1 + +/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" \ No newline at end of file diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm new file mode 100644 index 000000000000..0cd5736bba0c --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm @@ -0,0 +1,115 @@ +// Offensive wizard spells +/datum/spellbook_entry/fireball + name = "Fireball" + desc = "Fires an explosive fireball at a target. Considered a classic among all wizards." + spell_type = /datum/action/cooldown/spell/pointed/projectile/fireball + category = "Offensive" + +/datum/spellbook_entry/spell_cards + name = "Spell Cards" + desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" + spell_type = /datum/action/cooldown/spell/pointed/projectile/spell_cards + category = "Offensive" + +/datum/spellbook_entry/rod_form + name = "Rod Form" + desc = "Take on the form of an immovable rod, destroying all in your path. Purchasing this spell multiple times will also increase the rod's damage and travel range." + spell_type = /datum/action/cooldown/spell/rod_form + category = "Offensive" + +/datum/spellbook_entry/disintegrate + name = "Smite" + desc = "Charges your hand with an unholy energy that can be used to cause a touched victim to violently explode." + spell_type = /datum/action/cooldown/spell/touch/smite + category = "Offensive" + +/datum/spellbook_entry/blind + name = "Blind" + desc = "Temporarily blinds a single target." + spell_type = /datum/action/cooldown/spell/pointed/blind + category = "Offensive" + cost = 1 + +/datum/spellbook_entry/mutate + name = "Mutate" + desc = "Causes you to turn into a hulk and gain laser vision for a short while." + spell_type = /datum/action/cooldown/spell/apply_mutations/mutate + category = "Offensive" + +/datum/spellbook_entry/fleshtostone + name = "Flesh to Stone" + desc = "Charges your hand with the power to turn victims into inert statues for a long period of time." + spell_type = /datum/action/cooldown/spell/touch/flesh_to_stone + category = "Offensive" + +/datum/spellbook_entry/teslablast + name = "Tesla Blast" + desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down." + spell_type = /datum/action/cooldown/spell/charged/beam/tesla + category = "Offensive" + +/datum/spellbook_entry/lightningbolt + name = "Lightning Bolt" + desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." + spell_type = /datum/action/cooldown/spell/pointed/projectile/lightningbolt + category = "Offensive" + cost = 1 + +/datum/spellbook_entry/infinite_guns + name = "Lesser Summon Guns" + desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage." + spell_type = /datum/action/cooldown/spell/conjure_item/infinite_guns/gun + category = "Offensive" + cost = 3 + no_coexistance_typecache = list(/datum/action/cooldown/spell/conjure_item/infinite_guns/arcane_barrage) + +/datum/spellbook_entry/arcane_barrage + name = "Arcane Barrage" + desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Lesser Summon Gun." + spell_type = /datum/action/cooldown/spell/conjure_item/infinite_guns/arcane_barrage + category = "Offensive" + cost = 3 + no_coexistance_typecache = list(/datum/action/cooldown/spell/conjure_item/infinite_guns/gun) + +/datum/spellbook_entry/barnyard + name = "Barnyard Curse" + desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." + spell_type = /datum/action/cooldown/spell/pointed/barnyardcurse + category = "Offensive" + +/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 + category = "Offensive" + +/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 + category = "Offensive" + +/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 + category = "Offensive" + +/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 + category = "Offensive" + +/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 + category = "Offensive" + +/datum/spellbook_entry/item/highfrequencyblade + name = "High Frequency Blade" + desc = "An incredibly swift enchanted blade resonating at a frequency high enough to be able to slice through anything." + item_path = /obj/item/twohanded/vibro_weapon/wizard + category = "Offensive" + cost = 3 diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm new file mode 100644 index 000000000000..051af2f45d91 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm @@ -0,0 +1,87 @@ +// Ritual spells which affect the station at large +/// How much threat we need to let these rituals happen on dynamic +#define MINIMUM_THREAT_FOR_RITUALS 100 + +/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/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + summon_ghosts(user) + playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, TRUE) + return ..() + +/datum/spellbook_entry/summon/guns + name = "Summon Guns" + desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. \ + There is a good chance that they will shoot each other first." + +/datum/spellbook_entry/summon/guns/can_be_purchased() + // Summon Guns requires 100 threat. + var/datum/game_mode/dynamic/mode = SSticker.mode + if(mode?.threat_level < MINIMUM_THREAT_FOR_RITUALS) + return FALSE + // Also must be config enabled + return !CONFIG_GET(flag/no_summon_guns) + +/datum/spellbook_entry/summon/guns/buy_spell(mob/living/carbon/human/user,obj/item/spellbook/book) + summon_guns(user, 10) + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + return ..() + +/datum/spellbook_entry/summon/magic + name = "Summon Magic" + desc = "Share the wonders of magic with the crew and show them \ + why they aren't to be trusted with it at the same time." + +/datum/spellbook_entry/summon/magic/can_be_purchased() + // Summon Magic requires 100 threat. + var/datum/game_mode/dynamic/mode = SSticker.mode + if(!istype(mode) || mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) //YOGS - secret gamemode + return FALSE + // Also must be config enabled + return !CONFIG_GET(flag/no_summon_magic) + +/datum/spellbook_entry/summon/magic/buy_spell(mob/living/carbon/human/user,obj/item/spellbook/book) + summon_magic(user, 10) + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + return ..() + +/datum/spellbook_entry/summon/events + name = "Summon Events" + desc = "Give Murphy's law a little push and replace all events with \ + special wizard ones that will confound and confuse everyone. \ + Multiple castings increase the rate of these events." + cost = 2 + limit = 5 // Each purchase can intensify it. + +/datum/spellbook_entry/summon/events/can_be_purchased() + // Summon Events requires 100 threat. + var/datum/game_mode/dynamic/mode = SSticker.mode + if(!istype(mode) || mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) //YOGS - secret gamemode + return FALSE + // Also, must be config enabled + return !CONFIG_GET(flag/no_summon_events) + +/datum/spellbook_entry/summon/events/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + summon_events(user) + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + return ..() + +/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_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/message = tgui_input_text(user, "Whisper a secret truth to drive your victims to madness", "Whispers of Madness") + if(!message) + return FALSE + curse_of_madness(user, message) + playsound(user, 'sound/magic/mandswap.ogg', 50, TRUE) + return ..() + +#undef MINIMUM_THREAT_FOR_RITUALS diff --git a/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm new file mode 100644 index 000000000000..65a6213b28d0 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm @@ -0,0 +1,329 @@ +/obj/item/spellbook + name = "spell book" + desc = "An unearthly tome that glows with power." + icon = 'icons/obj/library.dmi' + icon_state = "book" + item_state = "book" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + + /// The number of book charges we have to buy spells + var/uses = 10 + /// The bonus that you get from going semi-random. + var/semi_random_bonus = 2 + /// The bonus that you get from going full random. + var/full_random_bonus = 5 + /// Determines if this spellbook can refund anything. + var/refunds_allowed = TRUE + /// The mind that first used the book. Automatically assigned when a wizard spawns. + var/datum/mind/owner + /// A list to all spellbook entries within + var/list/entries = list() + +/obj/item/spellbook/Initialize(mapload) + . = ..() + prepare_spells() + RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge)) + +/obj/item/spellbook/Destroy(force) + owner = null + entries.Cut() + return ..() + +/** + * Signal proc for [COMSIG_ITEM_MAGICALLY_CHARGED] + * + * Has no effect on charge, but gives a funny message to people who think they're clever. + */ +/obj/item/spellbook/proc/on_magic_charge(datum/source, datum/action/cooldown/spell/spell, mob/living/caster) + SIGNAL_HANDLER + + var/static/list/clever_girl = list( + "NICE TRY BUT NO!", + "CLEVER BUT NOT CLEVER ENOUGH!", + "SUCH FLAGRANT CHEESING IS WHY WE ACCEPTED YOUR APPLICATION!", + "CUTE! VERY CUTE!", + "YOU DIDN'T THINK IT'D BE THAT EASY, DID YOU?", + ) + + to_chat(caster, span_warning("Glowing red letters appear on the front cover...")) + to_chat(caster, span_red(pick(clever_girl))) + + return COMPONENT_ITEM_BURNT_OUT + +/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/attack_self(mob/user) + if(!owner) + if(!user.mind) + return + to_chat(user, span_notice("You bind [src] to yourself.")) + owner = user.mind + return + + if(user.mind != owner) + if(user.mind.has_antag_datum(/datum/antagonist/wizard/apprentice)) + to_chat(user, span_warning("If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not.")) + else + to_chat(user, span_warning("[src] does not recognize you as its owner and refuses to open!")) + return + + return ..() + +/obj/item/spellbook/attackby(obj/item/O, mob/user, params) + // This can be generalized in the future, but for now it stays + if(istype(O, /obj/item/antag_spawner/contract)) + var/datum/spellbook_entry/item/contract/contract_entry = locate() in entries + if(!istype(contract_entry)) + to_chat(user, span_warning("[src] doesn't seem to want to refund [O].")) + return + if(!contract_entry.can_refund(user, src)) + to_chat(user, span_warning("You can't refund [O].")) + return + 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!")) + return + + to_chat(user, span_notice("You feed the contract back into the spellbook, refunding your points.")) + uses += contract_entry.cost + contract_entry.times-- + qdel(O) + + else if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) + var/datum/spellbook_entry/item/hugbottle/demon_entry = locate() in entries + if(!istype(demon_entry)) + to_chat(user, span_warning("[src] doesn't seem to want to refund [O].")) + return + if(!demon_entry.can_refund(user, src)) + to_chat(user, span_warning("You can't refund [O].")) + return + + to_chat(user, span_notice("On second thought, maybe summoning a demon isn't a funny idea. You refund your points.")) + uses += demon_entry.cost + demon_entry.times-- + qdel(O) + + else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) + var/datum/spellbook_entry/item/bloodbottle/demon_entry = locate() in entries + if(!istype(demon_entry)) + to_chat(user, span_warning("[src] doesn't seem to want to refund [O].")) + return + if(!demon_entry.can_refund(user, src)) + to_chat(user, span_warning("You can't refund [O].")) + return + + to_chat(user, span_notice("On second thought, maybe summoning a demon is a bad idea. You refund your points.")) + uses += demon_entry.cost + demon_entry.times-- + qdel(O) + + return ..() + +/// Instantiates our list of spellbook entries. +/obj/item/spellbook/proc/prepare_spells() + var/entry_types = subtypesof(/datum/spellbook_entry) + for(var/type in entry_types) + var/datum/spellbook_entry/possible_entry = new type() + if(!possible_entry.can_be_purchased()) + qdel(possible_entry) + continue + + possible_entry.set_spell_info() //loads up things for the entry that require checking spell instance. + entries |= possible_entry + +/obj/item/spellbook/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Spellbook") + ui.open() + +/obj/item/spellbook/ui_data(mob/user) + var/list/data = list() + data["owner"] = owner + data["points"] = uses + data["semi_random_bonus"] = initial(uses) + semi_random_bonus + data["full_random_bonus"] = initial(uses) + full_random_bonus + return data + +//This is a MASSIVE amount of data, please be careful if you remove it from static. +/obj/item/spellbook/ui_static_data(mob/user) + var/list/data = list() + // Collect all info from each intry. + var/list/entry_data = list() + for(var/datum/spellbook_entry/entry as anything in entries) + var/list/individual_entry_data = list() + individual_entry_data["name"] = entry.name + individual_entry_data["desc"] = entry.desc + individual_entry_data["ref"] = REF(entry) + individual_entry_data["requires_wizard_garb"] = entry.requires_wizard_garb + individual_entry_data["cost"] = entry.cost + individual_entry_data["times"] = entry.times + individual_entry_data["cooldown"] = entry.cooldown + individual_entry_data["cat"] = entry.category + individual_entry_data["refundable"] = entry.refundable + individual_entry_data["buyword"] = entry.buy_word + entry_data += list(individual_entry_data) + + data["entries"] = entry_data + return data + +/obj/item/spellbook/ui_act(action, params) + . = ..() + if(.) + return + var/mob/living/carbon/human/wizard = usr + if(!istype(wizard)) + to_chat(wizard, span_warning("The book doesn't seem to listen to lower life forms.")) + return FALSE + + // Actions that are always available + switch(action) + if("purchase") + var/datum/spellbook_entry/entry = locate(params["spellref"]) in entries + return purchase_entry(entry, wizard) + + if("refund") + var/datum/spellbook_entry/entry = locate(params["spellref"]) in entries + if(!istype(entry)) + CRASH("[type] had an invalid ref to a spell passed in refund.") + if(!entry.can_refund(wizard, src)) + return FALSE + var/result = entry.refund_spell(wizard, src) + if(result <= 0) + return FALSE + + entry.times = 0 + uses += result + return TRUE + + if(uses < initial(uses)) + to_chat(wizard, span_warning("You need to have all your spell points to do this!")) + return FALSE + + // Actions that are only available if you have full spell points + switch(action) + if("semirandomize") + semirandomize(wizard, semi_random_bonus) + return TRUE + + if("randomize") + randomize(wizard, full_random_bonus) + return TRUE + + if("purchase_loadout") + wizard_loadout(wizard, locate(params["id"])) + return TRUE + +/// Attempts to purchased the passed entry [to_buy] for [user]. +/obj/item/spellbook/proc/purchase_entry(datum/spellbook_entry/to_buy, mob/living/carbon/human/user) + if(!istype(to_buy)) + CRASH("Spellbook attempted to buy an invalid entry. Got: [to_buy ? "[to_buy] ([to_buy.type])" : "null"]") + if(!to_buy.can_buy(user, src)) + return FALSE + if(!to_buy.buy_spell(user, src)) + return FALSE + + to_buy.times++ + uses -= to_buy.cost + return TRUE + +/// Purchases a wizard loadout [loadout] for [wizard]. +/obj/item/spellbook/proc/wizard_loadout(mob/living/carbon/human/wizard, loadout) + var/list/wanted_spells + switch(loadout) + if(WIZARD_LOADOUT_CLASSIC) //(Fireball>2, MM>2, Smite>2, Jauntx2>4) = 10 + wanted_spells = list( + /datum/spellbook_entry/fireball = 1, + /datum/spellbook_entry/magicm = 1, + /datum/spellbook_entry/disintegrate = 1, + /datum/spellbook_entry/jaunt = 2, + ) + if(WIZARD_LOADOUT_MJOLNIR) //(Mjolnir>2, Summon Itemx1>1, Mutate>2, Force Wall>1, Blink>2, tesla>2) = 10 + wanted_spells = list( + /datum/spellbook_entry/item/mjolnir = 1, + /datum/spellbook_entry/summonitem = 1, + /datum/spellbook_entry/mutate = 1, + /datum/spellbook_entry/forcewall = 1, + /datum/spellbook_entry/blink = 1, + /datum/spellbook_entry/teslablast = 1, + ) + if(WIZARD_LOADOUT_WIZARMY) //(Soulstones>2, Staff of Change>2, A Necromantic Stone>2, Teleport>2, Ethereal Jaunt>2) = 10 + wanted_spells = list( + /datum/spellbook_entry/item/soulstones = 1, + /datum/spellbook_entry/item/staffchange = 1, + /datum/spellbook_entry/item/necrostone = 1, + /datum/spellbook_entry/teleport = 1, + /datum/spellbook_entry/jaunt = 1, + ) + if(WIZARD_LOADOUT_SOULTAP) //(Soul Tap>1, Smite>2, Flesh to Stone>2, Mindswap>2, Knock>1, Teleport>2) = 10 + wanted_spells = list( + /datum/spellbook_entry/tap = 1, + /datum/spellbook_entry/disintegrate = 1, + /datum/spellbook_entry/fleshtostone = 1, + /datum/spellbook_entry/mindswap = 1, + /datum/spellbook_entry/knock = 1, + /datum/spellbook_entry/teleport = 1, + ) + + if(!length(wanted_spells)) + stack_trace("Wizard Loadout \"[loadout]\" did not find a loadout that existed.") + return + + for(var/entry in wanted_spells) + if(!ispath(entry, /datum/spellbook_entry)) + stack_trace("Wizard Loadout \"[loadout]\" had an non-spellbook_entry type in its wanted spells list. ([entry])") + continue + + var/datum/spellbook_entry/to_buy = locate(entry) in entries + if(!istype(to_buy)) + stack_trace("Wizard Loadout \"[loadout]\" had an invalid entry in its wanted spells list. ([entry])") + continue + + for(var/i in 1 to wanted_spells[entry]) + if(!purchase_entry(to_buy, wizard)) + stack_trace("Wizard Loadout \"[loadout]\" was unable to buy a spell for [wizard]. ([entry])") + message_admins("Wizard [wizard] purchased Loadout \"[loadout]\" but was unable to purchase one of the entries ([to_buy]) for some reason.") + break + + refunds_allowed = FALSE + + if(uses > 0) + stack_trace("Wizard Loadout \"[loadout]\" does not use 10 wizard spell slots (used: [initial(uses) - uses]). Stop scamming players out.") + +/// Purchases a semi-random wizard loadout for [wizard] +/// If passed a number [bonus_to_give], the wizard is given additional uses on their spellbook, used in randomization. +/obj/item/spellbook/proc/semirandomize(mob/living/carbon/human/wizard, bonus_to_give = 0) + var/list/needed_cats = list("Offensive", "Mobility") + var/list/shuffled_entries = shuffle(entries) + for(var/i in 1 to 2) + for(var/datum/spellbook_entry/entry as anything in shuffled_entries) + if(!(entry.category in needed_cats)) + continue + if(!purchase_entry(entry, wizard)) + continue + needed_cats -= entry.category //so the next loop doesn't find another offense spell + break + + refunds_allowed = FALSE + //we have given two specific category spells to the wizard. the rest are completely random! + randomize(wizard, bonus_to_give = bonus_to_give) + +/// Purchases a fully random wizard loadout for [wizard], with a point bonus [bonus_to_give]. +/// If passed a number [bonus_to_give], the wizard is given additional uses on their spellbook, used in randomization. +/obj/item/spellbook/proc/randomize(mob/living/carbon/human/wizard, bonus_to_give = 0) + var/list/entries_copy = entries.Copy() + uses += bonus_to_give + while(uses > 0 && length(entries_copy)) + var/datum/spellbook_entry/entry = pick(entries_copy) + if(!purchase_entry(entry, wizard)) + continue + entries_copy -= entry + + refunds_allowed = FALSE diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm index 6809832bfe4c..a0ea045d8d20 100644 --- a/code/modules/antagonists/wizard/wizard.dm +++ b/code/modules/antagonists/wizard/wizard.dm @@ -1,13 +1,16 @@ +/// Global assoc list. [ckey] = [spellbook entry type] +GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key) + /datum/antagonist/wizard name = "Space Wizard" roundend_category = "wizards/witches" antagpanel_category = "Wizard" job_rank = ROLE_WIZARD + antag_hud_name = "wizard" antag_moodlet = /datum/mood_event/focused var/give_objectives = TRUE var/strip = TRUE //strip before equipping var/allow_rename = TRUE - var/hud_version = "wizard" var/datum/team/wizard/wiz_team //Only created if wizard summons apprentices var/move_to_lair = TRUE var/outfit_type = /datum/outfit/wizard @@ -51,7 +54,6 @@ wiz_team = new(owner) wiz_team.name = "[owner.current.real_name] team" wiz_team.master_wizard = src - update_wiz_icons_added(owner.current) /datum/antagonist/wizard/proc/send_to_lair() if(!owner || !owner.current) @@ -109,7 +111,11 @@ /datum/antagonist/wizard/on_removal() unregister() - owner.RemoveAllSpells() // TODO keep track which spells are wizard spells which innate stuff + // Currently removes all spells regardless of innate or not. Could be improved. + for(var/datum/action/cooldown/spell/spell in owner.current.actions) + if(spell.target == owner) + qdel(spell) + owner.current.actions -= spell return ..() /datum/antagonist/wizard/proc/equip_wizard() @@ -154,12 +160,11 @@ /datum/antagonist/wizard/apply_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current - update_wiz_icons_added(M, wiz_team ? TRUE : FALSE) //Don't bother showing the icon if you're solo wizard M.faction |= ROLE_WIZARD + add_team_hud(M) /datum/antagonist/wizard/remove_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current - update_wiz_icons_removed(M) M.faction -= ROLE_WIZARD @@ -172,7 +177,7 @@ /datum/antagonist/wizard/apprentice name = "Wizard Apprentice" - hud_version = "apprentice" + antag_hud_name = "apprentice" var/datum/mind/master var/school = APPRENTICE_DESTRUCTION outfit_type = /datum/outfit/wizard/apprentice @@ -190,29 +195,58 @@ /datum/antagonist/wizard/apprentice/equip_wizard() . = ..() - if(!owner) - return - var/mob/living/carbon/human/H = owner.current - if(!istype(H)) + if(!ishuman(owner.current)) return + + var/list/spells_to_grant = list() + var/list/items_to_grant = list() + switch(school) if(APPRENTICE_DESTRUCTION) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball(null)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned powerful, destructive spells. You are able to cast magic missile and fireball.") + spells_to_grant = list( + /datum/action/cooldown/spell/aoe/magic_missile, + /datum/action/cooldown/spell/pointed/projectile/fireball, + ) + to_chat(owner, span_bold("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned powerful, \ + destructive spells. You are able to cast magic missile and fireball.")) + if(APPRENTICE_BLUESPACE) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned reality bending mobility spells. You are able to cast teleport and ethereal jaunt.") + spells_to_grant = list( + /datum/action/cooldown/spell/teleport/area_teleport/wizard, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt, + ) + to_chat(owner, span_bold("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned reality-bending \ + mobility spells. You are able to cast teleport and ethereal jaunt.")) + if(APPRENTICE_HEALING) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/charge(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/forcewall(null)) - H.put_in_hands(new /obj/item/gun/magic/staff/healing(H)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.") + spells_to_grant = list( + /datum/action/cooldown/spell/charge, + /datum/action/cooldown/spell/forcewall, + ) + items_to_grant = list( + /obj/item/gun/magic/staff/healing, + ) + to_chat(owner, span_bold("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned life-saving \ + survival spells. You are able to cast charge and forcewall, and have a staff of healing.")) if(APPRENTICE_ROBELESS) - owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.") + spells_to_grant = list( + /datum/action/cooldown/spell/aoe/knock, + /datum/action/cooldown/spell/pointed/mind_transfer, + ) + to_chat(owner, span_bold("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned stealthy, \ + robeless spells. You are able to cast knock and mindswap.")) + + for(var/spell_type in spells_to_grant) + var/datum/action/cooldown/spell/new_spell = new spell_type(owner) + new_spell.Grant(owner.current) + + for(var/item_type in items_to_grant) + var/obj/item/new_item = new item_type(owner.current) + owner.current.put_in_hands(new_item) /datum/antagonist/wizard/apprentice/create_objectives() var/datum/objective/protect/new_objective = new /datum/objective/protect @@ -250,20 +284,12 @@ H.equip_to_slot_or_del(new master_mob.back.type, SLOT_BACK) //Operation: Fuck off and scare people - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null)) - -/datum/antagonist/wizard/proc/update_wiz_icons_added(mob/living/wiz,join = TRUE) - var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ] - wizhud.join_hud(wiz) - set_antag_hud(wiz, hud_version) - -/datum/antagonist/wizard/proc/update_wiz_icons_removed(mob/living/wiz) - var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ] - wizhud.leave_hud(wiz) - set_antag_hud(wiz, null) - + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/jaunt = new(owner) + jaunt.Grant(H) + var/datum/action/cooldown/spell/teleport/area_teleport/wizard/teleport = new(owner) + teleport.Grant(H) + var/datum/action/cooldown/spell/teleport/radius_turf/blink/blink = new(owner) + blink.Grant(H) /datum/antagonist/wizard/academy name = "Academy Teacher" @@ -271,17 +297,19 @@ /datum/antagonist/wizard/academy/equip_wizard() . = ..() - - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile) - owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball) - - var/mob/living/M = owner.current - if(!istype(M)) + if(!isliving(owner.current)) return + var/mob/living/living_current = owner.current + + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/jaunt = new(owner) + jaunt.Grant(living_current) + var/datum/action/cooldown/spell/aoe/magic_missile/missile = new(owner) + missile.Grant(living_current) + var/datum/action/cooldown/spell/pointed/projectile/fireball/fireball = new(owner) + fireball.Grant(living_current) - var/obj/item/implant/exile/Implant = new/obj/item/implant/exile(M) - Implant.implant(M) + var/obj/item/implant/exile/exiled = new /obj/item/implant/exile(living_current) + exiled.implant(living_current) /datum/antagonist/wizard/academy/create_objectives() var/datum/objective/new_objective = new("Protect Wizard Academy from the intruders") @@ -310,12 +338,19 @@ else parts += span_redtext("The wizard has failed!") SSachievements.unlock_achievement(/datum/achievement/redtext/winlost, owner.current.client) //wizard loses, still give achievement lol - if(owner.spell_list.len>0) - parts += "[owner.name] used the following spells: " - var/list/spell_names = list() - for(var/obj/effect/proc_holder/spell/S in owner.spell_list) - spell_names += S.name - parts += spell_names.Join(", ") + + var/list/purchases = list() + for(var/list/log as anything in GLOB.wizard_spellbook_purchases_by_key[owner.key]) + var/datum/spellbook_entry/bought = log[LOG_SPELL_TYPE] + var/amount = log[LOG_SPELL_AMOUNT] + + purchases += "[amount > 1 ? "[amount]x ":""][initial(bought.name)]" + + if(length(purchases)) + parts += span_bold("[owner.name] used the following spells:") + parts += purchases.Join(", ") + else + parts += span_bold("[owner.name] didn't buy any spells!") return parts.Join("
") diff --git a/code/modules/antagonists/zombie/abilities/acid.dm b/code/modules/antagonists/zombie/abilities/acid.dm index 7fbd6ce89e44..dfcbe9987583 100644 --- a/code/modules/antagonists/zombie/abilities/acid.dm +++ b/code/modules/antagonists/zombie/abilities/acid.dm @@ -1,7 +1,7 @@ -/obj/effect/proc_holder/zombie/acid +/*/obj/effect/proc_holder/zombie/acid name = "Corrosive Acid" desc = "Drench an object in acid, destroying it over time." - action_icon_state = "alien_acid" + button_icon_state = "alien_acid" cooldown_time = 2.5 MINUTES /obj/effect/proc_holder/zombie/acid/on_gain(mob/living/carbon/user) @@ -39,7 +39,7 @@ if(!isinfected(usr)) return var/mob/living/carbon/user = usr - var/obj/effect/proc_holder/zombie/acid/A = locate() in user.abilities + var/obj/effect/proc_holder/zombie/acid/A = locate() in user.actions if(!A) return @@ -49,3 +49,4 @@ if(A.corrode(O, user)) A.start_cooldown() +*/ diff --git a/code/modules/antagonists/zombie/abilities/adrenaline.dm b/code/modules/antagonists/zombie/abilities/adrenaline.dm index 91af48f75d45..8b847cd1e82a 100644 --- a/code/modules/antagonists/zombie/abilities/adrenaline.dm +++ b/code/modules/antagonists/zombie/abilities/adrenaline.dm @@ -1,8 +1,8 @@ -/obj/effect/proc_holder/zombie/adrenaline +/*/obj/effect/proc_holder/zombie/adrenaline name = "Adrenaline Boost" desc = "Makes you able to sprint for a second or two!" - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "adrenaline" + button_icon = 'icons/mob/actions/actions_changeling.dmi' + button_icon_state = "adrenaline" cooldown_time = 3 MINUTES var/reagent_amount = 3 @@ -25,4 +25,5 @@ return FALSE if(add_reagent(user)) - return ..() \ No newline at end of file + return ..() +*/ diff --git a/code/modules/antagonists/zombie/abilities/necromance.dm b/code/modules/antagonists/zombie/abilities/necromance.dm index 40437112e988..0999fd035040 100644 --- a/code/modules/antagonists/zombie/abilities/necromance.dm +++ b/code/modules/antagonists/zombie/abilities/necromance.dm @@ -1,8 +1,8 @@ -/obj/effect/proc_holder/zombie/necromance +/*/obj/effect/proc_holder/zombie/necromance name = "Summon a Minion" desc = "Summons a zombie to help you." - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "equip" + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "equip" cooldown_time = 5 MINUTES var/list/summoned_minions = list() var/max_minions = 5 @@ -57,4 +57,5 @@ return FALSE necromance() - return ..() \ No newline at end of file + return ..() +*/ diff --git a/code/modules/antagonists/zombie/abilities/spit.dm b/code/modules/antagonists/zombie/abilities/spit.dm index e244411adfba..809bf1767c5b 100644 --- a/code/modules/antagonists/zombie/abilities/spit.dm +++ b/code/modules/antagonists/zombie/abilities/spit.dm @@ -1,7 +1,7 @@ -/obj/effect/proc_holder/zombie/spit +/*/obj/effect/proc_holder/zombie/spit name = "Spit Neurotoxin" desc = "Spits neurotoxin at someone, paralyzing them for a short time." - action_icon_state = "alien_neurotoxin_0" + button_icon_state = "alien_neurotoxin_0" active = FALSE cooldown_time = 1 MINUTES @@ -14,16 +14,16 @@ /obj/effect/proc_holder/zombie/spit/update_icon() action.button_icon_state = "alien_neurotoxin_[active]" - action.UpdateButtonIcon() + action.build_all_button_icons() /obj/effect/proc_holder/zombie/spit/InterceptClickOn(mob/living/caller, params, atom/target) if(..()) return FALSE - if(!isinfected(ranged_ability_user) || ranged_ability_user.stat != CONSCIOUS) + if(!isinfected(owner) || owner.stat != CONSCIOUS) remove_ranged_ability() return FALSE - var/mob/living/carbon/user = ranged_ability_user + var/mob/living/carbon/user = owner if(!ready) to_chat(user, span_warning("You cannot currently spit. You can spit again in [(cooldown_ends - world.time) / 10] seconds")) @@ -56,3 +56,4 @@ paralyze = 0 nodamage = TRUE return ..() +*/ diff --git a/code/modules/antagonists/zombie/abilities/tank.dm b/code/modules/antagonists/zombie/abilities/tank.dm index a5ce5f96e10d..4f74c6e0c195 100644 --- a/code/modules/antagonists/zombie/abilities/tank.dm +++ b/code/modules/antagonists/zombie/abilities/tank.dm @@ -1,8 +1,8 @@ -/obj/effect/proc_holder/zombie/tank +/*/obj/effect/proc_holder/zombie/tank name = "Tank" desc = "Gives you a moderate armor boost for a few seconds. Heals 60% of your brute and fire damage." - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "fake_death" + button_icon = 'icons/mob/actions/actions_changeling.dmi' + button_icon_state = "fake_death" cooldown_time = 2.5 MINUTES var/duration = 30 SECONDS var/armor_boost = 12.5 @@ -27,3 +27,4 @@ if(run_ability(user)) return ..() +*/ diff --git a/code/modules/antagonists/zombie/zombie.dm b/code/modules/antagonists/zombie/zombie.dm index e229647dd7cd..334a10cec74e 100644 --- a/code/modules/antagonists/zombie/zombie.dm +++ b/code/modules/antagonists/zombie/zombie.dm @@ -5,37 +5,37 @@ roundend_category = "zombies" antagpanel_category = "Zombie" - var/datum/action/innate/zombie/zomb/zombify = new +// var/datum/action/innate/zombie/zomb/zombify = new - var/datum/action/innate/zombie/talk/talk_action = new +// var/datum/action/innate/zombie/talk/talk_action = new - var/datum/action/innate/zombie/choose_class/evolution = new +// var/datum/action/innate/zombie/choose_class/evolution = new - var/datum/action/innate/zombie/choose_class/tier2/evolution2 = new +// var/datum/action/innate/zombie/choose_class/tier2/evolution2 = new //EVOLUTION var/evolutionTime = 0 //When can we evolve? //GENERAL ABILITIES - var/datum/action/innate/zombie/uncuff/uncuff = new +// var/datum/action/innate/zombie/uncuff/uncuff = new //SPITTER ABILITIES - var/obj/effect/proc_holder/zombie/spit/spit - var/obj/effect/proc_holder/zombie/acid/acid +// var/obj/effect/proc_holder/zombie/spit/spit +// var/obj/effect/proc_holder/zombie/acid/acid //Necromancer - var/obj/effect/proc_holder/zombie/necromance/necro +// var/obj/effect/proc_holder/zombie/necromance/necro //Runner - var/obj/effect/proc_holder/zombie/adrenaline/adren +// var/obj/effect/proc_holder/zombie/adrenaline/adren //Juggernaut - var/obj/effect/proc_holder/zombie/tank/tank +// var/obj/effect/proc_holder/zombie/tank/tank job_rank = ROLE_ZOMBIE var/datum/team/zombie/team - var/hud_type = "zombie" + antag_hud_name = "zombie" var/class_chosen = FALSE var/class_chosen_2 = FALSE @@ -66,9 +66,9 @@ /datum/antagonist/zombie/proc/add_objectives() objectives |= team.objectives -/datum/antagonist/zombie/Destroy() - QDEL_NULL(zombify) - return ..() +///datum/antagonist/zombie/Destroy() +// QDEL_NULL(zombify) +// return ..() /datum/antagonist/zombie/greet() @@ -82,72 +82,63 @@ var/mob/living/current = owner.current add_objectives() GLOB.zombies += owner - current.log_message("has been made a zombie!", LOG_ATTACK, color="#960000") - var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE] - zombie_hud.join_hud(current) - set_antag_hud(current, hud_type) - /datum/antagonist/zombie/apply_innate_effects() . = ..() var/mob/living/current = owner.current current.faction |= "zombies" - talk_action.Grant(current) +// talk_action.Grant(current) /datum/antagonist/zombie/remove_innate_effects() . = ..() var/mob/living/current = owner.current current.faction -= "zombies" - talk_action.Remove(current) +// talk_action.Remove(current) /datum/antagonist/zombie/on_removal() GLOB.zombies -= owner - - var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE] - zombie_hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) . = ..() -/datum/antagonist/zombie/proc/start_timer() - addtimer(CALLBACK(src, PROC_REF(add_button_timed)), 15 MINUTES) +///datum/antagonist/zombie/proc/start_timer() +// addtimer(CALLBACK(src, PROC_REF(add_button_timed)), 15 MINUTES) -/datum/antagonist/zombie/proc/add_button_timed() - zombify.Grant(owner.current) - to_chat(owner.current, span_userdanger("You can now turn into a zombie! The ability INSTANTLY kills you, and starts the process of turning into a zombie. IN 5 MINUTES YOU WILL FORCIBLY BE ZOMBIFIED IF YOU HAVEN'T.")) - addtimer(CALLBACK(src, PROC_REF(force_zombify)), 5 MINUTES) +///datum/antagonist/zombie/proc/add_button_timed() +// zombify.Grant(owner.current) +// to_chat(owner.current, span_userdanger("You can now turn into a zombie! The ability INSTANTLY kills you, and starts the process of turning into a zombie. IN 5 MINUTES YOU WILL FORCIBLY BE ZOMBIFIED IF YOU HAVEN'T.")) +// addtimer(CALLBACK(src, PROC_REF(force_zombify)), 5 MINUTES) -/datum/antagonist/zombie/proc/force_zombify() - if(!zombified) - zombify.Activate() +///datum/antagonist/zombie/proc/force_zombify() +// if(!zombified) +// zombify.Activate() /datum/antagonist/zombie/admin_add(datum/mind/new_owner,mob/admin) new_owner.add_antag_datum(src) message_admins("[key_name_admin(admin)] has zombied'ed [key_name_admin(new_owner)].") log_admin("[key_name(admin)] has zombied'ed [key_name(new_owner)].") - start_timer() +// start_timer() /datum/antagonist/zombie/get_admin_commands() . = ..() - .["Give Button"] = CALLBACK(src, PROC_REF(admin_give_button)) - .["Remove Button"] = CALLBACK(src, PROC_REF(remove_button)) +// .["Give Button"] = CALLBACK(src, PROC_REF(admin_give_button)) +// .["Remove Button"] = CALLBACK(src, PROC_REF(remove_button)) -/datum/antagonist/zombie/proc/admin_give_button(mob/admin) - zombify.Grant(owner.current) +///datum/antagonist/zombie/proc/admin_give_button(mob/admin) +// zombify.Grant(owner.current) -/datum/antagonist/zombie/proc/remove_button(mob/admin) - zombify.Remove(owner.current) +///datum/antagonist/zombie/proc/remove_button(mob/admin) +// zombify.Remove(owner.current) /datum/antagonist/zombie/proc/start_evolution_2() addtimer(CALLBACK(src, PROC_REF(finish_evolution_2)), TIER_2_TIME) /datum/antagonist/zombie/proc/finish_evolution_2() evolution_ready = TRUE - evolution2.Grant(owner.current) +// evolution2.Grant(owner.current) to_chat(owner.current, span_userdanger("You can now evolve into a Tier 2 zombie! There can only be tier 2 zombies equal to the amount of starting zombies!")) /datum/team/zombie @@ -182,13 +173,13 @@ return "
[parts.Join("
")]
" -/datum/action/innate/zombie - icon_icon = 'icons/mob/actions/actions_changeling.dmi' +/*/datum/action/innate/zombie + button_icon = 'icons/mob/actions/actions_changeling.dmi' background_icon_state = "bg_demon" buttontooltipstyle = "cult" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS -/datum/action/innate/zombie/IsAvailable() +/datum/action/innate/zombie/IsAvailable(feedback = FALSE) if(!isinfected(owner)) return FALSE return ..() @@ -224,12 +215,12 @@ /datum/action/innate/zombie/talk name = "Chat" desc = "Chat with your fellow infected." - icon_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon = 'icons/mob/actions/actions_cult.dmi' button_icon_state = "cult_comms" /datum/action/innate/zombie/talk/Activate() var/input = stripped_input(usr, "Please choose a message to tell to the other zombies.", "Infected Communications", "") - if(!input || !IsAvailable()) + if(!input || !IsAvailable(feedback = FALSE)) return talk(usr, input) @@ -254,12 +245,12 @@ /datum/action/innate/zombie/choose_class name = "Evolve" desc = "Evolve into a special class." - icon_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon = 'icons/mob/actions/actions_cult.dmi' button_icon_state = "cultfist" /datum/action/innate/zombie/choose_class/Activate() var/selected = input(usr, "Choose a class to evolve into", "Evolution") as null|anything in list("Runner", "Juggernaut", "Spitter") - if(!selected || !IsAvailable()) + if(!selected || !IsAvailable(feedback = FALSE)) return if(!isinfectedzombie(owner)) return @@ -307,10 +298,10 @@ /datum/action/innate/zombie/choose_class/tier2 name = "Evolve - Tier 2" desc = "Evolve into a Tier 2 special class." - icon_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon = 'icons/mob/actions/actions_cult.dmi' button_icon_state = "cultfist" -/datum/action/innate/zombie/choose_class/tier2/IsAvailable() +/datum/action/innate/zombie/choose_class/tier2/IsAvailable(feedback = FALSE) if(!isinfected(owner)) return var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums @@ -324,7 +315,7 @@ /datum/action/innate/zombie/choose_class/tier2/Activate() var/selected = input(usr, "Choose a class to evolve into", "Evolution") as null|anything in list("Necromancer") - if(!selected || !IsAvailable()) + if(!selected || !IsAvailable(feedback = FALSE)) return if(!isinfectedzombie(owner)) return @@ -372,9 +363,9 @@ panel = "Zombie" has_action = TRUE base_action = /datum/action/spell_action - action_icon = 'icons/mob/actions/actions_xeno.dmi' - action_icon_state = "spell_default" - action_background_icon_state = "bg_alien" + button_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "spell_default" + background_icon_state = "bg_alien" var/ready = TRUE var/cooldown_ends = 0 var/cooldown_time = 1 SECONDS @@ -427,7 +418,7 @@ /obj/effect/proc_holder/zombie/proc/start_cooldown() addtimer(CALLBACK(src, PROC_REF(reset_cooldown)), cooldown_time) cooldown_ends = world.time + cooldown_time - ready = FALSE + ready = FALSE*/ #undef TIER_2_TIME diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index cb1f5caf84c5..7ec8a2104e15 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -105,7 +105,7 @@ if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) return viewers(range, get_turf(target_loc)) else - return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) + return typecache_filter_list(target_loc.get_all_contents(), GLOB.typecache_living) /obj/item/assembly/flash/proc/try_use_flash(mob/user = null) if(user && HAS_TRAIT(user, TRAIT_NO_STUN_WEAPONS)) @@ -138,9 +138,7 @@ to_chat(M, span_disarm("[src] emits a blinding light!")) if(targeted) if(M.flash_act(1, 1)) - if(M.confused < power) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) + M.set_confusion_if_lower(power * CONFUSION_STACK_MAX_MULTIPLIER SECONDS) if(user) terrible_conversion_proc(M, user) visible_message(span_disarm("[user] blinds [M] with the flash!")) @@ -162,8 +160,7 @@ to_chat(M, span_danger("[src] fails to blind you!")) else if(M.flash_act()) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) + M.set_confusion_if_lower(power * CONFUSION_STACK_MAX_MULTIPLIER SECONDS) /obj/item/assembly/flash/attack(mob/living/M, mob/user) if(!try_use_flash(user)) @@ -177,8 +174,7 @@ log_combat(user, R, "flashed", src) update_icon(1) R.Paralyze(rand(80,120)) - var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - R.confused += min(5, diff) + R.set_confusion_if_lower(5 SECONDS * CONFUSION_STACK_MAX_MULTIPLIER) R.flash_act(affect_silicon = 1) user.visible_message(span_disarm("[user] overloads [R]'s sensors with the flash!"), span_danger("You overload [R]'s sensors with the flash!")) return TRUE @@ -335,10 +331,10 @@ if(!hypnosis) to_chat(M, span_notice("The light makes you feel oddly relaxed...")) - M.confused += min(M.confused + 10, 20) - M.dizziness += min(M.dizziness + 10, 20) - M.drowsyness += min(M.drowsyness + 10, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) + M.adjust_confusion_up_to(10 SECONDS, 20 SECONDS) + M.adjust_dizzy_up_to(20 SECONDS, 40 SECONDS) + M.adjust_drowsiness_up_to(20 SECONDS, 40 SECONDS) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 10 SECONDS) else M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) @@ -349,7 +345,7 @@ else if(M.flash_act()) to_chat(M, span_notice("Such a pretty light...")) - M.confused += min(M.confused + 4, 20) - M.dizziness += min(M.dizziness + 4, 20) - M.drowsyness += min(M.drowsyness + 4, 20) + M.adjust_confusion_up_to(4 SECONDS, 20 SECONDS) + M.adjust_dizzy_up_to(8 SECONDS, 40 SECONDS) + M.adjust_drowsiness_up_to(8 SECONDS, 40 SECONDS) M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index 2264b72a0459..6c1260c559b6 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -8,7 +8,7 @@ /obj/item/assembly/igniter/suicide_act(mob/living/carbon/user) user.visible_message(span_suicide("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")) - user.IgniteMob() + user.ignite_mob() return FIRELOSS /obj/item/assembly/igniter/Initialize() diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index 908d5d5ac71e..8b097bee28c5 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -223,3 +223,20 @@ return /obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) return + +/obj/item/assembly/signaler/internal + name = "internal remote signaling device" + +/obj/item/assembly/signaler/internal/ui_state(mob/user) + return GLOB.inventory_state + +/obj/item/assembly/signaler/internal/attackby(obj/item/W, mob/user, params) + return + +/obj/item/assembly/signaler/internal/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/item/assembly/signaler/internal/can_interact(mob/user) + if(ispAI(user)) + return TRUE + . = ..() diff --git a/code/modules/asset_cache/asset_cache_client.dm b/code/modules/asset_cache/asset_cache_client.dm index ec7afd591471..3cff8cb41f3e 100644 --- a/code/modules/asset_cache/asset_cache_client.dm +++ b/code/modules/asset_cache/asset_cache_client.dm @@ -42,4 +42,4 @@ stoplag(1) // Lock up the caller until this is received. t++ if (t < timeout_time) - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm index 059ebaebca8d..ad4596bdedc8 100644 --- a/code/modules/asset_cache/asset_cache_item.dm +++ b/code/modules/asset_cache/asset_cache_item.dm @@ -4,9 +4,13 @@ * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed. */ /datum/asset_cache_item + /// the name of this asset item, becomes the key in SSassets.cache list var/name + /// md5() of the file this asset item represents. var/hash + /// the file this asset represents var/resource + /// our file extension e.g. .png, .gif, etc var/ext = "" /// Should this file also be sent via the legacy browse_rsc system /// when cdn transports are enabled? @@ -21,11 +25,20 @@ /// TRUE for keeping local asset names when browse_rsc backend is used var/keep_local_name = FALSE -/datum/asset_cache_item/New(name, file) +///pass in a valid file_hash if you have one to save it from needing to do it again. +///pass in a valid dmi file path string e.g. "icons/path/to/dmi_file.dmi" to make generating the hash less expensive +/datum/asset_cache_item/New(name, file, file_hash, dmi_file_path) if (!isfile(file)) file = fcopy_rsc(file) - - hash = md5asfile(file) //icons sent to the rsc sometimes md5 incorrectly + + hash = file_hash + + //the given file is directly from a dmi file and is thus in the rsc already, we know that its file_hash will be correct + if(!hash) + if(dmi_file_path) + hash = md5(file) + else + hash = md5asfile(file) //icons sent to the rsc md5 incorrectly when theyre given incorrect data if (!hash) CRASH("invalid asset sent to asset cache") src.name = name diff --git a/code/modules/asset_cache/assets/contracts.dm b/code/modules/asset_cache/assets/contracts.dm new file mode 100644 index 000000000000..1229fe2d550e --- /dev/null +++ b/code/modules/asset_cache/assets/contracts.dm @@ -0,0 +1,7 @@ +/datum/asset/simple/contracts + assets = list( + "bluespace.png" = 'icons/UI_Icons/antags/contracts/bluespace.png', + "destruction.png" = 'icons/UI_Icons/antags/contracts/destruction.png', + "healing.png" = 'icons/UI_Icons/antags/contracts/healing.png', + "robeless.png" = 'icons/UI_Icons/antags/contracts/robeless.png', + ) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index d9d33a4d6745..a6346c3ff94e 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -974,12 +974,12 @@ nobliumformation = 1001 if (location) radiation_pulse(location, consumed_amount * 2, 2.5, TRUE, FALSE) for(var/mob/living/carbon/L in location) - L.hallucination += (energy_released * 0.7) // Yogs -- fixed accidental "path * number" - - var/new_heat_capacity = air.heat_capacity() - if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) - air.set_temperature(max((old_temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) - + L.adjust_hallucinations(air.get_moles(/datum/gas/bz) * 0.7) // Yogs -- fixed accidental "path * number" + energy_released += 100 + if(energy_released) + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.set_temperature(max((old_temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB)) return REACTING /datum/gas_reaction/pluonium_tritium_response diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 44cbca225bc0..ce6d38b8a42d 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -313,7 +313,7 @@ GLOBAL_LIST_EMPTY(pipeimages) user.forceMove(target_move.loc) //handle entering and so on. user.visible_message(span_notice("You hear something squeezing through the ducts..."), "You climb out the ventilation system.") else - var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() + var/list/pipenetdiff = return_pipenets() ^ target_move.return_pipenets() if(pipenetdiff.len) user.update_pipe_vision(target_move) user.forceMove(target_move) @@ -337,7 +337,7 @@ GLOBAL_LIST_EMPTY(pipeimages) /obj/machinery/atmospherics/proc/can_crawl_through() return TRUE -/obj/machinery/atmospherics/proc/returnPipenets() +/obj/machinery/atmospherics/proc/return_pipenets() return list() /obj/machinery/atmospherics/update_remote_sight(mob/user) diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm index 5c0b875c2a45..184258760675 100644 --- a/code/modules/atmospherics/machinery/components/components_base.dm +++ b/code/modules/atmospherics/machinery/components/components_base.dm @@ -143,7 +143,7 @@ else parent.update = TRUE -/obj/machinery/atmospherics/components/returnPipenets() +/obj/machinery/atmospherics/components/return_pipenets() . = list() for(var/i in 1 to device_type) . += returnPipenet(nodes[i]) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm index 035c40c4d3cb..d3c98bc979c2 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm @@ -568,8 +568,8 @@ continue var/distance_root = sqrt(1 / max(1, get_dist(human, src))) - human.hallucination += strength * distance_root * delta_time - human.hallucination = clamp(human.hallucination, 0, 200) + human.adjust_hallucinations(strength * distance_root * delta_time) + human.set_hallucinations_if_lower(20 SECONDS) /** * Emit radiation diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index 50c7d1ca7fe3..2836c6199e60 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -202,9 +202,8 @@ /datum/pipeline/proc/return_air() . = other_airs + air - if(null in .) + if(listclearnulls(.)) stack_trace("[src] has one or more null gas mixtures, which may cause bugs. Null mixtures will not be considered in reconcile_air().") - return removeNullsFromList(.) /datum/pipeline/proc/reconcile_air() var/list/datum/gas_mixture/GL = list() diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm index 57c24b1bb94a..2d9dfc0b4a13 100644 --- a/code/modules/atmospherics/machinery/pipes/pipes.dm +++ b/code/modules/atmospherics/machinery/pipes/pipes.dm @@ -111,7 +111,7 @@ var/obj/machinery/atmospherics/N = nodes[i] N.update_icon() -/obj/machinery/atmospherics/pipe/returnPipenets() +/obj/machinery/atmospherics/pipe/return_pipenets() . = list(parent) /obj/machinery/atmospherics/pipe/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) diff --git a/code/modules/awaymissions/away_props.dm b/code/modules/awaymissions/away_props.dm index 78c24a665428..e3143445d787 100644 --- a/code/modules/awaymissions/away_props.dm +++ b/code/modules/awaymissions/away_props.dm @@ -48,7 +48,7 @@ /obj/effect/path_blocker/CanAllowThrough(atom/movable/mover, turf/target) . = ..() if(blocked_types.len) - var/list/mover_contents = mover.GetAllContents() + var/list/mover_contents = mover.get_all_contents() for(var/atom/movable/thing in mover_contents) if(blocked_types[thing.type]) return reverse diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm index c51d79d7f263..1646f7671e60 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -35,23 +35,24 @@ /turf/cordon/ScrapeAway(amount, flags) return src // :devilcat: -/turf/cordon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) +/turf/cordon/bullet_act(obj/item/projectile/hitting_projectile, def_zone, piercing_hit) return BULLET_ACT_HIT /turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover) return FALSE -/area/cordon +/// Area used in conjuction with the cordon turf to create a fully functioning world border. +/area/misc/cordon name = "CORDON" icon_state = "cordon" - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED unique = TRUE noteleport = TRUE hidden = TRUE + area_flags = NOTELEPORT requires_power = FALSE -/area/cordon/Entered(atom/movable/arrived, area/old_area) +/area/misc/cordon/Entered(atom/movable/arrived, area/old_area) . = ..() for(var/mob/living/enterer as anything in arrived.get_all_contents_type(/mob/living)) to_chat(enterer, span_userdanger("This was a bad idea...")) diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index d826bb8a9909..38ebc62e93b6 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -314,7 +314,7 @@ //Random One-use spellbook T.visible_message(span_userdanger("A magical looking book drops to the floor!")) do_smoke(0, drop_location()) - new /obj/item/book/granter/spell/random(drop_location()) + new /obj/item/book/granter/action/spell/random(drop_location()) if(16) //Servant & Servant Summon T.visible_message(span_userdanger("A Dice Servant appears in a cloud of smoke!")) @@ -334,9 +334,8 @@ message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") H.key = C.key - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new - S.target_mob = H - user.mind.AddSpell(S) + var/datum/action/cooldown/spell/summon_mob/summon_servant = new(user.mind || user, H) + summon_servant.Grant(user) if(17) //Tator Kit @@ -367,31 +366,45 @@ glasses = /obj/item/clothing/glasses/monocle gloves = /obj/item/clothing/gloves/color/white -/obj/effect/proc_holder/spell/targeted/summonmob +/datum/action/cooldown/spell/summon_mob name = "Summon Servant" desc = "This spell can be used to call your servant, whenever you need it." - charge_max = 100 - clothes_req = 0 + + school = SCHOOL_CONJURATION + cooldown_time = 10 SECONDS invocation = "JE VES" - invocation_type = SPELL_INVOCATION_WHISPER - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = 1 + invocation_type = INVOCATION_WHISPER + + spell_requirements = NONE + spell_max_level = 0 //cannot be improved + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + + var/datum/weakref/summon_weakref - var/mob/living/target_mob + background_icon = 'icons/mob/actions/humble/actions_humble.dmi' + button_icon = "summon_servant" - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "summon_servant" +/datum/action/cooldown/spell/summon_mob/New(Target, mob/living/summoned_mob) + . = ..() + if(summoned_mob) + summon_weakref = WEAKREF(summoned_mob) -/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) - if(!target_mob) +/datum/action/cooldown/spell/summon_mob/cast(atom/cast_on) + . = ..() + var/mob/living/to_summon = summon_weakref?.resolve() + if(QDELETED(to_summon)) + to_chat(cast_on, span_warning("You can't seem to summon your servant - it seems they've vanished from reality, or never existed in the first place...")) return - var/turf/Start = get_turf(user) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(Start,direction) - if(!T.density) - target_mob.Move(T) + + do_teleport( + to_summon, + get_turf(cast_on), + precision = 1, + asoundin = 'sound/magic/wand_teleport.ogg', + asoundout = 'sound/magic/wand_teleport.ogg', + channel = TELEPORT_CHANNEL_MAGIC, + ) /obj/structure/ladder/unbreakable/rune name = "\improper Teleportation Rune" diff --git a/code/modules/awaymissions/mission_code/mining.dm b/code/modules/awaymissions/mission_code/mining.dm index d2ef68361c0b..fab15e64e147 100644 --- a/code/modules/awaymissions/mission_code/mining.dm +++ b/code/modules/awaymissions/mission_code/mining.dm @@ -13,7 +13,7 @@ mask = /obj/item/clothing/mask/gas/explorer glasses = /obj/item/clothing/glasses/hud/health suit_store = /obj/item/tank/internals/oxygen - internals_slot = SLOT_S_STORE + internals_slot = SLOT_SUIT_STORE backpack_contents = list( /obj/item/gun/energy/kinetic_accelerator=2) @@ -197,7 +197,7 @@ /obj/item/reagent_containers/glass/bottle/potion/flight=1, /obj/item/organ/heart/cursed/wizard=1, /obj/item/immortality_talisman=1, - /obj/item/book/granter/spell/summonitem=1, + /obj/item/book/granter/action/spell/summonitem=1, /obj/item/extinguisher=1) /datum/component/spawner/megafauna diff --git a/code/modules/awaymissions/mission_code/runner.dm b/code/modules/awaymissions/mission_code/runner.dm index 074340eff356..e881112a4cb7 100644 --- a/code/modules/awaymissions/mission_code/runner.dm +++ b/code/modules/awaymissions/mission_code/runner.dm @@ -103,8 +103,8 @@ GLOBAL_LIST_EMPTY(vr_runner_tiles) if(locate(A) in GLOB.vr_runner_players) if(!A.throwing) var/mob/living/carbon/human/H = A - var/obj/effect/proc_holder/spell/portal_recall/findspell = locate(/obj/effect/proc_holder/spell/portal_recall) in H.mind.spell_list - findspell.Click(H) + var/datum/action/cooldown/spell/portal_recall/findspell = locate(/datum/action/cooldown/spell/portal_recall) in H.actions + findspell.Trigger() else qdel(A) else if(!not_reset) // make sure it's not already currently falling @@ -119,9 +119,9 @@ GLOBAL_LIST_EMPTY(vr_runner_tiles) not_reset = FALSE color = COLOR_ALMOST_BLACK for(var/mob/living/carbon/human/H in contents) - var/obj/effect/proc_holder/spell/portal_recall/findspell = locate(/obj/effect/proc_holder/spell/portal_recall) in H.mind.spell_list + var/datum/action/cooldown/spell/portal_recall/findspell = locate(/datum/action/cooldown/spell/portal_recall) in H.actions if(H) - findspell.Click(H) + findspell.Trigger() /turf/open/indestructible/runner/proc/reset_fall() not_reset = FALSE diff --git a/code/modules/awaymissions/mission_code/snowdin.dm b/code/modules/awaymissions/mission_code/snowdin.dm index cff787e7edd2..9ab8b62ffc00 100644 --- a/code/modules/awaymissions/mission_code/snowdin.dm +++ b/code/modules/awaymissions/mission_code/snowdin.dm @@ -241,7 +241,7 @@ PP.visible_message(span_warning("[L] screams in pain as [L.p_their()] [NB] melts down to the bone!"), \ span_userdanger("You scream out in pain as your [NB] melts down to the bone, leaving an eerie plasma-like glow where flesh used to be!")) if(!plasma_parts.len && !robo_parts.len) //a person with no potential organic limbs left AND no robotic limbs, time to turn them into a plasmaman - PP.IgniteMob() + PP.ignite_mob() PP.set_species(/datum/species/plasmaman) PP.visible_message(span_warning("[L] bursts into a brilliant purple flame as [L.p_their()] entire body is that of a skeleton!"), \ span_userdanger("Your senses numb as all of your remaining flesh is turned into a purple slurry, sloshing off your body and leaving only your bones to show in a vibrant purple!")) @@ -473,8 +473,8 @@ name = "dungeon lite" loot = list(/obj/item/melee/classic_baton = 11, /obj/item/melee/classic_baton/telescopic = 12, - /obj/item/book/granter/spell/smoke = 10, - /obj/item/book/granter/spell/blind = 10, + /obj/item/book/granter/action/spell/smoke = 10, + /obj/item/book/granter/action/spell/blind = 10, /obj/item/storage/firstaid/regular = 45, /obj/item/storage/firstaid/toxin = 35, /obj/item/storage/firstaid/brute = 27, @@ -497,9 +497,9 @@ /obj/item/gun/magic/wand/fireball/inert = 3, /obj/item/pneumatic_cannon = 15, /obj/item/melee/transforming/energy/sword = 7, - /obj/item/book/granter/spell/knock = 15, - /obj/item/book/granter/spell/summonitem = 20, - /obj/item/book/granter/spell/forcewall = 17, + /obj/item/book/granter/action/spell/knock = 15, + /obj/item/book/granter/action/spell/summonitem = 20, + /obj/item/book/granter/action/spell/forcewall = 17, /obj/item/storage/backpack/holding = 12, /obj/item/grenade/spawnergrenade/manhacks = 6, /obj/item/grenade/spawnergrenade/spesscarp = 7, @@ -508,7 +508,7 @@ /obj/item/stack/sheet/mineral/uranium{amount = 15} = 10, /obj/item/stack/sheet/mineral/plasma{amount = 15} = 10, /obj/item/stack/sheet/mineral/gold{amount = 15} = 10, - /obj/item/book/granter/spell/barnyard = 4, + /obj/item/book/granter/action/spell/barnyard = 4, /obj/item/pickaxe/drill/diamonddrill = 6, /obj/item/borg/upgrade/vtec = 7, /obj/item/borg/upgrade/disablercooler = 7) @@ -526,9 +526,9 @@ /obj/item/gun/magic/wand/resurrection/inert = 15, /obj/item/gun/magic/wand/resurrection = 10, /obj/item/uplink/old = 2, - /obj/item/book/granter/spell/charge = 12, + /obj/item/book/granter/action/spell/charge = 12, /obj/item/grenade/clusterbuster/syndie/spawner_manhacks = 15, - /obj/item/book/granter/spell/fireball = 10, + /obj/item/book/granter/action/spell/fireball = 10, /obj/item/pickaxe/drill/jackhammer = 30, /obj/item/borg/upgrade/syndicate = 13, /obj/item/borg/upgrade/selfrepair = 17) diff --git a/code/modules/awaymissions/mission_code/vrhub.dm b/code/modules/awaymissions/mission_code/vrhub.dm index dae872f5e949..683193d39227 100644 --- a/code/modules/awaymissions/mission_code/vrhub.dm +++ b/code/modules/awaymissions/mission_code/vrhub.dm @@ -28,9 +28,9 @@ /obj/effect/portal/permanent/one_way/recall/Crossed(atom/movable/AM, oldloc) if(ismob(AM)) var/mob/user = AM - var/check = locate(/obj/effect/proc_holder/spell/portal_recall) in user.mind.spell_list + var/check = locate(/datum/action/cooldown/spell/portal_recall) in user.actions if(check) - var/obj/effect/proc_holder/spell/portal_recall/mob_recall = check + var/datum/action/cooldown/spell/portal_recall/mob_recall = check for(var/obj/effect/portal/permanent/one_way/recall/P in mob_recall.recall_portals) if(src == P) return ..(AM, oldloc, force_stop = TRUE) // don't teleport if they have a recall spell with this portal already (or have just teleported onto it) @@ -40,11 +40,11 @@ . = ..() if(. && ismob(M)) var/mob/user = M - var/findspell = locate(/obj/effect/proc_holder/spell/portal_recall) in user.mind.spell_list - var/obj/effect/proc_holder/spell/portal_recall/personal_recall = findspell ? findspell : new + var/findspell = locate(/datum/action/cooldown/spell/portal_recall) in user.actions + var/datum/action/cooldown/spell/portal_recall/personal_recall = findspell ? findspell : new personal_recall.recall_portals += src if(!findspell) - user.mind.AddSpell(personal_recall) + personal_recall.Grant(user) if(equipment && ishuman(user)) var/mob/living/carbon/human/H = user H.delete_equipment() @@ -54,29 +54,32 @@ /obj/effect/portal/permanent/one_way/recall/proc/recall_effect(mob/user) return -/obj/effect/proc_holder/spell/portal_recall +/datum/action/cooldown/spell/portal_recall name = "Portal Recall" desc = "This will teleport you back to your previously used portal. One use only." - clothes_req = FALSE - action_icon_state = "blink" + button_icon_state = "blink" + spell_requirements = NONE var/list/recall_portals = list() -/obj/effect/proc_holder/spell/portal_recall/Click(mob/user = usr) +/datum/action/cooldown/spell/portal_recall/Trigger() + . = ..() + if(!.) + return FALSE if(!recall_portals.len) - user.mind.RemoveSpell(src) // remove spell if no portals left + Remove(owner)// remove spell if no portals left var/obj/effect/portal/permanent/one_way/recall/last_portal = recall_portals[recall_portals.len] var/turf/recall_turf = get_turf(last_portal) if(recall_turf) - if(last_portal.recall_equipment && ishuman(user)) - var/mob/living/carbon/human/H = user + if(last_portal.recall_equipment && ishuman(owner)) + var/mob/living/carbon/human/H = owner H.delete_equipment() H.equipOutfit(last_portal.recall_equipment) - last_portal.recall_effect(user) - if(user) - do_teleport(user, recall_turf, 0, no_effects = FALSE, channel = TELEPORT_CHANNEL_BLUESPACE) + last_portal.recall_effect(owner) + if(owner) + do_teleport(owner, recall_turf, 0, no_effects = FALSE, channel = TELEPORT_CHANNEL_BLUESPACE) recall_portals -= last_portal if(!recall_portals.len) - user.mind.RemoveSpell(src) // remove spell if no portals left + Remove(owner) /obj/effect/mob_spawn/human/virtual_reality name = "Network Relay" @@ -153,4 +156,4 @@ desc = "A wall with rough metal plating." icon = 'icons/turf/walls/iron_wall.dmi' icon_state = "iron" - canSmoothWith = list(/turf/closed/indestructible/iron) \ No newline at end of file + canSmoothWith = list(/turf/closed/indestructible/iron) diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm index bf465ee091d1..cd4b156ad934 100644 --- a/code/modules/cargo/bounty.dm +++ b/code/modules/cargo/bounty.dm @@ -62,7 +62,7 @@ GLOBAL_LIST_EMPTY(bounties_list_syndicate) setup_syndicate_bounties() var/list/matched_one = FALSE - for(var/thing in reverseRange(AM.GetAllContents())) + for(var/thing in reverse_range(AM.get_all_contents())) var/matched_this = FALSE for(var/list/i in list(GLOB.bounties_list,GLOB.bounties_list_syndicate)) for(var/datum/bounty/B in i) diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 2b5c5b74fed2..4ceb0113c062 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -348,7 +348,7 @@ if (temp_pod.effectShrapnel == TRUE) //If already doing custom damage, set back to default (no shrapnel) temp_pod.effectShrapnel = FALSE return - var/shrapnelInput = input("Please enter the type of pellet cloud you'd like to create on landing (Can be any projectile!)", "Projectile Typepath", 0) in sortList(subtypesof(/obj/projectile), /proc/cmp_typepaths_asc) + var/shrapnelInput = input("Please enter the type of pellet cloud you'd like to create on landing (Can be any projectile!)", "Projectile Typepath", 0) in sortList(subtypesof(/obj/item/projectile), /proc/cmp_typepaths_asc) if (isnull(shrapnelInput)) return var/shrapnelMagnitude = input("Enter the magnitude of the pellet cloud. This is usually a value around 1-5. Please note that Ryll-Ryll has asked me to tell you that if you go too crazy with the projectiles you might crash the server. So uh, be gentle!", "Shrapnel Magnitude", 0) as null|num @@ -774,9 +774,9 @@ selector.moveToNullspace() //Otherwise, we move the selector to nullspace until it is needed again /datum/centcom_podlauncher/proc/clearBay() //Clear all objs and mobs from the selected bay - for (var/obj/O in bay.GetAllContents()) + for (var/obj/O in bay.get_all_contents()) qdel(O) - for (var/mob/M in bay.GetAllContents()) + for (var/mob/M in bay.get_all_contents()) qdel(M) for (var/bayturf in bay) var/turf/turf_to_clear = bayturf diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm index a20dab13ca8f..d0c9d54233b7 100644 --- a/code/modules/cargo/exports.dm +++ b/code/modules/cargo/exports.dm @@ -32,14 +32,14 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they if(!GLOB.exports_list.len) setupExports() - var/list/contents = AM.GetAllContents() + var/list/contents = AM.get_all_contents() var/datum/export_report/report = external_report if(!report) //If we don't have any longer transaction going on report = new // We go backwards, so it'll be innermost objects sold first - for(var/i in reverseRange(contents)) + for(var/i in reverse_range(contents)) var/atom/movable/thing = i var/obj/item/thingy = thing var/sold = FALSE diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 730a0cd4cd9f..a66293a499da 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -47,7 +47,7 @@ var/stay_after_drop = FALSE var/specialised = FALSE // It's not a general use pod for cargo/admin use var/effectShrapnel = FALSE - //var/shrapnel_type = /obj/projectile/bullet/shrapnel + //var/shrapnel_type = /obj/item/projectile/bullet/shrapnel var/shrapnel_magnitude = 3 var/rubble_type //Rubble effect associated with this supplypod var/decal = "default" //What kind of extra decals we add to the pod to make it look nice diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 53b2672e73c6..31cf29b4876b 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -12,6 +12,8 @@ var/datum/admins/holder = null ///Needs to implement InterceptClickOn(user,params,atom) proc var/datum/click_intercept = null + ///Time when the click was intercepted + var/click_intercept_time = 0 ///Used for admin AI interaction var/AI_Interact = FALSE @@ -81,6 +83,8 @@ var/mouse_up_icon = null ///used to make a special mouse cursor, this one for mouse up icon var/mouse_down_icon = null + ///used to override the mouse cursor so it doesnt get reset + var/mouse_override_icon = null ///Used for ip intel checking to identify evaders, disabled because of issues with traffic var/ip_intel = "Disabled" @@ -167,3 +171,6 @@ /// Whether or not this client has standard hotkeys enabled var/hotkeys = TRUE + + /// Whether or not this client has the combo HUD enabled + var/combo_hud_enabled = FALSE diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index fc57070b9d66..d8e1f3ba7e97 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -417,7 +417,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( send_resources() - generate_clickcatcher() apply_clickcatcher() if(prefs.lastchangelog != GLOB.changelog_hash) //bolds the changelog button on the interface so we know there are updates. @@ -836,6 +835,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( ip_intel = res.intel /client/Click(atom/object, atom/location, control, params) + if(click_intercept_time) + if(click_intercept_time >= world.time) + click_intercept_time = 0 //Reset and return. Next click should work, but not this one. + return + click_intercept_time = 0 //Just reset. Let's not keep re-checking forever. + if(src.afreeze) to_chat(src, span_userdanger("You have been frozen by an administrator.")) return @@ -847,7 +852,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(dragged && !L[dragged]) return - if (object && object == middragatom && L["left"]) + if (object && IS_WEAKREF_OF(object, middle_drag_atom_ref) && LAZYACCESS(L, LEFT_CLICK)) ab = max(0, 5 SECONDS-(world.time-middragtime)*0.1) var/mcl = CONFIG_GET(number/minute_click_limit) @@ -896,6 +901,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED]") + SEND_SIGNAL(src, COMSIG_CLIENT_CLICK, object, location, control, params, usr) + ..() /client/proc/add_verbs_from_config() @@ -1036,6 +1043,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( /client/proc/generate_clickcatcher() if(!void) void = new() + if(!(void in screen)) screen += void /client/proc/apply_clickcatcher() diff --git a/code/modules/client/preferences/buttons_locked.dm b/code/modules/client/preferences/buttons_locked.dm deleted file mode 100644 index b4f54ff10f8f..000000000000 --- a/code/modules/client/preferences/buttons_locked.dm +++ /dev/null @@ -1,5 +0,0 @@ -/datum/preference/toggle/buttons_locked - category = PREFERENCE_CATEGORY_GAME_PREFERENCES - savefile_key = "buttons_locked" - savefile_identifier = PREFERENCE_PLAYER - default_value = FALSE diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm index a89060efd8ef..64e145347c92 100644 --- a/code/modules/client/verbs/suicide.dm +++ b/code/modules/client/verbs/suicide.dm @@ -42,7 +42,7 @@ set_suicide(TRUE) //need to be called before calling suicide_act as fuck knows what suicide_act will do with your suicider var/obj/item/held_item = get_active_held_item() if(held_item) - var/damagetype = held_item.suicide_act(src) + var/damagetype = SEND_SIGNAL(src, COMSIG_HUMAN_SUICIDE_ACT) || held_item?.suicide_act(src) if(damagetype) if(damagetype & SHAME) adjustStaminaLoss(200) diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index b66f4b949198..0818c684de45 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -2,11 +2,11 @@ /datum/action/item_action/chameleon/drone/randomise name = "Randomise Headgear" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "random" /datum/action/item_action/chameleon/drone/randomise/Trigger() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return // Damn our lack of abstract interfeces @@ -22,7 +22,7 @@ /datum/action/item_action/chameleon/drone/togglehatmask name = "Toggle Headgear Mode" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' /datum/action/item_action/chameleon/drone/togglehatmask/New() ..() @@ -33,7 +33,7 @@ button_icon_state = "drone_camogear_mask" /datum/action/item_action/chameleon/drone/togglehatmask/Trigger() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return // No point making the code more complicated if no non-drone @@ -72,20 +72,26 @@ name = "Select Chameleon Outfit" button_icon_state = "chameleon_outfit" var/list/outfit_options //By default, this list is shared between all instances. It is not static because if it were, subtypes would not be able to have their own. If you ever want to edit it, copy it first. - syndicate = TRUE - + var/syndicate = FALSE + /datum/action/chameleon_outfit/New() ..() initialize_outfits() +/datum/action/chameleon_outfit/IsAvailable(feedback = FALSE) + if(syndicate) + if(!is_syndicate(owner)) + HideFrom(owner) + return is_syndicate(owner) + return ..() + /datum/action/chameleon_outfit/proc/initialize_outfits() var/static/list/standard_outfit_options if(!standard_outfit_options) standard_outfit_options = list() - for(var/path in subtypesof(/datum/outfit/job)) - var/datum/outfit/O = path + for(var/datum/outfit/O as anything in subtypesof(/datum/outfit/job)) if(initial(O.can_be_admin_equipped)) - standard_outfit_options[initial(O.name)] = path + standard_outfit_options[initial(O.name)] = O sortTim(standard_outfit_options, /proc/cmp_text_asc) outfit_options = standard_outfit_options @@ -93,10 +99,10 @@ return select_outfit(owner) /datum/action/chameleon_outfit/proc/select_outfit(mob/user) - if(!user || !IsAvailable()) + if(!user || !IsAvailable(feedback = FALSE)) return FALSE - var/selected = input("Select outfit to change into", "Chameleon Outfit") as null|anything in outfit_options - if(!IsAvailable() || QDELETED(src) || QDELETED(user)) + var/selected = tgui_input_list(user, "Select outfit to change into", "Chameleon Outfit", outfit_options) + if(!IsAvailable(feedback = FALSE) || QDELETED(src) || QDELETED(user)) return FALSE var/outfit_type = outfit_options[selected] if(!outfit_type) @@ -144,6 +150,7 @@ var/chameleon_name = "Item" var/emp_timer var/current_disguise = null + var/syndicate = FALSE /datum/action/item_action/chameleon/change/Grant(mob/M) if(M && (owner != M)) @@ -164,8 +171,8 @@ ..() /datum/action/item_action/chameleon/change/proc/initialize_disguises() - if(button) - button.name = "Change [chameleon_name] Appearance" + name = "Change [chameleon_name] Appearance" + build_all_button_icons() chameleon_blacklist |= typecacheof(target.type) for(var/V in typesof(chameleon_type)) @@ -211,7 +218,7 @@ update_item(picked_item) var/obj/item/thing = target thing.update_slot_icon() - UpdateButtonIcon() + build_all_button_icons() /datum/action/item_action/chameleon/change/proc/update_item(obj/item/picked_item, obj/item/target = src.target) //yogs -- add support for cham hardsuits target.name = initial(picked_item.name) @@ -244,7 +251,7 @@ current_disguise = picked_item /datum/action/item_action/chameleon/change/Trigger() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return select_look(owner) @@ -296,6 +303,7 @@ chameleon_action.chameleon_name = "Jumpsuit" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/under, /obj/item/clothing/under/color, /obj/item/clothing/under/rank, /obj/item/clothing/under/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/under/chameleon/emp_act(severity) . = ..() @@ -329,6 +337,7 @@ chameleon_action.chameleon_name = "Jumpsuit" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/under, /obj/item/clothing/under/color, /obj/item/clothing/under/rank, /obj/item/clothing/under/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/under/chameleon/emp_act(severity) . = ..() @@ -360,6 +369,7 @@ chameleon_action.chameleon_name = "Suit" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/suit/armor/abductor, /obj/item/clothing/suit/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/suit/chameleon/emp_act(severity) . = ..() @@ -393,6 +403,7 @@ chameleon_action.chameleon_name = "Glasses" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/glasses/chameleon/emp_act(severity) . = ..() @@ -427,6 +438,7 @@ chameleon_action.chameleon_name = "Gloves" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/gloves, /obj/item/clothing/gloves/color, /obj/item/clothing/gloves/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/gloves/chameleon/emp_act(severity) . = ..() @@ -460,6 +472,7 @@ chameleon_action.chameleon_name = "Hat" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/head/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/head/chameleon/emp_act(severity) . = ..() @@ -492,6 +505,7 @@ chameleon_action.chameleon_name = "Hat" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/head/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/head/helmet/space/plasmaman/chameleon/emp_act(severity) . = ..() @@ -510,9 +524,9 @@ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) chameleon_action.random_look() var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src) - togglehatmask_action.UpdateButtonIcon() + togglehatmask_action.build_all_button_icons() var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.UpdateButtonIcon() + randomise_action.build_all_button_icons() /obj/item/clothing/mask/chameleon name = "gas mask" @@ -543,6 +557,7 @@ chameleon_action.chameleon_name = "Mask" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/mask/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/mask/chameleon/emp_act(severity) . = ..() @@ -574,9 +589,9 @@ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) chameleon_action.random_look() var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src) - togglehatmask_action.UpdateButtonIcon() + togglehatmask_action.build_all_button_icons() var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.UpdateButtonIcon() + randomise_action.build_all_button_icons() /obj/item/clothing/mask/chameleon/drone/attack_self(mob/user) to_chat(user, span_notice("[src] does not have a voice changer.")) @@ -604,6 +619,7 @@ chameleon_action.chameleon_name = "Shoes" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/shoes/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/shoes/chameleon/emp_act(severity) . = ..() @@ -640,6 +656,7 @@ chameleon_action.chameleon_type = /obj/item/storage/backpack chameleon_action.chameleon_name = "Backpack" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/storage/backpack/chameleon/emp_act(severity) . = ..() @@ -668,6 +685,7 @@ chameleon_action.chameleon_type = /obj/item/storage/belt chameleon_action.chameleon_name = "Belt" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/storage/belt/chameleon/ComponentInitialize() . = ..() @@ -699,6 +717,7 @@ chameleon_action.chameleon_type = /obj/item/radio/headset chameleon_action.chameleon_name = "Headset" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/radio/headset/chameleon/emp_act(severity) . = ..() @@ -726,6 +745,7 @@ chameleon_action.chameleon_name = "PDA" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/pda/heads, /obj/item/pda/ai, /obj/item/pda/ai/pai), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/pda/chameleon/emp_act(severity) . = ..() @@ -755,6 +775,7 @@ chameleon_action.chameleon_type = /obj/item/stamp chameleon_action.chameleon_name = "Stamp" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/stamp/chameleon/broken/Initialize() . = ..() diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 413cf18de222..4f4b5f312f71 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -475,7 +475,7 @@ BLIND // can't see anything C.head_update(src, forced = 1) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() return TRUE /obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 7c6dd7ce2627..1e3e0d248d91 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -396,6 +396,7 @@ chameleon_action.chameleon_name = "Glasses" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/glasses/thermal/syndi/emp_act(severity) . = ..() @@ -459,31 +460,35 @@ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE resistance_flags = LAVA_PROOF | FIRE_PROOF clothing_flags = SCAN_REAGENTS + var/datum/action/cooldown/expose/expose_ability var/hud_type = DATA_HUD_MEDICAL_ADVANCED - var/obj/effect/proc_holder/expose/expose_ability /obj/item/clothing/glasses/godeye/Initialize() . = ..() - expose_ability = new(expose_ability) + expose_ability = new(src) + +/obj/item/clothing/glasses/godeye/Destroy() + QDEL_NULL(expose_ability) + return ..() /obj/item/clothing/glasses/godeye/equipped(mob/living/carbon/human/user, slot) . = ..() if(ishuman(user) && slot == ITEM_SLOT_EYES) ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) - user.AddAbility(expose_ability) + expose_ability.Grant(user) if(hud_type) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) /obj/item/clothing/glasses/godeye/dropped(mob/living/carbon/human/user) . = ..() // Behead someone, their "glasses" drop on the floor // and thus, the god eye should no longer be sticky REMOVE_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) - user.RemoveAbility(expose_ability) + expose_ability.Remove(user) if(hud_type && istype(user) && user.glasses == src) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) /obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) if(istype(W, src) && W != src && W.loc == user) @@ -497,59 +502,43 @@ qdel(src) ..() -/obj/effect/proc_holder/expose +/datum/action/cooldown/expose name = "Expose" desc = "Expose an enemy, increasing all damage dealt to them by 15% for 10 seconds, effect is magnified on megafauna." - action_background_icon_state = "bg_demon" - action_icon = 'icons/mob/actions/actions_items.dmi' - action_icon_state = "expose" - ranged_mousepointer = 'icons/effects/mouse_pointers/expose_target.dmi' - var/cooldown_time = 1 MINUTES - COOLDOWN_DECLARE(scan_cooldown) - -/obj/effect/proc_holder/expose/on_lose(mob/living/user) - remove_ranged_ability() + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "expose" -/obj/effect/proc_holder/expose/Click(location, control, params) - . = ..() - if(!isliving(usr)) - return TRUE - var/mob/living/user = usr - fire(user) - -/obj/effect/proc_holder/expose/fire(mob/living/carbon/user) - if(active) - remove_ranged_ability(span_notice("You relax your god eye.")) - else - add_ranged_ability(user, span_notice("You squint your god eye. Left-click a creature to expose it!"), TRUE) + click_to_activate = TRUE + cooldown_time = 45 SECONDS + ranged_mousepointer = 'icons/effects/mouse_pointers/expose_target.dmi' -/obj/effect/proc_holder/expose/InterceptClickOn(mob/living/caller, params, atom/target) - . = ..() - if(.) - return - if(ranged_ability_user.stat) - remove_ranged_ability() - return - if(!COOLDOWN_FINISHED(src, scan_cooldown)) - to_chat(ranged_ability_user, span_warning("You try to focus your god eye, but it's too tired. Give it some time to recharge!")) - return - if(!isliving(target) || target == ranged_ability_user) - return - var/mob/living/living_target = target - living_target.apply_status_effect(STATUS_EFFECT_EXPOSED) - living_target.Jitter(5) - to_chat(living_target, span_warning("You feel the gaze of a malevolent presence focus on you!")) - ranged_ability_user.playsound_local(get_turf(ranged_ability_user), 'sound/magic/smoke.ogg', 50, TRUE) - living_target.playsound_local(get_turf(living_target), 'sound/hallucinations/i_see_you1.ogg', 50, TRUE) - to_chat(ranged_ability_user, "You glare at [living_target], exposing them!") - COOLDOWN_START(src, scan_cooldown, cooldown_time) - addtimer(CALLBACK(src, PROC_REF(cooldown_over), ranged_ability_user), cooldown_time) - remove_ranged_ability() +/datum/action/cooldown/expose/IsAvailable(feedback = FALSE) + return ..() && isliving(owner) + +/datum/action/cooldown/expose/Activate(atom/exposed) + StartCooldown(15 SECONDS) + + if(owner.stat != CONSCIOUS) + return FALSE + if(!isliving(exposed) || exposed == owner) + owner.balloon_alert(owner, "invalid exposed!") + return FALSE + + var/mob/living/living_exposed = exposed + living_exposed.apply_status_effect(STATUS_EFFECT_EXPOSED) + living_exposed.adjust_jitter(5 SECONDS) + to_chat(living_exposed, span_warning("You feel the gaze of a malevolent presence focus on you!")) + owner.playsound_local(get_turf(owner), 'sound/magic/smoke.ogg', 50, TRUE) + + living_exposed.playsound_local(get_turf(living_exposed), 'sound/hallucinations/i_see_you1.ogg', 50, TRUE) + owner.balloon_alert(owner, "[living_exposed] exposed!") + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, balloon_alert), owner, "eye recharged"), cooldown_time) + + StartCooldown() return TRUE -/obj/effect/proc_holder/expose/proc/cooldown_over() - to_chat(usr, (span_notice("Your god eye is focused enough to expose again!"))) - /obj/item/clothing/glasses/AltClick(mob/user) if(glass_colour_type && ishuman(user)) var/mob/living/carbon/human/human_user = user diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm index 1b478834051b..d9e4b4733701 100644 --- a/code/modules/clothing/glasses/engine_goggles.dm +++ b/code/modules/clothing/glasses/engine_goggles.dm @@ -53,7 +53,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/glasses/meson/engine/attack_self(mob/user) toggle_mode(user, TRUE) diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 5210757be95a..0e402e3f71d4 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -8,13 +8,13 @@ ..() if(hud_type && slot == SLOT_GLASSES) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) /obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) ..() if(hud_type && istype(user) && user.glasses == src) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) /obj/item/clothing/glasses/hud/emp_act(severity) . = ..() @@ -123,6 +123,7 @@ chameleon_action.chameleon_name = "Glasses" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) . = ..() @@ -203,7 +204,7 @@ if (hud_type) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) if (hud_type == DATA_HUD_MEDICAL_ADVANCED) hud_type = null @@ -214,7 +215,10 @@ if (hud_type) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) + +/datum/action/item_action/switch_hud + name = "Switch HUD" /obj/item/clothing/glasses/hud/toggle/thermal name = "thermal HUD scanner" diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 84cc9bc291a3..12373ec95e72 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -141,7 +141,7 @@ desc = "Wristbands fashioned after one of the hungriest slaughter demons. Wearing these invokes a hunger in the wearer that can only be sated by bloodshed." icon_state = "cuff" item_state = "cuff" - var/obj/effect/proc_holder/swipe/swipe_ability + var/datum/action/cooldown/swipe/swipe_ability alternate_worn_layer = ABOVE_BODY_FRONT_LAYER /obj/item/clothing/gloves/bracer/cuffs/Initialize() @@ -151,52 +151,45 @@ /obj/item/clothing/gloves/bracer/cuffs/equipped(mob/living/user, slot) . = ..() if(ishuman(user) && slot == ITEM_SLOT_GLOVES) - user.AddAbility(swipe_ability) + swipe_ability = new(user) + swipe_ability.Grant(user) /obj/item/clothing/gloves/bracer/cuffs/dropped(mob/living/user) . = ..() - user.RemoveAbility(swipe_ability) + swipe_ability?.Remove(user) -obj/effect/proc_holder/swipe +/datum/action/cooldown/swipe //you stupid name = "Swipe" desc = "Swipe at a target area, dealing damage to heal yourself. Creatures take 60 damage while people and cyborgs take 20 damage. Living creatures hit with this ability will heal the user for 13 brute/burn/poison while dead ones heal for 20 and get butchered, while killing a creature with a swipe will heal the user for 33. People and cyborgs hit will heal for 5." - action_background_icon_state = "bg_demon" - action_icon = 'icons/mob/actions/actions_items.dmi' - action_icon_state = "cuff" + background_icon_state = "bg_demon" + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "cuff" ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi' - var/cooldown = 10 SECONDS - COOLDOWN_DECLARE(scan_cooldown) + cooldown_time = 10 SECONDS -/obj/effect/proc_holder/swipe/on_lose(mob/living/user) - remove_ranged_ability() - -/obj/effect/proc_holder/swipe/Click(location, control, params) - . = ..() - if(!isliving(usr)) - return TRUE - var/mob/living/user = usr - fire(user) +/datum/action/cooldown/swipe/Remove(mob/living/user) + unset_click_ability(user) + return ..() -/obj/effect/proc_holder/swipe/fire(mob/living/carbon/user) +/datum/action/cooldown/swipe/Trigger(mob/living/carbon/user) + if(!isliving(owner)) + return FALSE if(user.handcuffed) to_chat(user, span_danger("You can't attack while handcuffed!")) - return - if(active) - remove_ranged_ability(span_notice("You relax your arms.")) - else - add_ranged_ability(user, span_notice("You ready your cuffs. Left-click a creature or nearby location to swipe at it!"), TRUE) + return FALSE + return ..() -/obj/effect/proc_holder/swipe/InterceptClickOn(mob/living/caller, params, atom/target) +/datum/action/cooldown/swipe/InterceptClickOn(mob/living/caller, params, atom/target) . = ..() var/turf/open/T = get_turf(target) var/mob/living/L = target - if(.) + if(!.) return - if(ranged_ability_user.stat) - remove_ranged_ability() + if(owner.stat) + unset_click_ability(caller) return - if(!COOLDOWN_FINISHED(src, scan_cooldown)) - to_chat(ranged_ability_user, span_warning("Your cuffs aren't ready to do that yet. Give them some time to recharge!")) + if(!COOLDOWN_FINISHED(src, next_use_time)) + to_chat(owner, span_warning("Your cuffs aren't ready to do that yet. Give them some time to recharge!")) return if(!istype(T)) return @@ -206,7 +199,7 @@ obj/effect/proc_holder/swipe new /obj/effect/temp_visual/bubblegum_hands/rightpaw(T) new /obj/effect/temp_visual/bubblegum_hands/rightthumb(T) to_chat(L, span_userdanger("Claws reach out from the floor and maul you!")) - to_chat(ranged_ability_user, "You summon claws at [L]'s location!") + to_chat(owner, "You summon claws at [L]'s location!") L.visible_message(span_warning("[caller] rends [L]!")) for(L in range(0,T)) playsound(T, 'sound/magic/demon_attack1.ogg', 80, 5, -1) @@ -229,12 +222,11 @@ obj/effect/proc_holder/swipe caller.adjustBruteLoss(-5) caller.adjustFireLoss(-5) caller.adjustToxLoss(-5) - COOLDOWN_START(src, scan_cooldown, cooldown) - addtimer(CALLBACK(src, PROC_REF(cooldown_over), ranged_ability_user), cooldown) - remove_ranged_ability() + addtimer(CALLBACK(src, PROC_REF(cooldown_over), owner), cooldown_time) + unset_click_ability(owner) return TRUE -/obj/effect/proc_holder/swipe/proc/cooldown_over() +/datum/action/cooldown/swipe/proc/cooldown_over() to_chat(usr, (span_notice("You're ready to swipe again!"))) /obj/item/clothing/gloves/gauntlets diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index ae15ac340187..07554e146686 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -39,9 +39,8 @@ if(ishuman(loc)) var/mob/living/carbon/human/H = loc H.update_inv_head() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon(force = TRUE) + for(var/datum/action/A as anything in actions) + A.build_all_button_icons(force = TRUE) ..() /obj/item/clothing/head/hardhat/proc/turn_on(mob/user) @@ -138,6 +137,13 @@ if(user.canUseTopic(src, BE_CLOSE)) toggle_welding_screen(user) +/obj/item/clothing/head/hardhat/weldhat/ui_action_click(mob/user, actiontype) + if(istype(actiontype, /datum/action/item_action/toggle_welding_screen)) + toggle_welding_screen(user) + return + + return ..() + /obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) if(weldingvisortoggle(user)) playsound(src, 'sound/mecha/mechmove03.ogg', 50, 1) //Visors don't just come from nothing diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index b2c62e9f2eaf..364a27cbe599 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -447,7 +447,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/head/helmet/stormtrooper name = "Storm Trooper Helmet" diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index d83eae2d91b8..d8d444116ed3 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -434,6 +434,10 @@ icon_state = "taqiyahred" pocket_storage_component_path = /datum/component/storage/concrete/pockets/small +/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!" + /obj/item/clothing/head/hatsky name = "officer hatsky" desc = "A hat for true Beepsky appreciators. Not guaranteed to actually keep you safe from anything." diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index 069858c71eaf..4f1373b20593 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -76,4 +76,4 @@ user.wear_mask_update(src, toggle_off = mask_adjusted) if(loc == user) // Update action button icon for adjusted mask, if someone is holding it. - user.update_action_buttons_icon() + user.update_mob_action_buttons() diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index fa081c6f3c67..908398f3f412 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -96,7 +96,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() to_chat(user, span_notice("Your Clown Mask has now morphed into [choice], all praise the Honkmother!")) return TRUE @@ -150,7 +150,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() to_chat(user, span_notice("Your Mime Mask has now morphed into [choice]!")) return TRUE @@ -240,7 +240,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!") return 1 diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm index 364ffd2375f3..89020b73c1ca 100644 --- a/code/modules/clothing/masks/hailer.dm +++ b/code/modules/clothing/masks/hailer.dm @@ -1,6 +1,9 @@ // **** Security gas mask **** +/datum/action/item_action/halt + name = "HALT!" + /obj/item/clothing/mask/gas/sechailer name = "security gas mask" desc = "A standard issue Security gas mask with integrated 'Compli-o-nator 3000' device. Plays over a dozen pre-recorded compliance phrases designed to get scumbags to stand still whilst you tase them. Do not tamper with the device." diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index a91481d579a4..da574f2b72cf 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -79,6 +79,21 @@ desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." icon_state = "joy" + +GLOBAL_LIST_INIT(cursed_animal_masks, list( + /obj/item/clothing/mask/pig/cursed, + /obj/item/clothing/mask/frog/cursed, + /obj/item/clothing/mask/cowmask/cursed, + /obj/item/clothing/mask/horsehead/cursed, +// /obj/item/clothing/mask/animal/small/rat/cursed, +// /obj/item/clothing/mask/animal/small/fox/cursed, +// /obj/item/clothing/mask/animal/small/bee/cursed, +// /obj/item/clothing/mask/animal/small/bear/cursed, +// /obj/item/clothing/mask/animal/small/bat/cursed, +// /obj/item/clothing/mask/animal/small/raven/cursed, +// /obj/item/clothing/mask/animal/small/jackal/cursed + )) + /obj/item/clothing/mask/pig name = "pig mask" desc = "A rubber pig mask with a built in voice modulator." diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index 4d0a19deb7aa..c46ead6590c8 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -431,7 +431,7 @@ return BULLET_ACT_FORCE_PIERCE /obj/item/clothing/neck/cloak/ranger/proc/dodge(mob/living/carbon/human/user, atom/movable/hitby, attack_text) - if(!update_signals(user) || current_user.incapacitated(check_immobilized = TRUE) || !prob(cloak)) + if(!update_signals(user) || current_user.incapacitated() || !prob(cloak)) return FALSE set_cloak(cloak - cloak_dodge_loss) diff --git a/code/modules/clothing/neck/bodycamera.dm b/code/modules/clothing/neck/bodycamera.dm index 40ff25b41876..5fac7363de8d 100644 --- a/code/modules/clothing/neck/bodycamera.dm +++ b/code/modules/clothing/neck/bodycamera.dm @@ -63,7 +63,7 @@ item_state = "[prefix]_bodycam_[suffix]" for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/neck/bodycam/examine(mob/user) .=..() diff --git a/code/modules/clothing/outfits/standard.dm b/code/modules/clothing/outfits/standard.dm index b0d5bf65a833..d6732843b55e 100644 --- a/code/modules/clothing/outfits/standard.dm +++ b/code/modules/clothing/outfits/standard.dm @@ -251,22 +251,24 @@ uniform = /obj/item/clothing/under/color/lightpurple suit = /obj/item/clothing/suit/wizrobe - shoes = /obj/item/clothing/shoes/sandal/magic + back = /obj/item/storage/backpack + backpack_contents = list( + /obj/item/spellbook = 1, + ) + box = /obj/item/storage/box/survival ears = /obj/item/radio/headset head = /obj/item/clothing/head/wizard + shoes = /obj/item/clothing/shoes/sandal/magic r_pocket = /obj/item/teleportation_scroll - r_hand = /obj/item/spellbook l_hand = /obj/item/staff - back = /obj/item/storage/backpack - backpack_contents = list(/obj/item/storage/box/survival=1) -/datum/outfit/wizard/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) +/datum/outfit/wizard/post_equip(mob/living/carbon/human/wizard, visualsOnly = FALSE) if(visualsOnly) return - var/obj/item/spellbook/S = locate() in H.held_items - if(S) - S.owner = H + var/obj/item/spellbook/new_spellbook = locate() in wizard.back + if(new_spellbook) + new_spellbook.owner = wizard.mind /datum/outfit/wizard/apprentice name = "Wizard Apprentice" diff --git a/code/modules/clothing/outfits/vv_outfit.dm b/code/modules/clothing/outfits/vv_outfit.dm index e0508725e4a5..cafb4d9641ed 100644 --- a/code/modules/clothing/outfits/vv_outfit.dm +++ b/code/modules/clothing/outfits/vv_outfit.dm @@ -35,7 +35,7 @@ glasses = item_path if(SLOT_WEAR_ID) id = item_path - if(SLOT_S_STORE) + if(SLOT_SUIT_STORE) suit_store = item_path if(SLOT_L_STORE) l_pocket = item_path @@ -69,7 +69,7 @@ //Copy equipment var/list/result = list() - var/list/slots_to_check = list(SLOT_W_UNIFORM,SLOT_BACK,SLOT_WEAR_SUIT,SLOT_BELT,SLOT_GLOVES,SLOT_SHOES,SLOT_HEAD,SLOT_WEAR_MASK,SLOT_NECK,SLOT_EARS,SLOT_GLASSES,SLOT_WEAR_ID,SLOT_S_STORE,SLOT_L_STORE,SLOT_R_STORE) + var/list/slots_to_check = list(SLOT_W_UNIFORM,SLOT_BACK,SLOT_WEAR_SUIT,SLOT_BELT,SLOT_GLOVES,SLOT_SHOES,SLOT_HEAD,SLOT_WEAR_MASK,SLOT_NECK,SLOT_EARS,SLOT_GLASSES,SLOT_WEAR_ID,SLOT_SUIT_STORE,SLOT_L_STORE,SLOT_R_STORE) for(var/s in slots_to_check) var/obj/item/I = get_item_by_slot(s) var/vedits = collect_vv(I) diff --git a/code/modules/clothing/shoes/bananashoes.dm b/code/modules/clothing/shoes/bananashoes.dm index 36dcc8726d5c..20dd6785aadf 100644 --- a/code/modules/clothing/shoes/bananashoes.dm +++ b/code/modules/clothing/shoes/bananashoes.dm @@ -63,4 +63,4 @@ usr.update_inv_shoes() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index cc17e9453675..2db46c8beb16 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -35,7 +35,7 @@ user.update_gravity(user.has_gravity()) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/shoes/magboots/negates_gravity() return clothing_flags & NOSLIP diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 89b6a8b7ac46..a327e742a97e 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -324,7 +324,7 @@ desc = "They'll sure kindle something in you, and it's not childhood nostalgia..." icon_state = "kindleKicks" item_state = "kindleKicks" - actions_types = list(/datum/action/item_action/kindleKicks) + actions_types = list(/datum/action/item_action/kindle_kicks) light_system = MOVABLE_LIGHT light_range = 2 light_power = 3 @@ -544,6 +544,18 @@ xenoshoe = YES_DIGIT mutantrace_variation = MUTANTRACE_VARIATION +/datum/action/item_action/dash + name = "Dash" + desc = "Momentarily maximizes the jets of the shoes, allowing the user to dash a short distance." + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "thrust" + +/datum/action/item_action/airshoes + name = "Toggle thrust on air shoes." + desc = "Switch between walking and hovering." + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "airshoes_a" + /obj/item/clothing/shoes/airshoes name = "air shoes" desc = "Footwear that uses propulsion technology to keep you above the ground and let you move faster." @@ -551,7 +563,8 @@ obj_flags = UNIQUE_RENAME //im not fucking naming them 'sonic 11's you can do that yourself ffm actions_types = list(/datum/action/item_action/airshoes, /datum/action/item_action/dash) var/airToggle = FALSE - var/obj/vehicle/ridden/scooter/airshoes/A + ///Secret vehicle that helps us move around at mach speeds + var/obj/vehicle/ridden/scooter/airshoes/shoes_of_air permeability_coefficient = 0.05 var/recharging_time = 0 var/jumpdistance = 7 //Increased distance so it might see some offensive use @@ -561,7 +574,7 @@ /obj/item/clothing/shoes/airshoes/Initialize() . = ..() - A = new/obj/vehicle/ridden/scooter/airshoes(null) + shoes_of_air = new /obj/vehicle/ridden/scooter/airshoes(null) /obj/item/clothing/shoes/airshoes/ui_action_click(mob/user, action) if(!isliving(user)) @@ -569,24 +582,24 @@ if(!istype(user.get_item_by_slot(SLOT_SHOES), /obj/item/clothing/shoes/airshoes)) to_chat(user, span_warning("You must be wearing the air shoes to use them!")) return - if(istype(action,/datum/action/item_action/airshoes)) - if(!(A.is_occupant(user))) + if(istype(action, /datum/action/item_action/airshoes)) + if(!(shoes_of_air.is_occupant(user))) airToggle = FALSE if(airToggle) - A.unbuckle_mob(user) + shoes_of_air.unbuckle_mob(user) airToggle = FALSE return - A.forceMove(get_turf(user)) - A.buckle_mob(user) + shoes_of_air.forceMove(get_turf(user)) + shoes_of_air.buckle_mob(user) airToggle = TRUE - else if(istype(action,/datum/action/item_action/dash)) + else if(istype(action, /datum/action/item_action/dash)) if(recharging_time > world.time) to_chat(user, span_warning("The boot's internal propulsion needs to recharge still!")) return var/atom/target = get_edge_target_turf(user, user.dir) //gets the user's direction if (user.throw_at(target, jumpdistance, jumpspeed, spin = FALSE, diagonals_first = TRUE)) - playsound(src, 'sound/effects/stealthoff.ogg', 50, 1, 1) + playsound(src, 'sound/effects/stealthoff.ogg', 50, TRUE, 1) user.visible_message(span_warning("[usr] dashes forward into the air!")) recharging_time = world.time + recharging_rate else @@ -594,11 +607,12 @@ /obj/item/clothing/shoes/airshoes/dropped(mob/user) if(airToggle) - A.unbuckle_mob(user) + shoes_of_air.unbuckle_mob(user) airToggle = FALSE ..() + /obj/item/clothing/shoes/airshoes/Destroy() - QDEL_NULL(A) + QDEL_NULL(shoes_of_air) . = ..() /obj/item/clothing/shoes/drip diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm index bab43e242172..8fe7deb7ed8f 100644 --- a/code/modules/clothing/spacesuits/chronosuit.dm +++ b/code/modules/clothing/spacesuits/chronosuit.dm @@ -104,7 +104,7 @@ camera.remove_target_ui() camera.forceMove(user) user.reset_perspective(camera) - teleport_now.UpdateButtonIcon() + teleport_now.build_all_button_icons() /obj/item/clothing/suit/space/chronos/proc/chronowalk(atom/location) var/mob/living/carbon/human/user = src.loc @@ -118,7 +118,7 @@ if(camera) camera.remove_target_ui() - teleport_now.UpdateButtonIcon() + teleport_now.build_all_button_icons() var/list/nonsafe_slots = list(SLOT_BELT, SLOT_BACK) var/list/exposed = list() @@ -131,7 +131,7 @@ if(exposed_I && !(exposed_I.type in chronosafe_items) && user.dropItemToGround(exposed_I)) to_chat(user, span_notice("Your [exposed_I.name] got left behind.")) - user.ExtinguishMob() + user.extinguish_mob() for(var/obj/item/I in user.held_items) ADD_TRAIT(I, TRAIT_NODROP, CHRONOSUIT_TRAIT) @@ -326,7 +326,7 @@ /datum/action/innate/chrono_teleport name = "Teleport Now" - icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' button_icon_state = "chrono_phase" check_flags = AB_CHECK_CONSCIOUS //|AB_CHECK_INSIDE var/obj/item/clothing/suit/space/chronos/chronosuit = null @@ -335,10 +335,10 @@ chronosuit = null return ..() -/datum/action/innate/chrono_teleport/IsAvailable() +/datum/action/innate/chrono_teleport/IsAvailable(feedback = FALSE) return (chronosuit && chronosuit.activated && chronosuit.camera && !chronosuit.teleporting) /datum/action/innate/chrono_teleport/Activate() - if(IsAvailable()) + if(IsAvailable(feedback = FALSE)) if(chronosuit.camera) chronosuit.chronowalk(chronosuit.camera) diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 667c60a01d78..bbcfebefe739 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -40,7 +40,7 @@ for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/head/helmet/space/hardsuit/dropped(mob/user) ..() @@ -345,7 +345,7 @@ C.head_update(src, forced = 1) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/head/helmet/space/hardsuit/syndi/proc/toggle_hardsuit_mode(mob/user) //Helmet Toggles Suit Mode if(linkedsuit) @@ -555,13 +555,13 @@ ..() if (slot == SLOT_HEAD) var/datum/atom_hud/DHUD = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - DHUD.add_hud_to(user) + DHUD.show_to(user) /obj/item/clothing/head/helmet/space/hardsuit/rd/dropped(mob/living/carbon/human/user) ..() if (user.head == src) var/datum/atom_hud/DHUD = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - DHUD.remove_hud_from(user) + DHUD.hide_from(user) /obj/item/clothing/head/helmet/space/hardsuit/rd/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index e32685c55761..92861f131018 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -31,7 +31,7 @@ next_extinguish = world.time + extinguish_cooldown extinguishes_left-- H.visible_message(span_warning("[H]'s suit automatically extinguishes [H.p_them()]!"),span_warning("Your suit automatically extinguishes you.")) - H.ExtinguishMob() + H.extinguish_mob() new /obj/effect/particle_effect/water(get_turf(H)) @@ -88,7 +88,7 @@ user.add_overlay(helmet_mob_overlay) user.update_inv_head() for(var/datum/action/A as anything in actions) - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/head/helmet/space/plasmaman/equipped(mob/living/user, slot) . = ..() diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index b2ac7b0a93d8..dfc62d84cfb3 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -106,7 +106,7 @@ for(var/mob/living/carbon/C in range(6, owner)) if(C != owner) C.adjust_fire_stacks(8) - C.IgniteMob() + C.ignite_mob() owner.fire_stacks = -1 reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return 1 diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index 6aaa9e659514..a62310f7d57a 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -43,7 +43,7 @@ hood?.forceMove(src) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/suit/hooded/dropped() ..() @@ -65,7 +65,7 @@ H.update_inv_wear_suit() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() else RemoveHood() @@ -118,7 +118,7 @@ usr.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/toggle/examine(mob/user) . = ..() diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index bfc349fe356c..4da052bb66de 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -152,6 +152,13 @@ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0) resistance_flags = FLAMMABLE +//Stickmemes +/datum/action/item_action/stickmen + name = "Summon Stick Minions" + desc = "Allows you to summon faithful stickmen allies to aide you in battle." + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "art_summon" + /obj/item/clothing/suit/wizrobe/paper name = "papier-mâché robe" // yogs -- we live in the future desc = "A robe held together by various bits of clear-tape and paste." diff --git a/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm b/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm index 61209c571942..3015ff1a3d2e 100644 --- a/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm +++ b/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm @@ -85,7 +85,7 @@ next_extinguish = world.time + extinguish_cooldown extinguishes_left-- H.visible_message(span_warning("[H]'s suit spews out a tonne of space lube!"), span_warning("Your suit spews out a tonne of space lube!")) - H.ExtinguishMob() + H.extinguish_mob() var/datum/effect_system/fluid_spread/foam/foam = new var/datum/reagents/foamreagent = new /datum/reagents(15) foamreagent.add_reagent(/datum/reagent/lube, 15) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 1fde8cf7e107..cdee4d0d66e0 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -679,7 +679,7 @@ next_extinguish = world.time + extinguish_cooldown extinguishes_left-- H.visible_message(span_warning("[H]'s suit automatically extinguishes [H.p_them()]!"),span_warning("Your suit automatically extinguishes you.")) - H.ExtinguishMob() + H.extinguish_mob() new /obj/effect/particle_effect/water(get_turf(H)) return 0 @@ -836,7 +836,7 @@ for(var/X in actions) var/datum/action/A=X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/clothing/under/lampskirt/female icon_state = "lampskirt_female" diff --git a/code/modules/demo/hooks.dm b/code/modules/demo/hooks.dm index 58190da92845..f73397542564 100644 --- a/code/modules/demo/hooks.dm +++ b/code/modules/demo/hooks.dm @@ -6,9 +6,12 @@ /// Last location of the atom for demo recording purposes var/atom/demo_last_loc +/* /mob/Login() . = ..() - SSdemo.write_event_line("setmob [client.ckey] \ref[src]") + SSdemo.write_event_line("setmob [client.ckey] \ref[src]") +* handled in code\modules\mob.dm +*/ /client/New() SSdemo.write_event_line("login [ckey]") diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index b4a6244d3db5..558ddbb6a220 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -30,7 +30,7 @@ allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - return safepick(typecache_filter_list(GLOB.areas,allowed_areas)) + return pick(typecache_filter_list(GLOB.areas,allowed_areas)) /datum/round_event/anomaly/setup() impact_area = findEventArea() @@ -44,7 +44,7 @@ priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") /datum/round_event/anomaly/start() - var/turf/T = safepick(get_area_turfs(impact_area)) + var/turf/T = pick(get_area_turfs(impact_area)) var/newAnomaly if(T) newAnomaly = new anomaly_path(T) diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index fd18e5b46479..b44c04e8e71c 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -53,7 +53,7 @@ originMachine.visible_message("[originMachine] beeps and seems lifeless.") kill() return - vendingMachines = removeNullsFromList(vendingMachines) + vendingMachines = listclearnulls(vendingMachines) if(!vendingMachines.len) //if every machine is infected for(var/obj/machinery/vending/upriser in infectedMachines) if(prob(70) && !QDELETED(upriser)) diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index f8a30a7016bf..16e21c0260a1 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -50,7 +50,6 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 density = TRUE anchored = TRUE flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - var/mob/living/wizard var/z_original = 0 var/destination var/notify = TRUE @@ -149,21 +148,34 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 L.ex_act(EXPLODE_HEAVY) /obj/effect/immovablerod/attack_hand(mob/living/user) - if(ishuman(user)) - var/mob/living/carbon/human/U = user - if(U.job in list("Research Director")) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1) - for(var/mob/M in urange(8, src)) - if(!M.stat) - shake_camera(M, 2, 3) - if(wizard) - U.visible_message(span_boldwarning("[src] transforms into [wizard] as [U] suplexes them!"), span_warning("As you grab [src], it suddenly turns into [wizard] as you suplex them!")) - to_chat(wizard, span_boldwarning("You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!")) - wizard.Stun(60) - wizard.apply_damage(25, BRUTE) - qdel(src) - else - U.visible_message(span_boldwarning("[U] suplexes [src] into the ground!"), span_warning("You suplex [src] into the ground!")) - new /obj/structure/festivus/anchored(drop_location()) - new /obj/effect/anomaly/flux(drop_location()) - qdel(src) + . = ..() + if(.) + return + + var/mob/living/carbon/human/U = user + if(!(U.job in list("Research Director"))) + return + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + for(var/mob/living/nearby_mob in urange(8, src)) + if(nearby_mob.stat != CONSCIOUS) + continue + shake_camera(nearby_mob, 2, 3) + + return suplex_rod(user) + +/** + * Called when someone manages to suplex the rod. + * + * Arguments + * * strongman - the suplexer of the rod. + */ +/obj/effect/immovablerod/proc/suplex_rod(mob/living/strongman) +// strongman.client?.give_award(/datum/award/achievement/misc/feat_of_strength, strongman) + strongman.visible_message( + span_boldwarning("[strongman] suplexes [src] into the ground!"), + span_warning("You suplex [src] into the ground!") + ) + new /obj/structure/festivus/anchored(drop_location()) + new /obj/effect/anomaly/flux(drop_location()) + qdel(src) + return TRUE diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm index 15057f8f0c72..fa4eb45ab71c 100644 --- a/code/modules/events/portal_storm.dm +++ b/code/modules/events/portal_storm.dm @@ -67,14 +67,14 @@ spawn_effects(get_random_station_turf()) if(spawn_hostile()) - var/type = safepick(hostile_types) + var/type = pick(hostile_types) hostile_types[type] = hostile_types[type] - 1 spawn_mob(type, hostiles_spawn) if(!hostile_types[type]) hostile_types -= type if(spawn_boss()) - var/type = safepick(boss_types) + var/type = pick(boss_types) boss_types[type] = boss_types[type] - 1 spawn_mob(type, boss_spawn) if(!boss_types[type]) diff --git a/code/modules/events/tzimisce.dm b/code/modules/events/tzimisce.dm index 1a2f5e60c560..852b950defa5 100644 --- a/code/modules/events/tzimisce.dm +++ b/code/modules/events/tzimisce.dm @@ -1,9 +1,15 @@ +/datum/round_event_control/tzimisce + name = "Spawn Tzimisce" + typepath = /datum/round_event/ghost_role/tzimisce + max_occurrences = 2 + min_players = 25 + earliest_start = 45 MINUTES + /datum/round_event_control/tzimisce/bloodsucker name = "Spawn Tzimisce - Bloodsucker" max_occurrences = 1 - weight = 5 + weight = 2000 typepath = /datum/round_event/ghost_role/tzimisce/bloodsucker - max_occurrences = 2 min_players = 25 earliest_start = 30 MINUTES gamemode_whitelist = list("bloodsucker","traitorsucker") @@ -21,13 +27,7 @@ if(cancel_me) kill() return - -/datum/round_event_control/tzimisce - name = "Spawn Tzimisce" - typepath = /datum/round_event/ghost_role/tzimisce - max_occurrences = 1 - min_players = 25 - earliest_start = 45 MINUTES + try_spawning() /datum/round_event/ghost_role/tzimisce var/success_spawn = 0 @@ -53,31 +53,38 @@ var/mob/dead/selected_candidate = pick_n_take(candidates) var/key = selected_candidate.key - var/datum/mind/Mind = create_tzimisce_mind(key) - Mind.active = TRUE + var/datum/mind/tzimisce_mind = create_tzimisce_mind(key) + tzimisce_mind.active = TRUE var/mob/living/carbon/human/tzimisce = spawn_event_tzimisce() - Mind.transfer_to(tzimisce) - Mind.add_antag_datum(/datum/antagonist/bloodsucker) + tzimisce_mind.transfer_to(tzimisce) + tzimisce_mind.add_antag_datum(/datum/antagonist/bloodsucker) var/datum/antagonist/bloodsucker/bloodsuckerdatum = tzimisce.mind.has_antag_datum(/datum/antagonist/bloodsucker) + bloodsuckerdatum.antag_hud_name = "tzimisce" + bloodsuckerdatum.add_team_hud(tzimisce) bloodsuckerdatum.bloodsucker_level_unspent += round(world.time / (15 MINUTES), 1) - bloodsuckerdatum.AssignClanAndBane(tzimisce = TRUE) + bloodsuckerdatum.assign_clan_and_bane(tzimisce = TRUE) spawned_mobs += tzimisce message_admins("[ADMIN_LOOKUPFLW(tzimisce)] has been made into a tzimisce bloodsucker an event.") log_game("[key_name(tzimisce)] was spawned as a tzimisce bloodsucker by an event.") var/datum/job/jobdatum = SSjob.GetJob(pick("Assistant", "Botanist", "Station Engineer", "Medical Doctor", "Scientist", "Cargo Technician", "Cook")) - set_antag_hud(tzimisce, "tzimisce") if(SSshuttle.arrivals) SSshuttle.arrivals.QueueAnnounce(tzimisce, jobdatum.title) - Mind.assigned_role = jobdatum.title //sets up the manifest properly - jobdatum.equip(tzimisce) + tzimisce_mind.assigned_role = jobdatum.title //sets up the manifest properly + jobdatum.equip(tzimisce) var/obj/item/card/id/id = tzimisce.get_item_by_slot(SLOT_WEAR_ID) + if(!istype(id)) //pda on ID slot + var/obj/item/modular_computer/tablet/PDA = tzimisce.get_item_by_slot(SLOT_WEAR_ID) + var/obj/item/computer_hardware/card_slot/card_slot2 = PDA.all_components[MC_CARD2] + var/obj/item/computer_hardware/card_slot/card_slot = PDA.all_components[MC_CARD] + id = card_slot2?.stored_card || card_slot?.stored_card //check both slots, priority on 2nd id.assignment = jobdatum.title id.originalassignment = jobdatum.title id.update_label() GLOB.data_core.manifest_inject(tzimisce, force = TRUE) tzimisce.update_move_intent_slowdown() //prevents you from going super duper fast + announce_to_ghosts(tzimisce) return SUCCESSFUL_SPAWN @@ -89,6 +96,6 @@ return new_tzimisce /datum/round_event/ghost_role/tzimisce/proc/create_tzimisce_mind(key) - var/datum/mind/Mind = new /datum/mind(key) - Mind.special_role = ROLE_BLOODSUCKER - return Mind + var/datum/mind/tzimisce_mind = new /datum/mind(key) + tzimisce_mind.special_role = ROLE_BLOODSUCKER + return tzimisce_mind diff --git a/code/modules/events/wizard/aid.dm b/code/modules/events/wizard/aid.dm index d055cc7c3d12..92747d2cd76e 100644 --- a/code/modules/events/wizard/aid.dm +++ b/code/modules/events/wizard/aid.dm @@ -1,4 +1,5 @@ -//in this file: Various events that directly aid the wizard. This is the "lets entice the wizard to use summon events!" file. +// Various events that directly aid the wizard. +// This is the "lets entice the wizard to use summon events!" file. /datum/round_event_control/wizard/robelesscasting //EI NUDTH! name = "Robeless Casting" @@ -9,16 +10,19 @@ /datum/round_event/wizard/robelesscasting/start() - for(var/i in GLOB.mob_living_list) //Hey if a corgi has magic missle he should get the same benifit as anyone - var/mob/living/L = i - if(L.mind && L.mind.spell_list.len != 0) - var/spell_improved = FALSE - for(var/obj/effect/proc_holder/spell/S in L.mind.spell_list) - if(S.clothes_req) - S.clothes_req = 0 - spell_improved = TRUE - if(spell_improved) - to_chat(L, span_notice("You suddenly feel like you never needed those garish robes in the first place...")) + // Hey, if a corgi has magic missle, he should get the same benefit as anyone + for(var/mob/living/caster as anything in GLOB.mob_living_list) + if(!length(caster.actions)) + continue + + var/spell_improved = FALSE + for(var/datum/action/cooldown/spell/spell in caster.actions) + if(spell.spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + spell.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB + spell_improved = TRUE + + if(spell_improved) + to_chat(caster, span_notice("You suddenly feel like you never needed those garish robes in the first place...")) //--// @@ -30,29 +34,16 @@ earliest_start = 0 MINUTES /datum/round_event/wizard/improvedcasting/start() - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - if(L.mind && L.mind.spell_list.len != 0) - for(var/obj/effect/proc_holder/spell/S in L.mind.spell_list) - S.name = initial(S.name) - S.spell_level++ - if(S.spell_level >= 6 || S.charge_max <= 0) //Badmin checks, these should never be a problem in normal play - continue - if(S.level_max <= 0) - continue - S.charge_max = round(initial(S.charge_max) - S.spell_level * (initial(S.charge_max) - S.cooldown_min) / S.level_max) - if(S.charge_max < S.charge_counter) - S.charge_counter = S.charge_max - switch(S.spell_level) - if(1) - S.name = "Efficient [S.name]" - if(2) - S.name = "Quickened [S.name]" - if(3) - S.name = "Free [S.name]" - if(4) - S.name = "Instant [S.name]" - if(5) - S.name = "Ludicrous [S.name]" - - to_chat(L, span_notice("You suddenly feel more competent with your casting!")) + for(var/mob/living/caster as anything in GLOB.mob_living_list) + if(!length(caster.actions)) + continue + + var/upgraded_a_spell = FALSE + for(var/datum/action/cooldown/spell/spell in caster.actions) + // If improved casting has already boosted this spell further beyond, go no further + if(spell.spell_level >= spell.spell_max_level + 1) + continue + upgraded_a_spell = spell.level_spell(TRUE) + + if(upgraded_a_spell) + to_chat(caster, span_notice("You suddenly feel more competent with your casting!")) diff --git a/code/modules/events/wizard/curseditems.dm b/code/modules/events/wizard/curseditems.dm index d7bc152f4555..bd7c787eafe3 100644 --- a/code/modules/events/wizard/curseditems.dm +++ b/code/modules/events/wizard/curseditems.dm @@ -50,7 +50,7 @@ var/obj/item/I = new J //dumb but required because of byond throwing a fit anytime new gets too close to a list H.dropItemToGround(H.get_item_by_slot(i), TRUE) H.equip_to_slot_or_del(I, i) - ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT) + ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT(I)) I.item_flags |= DROPDEL I.name = "cursed " + I.name diff --git a/code/modules/events/wizard/magicarp.dm b/code/modules/events/wizard/magicarp.dm index 180dadd73971..63de076fd02d 100644 --- a/code/modules/events/wizard/magicarp.dm +++ b/code/modules/events/wizard/magicarp.dm @@ -39,7 +39,7 @@ gold_core_spawnable = NO_SPAWN //yogs - fuck this shit random_color = FALSE var/allowed_projectile_types = list(/obj/item/projectile/magic/animate, /obj/item/projectile/magic/resurrection, - /obj/item/projectile/magic/death, /obj/item/projectile/magic/teleport, /obj/item/projectile/magic/door, /obj/item/projectile/magic/aoe/fireball, + /obj/item/projectile/magic/death, /obj/item/projectile/magic/teleport, /obj/item/projectile/magic/door, /obj/item/projectile/magic/fireball, /obj/item/projectile/magic/spellblade, /obj/item/projectile/magic/arcane_barrage) /mob/living/simple_animal/hostile/carp/ranged/Initialize() diff --git a/code/modules/events/wizard/shuffle.dm b/code/modules/events/wizard/shuffle.dm index c3e71ab28898..2a580193a6dd 100644 --- a/code/modules/events/wizard/shuffle.dm +++ b/code/modules/events/wizard/shuffle.dm @@ -79,26 +79,29 @@ earliest_start = 0 MINUTES /datum/round_event/wizard/shuffleminds/start() - var/list/mobs = list() + var/list/mobs_to_swap = list() - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(H.stat || !H.mind || iswizard(H)) + for(var/mob/living/carbon/human/alive_human in GLOB.alive_mob_list) + if(alive_human.stat != CONSCIOUS || !alive_human.mind || iswizard(alive_human)) continue //the wizard(s) are spared on this one - mobs += H + mobs_to_swap += alive_human - if(!mobs) + if(!length(mobs_to_swap)) return - shuffle_inplace(mobs) + mobs_to_swap = shuffle(mobs_to_swap) - var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new /obj/effect/proc_holder/spell/targeted/mind_transfer - while(mobs.len > 1) - var/mob/living/carbon/human/H = pick(mobs) - mobs -= H - swapper.cast(list(H), mobs[mobs.len], 1) - mobs -= mobs[mobs.len] + var/datum/action/cooldown/spell/pointed/mind_transfer/swapper = new() - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - var/datum/effect_system/fluid_spread/smoke/smoke = new - smoke.set_up(0, location = H.loc) + while(mobs_to_swap.len > 1) + var/mob/living/swap_to = pick_n_take(mobs_to_swap) + var/mob/living/swap_from = pick_n_take(mobs_to_swap) + + swapper.swap_minds(swap_to, swap_from) + + qdel(swapper) + + for(var/mob/living/carbon/human/alive_human in GLOB.alive_mob_list) + var/datum/effect_system/fluid_spread/smoke/smoke = new() + smoke.set_up(0, holder = alive_human, location = alive_human.loc) smoke.start() diff --git a/code/modules/events/wizard/summons.dm b/code/modules/events/wizard/summons.dm new file mode 100644 index 000000000000..3a5150e39fc7 --- /dev/null +++ b/code/modules/events/wizard/summons.dm @@ -0,0 +1,29 @@ +/datum/round_event_control/wizard/summonguns //The Classic + name = "Summon Guns" + weight = 1 + typepath = /datum/round_event/wizard/summonguns + max_occurrences = 1 + earliest_start = 0 MINUTES + +/datum/round_event_control/wizard/summonguns/New() + if(CONFIG_GET(flag/no_summon_guns)) + weight = 0 + return ..() + +/datum/round_event/wizard/summonguns/start() + summon_guns(survivor_probability = 10) + +/datum/round_event_control/wizard/summonmagic //The Somewhat Less Classic + name = "Summon Magic" + weight = 1 + typepath = /datum/round_event/wizard/summonmagic + max_occurrences = 1 + earliest_start = 0 MINUTES + +/datum/round_event_control/wizard/summonmagic/New() + if(CONFIG_GET(flag/no_summon_magic)) + weight = 0 + return ..() + +/datum/round_event/wizard/summonmagic/start() + summon_magic(survivor_probability = 10) diff --git a/code/modules/fields/timestop.dm b/code/modules/fields/timestop.dm index b62980b2ce54..ebe972332846 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -28,16 +28,12 @@ freezerange = radius for(var/A in immune_atoms) immune[A] = TRUE - for(var/mob/living/L in GLOB.player_list) - if(locate(/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects - immune[L] = TRUE - for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites) - if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop) in G.summoner.spell_list) //It would only make sense that a person's stand would also be immune. - immune[G] = TRUE - if(locate(/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop) in G.mob_spell_list) - immune[G] = TRUE - if(G.summoner?.current) - immune[G.summoner.current] = TRUE + for(var/mob/living/to_check in GLOB.player_list) + if(HAS_TRAIT(to_check, TRAIT_TIME_STOP_IMMUNE)) + immune[to_check] = TRUE + for(var/mob/living/simple_animal/hostile/guardian/stand in GLOB.parasites) + if(stand.summoner && HAS_TRAIT(stand.summoner, TRAIT_TIME_STOP_IMMUNE)) //It would only make sense that a person's stand would also be immune. + immune[stand] = TRUE if(start) timestop() diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index bb11a2eb4780..aa69d9af88e4 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -1,4 +1,4 @@ -#define HAL_LINES_FILE "hallucination.json" +#define HALLUCINATION_FILE "hallucination.json" GLOBAL_LIST_INIT(hallucination_list, list( /datum/hallucination/chat = 100, @@ -24,28 +24,16 @@ GLOBAL_LIST_INIT(hallucination_list, list( /datum/hallucination/oh_yeah = 1 )) - -/mob/living/carbon/proc/handle_hallucinations() - hallucination = max(0, hallucination) - if(hallucination <= 0) - return - - hallucination-- - - if(world.time < next_hallucination) - return - - var/halpick = pickweight(GLOB.hallucination_list) - new halpick(src, FALSE) - - next_hallucination = world.time + rand(100, 600) - /mob/living/carbon/proc/set_screwyhud(hud_type) hal_screwyhud = hud_type update_health_hud() /datum/hallucination var/natural = TRUE + /// What is this hallucination's weight in the random hallucination pool? + var/random_hallucination_weight = 0 + /// Who's our next highest abstract parent type? + var/abstract_hallucination_parent = /datum/hallucination var/mob/living/carbon/target var/feedback_details //extra info for investigate @@ -54,6 +42,9 @@ GLOBAL_LIST_INIT(hallucination_list, list( target = C natural = !forced +/datum/hallucination/proc/start() + return TRUE //unfortunate + /datum/hallucination/proc/wake_and_restore() target.set_screwyhud(SCREWYHUD_NONE) target.SetSleeping(0) @@ -148,6 +139,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( #define FAKE_FLOOD_MAX_RADIUS 10 /datum/hallucination/fake_flood + random_hallucination_weight = 7 //Plasma starts flooding from the nearby vent var/turf/center var/list/flood_images = list() @@ -231,6 +223,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( target.visible_message(span_danger("[target] flails around wildly."),"[name] pounces on you!") /datum/hallucination/xeno_attack + random_hallucination_weight = 2 //Xeno crawls from nearby vent,jumps at you, and goes back in var/obj/machinery/atmospherics/components/unary/vent_pump/pump = null var/obj/effect/hallucination/simple/xeno/xeno = null @@ -278,6 +271,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( px = -32 /datum/hallucination/oh_yeah + random_hallucination_weight = 1 var/obj/effect/hallucination/simple/bubblegum/bubblegum var/image/fakebroken var/image/fakerune @@ -333,6 +327,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( return ..() /datum/hallucination/battle + random_hallucination_weight = 3 /datum/hallucination/battle/New(mob/living/carbon/C, forced = TRUE, battle_type) set waitfor = FALSE @@ -408,6 +403,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/items_other + random_hallucination_weight = 1 /datum/hallucination/items_other/New(mob/living/carbon/C, forced = TRUE, item_type) set waitfor = FALSE @@ -511,6 +507,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/delusion + random_hallucination_weight = 1 var/list/image/delusions = list() /datum/hallucination/delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300,skip_nearby = TRUE, custom_icon = null, custom_icon_file = null, custom_name = null) @@ -566,6 +563,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( return ..() /datum/hallucination/self_delusion + random_hallucination_weight = 1 var/image/delusion /datum/hallucination/self_delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300, custom_icon = null, custom_icon_file = null, wabbajack = TRUE) //set wabbajack to false if you want to use another fake source @@ -607,6 +605,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( return ..() /datum/hallucination/bolts + random_hallucination_weight = 7 var/list/locks = list() /datum/hallucination/bolts/New(mob/living/carbon/C, forced, door_number) @@ -658,26 +657,27 @@ GLOBAL_LIST_INIT(hallucination_list, list( return FALSE /datum/hallucination/chat + random_hallucination_weight = 100 /datum/hallucination/chat/New(mob/living/carbon/C, forced = TRUE, force_radio, specific_message) set waitfor = FALSE ..() var/target_name = target.first_name() - var/speak_messages = list("[pick_list_replacements(HAL_LINES_FILE, "suspicion")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "conversation")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "greetings")][target.first_name()]!",\ - "[pick_list_replacements(HAL_LINES_FILE, "getout")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "weird")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "didyouhearthat")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "doubt")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "aggressive")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "help")]!!",\ - "[pick_list_replacements(HAL_LINES_FILE, "escape")]",\ - "I'm infected, [pick_list_replacements(HAL_LINES_FILE, "infection_advice")]!") - - var/radio_messages = list("[pick_list_replacements(HAL_LINES_FILE, "people")] is [pick_list_replacements(HAL_LINES_FILE, "accusations")]!",\ + var/speak_messages = list("[pick_list_replacements(HALLUCINATION_FILE, "suspicion")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "conversation")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "greetings")][target.first_name()]!",\ + "[pick_list_replacements(HALLUCINATION_FILE, "getout")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "weird")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "didyouhearthat")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "doubt")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "aggressive")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "help")]!!",\ + "[pick_list_replacements(HALLUCINATION_FILE, "escape")]",\ + "I'm infected, [pick_list_replacements(HALLUCINATION_FILE, "infection_advice")]!") + + var/radio_messages = list("[pick_list_replacements(HALLUCINATION_FILE, "people")] is [pick_list_replacements(HALLUCINATION_FILE, "accusations")]!",\ "Help!",\ - "[pick_list_replacements(HAL_LINES_FILE, "threat")] in [pick_list_replacements(HAL_LINES_FILE, "location")][prob(50)?"!":"!!"]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "threat")] in [pick_list_replacements(HALLUCINATION_FILE, "location")][prob(50)?"!":"!!"]",\ "[pick("Where's [target.first_name()]?", "Set [target.first_name()] to arrest!")]",\ "[pick("C","Ai, c","Someone c","Rec")]all the shuttle!",\ "AI [pick("rogue", "is dead")]!!") @@ -718,6 +718,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/message + random_hallucination_weight = 60 /datum/hallucination/message/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -766,13 +767,14 @@ GLOBAL_LIST_INIT(hallucination_list, list( span_warning("You hear skittering on the ceiling."), span_warning("You see an inhumanly tall silhouette moving in the distance.")) if(prob(10)) - message_pool.Add("[pick_list_replacements(HAL_LINES_FILE, "advice")]") + message_pool.Add("[pick_list_replacements(HALLUCINATION_FILE, "advice")]") var/chosen = pick(message_pool) feedback_details += "Message: [chosen]" to_chat(target, chosen) qdel(src) /datum/hallucination/sounds + random_hallucination_weight = 5 /datum/hallucination/sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) set waitfor = FALSE @@ -829,6 +831,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/weird_sounds + random_hallucination_weight = 1 /datum/hallucination/weird_sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) set waitfor = FALSE @@ -873,6 +876,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/stationmessage + random_hallucination_weight = 1 /datum/hallucination/stationmessage/New(mob/living/carbon/C, forced = TRUE, message) set waitfor = FALSE @@ -919,6 +923,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( to_chat(target, span_boldannounce("You feel reality distort for a moment...")) /datum/hallucination/hudscrew + random_hallucination_weight = 4 /datum/hallucination/hudscrew/New(mob/living/carbon/C, forced = TRUE, screwyhud_type) set waitfor = FALSE @@ -934,6 +939,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/fake_alert + random_hallucination_weight = 1 /datum/hallucination/fake_alert/New(mob/living/carbon/C, forced = TRUE, specific, duration = 15 SECONDS) set waitfor = FALSE @@ -989,6 +995,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/items + random_hallucination_weight = 1 /datum/hallucination/items/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -1051,6 +1058,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/dangerflash + random_hallucination_weight = 5 /datum/hallucination/dangerflash/New(mob/living/carbon/C, forced = TRUE, danger_type) set waitfor = FALSE @@ -1151,6 +1159,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( new /datum/hallucination/shock(target) /datum/hallucination/death + random_hallucination_weight = 1 /datum/hallucination/death/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -1179,6 +1188,8 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/fire + random_hallucination_weight = 3 + var/active = TRUE var/stage = 0 var/image/fire_overlay @@ -1230,6 +1241,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/shock + random_hallucination_weight = 1 var/image/shock_image var/image/electrocution_skeleton_anim @@ -1246,13 +1258,12 @@ GLOBAL_LIST_INIT(hallucination_list, list( if(target.client) target.client.images |= shock_image target.client.images |= electrocution_skeleton_anim - addtimer(CALLBACK(src, PROC_REF(reset_shock_animation)), 40) + addtimer(CALLBACK(src, PROC_REF(reset_shock_animation)), 4 SECONDS) target.playsound_local(get_turf(src), "sparks", 100, 1) target.staminaloss += 50 - target.Stun(40) - target.jitteriness += 1000 - target.do_jitter_animation(target.jitteriness) - addtimer(CALLBACK(src, PROC_REF(shock_drop)), 20) + target.Stun(4 SECONDS) + target.do_jitter_animation(1000) + addtimer(CALLBACK(src, PROC_REF(shock_drop)), 2 SECONDS) /datum/hallucination/shock/proc/reset_shock_animation() if(target.client) @@ -1260,10 +1271,11 @@ GLOBAL_LIST_INIT(hallucination_list, list( target.client.images.Remove(electrocution_skeleton_anim) /datum/hallucination/shock/proc/shock_drop() - target.jitteriness = max(target.jitteriness - 990, 10) //Still jittery, but vastly less - target.Paralyze(60) + target.adjust_jitter(10 SECONDS) //Still jittery, but vastly less + target.Paralyze(6 SECONDS) /datum/hallucination/husks + random_hallucination_weight = 8 /datum/hallucination/husks/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -1296,6 +1308,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( //hallucination projectile code in code/modules/projectiles/projectile/special.dm /datum/hallucination/stray_bullet + random_hallucination_weight = 7 /datum/hallucination/stray_bullet/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm index c5939aa507ab..74af5070fc36 100644 --- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm +++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm @@ -571,4 +571,4 @@ name = "Genius Dry Stout" desc = "A fresh bottle of stout, popularized by inhabitants of Space Ireland." icon_state = "stout_bottle" - list_reagents = list(/datum/reagent/consumable/ethanol/beer/stout = 40) \ No newline at end of file + list_reagents = list(/datum/reagent/consumable/ethanol/beer/stout = 40) diff --git a/code/modules/food_and_drinks/food/snacks_egg.dm b/code/modules/food_and_drinks/food/snacks_egg.dm index 31fa3fe15dbd..296272cb8470 100644 --- a/code/modules/food_and_drinks/food/snacks_egg.dm +++ b/code/modules/food_and_drinks/food/snacks_egg.dm @@ -28,7 +28,7 @@ egg_rper = new(src) egg_rper.real_name = name egg_rper.name = name - egg_rper.stat = CONSCIOUS + egg_rper.set_stat(CONSCIOUS) user.mind.transfer_to(egg_rper) return BRUTELOSS diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm index e65b6017ea01..e9fc9463db46 100644 --- a/code/modules/food_and_drinks/food/snacks_pastry.dm +++ b/code/modules/food_and_drinks/food/snacks_pastry.dm @@ -705,7 +705,7 @@ contents += P update_overlays(P) P = I - clearlist(P.contents) + LAZYCLEARLIST(P.contents) return else if(contents.len) var/obj/O = contents[contents.len] diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm index 0a4a9c8aa5fe..1f023fd0863a 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm @@ -202,7 +202,7 @@ God bless America. the_nugget.nugget_man = new(the_nugget) the_nugget.nugget_man.real_name = the_nugget.name the_nugget.nugget_man.name = the_nugget.name - the_nugget.nugget_man.stat = CONSCIOUS + the_nugget.nugget_man.set_stat(CONSCIOUS) the_guy.mind.transfer_to(the_nugget.nugget_man) qdel(the_guy) return diff --git a/code/modules/holodeck/area_copy.dm b/code/modules/holodeck/area_copy.dm index fcc2ed8389e6..0897a4768918 100644 --- a/code/modules/holodeck/area_copy.dm +++ b/code/modules/holodeck/area_copy.dm @@ -1,9 +1,10 @@ //Vars that will not be copied when using /DuplicateObject -GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( +GLOBAL_LIST_INIT(duplicate_forbidden_vars, list( "tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key", "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup", "client_mobs_in_contents", "bodyparts", "internal_organs", "hand_bodyparts", "hud_list", - "actions", "AIStatus", "computer_id", "lastKnownIP", "implants", "tgui_shared_states" + "actions", "AIStatus", "computer_id", "lastKnownIP", "implants", "tgui_shared_states", "active_hud_list", + "important_recursive_contents", "update_on_z", )) /proc/DuplicateObject(atom/original, perfectcopy = TRUE, sameloc, atom/newloc = null, nerf, holoitem) @@ -114,13 +115,13 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( var/obj/O2 = DuplicateObject(O , perfectcopy=TRUE, newloc = B, nerf=nerf_weapons, holoitem=TRUE) if(!O2) continue - copiedobjs += O2.GetAllContents() + copiedobjs += O2.get_all_contents() for(var/mob/M in T) if(iscameramob(M)) continue // If we need to check for more mobs, I'll add a variable var/mob/SM = DuplicateObject(M , perfectcopy=TRUE, newloc = B, holoitem=TRUE) - copiedobjs += SM.GetAllContents() + copiedobjs += SM.get_all_contents() for(var/V in T.vars - GLOB.duplicate_forbidden_vars) if(V == "air") diff --git a/code/modules/hydroponics/grown/flowers.dm b/code/modules/hydroponics/grown/flowers.dm index bc5e695fc776..118e058a5f3e 100644 --- a/code/modules/hydroponics/grown/flowers.dm +++ b/code/modules/hydroponics/grown/flowers.dm @@ -233,7 +233,7 @@ if(isliving(M)) to_chat(M, span_danger("You are lit on fire from the intense heat of the [name]!")) M.adjust_fire_stacks(seed.potency / 20) - if(M.IgniteMob()) + if(M.ignite_mob()) message_admins("[ADMIN_LOOKUPFLW(user)] set [ADMIN_LOOKUPFLW(M)] on fire with [src] at [AREACOORD(user)]") log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm index f898ae966fa9..bc7dc006be89 100644 --- a/code/modules/hydroponics/grown/towercap.dm +++ b/code/modules/hydroponics/grown/towercap.dm @@ -263,7 +263,7 @@ else if(isliving(A)) var/mob/living/L = A L.adjust_fire_stacks(fire_stack_strength * 0.5 * delta_time) - L.IgniteMob() + L.ignite_mob() /obj/structure/bonfire/proc/Cook(delta_time = 2) var/turf/current_location = get_turf(src) @@ -274,7 +274,7 @@ else if(isliving(A)) //It's still a fire, idiot. var/mob/living/L = A L.adjust_fire_stacks(fire_stack_strength * 0.5 * delta_time) - L.IgniteMob() + L.ignite_mob() else if(G.GetComponent(/datum/component/grillable)) if(SEND_SIGNAL(G, COMSIG_ITEM_GRILLED, src) & COMPONENT_HANDLED_GRILLING) continue diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm index 16d9b1410bd9..1f4886b02ddb 100644 --- a/code/modules/hydroponics/plant_genes.dm +++ b/code/modules/hydroponics/plant_genes.dm @@ -233,7 +233,7 @@ /datum/plant_gene/trait/cell_charge/on_consume(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/target) if(!G.reagents.total_volume) var/batteries_recharged = 0 - for(var/obj/item/stock_parts/cell/C in target.GetAllContents()) + for(var/obj/item/stock_parts/cell/C in target.get_all_contents()) var/newcharge = min(G.seed.potency*0.01*C.maxcharge, C.maxcharge) if(C.charge < newcharge) C.charge = newcharge @@ -330,7 +330,7 @@ if(iscarbon(target)) var/mob/living/carbon/C = target C.adjust_disgust(15) //Two teleports is safe - C.confused += 7 + C.adjust_confusion(7 SECONDS) /datum/plant_gene/trait/teleport/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C) var/teleport_radius = max(round(G.seed.potency * rate), 1) //max of 5 diff --git a/code/modules/instruments/items.dm b/code/modules/instruments/items.dm index dc94eda440d3..59fd4e84fde6 100644 --- a/code/modules/instruments/items.dm +++ b/code/modules/instruments/items.dm @@ -213,6 +213,17 @@ icon_state = "recorder" allowed_instrument_ids = "recorder" +/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 ..() + /obj/item/instrument/harmonica name = "harmonica" desc = "For when you get a bad case of the space blues." diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index e64703912446..5677347d72e8 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -75,8 +75,12 @@ var/paycheck = PAYCHECK_MINIMAL /// Where to pull money to pay people var/paycheck_department = ACCOUNT_CIV - /// Traits assigned from jobs + /// Traits added to the mind of the mob assigned this job var/list/mind_traits + + ///Lazylist of traits added to the liver of the mob assigned this job (used for the classic "cops heal from donuts" reaction, among others) + var/list/liver_traits = null + /// Display order of the job var/display_order = JOB_DISPLAY_ORDER_DEFAULT @@ -136,13 +140,17 @@ //Only override this proc //H is usually a human unless an /equip override transformed it -/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE) - //do actions on H but send messages to M as the key may not have been transferred_yet - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, H, M, latejoin) - if(mind_traits) - for(var/t in mind_traits) - ADD_TRAIT(H.mind, t, JOB_TRAIT) - H.mind.add_employee(/datum/corporation/nanotrasen) +/datum/job/proc/after_spawn(mob/living/spawned, mob/M, latejoin = FALSE) + SHOULD_CALL_PARENT(TRUE) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, spawned, M, latejoin) + for(var/trait in mind_traits) + ADD_TRAIT(spawned.mind, trait, JOB_TRAIT) + + var/obj/item/organ/liver/liver = spawned.getorganslot(ORGAN_SLOT_LIVER) + if(liver) + for(var/trait in liver_traits) + ADD_TRAIT(liver, trait, JOB_TRAIT) + spawned.mind.add_employee(/datum/corporation/nanotrasen) /datum/job/proc/announce(mob/living/carbon/human/H) if(head_announce) @@ -337,7 +345,7 @@ H.equip_to_slot_if_possible(C, SLOT_WEAR_ID) if(H.stat != DEAD)//if a job has a gps and it isn't a decorative corpse, rename the GPS to the owner's name - for(var/obj/item/gps/G in H.GetAllContents()) + for(var/obj/item/gps/G in H.get_all_contents()) G.gpstag = H.real_name G.name = "global positioning system ([G.gpstag])" continue diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm index ae01c4fd3f3a..35e295a7d20e 100644 --- a/code/modules/jobs/job_types/atmospheric_technician.dm +++ b/code/modules/jobs/job_types/atmospheric_technician.dm @@ -64,4 +64,4 @@ mask = /obj/item/clothing/mask/gas suit = /obj/item/clothing/suit/space/hardsuit/engine/atmos suit_store = /obj/item/tank/internals/oxygen - internals_slot = SLOT_S_STORE + internals_slot = SLOT_SUIT_STORE diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index da8e15b89c43..11829c988074 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -84,4 +84,4 @@ glasses = /obj/item/clothing/glasses/meson/sunglasses gloves = /obj/item/clothing/gloves/color/yellow head = null - internals_slot = SLOT_S_STORE + internals_slot = SLOT_SUIT_STORE diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm index 394945edb273..d6293a483126 100644 --- a/code/modules/jobs/job_types/curator.dm +++ b/code/modules/jobs/job_types/curator.dm @@ -20,7 +20,7 @@ base_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION) paycheck = PAYCHECK_EASY paycheck_department = ACCOUNT_CIV - + mind_traits = list(TRAIT_BLOODSUCKER_HUNTER) display_order = JOB_DISPLAY_ORDER_CURATOR minimal_character_age = 18 //Don't need to be some aged-ass fellow to know how to care for things, possessions could easily have come from parents and the like. Bloodsucker knowledge is another thing, though that's likely mostly consulted by the book diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm index df44d53af8a5..ce5c1e04b416 100644 --- a/code/modules/jobs/job_types/cyborg.dm +++ b/code/modules/jobs/job_types/cyborg.dm @@ -29,6 +29,7 @@ return H.Robotize(FALSE, latejoin) /datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, mob/M) + . = ..() R.updatename(M.client) R.gender = NEUTER diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm index 0871df5b1ce4..8434c4726657 100644 --- a/code/modules/jobs/job_types/mime.dm +++ b/code/modules/jobs/job_types/mime.dm @@ -37,6 +37,7 @@ smells_like = "complete nothingness" /datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M) + . = ..() H.apply_pref_name(/datum/preference/name/mime, M.client) /datum/outfit/job/mime @@ -71,7 +72,8 @@ H.grant_language(/datum/language/french, TRUE, TRUE, LANGUAGE_MIME) if(H.mind) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null)) + var/datum/action/cooldown/spell/vow_of_silence/vow = new(H.mind) + vow.Grant(H) H.mind.miming = 1 /obj/item/book/mimery @@ -80,32 +82,55 @@ icon_state ="bookmime" /obj/item/book/mimery/attack_self(mob/user,) - user.set_machine(src) - var/dat = "" - dat += "Guide to Dank Mimery
" - dat += "Teaches one of three classic pantomime routines, allowing a practiced mime to conjure invisible objects into corporeal existence.
" - dat += "Once you have mastered your routine, this book will have no more to say to you.
" - dat += "
" - dat += "Invisible Wall
" - dat += "Invisible Chair
" - dat += "Invisible Box
" - dat += "" - user << browse(dat, "window=book") - -/obj/item/book/mimery/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained() || src.loc != usr) - return - if (!ishuman(usr)) + . = ..() + if(.) return - var/mob/living/carbon/human/H = usr - if(H.is_holding(src) && H.mind) - H.set_machine(src) - if (href_list["invisible_wall"]) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall(null)) - if (href_list["invisible_chair"]) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair(null)) - if (href_list["invisible_box"]) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box(null)) - to_chat(usr, span_notice("The book disappears into thin air.")) - qdel(src) + + var/list/spell_icons = list( + "Invisible Wall" = image(icon = 'icons/mob/actions/actions_mime.dmi', icon_state = "invisible_wall"), + "Invisible Chair" = image(icon = 'icons/mob/actions/actions_mime.dmi', icon_state = "invisible_chair"), + "Invisible Box" = image(icon = 'icons/mob/actions/actions_mime.dmi', icon_state = "invisible_box") + ) + var/picked_spell = show_radial_menu(user, src, spell_icons, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 36, require_near = TRUE) + var/datum/action/cooldown/spell/picked_spell_type + switch(picked_spell) + if("Invisible Wall") + picked_spell_type = /datum/action/cooldown/spell/conjure/invisible_wall + + if("Invisible Chair") + picked_spell_type = /datum/action/cooldown/spell/conjure/invisible_chair + + if("Invisible Box") + picked_spell_type = /datum/action/cooldown/spell/conjure_item/invisible_box + + if(ispath(picked_spell_type)) + // Gives the user a vow ability too, if they don't already 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) + + picked_spell_type = new picked_spell_type(user.mind || user) + picked_spell_type.Grant(user) + + to_chat(user, span_warning("The book disappears into thin air.")) + qdel(src) + + return TRUE + +/** + * Checks if we are allowed to interact with a radial menu + * + * Arguments: + * * user The human mob interacting with the menu + */ +/obj/item/book/mimery/proc/check_menu(mob/living/carbon/human/user) + if(!istype(user)) + return FALSE + if(!user.is_holding(src)) + return FALSE + if(user.incapacitated()) + return FALSE + if(!user.mind) + return FALSE + return TRUE diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 295b07866523..0443a35cdd9b 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -33,6 +33,8 @@ paycheck = PAYCHECK_COMMAND paycheck_department = ACCOUNT_SCI + liver_traits = list(TRAIT_BALLMER_SCIENTIST) + display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR minimal_character_age = 26 //Barely knows more than actual scientists, just responsibility and AI things @@ -86,4 +88,4 @@ mask = /obj/item/clothing/mask/breath suit = /obj/item/clothing/suit/space/hardsuit/rd suit_store = /obj/item/tank/internals/oxygen - internals_slot = SLOT_S_STORE + internals_slot = SLOT_SUIT_STORE diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm index 860a2021152a..5405cef5a80b 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -17,9 +17,12 @@ added_access = list(ACCESS_ROBO_CONTROL, ACCESS_TECH_STORAGE, ACCESS_GENETICS) base_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINERAL_STOREROOM) + paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_SCI + liver_traits = list(TRAIT_BALLMER_SCIENTIST) + display_order = JOB_DISPLAY_ORDER_SCIENTIST minimal_character_age = 24 //Consider the level of knowledge that spans xenobio, nanites, and toxins diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index bb89ea6a2c90..2b916810bdeb 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -123,7 +123,7 @@ GLOBAL_LIST_INIT(available_depts_sec, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICA else var/safety = 0 while(safety < 25) - T = safepick(get_area_turfs(destination)) + T = pick(get_area_turfs(destination)) if(T && !H.Move(T)) safety += 1 continue diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index 69b3c9019460..4398f90b79dc 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -75,7 +75,7 @@ mask = /obj/item/clothing/mask/gas/explorer glasses = /obj/item/clothing/glasses/meson suit_store = /obj/item/tank/internals/oxygen - internals_slot = SLOT_S_STORE + internals_slot = SLOT_SUIT_STORE backpack_contents = list( /obj/item/storage/bag/ore=1, /obj/item/kitchen/knife/combat/survival=1, diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm index b4455bff286a..e08a4ccccb2c 100644 --- a/code/modules/jobs/job_types/station_engineer.dm +++ b/code/modules/jobs/job_types/station_engineer.dm @@ -125,7 +125,7 @@ GLOBAL_LIST_INIT(available_depts_eng, list(ENG_DEPT_MEDICAL, ENG_DEPT_SCIENCE, E suit = /obj/item/clothing/suit/space/hardsuit/engine suit_store = /obj/item/tank/internals/oxygen head = null - internals_slot = SLOT_S_STORE + internals_slot = SLOT_SUIT_STORE /obj/item/radio/headset/headset_eng/department/Initialize() . = ..() diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index 768312e6c656..703f47e6f33d 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -195,7 +195,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) if(!ispath(component_type,/datum/component)) CRASH("Wrong component type in [type] - [component_type] is not a component") var/turf/T = get_turf(src) - for(var/atom/A in T.GetAllContents()) + for(var/atom/A in T.get_all_contents()) if(A == src) continue if(target_name && A.name != target_name) diff --git a/code/modules/mapping/minimap.dm b/code/modules/mapping/minimap.dm index 7ededf6ddc43..6b7293ade551 100644 --- a/code/modules/mapping/minimap.dm +++ b/code/modules/mapping/minimap.dm @@ -86,7 +86,7 @@ span_userdanger("You miss the map and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/mining/aux_base_camera.dm b/code/modules/mining/aux_base_camera.dm index 994e91766951..efb92142f8e4 100644 --- a/code/modules/mining/aux_base_camera.dm +++ b/code/modules/mining/aux_base_camera.dm @@ -130,7 +130,7 @@ eyeobj.invisibility = INVISIBILITY_MAXIMUM //Hide the eye when not in use. /datum/action/innate/aux_base //Parent aux base action - icon_icon = 'icons/mob/actions/actions_construction.dmi' + button_icon = 'icons/mob/actions/actions_construction.dmi' var/mob/living/C //Mob using the action var/mob/camera/aiEye/remote/base_construction/remote_eye //Console's eye mob var/obj/machinery/computer/camera_advanced/base_construction/B //Console itself diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm index 4ef8b67b1904..b5e179ce4912 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -143,7 +143,7 @@ add_overlay("legion_soul_crackle") for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/organ/regenerative_core/legion/go_inert() ..() diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm index 10fd17df362c..e4f7f6056887 100644 --- a/code/modules/mining/equipment/survival_pod.dm +++ b/code/modules/mining/equipment/survival_pod.dm @@ -319,7 +319,6 @@ /obj/item/gun/magic/wand/fireball, /obj/item/stack/telecrystal/twenty, /obj/item/nuke_core, - /obj/item/phylactery, /obj/item/bikehorn) /obj/item/fakeartefact/Initialize() diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm index a0ee74057612..11a91f89f3f2 100644 --- a/code/modules/mining/fulton.dm +++ b/code/modules/mining/fulton.dm @@ -114,7 +114,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons) if(ishuman(A)) var/mob/living/carbon/human/L = A L.SetUnconscious(0) - L.drowsyness = 0 + L.remove_status_effect(/datum/status_effect/drowsiness) L.SetSleeping(0) sleep(3 SECONDS) var/list/flooring_near_beacon = list() @@ -186,7 +186,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons) var/mob/living/L = A if(L.stat != DEAD) return 1 - for(var/thing in A.GetAllContents()) + for(var/thing in A.get_all_contents()) if(isliving(A)) var/mob/living/L = A if(L.stat != DEAD) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index fedf04b6e5f3..917258e61e4c 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -140,13 +140,13 @@ GLOBAL_LIST_EMPTY(aide_list) to_chat(itemUser, failText) return to_chat(itemUser, span_notice("The snake, satisfied with your oath, attaches itself and the rod to your forearm with an inseparable grip. Your thoughts seem to only revolve around the core idea of helping others, and harm is nothing more than a distant, wicked memory...")) - var/datum/status_effect/hippocraticOath/effect = itemUser.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH, efficiency, type) + var/datum/status_effect/hippocratic_oath/effect = itemUser.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH, efficiency, type) effect.hand = usedHand activated() /obj/item/rod_of_asclepius/proc/activated() item_flags = DROPDEL - ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) + ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT(type)) desc = "A short wooden rod with a mystical snake inseparably gripping itself and the rod to your forearm. It flows with a healing energy that disperses amongst yourself and those around you. The snake can learn surgeries from disks or operating consoles by hitting them with it." icon_state = "asclepius_active" activated = TRUE @@ -938,7 +938,7 @@ GLOBAL_LIST_EMPTY(aide_list) new /obj/item/melee/ghost_sword(src) if(2) new /obj/item/lava_staff(src) - new /obj/item/book/granter/spell/sacredflame(src) + new /obj/item/book/granter/action/spell/sacredflame(src) if(3) new /obj/item/dragon_egg(src) if(4) @@ -1071,9 +1071,8 @@ GLOBAL_LIST_EMPTY(aide_list) H.set_species(/datum/species/skeleton) if(2) to_chat(user, span_danger("Power courses through you! You can now shift your form at will.")) - if(user.mind) - var/obj/effect/proc_holder/spell/targeted/shapeshift/dragon/D = new - user.mind.AddSpell(D) + var/datum/action/cooldown/spell/shapeshift/dragon/dragon_shapeshift = new(user.mind || user) + dragon_shapeshift.Grant(user) if(3) to_chat(user, span_danger("You feel like you could walk straight through lava now.")) H.weather_immunities |= "lava" @@ -1291,6 +1290,19 @@ GLOBAL_LIST_EMPTY(aide_list) #define COOLDOWN_HUMAN 100 #define COOLDOWN_ANIMAL 60 #define COOLDOWN_SPLASH 100 + +/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." + button_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." + button_icon = 'icons/effects/effects.dmi' + button_icon_state = "rshield" + /obj/item/melee/knuckles name = "bloody knuckles" desc = "Knuckles born of a desire for violence. Made to ensure their victims stay in the fight until there's a winner. Activating these knuckles covers several meters \ @@ -1524,7 +1536,7 @@ GLOBAL_LIST_EMPTY(aide_list) playsound(T,'sound/magic/blind.ogg', 200, 1, -4) new /obj/effect/temp_visual/hierophant/telegraph/teleport(T, user) beacon = new/obj/effect/hierophant(T) - user.update_action_buttons_icon() + user.update_mob_action_buttons() user.visible_message("[span_hierophant_warning("[user] places a strange machine beneath [user.p_their()] feet!")]", \ "[span_hierophant("You detach the hierophant beacon, allowing you to teleport yourself and any allies to it at any time!")]\n\ [span_notice("You can remove the beacon to place it again by striking it with the club.")]") @@ -1544,7 +1556,7 @@ GLOBAL_LIST_EMPTY(aide_list) to_chat(user, span_warning("You don't have enough space to teleport from here!")) return teleporting = TRUE //start channel - user.update_action_buttons_icon() + user.update_mob_action_buttons() user.visible_message("[span_hierophant_warning("[user] starts to glow faintly...")]") timer = world.time + 5 SECONDS INVOKE_ASYNC(src, PROC_REF(prepare_icon_update)) @@ -1557,7 +1569,7 @@ GLOBAL_LIST_EMPTY(aide_list) if(is_blocked_turf(T, TRUE)) teleporting = FALSE to_chat(user, span_warning("The beacon is blocked by something, preventing teleportation!")) - user.update_action_buttons_icon() + user.update_mob_action_buttons() timer = world.time INVOKE_ASYNC(src, PROC_REF(prepare_icon_update)) beacon.icon_state = "hierophant_tele_off" @@ -1569,7 +1581,7 @@ GLOBAL_LIST_EMPTY(aide_list) if(!do_after(user, 0.3 SECONDS, user) || !user || !beacon || QDELETED(beacon)) //no walking away shitlord teleporting = FALSE if(user) - user.update_action_buttons_icon() + user.update_mob_action_buttons() timer = world.time INVOKE_ASYNC(src, PROC_REF(prepare_icon_update)) if(beacon) @@ -1578,7 +1590,7 @@ GLOBAL_LIST_EMPTY(aide_list) if(is_blocked_turf(T, TRUE)) teleporting = FALSE to_chat(user, span_warning("The beacon is blocked by something, preventing teleportation!")) - user.update_action_buttons_icon() + user.update_mob_action_buttons() timer = world.time INVOKE_ASYNC(src, PROC_REF(prepare_icon_update)) beacon.icon_state = "hierophant_tele_off" @@ -1606,7 +1618,7 @@ GLOBAL_LIST_EMPTY(aide_list) beacon.icon_state = "hierophant_tele_off" teleporting = FALSE if(user) - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/hierophant_club/proc/teleport_mob(turf/source, mob/M, turf/target, mob/user) var/turf/turf_to_teleport_to = get_step(target, get_dir(source, M)) //get position relative to caster @@ -1794,9 +1806,22 @@ GLOBAL_LIST_EMPTY(aide_list) new /obj/item/wisp_lantern(src) //Legion -#define COOLDOWN_TAP 60 -#define COOLDOWN_BAND 200 -#define COOLDOWN_TELE 15 +#define COOLDOWN_TAP 6 SECONDS +#define COOLDOWN_BAND 20 SECONDS +#define COOLDOWN_TELE 1.5 SECONDS + +/datum/action/item_action/band + name = "Band" + desc = "Summon all your thralls to your location." + button_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." + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "horde"*/ + /obj/item/cane/cursed name = "cursed cane" desc = "A pristine marble cane. Tapping the cane against the ground calls lesser minions to you while tapping it against a dead or dying victim will make them yours should you\ diff --git a/code/modules/mining/lavaland/seismicarm.dm b/code/modules/mining/lavaland/seismicarm.dm index 198169dfedea..30e3e37dad7e 100644 --- a/code/modules/mining/lavaland/seismicarm.dm +++ b/code/modules/mining/lavaland/seismicarm.dm @@ -1,9 +1,10 @@ -/obj/effect/proc_holder/spell/targeted/seismic - clothes_req = FALSE - include_user = TRUE - range = -1 +/datum/action/cooldown/spell/pointed/seismic + spell_requirements = SPELL_REQUIRES_HUMAN -/obj/effect/proc_holder/spell/targeted/seismic/can_cast(mob/living/user) +/datum/action/cooldown/spell/pointed/seismic/can_cast_spell() + if(!isliving(owner)) + return FALSE + var/mob/living/user = owner var/obj/item/bodypart/r_arm/R = user.get_bodypart(BODY_ZONE_R_ARM) if(R?.bodypart_disabled) to_chat(user, span_warning("The arm isn't in a functional state right now!")) @@ -12,15 +13,19 @@ return FALSE return TRUE -/obj/effect/proc_holder/spell/targeted/seismic/lariat +/datum/action/cooldown/spell/pointed/seismic/lariat name = "Lariat" desc = "Dash forward, catching whatever animal you hit with your arm and knocking them over." - action_icon = 'icons/mob/actions/actions_arm.dmi' - action_icon_state = "lariat" - charge_max = 70 + button_icon = 'icons/mob/actions/actions_arm.dmi' + button_icon_state = "lariat" + + cooldown_time = 7 SECONDS var/jumpdistance = 4 -/obj/effect/proc_holder/spell/targeted/seismic/lariat/cast(atom/target,mob/living/user) +/datum/action/cooldown/spell/pointed/seismic/lariat/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(!.) + return FALSE if(user.incapacitated()) return var/turf/T = get_step(get_turf(user), user.dir) @@ -62,16 +67,22 @@ L.adjustBruteLoss(12) playsound(L,'sound/effects/meteorimpact.ogg', 60, 1) T = get_step(user,user.dir) + + return TRUE -/obj/effect/proc_holder/spell/targeted/seismic/mop +/datum/action/cooldown/spell/pointed/seismic/mop name = "Mop the Floor" desc = "Launch forward and drag whoever's in front of you on the ground. The power of this move increases with closeness to the target upon using it." - action_icon = 'icons/mob/actions/actions_arm.dmi' - action_icon_state = "mop" - charge_max = 70 + button_icon = 'icons/mob/actions/actions_arm.dmi' + button_icon_state = "mop" + + cooldown_time = 7 SECONDS var/jumpdistance = 4 -/obj/effect/proc_holder/spell/targeted/seismic/mop/cast(atom/target,mob/living/user) +/datum/action/cooldown/spell/pointed/seismic/mop/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(!.) + return if(user.incapacitated()) return var/turf/T = get_step(get_turf(user), user.dir) @@ -133,14 +144,20 @@ if(C.stat == CONSCIOUS && C.resting == FALSE) animate(C, transform = null, time = 0.5 SECONDS, loop = 0) -/obj/effect/proc_holder/spell/targeted/seismic/suplex + return TRUE + +/datum/action/cooldown/spell/pointed/seismic/suplex name = "Suplex" desc = "Grab the target in front of you and slam them back onto the ground." - action_icon = 'icons/mob/actions/actions_arm.dmi' - action_icon_state = "suplex" - charge_max = 10 + button_icon = 'icons/mob/actions/actions_arm.dmi' + button_icon_state = "suplex" + + cooldown_time = 1 SECONDS -/obj/effect/proc_holder/spell/targeted/seismic/suplex/cast(atom/target,mob/living/user) +/datum/action/cooldown/spell/pointed/seismic/suplex/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(!.) + return FALSE var/turf/T = get_step(get_turf(user), user.dir) var/turf/Z = get_turf(user) user.visible_message(span_warning("[user] outstretches [user.p_their()] arm and goes for a grab!")) @@ -186,18 +203,23 @@ if(L.stat == CONSCIOUS && L.resting == FALSE) animate(L, transform = null, time = 0.1 SECONDS, loop = 0) -/obj/effect/proc_holder/spell/targeted/seismic/righthook +/datum/action/cooldown/spell/pointed/seismic/righthook name = "Right Hook" desc = "Put the arm through its paces, cranking the outputs located at the front and back of the hand to full capacity for a powerful blow. This attack can only be readied for five seconds and connecting with it will temporarily overwhelm the entire arm for fifteen." - action_icon = 'icons/mob/actions/actions_arm.dmi' - action_icon_state = "ponch" - charge_max = 30 + button_icon = 'icons/mob/actions/actions_arm.dmi' + button_icon_state = "ponch" -/obj/effect/proc_holder/spell/targeted/seismic/righthook/cast(list/targets, mob/living/user) + cooldown_time = 3 SECONDS + +/datum/action/cooldown/spell/pointed/seismic/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(!.) + return FALSE playsound(user,'sound/effects/beepskyspinsabre.ogg', 60, 1) do_after(user, 2 SECONDS, user, TRUE, stayStill = FALSE) user.put_in_r_hand(new /obj/item/overcharged_emitter) user.visible_message(span_warning("[user]'s arm begins crackling loudly!")) + return TRUE /obj/item/overcharged_emitter name = "supercharged emitter" @@ -290,15 +312,12 @@ /obj/item/bodypart/r_arm/robot/seismic/attach_limb(mob/living/carbon/C, special) . = ..() - C.AddSpell (new /obj/effect/proc_holder/spell/targeted/seismic/lariat) - C.AddSpell (new /obj/effect/proc_holder/spell/targeted/seismic/mop) - C.AddSpell (new /obj/effect/proc_holder/spell/targeted/seismic/suplex) - C.AddSpell (new /obj/effect/proc_holder/spell/targeted/seismic/righthook) + for(var/datum/action/cooldown/spell/pointed/seismic/spells as anything in subtypesof(/datum/action/cooldown/spell/pointed/seismic)) + spells = new(C) + spells.Grant(C) /obj/item/bodypart/r_arm/robot/seismic/drop_limb(special) var/mob/living/carbon/C = owner - C.RemoveSpell (/obj/effect/proc_holder/spell/targeted/seismic/lariat) - C.RemoveSpell (/obj/effect/proc_holder/spell/targeted/seismic/mop) - C.RemoveSpell (/obj/effect/proc_holder/spell/targeted/seismic/suplex) - C.RemoveSpell (/obj/effect/proc_holder/spell/targeted/seismic/righthook) - ..() + for(var/datum/action/cooldown/spell/pointed/seismic/spells in C.actions) + spells.Remove(C) + return ..() diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm index f8ac55148d16..1c2374ab6c39 100644 --- a/code/modules/mining/minebot.dm +++ b/code/modules/mining/minebot.dm @@ -226,8 +226,9 @@ /datum/action/innate/minedrone check_flags = AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/actions_mecha.dmi' + button_icon = 'icons/mob/actions/actions_mecha.dmi' background_icon_state = "bg_default" + overlay_icon_state = "bg_default_border" /datum/action/innate/minedrone/toggle_light name = "Toggle Light" diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 636af2f4b5e7..a950925c99f6 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -138,7 +138,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ return C.adjust_blurriness(6) C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage - C.confused += 5 + C.adjust_confusion(5 SECONDS) to_chat(C, span_userdanger("\The [src] gets into your eyes! The pain, it burns!")) qdel(src) diff --git a/code/modules/mob/dead/emote.dm b/code/modules/mob/dead/emote.dm index cbcd4a9830d5..0f93953fdf57 100644 --- a/code/modules/mob/dead/emote.dm +++ b/code/modules/mob/dead/emote.dm @@ -8,11 +8,11 @@ key_third_person = "dabs" message = "dabs." message_param = "dabs on %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/dead/dab/run_emote(mob/user, params) . = ..() var/mob/dead/observer/H = user var/light_dab_angle = rand(35,55) var/light_dab_speed = rand(3,7) - H.DabAnimation(angle = light_dab_angle , speed = light_dab_speed) \ No newline at end of file + H.DabAnimation(angle = light_dab_angle , speed = light_dab_speed) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 5fa3a30a3a8c..57311a10fe05 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -63,6 +63,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) // Current Viewrange var/view = 0 + /mob/dead/observer/Initialize() set_invisibility(GLOB.observer_default_invisibility) @@ -720,12 +721,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/dead/observer/proc/show_data_huds() for(var/hudtype in datahuds) var/datum/atom_hud/H = GLOB.huds[hudtype] - H.add_hud_to(src) + H.show_to(src) /mob/dead/observer/proc/remove_data_huds() for(var/hudtype in datahuds) var/datum/atom_hud/H = GLOB.huds[hudtype] - H.remove_hud_from(src) + H.hide_from(src) /mob/dead/observer/verb/toggle_data_huds() set name = "Toggle Sec/Med/Diag HUD" @@ -851,35 +852,51 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/dead/observer/proc/cleanup_observe() var/mob/target = observetarget + observetarget = null client?.perspective = initial(client.perspective) sight = initial(sight) - UnregisterSignal(target, COMSIG_MOVABLE_Z_CHANGED) - if(target && target.observers) - target.observers -= src - UNSETEMPTY(target.observers) - observetarget = null + if(target) + UnregisterSignal(target, COMSIG_MOVABLE_Z_CHANGED) + hide_other_mob_action_buttons(target) + LAZYREMOVE(target.observers, src) actions = originalactions actions -= UO update_action_buttons() /mob/dead/observer/verb/observe() set name = "Observe" - set category = "OOC" + set category = "Ghost" - var/list/creatures = getpois() + if(!isobserver(usr)) //Make sure they're an observer! + return reset_perspective(null) - var/eye_name = null + var/list/possible_destinations = getpois() + var/target = null - eye_name = input("Please, select a player!", "Observe", null, null) as null|anything in creatures - - if (!eye_name) + target = tgui_input_list(usr, "Please, select a player!", "Jump to Mob", possible_destinations) + if(isnull(target)) return + if(!isobserver(usr)) + return + + // In nullspace, invalid as a POI. provisonary till we get new POI system + if(ismob(target)) + var/mob/mob_target = target + if(!mob_target.loc) + return + + var/mob/chosen_target = possible_destinations[target] - do_observe(creatures[eye_name]) + do_observe(chosen_target) /mob/dead/observer/proc/do_observe(mob/mob_eye) + if(isnewplayer(mob_eye)) + stack_trace("/mob/dead/new_player: \[[mob_eye]\] is being observed by [key_name(src)]. This should never happen and has been blocked.") + message_admins("[ADMIN_LOOKUPFLW(src)] attempted to observe someone in the lobby: [ADMIN_LOOKUPFLW(mob_eye)]. This should not be possible and has been blocked.") + return + //Istype so we filter out points of interest that are not mobs if(client && mob_eye && istype(mob_eye)) client.eye = mob_eye @@ -888,27 +905,25 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp sight = null //we dont want ghosts to see through walls in secret areas RegisterSignal(mob_eye, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_observing_z_changed), TRUE) if(!UO) - UO = new // Convinent way to unobserve + UO = new(src) // Convinent way to unobserve UO.Grant(src) if(mob_eye.hud_used) - actions = mob_eye.actions + originalactions - LAZYINITLIST(mob_eye.observers) - mob_eye.observers |= src + client.screen = list() + LAZYOR(mob_eye.observers, src) mob_eye.hud_used.show_hud(mob_eye.hud_used.hud_version, src) - update_action_buttons() - mob_eye.update_action_buttons() observetarget = mob_eye /datum/action/unobserve name = "Stop Observing" desc = "Stops observing the person." - icon_icon = 'icons/mob/mob.dmi' + button_icon = 'icons/mob/mob.dmi' button_icon_state = "ghost_nodir" + show_to_observers = FALSE /datum/action/unobserve/Trigger() owner.reset_perspective(null) -/datum/action/unobserve/IsAvailable() +/datum/action/unobserve/IsAvailable(feedback = FALSE) return TRUE /mob/dead/observer/proc/on_observing_z_changed(datum/source, oldz, newz) diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index 405852fdd4e5..075bbe7392b5 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -42,7 +42,7 @@ /datum/emote/flip key = "flip" key_third_person = "flips" - restraint_check = TRUE + hands_use_check = TRUE mob_type_allowed_typecache = list(/mob/living, /mob/dead/observer) mob_type_ignore_stat_typecache = list(/mob/dead/observer) cooldown = 0 SECONDS @@ -79,7 +79,7 @@ /datum/emote/spin key = "spin" key_third_person = "spins" - restraint_check = TRUE + hands_use_check = TRUE mob_type_allowed_typecache = list(/mob/living, /mob/dead/observer) mob_type_ignore_stat_typecache = list(/mob/dead/observer) cooldown = 0 SECONDS @@ -109,15 +109,14 @@ return if(!iscarbon(user)) return - var/current_confusion = user.confused - if(current_confusion > BEYBLADE_PUKE_THRESHOLD) + if(user.get_timed_status_effect_duration(/datum/status_effect/confusion) > BEYBLADE_PUKE_THRESHOLD) user.vomit(BEYBLADE_PUKE_NUTRIENT_LOSS, distance = 0) return + if(prob(BEYBLADE_DIZZINESS_PROBABILITY)) - to_chat(user, "You feel woozy from spinning.") - user.Dizzy(BEYBLADE_DIZZINESS_DURATION) - if(current_confusion < BEYBLADE_CONFUSION_LIMIT) - user.confused += BEYBLADE_CONFUSION_INCREMENT + to_chat(user, span_warning("You feel woozy from spinning.")) + user.set_dizzy_if_lower(BEYBLADE_DIZZINESS_DURATION) + user.adjust_confusion_up_to(BEYBLADE_CONFUSION_INCREMENT, BEYBLADE_CONFUSION_LIMIT) #undef BEYBLADE_PUKE_THRESHOLD #undef BEYBLADE_PUKE_NUTRIENT_LOSS diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 8fd1facd6db0..6b212efaa5ec 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -423,7 +423,7 @@ if(hidden_slots & HIDESHOES) obscured |= SLOT_SHOES if(hidden_slots & HIDESUITSTORAGE) - obscured |= SLOT_S_STORE + obscured |= SLOT_SUIT_STORE return obscured @@ -510,7 +510,7 @@ hand_bodyparts[i] = BP ..() //Don't redraw hands until we have organs for them -//GetAllContents that is reasonable and not stupid +//get_all_contents that is reasonable and not stupid /mob/living/carbon/proc/get_all_gear() var/list/processing_list = get_equipped_items(TRUE) + held_items listclearnulls(processing_list) // handles empty hands diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 826e30ca5379..0c5d065ce298 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -202,16 +202,16 @@ //Gets blood from mob to a container or other mob, preserving all data in it. /mob/living/proc/transfer_blood_to(atom/movable/AM, total_amount, forced) if(!blood_volume || !AM.reagents) - return 0 + return FALSE if(blood_volume < BLOOD_VOLUME_BAD(src) && !forced) - return 0 + return FALSE if(blood_volume < total_amount) total_amount = blood_volume var/blood_id = get_blood_id() if(!blood_id) - return 0 + return FALSE var/amount = total_amount var/chems_amount = 0 @@ -221,6 +221,9 @@ amount = total_amount * blood_proportion chems_amount = total_amount * (1 - blood_proportion) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(src) + if(bloodsuckerdatum) + bloodsuckerdatum.bloodsucker_blood_volume -= amount blood_volume -= amount var/list/blood_data = get_blood_data(blood_id) @@ -237,15 +240,15 @@ C.ForceContractDisease(D) if(!(blood_data["blood_type"] in get_safe_blood(C.dna.blood_type))) C.reagents.add_reagent(/datum/reagent/toxin, amount * 0.5) - return 1 + return TRUE C.blood_volume = min(C.blood_volume + round(amount, 0.1), BLOOD_VOLUME_MAXIMUM(C)) - return 1 + return TRUE AM.reagents.add_reagent(blood_id, amount, blood_data, bodytemperature) if(chems_amount) reagents.trans_to(AM, chems_amount) - return 1 + return TRUE /mob/living/proc/get_blood_data(blood_id) diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm deleted file mode 100644 index dc39910920cd..000000000000 --- a/code/modules/mob/living/bloodcrawl.dm +++ /dev/null @@ -1,154 +0,0 @@ -/mob/living/proc/phaseout(obj/effect/decal/cleanable/B) - if(iscarbon(src)) - var/mob/living/carbon/C = src - for(var/obj/item/I in C.held_items) - //TODO make it toggleable to either forcedrop the items, or deny - //entry when holding them - // literally only an option for carbons though - to_chat(C, span_warning("You may not hold items while blood crawling!")) - return 0 - var/obj/item/bloodcrawl/B1 = new(C) - var/obj/item/bloodcrawl/B2 = new(C) - B1.icon_state = "bloodhand_left" - B2.icon_state = "bloodhand_right" - C.put_in_hands(B1) - C.put_in_hands(B2) - C.regenerate_icons() - src.notransform = TRUE - spawn(0) - bloodpool_sink(B) - src.notransform = FALSE - return 1 - -/mob/living/proc/bloodpool_sink(obj/effect/decal/cleanable/B) - var/turf/mobloc = get_turf(src.loc) - - src.visible_message(span_warning("[src] sinks into the pool of blood!")) - playsound(get_turf(src), 'sound/magic/enter_blood.ogg', 50, 1, -1) - // Extinguish, unbuckle, stop being pulled, set our location into the - // dummy object - var/obj/effect/dummy/phased_mob/holder = new /obj/effect/dummy/phased_mob(mobloc) - src.ExtinguishMob() - - // Keep a reference to whatever we're pulling, because forceMove() - // makes us stop pulling - var/pullee = src.pulling - - src.holder = holder - src.forceMove(holder) - - // if we're not pulling anyone, or we can't eat anyone - if(!pullee || src.bloodcrawl != BLOODCRAWL_EAT) - return - - // if the thing we're pulling isn't alive - if (!isliving(pullee)) - return - - var/mob/living/victim = pullee - var/kidnapped = FALSE - - if(victim.stat == CONSCIOUS) - src.visible_message(span_warning("[victim] kicks free of the blood pool just before entering it!"), null, span_notice("You hear splashing and struggling.")) - else if(victim.reagents?.has_reagent(/datum/reagent/consumable/ethanol/demonsblood, needs_metabolizing = TRUE)) - visible_message(span_warning("Something prevents [victim] from entering the pool!"), span_warning("A strange force is blocking [victim] from entering!"), span_notice("You hear a splash and a thud.")) - else - victim.forceMove(src) - victim.emote("scream") - src.visible_message(span_warning("[src] drags [victim] into the pool of blood!"), null, span_notice("You hear a splash.")) - kidnapped = TRUE - - if(kidnapped) - var/success = bloodcrawl_consume(victim) - if(!success) - to_chat(src, span_danger("You happily devour... nothing? Your meal vanished at some point!")) - return 1 - -/mob/living/proc/bloodcrawl_consume(mob/living/victim) - to_chat(src, span_danger("You begin to feast on [victim]. You can not move while you are doing this.")) - - var/sound - if(istype(src, /mob/living/simple_animal/slaughter)) - var/mob/living/simple_animal/slaughter/SD = src - sound = SD.feast_sound - else - sound = 'sound/magic/demon_consume.ogg' - - for(var/i in 1 to 3) - playsound(get_turf(src),sound, 50, 1) - sleep(3 SECONDS) - - if(!victim) - return FALSE - - if(victim.reagents?.has_reagent(/datum/reagent/consumable/ethanol/devilskiss, needs_metabolizing = TRUE)) - to_chat(src, span_warning("AAH! THEIR FLESH! IT BURNS!")) - adjustBruteLoss(25) //I can't use adjustHealth() here because bloodcrawl affects /mob/living and adjustHealth() only affects simple mobs - var/found_bloodpool = FALSE - for(var/obj/effect/decal/cleanable/target in range(1,get_turf(victim))) - if(target.can_bloodcrawl_in()) - victim.forceMove(get_turf(target)) - victim.visible_message(span_warning("[target] violently expels [victim]!")) - victim.exit_blood_effect(target) - found_bloodpool = TRUE - - if(!found_bloodpool) - // Fuck it, just eject them, thanks to some split second cleaning - victim.forceMove(get_turf(victim)) - victim.visible_message(span_warning("[victim] appears from nowhere, covered in blood!")) - victim.exit_blood_effect() - return TRUE - - to_chat(src, span_danger("You devour [victim]. Your health is fully restored.")) - src.revive(full_heal = 1) - - // No defib possible after laughter - victim.adjustBruteLoss(1000) - victim.death() - bloodcrawl_swallow(victim) - return TRUE - -/mob/living/proc/bloodcrawl_swallow(var/mob/living/victim) - qdel(victim) - -/obj/item/bloodcrawl - name = "blood crawl" - desc = "You are unable to hold anything while in this form." - icon = 'icons/effects/blood.dmi' - item_flags = ABSTRACT | DROPDEL - -/obj/item/bloodcrawl/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/mob/living/proc/exit_blood_effect(obj/effect/decal/cleanable/B) - playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 50, 1, -1) - //Makes the mob have the color of the blood pool it came out of - var/newcolor = rgb(149, 10, 10) - if(istype(B, /obj/effect/decal/cleanable/xenoblood)) - newcolor = rgb(43, 186, 0) - add_atom_colour(newcolor, TEMPORARY_COLOUR_PRIORITY) - // but only for a few seconds - addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, remove_atom_colour), TEMPORARY_COLOUR_PRIORITY, newcolor), 6 SECONDS) - -/mob/living/proc/phasein(obj/effect/decal/cleanable/B) - if(src.notransform) - to_chat(src, span_warning("Finish eating first!")) - return 0 - B.visible_message(span_warning("[B] starts to bubble...")) - if(!do_after(src, 2 SECONDS, B)) - return - if(!B) - return - forceMove(B.loc) - src.client.eye = src - src.visible_message(span_warning("[src] rises out of the pool of blood!")) - exit_blood_effect(B) - if(iscarbon(src)) - var/mob/living/carbon/C = src - for(var/obj/item/bloodcrawl/BC in C) - BC.flags_1 = null - qdel(BC) - qdel(src.holder) - src.holder = null - return 1 diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm index 02a6f15bfd53..27b646b0c990 100644 --- a/code/modules/mob/living/brain/MMI.dm +++ b/code/modules/mob/living/brain/MMI.dm @@ -69,7 +69,7 @@ brainmob.container = src var/fubar_brain = newbrain.brain_death && newbrain.suicided && brainmob.suiciding //brain is damaged beyond repair or from a suicider if(!fubar_brain && !(newbrain.organ_flags & ORGAN_FAILING)) // the brain organ hasn't been beaten to death, nor was from a suicider. - brainmob.stat = CONSCIOUS //we manually revive the brain mob + brainmob.set_stat(CONSCIOUS) //we manually revive the brain mob brainmob.remove_from_dead_mob_list() brainmob.add_to_alive_mob_list() else if(!fubar_brain && newbrain.organ_flags & ORGAN_FAILING) // the brain is damaged, but not from a suicider @@ -121,7 +121,7 @@ /obj/item/mmi/proc/eject_brain(mob/user) brainmob.container = null //Reset brainmob mmi var. brainmob.forceMove(brain) //Throw mob into brain. - brainmob.stat = DEAD + brainmob.set_stat(DEAD) brainmob.emp_damage = 0 brainmob.reset_perspective() //so the brainmob follows the brain organ instead of the mmi. And to update our vision brainmob.remove_from_alive_mob_list() //Get outta here diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index 142b52f38bba..e05e1c7fac84 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -17,7 +17,6 @@ OB.brainmob = src forceMove(OB) - /mob/living/brain/proc/create_dna() stored_dna = new /datum/dna/stored(src) if(!stored_dna.species) @@ -97,8 +96,6 @@ var/obj/mecha/M = container.mecha if(M.mouse_pointer) client.mouse_pointer_icon = M.mouse_pointer - if (client && ranged_ability && ranged_ability.ranged_mousepointer) - client.mouse_pointer_icon = ranged_ability.ranged_mousepointer /mob/living/brain/proc/get_traumas() . = list() diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index f9c5a427e95a..ac26a91dc1f1 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -55,7 +55,7 @@ //Update the body's icon so it doesnt appear debrained anymore C.update_hair() -/obj/item/organ/brain/Remove(mob/living/carbon/C, special = 0, no_id_transfer = FALSE) +/obj/item/organ/brain/Remove(mob/living/carbon/C, special = FALSE, no_id_transfer = FALSE) ..() if(!special) if(C.has_horror_inside()) diff --git a/code/modules/mob/living/brain/death.dm b/code/modules/mob/living/brain/death.dm index b9a6bdfb47f6..e90637f63ed8 100644 --- a/code/modules/mob/living/brain/death.dm +++ b/code/modules/mob/living/brain/death.dm @@ -1,7 +1,7 @@ /mob/living/brain/death(gibbed) if(stat == DEAD) return - stat = DEAD + set_stat(DEAD) if(!gibbed && container)//If not gibbed but in a container. var/obj/item/mmi = container @@ -17,4 +17,4 @@ if(loc) if(istype(loc, /obj/item/organ/brain)) qdel(loc)//Gets rid of the brain item - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm index 466595103bcb..e7f48ee9991e 100644 --- a/code/modules/mob/living/brain/posibrain.dm +++ b/code/modules/mob/living/brain/posibrain.dm @@ -118,7 +118,7 @@ GLOBAL_VAR(posibrain_notify_cooldown) brainmob.stored_dna = new /datum/dna/stored(brainmob) C.dna.copy_dna(brainmob.stored_dna) brainmob.timeofhostdeath = C.timeofdeath - brainmob.stat = CONSCIOUS + brainmob.set_stat(CONSCIOUS) if(brainmob.mind) brainmob.mind.assigned_role = new_role if(C.mind) @@ -142,7 +142,7 @@ GLOBAL_VAR(posibrain_notify_cooldown) name = "[initial(name)] ([brainmob.name])" to_chat(brainmob, welcome_message) brainmob.mind.assigned_role = new_role - brainmob.stat = CONSCIOUS + brainmob.set_stat(CONSCIOUS) brainmob.remove_from_dead_mob_list() brainmob.add_to_alive_mob_list() ADD_TRAIT(brainmob, TRAIT_PACIFISM, POSIBRAIN_TRAIT) diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index ec2de0314975..e4d9c172a5af 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -133,8 +133,10 @@ Des: Removes all infected images from the alien. return initial(pixel_y) /mob/living/carbon/alien/proc/alien_evolve(mob/living/carbon/alien/new_xeno) - to_chat(src, span_noticealien("You begin to evolve!")) - visible_message(span_alertalien("[src] begins to twist and contort!")) + visible_message( + span_alertalien("[src] begins to twist and contort!"), + span_noticealien("You begin to evolve!"), + ) new_xeno.setDir(dir) if(!alien_name_regex.Find(name)) new_xeno.name = name diff --git a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm index e74e3a189695..cf5920623c94 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm @@ -6,361 +6,383 @@ These are general powers. Specific powers are stored under the appropriate alien Doesn't work on other aliens/AI.*/ -/obj/effect/proc_holder/alien +/datum/action/cooldown/alien name = "Alien Power" panel = "Alien" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + button_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "spell_default" + check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + melee_cooldown_time = 0 SECONDS + + /// How much plasma this action uses. var/plasma_cost = 0 - var/check_turf = FALSE - has_action = TRUE - base_action = /datum/action/spell_action/alien - action_icon = 'icons/mob/actions/actions_xeno.dmi' - action_icon_state = "spell_default" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/alien/Initialize() + +/datum/action/cooldown/alien/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.getPlasma() < plasma_cost) + return FALSE + + return TRUE + +/datum/action/cooldown/alien/PreActivate(atom/target) + // Parent calls Activate(), so if parent returns TRUE, + // it means the activation happened successfuly by this point . = ..() - action = new(src) + if(!.) + return FALSE + // Xeno actions like "evolve" may result in our action (or our alien) being deleted + // In that case, we can just exit now as a "success" + if(QDELETED(src) || QDELETED(owner)) + return TRUE -/obj/effect/proc_holder/alien/Click() - if(!iscarbon(usr)) - return 1 - var/mob/living/carbon/user = usr - if(cost_check(check_turf,user)) - if(fire(user) && user) // Second check to prevent runtimes when evolving - user.adjustPlasma(-plasma_cost) - return 1 + var/mob/living/carbon/carbon_owner = owner + carbon_owner.adjustPlasma(-plasma_cost) + // It'd be really annoying if click-to-fire actions stayed active, + // even if our plasma amount went under the required amount. + if(click_to_activate && carbon_owner.getPlasma() < plasma_cost) + owner.balloon_alert(owner, "not enough plasma!") + unset_click_ability(owner, refund_cooldown = FALSE) -/obj/effect/proc_holder/alien/on_gain(mob/living/carbon/user) - return + return TRUE -/obj/effect/proc_holder/alien/on_lose(mob/living/carbon/user) - return +/datum/action/cooldown/alien/set_statpanel_format() + . = ..() + if(!islist(.)) + return -/obj/effect/proc_holder/alien/fire(mob/living/carbon/user) - return 1 + .[PANEL_DISPLAY_STATUS] = "PLASMA - [plasma_cost]" -/obj/effect/proc_holder/alien/get_panel_text() +/datum/action/cooldown/alien/make_structure + /// The type of structure the action makes on use + var/obj/structure/made_structure_type + +/datum/action/cooldown/alien/make_structure/IsAvailable(feedback = FALSE) . = ..() - if(plasma_cost > 0) - return "[plasma_cost]" + if(!.) + return FALSE + if(!isturf(owner.loc) || isspaceturf(owner.loc)) + return FALSE -/obj/effect/proc_holder/alien/proc/cost_check(check_turf = FALSE, mob/living/carbon/user, silent = FALSE) - if(user.stat) - if(!silent) - to_chat(user, span_noticealien("You must be conscious to do this.")) + return TRUE + +/datum/action/cooldown/alien/make_structure/PreActivate(atom/target) + if(!check_for_duplicate()) return FALSE - if(user.getPlasma() < plasma_cost) - if(!silent) - to_chat(user, span_noticealien("Not enough plasma stored.")) + + if(!check_for_vents()) return FALSE - if(check_turf && (!isturf(user.loc) || isspaceturf(user.loc))) - if(!silent) - to_chat(user, span_noticealien("Bad place for a garden!")) + + return ..() + +/datum/action/cooldown/alien/make_structure/Activate(atom/target) + new made_structure_type(owner.loc) + return TRUE + +/// Checks if there's a duplicate structure in the owner's turf +/datum/action/cooldown/alien/make_structure/proc/check_for_duplicate() + var/obj/structure/existing_thing = locate(made_structure_type) in owner.loc + if(existing_thing) + owner.balloon_alert(owner, "space occupied by \a [existing_thing]!") return FALSE + return TRUE -/obj/effect/proc_holder/alien/proc/check_vent_block(mob/living/user) - var/obj/machinery/atmospherics/components/unary/atmos_thing = locate() in user.loc +/// Checks if there's an atmos machine (vent) in the owner's turf +/datum/action/cooldown/alien/make_structure/proc/check_for_vents() + var/obj/machinery/atmospherics/components/unary/atmos_thing = locate() in owner.loc if(atmos_thing) - var/rusure = tgui_alert(user, "Laying eggs and shaping resin here would block access to [atmos_thing]. Do you want to continue?", "Blocking Atmospheric Component", list("Yes", "No")) - if(rusure != "Yes") + var/are_you_sure = tgui_alert(owner, "Laying eggs and shaping resin here would block access to [atmos_thing]. Do you want to continue?", "Blocking Atmospheric Component", list("Yes", "No")) + if(are_you_sure != "Yes") return FALSE + if(QDELETED(src) || QDELETED(owner) || !check_for_duplicate()) + return FALSE + return TRUE -/obj/effect/proc_holder/alien/plant +/datum/action/cooldown/alien/make_structure/plant_weeds name = "Plant Weeds" desc = "Plants some alien weeds." + button_icon_state = "alien_plant" plasma_cost = 50 - check_turf = TRUE - action_icon_state = "alien_plant" - -/obj/effect/proc_holder/alien/plant/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/weeds/node) in get_turf(user)) - to_chat(user, "There's already a weed node here.") - return 0 - user.visible_message(span_alertalien("[user] has planted some alien weeds!")) - new/obj/structure/alien/weeds/node(user.loc) - return 1 - -/obj/effect/proc_holder/alien/whisper + made_structure_type = /obj/structure/alien/weeds/node + +/datum/action/cooldown/alien/make_structure/plant_weeds/Activate(atom/target) + owner.visible_message(span_alertalien("[owner] plants some alien weeds!")) + return ..() + +/datum/action/cooldown/alien/whisper name = "Whisper" desc = "Whisper to someone." + button_icon_state = "alien_whisper" plasma_cost = 10 - action_icon_state = "alien_whisper" - -/obj/effect/proc_holder/alien/whisper/fire(mob/living/carbon/user) - var/list/options = list() - for(var/mob/living/Ms in oview(user)) - options += Ms - var/mob/living/M = input("Select who to whisper to:","Whisper to?",null) as null|mob in options - if(!M) - return 0 - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(user, span_noticealien("As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.")) + +/datum/action/cooldown/alien/whisper/Activate(atom/target) + var/list/possible_recipients = list() + for(var/mob/living/recipient in oview(owner)) + possible_recipients += recipient + + if(!length(possible_recipients)) + to_chat(owner, span_noticealien("There's no one around to whisper to.")) + return FALSE + + var/mob/living/chosen_recipient = tgui_input_list(owner, "Select whisper recipient", "Whisper", sortUsernames(possible_recipients)) + if(!chosen_recipient) + return FALSE + + var/to_whisper = tgui_input_text(owner, title = "Alien Whisper") + if(QDELETED(chosen_recipient) || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = FALSE) || !to_whisper) + return FALSE + if(chosen_recipient.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + to_chat(owner, span_warning("As you reach into [chosen_recipient]'s mind, you are stopped by a mental blockage. It seems you've been foiled.")) return FALSE - var/msg = sanitize(input("Message:", "Alien Whisper") as text|null, usr) - if(msg) - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(user, span_notice("As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.")) - return - log_directed_talk(user, M, msg, LOG_SAY, tag="alien whisper") - to_chat(M, "[span_noticealien("You hear a strange, alien voice in your head...")][msg]") - to_chat(user, span_noticealien("You said: \"[msg]\" to [M]")) - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - var/follow_link_user = FOLLOW_LINK(ded, user) - var/follow_link_whispee = FOLLOW_LINK(ded, M) - to_chat(ded, "[follow_link_user] [span_name("[user]")] [span_alertalien("Alien Whisper --> ")] [follow_link_whispee] [span_name("[M]")] [span_noticealien("[msg]")]") - else - return 0 - return 1 -/obj/effect/proc_holder/alien/transfer + log_directed_talk(owner, chosen_recipient, to_whisper, LOG_SAY, tag = "alien whisper") + to_chat(chosen_recipient, "[span_noticealien("You hear a strange, alien voice in your head...")][to_whisper]") + to_chat(owner, span_noticealien("You said: \"[to_whisper]\" to [chosen_recipient]")) + for(var/mob/dead_mob as anything in GLOB.dead_mob_list) + if(!isobserver(dead_mob)) + continue + var/follow_link_user = FOLLOW_LINK(dead_mob, owner) + var/follow_link_whispee = FOLLOW_LINK(dead_mob, chosen_recipient) + to_chat(dead_mob, "[follow_link_user] [span_name("[owner]")] [span_alertalien("Alien Whisper --> ")] [follow_link_whispee] [span_name("[chosen_recipient]")] [span_noticealien("[to_whisper]")]") + + return TRUE + +/datum/action/cooldown/alien/transfer name = "Transfer Plasma" desc = "Transfer Plasma to another alien." plasma_cost = 0 - action_icon_state = "alien_transfer" + button_icon_state = "alien_transfer" -/obj/effect/proc_holder/alien/transfer/fire(mob/living/carbon/user) +/datum/action/cooldown/alien/transfer/Activate(atom/target) + var/mob/living/carbon/carbon_owner = owner var/list/mob/living/carbon/aliens_around = list() - for(var/mob/living/carbon/A in oview(user)) - if(A.getorgan(/obj/item/organ/alien/plasmavessel)) - aliens_around.Add(A) - var/mob/living/carbon/M = input("Select who to transfer to:","Transfer plasma to?",null) as mob in aliens_around - if(!M) - return 0 - var/amount = input("Amount:", "Transfer Plasma to [M]") as num - if (amount) - amount = min(abs(round(amount)), user.getPlasma()) - if (get_dist(user,M) <= 1) - M.adjustPlasma(amount) - user.adjustPlasma(-amount) - to_chat(M, span_noticealien("[user] has transferred [amount] plasma to you.")) - to_chat(user, span_noticealien("You transfer [amount] plasma to [M]")) - else - to_chat(user, span_noticealien("You need to be closer!")) - return - -/obj/effect/proc_holder/alien/acid - name = "Corrosive Acid" - desc = "Drench an object in acid, destroying it over time." - plasma_cost = 200 - action_icon_state = "alien_acid" - ranged_mousepointer = 'icons/effects/mouse_pointers/acid.dmi' - active = FALSE + for(var/mob/living/carbon/alien in view(owner)) + if(alien.getPlasma() == -1 || alien == owner) + continue + aliens_around += alien -/obj/effect/proc_holder/alien/acid/on_gain(mob/living/carbon/user) - add_verb(user, /mob/living/carbon/proc/corrosive_acid) + if(!length(aliens_around)) + to_chat(owner, span_noticealien("There are no other aliens around.")) + return FALSE -/obj/effect/proc_holder/alien/acid/on_lose(mob/living/carbon/user) - remove_verb(user, /mob/living/carbon/proc/corrosive_acid) - remove_ranged_ability() + var/mob/living/carbon/donation_target = tgui_input_list(owner, "Target to transfer to", "Plasma Donation", sortUsernames(aliens_around)) + if(!donation_target) + return FALSE -/obj/effect/proc_holder/alien/acid/fire(mob/living/user) - if(active) - user.balloon_alert(user, "acid glands relaxed") - remove_ranged_ability() - else - user.balloon_alert(user, "acid glands ready") - add_ranged_ability(user) + var/amount = tgui_input_number(owner, "Amount", "Transfer Plasma to [donation_target]", max_value = carbon_owner.getPlasma()) + if(QDELETED(donation_target) || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = FALSE) || isnull(amount) || amount <= 0) + return FALSE -/obj/effect/proc_holder/alien/acid/proc/check_target(atom/target) - return isturf(target) || isobj(target) + if(get_dist(owner, donation_target) > 1) + owner.balloon_alert(owner, "too far!") + return FALSE + + + donation_target.adjustPlasma(amount) + carbon_owner.adjustPlasma(-amount) -/obj/effect/proc_holder/alien/acid/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return TRUE + to_chat(donation_target, span_noticealien("[owner] has transferred [amount] plasma to you.")) + to_chat(owner, span_noticealien("You transfer [amount] plasma to [donation_target].")) + return TRUE - // Oh great, another unconscious alien. Let's just remove the ranged ability. - if(!iscarbon(ranged_ability_user) || ranged_ability_user.stat != CONSCIOUS) - remove_ranged_ability() - return TRUE +/datum/action/cooldown/alien/acid + click_to_activate = TRUE + unset_after_click = FALSE - var/mob/living/carbon/user = ranged_ability_user +/datum/action/cooldown/alien/acid/corrosion + name = "Corrosive Acid" + desc = "Drench an object in acid, destroying it over time." + button_icon_state = "alien_acid" + plasma_cost = 200 + ranged_mousepointer = 'icons/effects/mouse_pointers/acid.dmi' - // If there's no target, why even bother? - if(!target || !check_target(target)) - user.balloon_alert(user, "can't acid this!") - return TRUE +/datum/action/cooldown/alien/acid/corrosion/set_click_ability(mob/on_who) + . = ..() + if(!.) + return - // Too far away - if(get_dist(user, target) > 1) - user.balloon_alert(user, "too far!") - return TRUE + owner.balloon_alert(owner, "acid glands ready!") + on_who.update_icons() - // Plasma cost check. Because aliens need a currency too, apparently. - if(user.getPlasma() < plasma_cost) - user.balloon_alert(user, "not enough plasma!") - remove_ranged_ability() - return TRUE +/datum/action/cooldown/alien/acid/corrosion/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return - // Let's just get this over with. Apply the acid effect and move on. - user.adjustPlasma(-plasma_cost) - if(target.acid_act(200, 100)) - user.visible_message(span_alertalien("[user] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!")) - remove_ranged_ability() - return FALSE + if(refund_cooldown) + owner.balloon_alert(owner, "acid glands relaxed") + on_who.update_icons() - // Fantastic. It didn't even work. - user.balloon_alert(user, "cannot disolve") - return TRUE +/datum/action/cooldown/alien/acid/corrosion/PreActivate(atom/target) + if(get_dist(owner, target) > 1) + owner.balloon_alert(owner, "too far!") + return FALSE -/mob/living/carbon/proc/corrosive_acid() // right click menu verb ugh - set name = "Corrosive Acid" + return ..() - if(!iscarbon(usr)) - return - var/mob/living/carbon/user = usr - var/obj/effect/proc_holder/alien/acid/A = locate() in user.abilities - if(!A) - return - A.fire(user) +/datum/action/cooldown/alien/acid/corrosion/Activate(atom/target) + if(!target.acid_act(200, 1000)) + owner.balloon_alert(owner, "cannot disolve") + return FALSE + owner.visible_message( + span_alertalien("[owner] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!"), + span_noticealien("You vomit globs of acid over [target]. It begins to sizzle and melt."), + ) + return TRUE -/obj/effect/proc_holder/alien/neurotoxin +/datum/action/cooldown/alien/acid/neurotoxin name = "Spit Neurotoxin" desc = "Spits neurotoxin at someone, paralyzing them for a short time." - action_icon_state = "alien_neurotoxin_0" - active = FALSE - -/obj/effect/proc_holder/alien/neurotoxin/fire(mob/living/carbon/user) - var/message - if(active) - message = span_notice("You empty your neurotoxin gland.") - remove_ranged_ability(message) - else - message = span_notice("You prepare your neurotoxin gland. Left-click to fire at a target!") - add_ranged_ability(user, message, TRUE) + button_icon_state = "alien_neurotoxin_0" + plasma_cost = 50 -/obj/effect/proc_holder/alien/neurotoxin/update_icon() - action.button_icon_state = "alien_neurotoxin_[active]" - action.UpdateButtonIcon() +/datum/action/cooldown/alien/acid/neurotoxin/IsAvailable(feedback = FALSE) + return ..() && isturf(owner.loc) -/obj/effect/proc_holder/alien/neurotoxin/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - var/p_cost = 50 - if(!iscarbon(ranged_ability_user) || ranged_ability_user.stat) - remove_ranged_ability() +/datum/action/cooldown/alien/acid/neurotoxin/set_click_ability(mob/on_who) + . = ..() + if(!.) return - var/mob/living/carbon/user = ranged_ability_user + owner.balloon_alert(owner, "neurotoxin glands ready!") - if(user.getPlasma() < p_cost) - to_chat(user, span_warning("You need at least [p_cost] plasma to spit.")) - remove_ranged_ability() - return + button_icon_state = "alien_neurotoxin_1" + build_all_button_icons() + on_who.update_icons() - var/turf/T = user.loc - var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction - if(!isturf(U) || !isturf(T)) +/datum/action/cooldown/alien/acid/neurotoxin/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + if(refund_cooldown) + owner.balloon_alert(owner, "neurotoxin glands relaxed") + + button_icon_state = "alien_neurotoxin_0" + build_all_button_icons() + on_who.update_icons() + +/datum/action/cooldown/alien/acid/neurotoxin/InterceptClickOn(mob/living/caller, params, atom/target) + . = ..() + if(!.) + unset_click_ability(caller, refund_cooldown = FALSE) return FALSE - user.visible_message("[user] spits neurotoxin!", span_alertalien("You spit neurotoxin.")) - var/obj/item/projectile/bullet/neurotoxin/A = new /obj/item/projectile/bullet/neurotoxin(user.loc) - A.firer = user - A.preparePixelProjectile(target, user, params) - A.fire() - user.newtonian_move(get_dir(U, T)) - user.adjustPlasma(-p_cost) + // We do this in InterceptClickOn() instead of Activate() + // because we use the click parameters for aiming the projectile + // (or something like that) + var/turf/user_turf = caller.loc + var/turf/target_turf = get_step(caller, target.dir) // Get the tile infront of the move, based on their direction + if(!isturf(target_turf)) + return FALSE + var/modifiers = params2list(params) + caller.visible_message( + span_danger("[caller] spits neurotoxin!"), + span_alertalien("You spit neurotoxin."), + ) + var/obj/item/projectile/bullet/neurotoxin/neurotoxin = new /obj/item/projectile/bullet/neurotoxin(caller.loc) + neurotoxin.preparePixelProjectile(target, caller, modifiers) + neurotoxin.firer = caller + neurotoxin.fire() + caller.newtonian_move(get_dir(target_turf, user_turf)) return TRUE -/obj/effect/proc_holder/alien/neurotoxin/on_lose(mob/living/carbon/user) - remove_ranged_ability() - -/obj/effect/proc_holder/alien/neurotoxin/add_ranged_ability(mob/living/user,msg,forced) - ..() - if(isalienadult(user)) - var/mob/living/carbon/alien/humanoid/A = user - A.drooling = 1 - A.update_icons() - -/obj/effect/proc_holder/alien/neurotoxin/remove_ranged_ability(msg) - if(isalienadult(ranged_ability_user)) - var/mob/living/carbon/alien/humanoid/A = ranged_ability_user - A.drooling = 0 - A.update_icons() - ..() +// Has to return TRUE, otherwise is skipped. +/datum/action/cooldown/alien/acid/neurotoxin/Activate(atom/target) + return TRUE -/obj/effect/proc_holder/alien/resin +/datum/action/cooldown/alien/make_structure/resin name = "Secrete Resin" desc = "Secrete tough malleable resin." + button_icon_state = "alien_resin" plasma_cost = 55 - check_turf = TRUE + /// A list of all structures we can make. var/list/structures = list( "resin wall" = /obj/structure/alien/resin/wall, "resin membrane" = /obj/structure/alien/resin/membrane, "resin nest" = /obj/structure/bed/nest) - action_icon_state = "alien_resin" +// Snowflake to check for multiple types of alien resin structures +/datum/action/cooldown/alien/make_structure/resin/check_for_duplicate() + for(var/blocker_name in structures) + var/obj/structure/blocker_type = structures[blocker_name] + if(locate(blocker_type) in owner.loc) + to_chat(owner, span_warning("There is already a resin structure there!")) + return FALSE -/obj/effect/proc_holder/alien/resin/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/resin) in user.loc) - to_chat(user, span_danger("There is already a resin structure there.")) - return FALSE + return TRUE - if(!check_vent_block(user)) +/datum/action/cooldown/alien/make_structure/resin/Activate(atom/target) + var/choice = show_radial_menu(owner, owner, structures, radius = 36) + if(isnull(choice) || QDELETED(src) || QDELETED(owner) || !check_for_duplicate() || !IsAvailable(feedback = FALSE)) return FALSE - var/choice = show_radial_menu(user, user, structures, radius = 36) - if(!choice) - return FALSE - if (!cost_check(check_turf,user)) + var/obj/structure/choice_path = structures[choice] + if(!ispath(choice_path)) return FALSE - to_chat(user, span_notice("You shape a [choice].")) - user.visible_message(span_notice("[user] vomits up a thick purple substance and begins to shape it.")) - choice = structures[choice] - new choice(user.loc) + owner.visible_message( + span_notice("[owner] vomits up a thick purple substance and begins to shape it."), + span_notice("You shape a [choice] out of resin."), + ) + new choice_path(owner.loc) return TRUE -/obj/effect/proc_holder/alien/sneak +/datum/action/cooldown/alien/sneak name = "Sneak" desc = "Blend into the shadows to stalk your prey." - active = 0 - - action_icon_state = "alien_sneak" - -/obj/effect/proc_holder/alien/sneak/fire(mob/living/carbon/alien/humanoid/user) - if(!active) - user.alpha = 75 //Still easy to see in lit areas with bright tiles, almost invisible on resin. - user.sneaking = 1 - active = 1 - to_chat(user, span_noticealien("You blend into the shadows...")) + button_icon_state = "alien_sneak" + /// The alpha we go to when sneaking. + var/sneak_alpha = 75 + +/datum/action/cooldown/alien/sneak/Remove(mob/living/remove_from) + if(HAS_TRAIT(remove_from, TRAIT_ALIEN_SNEAK)) + remove_from.alpha = initial(remove_from.alpha) + REMOVE_TRAIT(remove_from, TRAIT_ALIEN_SNEAK, name) + + return ..() + +/datum/action/cooldown/alien/sneak/Activate(atom/target) + if(HAS_TRAIT(owner, TRAIT_ALIEN_SNEAK)) + // It's safest to go to the initial alpha of the mob. + // Otherwise we get permanent invisbility exploits. + owner.alpha = initial(owner.alpha) + owner.balloon_alert(owner, "you reveal yourself!") + REMOVE_TRAIT(owner, TRAIT_ALIEN_SNEAK, name) + else - user.alpha = initial(user.alpha) - user.sneaking = 0 - active = 0 - to_chat(user, span_noticealien("You reveal yourself!")) + owner.alpha = sneak_alpha + owner.balloon_alert(owner, "you blend into the shadows...") + ADD_TRAIT(owner, TRAIT_ALIEN_SNEAK, name) + return TRUE +/// Gets the plasma level of this carbon's plasma vessel, or -1 if they don't have one /mob/living/carbon/proc/getPlasma() var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(!vessel) - return 0 - return vessel.storedPlasma - + return -1 + return vessel.stored_plasma +/// Adjusts the plasma level of the carbon's plasma vessel if they have one /mob/living/carbon/proc/adjustPlasma(amount) var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(!vessel) - return 0 - vessel.storedPlasma = max(vessel.storedPlasma + amount,0) - vessel.storedPlasma = min(vessel.storedPlasma, vessel.max_plasma) //upper limit of max_plasma, lower limit of 0 - for(var/X in abilities) - var/obj/effect/proc_holder/alien/APH = X - if(APH.has_action) - APH.action.UpdateButtonIcon() - return 1 + return FALSE + vessel.stored_plasma = max(vessel.stored_plasma + amount,0) + vessel.stored_plasma = min(vessel.stored_plasma, vessel.max_plasma) //upper limit of max_plasma, lower limit of 0 + for(var/datum/action/cooldown/alien/ability in actions) + ability.build_all_button_icons() + return TRUE /mob/living/carbon/alien/adjustPlasma(amount) . = ..() updatePlasmaDisplay() - -/mob/living/carbon/proc/usePlasma(amount) - if(getPlasma() >= amount) - adjustPlasma(-amount) - return 1 - - return 0 diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm index 0df4efaf265e..4b41954658af 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm @@ -6,40 +6,44 @@ icon_state = "aliend" /mob/living/carbon/alien/humanoid/drone/Initialize() - AddAbility(new/obj/effect/proc_holder/alien/evolve(null)) - . = ..() + var/datum/action/cooldown/alien/evolve_to_praetorian/evolution = new(src) + evolution.Grant(src) + return ..() /mob/living/carbon/alien/humanoid/drone/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/large internal_organs += new /obj/item/organ/alien/resinspinner internal_organs += new /obj/item/organ/alien/acid - ..() + return ..() -/obj/effect/proc_holder/alien/evolve +/datum/action/cooldown/alien/evolve_to_praetorian name = "Evolve to Praetorian" desc = "Praetorian" + button_icon_state = "alien_evolve_drone" plasma_cost = 500 - action_icon_state = "alien_evolve_drone" +/datum/action/cooldown/alien/evolve_to_praetorian/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + if(!isturf(owner.loc)) + return FALSE + + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal)) + return FALSE + + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/obj/item/organ/alien/hivenode/node = evolver.getorgan(/obj/item/organ/alien/hivenode) + // Players are Murphy's Law. We may not expect + // there to ever be a living xeno with no hivenode, + // but they _WILL_ make it happen. + if(!node || node.recent_queen_death) + return FALSE + + return TRUE -/obj/effect/proc_holder/alien/evolve/fire(mob/living/carbon/alien/humanoid/user) - var/obj/item/organ/alien/hivenode/node = user.getorgan(/obj/item/organ/alien/hivenode) - if(!node) //Players are Murphy's Law. We may not expect there to ever be a living xeno with no hivenode, but they _WILL_ make it happen. - to_chat(user, span_danger("Without the hivemind, you can't possibly hold the responsibility of leadership!")) - return 0 - if(node.recent_queen_death) - to_chat(user, span_danger("Your thoughts are still too scattered to take up the position of leadership.")) - return 0 - if(user.movement_type & (VENTCRAWLING)) - to_chat(user, span_danger("You cannot evolve in a pipe.")) - return 0 - if(!isturf(user.loc)) - to_chat(user, span_notice("You can't evolve here!")) - return 0 - if(!get_alien_type(/mob/living/carbon/alien/humanoid/royal)) - var/mob/living/carbon/alien/humanoid/royal/praetorian/new_xeno = new (user.loc) - user.alien_evolve(new_xeno) - return 1 - else - to_chat(user, span_notice("We already have a living royal!")) - return 0 \ No newline at end of file +/datum/action/cooldown/alien/evolve_to_praetorian/Activate(atom/target) + var/mob/living/carbon/alien/humanoid/evolver = owner + var/mob/living/carbon/alien/humanoid/royal/praetorian/new_xeno = new(owner.loc) + evolver.alien_evolve(new_xeno) + return TRUE diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm index 303490614891..93bef965b91e 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm @@ -7,36 +7,48 @@ /mob/living/carbon/alien/humanoid/royal/praetorian/Initialize() real_name = name - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src)) - AddAbility(new /obj/effect/proc_holder/alien/royal/praetorian/evolve()) - . = ..() + + var/datum/action/cooldown/spell/aoe/repulse/xeno/tail_whip = new(src) + tail_whip.Grant(src) + + var/datum/action/cooldown/alien/evolve_to_queen/evolution = new(src) + evolution.Grant(src) + + return ..() /mob/living/carbon/alien/humanoid/royal/praetorian/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/large internal_organs += new /obj/item/organ/alien/resinspinner internal_organs += new /obj/item/organ/alien/acid internal_organs += new /obj/item/organ/alien/neurotoxin - ..() + return ..() -/obj/effect/proc_holder/alien/royal/praetorian/evolve +/datum/action/cooldown/alien/evolve_to_queen name = "Evolve" desc = "Produce an internal egg sac capable of spawning children. Only one queen can exist at a time." + button_icon_state = "alien_evolve_praetorian" plasma_cost = 500 - action_icon_state = "alien_evolve_praetorian" - -/obj/effect/proc_holder/alien/royal/praetorian/evolve/fire(mob/living/carbon/alien/humanoid/user) - var/obj/item/organ/alien/hivenode/node = user.getorgan(/obj/item/organ/alien/hivenode) - if(!node) //Just in case this particular Praetorian gets violated and kept by the RD as a replacement for Lamarr. - to_chat(user, span_danger("Without the hivemind, you would be unfit to rule as queen!")) - return 0 - if(node.recent_queen_death) - to_chat(user, span_danger("You are still too burdened with guilt to evolve into a queen.")) - return 0 - if(!get_alien_type(/mob/living/carbon/alien/humanoid/royal/queen)) - var/mob/living/carbon/alien/humanoid/royal/queen/new_xeno = new (user.loc) - user.alien_evolve(new_xeno) - return 1 - else - to_chat(user, span_notice("We already have an alive queen.")) - return 0 +/datum/action/cooldown/alien/evolve_to_queen/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + + if(!isturf(owner.loc)) + return FALSE + + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/queen)) + return FALSE + + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/obj/item/organ/alien/hivenode/node = evolver.getorgan(/obj/item/organ/alien/hivenode) + if(!node || node.recent_queen_death) + return FALSE + + return TRUE + +/datum/action/cooldown/alien/evolve_to_queen/Activate(atom/target) + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/mob/living/carbon/alien/humanoid/royal/queen/new_queen = new(owner.loc) + evolver.alien_evolve(new_queen) + return TRUE diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm index 7c6443cfae7b..9a5b3e0ec149 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm @@ -6,11 +6,12 @@ icon_state = "aliens" /mob/living/carbon/alien/humanoid/sentinel/Initialize() - AddAbility(new /obj/effect/proc_holder/alien/sneak) - . = ..() + var/datum/action/cooldown/alien/sneak/sneaky_beaky = new(src) + sneaky_beaky.Grant(src) + return ..() /mob/living/carbon/alien/humanoid/sentinel/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel internal_organs += new /obj/item/organ/alien/acid internal_organs += new /obj/item/organ/alien/neurotoxin - ..() + return ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index 7bbd0b63f7a8..4ef122f9219f 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -15,8 +15,6 @@ var/pounce_cooldown_time = 30 var/custom_pixel_x_offset = 0 //for admin fuckery. var/custom_pixel_y_offset = 0 - var/sneaking = 0 //For sneaky-sneaky mode and appropriate slowdown - var/drooling = 0 //For Neruotoxic spit overlays deathsound = 'sound/voice/hiss6.ogg' bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien, /obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien) @@ -113,8 +111,38 @@ return A return FALSE - /mob/living/carbon/alien/humanoid/check_breath(datum/gas_mixture/breath) - if(breath && breath.total_moles() > 0 && !sneaking) + if(breath?.total_moles() > 0 && !HAS_TRAIT(src, TRAIT_ALIEN_SNEAK)) playsound(get_turf(src), pick('sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg'), 50, 0, -5) - ..() + return ..() + +/mob/living/carbon/alien/adult/proc/grab(mob/living/carbon/human/target) + if(target.check_block()) + target.visible_message(span_warning("[target] blocks [src]'s grab!"), \ + span_userdanger("You block [src]'s grab!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_warning("Your grab at [target] was blocked!")) + return FALSE + target.grabbedby(src) + return TRUE + +/mob/living/carbon/alien/adult/setGrabState(newstate) + if(newstate == grab_state) + return + if(newstate > GRAB_AGGRESSIVE) + newstate = GRAB_AGGRESSIVE + 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) + 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) + if(GRAB_NECK, GRAB_KILL) + if(. <= GRAB_AGGRESSIVE) + ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 9ed28a52296d..dbaeef7793c0 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -12,7 +12,6 @@ butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 20, /obj/item/stack/sheet/animalhide/xeno = 3) var/alt_inhands_file = 'icons/mob/alienqueen.dmi' - var/datum/timedevent/time_to_shuttle /mob/living/carbon/alien/humanoid/royal/can_inject() return 0 @@ -23,7 +22,7 @@ maxHealth = 400 health = 400 icon_state = "alienq" - var/datum/action/small_sprite/smallsprite = new /datum/action/small_sprite/queen() + var/datum/timedevent/time_to_shuttle /mob/living/carbon/alien/humanoid/royal/queen/Initialize() if(!is_centcom_level(get_turf(src))) @@ -41,9 +40,15 @@ real_name = src.name - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src)) - AddAbility(new/obj/effect/proc_holder/alien/royal/queen/promote()) + var/datum/action/cooldown/spell/aoe/repulse/xeno/tail_whip = new(src) + tail_whip.Grant(src) + + var/datum/action/small_sprite/queen/smallsprite = new(src) smallsprite.Grant(src) + + var/datum/action/cooldown/alien/promote/promotion = new(src) + promotion.Grant(src) + return ..() /mob/living/carbon/alien/humanoid/royal/queen/get_status_tab_items() @@ -63,7 +68,7 @@ internal_organs += new /obj/item/organ/alien/acid internal_organs += new /obj/item/organ/alien/neurotoxin internal_organs += new /obj/item/organ/alien/eggsac - ..() + return ..() /mob/living/carbon/alien/humanoid/royal/queen/proc/game_end() if(is_centcom_level(get_turf(src))) @@ -84,87 +89,113 @@ ..() //yogs end //Queen verbs -/obj/effect/proc_holder/alien/lay_egg +/datum/action/cooldown/alien/make_structure/lay_egg name = "Lay Egg" desc = "Lay an egg to produce huggers to impregnate prey with." + button_icon_state = "alien_egg" plasma_cost = 75 - check_turf = TRUE - action_icon_state = "alien_egg" + made_structure_type = /obj/structure/alien/egg + +/datum/action/cooldown/alien/make_structure/lay_egg/Activate(atom/target) + . = ..() + owner.visible_message(span_alertalien("[owner] lays an egg!")) + +//Button to let queen choose her praetorian. +/datum/action/cooldown/alien/promote + name = "Create Royal Parasite" + desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian." + button_icon_state = "alien_queen_promote" + /// The promotion only takes plasma when completed, not on activation. + var/promotion_plasma_cost = 500 + +/datum/action/cooldown/alien/promote/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + .[PANEL_DISPLAY_STATUS] = "PLASMA - [promotion_plasma_cost]" + +/datum/action/cooldown/alien/promote/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE -/obj/effect/proc_holder/alien/lay_egg/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/egg) in get_turf(user)) - to_chat(user, span_alertalien("There's already an egg here.")) + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.getPlasma() < promotion_plasma_cost) return FALSE - if(!check_vent_block(user)) + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian)) return FALSE - user.visible_message(span_alertalien("[user] has laid an egg!")) - new /obj/structure/alien/egg(user.loc) return TRUE -//Button to let queen choose her praetorian. -/obj/effect/proc_holder/alien/royal/queen/promote - name = "Create Royal Parasite" - desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian." - plasma_cost = 500 //Plasma cost used on promotion, not spawning the parasite. - - action_icon_state = "alien_queen_promote" - -/obj/effect/proc_holder/alien/royal/queen/promote/fire(mob/living/carbon/alien/user) - var/obj/item/queenpromote/prom - if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, span_noticealien("You already have a Praetorian!")) - return 0 - else - for(prom in user) - to_chat(user, span_noticealien("You discard [prom].")) - qdel(prom) - return 0 - - prom = new (user.loc) - if(!user.put_in_active_hand(prom, 1)) - to_chat(user, span_warning("You must empty your hands before preparing the parasite.")) - return 0 - else //Just in case telling the player only once is not enough! - to_chat(user, span_noticealien("Use the royal parasite on one of your children to promote her to Praetorian!")) - return 0 +/datum/action/cooldown/alien/promote/Activate(atom/target) + var/obj/item/queen_promotion/existing_promotion = locate() in owner.held_items + if(existing_promotion) + to_chat(owner, span_noticealien("You discard [existing_promotion].")) + owner.temporarilyRemoveItemFromInventory(existing_promotion) + qdel(existing_promotion) + return TRUE + + if(!owner.get_empty_held_indexes()) + to_chat(owner, span_warning("You must have an empty hand before preparing the parasite.")) + return FALSE + + var/obj/item/queen_promotion/new_promotion = new(owner.loc) + if(!owner.put_in_hands(new_promotion, del_on_fail = TRUE)) + to_chat(owner, span_noticealien("You fail to prepare a parasite.")) + return FALSE + + to_chat(owner, span_noticealien("Use [new_promotion] on one of your children to promote her to a Praetorian!")) + return TRUE -/obj/item/queenpromote +/obj/item/queen_promotion name = "\improper royal parasite" desc = "Inject this into one of your grown children to promote her to a Praetorian!" icon_state = "alien_medal" - item_flags = ABSTRACT | DROPDEL + item_flags = NOBLUDGEON | ABSTRACT | DROPDEL icon = 'icons/mob/alien.dmi' -/obj/item/queenpromote/Initialize() +/obj/item/queen_promotion/attack(mob/living/to_promote, mob/living/carbon/alien/humanoid/queen) . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + if(.) + return + + var/datum/action/cooldown/alien/promote/promotion = locate() in queen.actions + if(!promotion) + CRASH("[type] was created and handled by a mob ([queen]) that didn't have a promotion action associated.") -/obj/item/queenpromote/attack(mob/living/M, mob/living/carbon/alien/humanoid/user) - if(!isalienadult(M) || isalienroyal(M)) - to_chat(user, span_noticealien("You may only use this with your adult, non-royal children!")) + if(!isalienadult(to_promote) || isalienroyal(to_promote)) + to_chat(queen, span_noticealien("You may only use this with your adult, non-royal children!")) return - if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, span_noticealien("You already have a Praetorian!")) + + if(!promotion.IsAvailable(feedback = FALSE)) + to_chat(queen, span_noticealien("You cannot promote a child right now!")) return - var/mob/living/carbon/alien/humanoid/A = M - if(A.stat == CONSCIOUS && A.mind && A.key) - if(!user.usePlasma(500)) - to_chat(user, span_noticealien("You must have 500 plasma stored to use this!")) - return - - to_chat(A, span_noticealien("The queen has granted you a promotion to Praetorian!")) - user.visible_message(span_alertalien("[A] begins to expand, twist and contort!")) - var/mob/living/carbon/alien/humanoid/royal/praetorian/new_prae = new (A.loc) - A.mind.transfer_to(new_prae) - qdel(A) - qdel(src) + if(to_promote.stat != CONSCIOUS || !to_promote.mind || !to_promote.key) return - else - to_chat(user, span_warning("This child must be alert and responsive to become a Praetorian!")) -/obj/item/queenpromote/attack_self(mob/user) + queen.adjustPlasma(-promotion.promotion_plasma_cost) + + to_chat(queen, span_noticealien("You have promoted [to_promote] to a Praetorian!")) + to_promote.visible_message( + span_alertalien("[to_promote] begins to expand, twist and contort!"), + span_noticealien("The queen has granted you a promotion to Praetorian!"), + ) + + var/mob/living/carbon/alien/humanoid/royal/praetorian/new_prae = new(to_promote.loc) + to_promote.mind.transfer_to(new_prae) + + qdel(to_promote) + qdel(src) + return TRUE + +/obj/item/queen_promotion/attack_self(mob/user) to_chat(user, span_noticealien("You discard [src].")) qdel(src) + +/obj/item/queen_promotion/dropped(mob/user, silent) + if(!silent) + to_chat(user, span_noticealien("You discard [src].")) + return ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm index 256f7cdb2864..3e5fdaf31ef9 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm @@ -4,7 +4,8 @@ for(var/I in overlays_standing) add_overlay(I) - var/asleep = IsSleeping() + var/are_we_drooling = istype(click_intercept, /datum/action/cooldown/alien/acid) + if(stat == DEAD) //If we mostly took damage from fire if(fireloss > 125) @@ -12,7 +13,7 @@ else icon_state = "alien[caste]_dead" - else if((stat == UNCONSCIOUS && !asleep) || stat == SOFT_CRIT || IsParalyzed()) + else if((stat == UNCONSCIOUS && !IsSleeping()) || stat == SOFT_CRIT || IsParalyzed()) icon_state = "alien[caste]_unconscious" else if(leap_on_click) icon_state = "alien[caste]_pounce" @@ -21,11 +22,11 @@ icon_state = "alien[caste]_sleep" else if(mob_size == MOB_SIZE_LARGE) icon_state = "alien[caste]" - if(drooling) + if(are_we_drooling) add_overlay("alienspit_[caste]") else icon_state = "alien[caste]" - if(drooling) + if(are_we_drooling) add_overlay("alienspit") if(leaping) diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index 31d8fcbf2221..75db59ce56fc 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -21,15 +21,18 @@ //This is fine right now, if we're adding organ specific damage this needs to be updated /mob/living/carbon/alien/larva/Initialize() - AddAbility(new/obj/effect/proc_holder/alien/hide(null)) - AddAbility(new/obj/effect/proc_holder/alien/larva_evolve(null)) - . = ..() + var/datum/action/cooldown/alien/larva_evolve/evolution = new(src) + evolution.Grant(src) + var/datum/action/cooldown/alien/hide/hide = new(src) + hide.Grant(src) + return ..() /mob/living/carbon/alien/larva/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/small/tiny ..() //This needs to be fixed +// This comment is 12 years old I hope it's fixed by now /mob/living/carbon/alien/larva/get_status_tab_items() . = ..() . += "Progress: [amount_grown]/[max_grown]" diff --git a/code/modules/mob/living/carbon/alien/larva/life.dm b/code/modules/mob/living/carbon/alien/larva/life.dm index 0dcb9971184a..ce617589dd23 100644 --- a/code/modules/mob/living/carbon/alien/larva/life.dm +++ b/code/modules/mob/living/carbon/alien/larva/life.dm @@ -20,12 +20,12 @@ return if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || health <= crit_threshold) if(stat == CONSCIOUS) - stat = UNCONSCIOUS + set_stat(UNCONSCIOUS) blind_eyes(1) update_mobility() else if(stat == UNCONSCIOUS) - stat = CONSCIOUS + set_stat(CONSCIOUS) set_resting(FALSE) adjust_blindness(-1) update_damage_hud() diff --git a/code/modules/mob/living/carbon/alien/larva/powers.dm b/code/modules/mob/living/carbon/alien/larva/powers.dm index 65b3001f5325..f01678c6ede2 100644 --- a/code/modules/mob/living/carbon/alien/larva/powers.dm +++ b/code/modules/mob/living/carbon/alien/larva/powers.dm @@ -1,84 +1,102 @@ -/obj/effect/proc_holder/alien/hide +/datum/action/cooldown/alien/hide name = "Hide" - desc = "Allows aliens to hide beneath tables or certain items. Toggled on or off." + desc = "Allows you to hide beneath tables and certain objects." + button_icon_state = "alien_hide" plasma_cost = 0 + /// The layer we are on while hiding + var/hide_layer = ABOVE_NORMAL_TURF_LAYER - action_icon_state = "alien_hide" +/datum/action/cooldown/alien/hide/Activate(atom/target) + if(owner.layer == hide_layer) + owner.layer = initial(owner.layer) + owner.visible_message( + span_notice("[owner] slowly peeks up from the ground..."), + span_noticealien("You stop hiding."), + ) + owner.layer = hide_layer + owner.visible_message( + span_name("[owner] scurries to the ground!"), + span_noticealien("You are now hiding."), + ) -/obj/effect/proc_holder/alien/hide/fire(mob/living/carbon/alien/user) - if(user.stat != CONSCIOUS) - return - - if (user.layer != ABOVE_NORMAL_TURF_LAYER) - user.layer = ABOVE_NORMAL_TURF_LAYER - user.visible_message(span_name("[user] scurries to the ground!"), \ - span_noticealien("You are now hiding.")) - else - user.layer = MOB_LAYER - user.visible_message("[user] slowly peeks up from the ground...", \ - span_noticealien("You stop hiding.")) - return 1 + return TRUE - -/obj/effect/proc_holder/alien/larva_evolve +/datum/action/cooldown/alien/larva_evolve name = "Evolve" desc = "Evolve into a higher alien caste." + button_icon_state = "alien_evolve_larva" plasma_cost = 0 - action_icon_state = "alien_evolve_larva" +/datum/action/cooldown/alien/larva_evolve/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + if(!islarva(owner)) + return FALSE -/obj/effect/proc_holder/alien/larva_evolve/fire(mob/living/carbon/alien/user) - if(!islarva(user)) - return - var/mob/living/carbon/alien/larva/L = user + var/mob/living/carbon/alien/larva/larva = owner + if(larva.handcuffed || larva.legcuffed) // Cuffing larvas ? Eh ? + return FALSE + if(larva.amount_grown < larva.max_grown) + return FALSE + if(larva.movement_type & VENTCRAWLING) + return FALSE + + return TRUE + +/datum/action/cooldown/alien/larva_evolve/Activate(atom/target) + var/mob/living/carbon/alien/larva/larva = owner + var/static/list/caste_options + if(!caste_options) + caste_options = list() + + // This can probably be genericized in the future. + var/mob/hunter_path = /mob/living/carbon/alien/humanoid/hunter + var/datum/radial_menu_choice/hunter = new() + hunter.name = "Hunter" + hunter.image = image(icon = initial(hunter_path.icon), icon_state = initial(hunter_path.icon_state)) + hunter.info = span_info("Hunters are the most agile caste, tasked with hunting for hosts. \ + They are faster than a human and can even pounce, but are not much tougher than a drone.") - if(L.handcuffed || L.legcuffed) // Cuffing larvas ? Eh ? - to_chat(user, span_danger("You cannot evolve when you are cuffed.")) + caste_options["Hunter"] = hunter + + var/mob/sentinel_path = /mob/living/carbon/alien/humanoid/sentinel + var/datum/radial_menu_choice/sentinel = new() + sentinel.name = "Sentinel" + sentinel.image = image(icon = initial(sentinel_path.icon), icon_state = initial(sentinel_path.icon_state)) + sentinel.info = span_info("Sentinels are tasked with protecting the hive. \ + With their ranged spit, invisibility, and high health, they make formidable guardians \ + and acceptable secondhand hunters.") + + caste_options["Sentinel"] = sentinel + + var/mob/drone_path = /mob/living/carbon/alien/humanoid/drone + var/datum/radial_menu_choice/drone = new() + drone.name = "Drone" + drone.image = image(icon = initial(drone_path.icon), icon_state = initial(drone_path.icon_state)) + drone.info = span_info("Drones are the weakest and slowest of the castes, \ + but can grow into a praetorian and then queen if no queen exists, \ + and are vital to maintaining a hive with their resin secretion abilities.") + + caste_options["Drone"] = drone + + var/alien_caste = show_radial_menu(owner, owner, caste_options, radius = 38, require_near = TRUE, tooltips = TRUE) + if(QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = FALSE) || !alien_caste) return - if(L.movement_type & (VENTCRAWLING)) - to_chat(user, span_danger("You cannot evolve in a pipe.")) + if(alien_caste == null) return - if(L.amount_grown >= L.max_grown) //TODO ~Carn - var/totalaliens = 0 - for(var/I in GLOB.player_list) /// Scan player list for amt of xenos - var/mob/M = I - if(isalien(M) && M.stat != DEAD) - totalaliens++ - if(totalaliens == 1) /// Check if the user is the only xeno existing - /// Force them into drone - var/mob/living/carbon/alien/humanoid/new_xeno - new_xeno = new /mob/living/carbon/alien/humanoid/drone(L.loc) - L.alien_evolve(new_xeno) - to_chat(L, "You are growing into a beautiful alien! It is time to choose a caste.") - to_chat(L, "There are three to choose from:") - to_chat(L, "Hunters are the most agile caste, tasked with hunting for hosts. They are faster than a human and can even pounce, but are not much tougher than a drone.") - to_chat(L, "Sentinels are tasked with protecting the hive. With their ranged spit, invisibility, and high health, they make formidable guardians and acceptable secondhand hunters.") - to_chat(L, "Drones are the weakest and slowest of the castes, but can grow into a praetorian and then queen if no queen exists, and are vital to maintaining a hive with their resin secretion abilities.") - var/alien_caste = tgui_alert(L, "Please choose which alien caste you shall belong to.",,list("Hunter","Sentinel","Drone")) - - if(user.incapacitated()) //something happened to us while we were choosing. - return - - if(L.movement_type & (VENTCRAWLING)) - to_chat(user, span_danger("You cannot evolve in a pipe.")) - return - - var/mob/living/carbon/alien/humanoid/new_xeno - switch(alien_caste) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(L.loc) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(L.loc) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(L.loc) - - L.alien_evolve(new_xeno) - return 0 - else - to_chat(user, span_danger("You are not fully grown.")) - return 0 + var/mob/living/carbon/alien/humanoid/new_xeno + switch(alien_caste) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(larva.loc) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(larva.loc) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(larva.loc) + else + CRASH("Alien evolve was given an invalid / incorrect alien cast type. Got: [alien_caste]") + + larva.alien_evolve(new_xeno) + return TRUE diff --git a/code/modules/mob/living/carbon/alien/larva/update_icons.dm b/code/modules/mob/living/carbon/alien/larva/update_icons.dm index 983544e8c72e..bfac107218ff 100644 --- a/code/modules/mob/living/carbon/alien/larva/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/larva/update_icons.dm @@ -16,7 +16,7 @@ icon_state = "larva[state]_cuff" else if(!(mobility_flags & MOBILITY_STAND)) icon_state = "larva[state]_sleep" - else if(IsStun()) + else if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) icon_state = "larva[state]_stun" else icon_state = "larva[state]" diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index fbce73769415..d598ce7bb9ee 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -39,9 +39,6 @@ if(move_delay_add > 0) move_delay_add = max(0, move_delay_add - rand(1, 2)) -/mob/living/carbon/alien/handle_changeling() - return - /mob/living/carbon/alien/handle_fire()//Aliens on fire code . = ..() if(.) //if the mob isn't on fire anymore diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index d72ca1cb3a98..70a0074953aa 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -1,29 +1,6 @@ /obj/item/organ/alien icon_state = "xgibmid2" visual = FALSE - var/list/alien_powers = list() - -/obj/item/organ/alien/Initialize() - . = ..() - for(var/A in alien_powers) - if(ispath(A)) - alien_powers -= A - alien_powers += new A(src) - -/obj/item/organ/alien/Destroy() - QDEL_LIST(alien_powers) - return ..() - -/obj/item/organ/alien/Insert(mob/living/carbon/M, special = 0) - ..() - for(var/obj/effect/proc_holder/alien/P in alien_powers) - M.AddAbility(P) - - -/obj/item/organ/alien/Remove(mob/living/carbon/M, special = 0) - for(var/obj/effect/proc_holder/alien/P in alien_powers) - M.RemoveAbility(P) - ..() /obj/item/organ/alien/prepare_eat() var/obj/S = ..() @@ -37,23 +14,31 @@ w_class = WEIGHT_CLASS_NORMAL zone = BODY_ZONE_CHEST slot = "plasmavessel" - alien_powers = list(/obj/effect/proc_holder/alien/plant, /obj/effect/proc_holder/alien/transfer) - var/storedPlasma = 100 + actions_types = list( + /datum/action/cooldown/alien/make_structure/plant_weeds, + /datum/action/cooldown/alien/transfer, + ) + + /// The current amount of stored plasma. + var/stored_plasma = 100 + /// The maximum plasma this organ can store. var/max_plasma = 250 + /// The rate this organ regenerates its owners health at per damage type per second. var/heal_rate = 5 + /// The rate this organ regenerates plasma at per second. var/plasma_rate = 10 /obj/item/organ/alien/plasmavessel/prepare_eat() var/obj/S = ..() - S.reagents.add_reagent(/datum/reagent/toxin/plasma, storedPlasma/10) + S.reagents.add_reagent(/datum/reagent/toxin/plasma, stored_plasma/10) return S /obj/item/organ/alien/plasmavessel/large name = "large plasma vessel" icon_state = "plasma_large" w_class = WEIGHT_CLASS_BULKY - storedPlasma = 200 + stored_plasma = 200 max_plasma = 500 plasma_rate = 15 @@ -64,7 +49,7 @@ name = "small plasma vessel" icon_state = "plasma_small" w_class = WEIGHT_CLASS_SMALL - storedPlasma = 100 + stored_plasma = 100 max_plasma = 150 plasma_rate = 5 @@ -73,12 +58,12 @@ icon_state = "plasma_tiny" w_class = WEIGHT_CLASS_TINY max_plasma = 100 - alien_powers = list(/obj/effect/proc_holder/alien/transfer) + actions_types = list(/datum/action/cooldown/alien/transfer) /obj/item/organ/alien/plasmavessel/synthetic name = "synthetic plasma vessel" icon_state = "plasma-c" - storedPlasma = 50 + stored_plasma = 50 max_plasma = 100 /obj/item/organ/alien/plasmavessel/on_life() @@ -110,7 +95,7 @@ var/mob/living/carbon/alien/A = M A.updatePlasmaDisplay() -#define QUEEN_DEATH_DEBUFF_DURATION 2400 +#define QUEEN_DEATH_DEBUFF_DURATION 4 MINUTES /obj/item/organ/alien/hivenode name = "hive node" @@ -118,8 +103,9 @@ zone = BODY_ZONE_HEAD slot = "hivenode" w_class = WEIGHT_CLASS_TINY - var/recent_queen_death = 0 //Indicates if the queen died recently, aliens are heavily weakened while this is active. - alien_powers = list(/obj/effect/proc_holder/alien/whisper) + actions_types = list(/datum/action/cooldown/alien/whisper) + /// Indicates if the queen died recently, aliens are heavily weakened while this is active. + var/recent_queen_death = FALSE /obj/item/organ/alien/hivenode/Insert(mob/living/carbon/M, special = 0) ..() @@ -144,9 +130,9 @@ owner.emote("scream") owner.Paralyze(100) - owner.jitteriness += 30 - owner.confused += 30 - owner.stuttering += 30 + owner.adjust_jitter(30 SECONDS) + owner.adjust_confusion(30 SECONDS) + owner.adjust_stutter(30 SECONDS) recent_queen_death = 1 owner.throw_alert("alien_noqueen", /atom/movable/screen/alert/alien_vulnerable) @@ -169,7 +155,7 @@ icon_state = "stomach-x" zone = BODY_ZONE_PRECISE_MOUTH slot = "resinspinner" - alien_powers = list(/obj/effect/proc_holder/alien/resin) + actions_types = list(/datum/action/cooldown/alien/make_structure/resin) /obj/item/organ/alien/acid @@ -177,7 +163,7 @@ icon_state = "acid" zone = BODY_ZONE_PRECISE_MOUTH slot = "acidgland" - alien_powers = list(/obj/effect/proc_holder/alien/acid) + actions_types = list(/datum/action/cooldown/alien/acid/corrosion) /obj/item/organ/alien/neurotoxin @@ -185,7 +171,7 @@ icon_state = "neurotox" zone = BODY_ZONE_PRECISE_MOUTH slot = "neurotoxingland" - alien_powers = list(/obj/effect/proc_holder/alien/neurotoxin) + actions_types = list(/datum/action/cooldown/alien/acid/neurotoxin) /obj/item/organ/alien/eggsac @@ -194,4 +180,4 @@ zone = BODY_ZONE_PRECISE_GROIN slot = "eggsac" w_class = WEIGHT_CLASS_BULKY - alien_powers = list(/obj/effect/proc_holder/alien/lay_egg) + actions_types = list(/datum/action/cooldown/alien/make_structure/lay_egg) diff --git a/code/modules/mob/living/carbon/alien/screen.dm b/code/modules/mob/living/carbon/alien/screen.dm index 1e7ff076318f..12926135eb69 100644 --- a/code/modules/mob/living/carbon/alien/screen.dm +++ b/code/modules/mob/living/carbon/alien/screen.dm @@ -1,7 +1,7 @@ /mob/living/carbon/alien/proc/updatePlasmaDisplay() if(hud_used) //clientless aliens - hud_used.alien_plasma_display.maptext = "
[round(getPlasma())]
" + hud_used.alien_plasma_display.maptext = ANTAG_MAPTEXT(getPlasma(), COLOR_MAGENTA) //hmmmm /mob/living/carbon/alien/larva/updatePlasmaDisplay() return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index a92817ba3979..40bc46a2c8a7 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -300,12 +300,6 @@ /mob/living/carbon/is_muzzled() return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) -/mob/living/carbon/hallucinating() - if(hallucination) - return TRUE - else - return FALSE - /mob/living/carbon/resist_buckle() if(restrained()) changeNext_move(CLICK_CD_BREAKOUT) @@ -336,7 +330,7 @@ if(fire_stacks <= 0) visible_message(span_danger("[src] has successfully extinguished [p_them()]self!"), \ span_notice("You extinguish yourself.")) - ExtinguishMob() + extinguish_mob() return /mob/living/carbon/resist_restraints() @@ -358,7 +352,7 @@ cuff_resist(I) -/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 600, cuff_break = 0) +/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 1 MINUTES, cuff_break = 0) if(I.item_flags & BEING_REMOVED) to_chat(src, span_warning("You're already attempting to remove [I]!")) return @@ -373,7 +367,7 @@ to_chat(src, span_warning("You fail to remove [I]!")) else if(cuff_break == FAST_CUFFBREAK) - breakouttime = 50 + breakouttime = 5 SECONDS visible_message(span_warning("[src] is trying to break [I]!")) to_chat(src, span_notice("You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)")) if(do_after(src, breakouttime, src, FALSE)) @@ -386,64 +380,65 @@ I.item_flags &= ~BEING_REMOVED /mob/living/carbon/proc/uncuff() - if (handcuffed) - var/obj/item/W = handcuffed - handcuffed = null - if (buckled && buckled.buckle_requires_restraints) + var/obj/item/cuff = handcuffed || legcuffed + + if(!cuff) //none? fuck it we ball + changeNext_move(0) + return + + if(handcuffed) //clear handcuffs first + set_handcuffed(null) + if(buckled?.buckle_requires_restraints) buckled.unbuckle_mob(src) update_handcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) - if (legcuffed) - var/obj/item/W = legcuffed + + if(legcuffed) //then clear legcuffs legcuffed = null update_inv_legcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) -/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break) - if(!I.loc || buckled) - return - visible_message(span_danger("[src] manages to [cuff_break ? "break" : "remove"] [I]!")) - to_chat(src, span_notice("You successfully [cuff_break ? "break" : "remove"] [I].")) + client?.screen -= cuff //remove overlay + + cuff.forceMove(drop_location()) + cuff.dropped(src) //drop it to the ground + + if(!QDELETED(cuff)) //if it didn't delete on drop, update planes + cuff.layer = initial(cuff.layer) + cuff.plane = initial(cuff.plane) + + changeNext_move(0) + +/mob/living/carbon/proc/clear_cuffs(obj/item/cuff, cuff_break, ignore_buckle) + if(!cuff || !cuff.loc) + return TRUE //???, we win nonetheless + + if(buckled && !ignore_buckle) + return FALSE + + visible_message(span_danger("[src] manages to [cuff_break ? "break" : "remove"] [cuff]!")) + to_chat(src, span_notice("You successfully [cuff_break ? "break" : "remove"] [cuff].")) if(cuff_break) - . = !((I == handcuffed) || (I == legcuffed)) - qdel(I) - return + qdel(cuff) + return TRUE - else - if(I == handcuffed) - handcuffed.forceMove(drop_location()) - handcuffed.dropped(src) - handcuffed = null - if(buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - update_handcuffed() - return - if(I == legcuffed) - legcuffed.forceMove(drop_location()) - legcuffed.dropped() - legcuffed = null - update_inv_legcuffed() - return - else - dropItemToGround(I) - return + if(cuff == handcuffed) + handcuffed.forceMove(drop_location()) + handcuffed.dropped(src) + set_handcuffed(null) + if(buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + update_handcuffed() + return TRUE + + if(cuff == legcuffed) + legcuffed.forceMove(drop_location()) + legcuffed.dropped() + legcuffed = null + update_inv_legcuffed() + return TRUE + + dropItemToGround(cuff) + return TRUE /mob/living/carbon/get_standard_pixel_y_offset(lying = 0) if(lying) @@ -483,14 +478,10 @@ . = ..() var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(vessel) - . += "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]" + . += "Plasma Stored: [vessel.stored_plasma]/[vessel.max_plasma]" if(locate(/obj/item/assembly/health) in src) . += "Health: [health]" -/mob/living/carbon/get_proc_holders() - . = ..() - . += add_abilities_to_panel() - /mob/living/carbon/attack_ui(slot) if(!has_hand_for_held_index(active_hand_index)) return 0 @@ -594,7 +585,7 @@ if(!(status_flags & GODMODE)) staminaloss = round(total_stamina, DAMAGE_PRECISION) update_stat() - update_mobility() + update_stamina() if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD ) become_husk(BURN) med_hud_set_health() @@ -605,14 +596,18 @@ /mob/living/carbon/update_stamina() var/stam = getStaminaLoss() - if(istype(src, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = src + if(ishuman(src)) + var/mob/living/carbon/human/H = src //leaving this here but sus if(stam && H.hulk_stamina_check()) return - if(stam > DAMAGE_PRECISION && (maxHealth - stam) <= crit_threshold && !stat) - enter_stamcrit() - else if(stam_paralyzed) - stam_paralyzed = FALSE + if(stam > DAMAGE_PRECISION && (maxHealth - stam) <= crit_threshold) + if(!stat) + enter_stamcrit() + else if(HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) + REMOVE_TRAIT(src, TRAIT_INCAPACITATED, STAMINA) + REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, STAMINA) + REMOVE_TRAIT(src, TRAIT_FLOORED, STAMINA) + update_mobility() else return update_stamina_hud() @@ -890,7 +885,7 @@ death() return if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || (health <= HEALTH_THRESHOLD_FULLCRIT && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))) - stat = UNCONSCIOUS + set_stat(UNCONSCIOUS) blind_eyes(1) if(CONFIG_GET(flag/near_death_experience) && health <= HEALTH_THRESHOLD_NEARDEATH && !HAS_TRAIT(src, TRAIT_NODEATH)) ADD_TRAIT(src, TRAIT_SIXTHSENSE, "near-death") @@ -898,9 +893,9 @@ REMOVE_TRAIT(src, TRAIT_SIXTHSENSE, "near-death") else if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) - stat = SOFT_CRIT + set_stat(SOFT_CRIT) else - stat = CONSCIOUS + set_stat(CONSCIOUS) adjust_blindness(-1) REMOVE_TRAIT(src, TRAIT_SIXTHSENSE, "near-death") update_mobility() @@ -920,7 +915,7 @@ else clear_alert("handcuffed") SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed") - update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed. + update_mob_action_buttons() //some of our action buttons might be unusable when we're handcuffed. update_inv_handcuffed() update_hud_handcuffed() update_mobility() @@ -944,7 +939,7 @@ if(admin_revive) regenerate_limbs() regenerate_organs() - handcuffed = initial(handcuffed) + set_handcuffed(initial(handcuffed)) for(var/obj/item/restraints/R in contents) //actually remove cuffs from inventory qdel(R) update_handcuffed() @@ -990,7 +985,7 @@ if(organs_amt) to_chat(user, span_notice("You retrieve some of [src]\'s internal organs!")) -/mob/living/carbon/ExtinguishMob() +/mob/living/carbon/extinguish_mob() for(var/X in get_equipped_items()) var/obj/item/I = X I.acid_level = 0 //washes off the acid on our clothes diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 6648ded28f78..dec676f7b770 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -332,8 +332,7 @@ do_sparks(5, TRUE, src) var/power = M.powerlevel + rand(0,3) Paralyze(power*20) - if(stuttering < power) - stuttering = power + set_stutter_if_lower(power * 2 SECONDS) if (prob(stunprob) && M.powerlevel >= 8) adjustFireLoss(M.powerlevel * rand(6,10)) updatehealth() @@ -398,15 +397,12 @@ span_userdanger("You feel a powerful shock coursing through your body!"), \ span_italics("You hear a heavy electrical crack.") \ ) - jitteriness += 1000 //High numbers for violent convulsions - do_jitter_animation(jitteriness) - stuttering += 2 - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Paralyze(40) - spawn(20) - jitteriness = max(jitteriness - 990, 10) //Still jittery, but vastly less - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Paralyze(60) + do_jitter_animation(300) + adjust_stutter(4 SECONDS) + adjust_jitter(20 SECONDS) + var/should_stun = !tesla_shock || (tesla_shock && siemens_coeff > 0.5) && stun + Paralyze(4 SECONDS) + addtimer(CALLBACK(src, PROC_REF(secondary_shock), should_stun), 2 SECONDS) if(stat == DEAD && can_defib()) //yogs: ZZAPP if(!illusion && (shock_damage * siemens_coeff >= 1) && prob(80)) set_heartattack(FALSE) @@ -414,13 +410,18 @@ adjustToxLoss(-50) revive() INVOKE_ASYNC(src, PROC_REF(emote), "gasp") - Jitter(100) + adjust_jitter(10 SECONDS) adjustOrganLoss(ORGAN_SLOT_BRAIN, 100, 199) //yogs end if(override) return override else return shock_damage +///Called slightly after electrocute act to apply a secondary stun. +/mob/living/carbon/proc/secondary_shock(should_stun) + if(should_stun) + Paralyze(6 SECONDS) + /mob/living/carbon/proc/help_shake_act(mob/living/carbon/M) if(on_fire) to_chat(M, span_warning("You can't put [p_them()] out with just your bare hands!")) @@ -464,12 +465,13 @@ else if(averagestacks < -1) to_chat(src, span_notice("The hug [M] gave you was a little wet...")) - AdjustStun(-60) - AdjustKnockdown(-60) - AdjustUnconscious(-60) - AdjustSleeping(-100) - AdjustParalyzed(-60) - AdjustImmobilized(-60) + AdjustStun(-6 SECONDS) + AdjustKnockdown(-6 SECONDS) + AdjustUnconscious(-6 SECONDS) + AdjustSleeping(-10 SECONDS) + AdjustParalyzed(-6 SECONDS) + AdjustImmobilized(-6 SECONDS) +// adjustStaminaLoss(-10) if you want hugs to recover stamina damage, uncomment this if(dna && dna.check_mutation(ACTIVE_HULK)) if(prob(30)) adjustStaminaLoss(10) @@ -525,14 +527,10 @@ else to_chat(src, span_warning("Your eyes are really starting to hurt. This can't be good for you!")) - if(has_bane(BANE_LIGHT)) - mind.disrupt_spells(-500) return TRUE else if(damage == 0) // just enough protection if(prob(20)) to_chat(src, span_notice("Something bright flashes in the corner of your vision!")) - if(has_bane(BANE_LIGHT)) - mind.disrupt_spells(0) /mob/living/carbon/soundbang_act(intensity = 1, conf_pwr = 20, damage_pwr = 5, deafen_pwr = 15) @@ -544,7 +542,7 @@ var/effect_amount = intensity - ear_safety if(effect_amount > 0) if(conf_pwr) - confused += conf_pwr*effect_amount + adjust_confusion(conf_pwr*effect_amount) if(istype(ears) && (deafen_pwr || damage_pwr)) var/ear_damage = damage_pwr * effect_amount diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index e66022237d73..7ba9e49c058a 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -72,13 +72,10 @@ var/image/halbody var/obj/halitem var/hal_screwyhud = SCREWYHUD_NONE - var/next_hallucination = 0 var/cpr_time = 1 //CPR cooldown. var/damageoverlaytemp = 0 - var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects var/stam_regen_start_time = 0 //used to halt stamina regen temporarily - var/stam_paralyzed = FALSE //knocks you down /// All of the wounds a carbon has afflicted throughout their limbs var/list/all_wounds diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm index fde06911e525..1033a381f26b 100644 --- a/code/modules/mob/living/carbon/death.dm +++ b/code/modules/mob/living/carbon/death.dm @@ -62,7 +62,7 @@ I.forceMove(Tsec) I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) if(!no_brain && !no_organs)//drop other heads/brains carried if your own would be dropped - for(var/X in src.GetAllContents()) + for(var/X in src.get_all_contents()) if(istype(X, /obj/item/organ/brain) || istype(X, /obj/item/bodypart/head)) var/obj/item/H = X if(H) diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm index faad4db52df2..680e99d31964 100644 --- a/code/modules/mob/living/carbon/emote.dm +++ b/code/modules/mob/living/carbon/emote.dm @@ -4,7 +4,7 @@ /datum/emote/living/carbon/airguitar key = "airguitar" message = "is strumming the air and headbanging like a safari chimp." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/blink key = "blink" @@ -20,7 +20,7 @@ key_third_person = "claps" message = "claps." muzzle_ignore = TRUE - restraint_check = TRUE + hands_use_check = TRUE emote_type = EMOTE_AUDIBLE vary = TRUE @@ -64,21 +64,21 @@ key_third_person = "pointsdown" message = "points down." message_param = "points down towards %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/roll key = "roll" key_third_person = "rolls" message = "rolls." mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/scratch key = "scratch" key_third_person = "scratches" message = "scratches." mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/screech key = "screech" @@ -91,7 +91,7 @@ key_third_person = "signs" message_param = "signs the number %t." mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/sign/select_param(mob/user, params) . = ..() @@ -103,7 +103,7 @@ key_third_person = "signals" message_param = "raises %t fingers." mob_type_allowed_typecache = list(/mob/living/carbon/human) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/tail key = "tail" @@ -122,7 +122,7 @@ message_param = "snaps their fingers at %t." emote_type = EMOTE_AUDIBLE mob_type_allowed_typecache = list(/mob/living/carbon/human) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/snap/get_sound(mob/living/user) return pick('sound/misc/fingersnap1.ogg', 'sound/misc/fingersnap2.ogg') diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 8273dcdd8c77..735b2bd1daf1 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -59,9 +59,6 @@ . = ..() - dizziness = 0 - jitteriness = 0 - if(ismecha(loc)) var/obj/mecha/M = loc if(M.occupant == src) diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index fac87dc3b769..01ae668f9bbd 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -37,7 +37,7 @@ key_third_person = "daps" message = "sadly can't find anybody to give daps to, and daps themself. Shameful." message_param = "give daps to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/human/eyebrow key = "eyebrow" @@ -53,7 +53,7 @@ key = "handshake" message = "shakes their own hands." message_param = "shakes hands with %t." - restraint_check = TRUE + hands_use_check = TRUE emote_type = EMOTE_AUDIBLE /datum/emote/living/carbon/hiss @@ -90,7 +90,7 @@ key_third_person = "hugs" message = "hugs themself." message_param = "hugs %t." - restraint_check = TRUE + hands_use_check = TRUE emote_type = EMOTE_AUDIBLE /datum/emote/living/carbon/human/mumble @@ -149,14 +149,14 @@ key = "raise" key_third_person = "raises" message = "raises a hand." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/human/salute key = "salute" key_third_person = "salutes" message = "salutes." message_param = "salutes to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/human/shrug key = "shrug" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 0a329590d23f..e10426601c4e 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -23,7 +23,7 @@ . = list("This is [!obscure_name ? name : "Unknown"]!") - var/vampDesc = ReturnVampExamine(user) // Fulpstation Bloodsuckers edit STARTS + var/vampDesc = return_vamp_examine(user) // Fulpstation Bloodsuckers edit STARTS var/vassDesc = ReturnVassalExamine(user) if(vampDesc != "") . += vampDesc @@ -31,7 +31,6 @@ . += vassDesc // Fulpstation Bloodsucker edit ENDS var/list/obscured = check_obscured_slots() - var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) //uniform if(w_uniform && !(SLOT_W_UNIFORM in obscured)) @@ -50,7 +49,7 @@ if(wear_suit) . += "[t_He] [t_is] wearing [wear_suit.get_examine_string(user)]." //suit/armor storage - if(s_store && !(SLOT_S_STORE in obscured)) + if(s_store && !(SLOT_SUIT_STORE in obscured)) . += "[t_He] [t_is] carrying [s_store.get_examine_string(user)] on [t_his] [wear_suit.name]." //back if(back) @@ -114,15 +113,6 @@ //Status effects . += status_effect_examines() - //Jitters - switch(jitteriness) - if(300 to INFINITY) - . += span_warning("[t_He] [t_is] convulsing violently!") - if(200 to 300) - . += span_warning("[t_He] [t_is] extremely jittery.") - if(100 to 200) - . += span_warning("[t_He] [t_is] twitching ever so slightly.") - if(islist(dna.features) && dna.features["wings"] && dna.features["wings"] != "None") var/badwings = "" if(mind?.martial_art && istype(mind.martial_art, /datum/martial_art/ultra_violence)) @@ -373,21 +363,6 @@ msg += "[t_His] whole body is covered in sigils!\n" if(!appears_dead) - if(drunkenness && !skipface) //Drunkenness - switch(drunkenness) - if(11 to 21) - msg += "[t_He] [t_is] slightly flushed.\n" - if(21.01 to 41) //.01s are used in case drunkenness ends up to be a small decimal - msg += "[t_He] [t_is] flushed.\n" - if(41.01 to 51) - msg += "[t_He] [t_is] quite flushed and [t_his] breath smells of alcohol.\n" - if(51.01 to 61) - msg += "[t_He] [t_is] very flushed and [t_his] movements jerky, with breath reeking of alcohol.\n" - if(61.01 to 91) - msg += "[t_He] look[p_s()] like a drunken mess.\n" - if(91.01 to INFINITY) - msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n" - if(src != user) if(HAS_TRAIT(user, TRAIT_EMPATH)) if (a_intent != INTENT_HELP) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index daadd84208a7..5d6b8c20b40f 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -19,6 +19,8 @@ if(dna.species) set_species(dna.species.type) + prepare_huds() //Prevents a nasty runtime on human init + //initialise organs create_internal_organs() //most of it is done in set_species now, this is only for parent call physiology = new() @@ -181,12 +183,12 @@ dat += "Exosuit:[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "Empty"]" if(wear_suit) - if(SLOT_S_STORE in obscured) + if(SLOT_SUIT_STORE in obscured) dat += " ↳Suit Storage:" else - dat += " ↳Suit Storage:[(s_store && !(s_store.item_flags & ABSTRACT)) ? s_store : "Empty"]" + dat += " ↳Suit Storage:[(s_store && !(s_store.item_flags & ABSTRACT)) ? s_store : "Empty"]" if(has_breathable_mask && istype(s_store, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" + dat += " [internal ? "Disable Internals" : "Set Internals"]" dat += "" else dat += " ↳Suit Storage:" @@ -868,7 +870,6 @@ regenerate_organs() remove_all_embedded_objects() set_heartattack(FALSE) - drunkenness = 0 for(var/datum/mutation/human/HM in dna.mutations) if(HM.quality != POSITIVE) dna.remove_mutation(HM.name) @@ -1353,7 +1354,7 @@ /mob/living/carbon/human/proc/hulk_stamina_check() if(dna.check_mutation(ACTIVE_HULK)) if(staminaloss < 60 && prob(1)) - confused = 7 + adjust_confusion(7 SECONDS) say("HULK SMASH!!") if(staminaloss >= 90) dna.remove_mutation(ACTIVE_HULK) @@ -1365,6 +1366,6 @@ /mob/living/carbon/human/Bump(atom/movable/AM) ..() - if(dna.check_mutation(ACTIVE_HULK) && confused && (world.time - last_bumped) > 15) + if(dna.check_mutation(ACTIVE_HULK) && has_status_effect(/datum/status_effect/confusion) && (world.time - last_bumped) > 15) Bumped(AM) return AM.attack_hulk(src) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 4fa8aae26d79..cdca3dbba223 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -775,7 +775,7 @@ var/status = "" var/brutedamage = LB.brute_dam var/burndamage = LB.burn_dam - if(hallucination) + if(has_status_effect(/datum/status_effect/hallucination)) if(prob(30)) brutedamage += rand(30,40) if(prob(30)) diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 939a2c750e82..225a9b05baad 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -6,8 +6,7 @@ /mob/living/carbon/human/canBeHandcuffed() if(get_num_arms(FALSE) >= 2) return TRUE - else - return FALSE + return FALSE //gets assignment from ID or ID inside PDA or PDA itself //Useful when player do something with computers diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 9b6807a83744..6a8c17291a18 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -4,16 +4,6 @@ // Return the item currently in the slot ID /mob/living/carbon/human/get_item_by_slot(slot_id) switch(slot_id) - if(SLOT_BACK) - return back - if(SLOT_WEAR_MASK) - return wear_mask - if(SLOT_NECK) - return wear_neck - if(SLOT_HANDCUFFED) - return handcuffed - if(SLOT_LEGCUFFED) - return legcuffed if(SLOT_BELT) return belt if(SLOT_WEAR_ID) @@ -24,8 +14,6 @@ return glasses if(SLOT_GLOVES) return gloves - if(SLOT_HEAD) - return head if(SLOT_SHOES) return shoes if(SLOT_WEAR_SUIT) @@ -36,9 +24,48 @@ return l_store if(SLOT_R_STORE) return r_store - if(SLOT_S_STORE) + if(SLOT_SUIT_STORE) return s_store - return null + return ..() + +/mob/living/carbon/human/get_slot_by_item(obj/item/looking_for) + if(looking_for == belt) + return ITEM_SLOT_BELT + + if(looking_for == wear_id) + return ITEM_SLOT_ID + + if(looking_for == ears) + return ITEM_SLOT_EARS + + if(looking_for == glasses) + return ITEM_SLOT_EYES + + if(looking_for == gloves) + return ITEM_SLOT_GLOVES + + if(looking_for == head) + return ITEM_SLOT_HEAD + + if(looking_for == shoes) + return ITEM_SLOT_FEET + + if(looking_for == wear_suit) + return ITEM_SLOT_OCLOTHING + + if(looking_for == w_uniform) + return ITEM_SLOT_ICLOTHING + + if(looking_for == r_store) + return ITEM_SLOT_RPOCKET + + if(looking_for == l_store) + return ITEM_SLOT_LPOCKET + + if(looking_for == s_store) + return ITEM_SLOT_SUITSTORE + + return ..() /mob/living/carbon/human/proc/get_all_slots() . = get_head_slots() | get_body_slots() @@ -120,7 +147,7 @@ update_inv_w_uniform() if(wear_suit.breakouttime) //when equipping a straightjacket stop_pulling() //can't pull if restrained - update_action_buttons_icon() //certain action buttons will no longer be usable. + update_mob_action_buttons() //certain action buttons will no longer be usable. update_inv_wear_suit() if(SLOT_W_UNIFORM) w_uniform = I @@ -132,7 +159,7 @@ if(SLOT_R_STORE) r_store = I update_inv_pockets() - if(SLOT_S_STORE) + if(SLOT_SUIT_STORE) s_store = I update_inv_s_store() else @@ -156,7 +183,7 @@ dropItemToGround(s_store, TRUE) //It makes no sense for your suit storage to stay on you if you drop your suit. if(wear_suit.breakouttime) //when unequipping a straightjacket drop_all_held_items() //suit is restraining - update_action_buttons_icon() //certain action buttons may be usable again. + update_mob_action_buttons() //certain action buttons may be usable again. wear_suit = null if(!QDELETED(src)) //no need to update we're getting deleted anyway if(I.flags_inv & HIDEJUMPSUIT) @@ -370,12 +397,12 @@ /mob/living/carbon/human/proc/smart_equipsuit() var/obj/item/thing = get_active_held_item() - var/obj/item/equipped_suit = get_item_by_slot(SLOT_S_STORE) + var/obj/item/equipped_suit = get_item_by_slot(SLOT_SUIT_STORE) if(!equipped_suit) if(!thing) to_chat(src, span_notice("You have no suit storage to take something out of.")) return - if(equip_to_slot_if_possible(thing, SLOT_S_STORE)) + if(equip_to_slot_if_possible(thing, SLOT_SUIT_STORE)) update_inv_hands() return if(!SEND_SIGNAL(equipped_suit, COMSIG_CONTAINS_STORAGE)) // not a storage item diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 53e635a44177..5dee0a2496cd 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -165,17 +165,17 @@ thermal_protection = round(thermal_protection) return thermal_protection -/mob/living/carbon/human/IgniteMob() +/mob/living/carbon/human/ignite_mob() //If have no DNA or can be Ignited, call parent handling to light user //If firestacks are high enough - if(!dna || dna.species.CanIgniteMob(src)) + if(!dna || dna.species.Canignite_mob(src)) if(get_thermal_protection() > FIRE_SUIT_MAX_TEMP_PROTECT*0.95) // If they're resistant to fire (slightly undercut to make sure get_thermal_protection doesn't fuck over this achievement due to floating-point errors SSachievements.unlock_achievement(/datum/achievement/engineering/toasty,src.client) // Fear the reaper man! return ..() . = FALSE //No ignition -/mob/living/carbon/human/ExtinguishMob() - if(!dna || !dna.species.ExtinguishMob(src)) +/mob/living/carbon/human/extinguish_mob() + if(!dna || !dna.species.extinguish_mob(src)) last_fire_update = null ..() //END FIRE CODE diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index 69f0a59b8339..29e8b3326d14 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -5,10 +5,7 @@ else verb_say = dna.species.say_mod - if(slurring) - return "slurs" - else - . = ..() + . = ..() /mob/living/carbon/human/GetVoice() if(istype(wear_mask, /obj/item/clothing/mask/chameleon)) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index d226037d7bc2..5c67d5bab123 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -335,7 +335,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(slot == ORGAN_SLOT_BRAIN) var/obj/item/organ/brain/brain = oldorgan if(!brain.decoy_override)//"Just keep it if it's fake" - confucius, probably - brain.Remove(C,TRUE, TRUE) //brain argument used so it doesn't cause any... sudden death. + brain.Remove(C, TRUE, TRUE) //brain argument used so it doesn't cause any... sudden death. QDEL_NULL(brain) oldorgan = null //now deleted else @@ -1233,7 +1233,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_POCKET) ) return TRUE return FALSE - if(SLOT_S_STORE) + if(SLOT_SUIT_STORE) if(HAS_TRAIT(I, TRAIT_NODROP)) return FALSE if(H.s_store && H.s_store != I) @@ -1358,7 +1358,7 @@ GLOBAL_LIST_EMPTY(features_by_species) else if(H.satiety < 0) H.satiety++ if(prob(round(-H.satiety/40))) - H.Jitter(5) + H.adjust_jitter(5 SECONDS) hunger_rate = 3 * HUNGER_FACTOR hunger_rate *= H.physiology.hunger_mod H.adjust_nutrition(-hunger_rate) @@ -1828,7 +1828,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(H.stat == CONSCIOUS) H.visible_message(span_danger("[H] has been knocked senseless!"), \ span_userdanger("[H] has been knocked senseless!")) - H.confused = max(H.confused, 20) + H.set_confusion_if_lower(20 SECONDS) H.adjust_blurriness(10) if(prob(10)) H.gain_trauma(/datum/brain_trauma/mild/concussion) @@ -2076,7 +2076,7 @@ GLOBAL_LIST_EMPTY(features_by_species) ////////// /datum/species/proc/handle_fire(mob/living/carbon/human/H, no_protection = FALSE) - if(!CanIgniteMob(H)) + if(!Canignite_mob(H)) return TRUE if(H.on_fire) //the fire tries to damage the exposed clothes and items @@ -2137,12 +2137,12 @@ GLOBAL_LIST_EMPTY(features_by_species) H.adjust_bodytemperature(BODYTEMP_HEATING_MAX + (H.fire_stacks * 2)) SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire) -/datum/species/proc/CanIgniteMob(mob/living/carbon/human/H) +/datum/species/proc/Canignite_mob(mob/living/carbon/human/H) if(HAS_TRAIT(H, TRAIT_NOFIRE)) return FALSE return TRUE -/datum/species/proc/ExtinguishMob(mob/living/carbon/human/H) +/datum/species/proc/extinguish_mob(mob/living/carbon/human/H) return /datum/species/proc/spec_revival(mob/living/carbon/human/H, admin_revive = FALSE) @@ -2308,8 +2308,8 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/action/innate/flight name = "Toggle Flight" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN - icon_icon = 'icons/mob/actions/actions_items.dmi' + check_flags = AB_CHECK_CONSCIOUS| AB_CHECK_IMMOBILE + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "flight" /datum/action/innate/flight/Activate() diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm index a181ebaa48ed..ae2a2355f012 100644 --- a/code/modules/mob/living/carbon/human/species_types/IPC.dm +++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm @@ -75,7 +75,7 @@ else if(MUTCOLORS in C.dna.species.species_traits) C.dna.species.species_traits -= MUTCOLORS -datum/species/ipc/on_species_loss(mob/living/carbon/C) +/datum/species/ipc/on_species_loss(mob/living/carbon/C) . = ..() QDEL_NULL(C.particles) if(change_screen) @@ -126,11 +126,11 @@ datum/species/ipc/on_species_loss(mob/living/carbon/C) /datum/action/innate/change_screen name = "Change Display" check_flags = AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "drone_vision" /datum/action/innate/change_screen/Activate() - var/screen_choice = input(usr, "Which screen do you want to use?", "Screen Change") as null | anything in GLOB.ipc_screens_list + var/screen_choice = tgui_input_list(usr, "Which screen do you want to use?", "Screen Change", GLOB.ipc_screens_list) var/color_choice = input(usr, "Which color do you want your screen to be?", "Color Change") as null | color if(!screen_choice) return diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm index 17b367912cde..c8862226ec77 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -11,9 +11,9 @@ /datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] - abductor_hud.add_hud_to(C) + abductor_hud.show_to(C) /datum/species/abductor/on_species_loss(mob/living/carbon/C) . = ..() var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] - abductor_hud.remove_hud_from(C) + abductor_hud.hide_from(C) diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index a4422086f441..aaf009608d09 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -12,7 +12,7 @@ punchdamagelow = 5 punchdamagehigh = 14 punchstunthreshold = 11 //about 40% chance to stun - no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE) + no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_SUIT_STORE) nojumpsuit = 1 changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC sexes = 1 @@ -152,7 +152,7 @@ to_chat(owner, span_notice("You ignite yourself!")) else to_chat(owner, span_warning("You try to ignite yourself, but fail!")) - H.IgniteMob() //firestacks are already there passively + H.ignite_mob() //firestacks are already there passively //Harder to hurt /datum/species/golem/diamond @@ -323,7 +323,7 @@ H.adjust_nutrition(light_amount * 10) if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) - if(light_amount > 0.2) //if there's enough light, heal + if(light_amount > LIGHTING_TILE_IS_DARK) //if there's enough light, heal H.heal_overall_damage(1,1,0, BODYPART_ORGANIC) H.adjustToxLoss(-1) H.adjustOxyLoss(-1) @@ -525,11 +525,11 @@ name = "Unstable Teleport" check_flags = AB_CHECK_CONSCIOUS button_icon_state = "jaunt" - icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon = 'icons/mob/actions/actions_spells.dmi' var/cooldown = 15 SECONDS var/last_teleport = 0 -/datum/action/innate/unstable_teleport/IsAvailable() +/datum/action/innate/unstable_teleport/IsAvailable(feedback = FALSE) if(..()) if(world.time > last_teleport + cooldown) return 1 @@ -549,9 +549,9 @@ spark_system.start() do_teleport(H, get_turf(H), 12, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) last_teleport = world.time - UpdateButtonIcon() //action icon looks unavailable + build_all_button_icons() //action icon looks unavailable sleep(cooldown + 0.5 SECONDS) - UpdateButtonIcon() //action icon looks available again + build_all_button_icons() //action icon looks available again //honk @@ -646,9 +646,12 @@ prefix = "Runic" special_names = null - var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem/phase_shift - var/obj/effect/proc_holder/spell/targeted/abyssal_gaze/abyssal_gaze - var/obj/effect/proc_holder/spell/targeted/dominate/dominate + /// A ref to our jaunt spell that we get on species gain. + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/golem/jaunt + /// A ref to our gaze spell that we get on species gain. + var/datum/action/cooldown/spell/pointed/abyssal_gaze/abyssal_gaze + /// A ref to our dominate spell that we get on species gain. + var/datum/action/cooldown/spell/pointed/dominate/dominate /datum/species/golem/runic/random_name(gender,unique,lastname) var/edgy_first_name = pick("Razor","Blood","Dark","Evil","Cold","Pale","Black","Silent","Chaos","Deadly","Coldsteel") @@ -656,28 +659,33 @@ var/golem_name = "[edgy_first_name] [edgy_last_name]" return golem_name -/datum/species/golem/runic/on_species_gain(mob/living/carbon/C, datum/species/old_species) +/datum/species/golem/runic/on_species_gain(mob/living/carbon/grant_to, datum/species/old_species) . = ..() - C.faction |= "cult" - phase_shift = new - phase_shift.charge_counter = 0 - C.AddSpell(phase_shift) - abyssal_gaze = new - abyssal_gaze.charge_counter = 0 - C.AddSpell(abyssal_gaze) - dominate = new - dominate.charge_counter = 0 - C.AddSpell(dominate) - -/datum/species/golem/runic/on_species_loss(mob/living/carbon/C) + grant_to.faction |= "cult" + // Create our species specific spells here. + // Note we link them to the mob, not the mind, + // so they're not moved around on mindswaps + jaunt = new(grant_to) + jaunt.StartCooldown() + jaunt.Grant(grant_to) + + abyssal_gaze = new(grant_to) + abyssal_gaze.StartCooldown() + abyssal_gaze.Grant(grant_to) + + dominate = new(grant_to) + dominate.StartCooldown() + dominate.Grant(grant_to) + +/datum/species/golem/runic/on_species_loss(mob/living/carbon/remove_from) . = ..() - C.faction -= "cult" - if(phase_shift) - C.RemoveSpell(phase_shift) - if(abyssal_gaze) - C.RemoveSpell(abyssal_gaze) - if(dominate) - C.RemoveSpell(dominate) + remove_from.faction -= "cult" + // Aaand cleanup our species specific spells. + // No free rides. + QDEL_NULL(jaunt) + QDEL_NULL(abyssal_gaze) + QDEL_NULL(dominate) + return ..() /datum/species/golem/runic/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) if(istype(chem, /datum/reagent/water/holywater)) @@ -927,21 +935,21 @@ H.show_message(span_narsiesmall("You cringe with pain as your body rings around you!"), MSG_AUDIBLE) H.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) H.soundbang_act(2, 0, 10, 1) - H.jitteriness += 7 + M.adjust_jitter(7 SECONDS) var/distance = max(0,get_dist(get_turf(H),get_turf(M))) switch(distance) if(0 to 1) M.show_message(span_narsiesmall("GONG!"), MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) M.soundbang_act(1, 0, 10, 3) - M.confused += 10 - M.jitteriness += 4 + M.adjust_confusion(10 SECONDS) + M.adjust_jitter(4 SECONDS) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) if(2 to 3) M.show_message(span_cult("GONG!"), MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 75, TRUE) M.soundbang_act(1, 0, 5, 2) - M.jitteriness += 3 + M.adjust_jitter(3 SECONDS) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) else M.show_message(span_warning("GONG!"), MSG_AUDIBLE) @@ -960,8 +968,10 @@ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES) //no mutcolors, no eye sprites inherent_traits = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) - var/obj/effect/proc_holder/spell/targeted/conjure_item/snowball/ball - var/obj/effect/proc_holder/spell/aimed/cryo/cryo + /// A ref to our "throw snowball" spell we get on species gain. + var/datum/action/cooldown/spell/conjure_item/snowball/snowball + /// A ref to our cryobeam spell we get on species gain. + var/datum/action/cooldown/spell/pointed/projectile/cryo/cryo /datum/species/golem/snow/spec_death(gibbed, mob/living/carbon/human/H) H.visible_message(span_danger("[H] turns into a pile of snow!")) @@ -972,31 +982,23 @@ new /obj/item/reagent_containers/food/snacks/grown/carrot(get_turf(H)) qdel(H) -/datum/species/golem/snow/on_species_gain(mob/living/carbon/C, datum/species/old_species) +/datum/species/golem/snow/on_species_gain(mob/living/carbon/grant_to, datum/species/old_species) . = ..() - C.weather_immunities |= "snow" - ball = new - ball.charge_counter = 0 - C.AddSpell(ball) - cryo = new - cryo.charge_counter = 0 - C.AddSpell(cryo) - -/datum/species/golem/snow/on_species_loss(mob/living/carbon/C) + grant_to.weather_immunities |= "snow" + snowball = new(grant_to) + snowball.StartCooldown() + snowball.Grant(grant_to) + + cryo = new(grant_to) + cryo.StartCooldown() + cryo.Grant(grant_to) + +/datum/species/golem/snow/on_species_loss(mob/living/carbon/remove_from) . = ..() - C.weather_immunities -= "snow" - if(ball) - C.RemoveSpell(ball) - if(cryo) - C.RemoveSpell(cryo) - -/obj/effect/proc_holder/spell/targeted/conjure_item/snowball - name = "Snowball" - desc = "Concentrates cryokinetic forces to create snowballs, useful for throwing at people." - item_type = /obj/item/toy/snowball - charge_max = 15 - action_icon = 'icons/obj/toy.dmi' - action_icon_state = "snowball" + remove_from.weather_immunities -= "snow" + QDEL_NULL(snowball) + QDEL_NULL(cryo) + return ..() /datum/species/golem/cardboard //Faster but weaker, can also make new shells on its own name = "Cardboard Golem" @@ -1105,7 +1107,7 @@ name = "Bone Chill" desc = "Rattle your bones and strike fear into your enemies!" check_flags = AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/humble/actions_humble.dmi' + button_icon = 'icons/mob/actions/humble/actions_humble.dmi' button_icon_state = "bonechill" var/cooldown = 600 var/last_use @@ -1159,15 +1161,16 @@ to_chat(C, span_userdanger("Hit non-golems several times in order to get them fat and on your side!")) SEND_SOUND(C, sound('sound/misc/capitialism.ogg')) - C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) + var/datum/action/cooldown/spell/aoe/knock/OPEN_THE_DOOR = new(C) + OPEN_THE_DOOR.Grant(C) RegisterSignal(C, COMSIG_MOB_SAY, PROC_REF(handle_speech)) C.mind.add_antag_datum(/datum/antagonist/golem/capitalist) /datum/species/golem/capitalist/on_species_loss(mob/living/carbon/C) . = ..() UnregisterSignal(C, COMSIG_MOB_SAY) - for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) - C.RemoveSpell(spell) + for(var/datum/action/cooldown/spell/aoe/knock/OPEN_THE_DOOR in C.actions) + OPEN_THE_DOOR.Remove(C) var/datum/antagonist/golem/capitalist/CA = C.mind.has_antag_datum(/datum/antagonist/golem/capitalist) if(CA && !CA.removing) C.mind.remove_antag_datum(/datum/antagonist/golem/capitalist) @@ -1182,7 +1185,7 @@ target.adjust_nutrition(40) /datum/species/golem/capitalist/proc/handle_speech(datum/source, list/speech_args) - playsound(source, 'sound/misc/mymoney.ogg', 25, 0) + playsound(source, 'sound/misc/mymoney.ogg', 25, TRUE) speech_args[SPEECH_MESSAGE] = "Hello, I like money!" /datum/species/golem/church_capitalist //slightly faster reskinned iron golem gained from a cult of st credit rite @@ -1230,14 +1233,15 @@ to_chat(C, span_userdanger("Hit non-golems several times in order to get them starving and on your side!")) SEND_SOUND(C, sound('sound/misc/Russian_Anthem_chorus.ogg')) - C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) + var/datum/action/cooldown/spell/aoe/knock/OPEN_THE_DOOR = new(C) + OPEN_THE_DOOR.Grant(C) RegisterSignal(C, COMSIG_MOB_SAY, PROC_REF(handle_speech)) C.mind.add_antag_datum(/datum/antagonist/golem/communist) /datum/species/golem/soviet/on_species_loss(mob/living/carbon/C) . = ..() - for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) - C.RemoveSpell(spell) + for(var/datum/action/cooldown/spell/aoe/knock/OPEN_THE_DOOR in C.actions) + OPEN_THE_DOOR.Remove(C) UnregisterSignal(C, COMSIG_MOB_SAY) var/datum/antagonist/golem/communist/CU = C.mind.has_antag_datum(/datum/antagonist/golem/communist) if(CU && !CU.removing) @@ -1253,7 +1257,7 @@ target.adjust_nutrition(-40) /datum/species/golem/soviet/proc/handle_speech(datum/source, list/speech_args) - playsound(source, 'sound/misc/Cyka Blyat.ogg', 25, 0) + playsound(source, 'sound/misc/Cyka Blyat.ogg', 25, TRUE) //if it's handled with the ambient it's funnier speech_args[SPEECH_MESSAGE] = "Cyka Blyat" /datum/species/golem/cheese @@ -1320,35 +1324,35 @@ species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR,NOFLASH) prefix = "Telecrystal" special_names = list("Agent", "Operative") - var/obj/effect/proc_holder/spell/pointed/phase_jump/phase_jump + var/datum/action/cooldown/spell/pointed/phase_jump/phase_jump /datum/species/golem/telecrystal/on_species_gain(mob/living/carbon/C, datum/species/old_species) ..() if(ishuman(C)) - phase_jump = new - C.AddSpell(phase_jump) + phase_jump = new(C) + phase_jump.Grant(C) /datum/species/golem/telecrystal/on_species_loss(mob/living/carbon/C) if(phase_jump) - C.RemoveSpell(phase_jump) + phase_jump.Remove(C) ..() -/obj/effect/proc_holder/spell/pointed/phase_jump +/datum/action/cooldown/spell/pointed/phase_jump name = "Phase Jump" desc = "Tap the power of your telecrystal body to teleport a short distance!" - charge_max = 200 - clothes_req = FALSE - stat_allowed = FALSE - antimagic_allowed = TRUE - cooldown_min = 200 - range = 3 + button_icon_state = "phasejump" ranged_mousepointer = 'icons/effects/mouse_pointers/phase_jump.dmi' - action_icon_state = "phasejump" + + cooldown_time = 20 SECONDS + cast_range = 3 active_msg = span_notice("You start channeling your telecrystal core....") deactive_msg = span_notice("You stop channeling your telecrystal core.") + spell_requirements = NONE -/obj/effect/proc_holder/spell/pointed/phase_jump/cast(list/targets,mob/user = usr) - var/target = targets[1] +/datum/action/cooldown/spell/pointed/phase_jump/InterceptClickOn(atom/target, params, mob/living/user) + . = ..() + if(!.) + return FALSE var/turf/T = get_turf(target) var/phasein = /obj/effect/temp_visual/dir_setting/cult/phase var/phaseout = /obj/effect/temp_visual/dir_setting/cult/phase/out @@ -1357,16 +1361,17 @@ var/obj/spot2 = new phasein(get_turf(user), user.dir) spot1.Beam(spot2,"tentacle",time=20) user.visible_message("[user] phase shifts away!", span_warning("You shift around the space around you.")) + return TRUE -/obj/effect/proc_holder/spell/pointed/phase_jump/can_target(atom/target, mob/user, silent) +/datum/action/cooldown/spell/pointed/phase_jump/is_valid_target(atom/target) . = ..() if(!.) return FALSE var/turf/T = get_turf(target) - var/area/AU = get_area(user) + var/area/AU = get_area(owner) var/area/AT = get_area(T) if(AT.noteleport || AU.noteleport) - remove_ranged_ability("Something nullifies any teleports in the local area...") + owner.balloon_alert(owner, "something omnious prevents your teleport!") return FALSE return TRUE @@ -1379,31 +1384,29 @@ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES) //no mutcolors or eyesprites speedmod = 1.5 //inbetween gold golem and iron meat = /obj/item/reagent_containers/food/snacks/meat/slab/blessed - info_text = "As an Ruinous Golem, you are made of an ancient powerful metal. While not particularly tough, you have a connection with the old gods that grants you a selection of abilities." + info_text = "As an Ruinous Golem, you are made of an ancient powerful metal. While not particularly tough, \ + you have a connection with the old gods that grants you a selection of abilities." prefix = "Ruinous" special_names = list("One", "Elder", "Watcher", "Walker") //ominous - var/obj/effect/proc_holder/spell/targeted/telepathy/eldritch/ruinoustelepathy - var/obj/effect/proc_holder/spell/targeted/touch/flagellate/flagellate + var/datum/action/cooldown/spell/list_target/telepathy/eldritch/ruinoustelepathy +// var/datum/action/cooldown/spell/touch/flagellate/flagellate /datum/species/golem/ruinous/on_species_loss(mob/living/carbon/C) ..() UnregisterSignal(C, COMSIG_MOB_SAY) REMOVE_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) - if(ruinoustelepathy) - C.RemoveSpell(ruinoustelepathy) - if(flagellate) - C.RemoveSpell(flagellate) + ruinoustelepathy?.Remove(C) +// if(flagellate) +// C.RemoveSpell(flagellate) /datum/species/golem/ruinous/on_species_gain(mob/living/carbon/C, datum/species/old_species) ..() RegisterSignal(C, COMSIG_MOB_SAY, PROC_REF(handle_speech)) ADD_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) - ruinoustelepathy = new - ruinoustelepathy.charge_counter = 0 - C.AddSpell(ruinoustelepathy) - flagellate = new - flagellate.charge_counter = 0 - C.AddSpell(flagellate) + ruinoustelepathy = new(C) + ruinoustelepathy.Grant(C) +// flagellate = new +// C.AddSpell(flagellate) /datum/species/golem/ruinous/proc/handle_speech(datum/source, list/speech_args) speech_args[SPEECH_SPANS] |= SPAN_CULTLARGE diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index c5f1f5b8b019..118efc7eddcf 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -56,7 +56,7 @@ if(H.blood_volume < BLOOD_VOLUME_BAD(H)) Cannibalize_Body(H) if(regenerate_limbs) - regenerate_limbs.UpdateButtonIcon() + regenerate_limbs.build_all_button_icons() /datum/species/jelly/proc/Cannibalize_Body(mob/living/carbon/human/H) var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs() @@ -121,10 +121,11 @@ name = "Regenerate Limbs" check_flags = AB_CHECK_CONSCIOUS button_icon_state = "slimeheal" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" -/datum/action/innate/regenerate_limbs/IsAvailable() +/datum/action/innate/regenerate_limbs/IsAvailable(feedback = FALSE) if(..()) var/mob/living/carbon/human/H = owner var/list/limbs_to_heal = H.get_missing_limbs() @@ -218,10 +219,11 @@ name = "Split Body" check_flags = AB_CHECK_CONSCIOUS button_icon_state = "slimesplit" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" -/datum/action/innate/split_body/IsAvailable() +/datum/action/innate/split_body/IsAvailable(feedback = FALSE) if(..()) var/mob/living/carbon/human/H = owner if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) @@ -286,8 +288,9 @@ name = "Swap Body" check_flags = NONE button_icon_state = "slimeswap" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" /datum/action/innate/swap_body/Activate() if(!isslimeperson(owner)) @@ -474,9 +477,9 @@ /datum/species/jelly/luminescent/proc/update_slime_actions() integrate_extract.update_name() - integrate_extract.UpdateButtonIcon() - extract_minor.UpdateButtonIcon() - extract_major.UpdateButtonIcon() + integrate_extract.build_all_button_icons() + extract_minor.build_all_button_icons() + extract_major.build_all_button_icons() /datum/species/jelly/luminescent/proc/update_glow(mob/living/carbon/C, intensity) if(intensity) @@ -502,8 +505,9 @@ desc = "Eat a slime extract to use its properties." check_flags = AB_CHECK_CONSCIOUS button_icon_state = "slimeconsume" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" var/datum/species/jelly/luminescent/species /datum/action/innate/integrate_extract/New(_species) @@ -518,17 +522,25 @@ name = "Eject Extract" desc = "Eject your current slime extract." -/datum/action/innate/integrate_extract/UpdateButtonIcon(status_only, force) - if(!species || !species.current_extract) +/datum/action/innate/integrate_extract/update_button_name(atom/movable/screen/movable/action_button/button, force = FALSE) + var/datum/species/jelly/luminescent/species = target + if(!istype(species) || !species.current_extract) + name = "Integrate Extract" + desc = "Eat a slime extract to use its properties." + else + name = "Eject Extract" + desc = "Eject your current slime extract." + + return ..() + +/datum/action/innate/integrate_extract/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force) + var/datum/species/jelly/luminescent/species = target + if(!istype(species) || !species.current_extract) button_icon_state = "slimeconsume" else button_icon_state = "slimeeject" - ..() -/datum/action/innate/integrate_extract/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force) - ..(current_button, TRUE) - if(species && species.current_extract) - current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) + return ..() /datum/action/innate/integrate_extract/Activate() var/mob/living/carbon/human/H = owner @@ -564,8 +576,9 @@ desc = "Pulse the slime extract with energized jelly to activate it." check_flags = AB_CHECK_CONSCIOUS button_icon_state = "slimeuse1" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" var/activation_type = SLIME_ACTIVATE_MINOR var/datum/species/jelly/luminescent/species @@ -573,13 +586,13 @@ ..() species = _species -/datum/action/innate/use_extract/IsAvailable() +/datum/action/innate/use_extract/IsAvailable(feedback = FALSE) if(..()) if(species && species.current_extract && (world.time > species.extract_cooldown)) return TRUE return FALSE -/datum/action/innate/use_extract/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force) +/datum/action/innate/use_extract/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force) ..(current_button, TRUE) if(species && species.current_extract) current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) @@ -670,7 +683,7 @@ name = "Slimelink" desc = "Send a psychic message to everyone connected to your slime link." button_icon_state = "link_speech" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" var/datum/species/jelly/stargazer/species @@ -715,8 +728,9 @@ name = "Send Thought" desc = "Send a private psychic message to someone you can see." button_icon_state = "send_mind" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" /datum/action/innate/project_thought/Activate() var/mob/living/carbon/human/H = owner @@ -754,8 +768,9 @@ name = "Link Minds" desc = "Link someone's mind to your Slime Link, allowing them to communicate telepathically with other linked minds." button_icon_state = "mindlink" - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" var/datum/species/jelly/stargazer/species /datum/action/innate/link_minds/New(_species) diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 3ff011be04f2..b5bf2a76f675 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -206,41 +206,40 @@ punchdamagehigh = 7 punchstunthreshold = 7 action_speed_coefficient = 0.9 //they're smart and efficient unlike other lizards - species_language_holder = /datum/language_holder/lizard/shaman //shaman "smart" - var/obj/effect/proc_holder/spell/targeted/touch/healtouch/goodtouch + species_language_holder = /datum/language_holder/lizard/shaman + var/datum/action/cooldown/spell/touch/healtouch/lizardtouch //gives the heal spell /datum/species/lizard/ashwalker/shaman/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() - goodtouch = new /obj/effect/proc_holder/spell/targeted/touch/healtouch - C.AddSpell(goodtouch) + lizardtouch = new(C) + lizardtouch.Grant(C) //removes the heal spell /datum/species/lizard/ashwalker/shaman/on_species_loss(mob/living/carbon/C) . = ..() - if(goodtouch) - C.RemoveSpell(goodtouch) + QDEL_NULL(lizardtouch) //basic touch ability that heals brute and burn, only accessed by the ashwalker shaman -/obj/effect/proc_holder/spell/targeted/touch/healtouch +/datum/action/cooldown/spell/touch/healtouch name = "healing touch" desc = "This spell charges your hand with the vile energy of the Necropolis, permitting you to undo some external injuries from a target." + panel = "Ashwalker" + button_icon_state = "spell_default" hand_path = /obj/item/melee/touch_attack/healtouch - school = "evocation" - panel = "Ashwalker" - charge_max = 20 SECONDS - clothes_req = FALSE - antimagic_allowed = TRUE + school = SCHOOL_EVOCATION + invocation = "BE REPLENISHED!!" + invocation_type = INVOCATION_SHOUT - action_icon_state = "spell_default" + sound = 'sound/magic/staff_healing.ogg' + cooldown_time = 20 SECONDS + spell_requirements = NONE /obj/item/melee/touch_attack/healtouch name = "\improper healing touch" desc = "A blaze of life-granting energy from the hand. Heals minor to moderate injuries." - catchphrase = "BE REPLENISHED!!" - on_use_sound = 'sound/magic/staff_healing.ogg' - icon_state = "touchofdeath" //ironic huh + icon_state = "touchofdeath" //ironic huh //no item_state = "touchofdeath" var/healamount = 20 //total of 40 assuming they're hurt by both brute and burn diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index cda80928cb66..3f47cf8cc209 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -49,7 +49,7 @@ H.adjust_fire_stacks(0.5) if(!H.on_fire && H.fire_stacks > 0) H.visible_message(span_danger("[H]'s body reacts with the atmosphere and bursts into flames!"),span_userdanger("Your body reacts with the atmosphere and bursts into flame!")) - H.IgniteMob() + H.ignite_mob() internal_fire = TRUE else if(H.fire_stacks) diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index 8dc380cb5744..5a62594bd338 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -53,7 +53,7 @@ DISREGUARD THIS FILE IF YOU'RE INTENDING TO CHANGE ASPECTS OF PLAYER CONTROLLED H.adjust_nutrition(light_amount * 10) if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) - if(light_amount > 0.2) //if there's enough light, heal + if(light_amount > LIGHTING_TILE_IS_DARK) //if there's enough light, heal H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) H.adjustOxyLoss(-1) if(H.radiation < 500) diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 8d31a813278e..9d6fd3130a33 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -86,7 +86,7 @@ id = "nightmare" limbs_id = "shadow" burnmod = 1.5 - no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE) + no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_SUIT_STORE) species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES,NOFLASH) inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) mutanteyes = /obj/item/organ/eyes/night_vision/nightmare @@ -121,23 +121,20 @@ name = "tumorous mass" desc = "A fleshy growth that was dug out of the skull of a Nightmare." icon_state = "brain-x-d" - var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk + var/datum/action/cooldown/spell/jaunt/shadow_walk/our_jaunt -/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = 0) - ..() - if(M.dna.species.id != "nightmare") - M.set_species(/datum/species/shadow/nightmare) - visible_message(span_warning("[M] thrashes as [src] takes root in [M.p_their()] body!")) - var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new - M.AddSpell(SW) - shadowwalk = SW - - -/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = 0) - if(shadowwalk) - M.RemoveSpell(shadowwalk) +/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/host, special = FALSE) ..() + if(host.dna.species.id != "nightmare") + host.set_species(/datum/species/shadow/nightmare) + visible_message(span_warning("[host] thrashes as [src] takes root in [host.p_their()] body!")) + + our_jaunt = new(host) + our_jaunt.Grant(host) +/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/host, special = FALSE) + QDEL_NULL(our_jaunt) + return ..() /obj/item/organ/heart/nightmare name = "heart of darkness" @@ -186,7 +183,7 @@ return //always beating visually /obj/item/organ/heart/nightmare/process() - if(QDELETED(owner) || owner.stat != DEAD) + if(QDELETED(owner) || owner.stat != DEAD || !owner) respawn_progress = 0 return var/turf/T = get_turf(owner) @@ -249,7 +246,7 @@ if(borg.lamp_enabled) borg.smash_headlamp() else if(ishuman(AM)) - for(var/obj/item/O in AM.GetAllContents()) + for(var/obj/item/O in AM.get_all_contents()) if(O.light_range && O.light_power) disintegrate(O) if(L.pulling && L.pulling.light_range && isitem(L.pulling)) diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm index af55a09bcdb0..f028cddf77d4 100644 --- a/code/modules/mob/living/carbon/human/species_types/snail.dm +++ b/code/modules/mob/living/carbon/human/species_types/snail.dm @@ -61,7 +61,7 @@ /obj/item/storage/backpack/snail/Initialize() . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) + ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT(type)) /datum/species/snail/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) . = ..() diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 5a5f6c407ba5..47c8929b4137 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -14,7 +14,7 @@ limbs_id = "human" skinned_type = /obj/item/stack/sheet/animalhide/human var/info_text = "You are a Vampire. You will slowly but constantly lose blood if outside of a coffin. If inside a coffin, you will slowly heal. You may gain more blood by grabbing a live victim and using your drain ability." - var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/batform //attached to the datum itself to avoid cloning memes, and other duplicates + var/datum/action/cooldown/spell/shapeshift/bat/batform //attached to the datum itself to avoid cloning memes, and other duplicates /datum/species/vampire/check_roundstart_eligible() if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) @@ -27,13 +27,13 @@ C.skin_tone = "albino" C.update_body(0) if(isnull(batform)) - batform = new - C.AddSpell(batform) + batform = new(C) + batform.Grant(C) /datum/species/vampire/on_species_loss(mob/living/carbon/C) . = ..() if(!isnull(batform)) - C.RemoveSpell(batform) + batform.Remove(C) QDEL_NULL(batform) /datum/species/vampire/spec_life(mob/living/carbon/human/C) @@ -47,16 +47,15 @@ C.blood_volume -= 0.75 if(C.blood_volume <= BLOOD_VOLUME_SURVIVE(C)) to_chat(C, span_danger("You ran out of blood!")) - var/obj/shapeshift_holder/H = locate() in C - if(H) - H.shape.dust() //make sure we're killing the bat if you are out of blood, if you don't it creates weird situations where the bat is alive but the caster is dusted. + if(C.has_status_effect(/datum/status_effect/shapechange_mob/from_spell)) + C.dust() //make sure we're killing the bat if you are out of blood, if you don't it creates weird situations where the bat is alive but the caster is dusted. C.dust() var/area/A = get_area(C) if(istype(A, /area/chapel)) to_chat(C, span_danger("You don't belong here!")) C.adjustFireLoss(20) C.adjust_fire_stacks(6) - C.IgniteMob() + C.ignite_mob() /datum/species/vampire/check_species_weakness(obj/item/weapon, mob/living/attacker) if(istype(weapon, /obj/item/nullrod/whip)) @@ -205,10 +204,10 @@ var/mob/living/carbon/H = owner to_chat(H, span_notice("Current blood level: [H.blood_volume]/[BLOOD_VOLUME_MAXIMUM(H)].")) -/obj/effect/proc_holder/spell/targeted/shapeshift/bat +/datum/action/cooldown/spell/shapeshift/bat name = "Bat Form" desc = "Take on the shape of a space bat." + invocation = "Squeak!" - charge_max = 50 - cooldown_min = 50 - shapeshift_type = /mob/living/simple_animal/hostile/retaliate/bat + cooldown_time = 5 SECONDS + possible_shapes = list(/mob/living/simple_animal/hostile/retaliate/bat) diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm index bba496986004..d00c37605965 100644 --- a/code/modules/mob/living/carbon/human/status_procs.dm +++ b/code/modules/mob/living/carbon/human/status_procs.dm @@ -39,14 +39,3 @@ if(.) update_hair() -/mob/living/carbon/human/set_drugginess(amount) - ..() - if(!amount) - remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_HIGH) - -/mob/living/carbon/human/adjust_drugginess(amount) - ..() - if(druggy) - grant_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_HIGH) - else - remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_HIGH) diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 323dfed03833..c0bcb9f8e63a 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -310,7 +310,7 @@ There are several things that need to be remembered: remove_overlay(SUIT_STORE_LAYER) if(client && hud_used) - var/atom/movable/screen/inventory/inv = hud_used.inv_slots[SLOT_S_STORE] + var/atom/movable/screen/inventory/inv = hud_used.inv_slots[SLOT_SUIT_STORE] inv.update_icon() if(s_store) diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 0da3855f1697..fdb575db5865 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -12,7 +12,32 @@ return handcuffed if(SLOT_LEGCUFFED) return legcuffed - return null + + return ..() + +/mob/living/carbon/get_slot_by_item(obj/item/looking_for) + if(looking_for == back) + return ITEM_SLOT_BACK + + if(back && (looking_for in back)) + return ITEM_SLOT_BACKPACK + + if(looking_for == wear_mask) + return ITEM_SLOT_MASK + + if(looking_for == wear_neck) + return ITEM_SLOT_NECK + + if(looking_for == head) + return ITEM_SLOT_HEAD + + if(looking_for == handcuffed) + return ITEM_SLOT_HANDCUFFED + + if(looking_for == legcuffed) + return ITEM_SLOT_LEGCUFFED + + return ..() /mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = 1) for(var/slot in slots) @@ -63,7 +88,7 @@ wear_neck = I update_inv_neck(I) if(SLOT_HANDCUFFED) - handcuffed = I + set_handcuffed(I) update_handcuffed() if(SLOT_LEGCUFFED) legcuffed = I @@ -107,7 +132,7 @@ if(!QDELETED(src)) update_inv_neck(I) else if(I == handcuffed) - handcuffed = null + set_handcuffed(null) if(buckled && buckled.buckle_requires_restraints) buckled.unbuckle_mob(src) if(!QDELETED(src)) @@ -121,7 +146,7 @@ if(I == internal && (QDELETED(src) || QDELETED(I) || I.loc != src)) cutoff_internals() if(!QDELETED(src)) - update_action_buttons_icon(status_only = TRUE) + update_mob_action_buttons(UPDATE_BUTTON_STATUS) /// Returns TRUE if an air tank compatible helmet is equipped. /mob/living/carbon/proc/can_breathe_helmet() @@ -164,7 +189,7 @@ else internal = target_tank target_tank.after_internals_opened(src) - update_action_buttons_icon() + update_mob_action_buttons() return TRUE /** @@ -196,7 +221,7 @@ else internal = null target_tank.after_internals_closed(src) - update_action_buttons_icon() + update_mob_action_buttons() return TRUE /// Close the the currently open external (that's EX-ternal) air tank. Returns TREUE if successful. diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 90e1f9ec8c8d..3182fb560f61 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -48,9 +48,6 @@ stop_sound_channel(CHANNEL_HEARTBEAT) LoadComponent(/datum/component/rot/corpse) - //Updates the number of stored chemicals for powers - handle_changeling() - if(stat != DEAD) return 1 @@ -243,19 +240,19 @@ if(SA_partialpressure > NITROGEN_NARCOSIS_PRESSURE_HIGH) // Hallucinations if(prob(15)) to_chat(src, span_userdanger("You can't think straight!")) - confused = min(SA_partialpressure/10, confused + 12) - hallucination += 5 + adjust_confusion_up_to(SA_partialpressure/10, 12 SECONDS) + adjust_hallucinations(5 SECONDS) //yogs end //BZ (Facepunch port of their Agent B) if(breath.get_moles(/datum/gas/bz)) var/bz_partialpressure = (breath.get_moles(/datum/gas/bz)/breath.total_moles())*breath_pressure /* Yogs comment-out: Smoothed BZ hallucination levels if(bz_partialpressure > 1) - hallucination += 10 + adjust_hallucinations(10 SECONDS) else if(bz_partialpressure > 0.01) - hallucination += 5 + adjust_hallucinations(5 SECONDS) */ - hallucination += round(BZ_MAX_HALLUCINATION * (1 - NUM_E ** (-BZ_LAMBDA * bz_partialpressure))) // Yogs -- Better BZ hallucination values. Keep in mind that hallucination has to be an integer value, due to how it's handled in handle_hallucination() + adjust_hallucinations(round(BZ_MAX_HALLUCINATION * (1 - NUM_E ** (-BZ_LAMBDA * bz_partialpressure)))) // Yogs -- Better BZ hallucination values. Keep in mind that hallucination has to be an integer value, due to how it's handled in handle_hallucination() //TRITIUM if(breath.get_moles(/datum/gas/tritium)) @@ -340,7 +337,7 @@ var/stam_regen = FALSE if(stam_regen_start_time <= world.time && (has_dna() && !dna.check_mutation(ACTIVE_HULK))) stam_regen = TRUE - if(stam_paralyzed) + if(HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) . |= BODYPART_LIFE_UPDATE_HEALTH //make sure we remove the stamcrit for(var/I in bodyparts) var/obj/item/bodypart/BP = I @@ -367,18 +364,6 @@ if(W.processes) // meh W.handle_process() -//todo generalize this and move hud out -/mob/living/carbon/proc/handle_changeling() - if(mind && hud_used && hud_used.lingchemdisplay) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - changeling.regenerate() - hud_used.lingchemdisplay.invisibility = 0 - hud_used.lingchemdisplay.maptext = "
[round(changeling.chem_charges)]
" - else - hud_used.lingchemdisplay.invisibility = INVISIBILITY_ABSTRACT - - /mob/living/carbon/handle_mutations_and_radiation() if(dna && dna.temporary_mutations.len) for(var/mut in dna.temporary_mutations) @@ -448,154 +433,12 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put /mob/living/carbon/handle_status_effects() ..() - var/restingpwr = 1 + 4 * resting - - //Dizziness - if(dizziness) - var/client/C = client - var/pixel_x_diff = 0 - var/pixel_y_diff = 0 - var/temp - var/saved_dizz = dizziness - if(C) - var/oldsrc = src - var/amplitude = dizziness*(sin(dizziness * world.time) + 1) // This shit is annoying at high strength - src = null - spawn(0) - if(C) - temp = amplitude * sin(saved_dizz * world.time) - pixel_x_diff += temp - C.pixel_x += temp - temp = amplitude * cos(saved_dizz * world.time) - pixel_y_diff += temp - C.pixel_y += temp - sleep(0.3 SECONDS) - if(C) - temp = amplitude * sin(saved_dizz * world.time) - pixel_x_diff += temp - C.pixel_x += temp - temp = amplitude * cos(saved_dizz * world.time) - pixel_y_diff += temp - C.pixel_y += temp - sleep(0.3 SECONDS) - if(C) - C.pixel_x -= pixel_x_diff - C.pixel_y -= pixel_y_diff - src = oldsrc - dizziness = max(dizziness - restingpwr, 0) - - if(drowsyness) - drowsyness = max(drowsyness - restingpwr, 0) - blur_eyes(2) - if(prob(5)) - AdjustSleeping(20) - Unconscious(100) - - //Jitteriness - if(jitteriness) - do_jitter_animation(jitteriness) - jitteriness = max(jitteriness - restingpwr, 0) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "jittery", /datum/mood_event/jittery) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "jittery") - - if(stuttering) - stuttering = max(stuttering-1, 0) - - if(slurring) - slurring = max(slurring-1,0) - if(cultslurring) cultslurring = max(cultslurring-1, 0) if(silent) silent = max(silent-1, 0) - if(druggy) - adjust_drugginess(-1) - - if(hallucination) - handle_hallucinations() - - REMOVE_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk") - if(drunkenness) - drunkenness = max(drunkenness - (drunkenness * 0.04) - 0.01, 0) - if(drunkenness >= 6) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) - if(prob(25)) - slurring += 2 - jitteriness = max(jitteriness - 3, 0) - throw_alert("drunk", /atom/movable/screen/alert/drunk) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.12, FALSE) - adjustFireLoss(-0.06, FALSE) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") - clear_alert("drunk") - - if(drunkenness >= 11 && slurring < 5) - slurring += 1.2 - - if(mind && (mind.assigned_role == "Scientist" || mind.assigned_role == "Research Director")) - if(SSresearch.science_tech) - if(drunkenness >= 12.9 && drunkenness <= 13.8) - drunkenness = round(drunkenness, 0.01) - var/ballmer_percent = 0 - if(drunkenness == 13.35) // why run math if I dont have to - ballmer_percent = 1 - else - ballmer_percent = (-abs(drunkenness - 13.35) / 0.9) + 1 - if(prob(5)) - say(pick(GLOB.ballmer_good_msg), forced = "ballmer") - SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS * ballmer_percent)) - if(drunkenness > 26) // by this point you're into windows ME territory - if(prob(5)) - SSresearch.science_tech.remove_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS)) - say(pick(GLOB.ballmer_windows_me_msg), forced = "ballmer") - - if(drunkenness >= 41) - if(prob(25)) - confused += 2 - Dizzy(10) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) // effects stack with lower tiers - adjustBruteLoss(-0.3, FALSE) - adjustFireLoss(-0.15, FALSE) - ADD_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk") - - if(drunkenness >= 51) - if(prob(3)) - confused += 15 - vomit() // vomiting clears toxloss, consider this a blessing - Dizzy(25) - - if(drunkenness >= 61) - if(prob(50)) - blur_eyes(5) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.4, FALSE) - adjustFireLoss(-0.2, FALSE) - - if(drunkenness >= 71) - blur_eyes(5) - - if(drunkenness >= 81) - adjustToxLoss(1) - if(prob(5) && !stat) - to_chat(src, span_warning("Maybe you should lie down for a bit...")) - - if(drunkenness >= 91) - adjustToxLoss(1) - adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4) - if(prob(20) && !stat) - if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(z)) //QoL mainly - to_chat(src, span_warning("You're so tired... but you can't miss that shuttle...")) - else - to_chat(src, span_warning("Just a quick nap...")) - Sleeping(900) - - if(drunkenness >= 101) - adjustToxLoss(2) //Let's be honest you shouldn't be alive by now - //used in human and monkey handle_environment() /mob/living/carbon/proc/natural_bodytemperature_stabilization() var/body_temperature_difference = BODYTEMP_NORMAL - bodytemperature diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm index 44cd2ddc6619..fc4e2e24a953 100644 --- a/code/modules/mob/living/carbon/status_procs.dm +++ b/code/modules/mob/living/carbon/status_procs.dm @@ -4,38 +4,20 @@ /mob/living/carbon/IsParalyzed(include_stamcrit = TRUE) - return ..() || (include_stamcrit && stam_paralyzed) + return ..() || (include_stamcrit && HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) /mob/living/carbon/proc/enter_stamcrit() if(!(status_flags & CANKNOCKDOWN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)) return + if(HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) //Already in stamcrit + return if(absorb_stun(0)) //continuous effect, so we don't want it to increment the stuns absorbed. return - if(!IsParalyzed()) - to_chat(src, span_notice("You're too exhausted to keep going...")) - SEND_SIGNAL(src, COMSIG_CARBON_STATUS_STAMCRIT) - stam_paralyzed = TRUE - update_mobility() - -/mob/living/carbon/adjust_drugginess(amount) - druggy = max(druggy+amount, 0) - if(druggy) - overlay_fullscreen("high", /atom/movable/screen/fullscreen/high) - throw_alert("high", /atom/movable/screen/alert/high) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "high", /datum/mood_event/high) - else - clear_fullscreen("high") - clear_alert("high") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "high") + to_chat(src, span_notice("You're too exhausted to keep going...")) + add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED), STAMINA) + if(getStaminaLoss() < 120) // Puts you a little further into the initial stamcrit, makes stamcrit harder to outright counter with chems. + adjustStaminaLoss(30, FALSE) -/mob/living/carbon/set_drugginess(amount) - druggy = max(amount, 0) - if(druggy) - overlay_fullscreen("high", /atom/movable/screen/fullscreen/high) - throw_alert("high", /atom/movable/screen/alert/high) - else - clear_fullscreen("high") - clear_alert("high") /mob/living/carbon/adjust_disgust(amount) disgust = clamp(disgust+amount, 0, DISGUST_LEVEL_MAXEDOUT) diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index b27c8d5512a9..79935da5086a 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -98,18 +98,8 @@ if(EFFECT_IRRADIATE) if(!HAS_TRAIT(src, TRAIT_RADIMMUNE)&& !(status_flags & GODMODE)) radiation += max(effect * hit_percent, 0) - if(EFFECT_SLUR) - slurring = max(slurring,(effect * hit_percent)) - if(EFFECT_STUTTER) - if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) // stun is usually associated with stutter - stuttering = max(stuttering,(effect * hit_percent)) if(EFFECT_EYE_BLUR) blur_eyes(effect * hit_percent) - if(EFFECT_DROWSY) - drowsyness = max(drowsyness,(effect * hit_percent)) - if(EFFECT_JITTER) - if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) - jitteriness = max(jitteriness,(effect * hit_percent)) if(EFFECT_PARALYZE) Paralyze(effect * hit_percent) if(EFFECT_IMMOBILIZE) @@ -117,33 +107,45 @@ return TRUE -/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = 0, stamina = 0, jitter = 0, paralyze = 0, immobilize = 0) +/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, \ + blocked = 0, stamina = 0, jitter = 0, paralyze = 0, immobilize = 0) if(blocked >= 100) return FALSE if(stun) apply_effect(stun, EFFECT_STUN, blocked) + if(knockdown) apply_effect(knockdown, EFFECT_KNOCKDOWN, blocked) + if(unconscious) apply_effect(unconscious, EFFECT_UNCONSCIOUS, blocked) + if(paralyze) apply_effect(paralyze, EFFECT_PARALYZE, blocked) + if(immobilize) apply_effect(immobilize, EFFECT_IMMOBILIZE, blocked) + if(irradiate) apply_effect(irradiate, EFFECT_IRRADIATE, src.getarmor(null, RAD)) - if(slur) - apply_effect(slur, EFFECT_SLUR, blocked) - if(stutter) - apply_effect(stutter, EFFECT_STUTTER, blocked) + if(eyeblur) apply_effect(eyeblur, EFFECT_EYE_BLUR, blocked) - if(drowsy) - apply_effect(drowsy, EFFECT_DROWSY, blocked) + if(stamina) apply_damage(stamina, STAMINA, null, blocked) - if(jitter) - apply_effect(jitter, EFFECT_JITTER, blocked) + if(jitter && (status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) + adjust_jitter(jitter) + + if(slur) + adjust_slurring(slur) + + if(stutter) + adjust_stutter(stutter) + + if(drowsy) + adjust_drowsiness(drowsy) + return TRUE @@ -199,7 +201,7 @@ /mob/living/proc/getFireLoss() return fireloss -/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status) if(!forced && (status_flags & GODMODE)) return FALSE fireloss = clamp((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 8a04cd85f24c..20b2af928b14 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -49,7 +49,7 @@ GLOBAL_VAR_INIT(permadeath, FALSE) /mob/living/death(gibbed) - stat = DEAD + set_stat(DEAD) unset_machine() timeofdeath = world.time tod = station_time_timestamp() @@ -69,7 +69,7 @@ GLOBAL_VAR_INIT(permadeath, FALSE) blind_eyes(1) reset_perspective(null) reload_fullscreen() - update_action_buttons_icon() + update_mob_action_buttons() update_damage_hud() update_health_hud() update_mobility() @@ -79,6 +79,7 @@ GLOBAL_VAR_INIT(permadeath, FALSE) addtimer(CALLBACK(src, PROC_REF(med_hud_set_status)), (DEFIB_TIME_LIMIT) + 1) stop_pulling() + SEND_SIGNAL(src, COMSIG_LIVING_DEATH, gibbed) . = ..() if (client) diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 48eba8893e3c..d6a0b67b1711 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -14,7 +14,7 @@ key_third_person = "bows" message = "bows." message_param = "bows to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/burp key = "burp" @@ -33,7 +33,7 @@ key = "cross" key_third_person = "crosses" message = "crosses their arms." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/chuckle key = "chuckle" @@ -68,7 +68,7 @@ key = "dance" key_third_person = "dances" message = "dances around happily." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/deathgasp key = "deathgasp" @@ -129,7 +129,7 @@ key = "flap" key_third_person = "flaps" message = "flaps their wings." - restraint_check = TRUE + hands_use_check = TRUE var/wing_time = 20 /datum/emote/living/flap/run_emote(mob/user, params, type_override, intentional) @@ -149,7 +149,7 @@ key = "aflap" key_third_person = "aflaps" message = "flaps their wings ANGRILY!" - restraint_check = TRUE + hands_use_check = TRUE wing_time = 10 /datum/emote/living/frown @@ -205,7 +205,7 @@ key = "handsup" key_third_person = "raiseshands" message = span_surrender("raises their hands in the air, they surrender!") - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/handsup/run_emote(mob/living/user, params, type_override, intentional) . = ..() @@ -220,7 +220,7 @@ key = "jump" key_third_person = "jumps" message = "jumps!" - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/kiss key = "kiss" @@ -271,7 +271,7 @@ key_third_person = "points" message = "points." message_param = "points at %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/point/run_emote(mob/user, params, type_override, intentional) message_param = initial(message_param) // reset @@ -533,7 +533,7 @@ /datum/emote/living/circle key = "circle" key_third_person = "circles" - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/circle/run_emote(mob/user, params, type_override, intentional) . = ..() @@ -547,7 +547,7 @@ /datum/emote/living/slap key = "slap" key_third_person = "slaps" - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/slap/run_emote(mob/user, params, type_override, intentional) . = ..() @@ -564,11 +564,11 @@ key_third_person = "thumbs" message = "gives a thumbs up." message_param = "gives a thumbs up to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/clueless key = "clueless" key_third_person = "cluelesses" message = "looks clueless." message_param = "looks cluelessly at %t" - stat_allowed = SOFT_CRIT + stat_allowed = SOFT_CRIT \ No newline at end of file diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm new file mode 100644 index 000000000000..a8c33f7367a4 --- /dev/null +++ b/code/modules/mob/living/init_signals.dm @@ -0,0 +1,251 @@ +/// Called on [/mob/living/Initialize(mapload)], for the mob to register to relevant signals. +/mob/living/proc/register_init_signals() + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_KNOCKEDOUT), PROC_REF(on_knockedout_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_KNOCKEDOUT), PROC_REF(on_knockedout_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_DEATHCOMA), PROC_REF(on_deathcoma_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_DEATHCOMA), PROC_REF(on_deathcoma_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), PROC_REF(on_immobilized_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_IMMOBILIZED), PROC_REF(on_immobilized_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_FLOORED), PROC_REF(on_floored_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_FLOORED), PROC_REF(on_floored_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_FORCED_STANDING), PROC_REF(on_forced_standing_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_FORCED_STANDING), PROC_REF(on_forced_standing_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(on_handsblocked_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(on_handsblocked_trait_loss)) + +// RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_UI_BLOCKED), PROC_REF(on_ui_blocked_trait_gain)) +// RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_UI_BLOCKED), PROC_REF(on_ui_blocked_trait_loss)) + +// RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_PULL_BLOCKED), PROC_REF(on_pull_blocked_trait_gain)) +// RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_PULL_BLOCKED), PROC_REF(on_pull_blocked_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), PROC_REF(on_incapacitated_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_INCAPACITATED), PROC_REF(on_incapacitated_trait_loss)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_RESTRAINED), PROC_REF(on_restrained_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_RESTRAINED), PROC_REF(on_restrained_trait_loss)) + + RegisterSignals(src, list( + SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION), + SIGNAL_REMOVETRAIT(TRAIT_CRITICAL_CONDITION), + + SIGNAL_ADDTRAIT(TRAIT_NODEATH), + SIGNAL_REMOVETRAIT(TRAIT_NODEATH), + ), PROC_REF(update_succumb_action)) + +// RegisterSignal(src, COMSIG_MOVETYPE_FLAG_ENABLED, PROC_REF(on_movement_type_flag_enabled)) +// RegisterSignal(src, COMSIG_MOVETYPE_FLAG_DISABLED, PROC_REF(on_movement_type_flag_disabled)) + + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_SKITTISH), PROC_REF(on_skittish_trait_gain)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_SKITTISH), PROC_REF(on_skittish_trait_loss)) + +// RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_NEGATES_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_NEGATES_GRAVITY)), PROC_REF(on_negate_gravity)) +// RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_IGNORING_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_IGNORING_GRAVITY)), PROC_REF(on_ignore_gravity)) +// RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_FORCED_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_FORCED_GRAVITY)), PROC_REF(on_force_gravity)) + // We hook for forced grav changes from our turf and ourselves +// var/static/list/loc_connections = list( +// SIGNAL_ADDTRAIT(TRAIT_FORCED_GRAVITY) = PROC_REF(on_loc_force_gravity), +// SIGNAL_REMOVETRAIT(TRAIT_FORCED_GRAVITY) = PROC_REF(on_loc_force_gravity), +// ) +// AddElement(/datum/element/connect_loc, loc_connections) + +/// Called when [TRAIT_KNOCKEDOUT] is added to the mob. +/mob/living/proc/on_knockedout_trait_gain(datum/source) + SIGNAL_HANDLER + if(stat < UNCONSCIOUS) + set_stat(UNCONSCIOUS) + +/// Called when [TRAIT_KNOCKEDOUT] is removed from the mob. +/mob/living/proc/on_knockedout_trait_loss(datum/source) + SIGNAL_HANDLER + if(stat <= UNCONSCIOUS) + update_stat() + + +/// Called when [TRAIT_DEATHCOMA] is added to the mob. +/mob/living/proc/on_deathcoma_trait_gain(datum/source) + SIGNAL_HANDLER + ADD_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_DEATHCOMA) + +/// Called when [TRAIT_DEATHCOMA] is removed from the mob. +/mob/living/proc/on_deathcoma_trait_loss(datum/source) + SIGNAL_HANDLER + REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_DEATHCOMA) + + +/// Called when [TRAIT_IMMOBILIZED] is added to the mob. +/mob/living/proc/on_immobilized_trait_gain(datum/source) + SIGNAL_HANDLER + mobility_flags &= ~MOBILITY_MOVE +// if(living_flags & MOVES_ON_ITS_OWN) +// SSmove_manager.stop_looping(src) //stop mid walk //This is also really dumb + +/// Called when [TRAIT_IMMOBILIZED] is removed from the mob. +/mob/living/proc/on_immobilized_trait_loss(datum/source) + SIGNAL_HANDLER + mobility_flags |= MOBILITY_MOVE + + +/// Called when [TRAIT_FLOORED] is added to the mob. +/mob/living/proc/on_floored_trait_gain(datum/source) +// SIGNAL_HANDLER + if(buckled && buckled.buckle_lying != NO_BUCKLE_LYING) + return // Handled by the buckle. + if(HAS_TRAIT(src, TRAIT_FORCED_STANDING)) + return // Don't go horizontal if mob has forced standing trait. + mobility_flags &= ~MOBILITY_STAND + on_floored_start() + + +/// Called when [TRAIT_FLOORED] is removed from the mob. +/mob/living/proc/on_floored_trait_loss(datum/source) +// SIGNAL_HANDLER + mobility_flags |= MOBILITY_STAND + on_floored_end() + +/// Called when [TRAIT_FORCED_STANDING] is added to the mob. +/mob/living/proc/on_forced_standing_trait_gain(datum/source) +// SIGNAL_HANDLER + +// set_body_position(STANDING_UP) +// set_lying_angle(0) + set_resting(FALSE) + +/// Called when [TRAIT_FORCED_STANDING] is removed from the mob. +/mob/living/proc/on_forced_standing_trait_loss(datum/source) +// SIGNAL_HANDLER + + if(HAS_TRAIT(src, TRAIT_FLOORED)) +// on_fall() +// set_lying_down() +// else if(resting) +// set_lying_down() + set_resting(TRUE) + else + set_resting(FALSE) + +/// Called when [TRAIT_HANDS_BLOCKED] is added to the mob. +/mob/living/proc/on_handsblocked_trait_gain(datum/source) +// SIGNAL_HANDLER + mobility_flags &= ~(MOBILITY_USE | MOBILITY_PICKUP | MOBILITY_STORAGE) + on_handsblocked_start() + +/// Called when [TRAIT_HANDS_BLOCKED] is removed from the mob. +/mob/living/proc/on_handsblocked_trait_loss(datum/source) +// SIGNAL_HANDLER + mobility_flags |= (MOBILITY_USE | MOBILITY_PICKUP | MOBILITY_STORAGE) + on_handsblocked_end() + + +/// Called when [TRAIT_UI_BLOCKED] is added to the mob. +/mob/living/proc/on_ui_blocked_trait_gain(datum/source) +// SIGNAL_HANDLER + mobility_flags &= ~(MOBILITY_UI) + unset_machine() + update_mob_action_buttons() + +/// Called when [TRAIT_UI_BLOCKED] is removed from the mob. +/mob/living/proc/on_ui_blocked_trait_loss(datum/source) + SIGNAL_HANDLER + mobility_flags |= MOBILITY_UI + update_mob_action_buttons() + + +/// Called when [TRAIT_PULL_BLOCKED] is added to the mob. +/mob/living/proc/on_pull_blocked_trait_gain(datum/source) + SIGNAL_HANDLER + mobility_flags &= ~(MOBILITY_PULL) + if(pulling) + stop_pulling() + +/// Called when [TRAIT_PULL_BLOCKED] is removed from the mob. +/mob/living/proc/on_pull_blocked_trait_loss(datum/source) + SIGNAL_HANDLER + mobility_flags |= MOBILITY_PULL + + +/// Called when [TRAIT_INCAPACITATED] is added to the mob. +/mob/living/proc/on_incapacitated_trait_gain(datum/source) + SIGNAL_HANDLER + add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_INCAPACITATED) +// update_appearance() + +/// Called when [TRAIT_INCAPACITATED] is removed from the mob. +/mob/living/proc/on_incapacitated_trait_loss(datum/source) + SIGNAL_HANDLER + remove_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_INCAPACITATED) +// update_appearance() + + +/// Called when [TRAIT_RESTRAINED] is added to the mob. +/mob/living/proc/on_restrained_trait_gain(datum/source) +// SIGNAL_HANDLER + ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, TRAIT_RESTRAINED) + +/// Called when [TRAIT_RESTRAINED] is removed from the mob. +/mob/living/proc/on_restrained_trait_loss(datum/source) +// SIGNAL_HANDLER + REMOVE_TRAIT(src, TRAIT_HANDS_BLOCKED, TRAIT_RESTRAINED) + + +/** + * Called when traits that alter succumbing are added/removed. + * + * Will show or hide the succumb alert prompt. + */ +/mob/living/proc/update_succumb_action() +// SIGNAL_HANDLER +// if (CAN_SUCCUMB(src) || HAS_TRAIT(src, TRAIT_SUCCUMB_OVERRIDE)) +// throw_alert(ALERT_SUCCUMB, /atom/movable/screen/alert/succumb) +// else +// clear_alert(ALERT_SUCCUMB) + +///From [element/movetype_handler/on_movement_type_trait_gain()] +/mob/living/proc/on_movement_type_flag_enabled(datum/source, flag, old_movement_type) +// SIGNAL_HANDLER +// update_movespeed(FALSE) + +///From [element/movetype_handler/on_movement_type_trait_loss()] +/mob/living/proc/on_movement_type_flag_disabled(datum/source, flag, old_movement_type) +// SIGNAL_HANDLER +// update_movespeed(FALSE) + + +/// Called when [TRAIT_SKITTISH] is added to the mob. +/mob/living/proc/on_skittish_trait_gain(datum/source) +// SIGNAL_HANDLER +// AddElement(/datum/element/skittish) + +/// Called when [TRAIT_SKITTISH] is removed from the mob. +/mob/living/proc/on_skittish_trait_loss(datum/source) +// SIGNAL_HANDLER +// RemoveElement(/datum/element/skittish) + +/// Called when [TRAIT_NEGATES_GRAVITY] is gained or lost +/mob/living/proc/on_negate_gravity(datum/source) +// SIGNAL_HANDLER +// if(!isgroundlessturf(loc)) +// if(HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)) +// ADD_TRAIT(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) +// else +// REMOVE_TRAIT(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) + +/// Called when [TRAIT_IGNORING_GRAVITY] is gained or lost +/mob/living/proc/on_ignore_gravity(datum/source) +// SIGNAL_HANDLER +// refresh_gravity() + +/// Called when [TRAIT_FORCED_GRAVITY] is gained or lost +/mob/living/proc/on_force_gravity(datum/source) +// SIGNAL_HANDLER +// refresh_gravity() + +/// Called when our loc's [TRAIT_FORCED_GRAVITY] is gained or lost +/mob/living/proc/on_loc_force_gravity(datum/source) +// SIGNAL_HANDLER +// refresh_gravity() diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 3847050be270..c6edab34fa4f 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -114,19 +114,17 @@ if(fire_stacks > 0) adjust_fire_stacks(-0.1) //the fire is slowly consumed else - ExtinguishMob() - return TRUE //mob was put out, on_fire = FALSE via ExtinguishMob(), no need to update everything down the chain. + extinguish_mob() + return TRUE //mob was put out, on_fire = FALSE via extinguish_mob(), no need to update everything down the chain. var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment if(G.get_moles(/datum/gas/oxygen) < 1) - ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire + extinguish_mob() //If there's no oxygen in the tile we're on, put out the fire return TRUE var/turf/location = get_turf(src) location.hotspot_expose(700, 50, 1) //this updates all special effects: knockdown, druggy, stuttering, etc.. /mob/living/proc/handle_status_effects() - if(confused) - confused = max(0, confused - 1) /mob/living/proc/handle_traits() //Eyes diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 0e6eb09460a8..be0e733053c3 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1,12 +1,12 @@ -/mob/living/Initialize() +/mob/living/Initialize(mapload) . = ..() + register_init_signals() if(unique_name) - name = "[name] ([rand(1, 1000)])" - real_name = name + set_name() var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medhud.add_to_hud(src) + medhud.add_atom_to_hud(src) 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) faction += "[REF(src)]" GLOB.mob_living_list += src initialize_footstep() @@ -32,9 +32,6 @@ else effect.be_replaced() - if(ranged_ability) - ranged_ability.remove_ranged_ability(src) - if(buckled) buckled.unbuckle_mob(src,force=1) @@ -264,7 +261,7 @@ AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. pulling = AM - AM.pulledby = src + AM.set_pulledby(src) if(!supress_message) var/sound_to_play = 'sound/weapons/thudswoosh.ogg' if(ishuman(src)) @@ -389,9 +386,17 @@ to_chat(src, span_notice("You have given up life and succumbed to death.")) death() -/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE, ignore_stasis = FALSE) - if(stat || IsUnconscious() || IsStun() || IsParalyzed() || (check_immobilized && IsImmobilized()) || (!ignore_restraints && restrained(ignore_grab)) || (!ignore_stasis && IS_IN_STASIS(src))) +/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_stasis = FALSE) + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + return TRUE + + if(!ignore_restraints && restrained(ignore_grab)) + return TRUE + if(!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE) return TRUE + if(!ignore_stasis && IS_IN_STASIS(src)) + return TRUE + return FALSE /mob/living/canUseStorage() if (get_num_arms() <= 0) @@ -495,6 +500,11 @@ /mob/living/is_drawable(mob/user, allowmobs = TRUE) return (allowmobs && reagents && can_inject(user)) +///Sets the current mob's health value. Do not call directly if you don't know what you are doing, use the damage procs, instead. +/mob/living/proc/set_health(new_value) + . = health + health = new_value + /mob/living/proc/updatehealth() if(status_flags & GODMODE) return @@ -552,7 +562,7 @@ remove_from_dead_mob_list() add_to_alive_mob_list() set_suicide(FALSE) - stat = UNCONSCIOUS //the mob starts unconscious, + set_stat(UNCONSCIOUS) //the mob starts unconscious, blind_eyes(1) losebreath = 0 //losebreath stacks were persisting beyond death, immediately killing again after revival until they ran out natureally from time updatehealth() //then we check if the mob should wake up. @@ -562,10 +572,12 @@ reload_fullscreen() revive_guardian() . = 1 - if(mind) - for(var/S in mind.spell_list) - var/obj/effect/proc_holder/spell/spell = S - spell.updateButtonIcon() + if(IS_BLOODSUCKER(src)) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = src.mind.has_antag_datum(/datum/antagonist/bloodsucker) + bloodsuckerdatum.heal_vampire_organs() + + // The signal is called after everything else so components can properly check the updated values + SEND_SIGNAL(src, COMSIG_LIVING_REVIVE, full_heal, admin_revive) /mob/living/proc/remove_CC(should_update_mobility = TRUE) SetStun(0, FALSE) @@ -591,22 +603,14 @@ bodytemperature = BODYTEMP_NORMAL set_blindness(0) set_blurriness(0) - set_dizziness(0) cure_nearsighted() cure_blind() cure_husk() - hallucination = 0 heal_overall_damage(INFINITY, INFINITY, INFINITY, null, TRUE) //heal brute and burn dmg on both organic and robotic limbs, and update health right away. - ExtinguishMob() + extinguish_mob() losebreath = 0 fire_stacks = 0 - confused = 0 - dizziness = 0 - drowsyness = 0 - stuttering = 0 - slurring = 0 - jitteriness = 0 if(HAS_TRAIT_FROM(src, TRAIT_BADDNA, CHANGELING_DRAIN)) REMOVE_TRAIT(src, TRAIT_BADDNA, CHANGELING_DRAIN) var/datum/component/mood/mood = GetComponent(/datum/component/mood) @@ -616,6 +620,7 @@ stop_sound_channel(CHANNEL_HEARTBEAT) if(admin_revive) cure_fakedeath() + SEND_SIGNAL(src, COMSIG_LIVING_POST_FULLY_HEAL) //proc called by revive(), to check if we can actually ressuscitate the mob (we don't want to revive him and have him instantly die again) /mob/living/proc/can_be_revived() @@ -744,7 +749,11 @@ ..(pressure_difference, direction, pressure_resistance_prob_delta) /mob/living/can_resist() - return !((next_move > world.time) || incapacitated(ignore_restraints = TRUE, ignore_stasis = TRUE)) + if(next_move > world.time) + return FALSE + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + return FALSE + return TRUE /mob/living/verb/resist() set name = "Resist" @@ -913,16 +922,6 @@ else if(!src.mob_negates_gravity()) step_towards(src,S) -/mob/living/proc/do_jitter_animation(jitteriness) - var/amplitude = min(4, (jitteriness/100) + 1) - var/pixel_x_diff = rand(-amplitude, amplitude) - var/pixel_y_diff = rand(-amplitude/3, amplitude/3) - var/final_pixel_x = get_standard_pixel_x_offset(lying) - var/final_pixel_y = get_standard_pixel_y_offset(lying) - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff , time = 0.2 SECONDS, loop = 6) - animate(pixel_x = final_pixel_x , pixel_y = final_pixel_y , time = 0.2 SECONDS) - setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure to restart it in next life(). - /mob/living/proc/get_temperature(datum/gas_mixture/environment) var/loc_temp = environment ? environment.return_temperature() : T0C if(isobj(loc)) @@ -1070,7 +1069,7 @@ if(!magic && !holy && !tinfoil) return var/list/protection_sources = list() - if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, src, magic, holy, tinfoil, chargecost, self, protection_sources) & COMPONENT_BLOCK_MAGIC) + if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, src, magic, holy, tinfoil, chargecost, self, protection_sources) & COMPONENT_MAGIC_BLOCKED) if(protection_sources.len) return pick(protection_sources) else @@ -1139,7 +1138,7 @@ return BODYTEMP_NORMAL + get_body_temp_normal_change() //Mobs on Fire -/mob/living/proc/IgniteMob() +/mob/living/proc/ignite_mob() if(fire_stacks > 0 && !on_fire) on_fire = 1 src.visible_message(span_warning("[src] catches fire!"), \ @@ -1151,7 +1150,7 @@ return TRUE return FALSE -/mob/living/proc/ExtinguishMob() +/mob/living/proc/extinguish_mob() if(on_fire) on_fire = 0 fire_stacks = 0 @@ -1168,7 +1167,7 @@ else fire_stacks = clamp(fire_stacks + add_fire_stacks, -20, 20) if(on_fire && fire_stacks <= 0) - ExtinguishMob() + extinguish_mob() //Share fire evenly between the two mobs //Called in MobBump() and Crossed() @@ -1184,13 +1183,13 @@ else // If they were not fire_stacks /= 2 L.adjust_fire_stacks(fire_stacks) - if(L.IgniteMob()) // Ignite them + if(L.ignite_mob()) // Ignite them log_game("[key_name(src)] bumped into [key_name(L)] and set them on fire") else if(L.on_fire) // If they were on fire and we were not L.fire_stacks /= 2 adjust_fire_stacks(L.fire_stacks) - IgniteMob() // Ignite us + ignite_mob() // Ignite us //Mobs on Fire end @@ -1282,24 +1281,6 @@ if(!(mobility_flags & MOBILITY_USE)) drop_all_held_items() -/mob/living/proc/AddAbility(obj/effect/proc_holder/A) - abilities.Add(A) - A.on_gain(src) - if(A.has_action) - A.action.Grant(src) - -/mob/living/proc/RemoveAbility(obj/effect/proc_holder/A) - abilities.Remove(A) - A.on_lose(src) - if(A.action) - A.action.Remove(src) - -/mob/living/proc/add_abilities_to_panel() - var/list/L = list() - for(var/obj/effect/proc_holder/A in abilities) - L[++L.len] = list("[A.panel]",A.get_panel_text(),A.name,"[REF(A)]") - return L - /mob/living/lingcheck() if(mind) var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) @@ -1361,6 +1342,10 @@ L.visible_message(span_warning("[L] scoops up [src]!")) L.put_in_hands(holder) +/mob/living/proc/set_name() + name = "[name] ([rand(1, 1000)])" + real_name = name + /mob/living/proc/mob_try_pickup(mob/living/user) if(!ishuman(user)) return @@ -1395,11 +1380,6 @@ clear_fullscreen("remote_view", 0) update_pipe_vision() -/mob/living/update_mouse_pointer() - ..() - if (client && ranged_ability && ranged_ability.ranged_mousepointer) - client.mouse_pointer_icon = ranged_ability.ranged_mousepointer - /mob/living/vv_edit_var(var_name, var_value) switch(var_name) if ("maxHealth") @@ -1470,3 +1450,99 @@ /// Only defined for carbons who can wear masks and helmets, we just assume other mobs have visible faces /mob/living/proc/is_face_visible() return isturf(loc) // Yogs -- forbids making eye contact with things hidden within objects + +/mob/living/carbon/proc/set_handcuffed(new_value) + if(handcuffed == new_value) + return FALSE + . = handcuffed + handcuffed = new_value + if(.) + if(!handcuffed) + REMOVE_TRAIT(src, TRAIT_RESTRAINED, HANDCUFFED_TRAIT) + else if(handcuffed) + ADD_TRAIT(src, TRAIT_RESTRAINED, HANDCUFFED_TRAIT) + +/mob/living/set_pulledby(new_pulledby) + . = ..() + if(. == FALSE) //null is a valid value here, we only want to return if FALSE is explicitly passed. + return + if(pulledby) + if(!. && stat == SOFT_CRIT) + ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) + else if(. && stat == SOFT_CRIT) + REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) + +/mob/living/set_stat(new_stat) + . = ..() + if(isnull(.)) + return + + switch(.) //Previous stat. + if(CONSCIOUS) + if(stat >= UNCONSCIOUS) + ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) + add_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_INCAPACITATED, TRAIT_FLOORED), STAT_TRAIT) + if(SOFT_CRIT) + if(stat >= UNCONSCIOUS) + ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) //adding trait sources should come before removing to avoid unnecessary updates + if(pulledby) + REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) + if(UNCONSCIOUS) +// if(stat != HARD_CRIT) + cure_blind(UNCONSCIOUS_TRAIT) +// if(HARD_CRIT) +// if(stat != UNCONSCIOUS) +// cure_blind(UNCONSCIOUS_TRAIT) + if(DEAD) + remove_from_dead_mob_list() + add_to_alive_mob_list() + switch(stat) //Current stat. + if(CONSCIOUS) + if(. >= UNCONSCIOUS) + REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) + remove_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_CRITICAL_CONDITION), STAT_TRAIT) + if(SOFT_CRIT) + if(pulledby) + ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) //adding trait sources should come before removing to avoid unnecessary updates + if(. >= UNCONSCIOUS) + REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) + ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + if(UNCONSCIOUS) +// if(. != HARD_CRIT) + // become_blind(UNCONSCIOUS_TRAIT) + if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) + ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + else + REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) +// if(HARD_CRIT) +// if(. != UNCONSCIOUS) +// become_blind(UNCONSCIOUS_TRAIT) +// ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + if(DEAD) + REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + remove_from_alive_mob_list() + add_to_dead_mob_list() + +/// Proc to append behavior to the condition of being handsblocked. Called when the condition starts. +/mob/living/proc/on_handsblocked_start() + drop_all_held_items() + add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_HANDS_BLOCKED) + + +/// Proc to append behavior to the condition of being handsblocked. Called when the condition ends. +/mob/living/proc/on_handsblocked_end() + remove_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_HANDS_BLOCKED) + +/// Proc to append behavior to the condition of being floored. Called when the condition starts. +/mob/living/proc/on_floored_start() +// if(body_position == STANDING_UP) //force them on the ground +// set_lying_angle(pick(90, 270)) +// set_body_position(LYING_DOWN) +// on_fall() + set_resting(TRUE) + +/// Proc to append behavior to the condition of being floored. Called when the condition ends. +/mob/living/proc/on_floored_end() +// if(!resting) +// get_up() + set_resting(FALSE) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index f408d9e0e2c8..5a676d489d95 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -155,7 +155,7 @@ /mob/living/fire_act() last_damage = "fire" adjust_fire_stacks(3) - IgniteMob() + ignite_mob() /mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) if(user == src || anchored || !isturf(user.loc)) @@ -175,63 +175,65 @@ //proc to upgrade a simple pull into a more aggressive grab. /mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) - if(user.grab_state < GRAB_KILL) - user.changeNext_move(CLICK_CD_GRABBING) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - playsound(src.loc, sound_to_play, 50, 1, -1) - - if(user.grab_state) //only the first upgrade is instantaneous - var/old_grab_state = user.grab_state - var/grab_upgrade_time = instant ? 0 : 30 - visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ - span_userdanger("[user] starts to tighten [user.p_their()] grip on you!")) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "attempted to neck grab", addition="neck grab") - if(GRAB_NECK) - log_combat(user, src, "attempted to strangle", addition="kill grab") - if(!do_mob(user, src, grab_upgrade_time)) - return FALSE - if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) - return FALSE - if(user.a_intent != INTENT_GRAB) - to_chat(user, "You must be on grab intent to upgrade your grab further!") - return FALSE - user.grab_state++ + if(user.grab_state >= user.max_grab) + return + user.changeNext_move(CLICK_CD_GRABBING) + var/sound_to_play = 'sound/weapons/thudswoosh.ogg' + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species.grab_sound) + sound_to_play = H.dna.species.grab_sound + playsound(src.loc, sound_to_play, 50, TRUE, -1) + + if(user.grab_state) //only the first upgrade is instantaneous + var/old_grab_state = user.grab_state + var/grab_upgrade_time = instant ? 0 : 30 + visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ + span_userdanger("[user] starts to tighten [user.p_their()] grip on you!")) switch(user.grab_state) if(GRAB_AGGRESSIVE) - var/add_log = "" - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - visible_message(span_danger("[user] has firmly gripped [src]!"), - span_danger("[user] has firmly gripped you!")) - add_log = " (pacifist)" - else - visible_message(span_danger("[user] has grabbed [src] aggressively!"), \ - span_userdanger("[user] has grabbed you aggressively!")) - drop_all_held_items() - stop_pulling() - log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + log_combat(user, src, "attempted to neck grab", addition="neck grab") if(GRAB_NECK) - log_combat(user, src, "grabbed", addition="neck grab") - visible_message(span_danger("[user] has grabbed [src] by the neck!"),\ - span_userdanger("[user] has grabbed you by the neck!")) - update_mobility() //we fall down - if(!buckled && !density) - Move(user.loc) - if(GRAB_KILL) - last_damage = "grip marks on the neck" - log_combat(user, src, "strangled", addition="kill grab") - visible_message(span_danger("[user] is strangling [src]!"), \ - span_userdanger("[user] is strangling you!")) - update_mobility() //we fall down - if(!buckled && !density) - Move(user.loc) - user.set_pull_offsets(src, grab_state) - return TRUE + log_combat(user, src, "attempted to strangle", addition="kill grab") + if(!do_mob(user, src, grab_upgrade_time)) + return FALSE + if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) + return FALSE + if(user.a_intent != INTENT_GRAB) + to_chat(user, span_notice("You must be on grab intent to upgrade your grab further!")) + return FALSE + user.setGrabState(user.grab_state + 1) + switch(user.grab_state) + if(GRAB_AGGRESSIVE) + var/add_log = "" + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + visible_message(span_danger("[user] has firmly gripped [src]!"), + span_danger("[user] has firmly gripped you!")) + add_log = " (pacifist)" + else + visible_message(span_danger("[user] has grabbed [src] aggressively!"), \ + span_userdanger("[user] has grabbed you aggressively!")) + drop_all_held_items() + stop_pulling() + log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + if(GRAB_NECK) + log_combat(user, src, "grabbed", addition="neck grab") + visible_message(span_danger("[user] grabs [src] by the neck!"),\ + span_userdanger("[user] grabs you by the neck!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You grab [src] by the neck!")) + update_mobility() //we fall down + if(!buckled && !density) + Move(user.loc) + if(GRAB_KILL) + last_damage = "grip marks on the neck" + log_combat(user, src, "strangled", addition="kill grab") + visible_message(span_danger("[user] is strangling [src]!"), \ + span_userdanger("[user] is strangling you!")) + update_mobility() //we fall down + if(!buckled && !density) + Move(user.loc) + user.set_pull_offsets(src, grab_state) + return TRUE /mob/living/attack_slime(mob/living/simple_animal/slime/M) @@ -428,7 +430,7 @@ if(stat != DEAD && !is_servant_of_ratvar(src)) to_chat(src, span_userdanger("A blinding light boils you alive! Run!")) adjust_fire_stacks(20) - IgniteMob() + ignite_mob() return FALSE diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 5ca6e212cc6b..ec5c79be96c7 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -32,10 +32,6 @@ var/lying = 0 //number of degrees. DO NOT USE THIS IN CHECKS. CHECK FOR MOBILITY FLAGS INSTEAD!! var/lying_prev = 0 //last value of lying on update_mobility - var/confused = 0 //Makes the mob move in random directions. - - var/hallucination = 0 //Directly affects how long a mob will hallucinate for - var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. var/timeofdeath = 0 @@ -95,12 +91,10 @@ var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added var/blood_volume = 0 //how much blood the mob has - var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override var/see_override = 0 //0 for no override, sets see_invisible = see_override in silicon & carbon life process via update_sight() var/list/status_effects //a list of all status effects the mob has - var/druggy = 0 /// List of changes to body temperature, used by desease symtoms like fever var/list/body_temp_changes = list() @@ -113,18 +107,13 @@ var/worn_layer //use to set if you want your inhand mob sprite to be hidden or not //Speech - var/stuttering = 0 - var/slurring = 0 var/cultslurring = 0 - var/derpspeech = 0 var/lizardspeech = 0 var/list/implants = null var/last_words //used for database logging - var/list/obj/effect/proc_holder/abilities = list() - var/can_be_held = FALSE //whether this can be picked up and held. var/worn_slot_flags = NONE //if it can be held, can it be equipped to any slots? diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index 4a7216675043..5b4098da9021 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -15,9 +15,6 @@ if(ventcrawler) to_chat(src, span_notice("You can ventcrawl! Use alt+click on vents to quickly travel about the station.")) - if(ranged_ability) - ranged_ability.add_ranged_ability(src, span_notice("You currently have [ranged_ability] active!")) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) if(changeling) changeling.regain_powers() diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 43ff344df8e5..17efce97be20 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -238,6 +238,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( return on_say_success(message,message_range,succumbed, spans, language, message_mods)//Yogs + /mob/living/proc/on_say_success(message,message_range,succumbed, spans, language, message_mods) // A helper function of stuff that is deferred to when /mob/living/say() is done and has successfully said something. if(succumbed) succumb(1) @@ -322,7 +323,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( speech_bubble_recipients.Add(M.client) var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - INVOKE_ASYNC(GLOBAL_PROC, TYPE_PROC_REF(/, flick_overlay), I, speech_bubble_recipients, 30) + INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay), I, speech_bubble_recipients, 30) /mob/proc/binarycheck() return FALSE @@ -357,20 +358,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH)) message = unintelligize(message) - if(derpspeech) - message = derpspeech(message, stuttering) - - if(lizardspeech) - message = lizardspeech(message) - - if(stuttering) - message = stutter(message) - - if(slurring) - message = slur(message) - - if(cultslurring) - message = cultslur(message) + SEND_SIGNAL(src, COMSIG_LIVING_TREAT_MESSAGE, args) message = capitalize(message) @@ -386,7 +374,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( imp.radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) return ITALICS | REDUCE_RANGE - var/list/storage_item = list(get_active_held_item(), get_item_by_slot(SLOT_BELT), get_item_by_slot(SLOT_R_STORE), get_item_by_slot(SLOT_L_STORE), get_item_by_slot(SLOT_S_STORE)) + var/list/storage_item = list(get_active_held_item(), get_item_by_slot(SLOT_BELT), get_item_by_slot(SLOT_R_STORE), get_item_by_slot(SLOT_L_STORE), get_item_by_slot(SLOT_SUIT_STORE)) for(var/obj/item/radio/hand in storage_item) if(message_mods[MODE_HEADSET]) hand.talk_into(src, message, , spans, language, message_mods) @@ -432,15 +420,34 @@ GLOBAL_LIST_INIT(special_radio_keys, list( . = "[verb_whisper] in [p_their()] last breath" else if(message_mods[MODE_SING]) . = verb_sing - else if(stuttering) + // Any subtype of slurring in our status effects make us "slur" + else if(locate(/datum/status_effect/speech/slurring) in status_effects) + . = "slurs" + else if(has_status_effect(/datum/status_effect/speech/stutter)) . = "stammers" - else if(derpspeech) + else if(has_status_effect(/datum/status_effect/speech/stutter/derpspeech)) . = "gibbers" else . = ..() -/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - say("[MODE_KEY_WHISPER][message]", bubble_type, spans, sanitize, language, ignore_spam, forced) +/** + * Living level whisper. + * + * Living mobs which whisper have their message only appear to people very close. + * + * message - the message to display + * bubble_type - the type of speech bubble that shows up when they speak (currently does nothing) + * spans - a list of spans to apply around the message + * sanitize - whether we sanitize the message + * language - typepath language to force them to speak / whisper in + * ignore_spam - whether we ignore the spam filter + * forced - string source of what forced this speech to happen, also bypasses spam filter / mutes if supplied + * filterproof - whether we ignore the word filter + */ +/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language, ignore_spam = FALSE, forced, filterproof) + if(!message) + return + say("[MODE_KEY_WHISPER][message]", bubble_type, spans, sanitize, language, ignore_spam, forced, filterproof) /mob/living/get_language_holder(get_minds = TRUE) if(get_minds && mind) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index e4978817a7fa..a022db670b21 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -236,7 +236,7 @@ QDEL_NULL(modularInterface) . = ..() -/mob/living/silicon/ai/IgniteMob() +/mob/living/silicon/ai/ignite_mob() fire_stacks = 0 . = ..() @@ -930,7 +930,7 @@ /mob/living/silicon/ai/can_buckle() return 0 -/mob/living/silicon/ai/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE, ignore_stasis = FALSE) +/mob/living/silicon/ai/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_stasis = FALSE) if(aiRestorePowerRoutine && !available_ai_cores()) return TRUE return ..() @@ -1092,7 +1092,7 @@ /datum/action/innate/deploy_shell name = "Deploy to AI Shell" desc = "Wirelessly control a specialized cyborg shell." - icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon = 'icons/mob/actions/actions_AI.dmi' button_icon_state = "ai_shell" /datum/action/innate/deploy_shell/Trigger() @@ -1104,7 +1104,7 @@ /datum/action/innate/deploy_last_shell name = "Reconnect to shell" desc = "Reconnect to the most recently used AI shell." - icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon = 'icons/mob/actions/actions_AI.dmi' button_icon_state = "ai_last_shell" var/mob/living/silicon/robot/last_used_shell diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm index aa3696c2ab09..8099b40bf9fb 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm @@ -22,18 +22,15 @@ /datum/ai_project/induction_cyborg/finish() var/datum/action/innate/ai/ranged/charge_borg_or_apc/ability = add_ability(/datum/action/innate/ai/ranged/charge_borg_or_apc) - var/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc/effect if(ability) - effect = ability.linked_ability - effect.works_on_borgs = TRUE - effect.attached_action.button.name = "Charge cyborg" - effect.attached_action.button.desc = "Click a cyborg to charge it by 33%" + ability.works_on_borgs = TRUE + ability.name = "Charge cyborg" + ability.desc = "Click a cyborg to charge it by 33%" else ability = locate(/datum/action/innate/ai/ranged/charge_borg_or_apc) in ai.actions - effect = ability.linked_ability - effect.works_on_borgs = TRUE - effect.attached_action.button.name = "Charge cyborg/APC" - effect.attached_action.button.desc = "Click a cyborg or APC to charge it by 33%" + ability.works_on_borgs = TRUE + ability.name = "Charge cyborg/APC" + ability.desc = "Click a cyborg or APC to charge it by 33%" /datum/ai_project/induction_apc @@ -51,18 +48,14 @@ /datum/ai_project/induction_apc/finish() var/datum/action/innate/ai/ranged/charge_borg_or_apc/ability = add_ability(/datum/action/innate/ai/ranged/charge_borg_or_apc) - var/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc/effect if(ability) - effect = ability.linked_ability - effect.works_on_apcs = TRUE - effect.attached_action.button.name = "Charge APC" - effect.attached_action.button.desc = "Click an APC to charge it by 33%" + ability.name = "Charge APC" + ability.desc = "Click an APC to charge it by 33%" else ability = locate(/datum/action/innate/ai/ranged/charge_borg_or_apc) in ai.actions - effect = ability.linked_ability - effect.works_on_apcs = TRUE - effect.attached_action.button.name = "Charge cyborg/APC" - effect.attached_action.button.desc = "Click a cyborg or APC to charge it by 33%" + ability.name = "Charge cyborg/APC" + ability.desc = "Click a cyborg or APC to charge it by 33%" + ability.works_on_apcs = TRUE /datum/action/innate/ai/ranged/charge_borg_or_apc @@ -71,11 +64,41 @@ button_icon_state = "electrified" uses = 1 delete_on_empty = FALSE - linked_ability_type = /obj/effect/proc_holder/ranged_ai/charge_borg_or_apc + var/works_on_borgs = FALSE + var/works_on_apcs = FALSE + enable_text = span_notice("You prepare bluespace induction coils. Click a borg or APC to charge its cell by 33%") + disable_text = span_notice("You power down your induction coils.") + +/datum/action/innate/ai/ranged/charge_borg_or_apc/InterceptClickOn(mob/living/caller, params, atom/clicked_on) + . = ..() + if(!.) + return FALSE + if(owner.incapacitated()) + unset_ranged_ability(owner, "You are incapacitated!") + return FALSE + + if(!iscyborg(clicked_on) && !istype(clicked_on, /obj/machinery/power/apc)) + to_chat(owner, span_warning("You can only charge cyborgs or APCs!")) + return FALSE + if(!works_on_borgs && iscyborg(clicked_on)) + to_chat(owner, span_warning("You can only charge APCs!")) + return FALSE + if(!works_on_apcs && istype(clicked_on, /obj/machinery/power/apc)) + to_chat(owner, span_warning("You can only charge cyborgs!")) + return FALSE + + owner.playsound_local(owner, "sparks", 50, 0) + + if(charge_borg_or_apc(clicked_on)) + adjust_uses(-1) + do_sparks(3, FALSE,clicked_on) + to_chat(owner, span_notice("You charge [clicked_on].")) + clicked_on.audible_message(span_userdanger("You hear a soothing electrical buzzing sound coming from [clicked_on]!")) + return TRUE /datum/action/innate/ai/ranged/charge_borg_or_apc/proc/charge_borg_or_apc(atom/target) if(target && !QDELETED(target)) - if(istype(target, /mob/living/silicon/robot)) + if(iscyborg(target)) var/mob/living/silicon/robot/R = target log_game("[key_name(usr)] charged [R.name].") if(R.cell) @@ -98,37 +121,3 @@ return TRUE else to_chat(owner, span_warning("The APC has no powercell to charge!")) - -/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc - active = FALSE - var/works_on_borgs = FALSE - var/works_on_apcs = FALSE - enable_text = span_notice("You prepare bluespace induction coils. Click a borg or APC to charge its cell by 33%") - disable_text = span_notice("You power down your induction coils.") - -/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - if(!istype(target, /mob/living/silicon/robot) && !istype(target, /obj/machinery/power/apc)) - to_chat(ranged_ability_user, span_warning("You can only charge cyborgs or APCs!")) - return - if(!works_on_borgs && istype(target, /mob/living/silicon/robot)) - to_chat(ranged_ability_user, span_warning("You can only charge APCs!")) - return - if(!works_on_apcs && istype(target, /obj/machinery/power/apc)) - to_chat(ranged_ability_user, span_warning("You can only charge cyborgs!")) - return - - ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 50, 0) - - var/datum/action/innate/ai/ranged/charge_borg_or_apc/action = attached_action - if(action.charge_borg_or_apc(target)) - attached_action.adjust_uses(-1) - do_sparks(3, FALSE, target) - to_chat(caller, span_notice("You charge [target].")) - target.audible_message(span_userdanger("You hear a soothing electrical buzzing sound coming from [target]!")) - remove_ranged_ability() - return TRUE diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 6cc9aeb3dc09..0ad2cea4b45f 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -29,14 +29,14 @@ var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] var/list/old_images = hud_list[AI_DETECT_HUD] if(!ai_detector_visible) - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) QDEL_LIST(old_images) return - if(!length(hud.hudusers)) + if(!length(hud.hud_users)) return //no one is watching, do not bother updating anything - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) var/static/list/vis_contents_opaque = list() var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_opaque[ai_detector_color] @@ -56,7 +56,7 @@ for(var/i in (new_images.len + 1) to old_images.len) qdel(old_images[i]) hud_list[AI_DETECT_HUD] = new_images - hud.add_to_hud(src) + hud.add_atom_to_hud(src) /mob/camera/aiEye/proc/get_visible_turfs() if(!isturf(loc)) @@ -114,7 +114,7 @@ GLOB.aiEyes -= src if(ai_detector_visible) var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) var/list/L = hud_list[AI_DETECT_HUD] QDEL_LIST(L) return ..() diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index 2c0040839a03..cc329419a339 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -105,7 +105,7 @@ death() return else if(stat == UNCONSCIOUS) - stat = CONSCIOUS + set_stat(CONSCIOUS) adjust_blindness(-1) diag_hud_set_status() diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm index a4a417c2857f..c4f30635f746 100644 --- a/code/modules/mob/living/silicon/damage_procs.dm +++ b/code/modules/mob/living/silicon/damage_procs.dm @@ -51,9 +51,9 @@ return FALSE //Special snowflake AI damage nos -/mob/living/silicon/ai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/silicon/ai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status) return FALSE -/mob/living/silicon/ai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/silicon/ai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) return FALSE diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index 599ccab4ec51..fd5e049e67fe 100644 --- a/code/modules/mob/living/silicon/pai/death.dm +++ b/code/modules/mob/living/silicon/pai/death.dm @@ -1,7 +1,7 @@ /mob/living/silicon/pai/death(gibbed) if(stat == DEAD) return - stat = DEAD + set_stat(DEAD) mobility_flags = NONE update_sight() clear_fullscreens() diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 74767cbc43ec..d1fa0624d179 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -197,7 +197,7 @@ /datum/action/innate/pai name = "PAI Action" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' var/mob/living/silicon/pai/P /datum/action/innate/pai/Trigger() @@ -209,6 +209,7 @@ name = "Software Interface" button_icon_state = "pai" background_icon_state = "bg_tech" + overlay_icon_state = "bg_tech_border" /datum/action/innate/pai/software/Trigger() ..() @@ -218,6 +219,7 @@ name = "Toggle Holoform" button_icon_state = "pai_holoform" background_icon_state = "bg_tech" + overlay_icon_state = "bg_tech_border" /datum/action/innate/pai/shell/Trigger() ..() @@ -230,6 +232,7 @@ name = "Holochassis Appearance Composite" button_icon_state = "pai_chassis" background_icon_state = "bg_tech" + overlay_icon_state = "bg_tech_border" /datum/action/innate/pai/chassis/Trigger() ..() @@ -239,6 +242,7 @@ name = "Rest" button_icon_state = "pai_rest" background_icon_state = "bg_tech" + overlay_icon_state = "bg_tech_border" /datum/action/innate/pai/rest/Trigger() ..() @@ -246,9 +250,10 @@ /datum/action/innate/pai/light name = "Toggle Integrated Lights" - icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon = 'icons/mob/actions/actions_spells.dmi' button_icon_state = "emp" background_icon_state = "bg_tech" + overlay_icon_state = "bg_tech_border" /datum/action/innate/pai/light/Trigger() ..() diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm index 5c4e614d22f0..a4929149961b 100644 --- a/code/modules/mob/living/silicon/pai/pai_defense.dm +++ b/code/modules/mob/living/silicon/pai/pai_defense.dm @@ -15,13 +15,13 @@ //Ask and you shall receive switch(rand(1, 3)) if(1) - stuttering = 1 + adjust_stutter(1 MINUTES / severity) to_chat(src, span_danger("Warning: Feedback loop detected in speech module.")) if(2) - slurring = 1 + adjust_slurring(INFINITY) to_chat(src, span_danger("Warning: Audio synthesizer CPU stuck.")) if(3) - derpspeech = 1 + set_derpspeech(INFINITY) to_chat(src, span_danger("Warning: Vocabulary databank corrupted.")) if(prob(40)) mind.language_holder.selected_language = get_random_spoken_language() @@ -70,7 +70,7 @@ /mob/living/silicon/pai/stripPanelEquip(obj/item/what, mob/who, where) //prevents stripping to_chat(src, span_warning("Your holochassis stutters and warps intensely as you attempt to interact with the object, forcing you to cease lest the field fail.")) -/mob/living/silicon/pai/IgniteMob(var/mob/living/silicon/pai/P) +/mob/living/silicon/pai/ignite_mob(var/mob/living/silicon/pai/P) return FALSE //No we're not flammable /mob/living/silicon/pai/proc/take_holo_damage(amount) @@ -80,10 +80,10 @@ to_chat(src, span_userdanger("The impact degrades your holochassis!")) return amount -/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) return take_holo_damage(amount) -/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status) return take_holo_damage(amount) /mob/living/silicon/pai/adjustStaminaLoss(amount, updating_health, forced = FALSE) diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index e04ac4ebc56a..a54c85a9c65c 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -250,20 +250,20 @@ secHUD = !secHUD if(secHUD) var/datum/atom_hud/sec = GLOB.huds[sec_hud] - sec.add_hud_to(src) + sec.show_to(src) else var/datum/atom_hud/sec = GLOB.huds[sec_hud] - sec.remove_hud_from(src) + sec.hide_from(src) if("medicalhud") if(href_list["toggle"]) medHUD = !medHUD if(medHUD) var/datum/atom_hud/med = GLOB.huds[med_hud] - med.add_hud_to(src) + med.show_to(src) else var/datum/atom_hud/med = GLOB.huds[med_hud] - med.remove_hud_from(src) + med.hide_from(src) if("hostscan") if(href_list["toggle"]) diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 36851bce98f8..c2c5a20ff967 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -79,7 +79,7 @@ fire_stacks-- fire_stacks = max(0, fire_stacks) else - ExtinguishMob() + extinguish_mob() return TRUE //adjustFireLoss(3) @@ -97,4 +97,4 @@ else mobility_flags = MOBILITY_FLAGS_DEFAULT update_transform() - update_action_buttons_icon() + update_mob_action_buttons() diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 90d9d8022399..08ec212972ff 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -51,7 +51,6 @@ var/obj/item/stock_parts/cell/cell = null var/opened = FALSE - var/emagged = FALSE var/emag_cooldown = 0 var/wiresexposed = FALSE @@ -192,7 +191,7 @@ mmi.forceMove(T) if(mmi.brainmob) if(mmi.brainmob.stat == DEAD) - mmi.brainmob.stat = CONSCIOUS + mmi.brainmob.set_stat(CONSCIOUS) mmi.brainmob.remove_from_dead_mob_list() mmi.brainmob.add_to_alive_mob_list() mind.transfer_to(mmi.brainmob) @@ -1055,12 +1054,12 @@ return if(IsUnconscious() || IsStun() || IsKnockdown() || IsParalyzed() || getOxyLoss() > maxHealth*0.5) if(stat == CONSCIOUS) - stat = UNCONSCIOUS + set_stat(UNCONSCIOUS) blind_eyes(1) update_mobility() else if(stat == UNCONSCIOUS) - stat = CONSCIOUS + set_stat(CONSCIOUS) adjust_blindness(-1) update_mobility() diag_hud_set_status() @@ -1203,7 +1202,7 @@ /datum/action/innate/undeployment name = "Disconnect from shell" desc = "Stop controlling your shell and resume normal core operations." - icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon = 'icons/mob/actions/actions_AI.dmi' button_icon_state = "ai_core" /datum/action/innate/undeployment/Trigger() diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 32b1b74f6c5a..487c1d033d74 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -72,7 +72,7 @@ /mob/living/silicon/robot/fire_act() if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them - IgniteMob() + ignite_mob() /mob/living/silicon/robot/emp_act(severity) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index bd46c033b2fe..eadb09f55512 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -43,7 +43,8 @@ var/law_change_counter = 0 var/obj/machinery/camera/builtInCamera = null var/updating = FALSE //portable camera camerachunk update - + ///Whether we have been emagged + var/emagged = FALSE var/hack_software = FALSE //Will be able to use hacking actions var/interaction_range = 7 //wireless control range ///The reference to the built-in tablet that borgs carry. @@ -55,7 +56,7 @@ GLOB.silicon_mobs += src faction += "silicon" 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_status() diag_hud_set_health() @@ -376,17 +377,17 @@ var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] var/datum/atom_hud/medsensor = GLOB.huds[med_hud] var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] - secsensor.remove_hud_from(src) - medsensor.remove_hud_from(src) - diagsensor.remove_hud_from(src) + secsensor.hide_from(src) + medsensor.hide_from(src) + diagsensor.hide_from(src) /mob/living/silicon/proc/add_sensors() var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] var/datum/atom_hud/medsensor = GLOB.huds[med_hud] var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] - secsensor.add_hud_to(src) - medsensor.add_hud_to(src) - diagsensor.add_hud_to(src) + secsensor.show_to(src) + medsensor.show_to(src) + diagsensor.show_to(src) /mob/living/silicon/proc/toggle_sensors(silent = FALSE) if(incapacitated()) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 5575f234acbc..b5f5a013fc2b 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -160,7 +160,7 @@ //Adds bot to the diagnostic HUD system 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_bothealth() diag_hud_set_botstat() diag_hud_set_botmode() @@ -168,10 +168,10 @@ //If a bot has its own HUD (for player bots), provide it. if(data_hud_type) var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] - datahud.add_hud_to(src) + datahud.show_to(src) if(path_hud) - path_hud.add_to_hud(src) - path_hud.add_hud_to(src) + path_hud.add_atom_to_hud(src) + path_hud.show_to(src) /mob/living/simple_animal/bot/update_mobility() . = ..() @@ -989,7 +989,7 @@ Pass a positive integer as an argument to override a bot's default speed. path_huds_watching_me += path_hud for(var/V in path_huds_watching_me) var/datum/atom_hud/H = V - H.remove_from_hud(src) + H.remove_atom_from_hud(src) var/list/path_images = hud_list[DIAG_PATH_HUD] QDEL_LIST(path_images) @@ -1032,7 +1032,7 @@ Pass a positive integer as an argument to override a bot's default speed. for(var/V in path_huds_watching_me) var/datum/atom_hud/H = V - H.add_to_hud(src) + H.add_atom_to_hud(src) /mob/living/simple_animal/bot/proc/increment_path() diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index a67d8fd4c2ad..87cb73c8e6dc 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -73,7 +73,7 @@ //SECHUD var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - secsensor.add_hud_to(src) + secsensor.show_to(src) /mob/living/simple_animal/bot/ed209/turn_on() . = ..() @@ -545,8 +545,8 @@ Auto Patrol[]"}, spawn(2) icon_state = "[lasercolor]ed209[on]" var/threat = 5 - C.Paralyze(100) - C.stuttering = 5 + C.Paralyze(10 SECONDS) + C.adjust_stutter(5 SECONDS) if(ishuman(C)) var/mob/living/carbon/human/H = C var/judgement_criteria = judgement_criteria() @@ -568,6 +568,6 @@ Auto Patrol[]"}, if( !on || !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. return if(!C.handcuffed) - C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C) + C.set_handcuffed(new /obj/item/restraints/handcuffs/cable/zipties/used(C)) C.update_handcuffed() back_to_idle() diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index b5b97b1f89e3..384edd069202 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -192,9 +192,9 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}, sensor_blink() if(spam_flag == 0) if(ishuman(C)) - C.stuttering = 20 + C.adjust_stutter(20 SECONDS) C.adjustEarDamage(0, 5) //far less damage than the H.O.N.K. - C.Jitter(50) + C.adjust_jitter(50 SECONDS) C.Paralyze(60) var/mob/living/carbon/human/H = C if(client) //prevent spam from players.. @@ -213,8 +213,8 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}, C.visible_message(span_danger("[src] has honked [C]!"),\ span_userdanger("[src] has honked you!")) else - C.stuttering = 20 - C.Paralyze(80) + C.adjust_stutter(20 SECONDS) + C.Paralyze(8 SECONDS) addtimer(CALLBACK(src, PROC_REF(spam_flag_false)), cooldowntime) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 894f9eaa66fa..60bf9159515c 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -78,7 +78,7 @@ //SECHUD var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - secsensor.add_hud_to(src) + secsensor.show_to(src) if(prob(5)) russian = TRUE // imported from Russia @@ -255,7 +255,7 @@ Auto Patrol: []"}, if( !on || !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. return if(!C.handcuffed) - C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C) + C.set_handcuffed(new /obj/item/restraints/handcuffs/cable/zipties/used(C)) C.update_handcuffed() playsound(src, russian ? "law_russian" : "law", 50, 0) back_to_idle() @@ -267,15 +267,13 @@ Auto Patrol: []"}, addtimer(CALLBACK(src, PROC_REF(update_icon)), 2) var/threat = 5 if(ishuman(C)) - C.stuttering = 5 - C.Paralyze(100) var/mob/living/carbon/human/H = C threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, PROC_REF(check_for_weapons))) else - C.Paralyze(100) - C.stuttering = 5 threat = C.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, PROC_REF(check_for_weapons))) + C.Paralyze(10 SECONDS) + C.adjust_stutter(5 SECONDS) log_combat(src,C,"stunned") if(declare_arrests) var/area/location = get_area(src) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 9c4be1cc52a2..357540a42e94 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -9,7 +9,7 @@ response_disarm = "flails at" response_harm = "punches" speak_chance = 1 - icon = 'icons/mob/mob.dmi' + icon = 'icons/mob/nonhuman-player/cult.dmi' speed = 0 spacewalk = TRUE a_intent = INTENT_HARM @@ -39,28 +39,30 @@ var/seeking = FALSE var/can_repair_constructs = FALSE var/can_repair_self = FALSE - var/runetype + /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue + var/theme = THEME_CULT /mob/living/simple_animal/hostile/construct/Initialize() . = ..() update_health_hud() - var/spellnum = 1 for(var/spell in construct_spells) - var/the_spell = new spell(null) - AddSpell(the_spell) - var/obj/effect/proc_holder/spell/S = mob_spell_list[spellnum] - var/pos = 2+spellnum*31 + var/datum/action/new_spell = new spell(src) + new_spell.Grant(src) + + var/spellnum = 1 + for(var/datum/action/spell as anything in actions) + if(!(type in construct_spells)) + continue + + var/pos = 2 + spellnum * 31 if(construct_spells.len >= 4) - pos -= 31*(construct_spells.len - 4) - S.action.button.screen_loc = "6:[pos],4:-2" - S.action.button.moved = "6:[pos],4:-2" + pos -= 31 * (construct_spells.len - 4) + spell.default_button_position = "6:[pos],4:-2" // Set the default position to this random position spellnum++ - if(runetype) - var/datum/action/innate/cult/create_rune/CR = new runetype(src) - CR.Grant(src) - var/pos = 2+spellnum*31 - CR.button.screen_loc = "6:[pos],4:-2" - CR.button.moved = "6:[pos],4:-2" + update_action_buttons() + + if(icon_state) + add_overlay("glow_[icon_state]_[theme]") /mob/living/simple_animal/hostile/construct/Login() ..() @@ -69,7 +71,15 @@ /mob/living/simple_animal/hostile/construct/examine(mob/user) var/t_He = p_they(TRUE) var/t_s = p_s() - . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") + var/text_span + switch(theme) + if(THEME_CULT) + text_span = "cult" + if(THEME_WIZARD) + text_span = "purple" + if(THEME_HOLY) + text_span = "blue" + . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") if(health < maxHealth) if(health >= maxHealth/2) . += span_warning("[t_He] look[t_s] slightly dented.") @@ -122,8 +132,8 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, name = "Juggernaut" real_name = "Juggernaut" desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire." - icon_state = "behemoth" - icon_living = "behemoth" + icon_state = "juggernaut" + icon_living = "juggernaut" maxHealth = 150 health = 150 response_harm = "harmlessly punches" @@ -138,9 +148,11 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, status_flags = 0 mob_size = MOB_SIZE_LARGE force_threshold = 10 - construct_spells = list(/obj/effect/proc_holder/spell/targeted/forcewall/cult, - /obj/effect/proc_holder/spell/targeted/projectile/dumbfire/juggernaut) - runetype = /datum/action/innate/cult/create_rune/wall + construct_spells = list( + /datum/action/cooldown/spell/forcewall/cult, + /datum/action/cooldown/spell/basic_projectile/juggernaut, + /datum/action/innate/cult/create_rune/wall, + ) playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand heavy punishment, \ create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." @@ -179,8 +191,7 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, //////////////////////////Angelic-Juggernaut//////////////////////////// /mob/living/simple_animal/hostile/construct/armored/angelic - icon_state = "behemoth_angelic" - icon_living = "behemoth_angelic" + theme = THEME_HOLY loot = list(/obj/item/ectoplasm/angelic) /mob/living/simple_animal/hostile/construct/armored/noncult @@ -190,8 +201,8 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, name = "Wraith" real_name = "Wraith" desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines." - icon_state = "floating" - icon_living = "floating" + icon_state = "wraith" + icon_living = "wraith" maxHealth = 65 health = 65 melee_damage_lower = 20 @@ -200,16 +211,22 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, retreat_distance = 2 //AI wraiths will move in and out of combat attacktext = "slashes" attack_sound = 'sound/weapons/bladeslice.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) - runetype = /datum/action/innate/cult/create_rune/tele - playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, can phase through walls, and your attacks will lower the cooldown on phasing." - - var/attack_refund = 10 //1 second per attack - var/crit_refund = 50 //5 seconds when putting a target into critical - var/kill_refund = 250 //full refund on kills + construct_spells = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift, + /datum/action/innate/cult/create_rune/tele, + ) + playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, \ + can phase through walls, and your attacks will lower the cooldown on phasing." + + // Accomplishing various things gives you a refund on jaunt, to jump in and out. + /// The seconds refunded per attack + var/attack_refund = 1 SECONDS + /// The seconds refunded when putting a target into critical + var/crit_refund = 5 SECONDS /mob/living/simple_animal/hostile/construct/wraith/AttackingTarget() //refund jaunt cooldown when attacking living targets var/prev_stat + var/mob/living/living_target = target if(isliving(target) && !iscultist(target)) var/mob/living/L = target prev_stat = L.stat @@ -217,25 +234,35 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, . = ..() if(. && isnum(prev_stat)) - var/mob/living/L = target - var/refund = 0 - if(QDELETED(L) || (L.stat == DEAD && prev_stat != DEAD)) //they're dead, you killed them - refund += kill_refund - else if(L.InCritical() && prev_stat == CONSCIOUS) //you knocked them into critical - refund += crit_refund - if(L.stat != DEAD && prev_stat != DEAD) - refund += attack_refund - for(var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/S in mob_spell_list) - S.charge_counter = min(S.charge_counter + refund, S.charge_max) + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/jaunt = locate() in actions + if(!jaunt) + return + + var/total_refund = 0 SECONDS + // they're dead, and you killed them - full refund + if(QDELETED(living_target) || (living_target.stat == DEAD && prev_stat != DEAD)) + total_refund += jaunt.cooldown_time + // you knocked them into critical + else if(living_target.stat == UNCONSCIOUS && prev_stat == CONSCIOUS) + total_refund += crit_refund + + if(living_target.stat != DEAD && prev_stat != DEAD) + total_refund += attack_refund + + jaunt.next_use_time -= total_refund + jaunt.build_all_button_icons() /mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things AIStatus = AI_ON //////////////////////////Angelic-Wraith//////////////////////////// /mob/living/simple_animal/hostile/construct/wraith/angelic - icon_state = "floating_angelic" - icon_living = "floating_angelic" + theme = THEME_HOLY loot = list(/obj/item/ectoplasm/angelic) + construct_spells = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/angelic, + /datum/action/innate/cult/create_rune/tele, + ) /mob/living/simple_animal/hostile/construct/wraith/noncult @@ -258,17 +285,18 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, attacktext = "rams" environment_smash = ENVIRONMENT_SMASH_WALLS attack_sound = 'sound/weapons/punch2.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) - runetype = /datum/action/innate/cult/create_rune/revive - playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ - - use magic missile, repair allied constructs, shades, and yourself (by clicking on them), \ - and, most important of all, create new constructs by producing soulstones to capture souls, \ - and shells to place those soulstones into." + construct_spells = list( + /datum/action/cooldown/spell/conjure/cult_floor, + /datum/action/cooldown/spell/conjure/cult_wall, + /datum/action/cooldown/spell/conjure/soulstone, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) + playstyle_string = "You are an Artificer. You are incredibly weak and fragile, \ + but you are able to construct fortifications, use magic missile, and repair allied constructs, shades, \ + and yourself (by clicking on them). Additionally, and most important of all, you can create new constructs \ + by producing soulstones to capture souls, and shells to place those soulstones into." can_repair_constructs = TRUE can_repair_self = TRUE @@ -316,27 +344,32 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, /////////////////////////////Angelic Artificer///////////////////////// /mob/living/simple_animal/hostile/construct/builder/angelic - icon_state = "artificer_angelic" - icon_living = "artificer_angelic" + theme = THEME_HOLY loot = list(/obj/item/ectoplasm/angelic) - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult/purified, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + construct_spells = list( + /datum/action/cooldown/spell/conjure/soulstone/purified, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /mob/living/simple_animal/hostile/construct/builder/noncult - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + construct_spells = list( + /datum/action/cooldown/spell/conjure/cult_floor, + /datum/action/cooldown/spell/conjure/cult_wall, + /datum/action/cooldown/spell/conjure/soulstone/noncult, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /////////////////////////////Harvester///////////////////////// /mob/living/simple_animal/hostile/construct/harvester name = "Harvester" real_name = "Harvester" desc = "A long, thin construct built to herald Nar'sie's rise. It'll be all over soon." - icon_state = "chosen" - icon_living = "chosen" + icon_state = "harvester" + icon_living = "harvester" maxHealth = 40 health = 40 sight = SEE_MOBS @@ -345,9 +378,11 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, attack_vis_effect = ATTACK_EFFECT_SLASH attacktext = "butchers" attack_sound = 'sound/weapons/bladeslice.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/area_conversion, - /obj/effect/proc_holder/spell/targeted/forcewall/cult, - /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) + construct_spells = list( + /datum/action/cooldown/spell/aoe/area_conversion, + /datum/action/cooldown/spell/forcewall/cult, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift, + ) playstyle_string = "You are a Harvester. You are incapable of directly killing humans, but your attacks will remove their limbs: \ Bring those who still cling to this world of illusion back to the Geometer so they may know Truth. Your form and any you are pulling can pass through runed walls effortlessly." can_repair_constructs = TRUE @@ -403,7 +438,10 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, name = "Seek your Master" desc = "You and your master share a soul-link that informs you of their location" background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + buttontooltipstyle = "cult" + button_icon = "icons/mob/actions/actions_cult.dmi" button_icon_state = "cult_mark" var/tracking = FALSE var/mob/living/simple_animal/hostile/construct/the_construct @@ -440,8 +478,10 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, /datum/action/innate/seek_prey name = "Seek the Harvest" desc = "None can hide from Nar'sie, activate to track a survivor attempting to flee the red harvest!" - 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" button_icon_state = "cult_mark" var/mob/living/simple_animal/hostile/construct/harvester/the_construct diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm index 071c4710e2f6..2023bf0f5eda 100644 --- a/code/modules/mob/living/simple_animal/damage_procs.dm +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -7,13 +7,13 @@ updatehealth() return amount -/mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BRUTE]) . = adjustHealth(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) -/mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status) if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BURN]) diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm index c37aad8c59c1..e6c5cb6a540b 100644 --- a/code/modules/mob/living/simple_animal/eldritch_demons.dm +++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm @@ -28,21 +28,15 @@ deathmessage = "implodes into itself" faction = list("heretics") //simple_mob_flags = SILENCE_RANGED_MESSAGE + ///Innate spells that are supposed to be added when a beast is created - var/list/spells_to_add + var/list/actions_to_add /mob/living/simple_animal/hostile/eldritch/Initialize() . = ..() - add_spells() - -/** - * Add_spells - * - * Goes through spells_to_add and adds each spell to the mind. - */ -/mob/living/simple_animal/hostile/eldritch/proc/add_spells() - for(var/spell in spells_to_add) - AddSpell(new spell()) + for(var/spell in actions_to_add) + var/datum/action/cooldown/spell/new_spell = new spell(src) + new_spell.Grant(src) /mob/living/simple_animal/hostile/eldritch/raw_prophet name = "Raw Prophet" @@ -56,52 +50,70 @@ maxHealth = 50 health = 50 sight = SEE_MOBS|SEE_OBJS|SEE_TURFS - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long,/obj/effect/proc_holder/spell/pointed/manse_link,/obj/effect/proc_holder/spell/targeted/telepathy/eldritch,/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch) + actions_to_add = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long, + /datum/action/cooldown/spell/list_target/telepathy/eldritch, + /datum/action/cooldown/spell/pointed/blind/eldritch, + /datum/action/innate/expand_sight, + ) - var/list/linked_mobs = list() + /// A weakref to the last target we smacked. Hitting targets consecutively does more damage. + var/datum/weakref/last_target /mob/living/simple_animal/hostile/eldritch/raw_prophet/Initialize() . = ..() - link_mob(src) + var/on_link_message = "You feel something new enter your sphere of mind... \ + You hear whispers of people far away, screeches of horror and a huming of welcome to [src]'s Mansus Link." + + var/on_unlink_message = "Your mind shatters as [src]'s Mansus Link leaves your mind." + + AddComponent(/datum/component/mind_linker, \ + network_name = "Mansus Link", \ + chat_color = "#568b00", \ + linker_action_path = /datum/action/cooldown/spell/pointed/manse_link, \ + link_message = on_link_message, \ + unlink_message = on_unlink_message, \ + post_unlink_callback = CALLBACK(src, PROC_REF(after_unlink)), \ + speech_action_background_icon_state = "bg_heretic", \ + ) + +/mob/living/simple_animal/hostile/eldritch/raw_prophet/attack_animal(mob/living/simple_animal/user, list/modifiers) + if(user == src) // Easy to hit yourself + very fragile = accidental suicide, prevent that + return + + return ..() + +/mob/living/simple_animal/hostile/eldritch/raw_prophet/AttackingTarget(atom/attacked_target) + if(WEAKREF(attacked_target) == last_target) + melee_damage_lower = min(melee_damage_lower + 5, 30) + melee_damage_upper = min(melee_damage_upper + 5, 35) + else + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) -/mob/living/simple_animal/hostile/eldritch/raw_prophet/Login() . = ..() - client?.view_size.setTo(10) - -/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/link_mob(mob/living/mob_linked) - if(QDELETED(mob_linked) || mob_linked.stat == DEAD) - return FALSE - if(HAS_TRAIT(mob_linked, TRAIT_MINDSHIELD)) //mindshield implant, no dice - return FALSE - if(mob_linked.anti_magic_check(FALSE, FALSE, TRUE, 0)) - return FALSE - if(linked_mobs[mob_linked]) - return FALSE - - to_chat(mob_linked, span_notice("You feel something new enter your mind, you hear whispers of people far away, screeches of horror and a huming of welcome to [src]'s Mansus link.")) - var/datum/action/innate/mansus_speech/action = new(src) - linked_mobs[mob_linked] = action - action.Grant(mob_linked) - RegisterSignals(mob_linked, list(COMSIG_GLOB_MOB_DEATH, COMSIG_PARENT_QDELETING) , PROC_REF(unlink_mob)) - return TRUE + if(!.) + return + + SpinAnimation(5, 1) + last_target = WEAKREF(attacked_target) + +/mob/living/simple_animal/hostile/eldritch/raw_prophet/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) + . = ..() + var/rotation_degree = (360 / 3) + if(movement_dir & WEST || movement_dir & SOUTH) + rotation_degree *= -1 -/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/unlink_mob(mob/living/mob_linked) - if(!linked_mobs[mob_linked]) + var/matrix/to_turn = matrix(transform) + to_turn = turn(transform, rotation_degree) + animate(src, transform = to_turn, time = 0.1 SECONDS) + +/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/after_unlink(mob/living/unlinked_mob) + if(QDELETED(unlinked_mob) || unlinked_mob.stat == DEAD) return - UnregisterSignal(mob_linked, list(COMSIG_GLOB_MOB_DEATH, COMSIG_PARENT_QDELETING)) - var/datum/action/innate/mansus_speech/action = linked_mobs[mob_linked] - action.Remove(mob_linked) - qdel(action) - to_chat(mob_linked, span_notice("You feel something tear out of your mind as the [src]'s Mansus link leaves your mind.")) - mob_linked.emote("Scream") - //micro stun - mob_linked.AdjustParalyzed(0.5 SECONDS) - linked_mobs -= mob_linked - -/mob/living/simple_animal/hostile/eldritch/raw_prophet/death(gibbed) - for(var/linked_mob in linked_mobs) - unlink_mob(linked_mob) - return ..() + + INVOKE_ASYNC(unlinked_mob, TYPE_PROC_REF(/mob, emote), "scream") + unlinked_mob.AdjustParalyzed(0.5 SECONDS) //micro stun /mob/living/simple_animal/hostile/eldritch/armsy name = "Lavish Serpent" @@ -115,7 +127,7 @@ melee_damage_upper = 15 move_resist = MOVE_FORCE_OVERPOWERING+1 movement_type = GROUND - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/worm_contract) + actions_to_add = list(/datum/action/cooldown/spell/worm_contract) ranged_cooldown_time = 5 ranged = TRUE rapid = 1 @@ -299,7 +311,10 @@ melee_damage_lower = 15 melee_damage_upper = 20 sight = SEE_TURFS - spells_to_add = list(/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small,/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave/short) + actions_to_add = list( + /datum/action/cooldown/spell/aoe/rust_conversion/small, + /datum/action/cooldown/spell/basic_projectile/rust_wave/short, + ) /mob/living/simple_animal/hostile/eldritch/rust_spirit/setDir(newdir) . = ..() @@ -334,7 +349,11 @@ melee_damage_lower = 15 melee_damage_upper = 20 sight = SEE_TURFS - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/pointed/cleave,/obj/effect/proc_holder/spell/targeted/fire_sworn) + actions_to_add = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/pointed/cleave, + /datum/action/cooldown/spell/fire_sworn, + ) /mob/living/simple_animal/hostile/eldritch/stalker name = "Stalker" @@ -348,4 +367,8 @@ melee_damage_lower = 15 melee_damage_upper = 20 sight = SEE_MOBS - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch,/obj/effect/proc_holder/spell/targeted/emplosion/eldritch) + actions_to_add = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/shapeshift/eldritch, + /datum/action/cooldown/spell/emp/eldritch, + ) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index 792367c8cd90..851a966ea1c4 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -110,7 +110,7 @@ alert_drones(DRONE_NET_CONNECT) 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) if(pacifism) ADD_TRAIT(src, TRAIT_PACIFISM, JOB_TRAIT) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm index c7bcb9758482..520728b34532 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm @@ -40,8 +40,15 @@ return head if(SLOT_GENERC_DEXTROUS_STORAGE) return internal_storage + return ..() +/mob/living/simple_animal/drone/get_slot_by_item(obj/item/looking_for) + if(internal_storage == looking_for) + return ITEM_SLOT_DEX_STORAGE + if(head == looking_for) + return ITEM_SLOT_HEAD + return ..() /mob/living/simple_animal/drone/equip_to_slot(obj/item/I, slot) if(!slot) diff --git a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm index d6a36009597d..f5fe2ccbfd7f 100644 --- a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm +++ b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm @@ -30,7 +30,7 @@ var/obj/machinery/camera/camera = null var/obj/item/mmi/mmi = null var/req_access = ACCESS_ROBO_CONTROL //Access needed to pop out the brain. - var/emagged = 0 + var/emagged = FALSE var/obj/item/held_item = null //Storage for single item they can hold. /mob/living/simple_animal/spiderbot/attackby(obj/item/O, mob/user) diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm index 387d4b095533..dc2d4a0b8cfd 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -57,6 +57,17 @@ return 1 ..() +/mob/living/simple_animal/hostile/guardian/dextrous/get_item_by_slot(slot_id) + if(slot_id == ITEM_SLOT_DEX_STORAGE) + return internal_storage + return ..() + +/mob/living/simple_animal/hostile/guardian/dextrous/get_slot_by_item(obj/item/looking_for) + if(internal_storage == looking_for) + return ITEM_SLOT_DEX_STORAGE + return ..() + + /mob/living/simple_animal/hostile/guardian/dextrous/equip_to_slot(obj/item/I, slot) if(!..()) return diff --git a/code/modules/mob/living/simple_animal/guardian/types/fire.dm b/code/modules/mob/living/simple_animal/guardian/types/fire.dm index ed4e791b2347..ebb8a7795c05 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/fire.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/fire.dm @@ -15,7 +15,7 @@ /mob/living/simple_animal/hostile/guardian/fire/Life() . = ..() if(summoner) - summoner.ExtinguishMob() + summoner.extinguish_mob() summoner.fire_stacks = -1 /mob/living/simple_animal/hostile/guardian/fire/AttackingTarget() @@ -40,4 +40,4 @@ var/mob/living/M = AM if(!hasmatchingsummoner(M) && M != summoner && M.fire_stacks < 7) M.fire_stacks = 7 - M.IgniteMob() + M.ignite_mob() diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm index 572729a04484..546b140d3cdc 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm @@ -108,7 +108,7 @@ H.electrocution_animation(20) C.jitteriness += 1000 C.do_jitter_animation(jitteriness) - C.stuttering += 1 + C.adjust_stutter(1 SECONDS) spawn(20) if(C) C.jitteriness = max(C.jitteriness - 990, 10) diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm index 29fb548ee648..87c09d5473e9 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/support.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm @@ -18,7 +18,7 @@ /mob/living/simple_animal/hostile/guardian/healer/Initialize() . = ..() var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medsensor.add_hud_to(src) + medsensor.show_to(src) /mob/living/simple_animal/hostile/guardian/healer/get_status_tab_items() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm index a570c2b6eab9..d7c8f78e991d 100644 --- a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm +++ b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm @@ -31,7 +31,7 @@ //Lets the wizard summon his art to fight for him /datum/action/boss/wizard_summon_minions name = "Summon Minions" - icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' button_icon_state = "art_summon" usage_probability = 40 boss_cost = 30 @@ -59,7 +59,7 @@ //Hitting the wizard himself destroys all decoys /datum/action/boss/wizard_mimic name = "Craft Mimicry" - icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' button_icon_state = "mimic_summon" usage_probability = 30 boss_cost = 40 diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 63a8d935007e..61d4791dacec 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -50,19 +50,14 @@ see_in_dark = 4 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE var/playable_spider = FALSE - var/datum/action/innate/spider/lay_web/lay_web var/directive = "" //Message passed down to children, to relay the creator's orders do_footstep = TRUE /mob/living/simple_animal/hostile/poison/giant_spider/Initialize() . = ..() - lay_web = new - lay_web.Grant(src) - -/mob/living/simple_animal/hostile/poison/giant_spider/Destroy() - QDEL_NULL(lay_web) - return ..() + var/datum/action/innate/spider/lay_web/webbing = new(src) + webbing.Grant(src) /mob/living/simple_animal/hostile/poison/giant_spider/Topic(href, href_list) if(href_list["activate"]) @@ -112,26 +107,25 @@ poison_type = /datum/reagent/toxin/staminatoxin var/atom/movable/cocoon_target var/fed = 0 - var/obj/effect/proc_holder/wrap/wrap - var/datum/action/innate/spider/lay_eggs/lay_eggs - var/datum/action/innate/spider/set_directive/set_directive var/static/list/consumed_mobs = list() //the tags of mobs that have been consumed by nurse spiders to lay eggs gold_core_spawnable = NO_SPAWN /mob/living/simple_animal/hostile/poison/giant_spider/nurse/Initialize() . = ..() - wrap = new - AddAbility(wrap) - lay_eggs = new - lay_eggs.Grant(src) - set_directive = new - set_directive.Grant(src) - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Destroy() - RemoveAbility(wrap) - QDEL_NULL(lay_eggs) - QDEL_NULL(set_directive) - return ..() + var/datum/atom_hud/datahud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + datahud.show_to(src) + + var/datum/action/cooldown/wrap/wrapping = new(src) + wrapping.Grant(src) + + var/datum/action/innate/spider/lay_eggs/make_eggs = new(src) + make_eggs.Grant(src) + + var/datum/action/innate/spider/set_directive/give_orders = new(src) + give_orders.Grant(src) + + var/datum/action/innate/spider/comm/not_hivemind_talk = new(src) + not_hivemind_talk.Grant(src) //broodmothers are the queen of the spiders, can send messages to all them and web faster. That rare round where you get a queen spider and turn your 'for honor' players into 'r6siege' players will be a fun one. /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife @@ -142,18 +136,13 @@ icon_dead = "midwife_dead" maxHealth = 40 health = 40 - var/datum/action/innate/spider/comm/letmetalkpls gold_core_spawnable = HOSTILE_SPAWN //yogs xenobio spiders best spiders /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Initialize() . = ..() - letmetalkpls = new + var/datum/action/innate/spider/comm/letmetalkpls = new(src) letmetalkpls.Grant(src) -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Destroy() - QDEL_NULL(letmetalkpls) - return ..() - //hunters have the most poison and move the fastest, so they can find prey /mob/living/simple_animal/hostile/poison/giant_spider/hunter desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." @@ -277,11 +266,13 @@ //second, spin a sticky spiderweb on this tile var/obj/structure/spider/stickyweb/W = locate() in get_turf(src) if(!W) - lay_web.Activate() + for(var/datum/action/innate/spider/lay_web/lay_web in actions) + lay_web.Activate() else //third, lay an egg cluster there if(fed) - lay_eggs.Activate() + for(var/datum/action/innate/spider/lay_eggs/lay_eggs in actions) + lay_eggs.Activate() else //fourthly, cocoon any nearby items so those pesky pinkskins can't use them for(var/obj/O in can_see) @@ -332,7 +323,8 @@ if(L.blood_volume && (L.stat != DEAD || !consumed_mobs[L.tag])) //if they're not dead, you can consume them anyway consumed_mobs[L.tag] = TRUE fed++ - lay_eggs.UpdateButtonIcon(TRUE) + for(var/datum/action/innate/spider/lay_eggs/lay_eggs in actions) + lay_eggs.build_all_button_icons(TRUE) visible_message(span_danger("[src] sticks a proboscis into [L] and sucks a viscous substance out."),span_notice("You suck the nutriment out of [L], feeding you enough to lay a cluster of eggs.")) L.death() //you just ate them, they're dead. else @@ -346,143 +338,194 @@ stop_automated_movement = FALSE /datum/action/innate/spider - icon_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon = 'icons/mob/actions/actions_animal.dmi' background_icon_state = "bg_alien" -/datum/action/innate/spider/lay_web +/datum/action/innate/spider/lay_web // Todo: Unify this with the genetics power name = "Spin Web" desc = "Spin a web to slow down potential prey." check_flags = AB_CHECK_CONSCIOUS button_icon_state = "lay_web" -/datum/action/innate/spider/lay_web/Activate() +/datum/action/innate/spider/lay_web/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/S = owner + return FALSE - if(!isturf(S.loc)) - return - var/turf/T = get_turf(S) + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + var/obj/structure/spider/stickyweb/web = locate() in get_turf(spider) + if(web) //&& !spider.web_sealer) //if(web && (!spider.web_sealer || istype(web, /obj/structure/spider/stickyweb/sealed))) + to_chat(owner, span_warning("There's already a web here!")) + return FALSE - var/obj/structure/spider/stickyweb/W = locate() in T - if(W) - to_chat(S, span_warning("There's already a web here!")) - return + if(!isturf(spider.loc)) + return FALSE - if(S.busy != SPINNING_WEB) - S.busy = SPINNING_WEB - S.visible_message(span_notice("[S] begins to secrete a sticky substance."),span_notice("You begin to lay a web.")) - S.stop_automated_movement = TRUE - if(do_after(S, 4 SECONDS, T)) - if(S.busy == SPINNING_WEB && S.loc == T) - new /obj/structure/spider/stickyweb(T) - S.busy = SPIDER_IDLE - S.stop_automated_movement = FALSE + return TRUE + +/datum/action/innate/spider/lay_web/Activate() + var/turf/spider_turf = get_turf(owner) + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + var/obj/structure/spider/stickyweb/web = locate() in spider_turf + if(web) + spider.visible_message( + span_notice("[spider] begins to pack more webbing onto the web."), + span_notice("You begin to seal the web."), + ) else - to_chat(S, span_warning("You're already spinning a web!")) + to_chat(spider, span_warning("You're already doing something else!")) + spider.visible_message( + span_notice("[spider] begins to secrete a sticky substance."), + span_notice("You begin to lay a web."), + ) -/obj/effect/proc_holder/wrap - name = "Wrap" - panel = "Spider" - active = FALSE - datum/action/spell_action/action = null - desc = "Wrap something or someone in a cocoon. If it's a living being, you'll also consume them, allowing you to lay eggs." - ranged_mousepointer = 'icons/effects/wrap_target.dmi' - action_icon = 'icons/mob/actions/actions_animal.dmi' - action_icon_state = "wrap_0" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/wrap/Initialize() - . = ..() - action = new(src) + spider.stop_automated_movement = TRUE -/obj/effect/proc_holder/wrap/update_icon() - action.button_icon_state = "wrap_[active]" - action.UpdateButtonIcon() + if(do_after(spider, 4 SECONDS, target = spider_turf)) + if(spider.loc == spider_turf) + if(web) + qdel(web) +// new /obj/structure/spider/stickyweb/sealed(spider_turf) + new /obj/structure/spider/stickyweb(spider_turf) -/obj/effect/proc_holder/wrap/Click() - if(!istype(usr, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return TRUE - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = usr - activate(user) - return TRUE + spider.stop_automated_movement = FALSE -/obj/effect/proc_holder/wrap/proc/activate(mob/living/user) - var/message - if(active) - message = span_notice("You no longer prepare to wrap something in a cocoon.") - remove_ranged_ability(message) - else - message = span_notice("You prepare to wrap something in a cocoon. Left-click your target to start wrapping!") - add_ranged_ability(user, message, TRUE) - return 1 +/datum/action/cooldown/wrap + name = "Wrap" + desc = "Wrap something or someone in a cocoon. If it's a human or similar species, \ + you'll also consume them, allowing you to lay enriched eggs." + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "wrap_0" + check_flags = AB_CHECK_CONSCIOUS + click_to_activate = TRUE + ranged_mousepointer = 'icons/effects/mouse_pointers/wrap_target.dmi' + /// The time it takes to wrap something. + var/wrap_time = 5 SECONDS -/obj/effect/proc_holder/wrap/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) +/datum/action/cooldown/wrap/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + if(owner.incapacitated()) + return FALSE + return TRUE + +/datum/action/cooldown/wrap/set_click_ability(mob/on_who) + . = ..() + if(!.) return - if(ranged_ability_user.incapacitated() || !istype(ranged_ability_user, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - remove_ranged_ability() + + to_chat(on_who, span_notice("You prepare to wrap something in a cocoon. Left-click your target to start wrapping!")) + button_icon_state = "wrap_0" + build_all_button_icons() + +/datum/action/cooldown/wrap/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = ranged_ability_user + if(refund_cooldown) + to_chat(on_who, span_notice("You no longer prepare to wrap something in a cocoon.")) + button_icon_state = "wrap_1" + build_all_button_icons() - if(user.Adjacent(target) && (ismob(target) || isobj(target))) - var/atom/movable/target_atom = target - if(target_atom.anchored) - return - user.cocoon_target = target_atom - INVOKE_ASYNC(user, TYPE_PROC_REF(/mob/living/simple_animal/hostile/poison/giant_spider/nurse, cocoon)) - remove_ranged_ability() - return TRUE +/datum/action/cooldown/wrap/Activate(atom/to_wrap) + if(!owner.Adjacent(to_wrap)) + owner.balloon_alert(owner, "must be closer!") + return FALSE + + if(!ismob(to_wrap) && !isobj(to_wrap)) + return FALSE + + if(to_wrap == owner) + return FALSE + + if(istype(to_wrap, /mob/living/simple_animal/hostile/poison/giant_spider)) + owner.balloon_alert(owner, "can't wrap spiders!") + return FALSE + + var/atom/movable/target_movable = to_wrap + if(target_movable.anchored) + return FALSE + + StartCooldown(wrap_time) + INVOKE_ASYNC(src, PROC_REF(cocoon), to_wrap) + return TRUE + +/datum/action/cooldown/wrap/proc/cocoon(atom/movable/to_wrap) + owner.visible_message( + span_notice("[owner] begins to secrete a sticky substance around [to_wrap]."), + span_notice("You begin wrapping [to_wrap] into a cocoon."), + ) + + var/mob/living/simple_animal/animal_owner = owner + if(istype(animal_owner)) + animal_owner.stop_automated_movement = TRUE + + if(do_after(owner, wrap_time, target = to_wrap)) + var/obj/structure/spider/cocoon/casing = new(to_wrap.loc) + if(isliving(to_wrap)) + var/mob/living/living_wrapped = to_wrap + // if they're not dead, you can consume them anyway + if(ishuman(living_wrapped) && (living_wrapped.stat != DEAD || !HAS_TRAIT(living_wrapped, TRAIT_SPIDER_CONSUMED))) + living_wrapped.death() //you just ate them, they're dead. + else + to_chat(owner, span_warning("[living_wrapped] cannot sate your hunger!")) -/obj/effect/proc_holder/wrap/on_lose(mob/living/carbon/user) - remove_ranged_ability() + to_wrap.forceMove(casing) + if(to_wrap.density || ismob(to_wrap)) + casing.icon_state = pick("cocoon_large1", "cocoon_large2", "cocoon_large3") + + if(istype(animal_owner)) + animal_owner.stop_automated_movement = TRUE /datum/action/innate/spider/lay_eggs name = "Lay Eggs" desc = "Lay a cluster of eggs, which will soon grow into more spiders. You must wrap a living being to do this." check_flags = AB_CHECK_CONSCIOUS button_icon_state = "lay_eggs" + ///How long it takes for a broodmother to lay eggs. + var/egg_lay_time = 15 SECONDS + ///The type of egg we create + //var/egg_type = /obj/effect/mob_spawn/ghost_role/spider -/datum/action/innate/spider/lay_eggs/IsAvailable() - if(..()) - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return 0 - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - if(S.fed) - return 1 - return 0 +/datum/action/innate/spider/lay_eggs/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) + return FALSE + var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner) + if(eggs) + to_chat(owner, span_warning("There is already a cluster of eggs here!")) + return FALSE + + return TRUE /datum/action/innate/spider/lay_eggs/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - - var/obj/structure/spider/eggcluster/E = locate() in get_turf(S) - if(E) - to_chat(S, span_warning("There is already a cluster of eggs here!")) - else if(!S.fed) - to_chat(S, span_warning("You are too hungry to do this!")) - else if(S.busy != LAYING_EGGS) - S.busy = LAYING_EGGS - S.visible_message(span_notice("[S] begins to lay a cluster of eggs."),span_notice("You begin to lay a cluster of eggs.")) - S.stop_automated_movement = TRUE - if(do_after(S, 5 SECONDS, get_turf(S))) - if(S.busy == LAYING_EGGS) - E = locate() in get_turf(S) - if(!E || !isturf(S.loc)) - log_admin("[src] layed eggs at [S.loc]") - var/obj/structure/spider/eggcluster/C = new /obj/structure/spider/eggcluster(get_turf(S)) - if(S.ckey) - C.player_spiders = TRUE - C.directive = S.directive - C.poison_type = S.poison_type - C.poison_per_bite = S.poison_per_bite - C.faction = S.faction.Copy() - S.fed-- - UpdateButtonIcon(TRUE) - S.busy = SPIDER_IDLE - S.stop_automated_movement = FALSE + owner.visible_message( + span_notice("[owner] begins to lay a cluster of eggs."), + span_notice("You begin to lay a cluster of eggs."), + ) + + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + spider.stop_automated_movement = TRUE + + if(do_after(owner, egg_lay_time, target = get_turf(owner))) + var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner) + if(!eggs || !isturf(spider.loc)) + var/obj/structure/spider/eggcluster/new_eggs = new /obj/structure/spider/eggcluster(get_turf(spider)) + new_eggs.directive = spider.directive + new_eggs.faction = spider.faction + build_all_button_icons(TRUE) + + spider.stop_automated_movement = FALSE /datum/action/innate/spider/set_directive name = "Set Directive" @@ -490,11 +533,19 @@ check_flags = AB_CHECK_CONSCIOUS button_icon_state = "directive" +/datum/action/innate/spider/set_directive/IsAvailable(feedback = FALSE) + return ..() && istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider) + /datum/action/innate/spider/set_directive/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - S.directive = stripped_input(S, "Enter the new directive", "Create directive", "[S.directive]") + var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/spider = owner + + spider.directive = tgui_input_text(spider, "Enter the new directive", "Create directive", "[spider.directive]") + if(isnull(spider.directive) || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = FALSE)) + return FALSE + + message_admins("[ADMIN_LOOKUPFLW(owner)] set its directive to: '[spider.directive]'.") + log_game("[key_name(owner)] set its directive to: '[spider.directive]'.") + return TRUE /mob/living/simple_animal/hostile/poison/giant_spider/Login() . = ..() @@ -509,15 +560,14 @@ desc = "Send a command to all living spiders." button_icon_state = "command" -/datum/action/innate/spider/comm/IsAvailable() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife)) - return FALSE - return TRUE +/datum/action/innate/spider/comm/IsAvailable(feedback = FALSE) + return ..() && istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife) /datum/action/innate/spider/comm/Trigger() - var/input = stripped_input(owner, "Input a command for your legions to follow.", "Command", "") - if(QDELETED(src) || !input || !IsAvailable()) + var/input = tgui_input_text(owner, "Input a command for your legions to follow.", "Command") + if(!input || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = FALSE)) return FALSE + spider_command(owner, input) return TRUE @@ -526,12 +576,12 @@ return var/my_message my_message = span_spider("Command from [user]: [message]") - for(var/mob/living/simple_animal/hostile/poison/giant_spider/M in GLOB.spidermobs) - to_chat(M, my_message) - for(var/M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, user) - to_chat(M, "[link] [my_message]") - usr.log_talk(message, LOG_SAY, tag="spider command") + for(var/mob/living/simple_animal/hostile/poison/giant_spider/spider as anything in GLOB.spidermobs) + to_chat(spider, my_message) + for(var/ghost in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(ghost, user) + to_chat(ghost, "[link] [my_message]") + user.log_talk(message, LOG_SAY, tag = "spider command") /mob/living/simple_animal/hostile/poison/giant_spider/handle_temperature_damage() if(bodytemperature < minbodytemp) diff --git a/code/modules/mob/living/simple_animal/hostile/goose.dm b/code/modules/mob/living/simple_animal/hostile/goose.dm index e4650fb67a92..34b49b4f1f91 100644 --- a/code/modules/mob/living/simple_animal/hostile/goose.dm +++ b/code/modules/mob/living/simple_animal/hostile/goose.dm @@ -153,7 +153,7 @@ name = "Vomit" check_flags = AB_CHECK_CONSCIOUS button_icon_state = "vomit" - icon_icon = 'icons/mob/animal.dmi' + button_icon = 'icons/mob/animal.dmi' cooldown_time = 250 /datum/action/cooldown/vomit/Trigger() diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm index e1c7602d1c05..ef4fc6d283c2 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm @@ -159,7 +159,7 @@ K.transform = final living_target.adjustFireLoss(30) living_target.adjust_fire_stacks(0.2)//Just here for the showmanship - living_target.IgniteMob() + living_target.ignite_mob() playsound(living_target,'sound/weapons/sear.ogg', 50, 1) addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 5) return diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm index 8212cc10acf2..f8580f1963d4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -65,21 +65,21 @@ Difficulty: Medium /datum/action/innate/megafauna_attack/dash name = "Dash To Target" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now dashing to your target.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/kinetic_accelerator name = "Fire Kinetic Accelerator" - icon_icon = 'icons/obj/guns/energy.dmi' + button_icon = 'icons/obj/guns/energy.dmi' button_icon_state = "kineticgun" chosen_message = span_colossus("You are now shooting your kinetic accelerator.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/transform_weapon name = "Transform Weapon" - icon_icon = 'icons/obj/lavaland/artefacts.dmi' + button_icon = 'icons/obj/lavaland/artefacts.dmi' button_icon_state = "cleaving_saw" chosen_message = span_colossus("You are now transforming your weapon.") chosen_attack_num = 3 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 8f60b2dddecc..a5d76fa0f6cb 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -79,28 +79,28 @@ Difficulty: Hard /datum/action/innate/megafauna_attack/triple_charge name = "Triple Charge" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now triple charging at the target you click on.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/hallucination_charge name = "Hallucination Charge" - icon_icon = 'icons/effects/bubblegum.dmi' + button_icon = 'icons/effects/bubblegum.dmi' button_icon_state = "smack ya one" chosen_message = span_colossus("You are now charging with hallucinations at the target you click on.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/hallucination_surround name = "Surround Target" - icon_icon = 'icons/turf/walls/wall.dmi' + button_icon = 'icons/turf/walls/wall.dmi' button_icon_state = "wall" chosen_message = span_colossus("You are now surrounding the target you click on with hallucinations.") chosen_attack_num = 3 /datum/action/innate/megafauna_attack/blood_warp name = "Blood Warp" - icon_icon = 'icons/effects/blood.dmi' + button_icon = 'icons/effects/blood.dmi' button_icon_state = "floor1" chosen_message = span_colossus("You are now warping to blood around your clicked position.") chosen_attack_num = 4 @@ -401,7 +401,7 @@ Difficulty: Hard desc = "You're not quite sure how a signal can be bloody." invisibility = 100 -/mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) . = ..() if(. > 0 && prob(25)) var/obj/effect/decal/cleanable/blood/gibs/bubblegum/B = new /obj/effect/decal/cleanable/blood/gibs/bubblegum(loc) @@ -549,7 +549,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Life() return -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) return /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/OpenFire() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index cef398b33295..dd7d5fba6cb7 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -58,28 +58,28 @@ Difficulty: Very Hard /datum/action/innate/megafauna_attack/spiral_attack name = "Spiral Shots" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now firing in a spiral.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/aoe_attack name = "All Directions" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "at_shield2" chosen_message = span_colossus("You are now firing in all directions.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/shotgun name = "Shotgun Fire" - icon_icon = 'icons/obj/guns/projectile.dmi' + button_icon = 'icons/obj/guns/projectile.dmi' button_icon_state = "shotgun" chosen_message = span_colossus("You are now firing shotgun shots where you aim.") chosen_attack_num = 3 /datum/action/innate/megafauna_attack/alternating_cardinals name = "Alternating Shots" - icon_icon = 'icons/obj/guns/projectile.dmi' + button_icon = 'icons/obj/guns/projectile.dmi' button_icon_state = "pistol" chosen_message = span_colossus("You are now firing in alternating cardinal directions.") chosen_attack_num = 4 @@ -242,7 +242,7 @@ Difficulty: Very Hard /obj/effect/temp_visual/at_shield/Initialize(mapload, new_target) . = ..() target = new_target - INVOKE_ASYNC(src, /atom/movable/proc/orbit, target, 0, FALSE, 0, 0, FALSE, TRUE) + INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable/, orbit), target, 0, FALSE, 0, 0, FALSE, TRUE) /mob/living/simple_animal/hostile/megafauna/colossus/bullet_act(obj/item/projectile/P) if(!stat) @@ -691,7 +691,7 @@ Difficulty: Very Hard remove_verb(src, /mob/living/verb/pulled) remove_verb(src, /mob/verb/me_verb) var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medsensor.add_hud_to(src) + medsensor.show_to(src) /mob/living/simple_animal/hostile/lightgeist/ghostize() . = ..() @@ -726,7 +726,8 @@ Difficulty: Very Hard /mob/living/simple_animal/hostile/lightgeist/healing/photogeist/Initialize() . = ..() - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/plants) + var/datum/action/cooldown/spell/conjure/plants/terrarium = new(src) + terrarium.Grant(src) /mob/living/simple_animal/hostile/lightgeist/healing/photogeist/AttackingTarget() //photogeists can only heal plantlike stuff var/mob/living/L = target @@ -734,15 +735,15 @@ Difficulty: Very Hard return FALSE . = ..() -/obj/effect/proc_holder/spell/aoe_turf/conjure/plants +/datum/action/cooldown/spell/conjure/plants name = "Seed Plants" desc = "This spell seeds a random plant into the floor." - school = "conjuration" - charge_max = 200 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = 0 + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "plant" + + invocation_type = INVOCATION_NONE + + cooldown_time = 20 SECONDS summon_type = list( /obj/structure/flora/ausbushes, /obj/structure/flora/ausbushes/leafybush, @@ -752,8 +753,7 @@ Difficulty: Very Hard /obj/structure/flora/ausbushes/ppflowers, /obj/structure/flora/ausbushes/fullgrass ) - action_icon = 'icons/mob/actions/actions_animal.dmi' - action_icon_state = "plant" + spell_requirements = NONE /obj/effect/mob_spawn/photogeist name = "dormant photogeist" @@ -853,8 +853,8 @@ Difficulty: Very Hard ADD_TRAIT(L, TRAIT_MUTE, STASIS_MUTE) L.status_flags |= GODMODE L.mind.transfer_to(holder_animal) - var/obj/effect/proc_holder/spell/targeted/exit_possession/P = new /obj/effect/proc_holder/spell/targeted/exit_possession - holder_animal.mind.AddSpell(P) + var/datum/action/exit_possession/escape = new(holder_animal) + escape.Grant(holder_animal) remove_verb(holder_animal, /mob/living/verb/pulled) /obj/structure/closet/stasis/dump_contents(var/kill = 1) @@ -865,7 +865,7 @@ Difficulty: Very Hard L.notransform = 0 if(holder_animal) holder_animal.mind.transfer_to(L) - L.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession) + holder_animal.gib() if(kill || !isanimal(loc)) L.death(0) ..() @@ -876,32 +876,28 @@ Difficulty: Very Hard /obj/structure/closet/stasis/ex_act() return -/obj/effect/proc_holder/spell/targeted/exit_possession +/datum/action/exit_possession name = "Exit Possession" - desc = "Exits the body you are possessing." - charge_max = 60 - clothes_req = 0 - invocation_type = SPELL_INVOCATION_NONE - max_targets = 1 - range = -1 - include_user = TRUE - selection_type = "view" - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "exit_possession" - sound = null - -/obj/effect/proc_holder/spell/targeted/exit_possession/cast(list/targets, mob/user = usr) - if(!isfloorturf(user.loc)) - return - var/datum/mind/target_mind = user.mind - for(var/i in user) - if(istype(i, /obj/structure/closet/stasis)) - var/obj/structure/closet/stasis/S = i - S.dump_contents(0) - qdel(S) - break - user.gib() - target_mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession) + desc = "Exits the body you are possessing. They will explode violently when this occurs." + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "exit_possession" + +/datum/action/exit_possession/IsAvailable(feedback = FALSE) + return ..() && isfloorturf(owner.loc) + + +/datum/action/exit_possession/Trigger(trigger_flags) + . = ..() + if(!.) + return FALSE + + var/obj/structure/closet/stasis/stasis = locate() in owner + if(!stasis) + CRASH("[type] did not find a stasis closet thing in the owner.") + + stasis.dump_contents(FALSE) + qdel(stasis) + qdel(src) #undef ACTIVATE_TOUCH diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 6716ca88bad8..12fac8b01818 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -48,21 +48,21 @@ Difficulty: Extremely Hard /datum/action/innate/megafauna_attack/frost_orbs name = "Fire Frost Orbs" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now sending out frost orbs to track in on a target.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/snowball_machine_gun name = "Fire Snowball Machine Gun" - icon_icon = 'icons/obj/guns/energy.dmi' + button_icon = 'icons/obj/guns/energy.dmi' button_icon_state = "kineticgun" chosen_message = span_colossus("You are now firing a snowball machine gun at a target.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/ice_shotgun name = "Fire Ice Shotgun" - icon_icon = 'icons/obj/guns/projectile.dmi' + button_icon = 'icons/obj/guns/projectile.dmi' button_icon_state = "shotgun" chosen_message = span_colossus("You are now firing shotgun ice blasts.") chosen_attack_num = 3 @@ -284,7 +284,7 @@ Difficulty: Extremely Hard /obj/item/clothing/shoes/winterboots/ice_boots/speedy/Initialize() . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) + ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT(type)) /obj/item/pickaxe/drill/jackhammer/demonic name = "demonic jackhammer" diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index d786d8e87c10..3d987af9dea5 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -73,28 +73,28 @@ Difficulty: Medium /datum/action/innate/megafauna_attack/fire_cone name = "Fire Cone" - icon_icon = 'icons/obj/wizard.dmi' + button_icon = 'icons/obj/wizard.dmi' button_icon_state = "fireball" chosen_message = span_colossus("You are now shooting fire at your target.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/fire_cone_meteors name = "Fire Cone With Meteors" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now shooting fire at your target and raining fire around you.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/mass_fire name = "Mass Fire Attack" - icon_icon = 'icons/effects/fire.dmi' + button_icon = 'icons/effects/fire.dmi' button_icon_state = "1" chosen_message = span_colossus("You are now shooting mass fire at your target.") chosen_attack_num = 3 /datum/action/innate/megafauna_attack/lava_swoop name = "Lava Swoop" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "lavastaff_warn" chosen_message = span_colossus("You are now swooping and raining lava at your target.") chosen_attack_num = 4 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 0ebc4cac3a0b..7cf789c320af 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -90,28 +90,28 @@ Difficulty: Hard /datum/action/innate/megafauna_attack/blink name = "Blink To Target" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now blinking to your target.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/chaser_swarm name = "Chaser Swarm" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "hierophant_squares_indefinite" chosen_message = span_colossus("You are firing a chaser swarm at your target.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/cross_blasts name = "Cross Blasts" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "hierophant_blast_indefinite" chosen_message = span_colossus("You are now firing cross blasts at your target.") chosen_attack_num = 3 /datum/action/innate/megafauna_attack/blink_spam name = "Blink Chase" - icon_icon = 'icons/obj/lavaland/artefacts.dmi' + button_icon = 'icons/obj/lavaland/artefacts.dmi' button_icon_state = "hierophant_club_ready_beacon" chosen_message = span_colossus("You are now repeatedly blinking at your target.") chosen_attack_num = 4 @@ -403,14 +403,14 @@ Difficulty: Hard var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) if(D) D.adjust_money(maxHealth * MEGAFAUNA_CASH_SCALE) - stat = DEAD + set_stat(DEAD) blinking = TRUE //we do a fancy animation, release a huge burst(), and leave our staff. visible_message(span_hierophant("\"Mrmxmexmrk wipj-hiwxvygx wiuyirgi...\"")) visible_message("[span_hierophant_warning("[src] shrinks, releasing a massive burst of energy!")]") for(var/mob/living/L in view(7,src)) stored_nearby += L // store the people to grant the achievements to once we die hierophant_burst(null, get_turf(src), 10) - stat = CONSCIOUS // deathgasp wont run if dead, stupid + set_stat(CONSCIOUS) // deathgasp wont run if dead, stupid ..(force_grant = stored_nearby) /mob/living/simple_animal/hostile/megafauna/hierophant/Destroy() @@ -728,7 +728,7 @@ Difficulty: Hard new /obj/effect/temp_visual/hierophant/telegraph/teleport(get_turf(src), user) to_chat(user, "[span_hierophant_warning("You collect [src], reattaching it to the club!")]") H.beacon = null - user.update_action_buttons_icon() + user.update_mob_action_buttons() qdel(src) else H.timer = world.time diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm index 662a5eaa3109..711b67818090 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm @@ -55,14 +55,14 @@ Difficulty: Medium /datum/action/innate/megafauna_attack/create_skull name = "Create Legion Skull" - icon_icon = 'icons/mob/lavaland/lavaland_monsters.dmi' + button_icon = 'icons/mob/lavaland/lavaland_monsters.dmi' button_icon_state = "legion_head" chosen_message = span_colossus("You are now creating legion skulls.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/charge_target name = "Charge Target" - icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" chosen_message = span_colossus("You are now charging at your target.") chosen_attack_num = 2 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index 96a92ce025e1..f05c979b9813 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -141,7 +141,7 @@ /datum/action/innate/megafauna_attack name = "Megafauna Attack" - icon_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon = 'icons/mob/actions/actions_animal.dmi' button_icon_state = "" var/mob/living/simple_animal/hostile/megafauna/M var/chosen_message diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm index 4c0120ef92f9..efbfdf8c5343 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm @@ -38,35 +38,35 @@ /datum/action/innate/megafauna_attack/spiralpikes name = "Resonant Spiral" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "shield" chosen_message = span_boldannounce("You are now firing in a spiral.") chosen_attack_num = 1 /datum/action/innate/megafauna_attack/cardinalpikes name = "Cardinal Pikes" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "launchpad_target" chosen_message = span_boldannounce("You are now firing in 8 directions.") chosen_attack_num = 2 /datum/action/innate/megafauna_attack/backup name = "Warp Mini Mechanoid" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "curse" chosen_message = span_boldannounce("You are now summoning allies.") chosen_attack_num = 3 /datum/action/innate/megafauna_attack/stalnade name = "Volatile Orb Cone" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "m_shield" chosen_message = span_boldannounce("You are now firing a cone of slow, high damaging projectiles.") chosen_attack_num = 4 /datum/action/innate/megafauna_attack/stalnadespiral name = "Volatile Orb Spiral" - icon_icon = 'icons/effects/effects.dmi' + button_icon = 'icons/effects/effects.dmi' button_icon_state = "shield-old" chosen_message = span_boldannounce("You are now firing a spiral of slow, high damaging projectiles.") chosen_attack_num = 5 diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm index 670283b278db..8a76e5f81e31 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm @@ -139,7 +139,7 @@ var/mob/living/L = target if (istype(L)) L.adjust_fire_stacks(0.1) - L.IgniteMob() + L.ignite_mob() /obj/item/projectile/temp/basilisk/icewing damage = 5 diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm index 57e42bd9c6bc..5f87ea6dd28c 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm @@ -36,8 +36,8 @@ buckle_lying = 0 var/attack_cooldown = 0 var/list/food_items = list(/obj/item/reagent_containers/food/snacks/meat/slab/goliath = 20, /obj/item/reagent_containers/food/snacks/meat/steak/goliath = 40) - var/list/action_types = list(/obj/effect/proc_holder/drakeling/fire_breath, /obj/effect/proc_holder/drakeling/wing_flap) - var/list/obj/effect/proc_holder/drakeling/dragon_actions = list() + var/list/action_types = list(/datum/action/cooldown/spell/pointed/drakeling/fire_breath, /datum/action/cooldown/spell/pointed/drakeling/wing_flap) + var/list/dragon_actions = list() var/grinding = FALSE var/datum/action/drake_ollie/dollie @@ -52,9 +52,9 @@ D.vehicle_move_delay = 1 dollie = new dollie.dragon = src - for(var/action_type in action_types) - var/obj/effect/proc_holder/drakeling/attack_action = new action_type - AddAbility(attack_action) + for(var/datum/action/cooldown/spell/pointed/drakeling/attack_action in action_types) + attack_action = new(src) + attack_action.Grant(src) dragon_actions |= attack_action attack_action.drake = src RegisterSignal(src, COMSIG_MOVABLE_BUCKLE, PROC_REF(give_abilities)) @@ -62,25 +62,23 @@ /mob/living/simple_animal/hostile/drakeling/proc/give_abilities(mob/living/drake, mob/living/M, force = FALSE) toggle_ai(AI_OFF) - if(istype(click_intercept, /obj/effect/proc_holder/drakeling)) - var/obj/effect/proc_holder/drakeling/D = click_intercept - D.remove_ranged_ability() + if(istype(click_intercept, /datum/action/cooldown/spell/pointed/drakeling)) + var/datum/action/cooldown/spell/pointed/drakeling/D = click_intercept + D.unset_click_ability(D.owner) dollie.Grant(M) - for(var/action in dragon_actions) - var/obj/effect/proc_holder/drakeling/attack_action = action - RemoveAbility(attack_action) - M.AddAbility(attack_action) + for(var/datum/action/cooldown/spell/pointed/drakeling/attack_action as anything in dragon_actions) + attack_action.Remove(src) + attack_action.Grant(M) /mob/living/simple_animal/hostile/drakeling/proc/remove_abilities(mob/living/drake, mob/living/M, force = FALSE) toggle_ai(AI_ON) - if(istype(M.click_intercept, /obj/effect/proc_holder/drakeling)) - var/obj/effect/proc_holder/drakeling/D = M.click_intercept - D.remove_ranged_ability() + if(istype(M.click_intercept, /datum/action/cooldown/spell/pointed/drakeling)) + var/datum/action/cooldown/spell/pointed/drakeling/D = M.click_intercept + D.unset_click_ability(D.owner) dollie.Remove(M) - for(var/action in dragon_actions) - var/obj/effect/proc_holder/drakeling/attack_action = action - M.RemoveAbility(attack_action) - AddAbility(attack_action) + for(var/datum/action/cooldown/spell/pointed/drakeling/attack_action as anything in dragon_actions) + attack_action.Remove(M) + attack_action.Grant(src) /mob/living/simple_animal/hostile/drakeling/attackby(obj/item/O, mob/user, params) if(istype(O, /obj/item/clothing/neck/petcollar)) @@ -108,82 +106,54 @@ -/obj/effect/proc_holder/drakeling +/datum/action/cooldown/spell/pointed/drakeling name = "ULTRA DRAGON ATTACK" desc = "if you can see this something has probably gone very wrong and you should make a bug report." - var/mob/living/simple_animal/hostile/drakeling/drake - var/cooldown = 5 SECONDS - action_background_icon_state = "bg_demon" + background_icon_state = "bg_demon" panel = "Dragon" - active = FALSE - var/prepare_message = span_notice("You prepare %YOUR ULTRA DRAGON ATTACK") - var/unprepare_message = span_notice("You decide to spare the mortals for now...") -/obj/effect/proc_holder/drakeling/Click() - if(!isliving(usr)) - return TRUE - var/mob/living/user = usr - if(can_cast(user)) - fire(user) - return TRUE + cooldown_time = 5 SECONDS + active_msg = span_notice("You prepare %YOUR ULTRA DRAGON ATTACK") + deactive_msg = span_notice("You decide to spare the mortals for now...") + spell_requirements = NONE + var/mob/living/simple_animal/hostile/drakeling/drake -/obj/effect/proc_holder/drakeling/proc/can_cast(mob/living/L) - . = TRUE - if(L.stat) - to_chat(L, "You must be conscious to do this!") - return FALSE - if(L.IsStun() || L.IsParalyzed()) - to_chat(L, "You can't [L == drake ? "use your attacks" : "direct your dragon"] while you're stunned!") - return FALSE - if(L.restrained()) - to_chat(L, "You can't [L == drake ? "use your attacks" : "direct your dragon"] while you're restrained!") +/datum/action/cooldown/spell/pointed/drakeling/InterceptClickOn(mob/living/caller, params, atom/target) + . = ..() + if(!.) return FALSE - -/obj/effect/proc_holder/drakeling/fire(mob/living/user) - var/msg - if(!active) - msg = replacetext(prepare_message, "%YOUR", "[user == drake ? "your" : "to direct your dragon's"]") - add_ranged_ability(usr, msg, TRUE) - else - msg = replacetext(unprepare_message, "%YOUR", "[user == drake ? "your" : "to direct your dragon's"]") - remove_ranged_ability(msg) - - -/obj/effect/proc_holder/drakeling/InterceptClickOn(mob/living/L, params, atom/A) - if(..()) - return TRUE - drake.face_atom(A) + drake.face_atom(target) if(drake.attack_cooldown > world.time) - to_chat(L, "Your[L == drake ? "" : " dragon's"] attack is not ready yet!") + to_chat(owner, "Your[owner == drake ? "" : " dragon's"] attack is not ready yet!") return TRUE - if(!can_cast(L)) - remove_ranged_ability(L) - return TRUE - drake.attack_cooldown = cooldown + world.time - addtimer(CALLBACK(src, PROC_REF(cooldown_over), L), cooldown) - if(L != drake) - addtimer(CALLBACK(src, PROC_REF(cooldown_over), drake), cooldown) + drake.attack_cooldown = cooldown_time + world.time + addtimer(CALLBACK(src, PROC_REF(cooldown_over), owner), cooldown_time) + if(owner != drake) + addtimer(CALLBACK(src, .PROC_REF(cooldown_over), drake), cooldown_time) -/obj/effect/proc_holder/drakeling/proc/cooldown_over(mob/living/L) + return TRUE + +/datum/action/cooldown/spell/pointed/drakeling/proc/cooldown_over(mob/living/L) to_chat(L, "You[L == drake ? "are" : "r dragon is"] ready for another attack!") -/obj/effect/proc_holder/drakeling/Destroy() +/datum/action/cooldown/spell/pointed/drakeling/Destroy() drake = null return ..() ///drakeling fire breath attack: shoots a short line of fire that is very effective against lavaland fauna and not very effective against much else -/obj/effect/proc_holder/drakeling/fire_breath +/datum/action/cooldown/spell/pointed/drakeling/fire_breath name = "Fire Breath" desc = "Breathe a short flame that is effective against fauna but worthless off of lavaland." - action_icon = 'icons/obj/wizard.dmi' - action_icon_state = "fireball" - cooldown = 1.6 SECONDS //kinetic gun - prepare_message = span_notice("You prepare %YOUR fire breath attack") - unprepare_message = span_notice("You decide to refrain from roasting more peasants for the time.") + button_icon = 'icons/obj/wizard.dmi' + button_icon_state = "fireball" + cooldown_time = 1.6 SECONDS //kinetic gun + active_msg = span_notice("You prepare %YOUR fire breath attack") + deactive_msg = span_notice("You decide to refrain from roasting more peasants for the time.") -/obj/effect/proc_holder/drakeling/fire_breath/InterceptClickOn(mob/living/L, params, atom/A) - if(..()) - return TRUE +/datum/action/cooldown/spell/pointed/drakeling/fire_breath/InterceptClickOn(mob/living/L, params, atom/A) + . = ..() + if(!.) + return FALSE playsound(get_turf(drake),'sound/magic/fireball.ogg', 100, 1) var/turf/T = get_turf(drake) var/range = is_mining_level(T.z) ? 4 : 1 //1 tile range means it tends to be incapable of firing diagonally but if it's any longer it's "not a melee weapon" @@ -193,6 +163,8 @@ turfs = drake.line_target(range, A) INVOKE_ASYNC(src, PROC_REF(drakeling_fire_line), drake, turfs, damage, protected) + return TRUE + ///gets the list of turfs the fire breath attack hits /mob/living/simple_animal/hostile/drakeling/proc/line_target(var/range, var/atom/at) if(!at) @@ -207,7 +179,7 @@ return (getline(src, T) - get_turf(src)) ///actual bit that shoots fire for the fire breath attack -/obj/effect/proc_holder/drakeling/proc/drakeling_fire_line(var/source, var/list/turfs, var/damage, var/list/protected) +/datum/action/cooldown/spell/pointed/drakeling/proc/drakeling_fire_line(var/source, var/list/turfs, var/damage, var/list/protected) var/list/hit_list = list() for(var/turf/T in turfs) if(istype(T, /turf/closed)) @@ -225,19 +197,20 @@ sleep(0.1 SECONDS) ///drakeling wing flap attack: deals relatively minor damage to lavaland fauna and pushes anything it hits away, also breaks rocks on contact like a plasmacutter -/obj/effect/proc_holder/drakeling/wing_flap +/datum/action/cooldown/spell/pointed/drakeling/wing_flap name = "Wing Flap" desc = "Causes a large, powerful gust of air to push stuff away, deal damage to fauna, and break rocks." - action_icon_state = "tornado" - action_icon = 'icons/obj/wizard.dmi' - cooldown = 1 SECONDS //adv cutter - prepare_message = span_notice("You prepare %YOUR wings.") - unprepare_message = span_notice("You stop the flapping.") + button_icon_state = "tornado" + button_icon = 'icons/obj/wizard.dmi' + cooldown_time = 1 SECONDS //adv cutter + active_msg = span_notice("You prepare %YOUR wings.") + deactive_msg = span_notice("You stop the flapping.") var/shootie = /obj/item/projectile/wing -/obj/effect/proc_holder/drakeling/wing_flap/InterceptClickOn(mob/living/L, params, atom/A) - if(..()) - return TRUE +/datum/action/cooldown/spell/pointed/drakeling/wing_flap/InterceptClickOn(mob/living/L, params, atom/A) + . = ..() + if(!.) + return FALSE playsound(get_turf(drake),'sound/magic/repulse.ogg', 100, 1) var/list/shooties = list() shooties += new shootie(get_turf(drake)) @@ -252,6 +225,8 @@ shooted.firer = L shooted.fire(dir2angle(drake.dir)) + return TRUE + /obj/item/projectile/wing name = "wing blast" damage = 0 @@ -289,7 +264,7 @@ /datum/action/drake_ollie name = "DRAGON Ollie" desc = "This seems like a REALLY COOL IDEA" - icon_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon = 'icons/mob/actions/actions_animal.dmi' button_icon_state = "dragon_ollie" //cooldown to next jump var/next_ollie diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm index 2385cbb93864..17afeea61abd 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm @@ -71,22 +71,54 @@ While using this makes the system rely on OnFire, it still gives options for tim /datum/action/innate/elite_attack name = "Elite Attack" - icon_icon = 'icons/mob/actions/actions_elites.dmi' + button_icon = 'icons/mob/actions/actions_elites.dmi' button_icon_state = "" background_icon_state = "bg_default" - var/mob/living/simple_animal/hostile/asteroid/elite/M + overlay_icon_state = "bg_default_border" + ///The displayed message into chat when this attack is selected var/chosen_message + ///The internal attack ID for the elite's OpenFire() proc to use var/chosen_attack_num = 0 +/datum/action/innate/elite_attack/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/innate/elite_attack/process() + if(isnull(owner)) + STOP_PROCESSING(SSfastprocess, src) + qdel(src) + return + + build_all_button_icons(UPDATE_BUTTON_STATUS) + +/datum/action/innate/elite_attack/update_button_status(atom/movable/screen/movable/action_button/button, force = FALSE) + var/mob/living/simple_animal/hostile/asteroid/elite/elite_owner = owner + if(!istype(owner)) + button.maptext = "" + return + + var/timeleft = max(elite_owner.ranged_cooldown - world.time, 0) + if(timeleft == 0) + button.maptext = "" + else + button.maptext = MAPTEXT("[round(timeleft/10, 0.1)]") + /datum/action/innate/elite_attack/Grant(mob/living/L) if(istype(L, /mob/living/simple_animal/hostile/asteroid/elite)) - M = L + START_PROCESSING(SSfastprocess, src) return ..() return FALSE /datum/action/innate/elite_attack/Activate() - M.chosen_attack = chosen_attack_num - to_chat(M, chosen_message) + var/mob/living/simple_animal/hostile/asteroid/elite/elite_owner = owner + elite_owner.chosen_attack = chosen_attack_num + to_chat(elite_owner, chosen_message) /mob/living/simple_animal/hostile/asteroid/elite/updatehealth() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm index 74a6ebb9436e..444487a7bc50 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm @@ -275,7 +275,7 @@ if(isliving(mover)) var/mob/living/L = mover L.adjust_fire_stacks(3) - L.IgniteMob() + L.ignite_mob() . = ..() /obj/structure/legionnaire_bonfire/Destroy() @@ -355,18 +355,18 @@ /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/attendant/proc/give_abilities(mob/living/elite, mob/living/M, force = FALSE) toggle_ai(AI_OFF) - if(istype(click_intercept, /obj/effect/proc_holder/drakeling)) - var/obj/effect/proc_holder/drakeling/D = click_intercept - D.remove_ranged_ability() - for(var/action in attack_action_types) - RemoveAbility(action) - M.AddAbility(action) + if(istype(click_intercept, /datum/action/cooldown/spell/pointed/drakeling)) + var/datum/action/cooldown/spell/pointed/drakeling/D = click_intercept + D.unset_click_ability(D.owner) + for(var/datum/action/action in attack_action_types) + action.Remove(src) + action.Grant(M) /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/attendant/proc/remove_abilities(mob/living/elite, mob/living/M, force = FALSE) toggle_ai(AI_ON) - if(istype(M.click_intercept, /obj/effect/proc_holder/drakeling)) - var/obj/effect/proc_holder/drakeling/D = M.click_intercept - D.remove_ranged_ability() - for(var/action in attack_action_types) - M.RemoveAbility(action) - AddAbility(action) + if(istype(M.click_intercept, /datum/action/cooldown/spell/pointed/drakeling)) + var/datum/action/cooldown/spell/pointed/drakeling/D = M.click_intercept + D.unset_click_ability(D.owner) + for(var/datum/action/action in attack_action_types) + action.Remove(M) + action.Grant(src) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm index 9a8427f018b3..5c9048b455cd 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm @@ -52,15 +52,15 @@ retreat_distance = 10 minimum_distance = 10 if(will_burrow) - addtimer(CALLBACK(src, PROC_REF(Burrow)), chase_time) + addtimer(CALLBACK(src, PROC_REF(burrow)), chase_time) /mob/living/simple_animal/hostile/asteroid/goldgrub/AttackingTarget() if(wanted_objects[target.type]) - EatOre(target) + eat_ore(target) return return ..() -/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/EatOre(atom/targeted_ore) +/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/eat_ore(atom/targeted_ore) var/obj/item/stack/ore/O = targeted_ore if(length(loot) < max_loot) var/using = min(max_loot - length(loot), O.amount) @@ -72,7 +72,7 @@ search_objects = 0 visible_message(span_notice("\The [name] nibbles some of the ore and then stops. \She seems to be full!")) -/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/Burrow()//You failed the chase to kill the goldgrub in time! +/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/burrow()//You failed the chase to kill the goldgrub in time! if(stat == CONSCIOUS) visible_message(span_danger("\The [name] buries into the ground, vanishing from sight!")) qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm index f2171a7983c1..84ef2136b5ae 100644 --- a/code/modules/mob/living/simple_animal/hostile/regalrat.dm +++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm @@ -25,7 +25,6 @@ unique_name = TRUE faction = list("rat") lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - var/datum/action/cooldown/coffer var/datum/action/cooldown/riot var/datum/action/cooldown/domain var/opening_airlock = FALSE @@ -33,10 +32,8 @@ /mob/living/simple_animal/hostile/regalrat/Initialize() . = ..() - coffer = new /datum/action/cooldown/coffer riot = new /datum/action/cooldown/riot domain = new /datum/action/cooldown/domain - coffer.Grant(src) riot.Grant(src) domain.Grant(src) var/kingdom = pick("Plague","Miasma","Maintenance","Trash","Garbage","Rat","Vermin","Cheese") @@ -47,8 +44,6 @@ /mob/living/simple_animal/hostile/regalrat/handle_automated_action() if(prob(20)) riot.Trigger() - else if(prob(50)) - coffer.Trigger() return ..() /mob/living/simple_animal/hostile/regalrat/CanAttack(atom/the_target) @@ -83,53 +78,6 @@ if(miasma_percentage>=0.25) heal_bodypart_damage(1) -/** - *This action creates trash, money, dirt, and cheese. - */ -/datum/action/cooldown/coffer - name = "Fill Coffers" - desc = "Your newly granted regality and poise let you scavenge for lost junk, but more importantly, cheese." - icon_icon = 'icons/mob/actions/actions_ratking.dmi' - background_icon_state = "bg_clock" - button_icon_state = "coffer" - cooldown_time = 50 - -/datum/action/cooldown/coffer/Trigger() - . = ..() - if(!.) - return - var/turf/T = owner.loc - if(!istype(T)) - to_chat(owner, "There is no cheese in here!") - return - var/loot = rand(1,100) - switch(loot) - if(1 to 5) - to_chat(owner, "Score! You find some cheese!") - var/cheesetype = pick(subtypesof(/obj/item/reagent_containers/food/snacks/cheesewedge) - /obj/item/reagent_containers/food/snacks/cheesewedge/cheddar/custom) - new cheesetype(T) - if(6 to 10) - var/pickedcoin = pick(GLOB.ratking_coins) - to_chat(owner, "You find some leftover coins. More for the royal treasury!") - for(var/i = 1 to rand(1,3)) - new pickedcoin(T) - if(11) - to_chat(owner, "You find a... Hunh. This coin doesn't look right.") - var/rarecoin = rand(1,2) - if (rarecoin == 1) - new /obj/item/coin/twoheaded(T) - else - new /obj/item/coin/antagtoken(T) - if(12 to 40) - var/pickedtrash = pick(GLOB.ratking_trash) - to_chat(owner, "You just find more garbage and dirt. Lovely, but beneath you now.") - new /obj/effect/decal/cleanable/dirt(T) - new pickedtrash(T) - if(41 to 100) - to_chat(owner, "Drat. Nothing.") - new /obj/effect/decal/cleanable/dirt(T) - StartCooldown() - /** *This action checks all nearby mice, and converts them into hostile rats. If no mice are nearby, creates a new one. */ @@ -137,11 +85,29 @@ /datum/action/cooldown/riot name = "Raise Army" desc = "Raise an army out of the hordes of mice and pests crawling around the maintenance shafts." - icon_icon = 'icons/mob/actions/actions_ratking.dmi' + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED + button_icon = 'icons/mob/actions/actions_animal.dmi' button_icon_state = "riot" background_icon_state = "bg_clock" - cooldown_time = 80 - ///Checks to see if there are any nearby mice. Does not count Rats. + overlay_icon_state = "bg_clock_border" + cooldown_time = 8 SECONDS + melee_cooldown_time = 0 SECONDS + /// How close does something need to be for us to recruit it? + var/range = 5 + /// Commands you can give to your mouse army +// var/static/list/mouse_commands = list( +// /datum/pet_command/idle, +// /datum/pet_command/free, +// /datum/pet_command/follow, +// /datum/pet_command/point_targetting/attack/mouse +// ) + /// Commands you can give to glockroaches +// var/static/list/glockroach_commands = list( +// /datum/pet_command/idle, +// /datum/pet_command/free, +// /datum/pet_command/follow, +// /datum/pet_command/point_targetting/attack/glockroach +// ) /datum/action/cooldown/riot/Trigger() . = ..() @@ -183,17 +149,16 @@ /datum/action/cooldown/domain name = "Rat King's Domain" desc = "Corrupts this area to be more suitable for your rat army." - check_flags = AB_CHECK_CONSCIOUS - cooldown_time = 10 SECONDS - icon_icon = 'icons/mob/actions/actions_spells.dmi' + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED + cooldown_time = 6 SECONDS + melee_cooldown_time = 0 SECONDS + button_icon = 'icons/mob/actions/actions_animal.dmi' background_icon_state = "bg_clock" - button_icon_state = "smoke" + overlay_icon_state = "bg_clock_border" + button_icon_state = "coffer" -/datum/action/cooldown/domain/Trigger() - var/turf/T = owner.loc - if(!istype(T)) - to_chat(owner, "Building our domain here is for cowards!") - return FALSE +/datum/action/cooldown/domain/proc/domain() + var/turf/T = get_turf(owner) T.atmos_spawn_air("miasma=4;TEMP=[T20C]") switch (rand(1,10)) if (8) @@ -206,6 +171,11 @@ new /obj/effect/decal/cleanable/dirt(T) StartCooldown() +/datum/action/cooldown/domain/Activate(atom/target) + StartCooldown(10 SECONDS) + domain() + StartCooldown() + #define REGALRAT_INTERACTION "regalrat" /mob/living/simple_animal/hostile/regalrat/AttackingTarget() if (DOING_INTERACTION(src, REGALRAT_INTERACTION)) diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index d0025c3876d3..e8d5f243f870 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -68,7 +68,7 @@ /// Whether or not Space Dragon has completed their objective, and thus triggered the ending sequence. var/objective_complete = FALSE /// The togglable small sprite action - var/small_sprite_type = /datum/action/small_sprite/megafauna/spacedragon + var/small_sprite_type = /datum/action/small_sprite/space_dragon /// The innate ability to use wing gust var/datum/action/innate/space_dragon/gustAttack/gust /// The innate ability to summon rifts @@ -366,7 +366,7 @@ mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/T) /datum/action/innate/space_dragon background_icon_state = "bg_default" - icon_icon = 'icons/mob/actions/actions_space_dragon.dmi' + button_icon = 'icons/mob/actions/actions_space_dragon.dmi' /datum/action/innate/space_dragon/gustAttack name = "Gust Attack" diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm index b9ad906f85c2..de2d91a635c1 100644 --- a/code/modules/mob/living/simple_animal/hostile/statue.dm +++ b/code/modules/mob/living/simple_animal/hostile/statue.dm @@ -59,9 +59,13 @@ /mob/living/simple_animal/hostile/statue/Initialize(mapload, var/mob/living/creator) . = ..() // Give spells - mob_spell_list += new /obj/effect/proc_holder/spell/aoe_turf/flicker_lights(src) - mob_spell_list += new /obj/effect/proc_holder/spell/aoe_turf/blindness(src) - mob_spell_list += new /obj/effect/proc_holder/spell/targeted/night_vision(src) + + var/datum/action/cooldown/spell/aoe/flicker_lights/flicker = new(src) + flicker.Grant(src) + var/datum/action/cooldown/spell/aoe/blindness/blind = new(src) + blind.Grant(src) + var/datum/action/cooldown/spell/night_vision/night_vision = new(src) + night_vision.Grant(src) // Set creator if(creator) @@ -164,65 +168,52 @@ // Statue powers // Flicker lights -/obj/effect/proc_holder/spell/aoe_turf/flicker_lights +/datum/action/cooldown/spell/aoe/flicker_lights name = "Flicker Lights" desc = "You will trigger a large amount of lights around you to flicker." - charge_max = 300 - clothes_req = 0 - range = 14 + cooldown_time = 30 SECONDS + spell_requirements = NONE + aoe_radius = 14 + +/datum/action/cooldown/spell/aoe/flicker_lights/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/obj/machinery/light/nearby_light in range(aoe_radius, center)) + if(!nearby_light.on) + continue + + things += nearby_light -/obj/effect/proc_holder/spell/aoe_turf/flicker_lights/cast(list/targets,mob/user = usr) - for(var/turf/T in targets) - for(var/obj/machinery/light/L in T) - L.flicker() - return + return things + +/datum/action/cooldown/spell/aoe/flicker_lights/cast_on_thing_in_aoe(obj/machinery/light/victim, atom/caster) + victim.flicker() //Blind AOE -/obj/effect/proc_holder/spell/aoe_turf/blindness +/datum/action/cooldown/spell/aoe/blindness name = "Blindness" desc = "Your prey will be momentarily blind for you to advance on them." - message = span_notice("You glare your eyes.") - charge_max = 600 - clothes_req = 0 - range = 10 - -/obj/effect/proc_holder/spell/aoe_turf/blindness/cast(list/targets,mob/user = usr) - for(var/mob/living/L in GLOB.alive_mob_list) - var/turf/T = get_turf(L.loc) - if(T && (T in targets)) - L.blind_eyes(4) - return - -//Toggle Night Vision -/obj/effect/proc_holder/spell/targeted/night_vision - name = "Toggle Nightvision \[ON\]" - desc = "Toggle your nightvision mode." - - charge_max = 10 - clothes_req = 0 - - message = span_notice("You toggle your night vision!") - range = -1 - include_user = 1 - -/obj/effect/proc_holder/spell/targeted/night_vision/cast(list/targets, mob/user = usr) - for(var/mob/living/target in targets) - switch(target.lighting_alpha) - if (LIGHTING_PLANE_ALPHA_VISIBLE) - target.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - name = "Toggle Nightvision \[More]" - if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) - target.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - name = "Toggle Nightvision \[Full]" - if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) - target.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - name = "Toggle Nightvision \[OFF]" - else - target.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE - name = "Toggle Nightvision \[ON]" - target.update_sight() + cooldown_time = 1 MINUTES + spell_requirements = NONE + aoe_radius = 14 + +/datum/action/cooldown/spell/aoe/blindness/cast(atom/cast_on) + cast_on.visible_message(span_danger("[cast_on] glares their eyes.")) + return ..() + +/datum/action/cooldown/spell/aoe/blindness/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/nearby_mob in range(aoe_radius, center)) + if(nearby_mob == owner || nearby_mob == center) + continue + things += nearby_mob + + return things + + +/datum/action/cooldown/spell/aoe/blindness/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + victim.blind_eyes(4) /mob/living/simple_animal/hostile/statue/sentience_act() faction -= "neutral" diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm index 4ba398a85400..2527c1d7156b 100644 --- a/code/modules/mob/living/simple_animal/hostile/wizard.dm +++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm @@ -29,12 +29,14 @@ retreat_distance = 3 //out of fireball range minimum_distance = 3 del_on_death = 1 - loot = list(/obj/effect/mob_spawn/human/corpse/wizard, - /obj/item/staff) + loot = list( + /obj/effect/mob_spawn/human/corpse/wizard, + /obj/item/staff, + ) - var/obj/effect/proc_holder/spell/aimed/fireball/fireball = null - var/obj/effect/proc_holder/spell/targeted/turf_teleport/blink/blink = null - var/obj/effect/proc_holder/spell/targeted/projectile/magic_missile/mm = null + var/datum/action/cooldown/spell/pointed/projectile/fireball/fireball + var/datum/action/cooldown/spell/teleport/radius_turf/blink/blink + var/datum/action/cooldown/spell/aoe/magic_missile/magic_missile var/next_cast = 0 @@ -42,42 +44,46 @@ /mob/living/simple_animal/hostile/wizard/Initialize() . = ..() - fireball = new /obj/effect/proc_holder/spell/aimed/fireball - fireball.clothes_req = 0 - fireball.human_req = 0 - fireball.player_lock = 0 - AddSpell(fireball) - implants += new /obj/item/implant/exile(src) - - mm = new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - mm.clothes_req = 0 - mm.human_req = 0 - mm.player_lock = 0 - AddSpell(mm) - - blink = new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - blink.clothes_req = 0 - blink.human_req = 0 - blink.player_lock = 0 + var/obj/item/implant/exile/exiled = new /obj/item/implant/exile(src) + exiled.implant(src) + + fireball = new(src) + fireball.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + fireball.Grant(src) + + magic_missile = new(src) + magic_missile.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + magic_missile.Grant(src) + + blink = new(src) + blink.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) blink.outer_tele_radius = 3 - AddSpell(blink) + blink.Grant(src) + +/mob/living/simple_animal/hostile/wizard/Destroy() + QDEL_NULL(fireball) + QDEL_NULL(magic_missile) + QDEL_NULL(blink) + return ..() /mob/living/simple_animal/hostile/wizard/handle_automated_action() . = ..() if(target && next_cast < world.time) - if((get_dir(src,target) in list(SOUTH,EAST,WEST,NORTH)) && fireball.cast_check(0,src)) //Lined up for fireball - src.setDir(get_dir(src,target)) - fireball.perform(list(target), user = src) - next_cast = world.time + 10 //One spell per second - return . - if(mm.cast_check(0,src)) - mm.choose_targets(src) - next_cast = world.time + 10 - return . - if(blink.cast_check(0,src)) //Spam Blink when you can - blink.choose_targets(src) - next_cast = world.time + 10 - return . + if((get_dir(src, target) in list(SOUTH, EAST, WEST, NORTH)) && fireball.can_cast_spell(FALSE)) + setDir(get_dir(src, target)) + fireball.Trigger(null, target) + next_cast = world.time + 1 SECONDS + return + + if(magic_missile.IsAvailable(feedback = FALSE)) + magic_missile.Trigger(null, target) + next_cast = world.time + 1 SECONDS + return + + if(blink.IsAvailable(feedback = FALSE)) // Spam Blink when you can + blink.Trigger(null, src) + next_cast = world.time + 1 SECONDS + return /mob/living/simple_animal/hostile/academywizard //weaker wizard, only knows arcane barrage. name = "Academy Student" diff --git a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm index 4a7d6c219b6c..57ca75337958 100644 --- a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm +++ b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm @@ -63,7 +63,7 @@ E.Activate() /datum/action/innate/fugu - icon_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon = 'icons/mob/actions/actions_animal.dmi' /datum/action/innate/fugu/expand name = "Inflate" diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 25dc8d4b8437..edd779851c44 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -234,7 +234,7 @@ ears = headset_to_add to_chat(usr, span_notice("You fit the headset onto [src].")) - clearlist(available_channels) + LAZYCLEARLIST(available_channels) for(var/ch in headset_to_add.channels) switch(ch) if(RADIO_CHANNEL_ENGINEERING) diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index e08cf7d21830..1c84c5b1b22e 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -3,9 +3,9 @@ real_name = "Shade" desc = "A bound spirit." gender = PLURAL - icon = 'icons/mob/mob.dmi' - icon_state = "shade" - icon_living = "shade" + icon = 'icons/mob/nonhuman-player/cult.dmi' + icon_state = "shade_cult" + icon_living = "shade_cult" mob_biotypes = list(MOB_SPIRIT) maxHealth = 40 health = 40 @@ -23,6 +23,7 @@ minbodytemp = 0 maxbodytemp = INFINITY atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + speed = -1 //they don't have to lug a body made of runed metal around stop_automated_movement = 1 status_flags = 0 faction = list("cult") diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index c477d5426e42..aeb129cc76b4 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -156,16 +156,9 @@ if(health <= 0) death() else - stat = CONSCIOUS + set_stat(CONSCIOUS) med_hud_set_status() -/mob/living/simple_animal/handle_status_effects() - ..() - if(stuttering) - stuttering = 0 - if(slurring) - slurring = max(slurring-1,0) - /mob/living/simple_animal/proc/handle_automated_action() set waitfor = FALSE return @@ -375,10 +368,10 @@ /mob/living/simple_animal/handle_fire() return TRUE -/mob/living/simple_animal/IgniteMob() +/mob/living/simple_animal/ignite_mob() return FALSE -/mob/living/simple_animal/ExtinguishMob() +/mob/living/simple_animal/extinguish_mob() return /mob/living/simple_animal/revive(full_heal = 0, admin_revive = 0) @@ -457,7 +450,7 @@ walk(src, 0) //stop mid walk update_transform() - update_action_buttons_icon() + update_mob_action_buttons() /mob/living/simple_animal/update_transform() var/matrix/ntransform = matrix(transform) //aka transform.Copy() diff --git a/code/modules/mob/living/simple_animal/slime/death.dm b/code/modules/mob/living/simple_animal/slime/death.dm index 74d68719b2c6..5435ce561ecf 100644 --- a/code/modules/mob/living/simple_animal/slime/death.dm +++ b/code/modules/mob/living/simple_animal/slime/death.dm @@ -21,7 +21,7 @@ if(buckled) Feedstop(silent = TRUE) //releases ourselves from the mob we fed on. - stat = DEAD + set_stat(DEAD) cut_overlays() if(SSticker.mode) diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index ed84ad64e64c..44a859e1e8ed 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -139,14 +139,14 @@ if(stat == CONSCIOUS && stasis) to_chat(src, span_danger("Nerve gas in the air has put you in stasis!")) - stat = UNCONSCIOUS + set_stat(UNCONSCIOUS) powerlevel = 0 rabid = 0 update_mobility() regenerate_icons() else if(stat == UNCONSCIOUS && !stasis) to_chat(src, span_notice("You wake up from the stasis.")) - stat = CONSCIOUS + set_stat(CONSCIOUS) update_mobility() regenerate_icons() @@ -253,7 +253,7 @@ else if (nutrition >= get_grow_nutrition() && amount_grown < SLIME_EVOLUTION_THRESHOLD) adjust_nutrition(-20) amount_grown++ - update_action_buttons_icon() + update_mob_action_buttons() if(amount_grown >= SLIME_EVOLUTION_THRESHOLD && !buckled && !Target && !ckey) if(is_adult) diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm index fb8dd65339bf..34a93c979cbf 100644 --- a/code/modules/mob/living/simple_animal/slime/powers.dm +++ b/code/modules/mob/living/simple_animal/slime/powers.dm @@ -7,11 +7,12 @@ /datum/action/innate/slime check_flags = AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/actions_slime.dmi' + button_icon = 'icons/mob/actions/actions_slime.dmi' background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" var/needs_growth = NO_GROWTH_NEEDED -/datum/action/innate/slime/IsAvailable() +/datum/action/innate/slime/IsAvailable(feedback = FALSE) if(..()) var/mob/living/simple_animal/slime/S = owner if(needs_growth == GROWTH_NEEDED) diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index a415d1d24f40..606c75dec27b 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -250,7 +250,7 @@ . += "Power Level: [powerlevel]" -/mob/living/simple_animal/slime/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) +/mob/living/simple_animal/slime/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status) if(!forced) amount = -abs(amount) return ..() //Heals them diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index d1d9843963cf..8503c8463602 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -461,3 +461,164 @@ /mob/living/proc/ignore_slowdown(source) ADD_TRAIT(src, TRAIT_IGNORESLOWDOWN, source) update_movespeed(FALSE) + +/** + * Adjusts a timed status effect on the mob,taking into account any existing timed status effects. + * This can be any status effect that takes into account "duration" with their initialize arguments. + * + * Positive durations will add deciseconds to the duration of existing status effects + * or apply a new status effect of that duration to the mob. + * + * Negative durations will remove deciseconds from the duration of an existing version of the status effect, + * removing the status effect entirely if the duration becomes less than zero (less than the current world time). + * + * duration - the duration, in deciseconds, to add or remove from the effect + * effect - the type of status effect being adjusted on the mob + * max_duration - optional - if set, positive durations will only be added UP TO the passed max duration + */ +/mob/living/proc/adjust_timed_status_effect(duration, effect, max_duration) + if(!isnum(duration)) + CRASH("adjust_timed_status_effect: called with an invalid duration. (Got: [duration])") + + if(!ispath(effect, /datum/status_effect)) + CRASH("adjust_timed_status_effect: called with an invalid effect type. (Got: [effect])") + + // If we have a max duration set, we need to check our duration does not exceed it + if(isnum(max_duration)) + if(max_duration <= 0) + CRASH("adjust_timed_status_effect: Called with an invalid max_duration. (Got: [max_duration])") + + if(duration >= max_duration) + duration = max_duration + + var/datum/status_effect/existing = has_status_effect(effect) + if(existing) + if(isnum(max_duration) && duration > 0) + // Check the duration remaining on the existing status effect + // If it's greater than / equal to our passed max duration, we don't need to do anything + var/remaining_duration = existing.duration - world.time + if(remaining_duration >= max_duration) + return + + // Otherwise, add duration up to the max (max_duration - remaining_duration), + // or just add duration if it doesn't exceed our max at all + existing.duration += min(max_duration - remaining_duration, duration) + + else + existing.duration += duration + + // If the duration was decreased and is now less 0 seconds, + // qdel it / clean up the status effect immediately + // (rather than waiting for the process tick to handle it) + if(existing.duration <= world.time) + qdel(existing) + + else if(duration > 0) + apply_status_effect(effect, duration) + +/** + * Sets a timed status effect of some kind on a mob to a specific value. + * If only_if_higher is TRUE, it will only set the value up to the passed duration, + * so any pre-existing status effects of the same type won't be reduced down + * + * duration - the duration, in deciseconds, of the effect. 0 or lower will either remove the current effect or do nothing if none are present + * effect - the type of status effect given to the mob + * only_if_higher - if TRUE, we will only set the effect to the new duration if the new duration is longer than any existing duration + */ +/mob/living/proc/set_timed_status_effect(duration, effect, only_if_higher = FALSE) + if(!isnum(duration)) + CRASH("set_timed_status_effect: called with an invalid duration. (Got: [duration])") + + if(!ispath(effect, /datum/status_effect)) + CRASH("set_timed_status_effect: called with an invalid effect type. (Got: [effect])") + + var/datum/status_effect/existing = has_status_effect(effect) + if(existing) + // set_timed_status_effect to 0 technically acts as a way to clear effects, + // though remove_status_effect would achieve the same goal more explicitly. + if(duration <= 0) + qdel(existing) + return + + if(only_if_higher) + // If the existing status effect has a higher remaining duration + // than what we aim to set it to, don't downgrade it - do nothing (return) + var/remaining_duration = existing.duration - world.time + if(remaining_duration >= duration) + return + + // Set the duration accordingly + existing.duration = world.time + duration + + else if(duration > 0) + apply_status_effect(effect, duration) + +/** + * Gets how many deciseconds are remaining in + * the duration of the passed status effect on this mob. + * + * If the mob is unaffected by the passed effect, returns 0. + */ +/mob/living/proc/get_timed_status_effect_duration(effect) + if(!ispath(effect, /datum/status_effect)) + CRASH("get_timed_status_effect_duration: called with an invalid effect type. (Got: [effect])") + + var/datum/status_effect/existing = has_status_effect(effect) + if(!existing) + return 0 + // Infinite duration status effects technically are not "timed status effects" + // by name or nature, but support is included just in case. + if(existing.duration == -1) + return INFINITY + + return existing.duration - world.time + +/** + * Adjust the "drunk value" the mob is currently experiencing, + * or applies a drunk effect if the mob isn't currently drunk (or tipsy) + * + * The drunk effect doesn't have a set duration, like dizziness or drugginess, + * but instead relies on a value that decreases every status effect tick (2 seconds) by: + * 4% the current drunk_value + 0.01 + * + * A "drunk value" of 6 is the border between "tipsy" and "drunk". + * + * amount - the amount of "drunkness" to apply to the mob. + * down_to - the lower end of the clamp, when adding the value + * up_to - the upper end of the clamp, when adding the value + */ +/mob/living/proc/adjust_drunk_effect(amount, down_to = 0, up_to = INFINITY) + if(!isnum(amount)) + CRASH("adjust_drunk_effect: called with an invalid amount. (Got: [amount])") + + var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated) + if(inebriation) + inebriation.set_drunk_value(clamp(inebriation.drunk_value + amount, down_to, up_to)) + else if(amount > 0) + apply_status_effect(/datum/status_effect/inebriated/tipsy, amount) + + +/** + * Directly sets the "drunk value" the mob is currently experiencing to the passed value, + * or applies a drunk effect with the passed value if the mob isn't currently drunk + * + * set_to - the amount of "drunkness" to set on the mob. + */ +/mob/living/proc/set_drunk_effect(set_to) + if(!isnum(set_to) || set_to < 0) + CRASH("set_drunk_effect: called with an invalid value. (Got: [set_to])") + + var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated) + if(inebriation) + inebriation.set_drunk_value(set_to) + else if(set_to > 0) + apply_status_effect(/datum/status_effect/inebriated/tipsy, set_to) + +/// Helper to get the amount of drunkness the mob's currently experiencing. +/mob/living/proc/get_drunk_amount() + var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated) + return inebriation?.drunk_value || 0 + +/// Helper to check if we seem to be alive or not +/mob/living/proc/appears_alive() + return health >= 0 && !HAS_TRAIT(src, TRAIT_FAKEDEATH) diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index e8e8ce1fd808..816a2f0b299e 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -22,7 +22,7 @@ var/text_output = from.generate_taste_message(taste_sensitivity) // We dont want to spam the same message over and over again at the // person. Give it a bit of a buffer. - if(hallucination > 50 && prob(25)) + if(get_timed_status_effect_duration(/datum/status_effect/hallucination) > 100 SECONDS && prob(25)) text_output = pick("spiders","dreams","nightmares","the future","the past","victory",\ "defeat","pain","bliss","revenge","poison","time","space","death","life","truth","lies","justice","memory",\ "regrets","your soul","suffering","music","noise","blood","hunger","the american way") diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm index a1061fa2e8d9..33e5cc3d4d90 100644 --- a/code/modules/mob/living/ventcrawling.dm +++ b/code/modules/mob/living/ventcrawling.dm @@ -95,7 +95,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list( return var/list/totalMembers = list() - for(var/datum/pipeline/P in starting_machine.returnPipenets()) + for(var/datum/pipeline/P in starting_machine.return_pipenets()) totalMembers += P.members totalMembers += P.other_atmosmch diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 670d303561d1..84fd5c7ae920 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -24,6 +24,7 @@ /mob/Login() if(!client) return FALSE + canon_client = client add_to_player_list() lastKnownIP = client.address computer_id = client.computer_id @@ -33,14 +34,34 @@ client.images = list() if(!hud_used) - create_mob_hud() + create_mob_hud() // creating a hud will add it to the client's screen, which can process a disconnect + if(!client) + return FALSE + if(hud_used) - hud_used.show_hud(hud_used.hud_version) + hud_used.show_hud(hud_used.hud_version) // see above, this can process a disconnect + if(!client) + return FALSE hud_used.update_ui_style(ui_style2icon(client.prefs?.read_preference(/datum/preference/choiced/ui_style))) next_move = 1 - ..() + client.statobj = src + + SSdemo.write_event_line("setmob [client.ckey] \ref[src]") + + // DO NOT CALL PARENT HERE + // BYOND's internal implementation of login does two things + // 1: Set statobj to the mob being logged into (We got this covered) + // 2: And I quote "If the mob has no location, place it near (1,1,1) if possible" + // 3: Yog: handle the mob client in replays + // See, near is doing an agressive amount of legwork there + // What it actually does is takes the area that (1,1,1) is in, and loops through all those turfs + // If you successfully move into one, it stops + // Because we want Move() to mean standard movements rather then just what byond treats it as (ALL moves) + // We don't allow moves from nullspace -> somewhere. This means the loop has to iterate all the turfs in (1,1,1)'s area + // For us, (1,1,1) is a space tile. This means roughly 200,000! calls to Move() + // You do not want this if(!client) return FALSE diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 6fd87ec2789b..96234ae968e8 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -96,22 +96,69 @@ tag = "mob_[next_mob_id++]" /** - * Prepare the huds for this atom - * - * Goes through hud_possible list and adds the images to the hud_list variable (if not already - * cached) - */ + * set every hud image in the given category active so other people with the given hud can see it. + * Arguments: + * * hud_category - the index in our active_hud_list corresponding to an image now being shown. + * * update_huds - if FALSE we will just put the hud_category into active_hud_list without actually updating the atom_hud datums subscribed to it + * * exclusive_hud - if given a reference to an atom_hud, will just update that hud instead of all global ones attached to that category. + * This is because some atom_hud subtypes arent supposed to work via global categories, updating normally would affect all of these which we dont want. + */ +/atom/proc/set_hud_image_active(hud_category, update_huds = TRUE, datum/atom_hud/exclusive_hud) + if(!istext(hud_category) || !hud_list?[hud_category] || active_hud_list?[hud_category]) + return FALSE + + LAZYSET(active_hud_list, hud_category, hud_list[hud_category]) + + if(!update_huds) + return TRUE + + if(exclusive_hud) + exclusive_hud.add_single_hud_category_on_atom(src, hud_category) + else + for(var/datum/atom_hud/hud_to_update as anything in GLOB.huds_by_category[hud_category]) + hud_to_update.add_single_hud_category_on_atom(src, hud_category) + + return TRUE + +///sets every hud image in the given category inactive so no one can see it +/atom/proc/set_hud_image_inactive(hud_category, update_huds = TRUE, datum/atom_hud/exclusive_hud) + if(!istext(hud_category)) + return FALSE + + if(!update_huds) + LAZYREMOVE(active_hud_list, hud_category) + return TRUE + + if(exclusive_hud) + exclusive_hud.remove_single_hud_category_on_atom(src, hud_category) + else + for(var/datum/atom_hud/hud_to_update as anything in GLOB.huds_by_category[hud_category]) + hud_to_update.remove_single_hud_category_on_atom(src, hud_category) + + LAZYREMOVE(active_hud_list, hud_category) + + return TRUE + +/** + * Prepare the huds for this atom + * + * Goes through hud_possible list and adds the images to the hud_list variable (if not already cached) + */ /atom/proc/prepare_huds() + if(hud_list) // I choose to be lienient about people calling this proc more then once + return hud_list = list() for(var/hud in hud_possible) var/hint = hud_possible[hud] - switch(hint) - if(HUD_LIST_LIST) - hud_list[hud] = list() - else - var/image/I = image('icons/mob/hud.dmi', src, "") - I.appearance_flags = RESET_COLOR|RESET_TRANSFORM - hud_list[hud] = I + + if(hint == HUD_LIST_LIST) + hud_list[hud] = list() + + else + var/image/I = image('yogstation/icons/mob/hud.dmi', src, "") + I.appearance_flags = RESET_COLOR|RESET_TRANSFORM + hud_list[hud] = I + set_hud_image_active(hud, update_huds = FALSE) //by default everything is active. but dont add it to huds to keep control. /** * Some kind of debug verb that gives atmosphere environment details @@ -283,12 +330,21 @@ /mob/proc/get_item_by_slot(slot_id) return null +/// Gets what slot the item on the mob is held in. +/// Returns null if the item isn't in any slots on our mob. +/// Does not check if the passed item is null, which may result in unexpected outcoms. +/mob/proc/get_slot_by_item(obj/item/looking_for) + if(looking_for in held_items) + return ITEM_SLOT_HANDS + + return null + ///Is the mob restrained /mob/proc/restrained(ignore_grab) - return + return HAS_TRAIT(src, TRAIT_RESTRAINED) ///Is the mob incapacitated -/mob/proc/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE) +/mob/proc/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE) return /** @@ -377,7 +433,7 @@ SLOT_WEAR_MASK, SLOT_HEAD, SLOT_NECK,\ SLOT_SHOES, SLOT_GLOVES,\ SLOT_EARS, SLOT_GLASSES,\ - SLOT_BELT, SLOT_S_STORE,\ + SLOT_BELT, SLOT_SUIT_STORE,\ SLOT_L_STORE, SLOT_R_STORE,\ SLOT_GENERC_DEXTROUS_STORAGE\ ) @@ -844,29 +900,33 @@ . += "[obj_count]: [objective.explanation_text][objective.check_completion() ? " (COMPLETED)" : ""]" obj_count++ -/mob/proc/get_proc_holders() - . = list() - if(mind) - . += get_spells_for_statpanel(mind.spell_list) - . += get_spells_for_statpanel(mob_spell_list) - /** * Convert a list of spells into a displyable list for the statpanel * * Shows charge and other important info */ -/mob/proc/get_spells_for_statpanel(list/spells) - var/list/L = list() - for(var/obj/effect/proc_holder/spell/S in spells) - if(S.can_be_cast_by(src)) - switch(S.charge_type) - if(SPELL_CHARGE_TYPE_RECHARGE) - L[++L.len] = list("[S.panel]", "[S.charge_counter/10.0]/[S.charge_max/10]", S.name, REF(S)) - if(SPELL_CHARGE_TYPE_CHARGES) - L[++L.len] = list("[S.panel]", "[S.charge_counter]/[S.charge_max]", S.name, REF(S)) - if(SPELL_CHARGE_TYPE_HOLDERVAR) - L[++L.len] = list("[S.panel]", "[S.holder_var_type] [S.holder_var_amount]", S.name, REF(S)) - return L +/mob/proc/get_actions_for_statpanel() + var/list/data = list() + for(var/datum/action/cooldown/action in actions) + var/list/action_data = action.set_statpanel_format() + if(!length(action_data)) + return + + data += list(list( + // the panel the action gets displayed to + // in the future, this could probably be replaced with subtabs (a la admin tabs) + action_data[PANEL_DISPLAY_PANEL], + // the status of the action, - cooldown, charges, whatever + action_data[PANEL_DISPLAY_STATUS], + // the name of the action + action_data[PANEL_DISPLAY_NAME], + // a ref to the action button of this action for this mob + // it's a ref to the button specifically, instead of the action itself, + // because statpanel href calls click(), which the action button (not the action itself) handles + REF(action.viewers[hud_used]), + )) + + return data #define MOB_FACE_DIRECTION_DELAY 1 @@ -938,26 +998,43 @@ ghost.notify_cloning(message, sound, source, flashwindow) return ghost -///Add a spell to the mobs spell list -/mob/proc/AddSpell(obj/effect/proc_holder/spell/S) - mob_spell_list += S - S.action?.Grant(src) +/** + * Checks to see if the mob can cast normal magic spells. + * + * args: + * * magic_flags (optional) A bitfield with the type of magic being cast (see flags at: /datum/component/anti_magic) +**/ +/mob/proc/can_cast_magic(magic_flags = MAGIC_RESISTANCE) + if(magic_flags == NONE) // magic with the NONE flag can always be cast + return TRUE -///Remove a spell from the mobs spell list -/mob/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) - if(!spell) - return - for(var/X in mob_spell_list) - var/obj/effect/proc_holder/spell/S = X - if(istype(S, spell)) - mob_spell_list -= S - qdel(S) - if(client) - client << output(null, "statbrowser:check_spells") + var/restrict_magic_flags = SEND_SIGNAL(src, COMSIG_MOB_RESTRICT_MAGIC, magic_flags) + return restrict_magic_flags == NONE + +/** + * Checks to see if the mob can block magic + * + * args: + * * casted_magic_flags (optional) A bitfield with the types of magic resistance being checked (see flags at: /datum/component/anti_magic) + * * charge_cost (optional) The cost of charge to block a spell that will be subtracted from the protection used +**/ +/mob/proc/can_block_magic(casted_magic_flags = MAGIC_RESISTANCE, charge_cost = 1) + if(casted_magic_flags == NONE) // magic with the NONE flag is immune to blocking + return FALSE + + var/list/protection_was_used = list() // this is a janky way of interrupting signals using lists + var/is_magic_blocked = SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, casted_magic_flags, charge_cost, protection_was_used) & COMPONENT_MAGIC_BLOCKED + + if(casted_magic_flags && HAS_TRAIT(src, TRAIT_ANTIMAGIC)) + is_magic_blocked = TRUE + if((casted_magic_flags & MAGIC_RESISTANCE_HOLY) && HAS_TRAIT(src, TRAIT_HOLY)) + is_magic_blocked = TRUE + + return is_magic_blocked ///Return any anti magic atom on this mob that matches the magic type -/mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, tinfoil = FALSE, chargecost = 1, self = FALSE) - return +/mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, tinfoil = FALSE, chargecost = 1, self = FALSE) //new system above, kept cause this is not the aim of the PR but to + return //be changed later /** * Buckle to another mob @@ -1091,7 +1168,7 @@ ///update the ID name of this mob /mob/proc/replace_identification_name(oldname,newname) - var/list/searching = GetAllContents() + var/list/searching = get_all_contents() var/search_id = 1 var/search_pda = 1 @@ -1155,6 +1232,9 @@ if(E.mouse_pointer) client.mouse_pointer_icon = E.mouse_pointer + if(client.mouse_override_icon) + client.mouse_pointer_icon = client.mouse_override_icon + ///This mob is abile to read books /mob/proc/is_literate() @@ -1275,6 +1355,13 @@ /mob/proc/set_nutrition(var/change) //Seriously fuck you oldcoders. nutrition = max(0, change) +/mob/proc/set_stat(new_stat) + if(new_stat == stat) + return + . = stat + stat = new_stat + SEND_SIGNAL(src, COMSIG_MOB_STATCHANGE, new_stat, .) + ///Set the movement type of the mob and update it's movespeed /mob/setMovetype(newval) . = ..() diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 56355ba108fc..c3c2ca83a065 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -21,6 +21,12 @@ var/lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE var/datum/mind/mind var/static/next_mob_id = 0 + /// The current client inhabiting this mob. Managed by login/logout + /// This exists so we can do cleanup in logout for occasions where a client was transfere rather then destroyed + /// We need to do this because the mob on logout never actually has a reference to client + /// We also need to clear this var/do other cleanup in client/Destroy, since that happens before logout + /// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + var/client/canon_client /// List of movement speed modifiers applying to this mob var/list/movespeed_modification //Lazy list, see mob_movespeed.dm @@ -30,8 +36,9 @@ var/list/datum/action/actions = list() /// Actions that belong to this mob used in observers var/list/datum/action/originalactions = list() - /// A special action? No idea why this lives here - var/list/datum/action/chameleon_item_actions + /// A list of chameleon actions we have specifically + /// This can be unified with the actions list + var/list/datum/action/item_action/chameleon/chameleon_item_actions /// Whether a mob is alive or dead. TODO: Move this to living - Nodrak (2019, still here) var/stat = CONSCIOUS @@ -85,13 +92,6 @@ /// Default body temperature var/bodytemperature = BODYTEMP_NORMAL //310.15K / 98.6F - /// Drowsyness level of the mob - var/drowsyness = 0//Carbon - /// Dizziness level of the mob - var/dizziness = 0//Carbon - /// Jitteryness level of the mob - var/jitteriness = 0//Carbon - /// Hunger level of the mob var/nutrition = NUTRITION_LEVEL_START_MIN // randomised in Initialize /// Satiation level of the mob var/satiety = 0//Carbon @@ -154,15 +154,6 @@ ///A weakref to the last mob/living/carbon to push/drag/grab this mob (exclusively used by slimes friend recognition) var/datum/weakref/LAssailant = null - /** - * construct spells and mime spells. - * - * Spells that do not transfer from one mob to another and can not be lost in mindswap. - * obviously do not live in the mind - */ - var/list/mob_spell_list = list() - - /// bitflags defining which status effects can be inflicted (replaces canknockdown, canstun, etc) var/status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 160b7c86ecc1..b2368a0ba2a1 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -121,9 +121,9 @@ if(!(L.mobility_flags & MOBILITY_MOVE)) return FALSE - if(isobj(mob.loc) || ismob(mob.loc)) //Inside an object, tell it we moved - var/atom/O = mob.loc - return O.relaymove(mob, direct) + if(ismovable(mob.loc)) //Inside an object, tell it we moved + var/atom/loc_atom = mob.loc + return loc_atom.relaymove(mob, direct) if(!mob.Process_Spacemove(direct)) return FALSE @@ -140,13 +140,13 @@ else move_delay = world.time - if(L.confused) + if(L.has_status_effect(/datum/status_effect/confusion)) var/newdir = 0 - if(L.confused > 40) + if(L.get_timed_status_effect_duration(/datum/status_effect/confusion) > 40) newdir = pick(GLOB.alldirs) - else if(prob(L.confused * 1.5)) + else if(prob(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 1.5)) newdir = angle2dir(dir2angle(direct) + pick(90, -90)) - else if(prob(L.confused * 3)) + else if(prob(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 3)) newdir = angle2dir(dir2angle(direct) + pick(45, -45)) if(newdir) direct = newdir @@ -172,18 +172,18 @@ * Called by client/Move() */ /client/proc/Process_Grab() - if(mob.pulledby) - if((mob.pulledby == mob.pulling) && (mob.pulledby.grab_state == GRAB_PASSIVE)) //Don't autoresist passive grabs if we're grabbing them too. - return - if(mob.incapacitated(ignore_restraints = 1)) - move_delay = world.time + 10 - return TRUE - else if(mob.restrained(ignore_grab = 1)) - move_delay = world.time + 10 - to_chat(src, span_warning("You're restrained! You can't move!")) - return TRUE - else - return mob.resist_grab(1) + if(!mob.pulledby) + return FALSE + if(mob.pulledby == mob.pulling && mob.pulledby.grab_state == GRAB_PASSIVE) //Don't autoresist passive grabs if we're grabbing them too. + return FALSE + if(HAS_TRAIT(mob, TRAIT_INCAPACITATED)) + COOLDOWN_START(src, move_delay, 1 SECONDS) + return TRUE + else if(mob.restrained(ignore_grab = TRUE)) + COOLDOWN_START(src, move_delay, 1 SECONDS) + to_chat(src, span_warning("You're restrained! You can't move!")) + return TRUE + return mob.resist_grab(TRUE) /** * Allows mobs to ignore density and phase through objects @@ -502,6 +502,10 @@ set name = "Move Upwards" set category = "IC" + if(ismovable(loc)) //Inside an object, tell it we moved + var/atom/loc_atom = loc + return loc_atom.relaymove(src, UP) + if(zMove(UP, TRUE)) to_chat(src, span_notice("You move upwards.")) @@ -510,6 +514,10 @@ set name = "Move Down" set category = "IC" + if(ismovable(loc)) //Inside an object, tell it we moved + var/atom/loc_atom = loc + return loc_atom.relaymove(src, DOWN) + if(zMove(DOWN, TRUE)) to_chat(src, span_notice("You move down.")) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 009335ca702f..2f29088c8d51 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -61,9 +61,15 @@ return whisper(message) -///whisper a message -/mob/proc/whisper(message, datum/language/language=null) - say(message, language) //only living mobs actually whisper, everything else just talks +/** + * Whisper a message. + * + * Basic level implementation just speaks the message, nothing else. + */ +/mob/proc/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language, ignore_spam = FALSE, forced, filterproof) + if(!message) + return + say(message, language = language) /mob/verb/me_wrapper() set name = ".me" diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm index 0f11b1c2b117..ad0730f3cf4d 100644 --- a/code/modules/mob/status_procs.dm +++ b/code/modules/mob/status_procs.dm @@ -3,24 +3,6 @@ //The effects include: stun, knockdown, unconscious, sleeping, resting, jitteriness, dizziness, ear damage, // eye damage, eye_blind, eye_blurry, druggy, TRAIT_BLIND trait, and TRAIT_NEARSIGHT trait. - - -///Set the jitter of a mob -/mob/proc/Jitter(amount) - jitteriness = max(jitteriness,amount,0) - -/** - * Set the dizzyness of a mob to a passed in amount - * - * Except if dizziness is already higher in which case it does nothing - */ -/mob/proc/Dizzy(amount) - dizziness = max(dizziness,amount,0) - -///FOrce set the dizzyness of a mob -/mob/proc/set_dizziness(amount) - dizziness = max(amount, 0) - ///Blind a mobs eyes by amount /mob/proc/blind_eyes(amount) if(amount>0) @@ -118,14 +100,6 @@ GW.backdrop(src) OT.backdrop(src) -///Adjust the drugginess of a mob -/mob/proc/adjust_drugginess(amount) - return - -///Set the drugginess of a mob -/mob/proc/set_drugginess(amount) - return - ///Adjust the disgust level of a mob /mob/proc/adjust_disgust(amount) return diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 1df1106cacbe..0764f2aa2654 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -22,6 +22,8 @@ var/last_battery_percent = 0 var/last_world_time = "00:00" var/list/last_header_icons + ///A pAI currently loaded into the modular computer. + var/obj/item/paicard/inserted_pai /// Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. var/base_active_power_usage = 50 diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm index c98c6aab94de..1f7ae8e4b60b 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm @@ -10,7 +10,7 @@ H.SetImmobilized(0) H.SetParalyzed(0) H.adjustStaminaLoss(-75) - H.stuttering = 0 + H.remove_status_effect(/datum/status_effect/speech/stutter) H.lying = 0 H.update_mobility() H.reagents.add_reagent(/datum/reagent/medicine/stimulants, 5) diff --git a/code/modules/pai/actions.dm b/code/modules/pai/actions.dm new file mode 100644 index 000000000000..99f312dc917c --- /dev/null +++ b/code/modules/pai/actions.dm @@ -0,0 +1,62 @@ +/datum/action/innate/pai + name = "PAI Action" + background_icon = 'icons/mob/actions/actions_silicon.dmi' + var/mob/living/silicon/pai/pai_owner + +/datum/action/innate/pai/Trigger(trigger_flags) + if(!ispAI(owner)) + return FALSE + pai_owner = owner + +/datum/action/innate/pai/software + name = "Software Interface" + button_icon_state = "pai" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/software/Trigger(trigger_flags) + ..() + pai_owner.ui_act() + +/datum/action/innate/pai/shell + name = "Toggle Holoform" + button_icon_state = "pai_holoform" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/shell/Trigger(trigger_flags) + ..() + if(pai_owner.holoform) + pai_owner.fold_in(0) + else + pai_owner.fold_out() + +/datum/action/innate/pai/chassis + name = "Holochassis Appearance Composite" + button_icon_state = "pai_chassis" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/chassis/Trigger(trigger_flags) + ..() + pai_owner.choose_chassis() + +/datum/action/innate/pai/rest + name = "Rest" + button_icon_state = "pai_rest" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/rest/Trigger(trigger_flags) + ..() + var/mob/living/silicon/pai/pAI = usr + if(!pAI.resting) + pAI.set_resting(TRUE) + else + pAI.set_resting(FALSE) + +/datum/action/innate/pai/light + name = "Toggle Integrated Lights" + background_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "emp" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/light/Trigger(trigger_flags) + ..() + pai_owner.toggle_integrated_light() diff --git a/code/modules/pai/camera.dm b/code/modules/pai/camera.dm new file mode 100644 index 000000000000..30b1918651e4 --- /dev/null +++ b/code/modules/pai/camera.dm @@ -0,0 +1,58 @@ +/mob/living/silicon/pai/ClickOn(atom/target, params) + ..() + if(!camera?.in_camera_mode) + return FALSE + //pAI picture taking + camera.toggle_camera_mode(sound = FALSE) + camera.captureimage(target, usr, camera.picture_size_x - 1, camera.picture_size_y - 1) + return TRUE + +/obj/item/camera/siliconcam/pai_camera + name = "pAI photo camera" + light_color = COLOR_PAI_GREEN + +/obj/item/camera/siliconcam/pai_camera/after_picture(mob/user, datum/picture/picture) + var/number = length(stored) + picture.picture_name = "Image [number] (taken by [loc.name])" + stored[picture] = TRUE + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) + balloon_alert(user, "image recorded") + +/** + * Handles selecting and printing stored images. + * + * @param {mob} user - The pAI. + * + * @returns {boolean} - TRUE if the pAI prints an image, + * FALSE otherwise. +*/ +/obj/item/camera/siliconcam/pai_camera/proc/pai_print(mob/user) + var/mob/living/silicon/pai/pai = loc + var/datum/picture/selection = selectpicture(user) + if(!istype(selection)) + balloon_alert(user, "invalid image") + return FALSE + printpicture(user, selection) + user.visible_message(span_notice("A picture appears on top of the chassis of [pai.name]!"), span_notice("You print a photograph.")) + return TRUE + +/** + * All inclusive camera proc. Zooms, snaps, prints. + * + * @param {mob} user - The pAI requesting the camera. + * + * @param {string} mode - The camera option to toggle. + * + * @returns {boolean} - TRUE if the camera worked. + */ +/mob/living/silicon/pai/proc/use_camera(mob/user, mode) + if(!camera || isnull(mode)) + return FALSE + switch(mode) + if(PAI_PHOTO_MODE_CAMERA) + camera.toggle_camera_mode(user) + if(PAI_PHOTO_MODE_PRINTER) + camera.pai_print(user) + if(PAI_PHOTO_MODE_ZOOM) + camera.adjust_zoom(user) + return TRUE diff --git a/code/modules/pai/candidate.dm b/code/modules/pai/candidate.dm new file mode 100644 index 000000000000..6455492e1379 --- /dev/null +++ b/code/modules/pai/candidate.dm @@ -0,0 +1,35 @@ +/** + * #pAI Candidate + * + * Created when a user opens the pAI submit interface. + * Stores the candidate in an associative list of ckey: candidate objects. + */ +/datum/pai_candidate + /// User inputted OOC comments + var/comments + /// User inputted behavior description + var/description + /// User's ckey + var/ckey + /// User's pAI name. If blank, ninja name. + var/name + /// If the user has hit "submit" + var/ready = FALSE + +/datum/pai_candidate/New(ckey) + src.ckey = ckey + +/** + * Checks if a candidate is ready so that they may be displayed or + * downloaded. Removes any invalid entries. + * + * @returns {boolean} - TRUE if the candidate is ready, FALSE if not + */ +/datum/pai_candidate/proc/check_ready() + var/mob/candidate_mob = get_mob_by_key(ckey) + if(!candidate_mob?.client || !isobserver(candidate_mob) || is_banned_from(ckey, ROLE_PAI)) + SSpai.candidates -= ckey + return FALSE + if(!ready) + return FALSE + return TRUE diff --git a/code/modules/pai/card.dm b/code/modules/pai/card.dm new file mode 100644 index 000000000000..312b8a26abc1 --- /dev/null +++ b/code/modules/pai/card.dm @@ -0,0 +1,259 @@ +/obj/item/pai_card + name = "personal AI device" + desc = "Downloads personal AI assistants to accompany its owner or others." + icon = 'icons/obj/aicards.dmi' + icon_state = "pai" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + cryo_preserve = TRUE + /// Spam alert prevention + var/alert_cooldown + /// The emotion icon displayed. + var/emotion_icon = "off" + /// Any pAI personalities inserted + var/mob/living/silicon/pai/pai + /// Prevents a crew member from hitting "request pAI" repeatedly + var/request_spam = FALSE + +/obj/item/pai_card/attackby(obj/item/used, mob/user, params) + if(pai && istype(used, /obj/item/encryptionkey)) + if(!pai.encrypt_mod) + to_chat(user, span_alert("Encryption Key ports not configured.")) + return + user.set_machine(src) + pai.radio.attackby(used, user, params) + to_chat(user, span_notice("You insert [used] into the [src].")) + return + return ..() + +/obj/item/pai_card/attack_self(mob/user) + if(!in_range(src, user)) + return + user.set_machine(src) + ui_interact(user) + +/obj/item/pai_card/Destroy() + //Will stop people throwing friend pAIs into the singularity so they can respawn + SSpai.pai_card_list.Remove(src) + if(!QDELETED(pai)) + QDEL_NULL(pai) + return ..() + +/obj/item/pai_card/emag_act(mob/user) + if(pai) + pai.handle_emag(user) + +/obj/item/pai_card/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(pai && !pai.holoform) + pai.emp_act(severity) + +/obj/item/pai_card/handle_atom_del(atom/thing) + if(thing == pai) //double check /mob/living/silicon/pai/Destroy() if you change these. + pai = null + emotion_icon = initial(emotion_icon) + update_icon() + return ..() + +/obj/item/pai_card/Initialize(mapload) + . = ..() + update_icon() + SSpai.pai_card_list += src + +/obj/item/pai_card/suicide_act(mob/living/user) + user.visible_message(span_suicide("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!")) + return OXYLOSS + +/obj/item/pai_card/proc/update_overlays() + . = ..() + . += "pai-[emotion_icon]" + if(pai?.hacking_cable) + . += "[initial(icon_state)]-connector" + +/obj/item/pai_card/vv_edit_var(vname, vval) + . = ..() + if(vname == NAMEOF(src, emotion_icon)) + update_icon() + +/obj/item/pai_card/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PaiCard") + ui.open() + +/obj/item/pai_card/ui_status(mob/user) + if(user in get_nested_locs(src)) + return UI_INTERACTIVE + return ..() + +/obj/item/pai_card/ui_data(mob/user) + . = ..() + var/list/data = list() + if(!pai) + data["candidates"] = pool_candidates() || list() + return data + data["pai"] = list( + can_holo = pai.can_holo, + dna = pai.master_dna, + emagged = pai.emagged, + laws = pai.laws.supplied, + master = pai.master_name, + name = pai.name, + transmit = pai.can_transmit, + receive = pai.can_receive, + ) + return data + +/obj/item/pai_card/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return TRUE + // Actions that don't require a pAI + if(action == "download") + download_candidate(usr, params["ckey"]) + return TRUE + if(action == "request") + find_pai(usr) + return TRUE + // pAI specific actions. + if(!pai) + return FALSE + switch(action) + if("fix_speech") + pai.fix_speech() + return TRUE + if("reset_software") + pai.reset_software() + return TRUE + if("set_dna") + pai.set_dna(usr) + return TRUE + if("set_laws") + pai.set_laws(usr) + return TRUE + if("toggle_holo") + pai.toggle_holo() + return TRUE + if("toggle_radio") + pai.toggle_radio(params["option"]) + return TRUE + if("wipe_pai") + pai.wipe_pai(usr) + ui.close() + return TRUE + return FALSE + +/** Flashes the pai card screen */ +/obj/item/pai_card/proc/add_alert() + if(pai) + return + add_overlay( + list(mutable_appearance(icon, "[initial(icon_state)]-alert"), + mutable_appearance(icon, "[initial(icon_state)]-alert", src, alpha = src.alpha))) + +/** Removes any overlays */ +/obj/item/pai_card/proc/remove_alert() + if(pai) + return + cut_overlays() + +/** Alerts pAI cards that someone has submitted candidacy */ +/obj/item/pai_card/proc/alert_update() + if(!COOLDOWN_FINISHED(src, alert_cooldown)) + return + COOLDOWN_START(src, alert_cooldown, 5 SECONDS) + add_alert() + addtimer(CALLBACK(src, PROC_REF(remove_alert)), 5 SECONDS) + playsound(src, 'sound/machines/ping.ogg', 30, TRUE) + visible_message(span_notice("[src] flashes a message across its screen: New personalities available for download!"), blind_message = span_notice("[src] vibrates with an alert.")) + +/** + * Downloads a candidate from the list and removes them from SSpai.candidates + * + * @param {string} ckey The ckey of the candidate to download + * + * @returns {boolean} - TRUE if the candidate was downloaded, FALSE if not + */ +/obj/item/pai_card/proc/download_candidate(mob/user, ckey) + if(pai) + return FALSE + var/datum/pai_candidate/candidate = SSpai.candidates[ckey] + if(!candidate?.check_ready()) + balloon_alert(user, "download interrupted") + return FALSE + var/mob/living/silicon/pai/new_pai = new(src) + new_pai.name = candidate.name || pick(GLOB.ninja_names) + new_pai.real_name = new_pai.name + new_pai.key = candidate.ckey + set_personality(new_pai) + SSpai.candidates -= ckey + return TRUE + +/** + * Pings ghosts to announce that someone is requesting a pAI + * + * @param {mob} user - The user who is requesting a pAI + * + * @returns {boolean} - TRUE if the pAI was requested, FALSE if not + */ +/obj/item/pai_card/proc/find_pai(mob/user) + if(pai) + return FALSE + if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS)) + balloon_alert(user, "unavailable: NT blacklisted") + return FALSE + if(request_spam) + balloon_alert(user, "request sent too recently") + return FALSE + request_spam = TRUE + playsound(src, 'sound/machines/ping.ogg', 20, TRUE) + balloon_alert(user, "pAI assistance requested") + var/mutable_appearance/alert_overlay = mutable_appearance('icons/obj/aicards.dmi', "pai") + notify_ghosts("[user] is requesting a pAI companion! Use the pAI button to submit yourself as one.", source = user, alert_overlay = alert_overlay, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "pAI Request!", ignore_key = POLL_IGNORE_PAI) + addtimer(VARSET_CALLBACK(src, request_spam, FALSE), PAI_SPAM_TIME, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_CLIENT_TIME | TIMER_DELETE_ME) + return TRUE + +/** + * Gathers a list of candidates to display in the download candidate + * window. If the candidate isn't marked ready, ie they have not + * pressed submit, they will be skipped over. + * + * @returns - An array of candidate objects. + */ +/obj/item/pai_card/proc/pool_candidates() + var/list/candidates = list() + if(pai || !length(SSpai?.candidates)) + return candidates + for(var/key in SSpai.candidates) + var/datum/pai_candidate/candidate = SSpai.candidates[key] + if(!candidate?.check_ready()) + continue + candidates += list(list( + ckey = candidate.ckey, + comments = candidate.comments, + description = candidate.description, + name = candidate.name, + )) + return candidates + +/** + * Sets the personality on the current pai_card + * + * @param {silicon/pai} downloaded - The new pAI to load into the card. + */ +/obj/item/pai_card/proc/set_personality(mob/living/silicon/pai/downloaded) + if(pai) + return FALSE + pai = downloaded + emotion_icon = "null" + update_icon() + playsound(src, 'sound/effects/pai_boot.ogg', 50, TRUE, -1) + audible_message("[src] plays a cheerful startup noise!") + return TRUE diff --git a/code/modules/pai/death.dm b/code/modules/pai/death.dm new file mode 100644 index 000000000000..62cd82fe009b --- /dev/null +++ b/code/modules/pai/death.dm @@ -0,0 +1,19 @@ +/mob/living/silicon/pai/death(gibbed) + if(stat == DEAD) + return + set_stat(DEAD) + update_sight() + clear_fullscreens() + /** + * New pAI's get a brand new mind to prevent meta stuff from their previous + * life. This new mind causes problems down the line if it's not deleted here. + */ + ghostize() + + if (!QDELETED(card) && loc != card) + card.forceMove(drop_location()) + card.pai = null + card.emotion_icon = initial(card.emotion_icon) + card.update_icon() + + qdel(src) diff --git a/code/modules/pai/debug.dm b/code/modules/pai/debug.dm new file mode 100644 index 000000000000..63d6a8eb5020 --- /dev/null +++ b/code/modules/pai/debug.dm @@ -0,0 +1,45 @@ +/client/proc/makepAI(turf/target in GLOB.mob_list) + set category = "Admin.Fun" + 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/player as anything in GLOB.player_list) + if(player.client && player.key) + available.Add(player) + var/mob/choice = tgui_input_list(usr, "Choose a player to play the pAI", "Spawn pAI", available) + if(isnull(choice)) + return + + var/chosen_name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text|null + if (isnull(chosen_name)) + return + + if(!isobserver(choice)) + var/confirm = tgui_alert(usr, "[choice.key] isn't ghosting right now. Are you sure you want to yank them out of their body and place them in this pAI?", "Spawn pAI Confirmation", list("Yes", "No")) + if(confirm != "Yes") + return + var/obj/item/pai_card/card = new(target) + var/mob/living/silicon/pai/pai = new(card) + + pai.name = chosen_name + pai.real_name = pai.name + pai.key = choice.key + card.set_personality(pai) + if(SSpai.candidates[key]) + SSpai.candidates -= key + 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! + +/** + * Creates a new pAI. + * + * @param {boolean} delete_old - If TRUE, deletes the old pAI. + */ +/mob/proc/make_pai(delete_old) + var/obj/item/pai_card/card = new(src) + var/mob/living/silicon/pai/pai = new(card) + pai.key = key + pai.name = name + card.set_personality(pai) + if(delete_old) + qdel(src) diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm new file mode 100644 index 000000000000..688b61af1c76 --- /dev/null +++ b/code/modules/pai/defense.dm @@ -0,0 +1,90 @@ +/mob/living/silicon/pai/blob_act(obj/structure/blob/B) + return FALSE + +/mob/living/silicon/pai/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + take_holo_damage(50 / severity) + Stun(40 SECONDS / severity) + if(holoform) + fold_in(force = TRUE) + //Need more effects that aren't instadeath or permanent law corruption. + //Ask and you shall receive + switch(rand(1, 3)) + if(1) + stuttering = 1 MINUTES / severity + to_chat(src, span_danger("Warning: Feedback loop detected in speech module.")) + if(2) + slurring = INFINITY + to_chat(src, span_danger("Warning: Audio synthesizer CPU stuck.")) + if(3) + derpspeech = INFINITY + to_chat(src, span_danger("Warning: Vocabulary databank corrupted.")) + if(prob(40)) + mind.language_holder.selected_language = get_random_spoken_language() + + +/mob/living/silicon/pai/ex_act(severity, target) + take_holo_damage(50 * severity) + switch(severity) + if(EXPLODE_DEVASTATE) //RIP + qdel(card) + qdel(src) + if(EXPLODE_HEAVY) + fold_in(force = 1) + Paralyze(400) + if(EXPLODE_LIGHT) + fold_in(force = 1) + Paralyze(200) + +/mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user, list/modifiers) + if(user.a_intent == INTENT_HELP) + visible_message(span_notice("[user] gently pats [src] on the head, eliciting an off-putting buzzing from its holographic field.")) + return + user.do_attack_animation(src) + if(user.name != master_name) + visible_message(span_danger("[user] stomps on [src]!.")) + take_holo_damage(2) + return + visible_message(span_notice("Responding to its master's touch, [src] disengages its holochassis emitter, rapidly losing coherence.")) + if(!do_after(user, 1 SECONDS, src)) + return + fold_in() + if(user.put_in_hands(card)) + user.visible_message(span_notice("[user] promptly scoops up [user.p_their()] pAI's card.")) + +/mob/living/silicon/pai/bullet_act(obj/item/projectile/Proj) + if(Proj.stun) + fold_in(force = TRUE) + src.visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!")) + . = ..(Proj) + +/mob/living/silicon/pai/ignite_mob(silent) + return FALSE + +/mob/living/silicon/pai/proc/take_holo_damage(amount) + holochassis_health = clamp((holochassis_health - amount), -50, HOLOCHASSIS_MAX_HEALTH) + if(holochassis_health < 0) + fold_in(force = TRUE) + if(amount > 0) + to_chat(src, span_userdanger("The impact degrades your holochassis!")) + return amount + +/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) + return take_holo_damage(amount) + +/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status) + return take_holo_damage(amount) + +/mob/living/silicon/pai/adjustStaminaLoss(amount, updating_stamina, forced = FALSE, required_status) + if(forced) + take_holo_damage(amount) + else + take_holo_damage(amount * 0.25) + +/mob/living/silicon/pai/getBruteLoss() + return HOLOCHASSIS_MAX_HEALTH - holochassis_health + +/mob/living/silicon/pai/getFireLoss() + return HOLOCHASSIS_MAX_HEALTH - holochassis_health diff --git a/code/modules/pai/door_jack.dm b/code/modules/pai/door_jack.dm new file mode 100644 index 000000000000..50013cf5862c --- /dev/null +++ b/code/modules/pai/door_jack.dm @@ -0,0 +1,129 @@ +#define CABLE_LENGTH 2 + +/** + * Switch that handles door jack operations. + * + * @param {string} mode - The requested operation of the door jack. + * + * @returns {boolean} - TRUE if the door jack state was switched, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/door_jack(mode) + if(isnull(mode)) + return FALSE + switch(mode) + if(PAI_DOOR_JACK_CABLE) + extend_cable() + return TRUE + if(PAI_DOOR_JACK_HACK) + hack_door() + return TRUE + if(PAI_DOOR_JACK_CANCEL) + QDEL_NULL(hacking_cable) + visible_message(span_notice("The cable retracts into the pAI.")) + return TRUE + return FALSE + +/** + * #Extend cable supporting proc + * + * When doorjack is installed, allows the pAI to drop + * a cable which is placed either on the floor or in + * someone's hands based (on distance). + * + * @returns {boolean} - TRUE if the cable was dropped, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/extend_cable() + QDEL_NULL(hacking_cable) //clear any old cables + hacking_cable = new + var/mob/living/carbon/hacker = get_holder() + if(iscarbon(hacker) && hacker.put_in_hands(hacking_cable)) //important to double check since get_holder can return non-null values that aren't carbons. + hacker.visible_message(span_notice("A port on [src] opens to reveal a cable, which [hacker] quickly grabs."), span_notice("A port on [src] opens to reveal a cable, which you quickly grab."), span_hear("You hear the soft click of a plastic component and manage to catch the falling cable.")) + track_pai() + track_thing(hacking_cable) + return TRUE + hacking_cable.forceMove(drop_location()) + hacking_cable.visible_message(message = span_notice("A port on [src] opens to reveal a cable, which promptly falls to the floor."), blind_message = span_hear("You hear the soft click of a plastic component fall to the ground.")) + track_pai() + track_thing(hacking_cable) + return TRUE + +/** Tracks the associated pai */ +/mob/living/silicon/pai/proc/track_pai() + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + RegisterSignal(card, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + +/** Untracks the associated pai */ +/mob/living/silicon/pai/proc/untrack_pai() + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) + UnregisterSignal(card, COMSIG_MOVABLE_MOVED) + +/** Tracks the associated hacking_cable */ +/mob/living/silicon/pai/proc/track_thing(atom/movable/thing) + RegisterSignal(thing, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + var/list/locations = get_nested_locs(thing, include_turf = FALSE) + for(var/atom/movable/location in locations) + RegisterSignal(location, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + +/** Untracks the associated hacking */ +/mob/living/silicon/pai/proc/untrack_thing(atom/movable/thing) + UnregisterSignal(thing, COMSIG_MOVABLE_MOVED) + var/list/locations = get_nested_locs(thing, include_turf = FALSE) + for(var/atom/movable/location in locations) + UnregisterSignal(location, COMSIG_MOVABLE_MOVED) + +/** + * A periodic check to see if the source pAI is nearby. + * Deletes the extended cable if the source pAI is not nearby. + */ +/mob/living/silicon/pai/proc/handle_move(atom/movable/source, atom/movable/old_loc) + if(ismovable(old_loc)) + untrack_thing(old_loc) + if(hacking_cable && (!IN_GIVEN_RANGE(src, hacking_cable, CABLE_LENGTH))) + retract_cable() + return + if(ismovable(source.loc)) + track_thing(source.loc) + +/** + * Handles deleting the hacking cable and notifying the user. + */ +/mob/living/silicon/pai/proc/retract_cable() + balloon_alert(src, "cable retracted") + untrack_pai() + QDEL_NULL(hacking_cable) + return TRUE + +/** + * #Door jacking supporting proc + * + * After a 15 second timer, the door will crack open, + * provided they don't move out of the way. + * + * @returns {boolean} - TRUE if the door was jacked, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/hack_door() + if(!hacking_cable) + return FALSE + if(!hacking_cable.machine) + balloon_alert(src, "nothing connected") + return FALSE + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE) + balloon_alert(src, "overriding...") + // Now begin hacking + if(!do_after(src, 15 SECONDS, hacking_cable.machine, timed_action_flags = NONE, progress = TRUE)) + balloon_alert(src, "failed! retracting...") + untrack_pai() + untrack_thing(hacking_cable) + QDEL_NULL(hacking_cable) + if(!QDELETED(card)) + card.update_icon() + return FALSE + var/obj/machinery/door/door = hacking_cable.machine + balloon_alert(src, "success") + door.open() + untrack_pai() + untrack_thing(hacking_cable) + QDEL_NULL(hacking_cable) + return TRUE + +#undef CABLE_LENGTH diff --git a/code/modules/pai/hud.dm b/code/modules/pai/hud.dm new file mode 100644 index 000000000000..52df720e507e --- /dev/null +++ b/code/modules/pai/hud.dm @@ -0,0 +1,267 @@ +#define PAI_MISSING_SOFTWARE_MESSAGE span_warning("You must download the required software to use this.") + +/atom/movable/screen/pai + icon = 'icons/mob/screen_pai.dmi' + var/required_software + +/atom/movable/screen/pai/Click() + if(isobserver(usr) || usr.incapacitated()) + return FALSE + var/mob/living/silicon/pai/user = usr + if(required_software && !user.installed_software.Find(required_software)) + to_chat(user, PAI_MISSING_SOFTWARE_MESSAGE) + return FALSE + return TRUE + +/atom/movable/screen/pai/software + name = "Software Interface" + icon_state = "pai" + +/atom/movable/screen/pai/software/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.ui_interact(pAI) + +/atom/movable/screen/pai/shell + name = "Toggle Holoform" + icon_state = "pai_holoform" + +/atom/movable/screen/pai/shell/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + if(pAI.holoform) + pAI.fold_in(0) + else + pAI.fold_out() + +/atom/movable/screen/pai/chassis + name = "Holochassis Appearance Composite" + icon_state = "pai_chassis" + +/atom/movable/screen/pai/chassis/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.choose_chassis() + +/atom/movable/screen/pai/rest + name = "Rest" + icon_state = "pai_rest" + +/atom/movable/screen/pai/rest/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + if(pAI.resting) + pAI.set_resting(FALSE) + else + pAI.set_resting(TRUE) + +/atom/movable/screen/pai/light + name = "Toggle Integrated Lights" + icon_state = "light" + +/atom/movable/screen/pai/light/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.toggle_integrated_light() + +/atom/movable/screen/pai/newscaster + name = "pAI Newscaster" + icon_state = "newscaster" + required_software = "Newscaster" + +/atom/movable/screen/pai/newscaster/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.newscaster.ui_interact(usr) + +/atom/movable/screen/pai/host_monitor + name = "Host Health Scan" + icon_state = "host_monitor" + required_software = "Host Scan" + +/atom/movable/screen/pai/host_monitor/Click(location, control, params) + . = ..() + if(!.) + return + var/mob/living/silicon/pai/pAI = usr + pAI.host_scan(PAI_SCAN_TARGET) + return TRUE + +/atom/movable/screen/pai/crew_manifest + name = "Crew Manifest" + icon_state = "manifest" + required_software = "Crew Manifest" + +/atom/movable/screen/pai/crew_manifest/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.ai_roster() + +/atom/movable/screen/pai/state_laws + name = "State Laws" + icon_state = "state_laws" + +/atom/movable/screen/pai/state_laws/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.checklaws() + +/atom/movable/screen/pai/modpc + name = "Messenger" + icon_state = "pda_send" + required_software = "Digital Messenger" + var/mob/living/silicon/pai/pAI + +/atom/movable/screen/pai/modpc/Click() + . = ..() + if(!.) // this works for some reason. + return + pAI.modularInterface?.interact(pAI) + +/atom/movable/screen/pai/internal_gps + name = "Internal GPS" + icon_state = "internal_gps" + required_software = "Internal GPS" + +/atom/movable/screen/pai/internal_gps/Click() + . = ..() + if(!.) + return + var/mob/living/silicon/pai/pAI = usr + if(!pAI.internal_gps) + pAI.internal_gps = new(pAI) + pAI.internal_gps.attack_self(pAI) + +/atom/movable/screen/pai/image_take + name = "Take Image" + icon_state = "take_picture" + required_software = "Photography Module" + +/atom/movable/screen/pai/image_take/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.camera.toggle_camera_mode(usr) + +/atom/movable/screen/pai/image_view + name = "View Images" + icon_state = "view_images" + required_software = "Photography Module" + +/atom/movable/screen/pai/image_view/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.camera.viewpictures(usr) + +/atom/movable/screen/pai/radio + name = "radio" + icon = 'icons/mob/screen_cyborg.dmi' + icon_state = "radio" + +/atom/movable/screen/pai/radio/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.radio.interact(usr) + +/datum/hud/pai/New(mob/living/silicon/pai/owner) + ..() + var/atom/movable/screen/using + var/mob/living/silicon/pai/mypai = mymob + +// Software menu + using = new /atom/movable/screen/pai/software + using.screen_loc = ui_pai_software + static_inventory += using + +// Holoform + using = new /atom/movable/screen/pai/shell + using.screen_loc = ui_pai_shell + static_inventory += using + +// Chassis Select Menu + using = new /atom/movable/screen/pai/chassis + using.screen_loc = ui_pai_chassis + static_inventory += using + +// Rest + using = new /atom/movable/screen/pai/rest + using.screen_loc = ui_pai_rest + static_inventory += using + +// Integrated Light + using = new /atom/movable/screen/pai/light + using.screen_loc = ui_pai_light + static_inventory += using + +// Newscaster + using = new /atom/movable/screen/pai/newscaster + using.screen_loc = ui_pai_newscaster + static_inventory += using + +// Language menu + using = new /atom/movable/screen/language_menu + using.screen_loc = ui_pai_language_menu + static_inventory += using + +// Host Monitor + using = new /atom/movable/screen/pai/host_monitor() + using.screen_loc = ui_pai_host_monitor + static_inventory += using + +// Crew Manifest + using = new /atom/movable/screen/pai/crew_manifest() + using.screen_loc = ui_pai_crew_manifest + static_inventory += using + +// Laws + using = new /atom/movable/screen/pai/state_laws() + using.screen_loc = ui_pai_state_laws + static_inventory += using + +// Modular Interface + using = new /atom/movable/screen/pai/modpc() + using.screen_loc = ui_pai_mod_int + static_inventory += using + mypai.pda_button = using + var/atom/movable/screen/pai/modpc/tablet_button = using + tablet_button.pAI = mypai + +// Internal GPS + using = new /atom/movable/screen/pai/internal_gps() + using.screen_loc = ui_pai_internal_gps + static_inventory += using + +// Take image + using = new /atom/movable/screen/pai/image_take() + using.screen_loc = ui_pai_take_picture + static_inventory += using + +// View images + using = new /atom/movable/screen/pai/image_view() + using.screen_loc = ui_pai_view_images + static_inventory += using + +// Radio + using = new /atom/movable/screen/pai/radio() + using.screen_loc = ui_pai_radio + static_inventory += using + + update_software_buttons() + +/datum/hud/pai/proc/update_software_buttons() + var/mob/living/silicon/pai/owner = mymob + for(var/atom/movable/screen/pai/button in static_inventory) + if(button.required_software) + button.color = owner.installed_software.Find(button.required_software) ? null : "#808080" + +#undef PAI_MISSING_SOFTWARE_MESSAGE diff --git a/code/modules/pai/login.dm b/code/modules/pai/login.dm new file mode 100644 index 000000000000..0f418c4bf32b --- /dev/null +++ b/code/modules/pai/login.dm @@ -0,0 +1,10 @@ +/mob/living/silicon/pai/Login() + . = ..() + if(!. || !client) + return FALSE + + client.perspective = EYE_PERSPECTIVE + if(holoform) + client.eye = src + else + client.eye = card diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm new file mode 100644 index 000000000000..71a1013a4c19 --- /dev/null +++ b/code/modules/pai/pai.dm @@ -0,0 +1,412 @@ +/mob/living/silicon/pai + can_be_held = TRUE + density = FALSE + desc = "A generic pAI hard-light holographics emitter." + health = 500 + held_lh = 'icons/mob/pai_item_lh.dmi' + held_rh = 'icons/mob/pai_item_rh.dmi' + held_icon = 'icons/mob/pai_item_head.dmi' + hud_type = /datum/hud/pai + icon = 'icons/mob/pai.dmi' + icon_state = "repairbot" + job = "Personal AI" + layer = BELOW_MOB_LAYER + light_color = COLOR_PAI_GREEN + light_flags = LIGHT_ATTACHED + light_on = FALSE + light_range = 3 + light_system = MOVABLE_LIGHT + maxHealth = 500 + mob_size = MOB_SIZE_TINY + mobility_flags = MOBILITY_FLAGS_DEFAULT + mouse_opacity = MOUSE_OPACITY_ICON + move_force = 0 + move_resist = 0 + name = "pAI" + pass_flags = PASSTABLE | PASSMOB + pull_force = 0 + radio = /obj/item/radio/headset/silicon/pai + worn_slot_flags = ITEM_SLOT_HEAD + + /// If someone has enabled/disabled the pAIs ability to holo + var/can_holo = TRUE + /// Whether this pAI can recieve radio messages + var/can_receive = TRUE + /// Whether this pAI can transmit radio messages + var/can_transmit = TRUE + /// The card we inhabit + var/obj/item/pai_card/card + /// The current chasis that will appear when in holoform + var/chassis = "repairbot" + /// Toggles whether the pAI can hold encryption keys or not + var/encrypt_mod = FALSE + /// The cable we produce when hacking a door + var/obj/item/pai_cable/hacking_cable + /// The current health of the holochassis + var/holochassis_health = 20 + /// Holochassis available to use + var/holochassis_ready = FALSE + /// Whether we are currently holoformed + var/holoform = FALSE + /// Installed software on the pAI + var/list/installed_software = list() + /// Toggles whether universal translator has been activated. Cannot be reversed + var/languages_granted = FALSE + /// Reference of the bound master + var/datum/weakref/master_ref + /// The master's name string + var/master_name + /// DNA string for owner verification + var/master_dna + /// Toggles whether the Medical HUD is active or not + var/medHUD = FALSE + /// Used as currency to purchase different abilities + var/ram = 100 + /// Toggles whether the Security HUD is active or not + var/secHUD = FALSE + + // Onboard Items + /// Atmospheric analyzer + var/obj/item/analyzer/atmos_analyzer + /// Health analyzer + var/obj/item/healthanalyzer/host_scan + /// GPS + var/obj/item/gps/internal/internal_gps + /// Music Synthesizer + var/obj/item/instrument/piano_synth/instrument + /// Newscaster + var/obj/machinery/newscaster/pai/newscaster + /// PDA + var/atom/movable/screen/ai/modpc/pda_button + /// Photography module + var/obj/item/camera/siliconcam/pai_camera/camera + /// Remote signaler + var/obj/item/assembly/signaler/internal/signaler + + // Static lists + /// List of all available downloads + var/static/list/available_software = list( + "Atmospheric Sensor" = 5, + "Crew Manifest" = 5, + "Digital Messenger" = 5, + "Photography Module" = 5, + "Encryption Slot" = 10, + "Music Synthesizer" = 10, + "Newscaster" = 10, + "Remote Signaler" = 10, + "Host Scan" = 20, + "Medical HUD" = 20, + "Security HUD" = 20, + "Crew Monitor" = 35, + "Door Jack" = 35, + "Internal GPS" = 35, + "Universal Translator" = 35, + ) + /// List of all possible chasises. TRUE means the pAI can be picked up in this chasis. + var/static/list/possible_chassis = list( + "cat" = TRUE, + "corgi" = FALSE, + "fox" = FALSE, + "monkey" = TRUE, + "mouse" = TRUE, + "rabbit" = TRUE, + ) + /// List of all available card overlays. + var/static/list/possible_overlays = list( + "null", + "angry", + "cat", + "extremely-happy", + "face", + "happy", + "laugh", + "off", + "sad", + "sunglasses", + "what" + ) + +/mob/living/silicon/pai/add_sensors() //pAIs have to buy their HUDs + return + +/mob/living/silicon/pai/can_interact_with(atom/target) + if(target == signaler) // Bypass for signaler + return TRUE + if(target == modularInterface) + return TRUE + return ..() + +/mob/living/silicon/pai/Destroy() + QDEL_NULL(atmos_analyzer) + QDEL_NULL(camera) + QDEL_NULL(hacking_cable) + QDEL_NULL(host_scan) + QDEL_NULL(instrument) + QDEL_NULL(internal_gps) + QDEL_NULL(newscaster) + QDEL_NULL(signaler) + card = null + GLOB.pai_list.Remove(src) + return ..() + +/mob/living/silicon/pai/emag_act(mob/user) + handle_emag(user) + +/mob/living/silicon/pai/examine(mob/user) + . = ..() + . += "Its master ID string seems to be [(!master_name || emagged) ? "empty" : master_name]." + +/mob/living/silicon/pai/get_status_tab_items() + . += ..() + if(!stat) + . += text("Emitter Integrity: [holochassis_health * (100 / HOLOCHASSIS_MAX_HEALTH)].") + else + . += text("Systems nonfunctional.") + +/mob/living/silicon/pai/handle_atom_del(atom/deleting_atom) + if(deleting_atom == hacking_cable) + untrack_pai() + untrack_thing(hacking_cable) + hacking_cable = null + SStgui.update_user_uis(src) + if(!QDELETED(card)) + card.update_icon() + if(deleting_atom == atmos_analyzer) + atmos_analyzer = null + if(deleting_atom == camera) + camera = null + if(deleting_atom == host_scan) + host_scan = null + if(deleting_atom == internal_gps) + internal_gps = null + if(deleting_atom == instrument) + instrument = null + if(deleting_atom == newscaster) + newscaster = null + if(deleting_atom == signaler) + signaler = null + return ..() + +/mob/living/silicon/pai/Initialize(mapload) + . = ..() + START_PROCESSING(SSfastprocess, src) + GLOB.pai_list += src + make_laws() + for(var/law in laws.inherent) + lawcheck += law + var/obj/item/pai_card/pai_card = loc + if(!istype(pai_card)) // when manually spawning a pai, we create a card to put it into. + var/newcardloc = pai_card + pai_card = new(newcardloc) + pai_card.set_personality(src) + forceMove(pai_card) + card = pai_card + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_INIT_TIME) + if(!holoform) + Immobilize(50 MINUTES) + desc = "A pAI hard-light holographics emitter. This one appears in the form of a [chassis]." + + RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) + +/mob/living/silicon/pai/make_laws() + laws = new /datum/ai_laws/pai() + return TRUE + +/mob/living/silicon/pai/process(delta_time) + holochassis_health = clamp((holochassis_health + (HOLOCHASSIS_REGEN_PER_SECOND * delta_time)), -50, HOLOCHASSIS_MAX_HEALTH) + +/mob/living/silicon/pai/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) + . = ..() + if(!.) + add_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK, update = TRUE, priority = 100, multiplicative_slowdown = 2) + return TRUE + remove_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK, TRUE) + return TRUE + +/mob/living/silicon/pai/screwdriver_act(mob/living/user, obj/item/tool) + return radio.screwdriver_act(user, tool) + +/mob/living/silicon/pai/updatehealth() + if(status_flags & GODMODE) + return + set_health(maxHealth - getBruteLoss() - getFireLoss()) + update_stat() + SEND_SIGNAL(src, COMSIG_LIVING_HEALTH_UPDATE) + +/** + * Resolves the weakref of the pai's master. + * If the master has been deleted, calls reset_software(). + * + * @returns {mob/living || FALSE} - The master mob, or + * FALSE if the master is gone. + */ +/mob/living/silicon/pai/proc/find_master() + if(!master_ref) + return FALSE + var/mob/living/resolved_master = master_ref?.resolve() + if(!resolved_master) + reset_software() + return FALSE + return resolved_master + +/** + * Fixes weird speech issues with the pai. + * + * @returns {boolean} - TRUE if successful. + */ +/mob/living/silicon/pai/proc/fix_speech() + var/mob/living/silicon/pai = src + balloon_alert(pai, "speech modulation corrected") + stuttering = 0 + slurring = 0 + derpspeech = 0 + + return TRUE + +/** + * Gets the current holder of the pAI if its + * being carried in card or holoform. + * + * @returns {living/carbon || FALSE} - The holder of the pAI, + * or FALSE if the pAI is not being carried. + */ +/mob/living/silicon/pai/proc/get_holder() + var/mob/living/carbon/holder + if(!holoform && iscarbon(card.loc)) + holder = card.loc + if(holoform && ispickedupmob(loc) && iscarbon(loc.loc)) + holder = loc.loc + if(!holder || !iscarbon(holder)) + return FALSE + return holder + +/** + * Handles the pai card or the pai itself being hit with an emag. + * This replaces any current laws, masters, and DNA. + * + * @param {living/carbon} attacker - The user performing the action. + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/handle_emag(mob/living/carbon/attacker) + if(!isliving(attacker)) + return FALSE + balloon_alert(attacker, "directive override complete") + balloon_alert(src, "directive override detected") + log_game("[key_name(attacker)] emagged [key_name(src)], wiping their master DNA and supplemental directive.") + emagged = TRUE + master_ref = WEAKREF(attacker) + master_name = "The Syndicate" + master_dna = "Untraceable Signature" + // Sets supplemental directive to this + laws.supplied[1] = "Do not interfere with the operations of the Syndicate." + return TRUE + +/** + * Resets the pAI and any emagged status. + * + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/reset_software() + emagged = FALSE + if(!master_ref) + return FALSE + master_ref = null + master_name = null + master_dna = null + add_supplied_law(0, "None.") + balloon_alert(src, "software rebooted") + return TRUE + +/** + * Imprints your DNA onto the downloaded pAI + * + * @param {mob} user - The user performing the imprint. + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/set_dna(mob/user) + if(!iscarbon(user)) + balloon_alert(user, "incompatible DNA signature") + balloon_alert(src, "incompatible DNA signature") + return FALSE + if(emagged) + balloon_alert(user, "directive system malfunctional") + return FALSE + var/mob/living/carbon/master = user + master_ref = WEAKREF(master) + master_name = master.real_name + master_dna = master.dna.unique_enzymes + to_chat(src, span_boldannounce("You have been bound to a new master: [user.real_name]!")) + holochassis_ready = TRUE + return TRUE + +/** + * Opens a tgui alert that allows someone to enter laws. + * + * @param {mob} user - The user performing the law change. + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/set_laws(mob/user) + if(!master_ref) + balloon_alert(user, "access denied: no master") + return FALSE + var/new_laws = tgui_input_text(user, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", laws.supplied[1], 300) + if(!new_laws || !master_ref) + return FALSE + add_supplied_law(0, new_laws) + to_chat(src, span_notice(new_laws)) + return TRUE + +/** + * Toggles the ability of the pai to enter holoform + * + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/toggle_holo() + balloon_alert(src, "holomatrix [can_holo ? "disabled" : "enabled"]") + can_holo = !can_holo + return TRUE + +/** + * Toggles the radio settings on and off. + * + * @param {string} option - The option being toggled. + */ +/mob/living/silicon/pai/proc/toggle_radio(option) + // it can't be both so if we know it's not transmitting it must be receiving. + var/transmitting = option == "transmit" + var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) + if(transmitting) + can_transmit = !can_transmit + else //receiving + can_receive = !can_receive + radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states + transmit_holder = (transmitting ? can_transmit : can_receive) //recycling can be fun! + balloon_alert(src, "[transmitting ? "outgoing" : "incoming"] radio [transmit_holder ? "enabled" : "disabled"]") + return TRUE + +/** + * Wipes the current pAI on the card. + * + * @param {mob} user - The user performing the action. + * + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/wipe_pai(mob/user) + if(tgui_alert(user, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No")) != "Yes") + return FALSE + to_chat(src, span_warning("You feel yourself slipping away from reality.")) + to_chat(src, span_danger("Byte by byte you lose your sense of self.")) + to_chat(src, span_userdanger("Your mental faculties leave you.")) + to_chat(src, span_rose("oblivion... ")) + balloon_alert(user, "personality wiped") + playsound(src, "sound/machines/buzz-two.ogg", 30, TRUE) + qdel(src) + return TRUE + +/// Signal proc for [COMSIG_LIVING_CULT_SACRIFICED] to give a funny message when a pai is attempted to be sac'd +/mob/living/silicon/pai/proc/on_cult_sacrificed(datum/source, list/invokers) + SIGNAL_HANDLER + + for(var/mob/living/cultist as anything in invokers) + to_chat(cultist, span_cultitalic("You don't think this is what Nar'Sie had in mind when She asked for blood sacrifices...")) + return STOP_SACRIFICE diff --git a/code/modules/pai/personality.dm b/code/modules/pai/personality.dm new file mode 100644 index 000000000000..92cf5747801f --- /dev/null +++ b/code/modules/pai/personality.dm @@ -0,0 +1,56 @@ +/** + * name + * key + * description + * role + * comments + * ready = TRUE + */ + +/datum/pai_candidate/proc/savefile_path(mob/user) + return "data/player_saves/[user.ckey[1]]/[user.ckey]/pai.sav" + +/datum/pai_candidate/proc/savefile_save(mob/user) + if(is_guest_key(user.key)) + to_chat(usr, span_warning("You cannot save pAI information as a guest.")) + return FALSE + var/savefile/F = new /savefile(src.savefile_path(user)) + WRITE_FILE(F["name"], name) + WRITE_FILE(F["description"], description) + WRITE_FILE(F["comments"], comments) + WRITE_FILE(F["version"], 1) + to_chat(usr, span_boldnotice("You have saved pAI information locally.")) + return TRUE + +// loads the savefile corresponding to the mob's ckey +// if silent=true, report incompatible savefiles +// returns TRUE if loaded (or file was incompatible) +// returns FALSE if savefile did not exist + +/datum/pai_candidate/proc/savefile_load(mob/user, silent = TRUE) + if (is_guest_key(user.key)) + return FALSE + + var/path = savefile_path(user) + + if (!fexists(path)) + return FALSE + + var/savefile/F = new /savefile(path) + + if(!F) + return //Not everyone has a pai savefile. + + var/version = null + F["version"] >> version + + if (isnull(version) || version != 1) + fdel(path) + if (!silent) + tgui_alert(user, "Your savefile was incompatible with this version and was deleted.") + return FALSE + + F["name"] >> src.name + F["description"] >> src.description + F["comments"] >> src.comments + return TRUE diff --git a/code/modules/pai/say.dm b/code/modules/pai/say.dm new file mode 100644 index 000000000000..b35abfe7f9d8 --- /dev/null +++ b/code/modules/pai/say.dm @@ -0,0 +1,2 @@ +/mob/living/silicon/pai/binarycheck() + return radio?.translate_binary diff --git a/code/modules/pai/shell.dm b/code/modules/pai/shell.dm new file mode 100644 index 000000000000..66e2e8bfb279 --- /dev/null +++ b/code/modules/pai/shell.dm @@ -0,0 +1,169 @@ +/mob/living/silicon/pai/mob_try_pickup(mob/living/user, instant=FALSE) + if(!possible_chassis[chassis]) + to_chat(user, span_warning("[src]'s current form isn't able to be carried!")) + return FALSE + return ..() + +/mob/living/silicon/pai/start_pulling(atom/movable/thing, state, force = move_force, supress_message = FALSE) + return FALSE + +/mob/living/silicon/pai/update_resting() + . = ..() + if(resting) + icon_state = "[chassis]_rest" + else + icon_state = "[chassis]" + if(loc != card) + visible_message(span_notice("[src] [resting? "lays down for a moment..." : "perks up from the ground."]")) + +/mob/living/silicon/pai/wabbajack_act(what_to_randomize, change_flags = WABBAJACK) + if(length(possible_chassis) < 2) + return FALSE + var/holochassis = pick(possible_chassis - chassis) + set_holochassis(holochassis) + balloon_alert(src, "[holochassis] composite engaged") + return TRUE + +/** + * Checks if we are allowed to interact with a radial menu + * + * @param {atom} anchor - The atom that is anchoring the menu. + * + * @returns {boolean} - TRUE if we are allowed to interact with the menu, + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/check_menu(atom/anchor) + if(incapacitated()) + return FALSE + if(get_turf(src) != get_turf(anchor)) + return FALSE + if(!isturf(loc) && loc != card) + balloon_alert(src, "can't do that here") + return FALSE + return TRUE + +/** + * Sets a new holochassis skin based on a pAI's choice. + * + * @returns {boolean} - True if the skin was successfully set. + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/choose_chassis() + var/list/skins = list() + for(var/holochassis_option in possible_chassis) + var/image/item_image = image(icon = src.icon, icon_state = holochassis_option) + skins += list("[holochassis_option]" = item_image) + var/atom/anchor = get_atom_on_turf(src) + var/choice = show_radial_menu(src, anchor, skins, custom_check = CALLBACK(src, PROC_REF(check_menu), anchor), radius = 40, require_near = TRUE) + if(!choice) + return FALSE + set_holochassis(choice) + balloon_alert(src, "[choice] composite engaged") + update_resting() + return TRUE + +/** + * Returns the pAI to card mode. + * + * @param {boolean} force - If TRUE, the pAI will be forced to card mode. + * + * @returns {boolean} - TRUE if the pAI was forced to card mode. + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/fold_in(force = FALSE) + holochassis_ready = FALSE + if(!force) + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN) + else + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_OVERLOAD_COOLDOWN) + icon_state = "[chassis]" + if(!holoform) + . = fold_out(force) + return FALSE + visible_message(span_notice("[src] deactivates its holochassis emitter and folds back into a compact card!")) + stop_pulling() + if(ispickedupmob(loc)) + var/obj/item/clothing/mob_holder/mob_head = loc + mob_head.release(display_messages = FALSE) + if(client) + client.perspective = EYE_PERSPECTIVE + var/turf/target = drop_location() + card.forceMove(target) + forceMove(card) + Immobilize(30 MINUTES) + set_density(FALSE) + set_light_on(FALSE) + holoform = FALSE + set_resting(resting) + return TRUE + +/** + * Engage holochassis form. + * + * @param {boolean} force - Force the form to engage. + * + * @returns {boolean} - TRUE if the form was successfully engaged. + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/fold_out(force = FALSE) + if(holochassis_health < 0) + balloon_alert(src, "emitter repair incomplete") + return FALSE + if(!can_holo && !force) + balloon_alert(src, "emitters are disabled") + return FALSE + if(holoform) + . = fold_in(force) + return + if(!holochassis_ready) + balloon_alert(src, "emitters recycling...") + return FALSE + holochassis_ready = FALSE + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN) + SetImmobilized(0) + set_density(TRUE) + if(istype(card.loc, /obj/item/modular_computer)) + var/obj/item/modular_computer/pc = card.loc + pc.inserted_pai = null + pc.visible_message(span_notice("[src] ejects itself from [pc]!")) + if(isliving(card.loc)) + var/mob/living/living_holder = card.loc + if(!living_holder.temporarilyRemoveItemFromInventory(card)) + balloon_alert(src, "unable to expand") + return FALSE + forceMove(get_turf(card)) + card.forceMove(src) + if(client) + client.perspective = EYE_PERSPECTIVE + client.eye = src + set_light_on(FALSE) + icon_state = "[chassis]" + held_state = "[chassis]" + visible_message(span_boldnotice("[src] folds out its holochassis emitter and forms a holoshell around itself!")) + holoform = TRUE + return TRUE + +/** + * Sets the holochassis skin and updates the icons + * + * @param {string} choice - The skin that will be used for the pAI holoform + * + * @returns {boolean} - TRUE if the skin was successfully set. FALSE otherwise. + */ +/mob/living/silicon/pai/proc/set_holochassis(choice) + if(!choice) + return FALSE + chassis = choice + icon_state = "[chassis]" + held_state = "[chassis]" + desc = "A pAI mobile hard-light holographics emitter. This one appears in the form of a [chassis]." + return TRUE + +/** + * Toggles the onboard light + * + * @returns {boolean} - TRUE if the light was toggled. + */ +/mob/living/silicon/pai/proc/toggle_integrated_light() + set_light_on(!light_on) + return TRUE diff --git a/code/modules/pai/software.dm b/code/modules/pai/software.dm new file mode 100644 index 000000000000..1ff2e02d3585 --- /dev/null +++ b/code/modules/pai/software.dm @@ -0,0 +1,242 @@ +/mob/living/silicon/pai/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PaiInterface", name) + ui.open() + ui.set_autoupdate(FALSE) + +/mob/living/silicon/pai/ui_data(mob/user) + var/list/data = list() + data["door_jack"] = hacking_cable + data["image"] = card.emotion_icon + data["installed"] = installed_software + data["ram"] = ram + return data + +/mob/living/silicon/pai/ui_static_data(mob/user) + var/list/data = list() + data["available"] = available_software + data["directives"] = laws.supplied + data["emagged"] = emagged + data["languages"] = languages_granted + data["master_name"] = master_name + data["master_dna"] = master_dna + return data + +/mob/living/silicon/pai/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return TRUE + if(action == "buy") + buy_software(params["selection"]) + return TRUE + if(action == "change image") + change_image() + return TRUE + if(action == "check dna") + check_dna() + return TRUE + // Software related ui actions + if(available_software[action] && !installed_software.Find(action)) + balloon_alert(usr, "software unavailable") + return FALSE + switch(action) + if("Atmospheric Sensor") + atmos_analyzer.attack_self(src) + return TRUE + if("Crew Manifest") + ai_roster() + return TRUE + if("Crew Monitor") + GLOB.crewmonitor.show(usr, src) + return TRUE + if("Digital Messenger") + modularInterface?.interact(usr) + return TRUE + if("Door Jack") + // Look to door_jack.dm for implementation + door_jack(params["mode"]) + return TRUE + if("Encryption Slot") + balloon_alert(usr, "radio frequencies [!encrypt_mod ? "enabled" : "disabled"]") + encrypt_mod = !encrypt_mod + radio.subspace_transmission = !radio.subspace_transmission + return TRUE + if("Host Scan") + host_scan(params["mode"]) + return TRUE + if("Internal GPS") + internal_gps.attack_self(src) + return TRUE + if("Music Synthesizer") + instrument.interact(src) + return TRUE + if("Medical HUD") + toggle_hud(PAI_TOGGLE_MEDICAL_HUD) + return TRUE + if("Newscaster") + newscaster.ui_interact(src) + return TRUE + if("Photography Module") + // Look to pai_camera.dm for implementation + use_camera(usr, params["mode"]) + return TRUE + if("Remote Signaler") + signaler.ui_interact(src) + return TRUE + if("Security HUD") + toggle_hud(PAI_TOGGLE_SECURITY_HUD) + return TRUE + if("Universal Translator") + grant_languages() + ui.send_full_update() + return TRUE + return FALSE + +/** + * Purchases the selected software from the list and deducts their + * available ram. + * + * @param {string} selection - The software to purchase. + * + * @returns {boolean} - TRUE if the software was purchased, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/buy_software(selection) + if(!available_software[selection] || installed_software.Find(selection)) + return FALSE + var/cost = available_software[selection] + if(ram < cost) + return FALSE + installed_software.Add(selection) + ram -= cost + var/datum/hud/pai/pAIhud = hud_used + pAIhud?.update_software_buttons() + switch(selection) + if("Atmospheric Sensor") + atmos_analyzer = new(src) + if("Digital Messenger") + create_modularInterface() + if("Host Scan") + host_scan = new(src) + if("Internal GPS") + internal_gps = new(src) + if("Music Synthesizer") + instrument = new(src) + if("Newscaster") + newscaster = new(src) + if("Photography Module") + camera = new(src) + if("Remote Signaler") + signaler = new(src) + return TRUE + +/** + * Changes the image displayed on the pAI. + * + * @param {mob} user - The user who is changing the image. + * + * @returns {boolean} - TRUE if the image was changed, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/change_image() + var/new_image = tgui_input_list(src, "Select your new display image", "Display Image", possible_overlays) + if(isnull(new_image)) + return FALSE + card.emotion_icon = new_image + card.update_icon() + return TRUE + +/** + * Supporting proc for the pAI to prick it's master's hand + * or... whatever. It must be held in order to work + * Gives the owner a popup if they want to get the jab. + * + * @returns {boolean} - TRUE if a sample was taken, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/check_dna() + if(emagged) // Their master DNA signature is scrambled anyway + to_chat(src, span_syndradio("You are not at liberty to do this! All agents are clandestine.")) + return FALSE + var/mob/living/carbon/holder = get_holder() + if(!iscarbon(holder)) + balloon_alert(src, "not being carried") + return FALSE + balloon_alert(src, "requesting dna sample") + if(tgui_alert(holder, "[src] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "Checking DNA", list("Yes", "No")) != "Yes") + balloon_alert(src, "dna sample refused!") + return FALSE + holder.visible_message(span_notice("[holder] presses [holder.p_their()] thumb against [src]."), span_notice("You press your thumb against [src]."), span_notice("[src] makes a sharp clicking sound as it extracts DNA material from [holder].")) + if(!holder.has_dna()) + balloon_alert(src, "no dna detected!") + return FALSE + to_chat(src, span_boldannounce(("[holder]'s UE string: [holder.dna.unique_enzymes]"))) + to_chat(src, span_notice("DNA [holder.dna.unique_enzymes == master_dna ? "matches" : "does not match"] our stored Master's DNA.")) + return TRUE + +/** + * Grant all languages to the current pAI. + * + * @returns {boolean} - TRUE if the languages were granted, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/grant_languages() + if(languages_granted) + return FALSE + grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE) + languages_granted = TRUE + return TRUE + +/** + * Host scan supporting proc + * + * Allows the pAI to scan its host's health vitals + * using an integrated health analyzer. + * + * @returns {boolean} - TRUE if the scan was successful, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/host_scan(mode) + if(isnull(mode)) + return FALSE + if(mode == PAI_SCAN_TARGET) + var/mob/living/target = get_holder() + if(!target || !isliving(target)) + balloon_alert(src, "not being carried") + return FALSE + host_scan.attack(target, src) + return TRUE + if(mode == PAI_SCAN_MASTER) + if(!master_ref) + balloon_alert(src, "no master detected") + return FALSE + var/mob/living/resolved_master = find_master() + if(!resolved_master) + balloon_alert(src, "cannot locate master") + return FALSE + if(!is_valid_z_level(get_turf(src), get_turf(resolved_master))) + balloon_alert(src, "master out of range") + return FALSE + host_scan.attack(resolved_master, src) + return TRUE + return FALSE + +/** + * Proc that toggles any active huds based on the option. + * + * @param {string} mode - The hud to toggle. + */ +/mob/living/silicon/pai/proc/toggle_hud(mode) + if(isnull(mode)) + return FALSE + var/datum/atom_hud/hud + var/hud_on + if(mode == PAI_TOGGLE_MEDICAL_HUD) + hud = GLOB.huds[med_hud] + medHUD = !medHUD + hud_on = medHUD + if(mode == PAI_TOGGLE_SECURITY_HUD) + hud = GLOB.huds[sec_hud] + secHUD = !secHUD + hud_on = secHUD + if(hud_on) + hud.show_to(src) + else + hud.hide_from(src) + return TRUE diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm index d1d610aba843..f8e59ab8369a 100644 --- a/code/modules/paperwork/contract.dm +++ b/code/modules/paperwork/contract.dm @@ -66,7 +66,7 @@ H.say("OH GREAT INFERNO! I DEMAND YOU COLLECT YOUR BOUNTY IMMEDIATELY!", forced = "infernal contract suicide") H.visible_message(span_suicide("[H] holds up a contract claiming [user.p_their()] soul, then immediately catches fire. It looks like [user.p_theyre()] trying to commit suicide!")) H.adjust_fire_stacks(20) - H.IgniteMob() + H.ignite_mob() return(FIRELOSS) return ..() @@ -237,7 +237,8 @@ /obj/item/paper/contract/infernal/wealth/fulfillContract(mob/living/carbon/human/user = target.current, blood = 0) if(!istype(user) || !user.mind) // How in the hell could that happen? return -1 - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_wealth(null)) + var/datum/action/cooldown/spell/summon_wealth/money = new(user) + money.Grant(user) return ..() /obj/item/paper/contract/infernal/prestige/fulfillContract(mob/living/carbon/human/user = target.current, blood = 0) @@ -278,19 +279,24 @@ /obj/item/paper/contract/infernal/magic/fulfillContract(mob/living/carbon/human/user = target.current, blood = 0) if(!istype(user) || !user.mind) return -1 - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket/robeless(null)) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null)) + var/datum/action/cooldown/spell/conjure_item/spellpacket/spell_packet = new(user) + spell_packet.Grant(user) + + var/datum/action/cooldown/spell/aoe/knock/all_access = new(user) + all_access.Grant(user) return ..() /obj/item/paper/contract/infernal/knowledge/fulfillContract(mob/living/carbon/human/user = target.current, blood = 0) if(!istype(user) || !user.mind) return -1 user.dna.add_mutation(XRAY) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/view_range(null)) + var/datum/action/cooldown/spell/view_range/view_range = new(user) + view_range.Grant(user) return ..() /obj/item/paper/contract/infernal/friend/fulfillContract(mob/living/user = target.current, blood = 0) if(!istype(user) || !user.mind) return -1 - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_friend(null)) + var/datum/action/cooldown/spell/summon_friend/friend = new(user) + friend.Grant(user) return ..() diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm index 4871dc4cc401..5d77ce9e60f7 100644 --- a/code/modules/paperwork/handlabeler.dm +++ b/code/modules/paperwork/handlabeler.dm @@ -15,7 +15,7 @@ var/old_real_name = user.real_name user.real_name += " (suicide)" // no conflicts with their identification card - for(var/atom/A in user.GetAllContents()) + for(var/atom/A in user.get_all_contents()) if(istype(A, /obj/item/card/id)) var/obj/item/card/id/their_card = A diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index c4d5405fc542..94c01b862069 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -322,7 +322,7 @@ span_userdanger("You miss the paper and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 5b1998dda7fa..97985c89f6ff 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -83,7 +83,7 @@ span_userdanger("You miss [src] and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index 63fe8717a945..fcc8d03554f4 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -91,7 +91,7 @@ theirticket.fire_act() user.dropItemToGround(theirticket) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return /obj/machinery/ticket_machine/attackby(obj/item/O, mob/user, params) @@ -163,7 +163,7 @@ user.visible_message(span_warning("[user] accidentally ignites [user.p_them()]self!"), span_userdanger("You miss the ticket and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/pool/components/swimming_felinid.dm b/code/modules/pool/components/swimming_felinid.dm index 31a7355bf9b5..62006b528f11 100644 --- a/code/modules/pool/components/swimming_felinid.dm +++ b/code/modules/pool/components/swimming_felinid.dm @@ -14,10 +14,9 @@ to_chat(parent, "You can't touch the bottom!") L.emote("scream") if(5 to 7) - if(L.confused < 5) - L.confused += 1 + L.adjust_confusion_up_to(1 SECONDS, 5 SECONDS) if(8 to 12) - L.Jitter(10) + L.adjust_jitter(10 SECONDS) if(13 to 14) shake_camera(L, 15, 1) L.emote("whimper") diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index e8bc315f1d3a..2600bf0ebff7 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -1474,10 +1474,9 @@ INVOKE_ASYNC(src, PROC_REF(break_lights)) /obj/machinery/power/apc/proc/break_lights() - for(var/obj/machinery/light/L in area) - L.on = TRUE - L.break_light_tube() - L.on = FALSE + for(var/obj/machinery/light/breaked_light in area) + breaked_light.on = TRUE + breaked_light.break_light_tube() stoplag() /obj/machinery/power/apc/proc/shock(mob/user, prb) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 6560c7aa26a5..e8bb1661c7f0 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -441,7 +441,7 @@ auto.Grant(M, src) /datum/action/innate/protoemitter - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS var/obj/machinery/power/emitter/prototype/PE var/mob/living/carbon/U @@ -466,7 +466,7 @@ for(var/obj/item/I in U.held_items) if(istype(I, /obj/item/turret_control)) qdel(I) - UpdateButtonIcon() + build_all_button_icons() return else playsound(PE,'sound/mecha/mechmove01.ogg', 50, TRUE) @@ -483,7 +483,7 @@ else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand var/obj/item/turret_control/TC = new /obj/item/turret_control() U.put_in_hands(TC) - UpdateButtonIcon() + build_all_button_icons() /obj/item/turret_control diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 2d5f67e6b4cd..8037cb2e32a4 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -524,7 +524,7 @@ C.visible_message(span_warning("[C]'s skin bursts into flame!"), \ span_userdanger("You feel an inner fire as your skin bursts into flames!")) C.adjust_fire_stacks(5) - C.IgniteMob() + C.ignite_mob() return diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index 5eb4640052ea..125490890808 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -346,7 +346,7 @@ set_panels(azimuth_target) if(powernet && force_auto) search_for_connected() //are we actually connected to anything useful? - if(connected_tracker && !isemptylist(connected_panels)) + if(connected_tracker && length(connected_panels)) track = SOLAR_TRACK_AUTO connected_tracker.sun_update(SSsun, SSsun.azimuth) update_icon() diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 0d527a069126..517b7b00d72e 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -362,7 +362,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(istype(M) && T2 && T2.z == z) if(ishuman(M)) var/mob/living/carbon/human/H = M - H.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(M, src) + 1)) ) ) + H.adjust_hallucinations(max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(M, src) + 1)) ) ) ) var/rads = DETONATION_RADS * sqrt( 1 / (get_dist(M, src) + 1) ) M.rad_act(rads) @@ -604,9 +604,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) for(var/mob/living/carbon/human/l in view(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them. if((!HAS_TRAIT(l, TRAIT_MESONS)) || corruptor_attached) - var/D = sqrt(1 / max(1, get_dist(l, src))) - l.hallucination += power * config_hallucination_power * D - l.hallucination = clamp(l.hallucination, 0, 200) + visible_hallucination_pulse( + center = src, + radius = HALLUCINATION_RANGE(power), + hallucination_duration = power * 0.1, + hallucination_max_duration = 400 SECONDS, + ) power -= ((power/500)**3) * powerloss_inhibitor diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index 339359b98934..ec2bc2e82197 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -22,6 +22,7 @@ connect_to_network() RegisterSignal(SSsun, COMSIG_SUN_MOVED, PROC_REF(sun_update)) + /obj/machinery/power/tracker/Destroy() unset_control() //remove from control computer return ..() diff --git a/code/modules/projectiles/ammunition/special/magic.dm b/code/modules/projectiles/ammunition/special/magic.dm index aa429d6be159..59206f3a9ac9 100644 --- a/code/modules/projectiles/ammunition/special/magic.dm +++ b/code/modules/projectiles/ammunition/special/magic.dm @@ -34,7 +34,7 @@ harmful = FALSE /obj/item/ammo_casing/magic/fireball - projectile_type = /obj/item/projectile/magic/aoe/fireball + projectile_type = /obj/item/projectile/magic/fireball /obj/item/ammo_casing/magic/chaos projectile_type = /obj/item/projectile/magic diff --git a/code/modules/projectiles/attachments/laser_sight.dm b/code/modules/projectiles/attachments/laser_sight.dm index 70ecca8eb0db..23569ceadf88 100644 --- a/code/modules/projectiles/attachments/laser_sight.dm +++ b/code/modules/projectiles/attachments/laser_sight.dm @@ -71,7 +71,9 @@ P.gun = attached_gun P.color = laser_color var/turf/curloc = get_turf(src) - var/turf/targloc = get_turf(current_user.client.mouseObject) + + var/atom/target_atom = current_user.client.mouse_object_ref?.resolve() + var/turf/targloc = get_turf(target_atom) if(!istype(targloc)) if(!istype(curloc)) return @@ -137,3 +139,52 @@ STOP_PROCESSING(SSfastprocess, src) QDEL_LIST(attached_gun?.current_tracers) return ..() + +/datum/action/item_action/toggle_laser_sight + name = "Toggle Laser Sight" + button_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() + build_all_button_icons(UPDATE_BUTTON_ICON) + +/datum/action/item_action/toggle_laser_sight/apply_button_icon(atom/movable/screen/movable/action_button/button, force) + var/obj/item/attachment/laser_sight/sight = target + if(istype(sight)) + button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]" + + return ..() + +/datum/action/item_action/change_laser_sight_color + name = "Change Laser Sight Color" + button_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 + build_all_button_icons(UPDATE_BUTTON_ICON) + +/datum/action/item_action/change_laser_sight_color/apply_button_icon(atom/movable/screen/movable/action_button/button, force) + button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]" + ..() diff --git a/code/modules/projectiles/attachments/scopes.dm b/code/modules/projectiles/attachments/scopes.dm index 666405278701..4a340175c638 100644 --- a/code/modules/projectiles/attachments/scopes.dm +++ b/code/modules/projectiles/attachments/scopes.dm @@ -66,3 +66,27 @@ if(user) REMOVE_TRAIT(user, TRAIT_INFRARED_VISION, ATTACHMENT_TRAIT) user.update_sight() + +/datum/action/item_action/toggle_infrared_sight + name = "Toggle Infrared" + button_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() + build_all_button_icons(UPDATE_BUTTON_ICON) + +/datum/action/item_action/toggle_infrared_sight/apply_button_icon(atom/movable/screen/movable/action_button/current_button, status_only = FALSE, force) + var/obj/item/attachment/scope/infrared/ifrsight = target + if(istype(ifrsight)) + button_icon_state = "ifr_sight[att?.is_on ? "_on" : ""]" + + return ..() diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 43f20eed24fe..53b41c5996e9 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -632,7 +632,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/gun/proc/update_attachments() for(var/mutable_appearance/M in attachment_overlays) @@ -649,7 +649,7 @@ update_icon(TRUE) for(var/datum/action/A as anything in actions) - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/gun/pickup(mob/user) ..() @@ -732,15 +732,15 @@ /datum/action/toggle_scope_zoom name = "Toggle Scope" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_LYING - icon_icon = 'icons/mob/actions/actions_items.dmi' + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_LYING + button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" var/obj/item/gun/gun = null /datum/action/toggle_scope_zoom/Trigger() gun.zoom(owner, owner.dir) -/datum/action/toggle_scope_zoom/IsAvailable() +/datum/action/toggle_scope_zoom/IsAvailable(feedback = FALSE) . = ..() if(!. && gun) gun.zoom(owner, owner.dir, FALSE) diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 8873ce8e59c6..96de1e61b445 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -59,7 +59,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.build_all_button_icons() /obj/item/gun/ballistic/automatic/c20r name = "\improper C-20r SMG" diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 370f292ff332..b446dff500fe 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -121,7 +121,7 @@ var/carried = 0 if(!unique_frequency) - for(var/obj/item/gun/energy/kinetic_accelerator/K in loc.GetAllContents()) + for(var/obj/item/gun/energy/kinetic_accelerator/K in loc.get_all_contents()) if(!K.unique_frequency) carried++ diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 0aee14e52133..4a55356901c5 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -200,9 +200,10 @@ /obj/item/gun/energy/plasmacutter/use_tool(atom/target, mob/living/user, delay, amount=1, volume=0, datum/callback/extra_checks, robo_check) if(amount) - 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) else . = ..(amount=1) diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index f763ca931169..1c8e5592285c 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -50,7 +50,7 @@ recharge_rate = 2 no_den_usage = 1 var/allowed_projectile_types = list(/obj/item/projectile/magic/change, /obj/item/projectile/magic/animate, /obj/item/projectile/magic/resurrection, - /obj/item/projectile/magic/teleport, /obj/item/projectile/magic/door, /obj/item/projectile/magic/aoe/fireball, + /obj/item/projectile/magic/teleport, /obj/item/projectile/magic/door, /obj/item/projectile/magic/fireball, /obj/item/projectile/magic/spellblade, /obj/item/projectile/magic/arcane_barrage, /obj/item/projectile/magic/locker, /obj/item/projectile/magic/flying, /obj/item/projectile/magic/bounty, /obj/item/projectile/magic/antimagic, /obj/item/projectile/magic/fetch, /obj/item/projectile/magic/sapping, /obj/item/projectile/magic/necropotence, /obj/item/projectile/magic, /obj/item/projectile/temp/chill, /obj/item/projectile/magic/wipe) diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index bcc767b7236d..64e159b5570f 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -68,7 +68,7 @@ /obj/item/gun/magic/wand/death/zap_self(mob/living/user) ..() to_chat(user, "You irradiate yourself with pure energy! \ - [pick("Do not pass go. Do not collect 200 zorkmids.","You feel more confident in your spell casting skills.","You Die...","Do you want your possessions identified?")]\ + [pick("Do not pass go. Do not collect 200 zorkmids.","You feel more confident in your spell casting skills.","You die...","Do you want your possessions identified?")]\ ") user.adjustOxyLoss(500) charges-- diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm index 432bf6b7e9d9..a911ee20855d 100644 --- a/code/modules/projectiles/guns/misc/beam_rifle.dm +++ b/code/modules/projectiles/guns/misc/beam_rifle.dm @@ -27,6 +27,7 @@ weapon_weight = WEAPON_HEAVY w_class = WEIGHT_CLASS_BULKY ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan) + actions_types = list(/datum/action/item_action/zoom_lock_action) cell_type = /obj/item/stock_parts/cell/beam_rifle canMouseDown = TRUE pin = null @@ -73,7 +74,6 @@ var/static/image/charged_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_charged") var/static/image/drained_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_empty") - var/datum/action/item_action/zoom_lock_action/zoom_lock_action var/mob/listeningTo /obj/item/gun/energy/beam_rifle/debug @@ -96,7 +96,7 @@ return ..() /obj/item/gun/energy/beam_rifle/ui_action_click(mob/user, actiontype) - if(istype(actiontype, zoom_lock_action)) + if(istype(actiontype, /datum/action/item_action/zoom_lock_action)) zoom_lock++ if(zoom_lock > 3) zoom_lock = 0 @@ -110,8 +110,9 @@ if(ZOOM_LOCK_OFF) to_chat(user, span_boldnotice("You disable [src]'s zooming system.")) reset_zooming() - else - ..() + return + + return ..() /obj/item/gun/energy/beam_rifle/proc/set_autozoom_pixel_offsets_immediate(current_angle) if(zoom_lock == ZOOM_LOCK_CENTER_VIEW || zoom_lock == ZOOM_LOCK_OFF) @@ -170,7 +171,6 @@ . = ..() fire_delay = delay START_PROCESSING(SSfastprocess, src) - zoom_lock_action = new(src) /obj/item/gun/energy/beam_rifle/Destroy() STOP_PROCESSING(SSfastprocess, src) @@ -203,7 +203,7 @@ else P.color = rgb(0, 255, 0) var/turf/curloc = get_turf(src) - var/turf/targloc = get_turf(current_user.client.mouseObject) + var/turf/targloc = get_turf(current_user.client.mouse_object_ref?.resolve()) if(!istype(targloc)) if(!istype(curloc)) return @@ -300,7 +300,9 @@ process_aim() if(aiming_time_left <= aiming_time_fire_threshold && check_user()) sync_ammo() - afterattack(M.client.mouseObject, M, FALSE, M.client.mouseParams, passthrough = TRUE) + var/atom/target = M.client.mouse_object_ref?.resolve() + if(target) + afterattack(target, M, FALSE, M.client.mouseParams, passthrough = TRUE) stop_aiming() QDEL_LIST(current_tracers) return ..() diff --git a/code/modules/projectiles/guns/misc/medbeam.dm b/code/modules/projectiles/guns/misc/medbeam.dm index 8a97a0a89623..59f2de153c24 100644 --- a/code/modules/projectiles/guns/misc/medbeam.dm +++ b/code/modules/projectiles/guns/misc/medbeam.dm @@ -232,7 +232,7 @@ /datum/action/item_action/activate_uber name = "Activate Übercharge" - icon_icon = 'icons/obj/chronos.dmi' + button_icon = 'icons/obj/chronos.dmi' button_icon_state = "chronogun" /// Activates über if ubercharge is ready @@ -243,7 +243,7 @@ var/obj/item/gun/medbeam/uber/gun = target - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return if(gun.ubering) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 81b68011c720..a3e105157d81 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -111,6 +111,8 @@ var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. var/impact_effect_type //what type of impact effect to show when hitting something var/log_override = FALSE //is this type spammed enough to not log? (KAs) + /// We ignore mobs with these factions. + var/list/ignored_factions var/temporary_unstoppable_movement = FALSE @@ -341,18 +343,18 @@ if(!can_hit_target(M, permutated, M == original, TRUE)) continue mobs += M - var/mob/M = safepick(mobs) - if(M) - return M.lowest_buckled_mob() + if(LAZYLEN(mobs)) + var/mob/M = pick(mobs) + return M?.lowest_buckled_mob() var/list/obj/possible_objs = typecache_filter_list(T, GLOB.typecache_machine_or_structure) var/list/obj/objs = list() for(var/obj/O in possible_objs) if(!can_hit_target(O, permutated, O == original, TRUE)) continue objs += O - var/obj/O = safepick(objs) - if(O) - return O + if(LAZYLEN(objs)) + var/obj/object_chosen = pick(objs) + return object_chosen //Nothing else is here that we can hit, hit the turf if we haven't. if(!(T in permutated) && can_hit_target(T, permutated, T == original, TRUE)) return T @@ -587,7 +589,11 @@ var/mob/living/L = M if((target in L.hasparasites()) && target.loc == L.loc) return FALSE - if((target == firer) || ((target == firer.loc) && (ismecha(firer.loc) || isspacepod(firer.loc))) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) //cannot shoot yourself or your mech // yogs - or your spacepod) + if((target == firer) || ((target == firer.loc) && (ismecha(firer.loc) || isspacepod(firer.loc))) || !ismovable(M) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) //cannot shoot yourself or your mech // yogs - or your spacepod) + return FALSE + if(ignored_factions?.len && ismob(target) && !direct_target) + var/mob/target_mob = target + if(faction_check(target_mob.faction, ignored_factions)) return FALSE if(!ignore_loc && (loc != target.loc)) return FALSE @@ -657,12 +663,10 @@ var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32 //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. - var/list/screenview = getviewsize(user.client.view) - var/screenviewX = screenview[1] * world.icon_size - var/screenviewY = screenview[2] * world.icon_size + var/list/screenview = view_to_pixels(user.client.view) - var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x - var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y + var/ox = round(screenview[1] / 2) - user.client.pixel_x //"origin" x + var/oy = round(screenview[2] / 2) - user.client.pixel_y //"origin" y angle = ATAN2(y - oy, x - ox) return list(angle, p_x, p_y) diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index e4a753c2d616..86a42dfdd8f0 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -33,7 +33,7 @@ wound_bonus = 0 speed = 0.6 // higher power = faster, that's how light works right -/obj/projectile/beam/laser/hellfire/Initialize() +/obj/item/projectile/beam/laser/hellfire/Initialize() . = ..() transform *= 2 @@ -49,7 +49,7 @@ /obj/item/projectile/beam/laser/on_hit(atom/target, blocked = FALSE) if((blocked != 100) && iscarbon(target)) var/mob/living/carbon/M = target - M.IgniteMob() + M.ignite_mob() else if(isturf(target)) impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser/wall return ..() diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index b023e86fc288..04fa152412b3 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -7,7 +7,7 @@ if(iscarbon(target)) var/mob/living/carbon/M = target M.adjust_fire_stacks(fire_stacks) - M.IgniteMob() + M.ignite_mob() /obj/item/projectile/bullet/incendiary/Move() . = ..() diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 72c1cb25743e..dd9d72b15926 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -8,17 +8,26 @@ flag = MAGIC var/tile_dropoff = 0 var/tile_dropoff_s = 0 - - var/antimagic_affected = TRUE // Marks whether antimagic will cause this projectile to vanish on contact. + /// determines what type of antimagic can block the spell projectile + var/antimagic_flags = MAGIC_RESISTANCE + /// determines the drain cost on the antimagic item + var/antimagic_charge_cost = 1 /obj/item/projectile/magic/prehit(atom/target) . = ..() if(isliving(target)) - var/mob/living/L = target - if(L.anti_magic_check()) - L.visible_message(span_warning("[src] vanishes on contact with [target]!")) - qdel(src) - return FALSE + var/mob/living/victim = target + if(victim.can_block_magic(antimagic_flags, antimagic_charge_cost)) + visible_message(span_warning("[src] fizzles on contact with [victim]!")) + return BULLET_ACT_BLOCK + + if(istype(target, /obj/machinery/hydroponics)) // even plants can block antimagic + var/obj/machinery/hydroponics/plant_tray = target + if(!plant_tray.myseed) + return + if(LAZYFIND(plant_tray.myseed.reagents_add, /datum/reagent/water/holywater)) + visible_message(span_warning("[src] fizzles on contact with [plant_tray]!")) + return BULLET_ACT_BLOCK /obj/item/projectile/magic/death name = "bolt of death" @@ -30,6 +39,15 @@ var/mob/M = target M.death(0) +/obj/item/projectile/magic/spellcard + name = "enchanted card" + desc = "A piece of paper enchanted to give it extreme durability and stiffness, along with a very hot burn to anyone unfortunate enough to get hit by a charged one." + icon_state = "spellcard" + damage_type = BURN + damage = 2 + nodamage = FALSE + antimagic_charge_cost = 0 // since the cards gets spammed like a shotgun + /obj/item/projectile/magic/resurrection name = "bolt of resurrection" icon_state = "ion" @@ -311,7 +329,7 @@ B.desc = "What appears to be [M.real_name] reformed into a wheel of delicious parmesan..." B.name = "[M.name] Parmesan" B.real_name = "[M.name] Parmesan" - B.stat = CONSCIOUS + B.set_stat(CONSCIOUS) B.a_intent = INTENT_HARM if(M.mind) M.mind.transfer_to(B) @@ -534,7 +552,7 @@ . = ..() if(ismob(target)) var/mob/M = target - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, src, /datum/mood_event/sapped) + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, REF(src), /datum/mood_event/sapped) /obj/item/projectile/magic/necropotence name = "bolt of necropotence" @@ -542,21 +560,16 @@ /obj/item/projectile/magic/necropotence/on_hit(target) . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.anti_magic_check() || !L.mind || !L.mind.hasSoul) - L.visible_message(span_warning("[src] vanishes on contact with [target]!")) - return BULLET_ACT_BLOCK - to_chat(L, span_danger("Your body feels drained and there is a burning pain in your chest.")) - L.maxHealth -= 20 - L.health = min(L.health, L.maxHealth) - if(L.maxHealth <= 0) - to_chat(L, span_userdanger("Your weakened soul is completely consumed by the [src]!")) - L.mind.hasSoul = FALSE - for(var/obj/effect/proc_holder/spell/spell in L.mind.spell_list) - spell.charge_counter = spell.charge_max - spell.recharging = FALSE - spell.update_icon() + if(!isliving(target)) + return + + // Performs a soul tap on living targets hit. + // Takes away max health, but refreshes their spell cooldowns (if any) + var/datum/action/cooldown/spell/tap/tap = new(src) + if(tap.is_valid_target(target)) + tap.cast(target) + + qdel(tap) /obj/item/projectile/magic/wipe name = "bolt of possession" @@ -574,47 +587,90 @@ possession_test(M) return BULLET_ACT_HIT -/obj/item/projectile/magic/wipe/proc/possession_test(var/mob/living/carbon/M) - var/datum/brain_trauma/special/imaginary_friend/trapped_owner/trauma = M.gain_trauma(/datum/brain_trauma/special/imaginary_friend/trapped_owner) - var/poll_message = "Do you want to play as [M.real_name]?" - if(M?.mind?.assigned_role) - poll_message = "[poll_message] Job:[M.mind.assigned_role]." - if(M?.mind?.special_role) - poll_message = "[poll_message] Status:[M.mind.special_role]." - else if(M.mind) - var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/) +/obj/item/projectile/magic/wipe/proc/possession_test(mob/living/carbon/target) + var/datum/brain_trauma/special/imaginary_friend/trapped_owner/trauma = target.gain_trauma(/datum/brain_trauma/special/imaginary_friend/trapped_owner) + var/poll_message = "Do you want to play as [target.real_name]?" + if(target.mind) + poll_message = "[poll_message] Job:[target.mind.assigned_role]." + if(target.mind && target.mind.special_role) + poll_message = "[poll_message] Status:[target.mind.special_role]." + else if(target.mind) + var/datum/antagonist/A = target.mind.has_antag_datum(/datum/antagonist/) if(A) poll_message = "[poll_message] Status:[A.name]." - var/list/mob/dead/observer/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 100, M) - if(M.stat == DEAD)//boo. + var/list/mob/dead/observer/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 10 SECONDS, target) + if(target.stat == DEAD)//boo. return if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - to_chat(M, "You have been noticed by a ghost, and it has possessed you!") - var/oldkey = M.key - M.ghostize(0) - M.key = C.key + var/mob/dead/observer/ghost = pick(candidates) + to_chat(target, span_boldnotice("You have been noticed by a ghost and it has possessed you!")) + var/oldkey = target.key + target.ghostize(FALSE) + target.key = ghost.key trauma.friend.key = oldkey trauma.friend.reset_perspective(null) trauma.friend.Show() trauma.friend_initialized = TRUE else - to_chat(M, span_notice("Your mind has managed to go unnoticed in the spirit world.")) + to_chat(target, span_notice("Your mind has managed to go unnoticed in the spirit world.")) qdel(trauma) +/// Gives magic projectiles an area of effect radius that will bump into any nearby mobs /obj/item/projectile/magic/aoe - name = "Area Bolt" - desc = "What the fuck does this do?!" damage = 0 - var/proxdet = TRUE + + /// The AOE radius that the projectile will trigger on people. + var/trigger_range = 1 + /// Whether our projectile will only be able to hit the original target / clicked on atom + var/can_only_hit_target = FALSE + + /// Whether our projectile leaves a trail behind it as it moves. + var/trail = FALSE + /// The duration of the trail before deleting. + var/trail_lifespan = 0 SECONDS + /// The icon the trail uses. + var/trail_icon = 'icons/obj/wizard.dmi' + /// The icon state the trail uses. + var/trail_icon_state = "trail" + /obj/item/projectile/magic/aoe/Range() - if(proxdet) - for(var/mob/living/L in range(1, get_turf(src))) - if(L.stat != DEAD && L != firer && !L.anti_magic_check()) - return Bump(L) - ..() + if(trigger_range >= 1) + for(var/mob/living/nearby_guy in range(trigger_range, get_turf(src))) + if(nearby_guy.stat == DEAD) + continue + if(nearby_guy == firer) + continue + // Bump handles anti-magic checks for us, conveniently. + return Bump(nearby_guy) + + return ..() +/obj/item/projectile/magic/aoe/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) + if(can_only_hit_target && target != original) + return FALSE + return ..() + +/obj/item/projectile/magic/aoe/Moved(atom/OldLoc, Dir) + . = ..() + if(trail) + create_trail() + +/// Creates and handles the trail that follows the projectile. +/obj/item/projectile/magic/aoe/proc/create_trail() + if(!trajectory) + return + + var/datum/point/vector/previous = trajectory.return_vector_after_increments(1, -1) + var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) + trail.pixel_x = previous.return_px() + trail.pixel_y = previous.return_py() + trail.icon = trail_icon + trail.icon_state = trail_icon_state + //might be changed to temp overlay + trail.set_density(FALSE) + trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + QDEL_IN(trail, trail_lifespan) /obj/item/projectile/magic/aoe/lightning name = "lightning bolt" @@ -625,16 +681,19 @@ speed = 0.3 flag = MAGIC + /// The power of the zap itself when it electrocutes someone var/tesla_power = 20000 + /// The range of the zap itself when it electrocutes someone var/tesla_range = 15 + /// The flags of the zap itself when it electrocutes someone var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_MOB_STUN | TESLA_OBJ_DAMAGE - var/chain - var/mob/living/caster + /// A reference to the chain beam between the caster and the projectile + var/datum/beam/chain /obj/item/projectile/magic/aoe/lightning/fire(setAngle) - if(caster) - chain = caster.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY) - ..() + if(firer) + chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]") + return ..() /obj/item/projectile/magic/aoe/lightning/on_hit(target) . = ..() @@ -642,42 +701,105 @@ qdel(src) /obj/item/projectile/magic/aoe/lightning/Destroy() - qdel(chain) - . = ..() + QDEL_NULL(chain) + return ..() -/obj/item/projectile/magic/aoe/fireball +/obj/item/projectile/magic/fireball name = "bolt of fireball" icon_state = "fireball" damage = 10 damage_type = BRUTE nodamage = FALSE - //explosion values + /// Heavy explosion range of the fireball var/exp_heavy = 0 + /// Light explosion range of the fireball var/exp_light = 2 - var/exp_flash = 3 + /// Fire radius of the fireball var/exp_fire = 2 + /// Flash radius of the fireball + var/exp_flash = 3 -/obj/item/projectile/magic/aoe/fireball/on_hit(target) +/obj/item/projectile/magic/fireball/on_hit(atom/target, blocked = FALSE, pierce_hit) . = ..() - if(ismob(target)) - var/mob/living/M = target - M.take_overall_damage(0,10) //between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, your at about 65 damage if you stop drop and roll immediately - var/turf/T = get_turf(target) - explosion(T, -1, exp_heavy, exp_light, exp_flash, 0, flame_range = exp_fire) + if(isliving(target)) + var/mob/living/mob_target = target + // between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, + // you are at about 65 damage if you stop drop and roll immediately + mob_target.take_overall_damage(burn = 10) + + var/turf/target_turf = get_turf(target) + + explosion( + target_turf, + devastation_range = -1, + heavy_impact_range = exp_heavy, + light_impact_range = exp_light, + flame_range = exp_fire, + flash_range = exp_flash, + adminlog = FALSE, + ) + + +/obj/item/projectile/magic/aoe/magic_missile + name = "magic missile" + icon_state = "magicm" + range = 20 + speed = 5 + trigger_range = 0 + can_only_hit_target = TRUE + nodamage = FALSE + paralyze = 6 SECONDS + hitsound = 'sound/magic/mm_hit.ogg' -/obj/item/projectile/magic/aoe/fireball/infernal + trail = TRUE + trail_lifespan = 0.5 SECONDS + trail_icon_state = "magicmd" + +/obj/item/projectile/magic/aoe/magic_missile/lesser + color = "red" //Looks more culty this way + range = 10 + +/obj/item/projectile/magic/aoe/juggernaut + name = "Gauntlet Echo" + icon_state = "cultfist" + alpha = 180 + damage = 30 + damage_type = BRUTE + knockdown = 50 + hitsound = 'sound/weapons/punch3.ogg' + trigger_range = 0 + antimagic_flags = MAGIC_RESISTANCE_HOLY + ignored_factions = list("cult") + range = 15 + speed = 7 + +/obj/item/projectile/magic/spell/juggernaut/on_hit(atom/target, blocked) + . = ..() + var/turf/target_turf = get_turf(src) + playsound(target_turf, 'sound/weapons/resonator_blast.ogg', 100, FALSE) + new /obj/effect/temp_visual/cult/sac(target_turf) + for(var/obj/adjacent_object in range(1, src)) + if(!adjacent_object.density) + continue + if(istype(adjacent_object, /obj/structure/destructible/cult)) + continue + + adjacent_object.take_damage(90, BRUTE, MELEE, 0) + new /obj/effect/temp_visual/cult/turf/floor(get_turf(adjacent_object)) + +/obj/item/projectile/magic/fireball/infernal name = "infernal fireball" exp_heavy = -1 exp_light = -1 exp_flash = 4 exp_fire= 5 -/obj/item/projectile/magic/aoe/fireball/infernal/on_hit(target) +/obj/item/projectile/magic/fireball/infernal/on_hit(target) . = ..() var/turf/T = get_turf(target) for(var/i=0, i<50, i+=10) - addtimer(CALLBACK(GLOBAL_PROC, PROC_REF(explosion), T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i) //still magic related, but a different path @@ -757,7 +879,7 @@ if(iscarbon(target)) var/mob/living/carbon/X = target X.adjust_fire_stacks(2) - X.IgniteMob() + X.ignite_mob() /obj/item/projectile/magic/runic_honk @@ -868,7 +990,7 @@ if(iscarbon(target)) var/mob/living/carbon/X = target X.adjust_fire_stacks(1) - X.IgniteMob() + X.ignite_mob() X.adjustBruteLoss(5) /obj/item/projectile/magic/runic_mutation diff --git a/code/modules/projectiles/projectile/reusable/arrow.dm b/code/modules/projectiles/projectile/reusable/arrow.dm index d6217c35e078..b05e955f931a 100644 --- a/code/modules/projectiles/projectile/reusable/arrow.dm +++ b/code/modules/projectiles/projectile/reusable/arrow.dm @@ -34,7 +34,7 @@ if(arrow.flaming) L.adjust_fire_stacks(1) - L.IgniteMob() + L.ignite_mob() arrow.flaming = FALSE arrow.update_icon() diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm index 21648040e366..f4c05e0af7df 100644 --- a/code/modules/projectiles/projectile/special/hallucination.dm +++ b/code/modules/projectiles/projectile/special/hallucination.dm @@ -167,11 +167,11 @@ /obj/item/projectile/hallucination/taser/hal_apply_effect() hal_target.Paralyze(100) - hal_target.stuttering += 20 + hal_target.adjust_stutter(2 SECONDS) if(hal_target.dna && (hal_target.dna.check_mutation(HULK)|| hal_target.dna.check_mutation(ACTIVE_HULK))) hal_target.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") else if((hal_target.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(hal_target, TRAIT_STUNIMMUNE)) - addtimer(CALLBACK(hal_target, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 5) + addtimer(CALLBACK(hal_target, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 0.5 SECONDS) /obj/item/projectile/hallucination/disabler name = "disabler beam" @@ -200,7 +200,7 @@ /obj/item/projectile/hallucination/ebow/hal_apply_effect() hal_target.Paralyze(100) - hal_target.stuttering += 5 + hal_target.adjust_stutter(5 SECONDS) hal_target.adjustStaminaLoss(8) /obj/item/projectile/hallucination/change diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index a50c2972e652..a43737ee8121 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -19,7 +19,7 @@ var/mob/living/carbon/human/M = target var/current_oxygen_damage = M.getOxyLoss() M.adjustOrganLoss(ORGAN_SLOT_BRAIN, current_oxygen_damage) //the more oxygen damage you have the more brain damage you get - M.hallucination = max(50, M.hallucination) //50 hallucination (5 seconds) when the target get hit but you can't stack it to make someone hallucinate for 5 hours because door shock hallucination exist + M.adjust_hallucinations_up_to(5 SECONDS, 5 SECONDS) //50 hallucination (5 seconds) when the target get hit but you can't stack it to make someone hallucinate for 5 hours because door shock hallucination exist /obj/item/projectile/beam/anoxia/bounce name = "bouncing anoxia ball" @@ -29,4 +29,4 @@ ricochet_chance = 100 /obj/item/projectile/beam/anoxia/bounce/check_ricochet_flag(atom/A) - return TRUE //whatever it is, we bounce on it \ No newline at end of file + return TRUE //whatever it is, we bounce on it diff --git a/code/modules/reagents/chem_splash.dm b/code/modules/reagents/chem_splash.dm index b16392ecd161..d1abacaba8ac 100644 --- a/code/modules/reagents/chem_splash.dm +++ b/code/modules/reagents/chem_splash.dm @@ -58,7 +58,7 @@ break var/list/reactable = accessible for(var/turf/T in accessible) - for(var/atom/A in T.GetAllContents()) + for(var/atom/A in T.get_all_contents()) if(!(A in viewable)) continue reactable |= A diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 7a309e7933ed..8ba1ec3228e6 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -33,17 +33,19 @@ All effects don't start immediately, but rather get worse over time; the rate is 91-100: Dangerously toxic - swift death */ -/datum/reagent/consumable/ethanol/on_mob_life(mob/living/carbon/C) - if(C.drunkenness < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER) +/datum/reagent/consumable/ethanol/on_mob_life(mob/living/carbon/drinker) + if(drinker.get_drunk_amount() < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER || boozepwr < 0) var/booze_power = boozepwr - if(HAS_TRAIT(C, TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker + if(HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker booze_power *= 0.7 - if(HAS_TRAIT(C, TRAIT_LIGHT_DRINKER)) + if(HAS_TRAIT(drinker, TRAIT_LIGHT_DRINKER)) booze_power *= 2 - C.drunkenness = max((C.drunkenness + (sqrt(volume) * booze_power * ALCOHOL_RATE)), 0) //Volume, power, and server alcohol rate effect how quickly one gets drunk - var/obj/item/organ/liver/L = C.getorganslot(ORGAN_SLOT_LIVER) - if (istype(L)) - L.applyOrganDamage(((max(sqrt(volume) * (boozepwr ** ALCOHOL_EXPONENT) * L.alcohol_tolerance, 0))/150)) + // Volume, power, and server alcohol rate effect how quickly one gets drunk + drinker.adjust_drunk_effect(sqrt(volume) * booze_power * ALCOHOL_RATE * REM) + if(boozepwr > 0) + var/obj/item/organ/liver/liver = drinker.getorganslot(ORGAN_SLOT_LIVER) + if (istype(liver)) + liver.applyOrganDamage(((max(sqrt(volume) * (boozepwr ** ALCOHOL_EXPONENT) * liver.alcohol_tolerance, 0))/150)) return ..() /datum/reagent/consumable/ethanol/reaction_obj(obj/O, reac_volume) @@ -159,11 +161,11 @@ All effects don't start immediately, but rather get worse over time; the rate is shot_glass_icon_state = "shotglasscream" /datum/reagent/consumable/ethanol/kahlua/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.AdjustSleeping(-40, FALSE) if(!HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -193,16 +195,16 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "This is a glass of Thirteen Loko, it appears to be of the highest quality. The drink, not the glass." /datum/reagent/consumable/ethanol/thirteenloko/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-7) + M.adjust_drowsiness(-7 SECONDS) M.AdjustSleeping(-40) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) if(!HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) return ..() /datum/reagent/consumable/ethanol/thirteenloko/overdose_start(mob/living/M) to_chat(M, span_userdanger("Your entire body violently jitters as you start to feel queasy. You really shouldn't have drank all of that [name]!")) - M.Jitter(20) + M.adjust_jitter(20 SECONDS) M.Stun(15) /datum/reagent/consumable/ethanol/thirteenloko/overdose_process(mob/living/M) @@ -211,7 +213,7 @@ All effects don't start immediately, but rather get worse over time; the rate is if(I) M.dropItemToGround(I) to_chat(M, "Your hands jitter and you drop what you were holding!") - M.Jitter(10) + M.adjust_jitter(10 SECONDS) if(prob(7)) to_chat(M, span_notice("[pick("You have a really bad headache.", "Your eyes hurt.", "You find it hard to stay still.", "You feel your heart practically beating out of your chest.")]")) @@ -233,7 +235,7 @@ All effects don't start immediately, but rather get worse over time; the rate is if(prob(3) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) M.Unconscious(100) - M.Jitter(350) + M.adjust_jitter(350) if(prob(1) && iscarbon(M)) var/datum/disease/D = new /datum/disease/heart_failure @@ -395,7 +397,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/absinthe/on_mob_life(mob/living/carbon/M) if(prob(10) && !HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.hallucination += 4 //Reference to the urban myth + M.adjust_hallucinations(4 SECONDS) //Reference to the urban myth ..() /datum/reagent/consumable/ethanol/hooch @@ -669,7 +671,7 @@ All effects don't start immediately, but rather get worse over time; the rate is ..() /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_life(mob/living/carbon/M) - M.Jitter(2) + M.adjust_jitter(2 SECONDS) if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM)) M.adjustStaminaLoss(-10, 0) if(prob(20)) @@ -891,7 +893,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Snow White" glass_desc = "A cold refreshment." -/datum/reagent/consumable/ethanol/demonsblood //Prevents the imbiber from being dragged into a pool of blood by a slaughter demon. +/datum/reagent/consumable/ethanol/demonsblood name = "Demon's Blood" description = "AHHHH!!!!" color = "#820000" // rgb: 130, 0, 0 @@ -902,7 +904,34 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Demons Blood" glass_desc = "Just looking at this thing makes the hair at the back of your neck stand up." -/datum/reagent/consumable/ethanol/devilskiss //If eaten by a slaughter demon, the demon will regret it. +/datum/reagent/consumable/ethanol/demonsblood/on_mob_metabolize(mob/living/metabolizer) + . = ..() + RegisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED, PROC_REF(pre_bloodcrawl_consumed)) + +/datum/reagent/consumable/ethanol/demonsblood/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + UnregisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED) + +/// Prevents the imbiber from being dragged into a pool of blood by a slaughter demon. +/datum/reagent/consumable/ethanol/demonsblood/proc/pre_bloodcrawl_consumed( + mob/living/source, + datum/action/cooldown/spell/jaunt/bloodcrawl/crawl, + mob/living/jaunter, + obj/effect/decal/cleanable/blood, +) + + SIGNAL_HANDLER + + var/turf/jaunt_turf = get_turf(jaunter) + jaunt_turf.visible_message( + span_warning("Something prevents [source] from entering [blood]!"), + blind_message = span_notice("You hear a splash and a thud.") + ) + to_chat(jaunter, span_warning("A strange force is blocking [source] from entering!")) + + return COMPONENT_STOP_CONSUMPTION + +/datum/reagent/consumable/ethanol/devilskiss name = "Devil's Kiss" description = "Creepy time!" color = "#A68310" // rgb: 166, 131, 16 @@ -913,6 +942,41 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Devils Kiss" glass_desc = "Creepy time!" +/datum/reagent/consumable/ethanol/devilskiss/on_mob_metabolize(mob/living/metabolizer) + . = ..() + RegisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_CONSUMED, PROC_REF(on_bloodcrawl_consumed)) + +/datum/reagent/consumable/ethanol/devilskiss/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + UnregisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_CONSUMED) + +/// If eaten by a slaughter demon, the demon will regret it. +/datum/reagent/consumable/ethanol/devilskiss/proc/on_bloodcrawl_consumed( + mob/living/source, + datum/action/cooldown/spell/jaunt/bloodcrawl/crawl, + mob/living/jaunter, +) + + SIGNAL_HANDLER + + . = COMPONENT_STOP_CONSUMPTION + + to_chat(jaunter, span_boldwarning("AAH! THEIR FLESH! IT BURNS!")) + INVOKE_ASYNC(jaunter, TYPE_PROC_REF(/mob/living/, apply_damage), 25, BRUTE, null, FALSE, CANT_WOUND) + + for(var/obj/effect/decal/cleanable/nearby_blood in range(1, get_turf(source))) + if(!nearby_blood.can_bloodcrawl_in()) + continue + INVOKE_ASYNC(source, TYPE_PROC_REF(/atom/movable/, forceMove), get_turf(nearby_blood)) + source.visible_message(span_warning("[nearby_blood] violently expels [source]!")) + crawl.exit_blood_effect(source) + return + + // Fuck it, just eject them, thanks to some split second cleaning + INVOKE_ASYNC(source, TYPE_PROC_REF(/atom/movable/, forceMove), get_turf(source)) + source.visible_message(span_warning("[source] appears from nowhere, covered in blood!")) + INVOKE_ASYNC(crawl, TYPE_PROC_REF(/datum/action/cooldown/spell/jaunt/bloodcrawl/, exit_blood_effect), source) + /datum/reagent/consumable/ethanol/vodkatonic name = "Vodka and Tonic" description = "For when a gin and tonic isn't Russian enough." @@ -1119,12 +1183,9 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Changeling Sting" glass_desc = "A stingy drink." -/datum/reagent/consumable/ethanol/changelingsting/on_mob_life(mob/living/carbon/M) - if(M.mind) //Changeling Sting assists in the recharging of changeling chemicals. - var/datum/antagonist/changeling/changeling = M.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - changeling.chem_charges += metabolization_rate - changeling.chem_charges = clamp(changeling.chem_charges, 0, changeling.chem_storage) +/datum/reagent/consumable/ethanol/changelingsting/on_mob_life(mob/living/carbon/target) + var/datum/antagonist/changeling/changeling = target.mind?.has_antag_datum(/datum/antagonist/changeling) + changeling?.adjust_chemicals(metabolization_rate * REM) return ..() /datum/reagent/consumable/ethanol/irishcarbomb @@ -1344,22 +1405,20 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Atomic Bomb" glass_desc = "Nanotrasen cannot take legal responsibility for your actions after imbibing." -/datum/reagent/consumable/ethanol/atomicbomb/on_mob_life(mob/living/carbon/M) - M.set_drugginess(50) - if(!HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.confused = max(M.confused+2,0) - M.Dizzy(10) - if (!M.slurring) - M.slurring = 1 - M.slurring += 3 +/datum/reagent/consumable/ethanol/atomicbomb/on_mob_life(mob/living/carbon/drinker) + drinker.set_drugginess(100 SECONDS * REM) + if(!HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) + drinker.adjust_confusion(2 SECONDS * REM) + drinker.set_dizzy_if_lower(20 SECONDS * REM) + drinker.adjust_slurring(6 SECONDS * REM) switch(current_cycle) if(51 to 200) - M.Sleeping(100, FALSE) - . = 1 + drinker.Sleeping(10 SECONDS * REM) + . = TRUE if(201 to INFINITY) - M.AdjustSleeping(40, FALSE) - M.adjustToxLoss(2, 0) - . = 1 + drinker.AdjustSleeping(4 SECONDS* REM) + drinker.adjustToxLoss(2 * REM, FALSE) + . = TRUE ..() /datum/reagent/consumable/ethanol/gargle_blaster @@ -1373,21 +1432,20 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Pan-Galactic Gargle Blaster" glass_desc = "Like having your brain smashed out by a slice of lemon wrapped around a large gold brick." -/datum/reagent/consumable/ethanol/gargle_blaster/on_mob_life(mob/living/carbon/M) - M.dizziness +=1.5 +/datum/reagent/consumable/ethanol/gargle_blaster/on_mob_life(mob/living/carbon/drinker) + drinker.adjust_dizzy(3 SECONDS * REM) switch(current_cycle) if(15 to 45) - if(!M.slurring) - M.slurring = 1 - M.slurring += 3 + drinker.adjust_slurring(3 SECONDS * REM) + if(45 to 55) if(prob(50)) - M.confused = max(M.confused+3,0) + drinker.adjust_confusion(3 SECONDS * REM) if(55 to 200) - M.set_drugginess(55) + drinker.set_drugginess(110 SECONDS * REM) if(200 to INFINITY) - M.adjustToxLoss(2, 0) - . = 1 + drinker.adjustToxLoss(2 * REM, FALSE) + . = TRUE ..() /datum/reagent/consumable/ethanol/neurotoxin @@ -1407,7 +1465,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/neurotoxin/on_mob_life(mob/living/carbon/M) M.set_drugginess(50) - M.dizziness +=2 + M.adjust_dizzy(2 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1*REM, 150) if(prob(20)) M.adjustStaminaLoss(10) @@ -1450,29 +1508,28 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "A drink enjoyed by people during the 1960's." /datum/reagent/consumable/ethanol/hippies_delight/on_mob_life(mob/living/carbon/M) - if (!M.slurring) - M.slurring = 1 + M.set_slurring_if_lower(1 SECONDS * REM) switch(current_cycle) if(1 to 5) - M.Dizzy(10) + M.adjust_dizzy(10) M.set_drugginess(30) if(prob(10)) M.emote(pick("twitch","giggle")) if(5 to 10) - M.Jitter(20) - M.Dizzy(20) + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(20) M.set_drugginess(45) if(prob(20)) M.emote(pick("twitch","giggle")) if (10 to 200) - M.Jitter(40) - M.Dizzy(40) + M.adjust_jitter(40 SECONDS) + M.adjust_dizzy(40) M.set_drugginess(60) if(prob(30)) M.emote(pick("twitch","giggle")) if(200 to INFINITY) - M.Jitter(60) - M.Dizzy(60) + M.adjust_jitter(1 MINUTES) + M.adjust_dizzy(60) M.set_drugginess(75) if(prob(40)) M.emote(pick("twitch","giggle")) @@ -1517,11 +1574,11 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Nar'Sour" glass_desc = "A new hit cocktail inspired by THE ARM Breweries will have you shouting Fuu ma'jin in no time!" -/datum/reagent/consumable/ethanol/narsour/on_mob_life(mob/living/carbon/M) - M.cultslurring = min(M.cultslurring + 3, 3) - M.stuttering = min(M.stuttering + 3, 3) - if(iscultist(M)) - M.heal_overall_damage(0.5, 0.5) +/datum/reagent/consumable/ethanol/narsour/on_mob_life(mob/living/carbon/drinker) + drinker.adjust_timed_status_effect(6 SECONDS * REM, /datum/status_effect/speech/slurring/cult, max_duration = 6 SECONDS) + drinker.adjust_stutter_up_to(6 SECONDS * REM, 6 SECONDS) + if(iscultist(drinker)) + drinker.heal_overall_damage(0.5, 0.5) ..() /datum/reagent/consumable/ethanol/triple_sec @@ -2139,7 +2196,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/turbo/on_mob_life(mob/living/carbon/M) if(prob(4)) to_chat(M, span_notice("[pick("You feel disregard for the rule of law.", "You feel pumped!", "Your head is pounding.", "Your thoughts are racing..")]")) - M.adjustStaminaLoss(-M.drunkenness * 0.25) + M.adjustStaminaLoss(-M.get_drunk_amount() * 0.25) return ..() /datum/reagent/consumable/ethanol/old_timer @@ -2210,8 +2267,8 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/trappist/on_mob_life(mob/living/carbon/M) if(M.mind.holy_role) M.adjustFireLoss(-2.5, 0) - M.jitteriness = max(0, M.jitteriness-1) - M.stuttering = max(0, M.stuttering-1) + M.adjust_jitter(-1 SECONDS) + M.adjust_stutter(-1 SECONDS) return ..() /datum/reagent/consumable/ethanol/blazaam @@ -2226,7 +2283,7 @@ All effects don't start immediately, but rather get worse over time; the rate is var/stored_teleports = 0 /datum/reagent/consumable/ethanol/blazaam/on_mob_life(mob/living/carbon/M) - if(M.drunkenness > 40) + if(M.get_drunk_amount() > 40) if(stored_teleports) do_teleport(M, get_turf(M), rand(1,3), channel = TELEPORT_CHANNEL_WORMHOLE) stored_teleports-- @@ -2367,7 +2424,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "an amazing concoction of various different bar drinks and a secret ingredient" /datum/reagent/consumable/ethanol/flaming_moe/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-5 SECONDS) M.AdjustStun(-20, FALSE) M.AdjustKnockdown(-20, FALSE) M.AdjustUnconscious(-20, FALSE) @@ -2375,7 +2432,7 @@ All effects don't start immediately, but rather get worse over time; the rate is M.AdjustParalyzed(-20, FALSE) if(M.reagents.has_reagent(/datum/reagent/toxin/mindbreaker)) M.reagents.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) - M.hallucination = max(0, M.hallucination - 10) + M.adjust_hallucinations(-10 SECONDS) if(prob(30)) M.adjustToxLoss(1, 0) . = 1 diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index 2955be815c50..2b1ddca8c90a 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -50,7 +50,7 @@ /datum/reagent/medicine/c2/probital/overdose_process(mob/living/M) M.adjustStaminaLoss(3 * REM, 0) if(M.getStaminaLoss() >= 80) - M.drowsyness += 1 * REM + M.adjust_drowsiness(2 SECONDS * REM) if(M.getStaminaLoss() >= 100) to_chat(M,span_warning("You feel more tired than you usually do, perhaps if you rest your eyes for a bit...")) M.adjustStaminaLoss(-100, TRUE) @@ -136,7 +136,7 @@ exposed_mob.adjust_bodytemperature(-reac_volume * TEMPERATURE_DAMAGE_COEFFICIENT, 50) exposed_mob.adjust_fire_stacks(-reac_volume / 2) if(reac_volume >= metabolization_rate) - exposed_mob.ExtinguishMob() + exposed_mob.extinguish_mob() /datum/reagent/medicine/c2/rhigoxane/overdose_process(mob/living/carbon/M) M.adjust_bodytemperature(-10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM, 50) //chilly chilly @@ -157,7 +157,7 @@ M.adjustOxyLoss(-3 * REM) M.adjustStaminaLoss(2 * REM) if(drowsycd && COOLDOWN_FINISHED(src, drowsycd)) - M.drowsyness += 10 + M.adjust_drowsiness(20 SECONDS) COOLDOWN_START(src, drowsycd, 45 SECONDS) else if(!drowsycd) COOLDOWN_START(src, drowsycd, 15 SECONDS) diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 1fd3835b1ace..88d23f38ac20 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -351,12 +351,12 @@ glass_desc = "Don't drop it, or you'll send scalding liquid and glass shards everywhere." /datum/reagent/consumable/coffee/overdose_process(mob/living/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() /datum/reagent/consumable/coffee/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-10 SECONDS * REM) + M.adjust_drowsiness(-6 SECONDS * REM) M.AdjustSleeping(-40, FALSE) //310.15 is the normal bodytemp. M.adjust_bodytemperature(25 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) @@ -376,9 +376,9 @@ glass_desc = "Drinking it from here would not seem right." /datum/reagent/consumable/tea/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-2) - M.drowsyness = max(0,M.drowsyness-1) - M.jitteriness = max(0,M.jitteriness-3) + M.adjust_dizzy(-2 SECONDS) + M.adjust_drowsiness(-1 SECONDS) + M.adjust_jitter(-3 SECONDS) M.AdjustSleeping(-20, FALSE) if(M.getToxLoss() && prob(20)) M.adjustToxLoss(-1, 0) @@ -440,11 +440,11 @@ glass_desc = "A drink to perk you up and refresh you!" /datum/reagent/consumable/icecoffee/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -459,8 +459,8 @@ glass_desc = "All natural, antioxidant-rich flavour sensation." /datum/reagent/consumable/icetea/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-2) - M.drowsyness = max(0,M.drowsyness-1) + M.adjust_dizzy(-2 SECONDS) + M.adjust_drowsiness(-1 SECONDS) M.AdjustSleeping(-40, FALSE) if(M.getToxLoss() && prob(20)) M.adjustToxLoss(-1, 0) @@ -478,7 +478,7 @@ glass_desc = "A glass of refreshing Space Cola." /datum/reagent/consumable/space_cola/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-5) + M.adjust_drowsiness(-5 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -492,7 +492,7 @@ glass_desc = "A glass of refreshing fizzing root beer." /datum/reagent/consumable/rootbeer/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-5) + M.adjust_drowsiness(-5 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -515,10 +515,10 @@ ..() /datum/reagent/consumable/nuka_cola/on_mob_life(mob/living/carbon/M) - M.Jitter(20) + M.adjust_jitter(20 SECONDS) M.set_drugginess(30) - M.dizziness +=1.5 - M.drowsyness = 0 + M.adjust_dizzy(1.5 SECONDS) + M.remove_status_effect(/datum/status_effect/drowsiness) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) M.apply_effect(5,EFFECT_IRRADIATE,0) @@ -544,9 +544,9 @@ ..() /datum/reagent/consumable/grey_bull/on_mob_life(mob/living/carbon/M) - M.Jitter(20) - M.dizziness +=1 - M.drowsyness = 0 + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(1 SECONDS) + M.remove_status_effect(/datum/status_effect/drowsiness) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -561,10 +561,10 @@ glass_desc = "Space Mountain Wind. As you know, there are no mountains in space, only wind." /datum/reagent/consumable/spacemountainwind/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-7) + M.adjust_drowsiness(-7 SECONDS) M.AdjustSleeping(-20, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -578,7 +578,7 @@ glass_desc = "Dr. Gibb. Not as dangerous as the glass_name might imply." /datum/reagent/consumable/dr_gibb/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-6) + M.adjust_drowsiness(-6 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -645,8 +645,8 @@ glass_desc = "Soda water. Why not make a scotch and soda?" /datum/reagent/consumable/sodawater/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -660,8 +660,8 @@ glass_desc = "Quinine tastes funny, but at least it'll keep that Space Malaria away." /datum/reagent/consumable/tonic/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -676,12 +676,12 @@ glass_name = "glass of Monkey Energy" glass_desc = "You can unleash the ape, but without the pop of the can?" -/datum/reagent/consumable/monkey_energy/on_mob_life(mob/living/carbon/M) - M.Jitter(20) - M.dizziness +=1 - M.drowsyness = 0 - M.AdjustSleeping(-40, FALSE) - M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) +/datum/reagent/consumable/monkey_energy/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_jitter(20 SECONDS) + affected_mob.adjust_dizzy(2 SECONDS * REM) + affected_mob.remove_status_effect(/datum/status_effect/drowsiness) + affected_mob.AdjustSleeping(-40, FALSE) + affected_mob.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() /datum/reagent/consumable/monkey_energy/on_mob_metabolize(mob/living/L) @@ -718,11 +718,11 @@ glass_desc = "A nice and refreshing beverage while you're reading." /datum/reagent/consumable/soy_latte/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.SetSleeping(0, FALSE) M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(M.getBruteLoss() && prob(20)) M.heal_bodypart_damage(1,0, 0) ..() @@ -739,11 +739,11 @@ glass_desc = "A nice, strong and refreshing beverage while you're reading." /datum/reagent/consumable/cafe_latte/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-10 SECONDS * REM) + M.adjust_drowsiness(-12 SECONDS * REM) M.SetSleeping(0, FALSE) M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(M.getBruteLoss() && prob(20)) M.heal_bodypart_damage(1,0, 0) ..() diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 2d1a3db2c642..75598c9b2171 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -28,9 +28,10 @@ to_chat(M, span_userdanger("You start tripping hard!")) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "[type]_overdose", /datum/mood_event/overdose, name) -/datum/reagent/drug/space_drugs/overdose_process(mob/living/M) - if(M.hallucination < volume && prob(20)) - M.hallucination += 5 +/datum/reagent/drug/space_drugs/overdose_process(mob/living/affected_mob) + var/hallucination_duration_in_seconds = (affected_mob.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10) + if(hallucination_duration_in_seconds < volume && prob(20)) + affected_mob.adjust_hallucinations(10 SECONDS) ..() /datum/reagent/drug/nicotine @@ -67,24 +68,24 @@ /datum/reagent/drug/nicotine/addiction_act_stage1(mob/living/M) if(prob(5) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() /datum/reagent/drug/nicotine/addiction_act_stage2(mob/living/M) if(prob(20) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() . = 1 /datum/reagent/drug/nicotine/addiction_act_stage3(mob/living/M) if(prob(20) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() . = 1 /datum/reagent/drug/nicotine/addiction_act_stage4(mob/living/M) if(prob(40) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() . = 1 @@ -213,7 +214,7 @@ M.AdjustParalyzed(-40, FALSE) M.AdjustImmobilized(-40, FALSE) M.adjustStaminaLoss(-2, 0) - M.Jitter(2) + M.adjust_jitter(2 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(1,4)) if(prob(5)) M.emote(pick("twitch", "shiver")) @@ -235,14 +236,14 @@ . = 1 /datum/reagent/drug/methamphetamine/addiction_act_stage1(mob/living/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(20)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/methamphetamine/addiction_act_stage2(mob/living/M) - M.Jitter(10) - M.Dizzy(10) + M.adjust_jitter(10 SECONDS) + M.adjust_dizzy(10) if(prob(30)) M.emote(pick("twitch","drool","moan")) ..() @@ -251,8 +252,8 @@ if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 4, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(15) - M.Dizzy(15) + M.adjust_jitter(15 SECONDS) + M.adjust_dizzy(15) if(prob(40)) M.emote(pick("twitch","drool","moan")) ..() @@ -261,8 +262,8 @@ if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 8, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(20) - M.Dizzy(20) + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(20) M.adjustToxLoss(5, 0) if(prob(50)) M.emote(pick("twitch","drool","moan")) @@ -307,7 +308,7 @@ to_chat(M, span_notice("[high_message]")) M.adjustStaminaLoss(-5, 0) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4) - M.hallucination += 5 + M.adjust_hallucinations(5 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) step(M, pick(GLOB.cardinals)) step(M, pick(GLOB.cardinals)) @@ -315,7 +316,7 @@ . = 1 /datum/reagent/drug/bath_salts/overdose_process(mob/living/M) - M.hallucination += 5 + M.adjust_hallucinations(5 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i in 1 to 8) step(M, pick(GLOB.cardinals)) @@ -326,47 +327,47 @@ ..() /datum/reagent/drug/bath_salts/addiction_act_stage1(mob/living/M) - M.hallucination += 10 + M.adjust_hallucinations(5 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 8, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(20)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/bath_salts/addiction_act_stage2(mob/living/M) - M.hallucination += 20 + M.adjust_hallucinations(10 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 8, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(10) - M.Dizzy(10) + M.adjust_jitter(10 SECONDS) + M.adjust_dizzy(10 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(30)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/bath_salts/addiction_act_stage3(mob/living/M) - M.hallucination += 30 + M.adjust_hallucinations(30 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 12, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(15) - M.Dizzy(15) + M.adjust_jitter(15 SECONDS) + M.adjust_dizzy(15 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(40)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/bath_salts/addiction_act_stage4(mob/living/carbon/human/M) - M.hallucination += 30 + M.adjust_hallucinations(30 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 16, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(50) - M.Dizzy(50) + M.adjust_jitter(50 SECONDS) + M.adjust_dizzy(50 SECONDS) M.adjustToxLoss(5, 0) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(50)) @@ -410,10 +411,10 @@ SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "happiness_drug") ..() -/datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/M) - M.jitteriness = 0 - M.confused = 0 - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2) +/datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.remove_status_effect(/datum/status_effect/jitter) + affected_mob.remove_status_effect(/datum/status_effect/confusion) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2) ..() . = 1 @@ -426,7 +427,7 @@ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug_good_od) if(2) M.emote("sway") - M.Dizzy(25) + M.adjust_dizzy(25) if(3) M.emote("frown") SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug_bad_od) @@ -437,7 +438,7 @@ /datum/reagent/drug/happiness/addiction_act_stage1(mob/living/M)// all work and no play makes jack a dull boy var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(min(mood.sanity, SANITY_DISTURBED)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(20)) M.emote(pick("twitch","laugh","frown")) ..() @@ -445,7 +446,7 @@ /datum/reagent/drug/happiness/addiction_act_stage2(mob/living/M) var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(min(mood.sanity, SANITY_UNSTABLE)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) if(prob(30)) M.emote(pick("twitch","laugh","frown")) ..() @@ -453,7 +454,7 @@ /datum/reagent/drug/happiness/addiction_act_stage3(mob/living/M) var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(min(mood.sanity, SANITY_CRAZY)) - M.Jitter(15) + M.adjust_jitter(15 SECONDS) if(prob(40)) M.emote(pick("twitch","laugh","frown")) ..() @@ -461,7 +462,7 @@ /datum/reagent/drug/happiness/addiction_act_stage4(mob/living/carbon/human/M) var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(SANITY_INSANE) - M.Jitter(20) + M.adjust_jitter(20 SECONDS) if(prob(50)) M.emote(pick("twitch","laugh","frown")) ..() @@ -492,17 +493,17 @@ if(10) to_chat(M, span_warning("You start to feel tired...") ) if(11 to 25) - M.drowsyness ++ + M.adjust_drowsiness(2 SECONDS) if(26 to INFINITY) M.Sleeping(60, 0) . = 1 //Providing a Mood Boost - M.confused -= 3 - M.jitteriness -= 5 + M.adjust_confusion(-3 SECONDS) + M.adjust_jitter(-5 SECONDS) M.disgust -= 3 //Ketamine is also a dissociative anasthetic which means Hallucinations! - M.hallucination += 5 + M.adjust_hallucinations(20 SECONDS) ..() /datum/reagent/drug/ketamine/overdose_process(mob/living/M) @@ -514,7 +515,7 @@ /datum/reagent/drug/ketamine/addiction_act_stage1(mob/living/M) if(prob(20)) M.drop_all_held_items() - M.Jitter(2) + M.adjust_jitter(2 SECONDS) ..() /datum/reagent/drug/ketamine/addiction_act_stage2(mob/living/M) @@ -522,8 +523,8 @@ M.drop_all_held_items() M.adjustToxLoss(2*REM, 0) . = 1 - M.Jitter(3) - M.Dizzy(3) + M.adjust_jitter(3 SECONDS) + M.adjust_dizzy(3) ..() /datum/reagent/drug/ketamine/addiction_act_stage3(mob/living/M) @@ -531,8 +532,8 @@ M.drop_all_held_items() M.adjustToxLoss(3*REM, 0) . = 1 - M.Jitter(4) - M.Dizzy(4) + M.adjust_jitter(4 SECONDS) + M.adjust_dizzy(4) ..() /datum/reagent/drug/pumpup @@ -556,7 +557,7 @@ ..() /datum/reagent/drug/pumpup/on_mob_life(mob/living/carbon/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(5)) to_chat(M, span_notice("[pick("Go! Go! GO!", "You feel ready...", "You feel invincible...")]")) @@ -570,7 +571,7 @@ to_chat(M, span_userdanger("You can't stop shaking, your heart beats faster and faster...")) /datum/reagent/drug/pumpup/overdose_process(mob/living/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(5)) M.drop_all_held_items() if(prob(15)) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 5f939b07accd..6e9257df52cd 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -312,16 +312,16 @@ victim.emote("scream") victim.blur_eyes(14) victim.blind_eyes(10) - victim.confused = max(M.confused, 10) + victim.set_confusion_if_lower(10 SECONDS) victim.damageoverlaytemp = 75 - victim.Paralyze(100) + victim.Paralyze(10 SECONDS) M.adjustStaminaLoss(3) return else if ( eyes_covered ) // Eye cover is better than mouth cover if(prob(20)) victim.emote("cough") victim.blur_eyes(4) - victim.confused = max(M.confused, 6) + victim.set_confusion_if_lower(5 SECONDS) victim.damageoverlaytemp = 50 M.adjustStaminaLoss(3) return @@ -330,9 +330,9 @@ victim.emote("scream") victim.blur_eyes(14) victim.blind_eyes(10) - victim.confused = max(M.confused, 12) + victim.set_confusion_if_lower(12 SECONDS) victim.damageoverlaytemp = 100 - victim.Paralyze(140) + victim.Paralyze(14 SECONDS) M.adjustStaminaLoss(5) victim.update_damage_hud() @@ -352,12 +352,6 @@ color = "#FFFFFF" // rgb: 255,255,255 taste_description = "salt" -/datum/reagent/consumable/sodiumchloride/reaction_mob(mob/living/M, method=TOUCH, reac_volume) - if(!istype(M)) - return - if(M.has_bane(BANE_SALT)) - M.mind.disrupt_spells(-200) - /datum/reagent/consumable/sodiumchloride/reaction_turf(turf/T, reac_volume) //Creates an umbra-blocking salt pile if(!istype(T)) return @@ -402,23 +396,22 @@ taste_description = "mushroom" /datum/reagent/drug/mushroomhallucinogen/on_mob_life(mob/living/carbon/M) - if(!M.slurring) - M.slurring = 1 + M.set_slurring_if_lower(1 SECONDS) switch(current_cycle) if(1 to 5) - M.Dizzy(5) + M.adjust_dizzy(5 SECONDS) M.set_drugginess(30) if(prob(10)) M.emote(pick("twitch","giggle")) if(5 to 10) - M.Jitter(10) - M.Dizzy(10) + M.adjust_jitter(10 SECONDS) + M.adjust_dizzy(10 SECONDS) M.set_drugginess(35) if(prob(20)) M.emote(pick("twitch","giggle")) if (10 to INFINITY) - M.Jitter(20) - M.Dizzy(20) + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(20 SECONDS) M.set_drugginess(40) if(prob(30)) M.emote(pick("twitch","giggle")) @@ -436,7 +429,7 @@ if(prob(min(25,current_cycle))) to_chat(M, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) M.Paralyze(10) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) else if(ishuman(M)) var/mob/living/carbon/human/H = M if(H.job == "Cook") diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 04981b3809a5..c076cc034b6e 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -44,7 +44,7 @@ M.radiation = 0 M.heal_bodypart_damage(5,5) M.adjustToxLoss(-5, 0, TRUE) - M.hallucination = 0 + M.remove_status_effect(/datum/status_effect/hallucination) REMOVE_TRAITS_NOT_IN(M, list(SPECIES_TRAIT, ROUNDSTART_TRAIT, ORGAN_TRAIT)) M.set_blurriness(0) M.set_blindness(0) @@ -54,14 +54,14 @@ M.SetParalyzed(0, FALSE) M.SetImmobilized(0, FALSE) M.silent = FALSE - M.dizziness = 0 + M.remove_status_effect(/datum/status_effect/dizziness) M.disgust = 0 - M.drowsyness = 0 - M.stuttering = 0 - M.slurring = 0 - M.confused = 0 + M.remove_status_effect(/datum/status_effect/drowsiness) + M.remove_status_effect(/datum/status_effect/speech/stutter) + M.remove_status_effect(/datum/status_effect/speech/slurring) + M.remove_status_effect(/datum/status_effect/confusion) M.SetSleeping(0, 0) - M.jitteriness = 0 + M.remove_status_effect(/datum/status_effect/jitter) if(M.blood_volume < BLOOD_VOLUME_NORMAL(M)) M.blood_volume = BLOOD_VOLUME_NORMAL(M) @@ -87,18 +87,18 @@ description = "Increases resistance to stuns as well as reducing drowsiness and hallucinations." color = "#FF00FF" -/datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(M.drowsyness-5, 0) - M.AdjustStun(-20, FALSE) - M.AdjustKnockdown(-20, FALSE) - M.AdjustUnconscious(-20, FALSE) - M.AdjustImmobilized(-20, FALSE) - M.AdjustParalyzed(-20, FALSE) +/datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_drowsiness(-10 SECONDS * REM) + affected_mob.AdjustStun(-20, FALSE) + affected_mob.AdjustKnockdown(-20, FALSE) + affected_mob.AdjustUnconscious(-20, FALSE) + affected_mob.AdjustImmobilized(-20, FALSE) + affected_mob.AdjustParalyzed(-20, FALSE) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) - M.hallucination = max(0, M.hallucination - 10) + affected_mob.adjust_hallucinations(-20 SECONDS * REM) if(prob(30)) - M.adjustToxLoss(1, 0) + affected_mob.adjustToxLoss(1, 0) . = 1 ..() @@ -108,12 +108,12 @@ color = "#EC536D" // rgb: 236, 83, 109 /datum/reagent/medicine/synaphydramine/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-10 SECONDS * REM) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) if(holder.has_reagent(/datum/reagent/toxin/histamine)) holder.remove_reagent(/datum/reagent/toxin/histamine, 5) - M.hallucination = max(0, M.hallucination - 10) + M.adjust_hallucinations(-20 SECONDS * REM) if(prob(30)) M.adjustToxLoss(1, 0) . = 1 @@ -220,8 +220,8 @@ /datum/reagent/medicine/rezadone/overdose_process(mob/living/M) M.adjustToxLoss(1, 0) - M.Dizzy(5) - M.Jitter(5) + M.adjust_dizzy(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -637,7 +637,7 @@ var/obj/item/I = M.get_active_held_item() if(I && M.dropItemToGround(I)) to_chat(M, "Your hands spaz out and you drop what you were holding!") - M.Jitter(10) + M.adjust_jitter(10 SECONDS) M.AdjustAllImmobility(-20, FALSE) M.adjustStaminaLoss(-1*REM, FALSE) @@ -663,8 +663,8 @@ /datum/reagent/medicine/ephedrine/addiction_act_stage1(mob/living/M) if(prob(3) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) - M.Unconscious(100) - M.Jitter(350) + M.Unconscious(10 SECONDS) + M.adjust_jitter(350 SECONDS) if(prob(33)) M.adjustToxLoss(2*REM, 0) @@ -675,8 +675,8 @@ /datum/reagent/medicine/ephedrine/addiction_act_stage2(mob/living/M) if(prob(6) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) - M.Unconscious(100) - M.Jitter(350) + M.Unconscious(10 SECONDS) + M.adjust_jitter(350 SECONDS) if(prob(33)) M.adjustToxLoss(3*REM, 0) @@ -688,7 +688,7 @@ if(prob(12) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) M.Unconscious(100) - M.Jitter(350) + M.adjust_jitter(350) if(prob(33)) M.adjustToxLoss(4*REM, 0) @@ -700,7 +700,7 @@ if(prob(24) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) M.Unconscious(100) - M.Jitter(350) + M.adjust_jitter(350) if(prob(33)) M.adjustToxLoss(5*REM, 0) @@ -718,14 +718,14 @@ /datum/reagent/medicine/diphenhydramine/on_mob_life(mob/living/carbon/M) if(prob(10)) - M.drowsyness += 1 - M.jitteriness -= 1 + M.adjust_drowsiness(1 SECONDS) + M.adjust_jitter(-1 SECONDS) M.reagents.remove_reagent(/datum/reagent/toxin/histamine,3) ..() /datum/reagent/medicine/diphenhydramine/overdose_process(mob/living/M) M.set_drugginess(15) - M.hallucination += 5*REM + M.adjust_hallucinations(20 SECONDS * REM) ..() /datum/reagent/medicine/morphine @@ -753,7 +753,7 @@ if(11) to_chat(M, span_warning("You start to feel tired...") ) if(12 to 24) - M.drowsyness += 1 + M.adjust_drowsiness(1 SECONDS) if(24 to INFINITY) M.Sleeping(40, 0) . = 1 @@ -762,14 +762,14 @@ /datum/reagent/medicine/morphine/overdose_process(mob/living/M) if(prob(33)) M.drop_all_held_items() - M.Dizzy(2) - M.Jitter(2) + M.adjust_dizzy(2) + M.adjust_jitter(2 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage1(mob/living/M) if(prob(33)) M.drop_all_held_items() - M.Jitter(2) + M.adjust_jitter(2 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage2(mob/living/M) @@ -777,8 +777,8 @@ M.drop_all_held_items() M.adjustToxLoss(1*REM, 0) . = 1 - M.Dizzy(3) - M.Jitter(3) + M.adjust_dizzy(3) + M.adjust_jitter(3 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage3(mob/living/M) @@ -786,8 +786,8 @@ M.drop_all_held_items() M.adjustToxLoss(2*REM, 0) . = 1 - M.Dizzy(4) - M.Jitter(4) + M.adjust_dizzy(4) + M.adjust_jitter(4 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage4(mob/living/M) @@ -795,8 +795,8 @@ M.drop_all_held_items() M.adjustToxLoss(3*REM, 0) . = 1 - M.Dizzy(5) - M.Jitter(5) + M.adjust_dizzy(5) + M.adjust_jitter(5 SECONDS) ..() /datum/reagent/medicine/oculine @@ -845,15 +845,15 @@ . = 1 M.losebreath = 0 if(prob(20)) - M.Dizzy(5) - M.Jitter(5) + M.adjust_dizzy(5) + M.adjust_jitter(5 SECONDS) ..() /datum/reagent/medicine/atropine/overdose_process(mob/living/M) M.adjustToxLoss(0.5*REM, 0) . = 1 - M.Dizzy(1) - M.Jitter(1) + M.adjust_dizzy(1) + M.adjust_jitter(1 SECONDS) ..() /datum/reagent/medicine/epinephrine @@ -973,7 +973,7 @@ taste_description = "acid" /datum/reagent/medicine/mutadone/on_mob_life(mob/living/carbon/M) - M.jitteriness = 0 + M.remove_status_effect(/datum/status_effect/jitter) if(M.has_dna()) M.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), TRUE) if(!QDELETED(M)) //We were a monkey, now a human @@ -984,17 +984,21 @@ description = "Purges alcoholic substance from the patient's body and eliminates its side effects." color = "#00B4C8" taste_description = "raw egg" - -/datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/M) - M.dizziness = 0 - M.drowsyness = 0 - M.slurring = 0 - M.confused = 0 - M.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3*REM, 0, 1) - M.adjustToxLoss(-0.2*REM, 0) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.drunkenness = max(H.drunkenness - 10, 0) + /// All status effects we remove on metabolize. + /// Does not include drunk (despite what you may think) as that's decresed gradually + var/static/list/status_effects_to_clear = list( + /datum/status_effect/confusion, + /datum/status_effect/dizziness, + /datum/status_effect/drowsiness, + /datum/status_effect/speech/slurring/drunk, + ) + +/datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/affected_mob) + for(var/effect in status_effects_to_clear) + affected_mob.remove_status_effect(effect) + affected_mob.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3*REM, 0, 1) + affected_mob.adjustToxLoss(-0.2*REM, 0) + affected_mob.adjust_drunk_effect(-10 * REM) ..() . = 1 @@ -1261,14 +1265,14 @@ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * REM, 150) //This does, after all, come from ambrosia, and the most powerful ambrosia in existence, at that! M.adjustCloneLoss(-1 * REM, 0) M.adjustStaminaLoss(-30 * REM, 0) - M.jitteriness = min(max(0, M.jitteriness + 3), 30) - M.druggy = min(max(0, M.druggy + 10), 15) //See above + M.adjust_jitter_up_to(6 SECONDS * REM, 1 MINUTES) + M.adjust_drugginess_up_to(20 SECONDS * REM, 30 SECONDS * REM) //See above ..() . = 1 -/datum/reagent/medicine/earthsblood/overdose_process(mob/living/M) - M.hallucination = min(max(0, M.hallucination + 5), 60) - M.adjustToxLoss(5 * REM, 0) +/datum/reagent/medicine/earthsblood/overdose_process(mob/living/affected_mob) + affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM, 120 SECONDS) + affected_mob.adjustToxLoss(5 * REM, 0) ..() . = 1 @@ -1282,11 +1286,9 @@ /datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/M) for(var/datum/reagent/drug/R in M.reagents.reagent_list) M.reagents.remove_reagent(R.type,5) - M.drowsyness += 2 - if(M.jitteriness >= 3) - M.jitteriness -= 3 - if (M.hallucination >= 5) - M.hallucination -= 5 + M.adjust_drowsiness(2 SECONDS) + M.adjust_jitter(-3 SECONDS) + M.adjust_hallucinations(-5 SECONDS) if(prob(20)) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1*REM, 50) M.adjustStaminaLoss(2.5*REM, 0) @@ -1409,7 +1411,7 @@ overdose_threshold = overdose_threshold + rand(-10,10)/10 // for extra fun M.AdjustAllImmobility(-5, FALSE) M.adjustStaminaLoss(-0.5*REM, 0) - M.Jitter(1) + M.adjust_jitter(1 SECONDS) metabolization_rate = 0.01 * REAGENTS_METABOLISM * rand(5,20) // randomizes metabolism between 0.02 and 0.08 per tick . = TRUE ..() @@ -1422,17 +1424,17 @@ overdose_progress++ switch(overdose_progress) if(1 to 40) - M.jitteriness = min(M.jitteriness+1, 10) - M.stuttering = min(M.stuttering+1, 10) - M.Dizzy(5) + M.adjust_jitter_up_to(1 SECONDS, 10 SECONDS) + M.adjust_stutter_up_to(1 SECONDS, 10 SECONDS) + M.adjust_dizzy(5) if(prob(50)) M.losebreath++ if(41 to 80) M.adjustOxyLoss(0.1*REM, 0) M.adjustStaminaLoss(0.1*REM, 0) - M.jitteriness = min(M.jitteriness+1, 20) - M.stuttering = min(M.stuttering+1, 20) - M.Dizzy(10) + M.adjust_jitter_up_to(1 SECONDS, 20 SECONDS) + M.adjust_stutter_up_to(1 SECONDS, 20 SECONDS) + M.adjust_dizzy(10) if(prob(50)) M.losebreath++ if(prob(20)) @@ -1466,20 +1468,20 @@ REMOVE_TRAIT(L, TRAIT_FEARLESS, type) ..() -/datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/M) - M.jitteriness = max(0, M.jitteriness-6) - M.dizziness = max(0, M.dizziness-6) - M.confused = max(0, M.confused-6) - M.disgust = max(0, M.disgust-6) - var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) +/datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/affected_mob, delta_time, times_fired) + affected_mob.adjust_jitter(-12 SECONDS * REM) + affected_mob.adjust_dizzy(-12 SECONDS * REM) + affected_mob.adjust_confusion(-6 SECONDS * REM) + affected_mob.disgust = max(affected_mob.disgust - (6 * REM), 0) + var/datum/component/mood/mood = affected_mob.GetComponent(/datum/component/mood) if(mood && mood.sanity <= SANITY_NEUTRAL) // only take effect if in negative sanity and then... mood.setSanity(min(mood.sanity+5, SANITY_NEUTRAL)) // set minimum to prevent unwanted spiking over neutral ..() . = 1 -/datum/reagent/medicine/psicodine/overdose_process(mob/living/M) - M.hallucination = min(max(0, M.hallucination + 5), 60) - M.adjustToxLoss(1, 0) +/datum/reagent/medicine/psicodine/overdose_process(mob/living/affected_mob) + affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM, 120 SECONDS) + affected_mob.adjustToxLoss(1, 0) ..() . = 1 @@ -1699,9 +1701,9 @@ M.emote("twitch") if(prob(90)) M.adjustToxLoss(tox_roll*REM, 0) - M.Jitter(jitter) + M.adjust_jitter(jitter) if(prob(1)) // Last set of non OD probability effects - M.Dizzy(jitter) + M.adjust_dizzy(jitter) M.apply_effect(slur, EFFECT_SLUR) // End of base probability effects if(heal_roll < 0) // Healing payload after calculations M.adjustFireLoss(heal_roll*REM, 0) @@ -1714,12 +1716,12 @@ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, pick(0,1,2,2.5,-1,-2)) if(prob(15)) M.set_drugginess(rand(2,6)) - M.Jitter(jitter) + M.adjust_jitter(jitter) if(prob(40)) M.adjustToxLoss(pick(1,-1)*REM, 0) if(prob(1)) - M.Dizzy(jitter) // end last set of OD probability effects - M.Jitter(jitter) // OD slur and jitter after calculations + M.adjust_dizzy(jitter) // end last set of OD probability effects + M.adjust_jitter(jitter) // OD slur and jitter after calculations M.apply_effect(slur, EFFECT_SLUR) if(heal_roll < 0) // OD Healing payload after calculations M.adjustFireLoss(heal_roll*REM, FALSE, FALSE, BODYPART_ORGANIC) @@ -1731,7 +1733,7 @@ if(!overdosed) jitter += 5 slur += 25 - M.drowsyness += 0.5 + M.adjust_drowsiness(0.5 SECONDS) heal_roll -= 0.2 else if(overdosed) heal_roll -= 0.4 @@ -1775,7 +1777,7 @@ if(prob(30)) jitter = 50 if(prob(30)) - M.drowsyness += 0.5 + M.adjust_drowsiness(0.5 SECONDS) if(prob(2)) to_chat(M, "Your fingers spasm!") M.drop_all_held_items() // end IF both drugs present @@ -1874,7 +1876,7 @@ if(M.stat != DEAD) return M.revive(full_heal = TRUE) - M.Jitter(10 SECONDS) + M.adjust_jitter(10 SECONDS) M.emote("gasp") /datum/reagent/medicine/naniteremover diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index c9a64fc5cb88..d142e376a3db 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -11,6 +11,11 @@ shot_glass_icon_state = "shotglassred" /datum/reagent/blood/reaction_mob(mob/living/L, method=TOUCH, reac_volume) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(L) //bloodsucker start + if(bloodsuckerdatum) + bloodsuckerdatum.bloodsucker_blood_volume = min(bloodsuckerdatum.bloodsucker_blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM(L)) + return //bloodsucker end + if(data && data["viruses"]) for(var/thing in data["viruses"]) var/datum/disease/D = thing @@ -182,7 +187,7 @@ return if(method == TOUCH) M.adjust_fire_stacks(-(reac_volume / 10)) - M.ExtinguishMob() + M.extinguish_mob() ..() /datum/reagent/water/on_mob_life(mob/living/carbon/M) @@ -220,17 +225,15 @@ if(!data) data = list("misc" = 1) data["misc"]++ - M.jitteriness = min(M.jitteriness+4,10) + M.adjust_jitter_up_to(4 SECONDS, 20 SECONDS) if(iscultist(M)) for(var/datum/action/innate/cult/blood_magic/BM in M.actions) to_chat(M, span_cultlarge("Your blood rites falter as holy water scours your body!")) for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) qdel(BS) if(data["misc"] >= 25) // 10 units, 45 seconds @ metabolism 0.4 units & tick rate 1.8 sec - if(!M.stuttering) - M.stuttering = 1 - M.stuttering = min(M.stuttering+4, 10) - M.Dizzy(5) + M.adjust_stutter_up_to(4 SECONDS, 20 SECONDS) + M.set_dizzy_if_lower(10 SECONDS) if(iscultist(M) && prob(20)) M.say(pick("Av'te Nar'sie","Pa'lid Mors","INO INO ORA ANA","SAT ANA!","Daim'niodeis Arc'iai Le'eones","R'ge Na'sie","Diabo us Vo'iscum","Eld' Mon Nobis"), forced = "holy water") if(prob(10)) @@ -253,8 +256,8 @@ SSticker.mode.remove_cultist(M.mind, FALSE, TRUE) else if(is_servant_of_ratvar(M)) remove_servant_of_ratvar(M) - M.jitteriness = 0 - M.stuttering = 0 + M.remove_status_effect(/datum/status_effect/jitter) + M.remove_status_effect(/datum/status_effect/speech/stutter) holder.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better?? return if(ishuman(M) && is_vampire(M) && prob(80)) // Yogs Start @@ -270,7 +273,7 @@ if(13 to INFINITY) M.visible_message("[M] suddenly bursts into flames!", span_userdanger("You suddenly ignite in a holy fire!")) M.adjust_fire_stacks(3) - M.IgniteMob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire + M.ignite_mob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire M.adjustFireLoss(3) //Hence the other damages... ain't I a bastard? // Yogs End if(ishuman(M) && is_sinfuldemon(M) && prob(80)) switch(data) @@ -287,7 +290,7 @@ if(13 to INFINITY) M.visible_message("[M] suddenly ignites in a brilliant flash of white!", span_userdanger("You suddenly ignite in a holy fire!")) M.adjust_fire_stacks(3) - M.IgniteMob() + M.ignite_mob() M.adjustFireLoss(4) holder.remove_reagent(type, 0.4) //fixed consumption to prevent balancing going out of whack @@ -313,7 +316,7 @@ /datum/reagent/fuel/unholywater/on_mob_life(mob/living/carbon/M) if(iscultist(M)) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-5 SECONDS) M.AdjustAllImmobility(-40, FALSE) M.adjustStaminaLoss(-10, 0) M.adjustToxLoss(-2, 0) @@ -339,7 +342,7 @@ /datum/reagent/hellwater/on_mob_life(mob/living/carbon/M) M.fire_stacks = min(5,M.fire_stacks + 3) - M.IgniteMob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire + M.ignite_mob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire M.adjustToxLoss(1, 0) M.adjustFireLoss(1, 0) //Hence the other damages... ain't I a bastard? M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5, 150) @@ -353,7 +356,7 @@ /datum/reagent/eldritch/on_mob_life(mob/living/carbon/M) if(IS_HERETIC(M) || IS_HERETIC_MONSTER(M)) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-5 SECONDS) M.AdjustAllImmobility(-40, FALSE) M.adjustStaminaLoss(-10, FALSE) M.adjustToxLoss(-2, FALSE) @@ -1069,7 +1072,7 @@ /datum/reagent/bluespace/on_mob_life(mob/living/carbon/M) if(current_cycle > 10 && prob(15)) to_chat(M, span_warning("You feel unstable...")) - M.Jitter(2) + M.adjust_jitter(2 SECONDS) current_cycle = 1 addtimer(CALLBACK(M, /mob/living/proc/bluespace_shuffle), 30) ..() @@ -1175,11 +1178,17 @@ metabolization_rate = 1.5 * REAGENTS_METABOLISM taste_description = "sourness" -/datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/M) - M.Dizzy(1) - if(!M.confused) - M.confused = 1 - M.confused = max(M.confused, 20) +/datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.set_dizzy_if_lower(2 SECONDS) + + // Cryptobiolin adjusts the mob's confusion down to 20 seconds if it's higher, + // or up to 1 second if it's lower, but will do nothing if it's in between + var/confusion_left = affected_mob.get_timed_status_effect_duration(/datum/status_effect/confusion) + if(confusion_left < 1 SECONDS) + affected_mob.set_confusion(1 SECONDS) + + else if(confusion_left > 20 SECONDS) + affected_mob.set_confusion(20 SECONDS) ..() /datum/reagent/impedrezene @@ -1188,14 +1197,14 @@ color = "#C8A5DC" // rgb: 200, 165, 220A taste_description = "numbness" -/datum/reagent/impedrezene/on_mob_life(mob/living/carbon/M) - M.jitteriness = max(M.jitteriness-5,0) +/datum/reagent/impedrezene/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_jitter(-5 SECONDS) if(prob(80)) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM) if(prob(50)) - M.drowsyness = max(M.drowsyness, 3) + affected_mob.adjust_drowsiness(6 SECONDS) if(prob(10)) - M.emote("drool") + affected_mob.emote("drool") ..() /datum/reagent/nanomachines @@ -1313,18 +1322,20 @@ var/temp = holder ? holder.chem_temp : T20C T.atmos_spawn_air("n2o=[reac_volume/5];TEMP=[temp]") -/datum/reagent/nitrous_oxide/reaction_mob(mob/living/M, method=TOUCH, reac_volume) +/datum/reagent/nitrous_oxide/reaction_mob(mob/living/exposed_mob, method=TOUCH, reac_volume) if(method == VAPOR) - M.drowsyness += max(round(reac_volume, 1), 2) + // apply 2 seconds of drowsiness per unit applied, with a min duration of 4 seconds + var/drowsiness_to_apply = max(round(reac_volume, 1) * 2 SECONDS, 4 SECONDS) + exposed_mob.adjust_drowsiness(drowsiness_to_apply) /datum/reagent/nitrous_oxide/on_mob_life(mob/living/carbon/M) - M.drowsyness += 2 + M.adjust_drowsiness(4 SECONDS * REM) if(ishuman(M)) var/mob/living/carbon/human/H = M H.blood_volume = max(H.blood_volume - 5, 0) if(prob(20)) M.losebreath += 2 - M.confused = min(M.confused + 2, 5) + M.adjust_confusion_up_to(2 SECONDS, 5 SECONDS) ..() /datum/reagent/nitrium_low_metabolization @@ -1356,7 +1367,7 @@ if(M.getStaminaLoss() > 0) M.adjustStaminaLoss(-2 * REM, FALSE) M.adjustToxLoss(1.5 *REM, FALSE) - M.Jitter(15) + M.adjust_jitter(15 SECONDS) return ..() /datum/reagent/nitrium_high_metabolization @@ -2021,9 +2032,9 @@ if(L.mind && L.mind.has_antag_datum(/datum/antagonist/changeling)) //yogs to_chat(L, span_boldnotice("Our blood is pure, we can regenerate chemicals again.")) //yogs -/datum/reagent/bz_metabolites/on_mob_life(mob/living/L) - if(L.mind) - var/datum/antagonist/changeling/changeling = L.mind.has_antag_datum(/datum/antagonist/changeling) +/datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target) + if(target.mind) + var/datum/antagonist/changeling/changeling = target.mind.has_antag_datum(/datum/antagonist/changeling) if(changeling) changeling.chem_charges = max(changeling.chem_charges-6, 0) return ..() @@ -2039,13 +2050,11 @@ metabolization_rate = 1.5 * REAGENTS_METABOLISM taste_description = "dizziness" -/datum/reagent/peaceborg/confuse/on_mob_life(mob/living/carbon/M) - if(M.confused < 6) - M.confused = clamp(M.confused + 3, 0, 5) - if(M.dizziness < 6) - M.dizziness = clamp(M.dizziness + 3, 0, 5) +/datum/reagent/peaceborg/confuse/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_confusion_up_to(3 SECONDS * REM , 5 SECONDS) + affected_mob.adjust_dizzy_up_to(6 SECONDS * REM, 12 SECONDS) if(prob(20)) - to_chat(M, "You feel confused and disorientated.") + to_chat(affected_mob, "You feel confused and disorientated.") ..() /datum/reagent/peaceborg/tire diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index a3689f63b783..145c5db97230 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -71,7 +71,7 @@ if(istype(M)) if(method != INGEST && method != INJECT) M.adjust_fire_stacks(min(reac_volume/5, 10)) - M.IgniteMob() + M.ignite_mob() if(!locate(/obj/effect/hotspot) in M.loc) new /obj/effect/hotspot(M.loc) @@ -100,7 +100,7 @@ /datum/reagent/blackpowder/on_mob_life(mob/living/carbon/M) ..() if(isplasmaman(M)) - M.hallucination += 5 + M.adjust_hallucinations(20 SECONDS) /datum/reagent/blackpowder/on_ex_act() var/location = get_turf(holder.my_atom) @@ -143,7 +143,7 @@ M.adjust_fire_stacks(1) var/burndmg = max(0.3*M.fire_stacks, 0.3) M.adjustFireLoss(burndmg, 0) - M.IgniteMob() + M.ignite_mob() ..() /datum/reagent/phlogiston/on_mob_life(mob/living/carbon/M) @@ -291,5 +291,5 @@ /datum/reagent/firefighting_foam/reaction_mob(mob/living/M, method=TOUCH, reac_volume) if(method in list(VAPOR, TOUCH)) M.adjust_fire_stacks(-reac_volume) - M.ExtinguishMob() + M.extinguish_mob() ..() diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 4dc0689782e7..abca2aa9f6da 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -198,9 +198,9 @@ return TRUE switch(current_cycle) if(1 to 5) - M.confused += 1 - M.drowsyness += 1 - M.slurring += 3 + M.adjust_confusion(1 SECONDS) + M.adjust_drowsiness(1 SECONDS) + M.adjust_slurring(3 SECONDS) if(5 to 8) M.adjustStaminaLoss(40, 0) if(9 to INFINITY) @@ -236,7 +236,7 @@ /datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/M) if(!M.has_trauma_type(/datum/brain_trauma/mild/reality_dissociation)) - M.hallucination += 5 + M.adjust_hallucinations(20 SECONDS) return ..() /datum/reagent/toxin/relaxant @@ -316,7 +316,7 @@ /datum/reagent/toxin/spore_burning/on_mob_life(mob/living/carbon/M) M.adjust_fire_stacks(2) - M.IgniteMob() + M.ignite_mob() return ..() /datum/reagent/toxin/chloralhydrate @@ -328,18 +328,18 @@ toxpwr = 0 metabolization_rate = 1.5 * REAGENTS_METABOLISM -/datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/M) +/datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/affected_mob) switch(current_cycle) if(1 to 10) - M.confused += 2 - M.drowsyness += 2 + affected_mob.adjust_confusion(2 SECONDS * REM) + affected_mob.adjust_drowsiness(4 SECONDS * REM) if(10 to 50) - M.Sleeping(40, 0) - . = 1 + affected_mob.Sleeping(4 SECONDS * REM) + . = TRUE if(51 to INFINITY) - M.Sleeping(40, 0) - M.adjustToxLoss((current_cycle - 50)*REM, 0) - . = 1 + affected_mob.Sleeping(4 SECONDS * REM ) + affected_mob.adjustToxLoss(1 * (current_cycle - 50) * REM, FALSE) + . = TRUE ..() /datum/reagent/toxin/fakebeer //disguised as normal beer for use by emagged brobots @@ -418,12 +418,12 @@ return selected /datum/reagent/toxin/staminatoxin/neurotoxin_alien/on_mob_life(mob/living/carbon/M) - M.dizziness += 2 + M.adjust_dizzy(2 SECONDS) if(prob(40)) if(prob(50)) var/part = pickparalyze() if(part) - to_chat(M, span_userdanger("one of your limbs goes numb!")) + M.balloon_alert(M, "your limbs go numb!") ADD_TRAIT(M, part, type) else M.drop_all_held_items() diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 4c7e2cc2aa84..9e372db9d103 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -90,7 +90,7 @@ to_chat(C, span_userdanger("The divine explosion sears you!")) C.Paralyze(40) C.adjust_fire_stacks(5) - C.IgniteMob() + C.ignite_mob() /datum/chemical_reaction/blackpowder name = "Black Powder" diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm index 59b4f21c0139..0ec2ddb5d18b 100644 --- a/code/modules/reagents/reagent_containers/blood_pack.dm +++ b/code/modules/reagents/reagent_containers/blood_pack.dm @@ -8,45 +8,49 @@ var/unique_blood = null var/labelled = 0 +#define BLOODBAG_GULP_SIZE 10 + /obj/item/reagent_containers/blood/attack(mob/target, mob/user, def_zone) - if(reagents.total_volume > 0) - if(target != user) - user.visible_message( - span_notice("[user] forces [target] to drink from the [src]."), - span_notice("You put the [src] up to [target]'s mouth."), - ) - if(!do_mob(user, target, 5 SECONDS)) - return - else - if(!do_mob(user, target, 1 SECONDS)) - return - user.visible_message( - span_notice("[user] puts the [src] up to their mouth."), - span_notice("You take a sip from the [src]."), - ) - // Safety: In case you spam clicked the blood bag on yourself, and it is now empty (below will divide by zero) - if(reagents.total_volume <= 0) + if(!reagents.total_volume) + user.balloon_alert(user, "empty!") + return ..() + + if(target != user) + if(!do_after(user, 5 SECONDS, target)) return - var/gulp_size = 5 - if(is_vampire(target)) - gulp_size = 10 - var/datum/antagonist/vampire/V = is_vampire(target) - V.usable_blood += 5 - - if(IS_BLOODSUCKER(target)) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = target.mind.has_antag_datum(/datum/antagonist/bloodsucker) - bloodsuckerdatum.AddBloodVolume(gulp_size) - var/mob/living/carbon/H = target - addtimer(CALLBACK(reagents, TYPE_PROC_REF(/datum/reagents, trans_to), target, gulp_size), 5) - reagents.reaction(target, INGEST, 100*gulp_size) - if(H.blood_volume >= bloodsuckerdatum.max_blood_volume) - to_chat(target, span_notice("You are full, and can't consume more blood")) - return - else - reagents.reaction(target, INGEST, gulp_size) - addtimer(CALLBACK(reagents, TYPE_PROC_REF(/datum/reagents, trans_to), target, gulp_size), 5) - playsound(user.loc, 'sound/items/drink.ogg', rand(10,50), 1) - return ..() + user.visible_message( + span_notice("[user] forces [target] to drink from the [src]."), + span_notice("You put the [src] up to [target]'s mouth."), + ) + reagents.reaction(user, INGEST, BLOODBAG_GULP_SIZE) + reagents.trans_to(user, BLOODBAG_GULP_SIZE, transfered_by = user) + playsound(user.loc, 'sound/items/drink.ogg', rand(10, 50), TRUE) + return TRUE + + while(do_after(user, 1 SECONDS, stayStill = FALSE)) + user.visible_message( + span_notice("[user] puts the [src] up to their mouth."), + span_notice("You take a sip from the [src]."), + ) + var/datum/antagonist/vampire/V = is_vampire(user) + V?.usable_blood += 5 + reagents.reaction(user, INGEST, BLOODBAG_GULP_SIZE) + reagents.trans_to(user, BLOODBAG_GULP_SIZE, transfered_by = user) + playsound(user.loc, 'sound/items/drink.ogg', rand(10, 50), TRUE) + return TRUE + +#undef BLOODBAG_GULP_SIZE + +///Bloodbag of Bloodsucker blood (used by Vassals only) +/obj/item/reagent_containers/blood/o_minus/bloodsucker + name = "blood pack" + unique_blood = /datum/reagent/blood/bloodsucker + +/obj/item/reagent_containers/blood/o_minus/bloodsucker/examine(mob/user) + . = ..() + if(user.mind.has_antag_datum(/datum/antagonist/ex_vassal) || user.mind.has_antag_datum(/datum/antagonist/vassal/revenge)) + . += span_notice("Seems to be just about the same color as your Master's...") + /obj/item/reagent_containers/blood/Initialize() . = ..() diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index 5418727b7fa6..678c2b7d51e9 100755 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -267,7 +267,7 @@ SLOT_WEAR_MASK, SLOT_HEAD, SLOT_NECK,\ SLOT_SHOES, SLOT_GLOVES,\ SLOT_EARS, SLOT_GLASSES,\ - SLOT_BELT, SLOT_S_STORE,\ + SLOT_BELT, SLOT_SUIT_STORE,\ SLOT_L_STORE, SLOT_R_STORE,\ SLOT_GENERC_DEXTROUS_STORAGE ) diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index 3db50cbcaa55..1ca00f8877fd 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -50,7 +50,7 @@ /obj/item/reagent_containers/syringe/attackby(obj/item/I, mob/user, params) return -/obj/item/reagent_containers/syringe/afterattack(atom/target, mob/user , proximity) +/obj/item/reagent_containers/syringe/afterattack(atom/target, mob/user, proximity) . = ..() if(busy) return @@ -59,16 +59,9 @@ if(!target.reagents) return - var/mob/living/L - if(isliving(target)) - L = target - if(!L.can_inject(null, TRUE, BODY_ZONE_CHEST, proj_piercing)) - return - // chance of monkey retaliation if(ismonkey(target) && prob(MONKEY_SYRINGE_RETALIATION_PROB)) - var/mob/living/carbon/monkey/M - M = target + var/mob/living/carbon/monkey/M = target M.retaliate(user) switch(mode) @@ -78,7 +71,8 @@ to_chat(user, span_notice("The syringe is full.")) return - if(L) //living mob + if(isliving(target)) //living mob + var/mob/living/L = target var/drawn_amount = reagents.maximum_volume - reagents.total_volume if(target != user) target.visible_message(span_danger("[user] is trying to take a blood sample from [target]!"), \ @@ -120,7 +114,7 @@ to_chat(user, span_notice("[src] is empty.")) return - if(!L && !target.is_injectable(user)) //only checks on non-living mobs, due to how can_inject() handles + if(!isliving(target) && !target.is_injectable(user)) //only checks on non-living mobs, due to how can_inject() handles to_chat(user, span_warning("You cannot directly fill [target]!")) return @@ -128,7 +122,9 @@ to_chat(user, span_notice("[target] is full.")) return - if(L) //living mob + var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) + if(isliving(target)) //living mob + var/mob/living/L = target if(!L.can_inject(null, TRUE, BODY_ZONE_CHEST, proj_piercing)) return if(L != user) @@ -164,8 +160,7 @@ log_combat(user, L, "injected", src, addition="which had [contained]") else L.log_message("injected themselves ([contained]) with [src.name]", LOG_ATTACK, color="orange") - var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) - reagents.reaction(L, INJECT, fraction) + reagents.reaction(L, INJECT, fraction) reagents.trans_to(target, amount_per_transfer_from_this, transfered_by = user) to_chat(user, span_notice("You inject [amount_per_transfer_from_this] units of the solution. The syringe now contains [reagents.total_volume] units.")) if (reagents.total_volume <= 0 && mode==SYRINGE_INJECT) diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index a6d5c9f25b19..afe6cd520b0f 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -430,7 +430,7 @@ /datum/religion_sect/holylight/on_conversion(mob/living/L) . = ..() - for(var/obj/item/storage/book/bible/da_bible in L.GetAllContents()) + for(var/obj/item/storage/book/bible/da_bible in L.get_all_contents()) da_bible.success_heal_chance = 100 /datum/religion_sect/holylight/sect_bless(mob/living/L, mob/living/user) diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 7b624833966b..782688384e82 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -395,7 +395,7 @@ if(MT) visible_message(span_danger("[src] dangerously overheats, launching a flaming fuel orb!")) investigate_log("Experimentor has launched a fireball at [M]!", INVESTIGATE_EXPERIMENTOR) - var/obj/item/projectile/magic/aoe/fireball/FB = new /obj/item/projectile/magic/aoe/fireball(start) + var/obj/item/projectile/magic/fireball/FB = new /obj/item/projectile/magic/fireball(start) FB.preparePixelProjectile(MT, start) FB.fire() else if(prob(EFFECT_PROB_LOW-badThingCoeff)) diff --git a/code/modules/research/nanites/nanite_programs/healing.dm b/code/modules/research/nanites/nanite_programs/healing.dm index 6ad4544b9494..b015d1695caa 100644 --- a/code/modules/research/nanites/nanite_programs/healing.dm +++ b/code/modules/research/nanites/nanite_programs/healing.dm @@ -237,7 +237,7 @@ C.set_heartattack(FALSE) C.revive() C.emote("gasp") - C.Jitter(10 SECONDS) + C.adjust_jitter(10 SECONDS) SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK) else playsound(C, 'sound/machines/defib_failed.ogg', 50, 0) diff --git a/code/modules/research/nanites/nanite_programs/rogue.dm b/code/modules/research/nanites/nanite_programs/rogue.dm index 008171043c0e..37acbe23bc47 100644 --- a/code/modules/research/nanites/nanite_programs/rogue.dm +++ b/code/modules/research/nanites/nanite_programs/rogue.dm @@ -63,7 +63,7 @@ /datum/nanite_program/brain_decay/active_effect() if(prob(4)) - host_mob.hallucination = min(15, host_mob.hallucination) + host_mob.adjust_hallucinations_up_to(15 SECONDS, 5 SECONDS) host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1) //Generic brain-affecting programs can also decay into this @@ -79,13 +79,13 @@ if(prob(10)) switch(rand(1,4)) if(1) - host_mob.hallucination += 15 + host_mob.adjust_hallucinations(15 SECONDS) if(2) - host_mob.confused += 10 + host_mob.adjust_confusion(10 SECONDS) if(3) - host_mob.drowsyness += 10 + host_mob.adjust_drowsiness(10 SECONDS) if(4) - host_mob.slurring += 10 + host_mob.adjust_slurring(10 SECONDS) //Generic skin-affecting programs will decay into this /datum/nanite_program/skin_decay @@ -118,10 +118,10 @@ /datum/nanite_program/nerve_decay/active_effect() if(prob(5)) to_chat(host_mob, span_warning("You feel unbalanced!")) - host_mob.confused += 10 + host_mob.adjust_confusion(10 SECONDS) else if(prob(4)) to_chat(host_mob, span_warning("You can't feel your hands!")) host_mob.drop_all_held_items() else if(prob(4)) to_chat(host_mob, span_warning("You can't feel your legs!")) - host_mob.Knockdown(30) + host_mob.Knockdown(3 SECONDS) diff --git a/code/modules/research/nanites/nanite_programs/suppression.dm b/code/modules/research/nanites/nanite_programs/suppression.dm index 54d6ffc23603..a551fb88cff3 100644 --- a/code/modules/research/nanites/nanite_programs/suppression.dm +++ b/code/modules/research/nanites/nanite_programs/suppression.dm @@ -12,8 +12,8 @@ if(!..()) return to_chat(host_mob, span_warning("You start to feel very sleepy...")) - host_mob.drowsyness += 20 - addtimer(CALLBACK(host_mob, TYPE_PROC_REF(/mob/living, Sleeping), 200), rand(60,200)) + host_mob.adjust_drowsiness(20 SECONDS) + addtimer(CALLBACK(host_mob, TYPE_PROC_REF(/mob/living, Sleeping), 200), rand(60, 200)) /datum/nanite_program/triggered/shocking name = "Electric Shock" @@ -216,7 +216,7 @@ return var/mob/living/carbon/C = host_mob if(!hal_type) - C.hallucination += 15 + C.adjust_hallucinations(15 SECONDS) else switch(hal_type) if("Message") diff --git a/code/modules/research/nanites/nanite_programs/utility.dm b/code/modules/research/nanites/nanite_programs/utility.dm index cbcfcd0aaf6d..eeb7698603de 100644 --- a/code/modules/research/nanites/nanite_programs/utility.dm +++ b/code/modules/research/nanites/nanite_programs/utility.dm @@ -402,8 +402,8 @@ /datum/action/innate/nanite_button name = "Button" - icon_icon = 'icons/mob/actions/actions_items.dmi' - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + button_icon = 'icons/mob/actions/actions_items.dmi' + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS button_icon_state = "power_green" var/datum/nanite_program/dermal_button/program diff --git a/code/modules/research/nanites/nanite_programs/weapon.dm b/code/modules/research/nanites/nanite_programs/weapon.dm index 3e3542662ef0..2590b8980f49 100644 --- a/code/modules/research/nanites/nanite_programs/weapon.dm +++ b/code/modules/research/nanites/nanite_programs/weapon.dm @@ -138,7 +138,7 @@ /datum/nanite_program/pyro/active_effect() host_mob.adjust_fire_stacks(1) - host_mob.IgniteMob() + host_mob.ignite_mob() /datum/nanite_program/pyro name = "Sub-Dermal Combustion" @@ -154,7 +154,7 @@ /datum/nanite_program/pyro/active_effect() host_mob.adjust_fire_stacks(1) - host_mob.IgniteMob() + host_mob.ignite_mob() /datum/nanite_program/cryo name = "Cryogenic Treatment" diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm index 410b2868a9fa..61e85a918bcc 100644 --- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm +++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm @@ -63,11 +63,11 @@ Slimecrossing Armor /datum/action/item_action/change_prism_colour name = "Adjust Prismatic Lens" - icon_icon = 'icons/obj/slimecrossing.dmi' + button_icon = 'icons/obj/slimecrossing.dmi' button_icon_state = "prismcolor" /datum/action/item_action/change_prism_colour/Trigger() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return var/obj/item/clothing/glasses/prism_glasses/glasses = target var/new_color = input(owner, "Choose the lens color:", "Color change",glasses.glasses_color) as color|null @@ -77,11 +77,11 @@ Slimecrossing Armor /datum/action/item_action/place_light_prism name = "Fabricate Light Prism" - icon_icon = 'icons/obj/slimecrossing.dmi' + button_icon = 'icons/obj/slimecrossing.dmi' button_icon_state = "lightprism" /datum/action/item_action/place_light_prism/Trigger() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return var/obj/item/clothing/glasses/prism_glasses/glasses = target if(locate(/obj/structure/light_prism) in get_turf(owner)) diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index d7a6935419ea..99967f100518 100644 --- a/code/modules/research/xenobiology/crossbreeding/_misc.dm +++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm @@ -209,7 +209,7 @@ Slimecrossing Items icon_state = "slimebarrier_thick" CanAtmosPass = ATMOS_PASS_NO opacity = TRUE - timeleft = 100 + initial_duration = 10 SECONDS //Rainbow barrier - Chilling Rainbow /obj/effect/forcefield/slimewall/rainbow diff --git a/code/modules/research/xenobiology/crossbreeding/_mobs.dm b/code/modules/research/xenobiology/crossbreeding/_mobs.dm index 834f5c1f09f4..cbaabdf35d53 100644 --- a/code/modules/research/xenobiology/crossbreeding/_mobs.dm +++ b/code/modules/research/xenobiology/crossbreeding/_mobs.dm @@ -4,30 +4,37 @@ Slimecrossing Mobs Collected here for clarity. */ -//Slime transformation power - Burning Black -/obj/effect/proc_holder/spell/targeted/shapeshift/slimeform +/// Slime transformation power - from Burning Black +/datum/action/cooldown/spell/shapeshift/slime_form name = "Slime Transformation" desc = "Transform from a human to a slime, or back again!" - action_icon_state = "transformslime" - cooldown_min = 0 - charge_max = 0 - invocation_type = SPELL_INVOCATION_NONE - shapeshift_type = /mob/living/simple_animal/slime/transformedslime + button_icon_state = "transformslime" + cooldown_time = 0 SECONDS + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + convert_damage = TRUE convert_damage_type = CLONE + possible_shapes = list(/mob/living/simple_animal/slime/transformed_slime) + + /// If TRUE, we self-delete (remove ourselves) the next time we turn back into a human var/remove_on_restore = FALSE -/obj/effect/proc_holder/spell/targeted/shapeshift/slimeform/Restore(mob/living/M) +/datum/action/cooldown/spell/shapeshift/slime_form/do_unshapeshift(mob/living/caster) + . = ..() + if(!.) + return + if(remove_on_restore) - if(M.mind) - M.mind.RemoveSpell(src) - ..() + qdel(src) -//Transformed slime - Burning Black -/mob/living/simple_animal/slime/transformedslime +/// Transformed slime - from Burning Black +/mob/living/simple_animal/slime/transformed_slime -/mob/living/simple_animal/slime/transformedslime/Reproduce() //Just in case. - to_chat(src, span_warning("I can't reproduce...")) +// Just in case. +/mob/living/simple_animal/slime/transformed_slime/Reproduce() + to_chat(src, span_warning("I can't reproduce...")) // Mood return //Slime corgi - Chilling Pink diff --git a/code/modules/research/xenobiology/crossbreeding/_potions.dm b/code/modules/research/xenobiology/crossbreeding/_potions.dm index 8dca506eab61..188ae83d7619 100644 --- a/code/modules/research/xenobiology/crossbreeding/_potions.dm +++ b/code/modules/research/xenobiology/crossbreeding/_potions.dm @@ -176,7 +176,7 @@ Slimecrossing Potions if(M.maxHealth <= 0) to_chat(user, span_warning("The slime is too unstable to return!")) M.revive(full_heal = 1) - M.stat = CONSCIOUS + M.set_stat(CONSCIOUS) M.visible_message(span_notice("[M] is filled with renewed vigor and blinks awake!")) M.maxHealth -= 10 //Revival isn't healthy. M.health -= 10 diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 76bd5ffdbd75..256606f8fbeb 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -213,7 +213,7 @@ /datum/status_effect/bonechill/tick() if(prob(50)) owner.adjustFireLoss(1) - owner.Jitter(3) + owner.adjust_jitter(3 SECONDS) owner.adjust_bodytemperature(-10) /datum/status_effect/bonechill/on_remove() @@ -484,24 +484,33 @@ datum/status_effect/rebreathing/tick() /datum/status_effect/stabilized/purple id = "stabilizedpurple" colour = "purple" + /// Whether we healed from our last tick + var/healed_last_tick = FALSE /datum/status_effect/stabilized/purple/tick() - var/is_healing = FALSE if(owner.getBruteLoss() > 0) owner.adjustBruteLoss(-0.2) - is_healing = TRUE + healed_last_tick = TRUE + if(owner.getFireLoss() > 0) owner.adjustFireLoss(-0.2) - is_healing = TRUE + healed_last_tick = TRUE + if(owner.getToxLoss() > 0) owner.adjustToxLoss(-0.2, forced = TRUE) //Slimepeople should also get healed. - is_healing = TRUE - if(is_healing) - examine_text = span_warning("SUBJECTPRONOUN is regenerating slowly, purplish goo filling in small injuries!") + healed_last_tick = TRUE + + // Technically, "healed this tick" by now. + if(healed_last_tick) new /obj/effect/temp_visual/heal(get_turf(owner), "#FF0000") - else - examine_text = null - ..() + + return ..() + +/datum/status_effect/stabilized/purple/get_examine_text() + if(healed_last_tick) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] regenerating slowly, purplish goo filling in small injuries!") + + return null /datum/status_effect/stabilized/blue id = "stabilizedblue" @@ -526,7 +535,7 @@ datum/status_effect/stabilized/blue/on_remove() else cooldown = max_cooldown var/list/sheets = list() - for(var/obj/item/stack/sheet/S in owner.GetAllContents()) + for(var/obj/item/stack/sheet/S in owner.get_all_contents()) if(S.amount < S.max_amount) sheets += S @@ -550,7 +559,7 @@ datum/status_effect/stabilized/blue/on_remove() return ..() cooldown = max_cooldown var/list/batteries = list() - for(var/obj/item/stock_parts/cell/C in owner.GetAllContents()) + for(var/obj/item/stock_parts/cell/C in owner.get_all_contents()) if(C.charge < C.maxcharge) batteries += C if(batteries.len) @@ -680,7 +689,7 @@ datum/status_effect/stabilized/blue/on_remove() owner.apply_status_effect(/datum/status_effect/bluespacestabilization) to_chat(owner, span_warning("You feel sick after [linked_extract] dragged you through bluespace.")) owner.Stun(1 SECONDS) - owner.dizziness += 30 + owner.adjust_dizzy(30 SECONDS) healthcheck = owner.health return ..() diff --git a/code/modules/research/xenobiology/crossbreeding/_structures.dm b/code/modules/research/xenobiology/crossbreeding/_structures.dm index 022e2082fdec..c697f97c2cc3 100644 --- a/code/modules/research/xenobiology/crossbreeding/_structures.dm +++ b/code/modules/research/xenobiology/crossbreeding/_structures.dm @@ -136,7 +136,7 @@ GLOBAL_LIST_EMPTY(bluespace_slime_crystals) return var/mob/living/carbon/carbon_mob = affected_mob carbon_mob.fire_stacks++ - carbon_mob.IgniteMob() + carbon_mob.ignite_mob() /obj/structure/slime_crystal/orange/process() . = ..() diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm index a32c8f968620..373c2754e875 100644 --- a/code/modules/research/xenobiology/crossbreeding/burning.dm +++ b/code/modules/research/xenobiology/crossbreeding/burning.dm @@ -275,15 +275,14 @@ Burning extracts: effect_desc = "Transforms the user into a slime. They can transform back at will and do not lose any items." /obj/item/slimecross/burning/black/do_effect(mob/user) - var/mob/living/L = user - if(!istype(L)) + if(!isliving(user)) return user.visible_message(span_danger("[src] absorbs [user], transforming [user.p_them()] into a slime!")) - var/obj/effect/proc_holder/spell/targeted/shapeshift/slimeform/S = new() - S.remove_on_restore = TRUE - user.mind.AddSpell(S) - S.cast(list(user),user) - ..() + var/datum/action/cooldown/spell/shapeshift/slime_form/transform = new(user.mind || user) + transform.remove_on_restore = TRUE + transform.Grant(user) + transform.cast(user) + return ..() /obj/item/slimecross/burning/lightpink colour = "light pink" diff --git a/code/modules/research/xenobiology/crossbreeding/consuming.dm b/code/modules/research/xenobiology/crossbreeding/consuming.dm index d63e88e1e1b0..21f4dab57dea 100644 --- a/code/modules/research/xenobiology/crossbreeding/consuming.dm +++ b/code/modules/research/xenobiology/crossbreeding/consuming.dm @@ -196,7 +196,7 @@ Consuming extracts: /obj/item/slime_cookie/darkblue/do_effect(mob/living/M, mob/user) M.adjust_bodytemperature(-110) - M.ExtinguishMob() + M.extinguish_mob() /obj/item/slimecross/consuming/silver colour = "silver" diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm index 806ea1a043cf..c0766e81aefd 100644 --- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm +++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm @@ -85,7 +85,7 @@ Regenerative extracts: /obj/item/slimecross/regenerative/yellow/core_effect(mob/living/target, mob/user) var/list/batteries = list() - for(var/obj/item/stock_parts/cell/C in target.GetAllContents()) + for(var/obj/item/stock_parts/cell/C in target.get_all_contents()) if(C.charge < C.maxcharge) batteries += C if(batteries.len) diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index 39d123a2b655..8b9fc265fc4f 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -178,7 +178,7 @@ /datum/action/innate/slime_place name = "Place Slimes" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "slime_down" /datum/action/innate/slime_place/Activate() @@ -198,7 +198,7 @@ /datum/action/innate/slime_pick_up name = "Pick up Slime" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "slime_up" /datum/action/innate/slime_pick_up/Activate() @@ -224,7 +224,7 @@ /datum/action/innate/feed_slime name = "Feed Slimes" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "monkey_down" /datum/action/innate/feed_slime/Activate() @@ -250,7 +250,7 @@ /datum/action/innate/monkey_recycle name = "Recycle Monkeys" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "monkey_up" /datum/action/innate/monkey_recycle/Activate() @@ -278,7 +278,7 @@ /datum/action/innate/slime_scan name = "Scan Slime" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "slime_scan" /datum/action/innate/slime_scan/Activate() @@ -295,7 +295,7 @@ /datum/action/innate/feed_potion name = "Apply Potion" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "slime_potion" /datum/action/innate/feed_potion/Activate() @@ -319,7 +319,7 @@ /datum/action/innate/hotkey_help name = "Hotkey Help" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "hotkey_help" /datum/action/innate/hotkey_help/Activate() diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index e99b16615b17..4cea74f11ba4 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -336,7 +336,7 @@ switch(activation_type) if(SLIME_ACTIVATE_MINOR) to_chat(user, span_notice("You activate [src]. You start feeling colder!")) - user.ExtinguishMob() + user.extinguish_mob() user.adjust_fire_stacks(-20) user.reagents.add_reagent(/datum/reagent/consumable/frostoil,4) user.reagents.add_reagent(/datum/reagent/medicine/cryoxadone,5) diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm index 4fecad0a92c2..64acd0c6d9fe 100644 --- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm @@ -316,7 +316,7 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337) . = ..() if(istype(AM, /obj/item/hilbertshotel)) relocate(AM) - var/list/obj/item/hilbertshotel/hotels = AM.GetAllContents(/obj/item/hilbertshotel) + var/list/obj/item/hilbertshotel/hotels = AM.get_all_contents(/obj/item/hilbertshotel) for(var/obj/item/hilbertshotel/H in hotels) if(parentSphere == H) relocate(H) @@ -345,7 +345,7 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337) var/mob/M = AM if(M.mind) var/stillPopulated = FALSE - var/list/currentLivingMobs = GetAllContents(/mob/living) //Got to catch anyone hiding in anything + var/list/currentLivingMobs = get_all_contents(/mob/living) //Got to catch anyone hiding in anything for(var/mob/living/L in currentLivingMobs) //Check to see if theres any sentient mobs left. if(L.mind) stillPopulated = TRUE diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index 32a112d4ccee..6c93fe483785 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -79,7 +79,7 @@ damaged = TRUE if(console) console.say("Alert, hull breach detected!") - var/obj/machinery/announcement_system/announcer = safepick(GLOB.announcement_systems) + var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) if(!QDELETED(announcer)) announcer.announce("ARRIVALS_BROKEN", channels = list()) if(mode != SHUTTLE_CALL) diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm index 8660af3757ea..68dfb9704089 100644 --- a/code/modules/shuttle/assault_pod.dm +++ b/code/modules/shuttle/assault_pod.dm @@ -42,7 +42,7 @@ if(!src || QDELETED(src)) return - var/turf/T = safepick(get_area_turfs(picked_area)) + var/turf/T = pick(get_area_turfs(picked_area)) if(!T) return var/obj/docking_port/stationary/landing_zone = new /obj/docking_port/stationary(T) diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index 26f29fe559f9..a774b4f7466d 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -292,7 +292,7 @@ /datum/action/innate/shuttledocker_rotate name = "Rotate" - 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/innate/shuttledocker_rotate/Activate() @@ -305,7 +305,7 @@ /datum/action/innate/shuttledocker_place name = "Place" - icon_icon = 'icons/mob/actions/actions_mecha.dmi' + button_icon = 'icons/mob/actions/actions_mecha.dmi' button_icon_state = "mech_zoom_off" /datum/action/innate/shuttledocker_place/Activate() diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index db6f40af8e93..589d8ac7533b 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -501,7 +501,7 @@ // Loop over mobs for(var/t in return_turfs()) var/turf/T = t - for(var/mob/living/M in T.GetAllContents()) + for(var/mob/living/M in T.get_all_contents()) // If they have a mind and they're not in the brig, they escaped if(M.mind && !istype(t, /turf/open/floor/plasteel/shuttle/red) && !istype(t, /turf/open/floor/mineral/plastitanium/red/brig)) M.mind.force_escaped = TRUE diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm index 0cf06721aff7..6c130af91c45 100644 --- a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm @@ -1,6 +1,6 @@ //============ Actions ============ /datum/action/innate/shuttle_creator - icon_icon = 'icons/mob/actions/actions_shuttle.dmi' + button_icon = 'icons/mob/actions/actions_shuttle.dmi' var/mob/living/C var/mob/camera/aiEye/remote/shuttle_creation/remote_eye var/obj/item/shuttle_creator/shuttle_creator diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index 8861fa2576e1..65f56f9b655b 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -265,17 +265,17 @@ var/list/counted_money = list() - for(var/obj/item/coin/C in AM.GetAllContents()) + for(var/obj/item/coin/C in AM.get_all_contents()) if(payees[AM] >= threshold) break payees[AM] += C.value counted_money += C - for(var/obj/item/stack/spacecash/S in AM.GetAllContents()) + for(var/obj/item/stack/spacecash/S in AM.get_all_contents()) if(payees[AM] >= threshold) break payees[AM] += S.value * S.amount counted_money += S - for(var/obj/item/holochip/H in AM.GetAllContents()) + for(var/obj/item/holochip/H in AM.get_all_contents()) if(payees[AM] >= threshold) break payees[AM] += H.credits diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 1deb79549711..9db265bd337c 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -60,7 +60,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( var/area/shuttle/shuttle_area = place for(var/trf in shuttle_area) var/turf/T = trf - for(var/a in T.GetAllContents()) + for(var/a in T.get_all_contents()) if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types)) return FALSE return TRUE diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index b48a884a2600..90821ce82ef7 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -1,568 +1,450 @@ -#define TARGET_CLOSEST 1 -#define TARGET_RANDOM 2 - - -/obj/effect/proc_holder - var/panel = "Debug"//What panel the proc holder needs to go on. - var/active = FALSE //Used by toggle based abilities. - var/ranged_mousepointer - var/mob/living/ranged_ability_user - var/ranged_clickcd_override = -1 - var/has_action = TRUE - var/datum/action/spell_action/action = null - var/action_icon = 'icons/mob/actions/actions_spells.dmi' - var/action_icon_state = "spell_default" - var/action_background_icon_state = "bg_spell" - var/base_action = /datum/action/spell_action - -/obj/effect/proc_holder/Initialize() - . = ..() - if(has_action) - action = new base_action(src) +/** + * # The spell action + * + * This is the base action for how many of the game's + * spells (and spell adjacent) abilities function. + * These spells function off of a cooldown-based system. + * + * ## Pre-spell checks: + * - [can_cast_spell][/datum/action/cooldown/spell/can_cast_spell] checks if the OWNER + * of the spell is able to cast the spell. + * - [is_valid_target][/datum/action/cooldown/spell/is_valid_target] checks if the TARGET + * THE SPELL IS BEING CAST ON is a valid target for the spell. NOTE: The CAST TARGET is often THE SAME as THE OWNER OF THE SPELL, + * but is not always - depending on how [Pre Activate][/datum/action/cooldown/spell/PreActivate] is resolved. + * - [try_invoke][/datum/action/cooldown/spell/try_invoke] is run in can_cast_spell to check if + * the OWNER of the spell is able to say the current invocation. + * + * ## The spell chain: + * - [before_cast][/datum/action/cooldown/spell/before_cast] is the last chance for being able + * to interrupt a spell cast. This returns a bitflag. if SPELL_CANCEL_CAST is set, the spell will not continue. + * - [spell_feedback][/datum/action/cooldown/spell/spell_feedback] is called right before cast, and handles + * invocation and sound effects. Overridable, if you want a special method of invocation or sound effects, + * or you want your spell to handle invocation / sound via special means. + * - [cast][/datum/action/cooldown/spell/cast] is where the brunt of the spell effects should be done + * and implemented. + * - [after_cast][/datum/action/cooldown/spell/after_cast] is the aftermath - final effects that follow + * the main cast of the spell. By now, the spell cooldown has already started + * + * ## Other procs called / may be called within the chain: + * - [invocation][/datum/action/cooldown/spell/invocation] handles saying any vocal (or emotive) invocations the spell + * may have, and can be overriden or extended. Called by spell_feedback. + * - [reset_spell_cooldown][/datum/action/cooldown/spell/reset_spell_cooldown] is a way to handle reverting a spell's + * cooldown and making it ready again if it fails to go off at any point. Not called anywhere by default. If you + * want to cancel a spell in before_cast and would like the cooldown restart, call this. + * + * ## Other procs of note: + * - [level_spell][/datum/action/cooldown/spell/level_spell] is where the process of adding a spell level is handled. + * this can be extended if you wish to add unique effects on level up for wizards. + * - [delevel_spell][/datum/action/cooldown/spell/delevel_spell] is where the process of removing a spell level is handled. + * this can be extended if you wish to undo unique effects on level up for wizards. + * - [get_spell_title][/datum/action/cooldown/spell/get_spell_title] returns the prefix of the spell name based on its level, + * for use in updating the button name / spell name. + */ +/datum/action/cooldown/spell + name = "Spell" + desc = "A wizard spell." + background_icon_state = "bg_spell" + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "spell_default" + overlay_icon_state = "bg_spell_border" + active_overlay_icon_state = "bg_spell_border_active_red" + check_flags = AB_CHECK_CONSCIOUS + panel = "Spells" -/obj/effect/proc_holder/proc/on_gain(mob/living/user) - return + /// The sound played on cast. + var/sound = null + /// The school of magic the spell belongs to. + /// Checked by some holy sects to punish the + /// caster for casting things that do not align + /// with their sect's alignment - see magic.dm in defines to learn more + var/school = SCHOOL_UNSET + /// If the spell uses the wizard spell rank system, the cooldown reduction per rank of the spell + var/cooldown_reduction_per_rank = 0 SECONDS + /// What is uttered when the user casts the spell + var/invocation + /// What is shown in chat when the user casts the spell, only matters for INVOCATION_EMOTE + var/invocation_self_message + /// What type of invocation the spell is. + /// Can be "none", "whisper", "shout", "emote" + var/invocation_type = INVOCATION_NONE + /// Flag for certain states that the spell requires the user be in to cast. + var/spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC + /// This determines what type of antimagic is needed to block the spell. + /// (MAGIC_RESISTANCE, MAGIC_RESISTANCE_MIND, MAGIC_RESISTANCE_HOLY) + /// If SPELL_REQUIRES_NO_ANTIMAGIC is set in Spell requirements, + /// The spell cannot be cast if the caster has any of the antimagic flags set. + var/antimagic_flags = MAGIC_RESISTANCE + /// The current spell level, if taken multiple times by a wizard + var/spell_level = 1 + /// The max possible spell level + var/spell_max_level = 5 + /// If set to a positive number, the spell will produce sparks when casted. + var/sparks_amt = 0 + /// The typepath of the smoke to create on cast. + var/smoke_type + /// The amount of smoke to create on cast. This is a range, so a value of 5 will create enough smoke to cover everything within 5 steps. + var/smoke_amt = 0 -/obj/effect/proc_holder/proc/on_lose(mob/living/user) - return +/datum/action/cooldown/spell/Grant(mob/grant_to) + // If our spell is mind-bound, we only wanna grant it to our mind + if(istype(target, /datum/mind)) + var/datum/mind/mind_target = target + if(mind_target.current != grant_to) + return -/obj/effect/proc_holder/proc/fire(mob/living/user) - return TRUE + . = ..() + if(!owner) + return -/obj/effect/proc_holder/proc/get_panel_text() - return "" + // Register some signals so our button's icon stays up to date + if(spell_requirements & SPELL_REQUIRES_STATION) + RegisterSignal(owner, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(update_status_on_signal)) + if(spell_requirements & (SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_WIZARD_GARB)) + RegisterSignal(owner, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(update_status_on_signal)) -GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for the badmin verb for now + RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) + if(owner.client) + owner.client << output(null, "statbrowser:check_spells") -/obj/effect/proc_holder/Destroy() - if (action) - qdel(action) - if(ranged_ability_user) - remove_ranged_ability() - return ..() +/datum/action/cooldown/spell/Remove(mob/living/remove_from) -/obj/effect/proc_holder/singularity_act() - return + if(remove_from.client) + remove_from.client << output(null, "statbrowser:check_spells") + UnregisterSignal(remove_from, list( + COMSIG_MOB_AFTER_EXIT_JAUNT, + COMSIG_MOB_ENTER_JAUNT, + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOVABLE_Z_CHANGED, + )) -/obj/effect/proc_holder/singularity_pull() - return + return ..() -/obj/effect/proc_holder/Topic(href, href_list) - . = ..() - if(href_list["click"]) - // first of all make sure we valid - var/mob/living/as_living = usr - if(!(src in usr.mob_spell_list) && !(usr.mind && (src in usr.mind.spell_list)) && !(istype(as_living) && (src in as_living.abilities))) - message_admins("[key_name_admin(src)] clicked on an invalid proc_holder href! ([src])") - log_game("[key_name(src)] clicked on an invalid proc_holder href! ([src])") - return - Click() - -/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/caller, params, atom/A) - if(caller.ranged_ability != src || ranged_ability_user != caller) //I'm not actually sure how these would trigger, but, uh, safety, I guess? - to_chat(caller, span_warning("[caller.ranged_ability.name] has been disabled.")) - caller.ranged_ability.remove_ranged_ability() - return TRUE //TRUE for failed, FALSE for passed. - if(ranged_clickcd_override >= 0) - ranged_ability_user.next_click = world.time + ranged_clickcd_override - else - ranged_ability_user.next_click = world.time + CLICK_CD_CLICK_ABILITY - ranged_ability_user.face_atom(A) - return FALSE +/datum/action/cooldown/spell/IsAvailable(feedback = FALSE) + return ..() && can_cast_spell(FALSE) -/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, msg, forced) - if(!user || !user.client) - return - if(user.ranged_ability && user.ranged_ability != src) - if(forced) - to_chat(user, span_warning("[user.ranged_ability.name] has been replaced by [name].")) - user.ranged_ability.remove_ranged_ability() - else - return - user.ranged_ability = src - user.click_intercept = src - user.update_mouse_pointer() - ranged_ability_user = user - if(msg) - to_chat(ranged_ability_user, msg) - active = TRUE - update_icon() - -/obj/effect/proc_holder/proc/remove_ranged_ability(msg) - if(!ranged_ability_user || !ranged_ability_user.client || (ranged_ability_user.ranged_ability && ranged_ability_user.ranged_ability != src)) //To avoid removing the wrong ability - return - ranged_ability_user.ranged_ability = null - ranged_ability_user.click_intercept = null - ranged_ability_user.update_mouse_pointer() - if(msg) - to_chat(ranged_ability_user, msg) - ranged_ability_user = null - active = FALSE - update_icon() - -/obj/effect/proc_holder/spell - name = "Spell" - desc = "A wizard spell." - panel = "Spells" - var/sound = null //The sound the spell makes when it is cast - anchored = TRUE // Crap like fireball projectiles are proc_holders, this is needed so fireballs don't get blown back into your face via atmos etc. - pass_flags = PASSTABLE - density = FALSE - opacity = 0 - - var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit? - - var/charge_type = SPELL_CHARGE_TYPE_RECHARGE // See spell defines for a full list of options and what they do - - var/charge_max = 10 SECONDS //recharge time in deciseconds if charge_type = SPELL_CHARGE_TYPE_RECHARGE or starting charges if charge_type = SPELL_CHARGE_TYPE_CHARGES - var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = SPELL_CHARGE_TYPE_RECHARGE or -- each cast if charge_type = SPELL_CHARGE_TYPE_CHARGES - var/still_recharging_msg = span_notice("The spell is still recharging.") - var/recharging = TRUE - - var/holder_var_type = "bruteloss" //only used if charge_type equals to SPELL_CHARGE_TYPE_HOLDERVAR - var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used - - var/clothes_req = TRUE //see if it requires clothes - var/cult_req = FALSE //SPECIAL SNOWFLAKE clothes required for cult only spells - var/human_req = FALSE //spell can only be cast by humans - var/nonabstract_req = FALSE //spell can only be cast by mobs that are physical entities - var/stat_allowed = FALSE //see if it requires being conscious/alive, need to set to 1 for ghostpells - var/phase_allowed = FALSE // If true, the spell can be cast while phased, eg. blood crawling, ethereal jaunting - var/antimagic_allowed = FALSE // If false, the spell cannot be cast while under the effect of antimagic - var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell - var/invocation_emote_self = null - var/invocation_type = SPELL_INVOCATION_NONE // See spell defines for a full list of options and what they do - var/range = 7 //the range of the spell; outer radius for aoe spells - var/message = "" //whatever it says to the guy affected by it - var/selection_type = "view" //can be "range" or "view" - var/spell_level = 0 //if a spell can be taken multiple times, this raises - var/level_max = 4 //The max possible level_max is 4 - var/cooldown_min = 0 //This defines what spell quickened four times has as a cooldown. Make sure to set this for every spell - var/player_lock = TRUE //If it can be used by simple mobs - - var/overlay = 0 - var/overlay_icon = 'icons/obj/wizard.dmi' - var/overlay_icon_state = "spell" - var/overlay_lifespan = 0 - - var/sparks_spread = 0 - var/sparks_amt = 0 //cropped at 10 - /// The typepath of the smoke to create on cast. - var/smoke_spread = null - /// The amount of smoke to create on case. This is a range so a value of 5 will create enough smoke to cover everything within 5 steps. - var/smoke_amt = 0 - - var/centcom_cancast = TRUE //Whether or not the spell should be allowed on z2 +/datum/action/cooldown/spell/Trigger(trigger_flags, atom/target) + // We implement this can_cast_spell check before the parent call of Trigger() + // to allow people to click unavailable abilities to get a feedback chat message + // about why the ability is unavailable. + // It is otherwise redundant, however, as IsAvailable(feedback = FALSE) checks can_cast_spell as well. + if(!can_cast_spell()) + return FALSE - var/atom/movable/screen/cooldown_overlay/cooldown_overlay + return ..() - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "spell_default" - action_background_icon_state = "bg_spell" - base_action = /datum/action/spell_action/spell +/datum/action/cooldown/spell/set_click_ability(mob/on_who) + if(SEND_SIGNAL(on_who, COMSIG_MOB_SPELL_ACTIVATED, src) & SPELL_CANCEL_CAST) + return FALSE -/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0,mob/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell - if(player_lock) - if(!user.mind || !(src in user.mind.spell_list) && !(src in user.mob_spell_list)) - to_chat(user, span_warning("You shouldn't have this spell! Something's wrong.")) - return FALSE - else - if(!(src in user.mob_spell_list)) - return FALSE + return ..() - var/turf/T = get_turf(user) - if(is_centcom_level(T.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel - to_chat(user, span_notice("You can't cast this spell here.")) +// Where the cast chain starts +/datum/action/cooldown/spell/PreActivate(atom/target) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + return FALSE + if(!is_valid_target(target)) return FALSE - if(!skipcharge) - if(!charge_check(user)) - return FALSE + return Activate(target) - if(user.stat && !stat_allowed) - to_chat(user, span_notice("Not when you're incapacitated.")) +/// Checks if the owner of the spell can currently cast it. +/// Does not check anything involving potential targets. +/datum/action/cooldown/spell/proc/can_cast_spell(feedback = TRUE) + if(!owner) + CRASH("[type] - can_cast_spell called on a spell without an owner!") + + // Certain spells are not allowed on the centcom zlevel + var/turf/caster_turf = get_turf(owner) + // Spells which require being on the station + if((spell_requirements & SPELL_REQUIRES_STATION) && !is_station_level(caster_turf.z)) + if(feedback) + to_chat(owner, span_warning("You can't cast [src] here!")) return FALSE - if(!antimagic_allowed) - var/antimagic = user.anti_magic_check(TRUE, FALSE, FALSE, 0, TRUE) - if(antimagic) - if(isitem(antimagic)) - to_chat(user, span_notice("[antimagic] is interfering with your magic.")) - else - to_chat(user, span_notice("Magic seems to flee from you, you can't gather enough power to cast this spell.")) - return FALSE + if((spell_requirements & SPELL_REQUIRES_MIND) && !owner.mind) + // No point in feedback here, as mindless mobs aren't players + return FALSE - if(!phase_allowed && istype(user.loc, /obj/effect/dummy)) - to_chat(user, span_notice("[name] cannot be cast unless you are completely manifested in the material plane.")) + if((spell_requirements & SPELL_REQUIRES_MIME_VOW) && !owner.mind?.miming) + // In the future this can be moved out of spell checks exactly + if(feedback) + to_chat(owner, span_warning("You must dedicate yourself to silence first!")) return FALSE - if(ishuman(user)) + // If the spell requires the user has no antimagic equipped, and they're holding antimagic + // that corresponds with the spell's antimagic, then they can't actually cast the spell + if((spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC) && !owner.can_cast_magic(antimagic_flags)) + if(feedback) + to_chat(owner, span_warning("Some form of antimagic is preventing you from casting [src]!")) + return FALSE - var/mob/living/carbon/human/H = user + if(!(spell_requirements & SPELL_CASTABLE_WHILE_PHASED) && HAS_TRAIT(owner, TRAIT_MAGICALLY_PHASED)) + if(feedback) + to_chat(owner, span_warning("[src] cannot be cast unless you are completely manifested in the material plane!")) + return FALSE - if((invocation_type == SPELL_INVOCATION_SAY || invocation_type == SPELL_INVOCATION_WHISPER) && !H.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return FALSE + if(!try_invoke(feedback = feedback)) + return FALSE - var/list/casting_clothes = typecacheof(list(/obj/item/clothing/suit/wizrobe, + var/list/casting_clothes = typecacheof(list( //HELLO. CHANGE THIS LATER.... + /obj/item/clothing/suit/wizrobe, /obj/item/clothing/suit/space/hardsuit/wizard, /obj/item/clothing/head/wizard, /obj/item/clothing/head/wizard/armor, /obj/item/clothing/suit/wizrobe/armor, /obj/item/clothing/head/helmet/space/hardsuit/wizard, - /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard)) - - if(clothes_req) //clothes check - if(!is_type_in_typecache(H.wear_suit, casting_clothes)) - to_chat(H, span_notice("I don't feel strong enough without my robe.")) - return FALSE - if(!is_type_in_typecache(H.head, casting_clothes)) - to_chat(H, span_notice("I don't feel strong enough without my hat.")) - return FALSE - if(cult_req) //CULT_REQ CLOTHES CHECK - if(!istype(H.wear_suit, /obj/item/clothing/suit/magusred) && !istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit/cult)) - to_chat(H, span_notice("I don't feel strong enough without my armor.")) + /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + )) + + if(ishuman(owner)) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + var/mob/living/carbon/human/human_owner = owner + if(!is_type_in_typecache(human_owner.wear_suit, casting_clothes)) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your robe!")) return FALSE - if(!istype(H.head, /obj/item/clothing/head/magus) && !istype(H.head, /obj/item/clothing/head/helmet/space/hardsuit/cult)) - to_chat(H, span_notice("I don't feel strong enough without my helmet.")) + if(!is_type_in_typecache(human_owner.head, casting_clothes)) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your hat!")) return FALSE + else - if(clothes_req || human_req) - to_chat(user, span_notice("This spell can only be cast by humans!")) + // If the spell requires wizard equipment and we're not a human (can't wear robes or hats), that's just a given + if(spell_requirements & (SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_HUMAN)) + if(feedback) + to_chat(owner, span_warning("[src] can only be cast by humans!")) return FALSE - if(nonabstract_req && (isbrain(user) || ispAI(user))) - to_chat(user, span_notice("This spell can only be cast by physical beings!")) + + if(!(spell_requirements & SPELL_CASTABLE_AS_BRAIN) && isbrain(owner)) + if(feedback) + to_chat(owner, span_warning("[src] can't be cast in this state!")) return FALSE + // Being put into a card form breaks a lot of spells, so we'll just forbid them in these states + if(ispAI(owner) || (isAI(owner) && istype(owner.loc, /obj/item/aicard))) + return FALSE - if(!skipcharge) - switch(charge_type) - if(SPELL_CHARGE_TYPE_RECHARGE) - charge_counter = 0 //doesn't start recharging until the targets selecting ends - if(SPELL_CHARGE_TYPE_CHARGES) - charge_counter-- //returns the charge if the targets selecting fails - if(SPELL_CHARGE_TYPE_HOLDERVAR) - adjust_var(user, holder_var_type, holder_var_amount) - if(action) - action.UpdateButtonIcon() return TRUE -/obj/effect/proc_holder/spell/proc/charge_check(mob/user, silent = FALSE) - switch(charge_type) - if(SPELL_CHARGE_TYPE_RECHARGE) - if(charge_counter < charge_max) - if(!silent) - to_chat(user, still_recharging_msg) - return FALSE - if(SPELL_CHARGE_TYPE_CHARGES) - if(!charge_counter) - if(!silent) - to_chat(user, span_notice("[name] has no charges left.")) - return FALSE +/** + * Check if the target we're casting on is a valid target. + * For self-casted spells, the target being checked (cast_on) is the caster. + * For click_to_activate spells, the target being checked is the clicked atom. + * + * Return TRUE if cast_on is valid, FALSE otherwise + */ +/datum/action/cooldown/spell/proc/is_valid_target(atom/cast_on) return TRUE -/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount - switch(invocation_type) - if(SPELL_INVOCATION_SAY) - if(prob(50))//Auto-mute? Fuck that noise - user.say(invocation, forced = "spell") - else - user.say(replacetext(invocation," ","`"), forced = "spell") - if(SPELL_INVOCATION_WHISPER) - if(prob(50)) - user.whisper(invocation) - else - user.whisper(replacetext(invocation," ","`")) - if(SPELL_INVOCATION_EMOTE) - user.emote(invocation) - if(SPELL_INVOCATION_MESSAGE) - user.visible_message(invocation, invocation_emote_self) //same style as in mob/living/emote.dm +// The actual cast chain occurs here, in Activate(). +// You should generally not be overriding or extending Activate() for spells. +// Defer to any of the cast chain procs instead. +/datum/action/cooldown/spell/Activate(atom/cast_on) + SHOULD_NOT_OVERRIDE(TRUE) + + // Pre-casting of the spell + // Pre-cast is the very last chance for a spell to cancel + // Stuff like target input can go here. + var/precast_result = before_cast(cast_on) + if(precast_result & SPELL_CANCEL_CAST) + return FALSE -/obj/effect/proc_holder/spell/proc/playMagSound() - playsound(get_turf(usr), sound,50,1) + // Spell is officially being cast + if(!(precast_result & SPELL_NO_FEEDBACK)) + // We do invocation and sound effects here, before actual cast + // That way stuff like teleports or shape-shifts can be invoked before ocurring + spell_feedback() -/obj/effect/proc_holder/spell/Initialize() - . = ..() - START_PROCESSING(SSfastprocess, src) + // Actually cast the spell. Main effects go here + cast(cast_on) - still_recharging_msg = span_notice("[name] is still recharging.") - charge_counter = charge_max + if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) + // The entire spell is done, start the actual cooldown at its set duration + StartCooldown() -/obj/effect/proc_holder/spell/Destroy() - STOP_PROCESSING(SSfastprocess, src) - qdel(action) - return ..() + // And then proceed with the aftermath of the cast + // Final effects that happen after all the casting is done can go here + after_cast(cast_on) + build_all_button_icons() + + return TRUE -/obj/effect/proc_holder/spell/Click() - if(cast_check()) - choose_targets() - return 1 +/** + * Actions done before the actual cast is called. + * This is the last chance to cancel the spell from being cast. + * + * Can be used for target selection or to validate checks on the caster (cast_on). + * + * Returns a bitflag. + * - SPELL_CANCEL_CAST will stop the spell from being cast. + * - SPELL_NO_FEEDBACK will prevent the spell from calling [proc/spell_feedback] on cast. (invocation), sounds) + * - SPELL_NO_IMMEDIATE_COOLDOWN will prevent the spell from starting its cooldown between cast and before after_cast. + */ +/datum/action/cooldown/spell/proc/before_cast(atom/cast_on) + SHOULD_CALL_PARENT(TRUE) + + var/sig_return = SEND_SIGNAL(src, COMSIG_SPELL_BEFORE_CAST, cast_on) + if(owner) + sig_return |= SEND_SIGNAL(owner, COMSIG_MOB_BEFORE_SPELL_CAST, src, cast_on) + + return sig_return + +/** + * Actions done as the main effect of the spell. + * + * For spells without a click intercept, [cast_on] will be the owner. + * For click spells, [cast_on] is whatever the owner clicked on in casting the spell. + */ +/datum/action/cooldown/spell/proc/cast(atom/cast_on) + SHOULD_CALL_PARENT(TRUE) + + SEND_SIGNAL(src, COMSIG_SPELL_CAST, cast_on) + if(owner) + SEND_SIGNAL(owner, COMSIG_MOB_CAST_SPELL, src, cast_on) + if(owner.ckey) + owner.log_message("cast the spell [name][cast_on != owner ? " on / at [cast_on]":""].", LOG_ATTACK) + +/** + * Actions done after the main cast is finished. + * This is called after the cooldown's already begun. + * + * It can be used to apply late spell effects where order matters + * (for example, causing smoke *after* a teleport occurs in cast()) + * or to clean up variables or references post-cast. + */ +/datum/action/cooldown/spell/proc/after_cast(atom/cast_on) + SHOULD_CALL_PARENT(TRUE) + if(!owner) // Could have been destroyed by the effect of the spell + SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on) + return -/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf - return + if(sparks_amt) + do_sparks(sparks_amt, FALSE, get_turf(owner)) + if(ispath(smoke_type, /datum/effect_system/fluid_spread/smoke)) + var/datum/effect_system/fluid_spread/smoke/smoke = new smoke_type() + smoke.set_up(smoke_amt, holder = owner, location = get_turf(owner)) + smoke.start() -/obj/effect/proc_holder/spell/proc/can_target(mob/living/target) - return TRUE + // Send signals last in case they delete the spell + SEND_SIGNAL(owner, COMSIG_MOB_AFTER_SPELL_CAST, src, cast_on) + SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on) -/obj/effect/proc_holder/spell/proc/start_recharge() - if(cooldown_overlay) - QDEL_NULL(cooldown_overlay) - cooldown_overlay = start_cooldown(action.button, world.time + charge_max) - recharging = TRUE - -/obj/effect/proc_holder/spell/process(delta_time) - if(recharging && charge_type == SPELL_CHARGE_TYPE_RECHARGE && (charge_counter < charge_max)) - charge_counter += delta_time * 10 - cooldown_overlay?.tick() - if(charge_counter >= charge_max) - action.UpdateButtonIcon() - charge_counter = charge_max - recharging = FALSE - QDEL_NULL(cooldown_overlay) - -/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = TRUE, mob/user = usr) //if recharge is started is important for the trigger spells - before_cast(targets) - invocation(user) - if(user && user.ckey) - user.log_message(span_danger("cast the spell [name]."), LOG_ATTACK) - if(recharge) - start_recharge() - if(sound) - playMagSound() - cast(targets,user=user) - after_cast(targets) - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/before_cast(list/targets) - if(overlay) - for(var/atom/target in targets) - var/location - if(isliving(target)) - location = target.loc - else if(isturf(target)) - location = target - var/obj/effect/overlay/spell = new /obj/effect/overlay(location) - spell.icon = overlay_icon - spell.icon_state = overlay_icon_state - spell.anchored = TRUE - spell.density = FALSE - QDEL_IN(spell, overlay_lifespan) - -/obj/effect/proc_holder/spell/proc/after_cast(list/targets) - for(var/atom/target in targets) - var/location - if(isliving(target)) - location = target.loc - else if(isturf(target)) - location = target - if(isliving(target) && message) - to_chat(target, text("[message]")) - if(sparks_spread) - do_sparks(sparks_amt, FALSE, location) - if(ispath(smoke_spread, /datum/effect_system/fluid_spread/smoke)) // Dear god this code is :agony: - var/datum/effect_system/fluid_spread/smoke/smoke = new smoke_spread() - smoke.set_up(smoke_amt, location = location) - smoke.start() - - -/obj/effect/proc_holder/spell/proc/cast(list/targets,mob/user = usr) - return - -/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge - switch(charge_type) - if(SPELL_CHARGE_TYPE_RECHARGE) - charge_counter = charge_max - if(SPELL_CHARGE_TYPE_CHARGES) - charge_counter++ - if(SPELL_CHARGE_TYPE_HOLDERVAR) - adjust_var(user, holder_var_type, -holder_var_amount) - QDEL_NULL(cooldown_overlay) - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types - if (!istype(target)) +/// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation +/datum/action/cooldown/spell/proc/spell_feedback() + if(!owner) return - switch(type) - if("bruteloss") - target.adjustBruteLoss(amount) - if("fireloss") - target.adjustFireLoss(amount) - if("toxloss") - target.adjustToxLoss(amount) - if("oxyloss") - target.adjustOxyLoss(amount) - if("stun") - target.AdjustStun(amount) - if("knockdown") - target.AdjustKnockdown(amount) - if("paralyze") - target.AdjustParalyzed(amount) - if("immobilize") - target.AdjustImmobilized(amount) - if("unconscious") - target.AdjustUnconscious(amount) - else - target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existent vars - -/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob - var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range - var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast - var/include_user = 0 //if it includes usr in the target list - var/random_target = 0 // chooses random viable target instead of asking the caster - var/random_target_priority = TARGET_CLOSEST // if random_target is enabled how it will pick the target - - -/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends) - var/inner_radius = -1 //for all your ring spell needs - -/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr) - var/list/targets = list() - - switch(max_targets) - if(0) //unlimited - for(var/mob/living/target in view_or_range(range, user, selection_type)) - if(!can_target(target)) - continue - targets += target - if(1) //single target can be picked - if(range < 0) - targets += user + + if(invocation_type != INVOCATION_NONE) + invocation() + if(sound) + playsound(get_turf(owner), sound, 50, TRUE) + +/// The invocation that accompanies the spell, called from spell_feedback() before cast(). +/datum/action/cooldown/spell/proc/invocation() + switch(invocation_type) + if(INVOCATION_SHOUT) + if(prob(50)) + owner.say(invocation, forced = "spell ([src])") else - var/possible_targets = list() - - for(var/mob/living/M in view_or_range(range, user, selection_type)) - if(!include_user && user == M) - continue - if(!can_target(M)) - continue - possible_targets += M - - //targets += input("Choose the target for the spell.", "Targeting") as mob in possible_targets - //Adds a safety check post-input to make sure those targets are actually in range. - var/mob/M - if(!random_target) - M = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets - else - switch(random_target_priority) - if(TARGET_RANDOM) - M = pick(possible_targets) - if(TARGET_CLOSEST) - for(var/mob/living/L in possible_targets) - if(M) - if(get_dist(user,L) < get_dist(user,M)) - if(los_check(user,L)) - M = L - else - if(los_check(user,L)) - M = L - if(M in view_or_range(range, user, selection_type)) - targets += M - - else - var/list/possible_targets = list() - for(var/mob/living/target in view_or_range(range, user, selection_type)) - if(!can_target(target)) - continue - possible_targets += target - for(var/i=1,i<=max_targets,i++) - if(!possible_targets.len) - break - if(target_ignore_prev) - var/target = pick(possible_targets) - possible_targets -= target - targets += target - else - targets += pick(possible_targets) - - if(!include_user && (user in targets)) - targets -= user - - if(!targets.len) //doesn't waste the spell - revert_cast(user) - return + owner.say(replacetext(invocation," ","`"), forced = "spell ([src])") - perform(targets,user=user) + if(INVOCATION_WHISPER) + if(prob(50)) + owner.whisper(invocation, forced = "spell ([src])") + else + owner.whisper(replacetext(invocation," ","`"), forced = "spell ([src])") -/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr) - var/list/targets = list() + if(INVOCATION_EMOTE) + owner.visible_message(invocation, invocation_self_message) - for(var/turf/target in view_or_range(range,user,selection_type)) - if(!can_target(target)) - continue - if(!(target in view_or_range(inner_radius,user,selection_type))) - targets += target +/// Checks if the current OWNER of the spell is in a valid state to say the spell's invocation +/datum/action/cooldown/spell/proc/try_invoke(feedback = TRUE) + if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION) + return TRUE - if(!targets.len) //doesn't waste the spell - revert_cast() - return + if(invocation_type == INVOCATION_NONE) + return TRUE - perform(targets,user=user) - -/obj/effect/proc_holder/spell/proc/updateButtonIcon(status_only, force) - action.UpdateButtonIcon(status_only, force) - -/obj/effect/proc_holder/spell/proc/can_be_cast_by(mob/caster) - if((human_req || clothes_req) && !ishuman(caster)) - return 0 - return 1 - -/obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B) - //Checks for obstacles from A to B - var/obj/dummy = new(A.loc) - dummy.pass_flags |= PASSTABLE - for(var/turf/turf in getline(A,B)) - for(var/atom/movable/AM in turf) - if(!AM.CanPass(dummy,turf,1)) - qdel(dummy) - return 0 - qdel(dummy) - return 1 - -/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr) - if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) + // If you want a spell usable by ghosts for some reason, it must be INVOCATION_NONE + if(!isliving(owner)) + if(feedback) + to_chat(owner, span_warning("You need to be living to invoke [src]!")) return FALSE - if(!charge_check(user,TRUE)) + var/mob/living/living_owner = owner + if(invocation_type == INVOCATION_EMOTE && HAS_TRAIT(living_owner, TRAIT_EMOTEMUTE)) + if(feedback) + to_chat(owner, span_warning("You can't position your hands correctly to invoke [src]!")) return FALSE - if(user.stat && !stat_allowed) + if((invocation_type == INVOCATION_WHISPER || invocation_type == INVOCATION_SHOUT) && !living_owner.can_speak()) + if(feedback) + to_chat(owner, span_warning("You can't get the words out to invoke [src]!")) return FALSE - if(!antimagic_allowed && user.anti_magic_check(TRUE, FALSE, FALSE, 0, TRUE)) + return TRUE + +/// Resets the cooldown of the spell, sending COMSIG_SPELL_CAST_RESET +/// and allowing it to be used immediately (+ updating button icon accordingly) +/datum/action/cooldown/spell/proc/reset_spell_cooldown() + SEND_SIGNAL(src, COMSIG_SPELL_CAST_RESET) + next_use_time -= cooldown_time // Basically, ensures that the ability can be used now + build_all_button_icons() + +/** + * Levels the spell up a single level, reducing the cooldown. + * If bypass_cap is TRUE, will level the spell up past it's set cap. + */ +/datum/action/cooldown/spell/proc/level_spell(bypass_cap = FALSE) + // Spell cannot be levelled + if(spell_max_level <= 1) return FALSE - if(!ishuman(user)) - if(clothes_req || human_req) - return FALSE - if(nonabstract_req && (isbrain(user) || ispAI(user))) - return FALSE + // Spell is at cap, and we will not bypass it + if(!bypass_cap && (spell_level >= spell_max_level)) + return FALSE + + spell_level++ + cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0.25 SECONDS) // 0 second CD starts to break things. + build_all_button_icons(UPDATE_BUTTON_NAME) + return TRUE + +/** + * Levels the spell down a single level, down to 1. + */ +/datum/action/cooldown/spell/proc/delevel_spell() + // Spell cannot be levelled + if(spell_max_level <= 1) + return FALSE + + if(spell_level <= 1) + return FALSE + + spell_level-- + if(cooldown_reduction_per_rank > 0 SECONDS) + cooldown_time = min(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time)) + else + cooldown_time = max(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time)) + + build_all_button_icons(UPDATE_BUTTON_NAME) return TRUE -/obj/effect/proc_holder/spell/self //Targets only the caster. Good for buffs and heals, but probably not wise for fireballs (although they usually fireball themselves anyway, honke) - range = -1 //Duh +/datum/action/cooldown/spell/update_button_name(atom/movable/screen/movable/action_button/button, force) + name = "[get_spell_title()][initial(name)]" + return ..() -/obj/effect/proc_holder/spell/self/choose_targets(mob/user = usr) - if(!user) - revert_cast() - return - perform(null,user=user) - -/obj/effect/proc_holder/spell/self/basic_heal //This spell exists mainly for debugging purposes, and also to show how casting works - name = "Lesser Heal" - desc = "Heals a small amount of brute and burn damage." - human_req = TRUE - clothes_req = FALSE - charge_max = 100 - cooldown_min = 50 - invocation = "Victus sano!" - invocation_type = SPELL_INVOCATION_WHISPER - school = "restoration" - sound = 'sound/magic/staff_healing.ogg' - -/obj/effect/proc_holder/spell/self/basic_heal/cast(mob/living/carbon/human/user) //Note the lack of "list/targets" here. Instead, use a "user" var depending on mob requirements. - //Also, notice the lack of a "for()" statement that looks through the targets. This is, again, because the spell can only have a single target. - user.visible_message(span_warning("A wreath of gentle light passes over [user]!"), span_notice("You wreath yourself in healing light!")) - user.adjustBruteLoss(-10) - user.adjustFireLoss(-10) +/// Gets the title of the spell based on its level. +/datum/action/cooldown/spell/proc/get_spell_title() + switch(spell_level) + if(2) + return "Efficient " + if(3) + return "Quickened " + if(4) + return "Free " + if(5) + return "Instant " + if(6) + return "Ludicrous " + + return "" diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm deleted file mode 100644 index 66993c31a22e..000000000000 --- a/code/modules/spells/spell_types/aimed.dm +++ /dev/null @@ -1,181 +0,0 @@ - -/obj/effect/proc_holder/spell/aimed - name = "aimed projectile spell" - var/projectile_type = /obj/item/projectile/magic/teleport - var/deactive_msg = "You discharge your projectile..." - var/active_msg = "You charge your projectile!" - var/base_icon_state = "projectile" - var/active_icon_state = "projectile" - var/list/projectile_var_overrides = list() - var/projectile_amount = 1 //Projectiles per cast. - var/current_amount = 0 //How many projectiles left. - var/projectiles_per_fire = 1 //Projectiles per fire. Probably not a good thing to use unless you override ready_projectile(). - -/obj/effect/proc_holder/spell/aimed/Click() - var/mob/living/user = usr - if(!istype(user)) - return - var/msg - if(!can_cast(user)) - msg = span_warning("You can no longer cast [name]!") - remove_ranged_ability(msg) - return - if(active) - msg = span_notice("[deactive_msg]") - if(charge_type == SPELL_CHARGE_TYPE_RECHARGE) - var/refund_percent = current_amount/projectile_amount - charge_counter = charge_max * refund_percent - start_recharge() - remove_ranged_ability(msg) - on_deactivation(user) - else - msg = span_notice("[active_msg] Left-click to shoot it at a target!") - current_amount = projectile_amount - add_ranged_ability(user, msg, TRUE) - on_activation(user) - -/obj/effect/proc_holder/spell/aimed/proc/on_activation(mob/user) - return - -/obj/effect/proc_holder/spell/aimed/proc/on_deactivation(mob/user) - return - -/obj/effect/proc_holder/spell/aimed/update_icon() - if(!action) - return - action.button_icon_state = "[base_icon_state][active]" - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/aimed/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return FALSE - var/ran_out = (current_amount <= 0) - if(!cast_check(!ran_out, ranged_ability_user)) - remove_ranged_ability() - return FALSE - var/list/targets = list(target) - perform(targets, ran_out, user = ranged_ability_user) - return TRUE - -/obj/effect/proc_holder/spell/aimed/cast(list/targets, mob/living/user) - var/target = targets[1] - var/turf/T = user.loc - var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction - if(!isturf(U) || !isturf(T)) - return FALSE - fire_projectile(user, target) - user.newtonian_move(get_dir(U, T)) - if(current_amount <= 0) - remove_ranged_ability() //Auto-disable the ability once you run out of bullets. - charge_counter = 0 - start_recharge() - on_deactivation(user) - return TRUE - -/obj/effect/proc_holder/spell/aimed/proc/fire_projectile(mob/living/user, atom/target) - current_amount-- - for(var/i in 1 to projectiles_per_fire) - var/obj/item/projectile/P = new projectile_type(user.loc) - P.firer = user - P.preparePixelProjectile(target, user) - for(var/V in projectile_var_overrides) - if(P.vars[V]) - P.vv_edit_var(V, projectile_var_overrides[V]) - ready_projectile(P, target, user, i) - P.fire() - return TRUE - -/obj/effect/proc_holder/spell/aimed/proc/ready_projectile(obj/item/projectile/P, atom/target, mob/user, iteration) - return - -/obj/effect/proc_holder/spell/aimed/lightningbolt - name = "Lightning Bolt" - desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down. Insulated gloves can protect people from the blast." - school = "evocation" - charge_max = 100 - clothes_req = FALSE - invocation = "UN'LTD P'WAH" - invocation_type = SPELL_INVOCATION_SAY - cooldown_min = 30 - active_icon_state = "lightning" - base_icon_state = "lightning" - sound = 'sound/magic/lightningbolt.ogg' - active = FALSE - projectile_var_overrides = list("tesla_range" = 15, "tesla_power" = 20000, "tesla_flags" = TESLA_MOB_DAMAGE) - active_msg = "You energize your hand with arcane lightning!" - deactive_msg = "You let the energy flow out of your hands back into yourself..." - projectile_type = /obj/item/projectile/magic/aoe/lightning - -/obj/effect/proc_holder/spell/aimed/fireball - name = "Fireball" - desc = "This spell fires an explosive fireball at a target." - school = "evocation" - charge_max = 60 - clothes_req = FALSE - invocation = "ONI SOMA" - invocation_type = SPELL_INVOCATION_SAY - range = 20 - cooldown_min = 20 //10 deciseconds reduction per rank - projectile_type = /obj/item/projectile/magic/aoe/fireball - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - base_icon_state = "fireball" - action_icon_state = "fireball0" - sound = 'sound/magic/fireball.ogg' - active_msg = "You prepare to cast your fireball spell!" - deactive_msg = "You extinguish your fireball... for now." - active = FALSE - -/obj/effect/proc_holder/spell/aimed/spell_cards - name = "Spell Cards" - desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" - school = "evocation" - charge_max = 50 - clothes_req = FALSE - invocation = "Sigi'lu M'Fan 'Tasia" - invocation_type = SPELL_INVOCATION_SAY - range = 40 - cooldown_min = 10 - projectile_amount = 5 - projectiles_per_fire = 7 - projectile_type = /obj/item/projectile/spellcard - var/datum/weakref/current_target_weakref - var/projectile_turnrate = 10 - var/projectile_pixel_homing_spread = 32 - var/projectile_initial_spread_amount = 30 - var/projectile_location_spread_amount = 12 - var/datum/component/lockon_aiming/lockon_component - ranged_clickcd_override = TRUE - -/obj/effect/proc_holder/spell/aimed/spell_cards/on_activation(mob/M) - QDEL_NULL(lockon_component) - lockon_component = M.AddComponent(/datum/component/lockon_aiming, 5, typecacheof(list(/mob/living)), 1, null, CALLBACK(src, PROC_REF(on_lockon_component))) - -/obj/effect/proc_holder/spell/aimed/spell_cards/proc/on_lockon_component(list/locked_weakrefs) - if(!length(locked_weakrefs)) - current_target_weakref = null - return - current_target_weakref = locked_weakrefs[1] - var/atom/A = current_target_weakref.resolve() - if(A) - var/mob/M = lockon_component.parent - M.face_atom(A) - -/obj/effect/proc_holder/spell/aimed/spell_cards/on_deactivation(mob/M) - QDEL_NULL(lockon_component) - -/obj/effect/proc_holder/spell/aimed/spell_cards/ready_projectile(obj/item/projectile/P, atom/target, mob/user, iteration) - if(current_target_weakref) - var/atom/A = current_target_weakref.resolve() - if(A && get_dist(A, user) < 7) - P.homing_turn_speed = projectile_turnrate - P.homing_inaccuracy_min = projectile_pixel_homing_spread - P.homing_inaccuracy_max = projectile_pixel_homing_spread - P.set_homing_target(current_target_weakref.resolve()) - var/rand_spr = rand() - var/total_angle = projectile_initial_spread_amount * 2 - var/adjusted_angle = total_angle - ((projectile_initial_spread_amount / projectiles_per_fire) * 0.5) - var/one_fire_angle = adjusted_angle / projectiles_per_fire - var/current_angle = iteration * one_fire_angle * rand_spr - (projectile_initial_spread_amount / 2) - P.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) - P.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) - P.preparePixelProjectile(target, user, null, current_angle) diff --git a/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm new file mode 100644 index 000000000000..49626cafc1f7 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm @@ -0,0 +1,57 @@ +/** + * ## AOE spells + * + * A spell that iterates over atoms near the caster and casts a spell on them. + * Calls cast_on_thing_in_aoe on all atoms returned by get_things_to_cast_on by default. + */ +/datum/action/cooldown/spell/aoe + /// The max amount of targets we can affect via our AOE. 0 = unlimited + var/max_targets = 0 + /// The radius of the aoe. + var/aoe_radius = 7 + +// At this point, cast_on == owner. Either works. +/datum/action/cooldown/spell/aoe/cast(atom/cast_on) + . = ..() + // Get every atom around us to our aoe cast on + var/list/atom/things_to_cast_on = get_things_to_cast_on(cast_on) + // If we have a target limit, shuffle it (for fariness) + if(max_targets > 0) + things_to_cast_on = shuffle(things_to_cast_on) + + SEND_SIGNAL(src, COMSIG_SPELL_AOE_ON_CAST, things_to_cast_on, cast_on) + + // Now go through and cast our spell where applicable + var/num_targets = 0 + for(var/thing_to_target in things_to_cast_on) + if(max_targets > 0 && num_targets >= max_targets) + continue + + cast_on_thing_in_aoe(thing_to_target, cast_on) + num_targets++ + +/** + * Gets a list of atoms around [center] + * that are within range and affected by our aoe. + */ +/datum/action/cooldown/spell/aoe/proc/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/atom/nearby_thing in range(aoe_radius, center)) + if(nearby_thing == owner || nearby_thing == center) + continue + + things += nearby_thing + + return things + +/** + * Actually cause effects on the thing in our aoe. + * Override this for your spell! Not cast(). + * + * Arguments + * * victim - the atom being affected by our aoe + * * caster - the mob who cast the aoe + */ +/datum/action/cooldown/spell/aoe/proc/cast_on_thing_in_aoe(atom/victim, atom/caster) + SHOULD_CALL_PARENT(FALSE) + CRASH("[type] did not implement cast_on_thing_in_aoe and either has no effects or implemented the spell incorrectly.") \ No newline at end of file diff --git a/code/modules/spells/spell_types/aoe_spell/area_conversion.dm b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm new file mode 100644 index 000000000000..f75c39586852 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm @@ -0,0 +1,27 @@ +/datum/action/cooldown/spell/aoe/area_conversion + name = "Area Conversion" + desc = "This spell instantly converts a small area around you." + background_icon_state = "bg_cult" + overlay_icon_state = "bg_cult_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "areaconvert" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 5 SECONDS + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + aoe_radius = 2 + +/datum/action/cooldown/spell/aoe/area_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/area_conversion/cast_on_thing_in_aoe(turf/victim, atom/caster) + playsound(victim, 'sound/items/welder.ogg', 75, TRUE) + victim.narsie_act(FALSE, TRUE, 100 - (get_dist(victim, caster) * 25)) diff --git a/code/modules/spells/spell_types/aoe_spell/knock.dm b/code/modules/spells/spell_types/aoe_spell/knock.dm new file mode 100644 index 000000000000..ca90056dc489 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/knock.dm @@ -0,0 +1,20 @@ +/datum/action/cooldown/spell/aoe/knock + name = "Knock" + desc = "This spell opens nearby doors and closets." + button_icon_state = "knock" + + sound = 'sound/magic/knock.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 2 SECONDS + + invocation = "AULIE OXIN FIERA" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + aoe_radius = 3 + +/datum/action/cooldown/spell/aoe/knock/get_things_to_cast_on(atom/center) + return RANGE_TURFS(aoe_radius, center) + +/datum/action/cooldown/spell/aoe/knock/cast_on_thing_in_aoe(turf/victim, atom/caster) + SEND_SIGNAL(victim, COMSIG_ATOM_MAGICALLY_UNLOCKED, src, caster) \ No newline at end of file diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm new file mode 100644 index 000000000000..f5fe2cf3ee07 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -0,0 +1,48 @@ +/datum/action/cooldown/spell/aoe/magic_missile + name = "Magic Missile" + desc = "This spell fires several, slow moving, magic projectiles at nearby targets." + button_icon_state = "magicm" + sound = 'sound/magic/magic_missile.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 20 SECONDS + cooldown_reduction_per_rank = 3.5 SECONDS + + invocation = "FORTI GY AMA" + invocation_type = INVOCATION_SHOUT + + aoe_radius = 7 + + /// The projectile type fired at all people around us + var/obj/item/projectile/projectile_type = /obj/item/projectile/magic/aoe/magic_missile + +/datum/action/cooldown/spell/aoe/magic_missile/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/nearby_mob in view(aoe_radius, center)) + if(nearby_mob == owner || nearby_mob == center) + continue + + things += nearby_mob + + return things + +/datum/action/cooldown/spell/aoe/magic_missile/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + fire_projectile(victim, caster) + +/datum/action/cooldown/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster) + var/obj/item/projectile/to_fire = new projectile_type() + to_fire.preparePixelProjectile(victim, caster) + to_fire.fire() + +/datum/action/cooldown/spell/aoe/magic_missile/lesser + name = "Lesser Magic Missile" + desc = "This spell fires several, slow moving, magic projectiles at nearby targets." + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + cooldown_time = 40 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + max_targets = 6 + projectile_type = /obj/item/projectile/magic/aoe/magic_missile/lesser diff --git a/code/modules/spells/spell_types/aoe_spell/repulse.dm b/code/modules/spells/spell_types/aoe_spell/repulse.dm new file mode 100644 index 000000000000..2e20e7249881 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/repulse.dm @@ -0,0 +1,88 @@ +/datum/action/cooldown/spell/aoe/repulse + /// The max throw range of the repulsioon. + var/max_throw = 5 + /// A visual effect to be spawned on people who are thrown away. + var/obj/effect/sparkle_path = /obj/effect/temp_visual/gravpush + /// The moveforce of the throw done by the repulsion. + var/repulse_force = MOVE_FORCE_EXTREMELY_STRONG + +/datum/action/cooldown/spell/aoe/repulse/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/atom/movable/nearby_movable in view(aoe_radius, center)) + if(nearby_movable == owner || nearby_movable == center) + continue + if(nearby_movable.anchored) + continue + + things += nearby_movable + + return things + +/datum/action/cooldown/spell/aoe/repulse/cast_on_thing_in_aoe(atom/movable/victim, atom/caster) + if(ismob(victim)) + var/mob/victim_mob = victim + if(victim_mob.can_block_magic(antimagic_flags)) + return + + var/turf/throwtarget = get_edge_target_turf(caster, get_dir(caster, get_step_away(victim, caster))) + var/dist_from_caster = get_dist(victim, caster) + + if(dist_from_caster == 0) + if(isliving(victim)) + var/mob/living/victim_living = victim + victim_living.Paralyze(10 SECONDS) + victim_living.adjustBruteLoss(5) + to_chat(victim, span_userdanger("You're slammed into the floor by [caster]!")) + else + if(sparkle_path) + // Created sparkles will disappear on their own + new sparkle_path(get_turf(victim), get_dir(caster, victim)) + + if(isliving(victim)) + var/mob/living/victim_living = victim + victim_living.Paralyze(4 SECONDS) + to_chat(victim, span_userdanger("You're thrown back by [caster]!")) + + // So stuff gets tossed around at the same time. + victim.safe_throw_at(throwtarget, ((clamp((max_throw - (clamp(dist_from_caster - 2, 0, dist_from_caster))), 3, max_throw))), 1, caster, force = repulse_force) + +/datum/action/cooldown/spell/aoe/repulse/wizard + name = "Repulse" + desc = "This spell throws everything around the user away." + button_icon_state = "repulse" + sound = 'sound/magic/repulse.ogg' + + school = SCHOOL_EVOCATION + invocation = "GITTAH WEIGH" + invocation_type = INVOCATION_SHOUT + aoe_radius = 5 + + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 6.25 SECONDS + +/datum/action/cooldown/spell/aoe/repulse/xeno + name = "Tail Sweep" + desc = "Throw back attackers with a sweep of your tail." + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + button_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "tailsweep" + panel = "Alien" + sound = 'sound/magic/tail_swing.ogg' + + cooldown_time = 15 SECONDS + spell_requirements = NONE + + invocation_type = INVOCATION_NONE + antimagic_flags = NONE + aoe_radius = 2 + + sparkle_path = /obj/effect/temp_visual/dir_setting/tailsweep + +/datum/action/cooldown/spell/aoe/repulse/xeno/cast(atom/cast_on) + if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_caster = cast_on + playsound(get_turf(carbon_caster), 'sound/voice/hiss5.ogg', 80, TRUE, TRUE) + carbon_caster.spin(6, 1) + + return ..() diff --git a/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm b/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm new file mode 100644 index 000000000000..188dc1a21f98 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm @@ -0,0 +1,39 @@ +/datum/action/cooldown/spell/aoe/sacred_flame + name = "Sacred Flame" + desc = "Makes everyone around you more flammable, and lights yourself on fire." + button_icon_state = "sacredflame" + sound = 'sound/magic/fireball.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 6 SECONDS + + invocation = "FI'RAN DADISKO" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + aoe_radius = 6 + + /// The amount of firestacks to put people afflicted. + var/firestacks_to_give = 20 + +/datum/action/cooldown/spell/aoe/sacred_flame/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/nearby_mob in view(aoe_radius, center)) + things += nearby_mob + + return things + +/datum/action/cooldown/spell/aoe/sacred_flame/cast_on_thing_in_aoe(mob/living/victim, mob/living/caster) + if(victim.can_block_magic(antimagic_flags)) + return + + victim.adjust_fire_stacks(firestacks_to_give) + // Let people who got afflicted know they're suddenly a matchstick + // But skip the caster - they'll know anyways. + if(victim != caster) + to_chat(victim, span_warning("You suddenly feel very flammable.")) + +/datum/action/cooldown/spell/aoe/sacred_flame/cast(mob/living/cast_on) + . = ..() + cast_on.ignite_mob() + to_chat(cast_on, span_danger("You feel a roaring flame build up inside you!")) \ No newline at end of file diff --git a/code/modules/spells/spell_types/area_teleport.dm b/code/modules/spells/spell_types/area_teleport.dm deleted file mode 100644 index 9ee9402ce8c8..000000000000 --- a/code/modules/spells/spell_types/area_teleport.dm +++ /dev/null @@ -1,79 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/area_teleport - name = "Area teleport" - desc = "This spell teleports you to a type of area of your selection." - nonabstract_req = TRUE - - var/randomise_selection = FALSE //if it lets the usr choose the teleport loc or picks it from the list - var/invocation_area = TRUE //if the invocation appends the selected area - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - - var/say_destination = TRUE - -/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1,mob/living/user = usr) - var/thearea = before_cast(targets) - if(!thearea || !cast_check(1)) - revert_cast() - return - invocation(thearea,user) - if(charge_type == SPELL_CHARGE_TYPE_RECHARGE && recharge) - INVOKE_ASYNC(src, PROC_REF(start_recharge)) - cast(targets,thearea,user) - after_cast(targets) - -/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets) - var/A = null - - if(!randomise_selection) - A = input("Area to teleport to", "Teleport", A) as null|anything in GLOB.teleportlocs - else - A = pick(GLOB.teleportlocs) - if(!A) - return - var/area/thearea = GLOB.teleportlocs[A] - - return thearea - -/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!T.density) - var/clear = TRUE - for(var/obj/O in T) - if(O.density) - clear = FALSE - break - if(clear) - L+=T - - if(!L.len) - to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(target && target.buckled) - target.buckled.unbuckle_mob(target, force=1) - - var/list/tempL = L - var/attempt = null - var/success = FALSE - while(tempL.len) - attempt = pick(tempL) - do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC) - if(get_turf(target) == attempt) - success = TRUE - break - else - tempL.Remove(attempt) - - if(!success) - do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC) - playsound(get_turf(user), sound2, 50,1) - -/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/living/user = usr) - if(say_destination && chosenarea) - invocation = "[initial(invocation)] [uppertext(chosenarea.name)]" - else - invocation = initial(invocation) - ..() diff --git a/code/modules/spells/spell_types/arrow.dm b/code/modules/spells/spell_types/arrow.dm deleted file mode 100644 index 05901c5e2ea8..000000000000 --- a/code/modules/spells/spell_types/arrow.dm +++ /dev/null @@ -1,35 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/conjure_item/arrow - name = "Summon Arrow" - desc = "A spell that summons a sharp arrow in the user's hand, ready to be shot out of a bow. Can be quickly casted by pressing the 'quick-equip' key on an empty hand." - action_icon = 'icons/obj/ammo.dmi' - action_icon_state = "arrow" - invocation_type = SPELL_INVOCATION_EMOTE - invocation = "snap" - item_type = /obj/item/ammo_casing/reusable/arrow - charge_max = 15 SECONDS - cooldown_min = 1 SECONDS - delete_old = FALSE - drop_currently_held = FALSE - -/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/on_gain(mob/living/user) - . = ..() - RegisterSignal(user, COMSIG_MOB_QUICK_EQUIP, PROC_REF(on_quick_equip)) - -/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/on_lose(mob/living/user) - . = ..() - UnregisterSignal(user, COMSIG_MOB_QUICK_EQUIP) - -/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/proc/on_quick_equip(obj/item/held_item) - if(isitem(held_item) || isitem(usr.get_active_held_item())) - return - Click() - if(usr.get_active_held_item()) - return COMPONENT_BLOCK_QUICK_EQUIP - -/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/magic - name = "Summon Magic Arrow" - desc = "A spell that summons a homing arrow in the user's hand, ready to be shot out of a bow that quickly becomes dull after hitting something. Can be quickly casted by pressing the 'quick-equip' key on an empty hand." - action_icon_state = "arrow_magic" - item_type = /obj/item/ammo_casing/reusable/arrow/magic - charge_max = 15 SECONDS - cooldown_min = 0.25 SECONDS diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/barnyard.dm deleted file mode 100644 index 1d690086642a..000000000000 --- a/code/modules/spells/spell_types/barnyard.dm +++ /dev/null @@ -1,51 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/barnyardcurse - name = "Curse of the Barnyard" - desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." - school = "transmutation" - charge_type = SPELL_CHARGE_TYPE_RECHARGE - charge_max = 150 - charge_counter = 0 - clothes_req = FALSE - stat_allowed = FALSE - invocation = "KN'A FTAGHU, PUCK 'BTHNK!" - invocation_type = SPELL_INVOCATION_SAY - range = 7 - cooldown_min = 30 - selection_type = "range" - var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/monkey)) - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "barn" - -/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr) - if(!targets.len) - to_chat(user, span_notice("No target found in range.")) - return - - var/mob/living/carbon/target = targets[1] - - - if(!is_type_in_typecache(target, compatible_mobs_typecache)) - to_chat(user, span_notice("You are unable to curse [target]'s head!")) - return - - if(!(target in oview(range))) - to_chat(user, span_notice("[target.p_theyre(TRUE)] too far away!")) - return - - if(target.anti_magic_check()) - to_chat(user, span_warning("The spell had no effect!")) - target.visible_message(span_danger("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!"), \ - span_danger("Your face starts burning up, but the flames are repulsed by your anti-magic protection!")) - return - - var/list/masks = list(/obj/item/clothing/mask/pig/cursed, /obj/item/clothing/mask/cowmask/cursed, /obj/item/clothing/mask/horsehead/cursed) - - var/choice = pick(masks) - var/obj/item/clothing/mask/magichead = new choice(get_turf(target)) - target.visible_message(span_danger("[target]'s face bursts into flames, and a barnyard animal's head takes its place!"), \ - span_danger("Your face burns up, and shortly after the fire you realise you have the face of a barnyard animal!")) - if(!target.dropItemToGround(target.wear_mask)) - qdel(target.wear_mask) - target.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, 1, 1) - - target.flash_act() diff --git a/code/modules/spells/spell_types/bloodcrawl.dm b/code/modules/spells/spell_types/bloodcrawl.dm deleted file mode 100644 index 3519b1e45022..000000000000 --- a/code/modules/spells/spell_types/bloodcrawl.dm +++ /dev/null @@ -1,41 +0,0 @@ -/obj/effect/proc_holder/spell/bloodcrawl - name = "Blood Crawl" - desc = "Use pools of blood to phase out of existence." - charge_max = 0 - clothes_req = FALSE - //If you couldn't cast this while phased, you'd have a problem - phase_allowed = TRUE - selection_type = "range" - range = 1 - cooldown_min = 0 - overlay = null - action_icon = 'icons/mob/actions/actions_minor_antag.dmi' - action_icon_state = "bloodcrawl" - action_background_icon_state = "bg_demon" - var/phased = FALSE - -/obj/effect/proc_holder/spell/bloodcrawl/choose_targets(mob/user = usr) - for(var/obj/effect/decal/cleanable/target in range(range, get_turf(user))) - if(target.can_bloodcrawl_in()) - perform(target) - return - revert_cast() - to_chat(user, span_warning("There must be a nearby source of blood!")) - -/obj/effect/proc_holder/spell/bloodcrawl/perform(obj/effect/decal/cleanable/target, recharge = 1, mob/living/user = usr) - if(istype(user)) - if(istype(user, /mob/living/simple_animal/slaughter)) - var/mob/living/simple_animal/slaughter/slaught = user - slaught.current_hitstreak = 0 - slaught.wound_bonus = initial(slaught.wound_bonus) - slaught.bare_wound_bonus = initial(slaught.bare_wound_bonus) - if(phased) - if(user.phasein(target)) - phased = FALSE - else - if(user.phaseout(target)) - phased = TRUE - start_recharge() - return - revert_cast() - to_chat(user, span_warning("You are unable to blood crawl!")) diff --git a/code/modules/spells/spell_types/charge.dm b/code/modules/spells/spell_types/charge.dm deleted file mode 100644 index a7497158f286..000000000000 --- a/code/modules/spells/spell_types/charge.dm +++ /dev/null @@ -1,103 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/charge - name = "Charge" - desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user." - - school = "transmutation" - charge_max = 600 - clothes_req = FALSE - invocation = "DIRI CEL" - invocation_type = SPELL_INVOCATION_WHISPER - range = -1 - cooldown_min = 400 //50 deciseconds reduction per rank - include_user = TRUE - - -/obj/effect/proc_holder/spell/targeted/charge/cast(list/targets,mob/user = usr) - for(var/mob/living/L in targets) - var/list/hand_items = list(L.get_active_held_item(),L.get_inactive_held_item()) - var/charged_item = null - var/burnt_out = FALSE - - if(L.pulling && isliving(L.pulling)) - var/mob/living/M = L.pulling - if(M.mob_spell_list.len != 0 || (M.mind?.spell_list.len != 0)) - for(var/obj/effect/proc_holder/spell/S in M.mob_spell_list) - S.charge_counter = S.charge_max - if(M.mind) - for(var/obj/effect/proc_holder/spell/S in M.mind.spell_list) - S.charge_counter = S.charge_max - to_chat(M, span_notice("You feel raw magic flowing through you. It feels good!")) - else - to_chat(M, span_notice("You feel very strange for a moment, but then it passes.")) - burnt_out = TRUE - charged_item = M - break - for(var/obj/item in hand_items) - if(istype(item, /obj/item/spellbook)) - to_chat(L, span_danger("Glowing red letters appear on the front cover...")) - to_chat(L, span_warning("[pick("NICE TRY BUT NO!","CLEVER BUT NOT CLEVER ENOUGH!", "SUCH FLAGRANT CHEESING IS WHY WE ACCEPTED YOUR APPLICATION!", "CUTE! VERY CUTE!", "YOU DIDN'T THINK IT'D BE THAT EASY, DID YOU?")]")) - burnt_out = TRUE - else if(istype(item, /obj/item/book/granter/spell)) - var/obj/item/book/granter/spell/I = item - if(!I.oneuse) - to_chat(L, span_notice("This book is infinite use and can't be recharged, yet the magic has improved the book somehow...")) - burnt_out = TRUE - I.pages_to_mastery-- - break - if(prob(80)) - L.visible_message(span_warning("[I] catches fire!")) - qdel(I) - else - I.used = FALSE - charged_item = I - break - else if(istype(item, /obj/item/gun/magic)) - var/obj/item/gun/magic/I = item - if(prob(80) && !I.can_charge) - I.max_charges-- - if(I.max_charges <= 0) - I.max_charges = 0 - burnt_out = TRUE - I.charges = I.max_charges - if(istype(item, /obj/item/gun/magic/wand) && I.max_charges != 0) - var/obj/item/gun/magic/W = item - W.icon_state = initial(W.icon_state) - I.recharge_newshot() - charged_item = I - break - else if(istype(item, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/C = item - if(!C.self_recharge) - if(prob(80)) - C.maxcharge -= 200 - if(C.maxcharge <= 1) //Div by 0 protection - C.maxcharge = 1 - burnt_out = TRUE - C.charge = C.maxcharge - charged_item = C - break - else if(item.contents) - var/obj/I = null - for(I in item.contents) - if(istype(I, /obj/item/stock_parts/cell/)) - var/obj/item/stock_parts/cell/C = I - if(!C.self_recharge) - if(prob(80)) - C.maxcharge -= 200 - if(C.maxcharge <= 1) //Div by 0 protection - C.maxcharge = 1 - burnt_out = TRUE - C.charge = C.maxcharge - if(istype(C.loc, /obj/item/gun)) - var/obj/item/gun/G = C.loc - G.process_chamber() - item.update_icon() - charged_item = item - break - if(!charged_item) - to_chat(L, span_notice("You feel magical power surging through your hands, but the feeling rapidly fades...")) - else if(burnt_out) - to_chat(L, span_caution("[charged_item] doesn't seem to be reacting to the spell...")) - else - playsound(get_turf(L), 'sound/magic/charge.ogg', 50, 1) - to_chat(L, span_notice("[charged_item] suddenly feels very warm!")) diff --git a/code/modules/spells/spell_types/charged/_charged.dm b/code/modules/spells/spell_types/charged/_charged.dm new file mode 100644 index 000000000000..adfcc6d28fba --- /dev/null +++ b/code/modules/spells/spell_types/charged/_charged.dm @@ -0,0 +1,163 @@ +/** + * ## Channelled spells + * + * These spells do something after a channel time. + * To use this template, all that's needed is for cast() to be implemented. + */ +/datum/action/cooldown/spell/charged + active_overlay_icon_state = "bg_spell_border_active_yellow" + + /// What message do we display when we start chanelling? + var/channel_message + /// Whether we're currently channelling / charging the spell + var/currently_channeling = FALSE + /// How long it takes to channel the spell. + var/channel_time = 10 SECONDS + + // Overlay optional, applied when we start channelling + /// What icon should we use for our overlay + var/charge_overlay_icon + /// What icon state should we use for our overlay + var/charge_overlay_state + /// The actual appearance / our overlay. Don't mess with this + var/mutable_appearance/charge_overlay_instance + + // Sound optional, played when we start chanelling + /// What soundpath should we play when we start chanelling + var/charge_sound + /// The actual sound we generate, don't mess with this + var/sound/charge_sound_instance + +/datum/action/cooldown/spell/charged/New(Target, original) + . = ..() + if(!channel_message) + channel_message = span_notice("You start chanelling [src]...") + + if(charge_sound) + charge_sound_instance = sound(charge_sound, channel = CHANNEL_CHARGED_SPELL) + + if(charge_overlay_icon && charge_overlay_state) + charge_overlay_instance = mutable_appearance(charge_overlay_icon, charge_overlay_state, EFFECTS_LAYER) + +/datum/action/cooldown/spell/charged/Destroy() + if(owner) + stop_channel_effect(owner) + + charge_overlay_instance = null + charge_sound_instance = null + return ..() + +/datum/action/cooldown/spell/charged/Remove(mob/living/remove_from) + stop_channel_effect(remove_from) + return ..() + +/datum/action/cooldown/spell/charged/is_action_active(atom/movable/screen/movable/action_button/current_button) + return currently_channeling + +/datum/action/cooldown/spell/charged/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(currently_channeling) + if(feedback) + to_chat(owner, span_warning("You're already channeling [src]!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/charged/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + to_chat(cast_on, channel_message) + + if(charge_sound_instance) + playsound(cast_on, charge_sound_instance, 50, FALSE) + + if(charge_overlay_instance) + cast_on.add_overlay(charge_overlay_instance) + + currently_channeling = TRUE + build_all_button_icons(UPDATE_BUTTON_STATUS) + if(!do_after(cast_on, channel_time, stayStill = FALSE, needhand = FALSE)) + stop_channel_effect(cast_on) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/charged/cast(atom/cast_on) + . = ..() + stop_channel_effect(cast_on) + +/datum/action/cooldown/spell/charged/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + if(currently_channeling) + .[PANEL_DISPLAY_STATUS] = "CHANNELING" + +/// Interrupts the chanelling effect, removing any overlay or sound playing (for the passed mob) +/datum/action/cooldown/spell/charged/proc/stop_channel_effect(mob/for_who) + if(charge_overlay_instance) + for_who.cut_overlay(charge_overlay_instance) + + if(charge_sound_instance) + for_who.stop_sound_channel(CHANNEL_CHARGED_SPELL) + // Play a null sound in to cancel the sound playing, because byond + playsound(for_who, sound(null, repeat = 0, channel = CHANNEL_CHARGED_SPELL), 50, FALSE) + + currently_channeling = FALSE + build_all_button_icons(UPDATE_BUTTON_STATUS) + +/** + * ### Channelled "Beam" spells + * + * Channelled spells that pick a random target from nearby atoms to cast a spell on. + * Commonly used for beams, hence the name, but nothing's stopping projectiles or whatever from working. + * + * If no targets are nearby, cancels the spell and refunds the cooldown. + */ +/datum/action/cooldown/spell/charged/beam + /// The radius around the caster to find a target. + var/target_radius = 5 + /// The maximum number of bounces the beam will go before stopping. + var/max_beam_bounces = 1 + /// Who's our initial beam target? Set in before cast, used in cast. + var/atom/initial_target + +/datum/action/cooldown/spell/charged/beam/Destroy() + initial_target = null // This like shouuld never hang references but I've seen some cursed things so let's be safe + return ..() + +/datum/action/cooldown/spell/charged/beam/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + initial_target = get_target(cast_on) + if(isnull(initial_target)) + cast_on.balloon_alert(cast_on, "no targets nearby!") + stop_channel_effect(cast_on) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/charged/beam/cast(atom/cast_on) + . = ..() + send_beam(cast_on, initial_target, max_beam_bounces) + initial_target = null + +/datum/action/cooldown/spell/charged/beam/proc/send_beam(atom/origin, atom/to_beam, bounces) + SHOULD_CALL_PARENT(FALSE) + CRASH("[type] did not implement send_beam and either has no effects or implemented the spell incorrectly.") + +/datum/action/cooldown/spell/charged/beam/proc/get_target(atom/center) + var/list/things = list() + for(var/atom/nearby_thing in range(target_radius, center)) + if(nearby_thing == owner || nearby_thing == center) + continue + + things += nearby_thing + + if(!length(things)) + return null + + return pick(things) diff --git a/code/modules/spells/spell_types/charged/tesla_blast.dm b/code/modules/spells/spell_types/charged/tesla_blast.dm new file mode 100644 index 000000000000..ff98073b38b9 --- /dev/null +++ b/code/modules/spells/spell_types/charged/tesla_blast.dm @@ -0,0 +1,62 @@ +/datum/action/cooldown/spell/charged/beam/tesla + name = "Tesla Blast" + desc = "Charge up a tesla arc and release it at random nearby targets! \ + You can move freely while it charges. The arc jumps between targets and can knock them down." + button_icon_state = "lightning" + sound = 'sound/magic/lightningbolt.ogg' + + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 6.75 SECONDS + + invocation = "UN'LTD P'WAH!" + invocation_type = INVOCATION_SHOUT + school = SCHOOL_EVOCATION + + channel_message = span_notice("You start gathering power...") + charge_overlay_icon = 'icons/effects/effects.dmi' + charge_overlay_state = "electricity" + charge_sound = 'sound/magic/lightning_chargeup.ogg' + target_radius = 7 + max_beam_bounces = 5 + + /// How much energy / damage does the initial bounce + var/initial_energy = 30 + /// How much energy / damage is lost per bounce + var/energy_lost_per_bounce = 5 + +/// Zaps a target, the bolt originating from origin. +/datum/action/cooldown/spell/charged/beam/tesla/send_beam(atom/origin, mob/living/carbon/to_beam, bolt_energy = 30, bounces = 5) + origin.Beam(to_beam, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS) + playsound(get_turf(to_beam), 'sound/magic/lightningshock.ogg', 50, TRUE, -1) + + if(to_beam.can_block_magic(antimagic_flags)) + to_beam.visible_message( + span_warning("[to_beam] absorbs the spell, remaining unharmed!"), + span_userdanger("You absorb the spell, remaining unharmed!"), + ) + + else + to_beam.electrocute_act(bolt_energy, "Lightning Bolt") + + // Bounce again! Call our proc recursively to keep the chain going (even if our mob blocked it with antimagic) + if(bounces < 1) + return + var/mob/living/carbon/to_beam_next = get_target(to_beam) + if(isnull(to_beam_next)) + return + send_beam(to_beam, to_beam_next, max(bolt_energy - energy_lost_per_bounce, energy_lost_per_bounce), bounces - 1) + +/datum/action/cooldown/spell/charged/beam/tesla/get_target(atom/center) + var/list/possibles = list() + for(var/mob/living/carbon/to_check in view(target_radius, center)) + if(to_check == center || to_check == owner) + continue + if(!length(get_path_to(center, to_check, dist = target_radius, simulated_only = FALSE))) + continue + + possibles += to_check + + if(!length(possibles)) + return null + + return pick(possibles) diff --git a/code/modules/spells/spell_types/cone/_cone.dm b/code/modules/spells/spell_types/cone/_cone.dm new file mode 100644 index 000000000000..45ba9d456bbe --- /dev/null +++ b/code/modules/spells/spell_types/cone/_cone.dm @@ -0,0 +1,123 @@ +/** + * ## Cone spells + * + * Cone spells shoot off as a cone from the caster. + */ +/datum/action/cooldown/spell/cone + /// This controls how many levels the cone has. Increase this value to make a bigger cone. + var/cone_levels = 3 + /// This value determines if the cone penetrates walls. + var/respect_density = FALSE + +/datum/action/cooldown/spell/cone/cast(atom/cast_on) + . = ..() + var/list/cone_turfs = get_cone_turfs(get_turf(cast_on), cast_on.dir, cone_levels) + SEND_SIGNAL(src, COMSIG_SPELL_CONE_ON_CAST, cone_turfs, cast_on) + make_cone(cone_turfs, cast_on) + +/datum/action/cooldown/spell/cone/proc/make_cone(list/cone_turfs, atom/caster) + for(var/list/turf_list in cone_turfs) + do_cone_effects(turf_list, caster) + +/// This proc does obj, mob and turf cone effects on all targets in the passed list. +/datum/action/cooldown/spell/cone/proc/do_cone_effects(list/target_turf_list, atom/caster, level = 1) + SEND_SIGNAL(src, COMSIG_SPELL_CONE_ON_LAYER_EFFECT, target_turf_list, caster, level) + for(var/turf/target_turf as anything in target_turf_list) + if(QDELETED(target_turf)) //if turf is no longer there + continue + + do_turf_cone_effect(target_turf, caster, level) + if(!isopenturf(target_turf)) + continue + + for(var/atom/movable/movable_content as anything in target_turf) + if(isobj(movable_content)) + do_obj_cone_effect(movable_content, level) + else if(isliving(movable_content)) + do_mob_cone_effect(movable_content, level) + +///This proc deterimines how the spell will affect turfs. +/datum/action/cooldown/spell/cone/proc/do_turf_cone_effect(turf/target_turf, atom/caster, level) + return + +///This proc deterimines how the spell will affect objects. +/datum/action/cooldown/spell/cone/proc/do_obj_cone_effect(obj/target_obj, atom/caster, level) + return + +///This proc deterimines how the spell will affect mobs. +/datum/action/cooldown/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, atom/caster, level) + return + +///This proc creates a list of turfs that are hit by the cone. +/datum/action/cooldown/spell/cone/proc/get_cone_turfs(turf/starter_turf, dir_to_use, cone_levels = 3) + var/list/turfs_to_return = list() + var/turf/turf_to_use = starter_turf + var/turf/left_turf + var/turf/right_turf + var/right_dir + var/left_dir + switch(dir_to_use) + if(NORTH) + left_dir = WEST + right_dir = EAST + if(SOUTH) + left_dir = EAST + right_dir = WEST + if(EAST) + left_dir = NORTH + right_dir = SOUTH + if(WEST) + left_dir = SOUTH + right_dir = NORTH + + for(var/i in 1 to cone_levels) + var/list/level_turfs = list() + turf_to_use = get_step(turf_to_use, dir_to_use) + level_turfs += turf_to_use + if(i != 1) + left_turf = get_step(turf_to_use, left_dir) + level_turfs += left_turf + right_turf = get_step(turf_to_use, right_dir) + level_turfs += right_turf + for(var/left_i in 1 to i -calculate_cone_shape(i)) + if(left_turf.density && respect_density) + break + left_turf = get_step(left_turf, left_dir) + level_turfs += left_turf + for(var/right_i in 1 to i -calculate_cone_shape(i)) + if(right_turf.density && respect_density) + break + right_turf = get_step(right_turf, right_dir) + level_turfs += right_turf + turfs_to_return += list(level_turfs) + if(i == cone_levels) + continue + if(turf_to_use.density && respect_density) + break + return turfs_to_return + +///This proc adjusts the cones width depending on the level. +/datum/action/cooldown/spell/cone/proc/calculate_cone_shape(current_level) + var/end_taper_start = round(cone_levels * 0.8) + if(current_level > end_taper_start) + return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula. + else + return 2 + +/** + * ### Staggered Cone + * + * Staggered Cone spells will reach each cone level + * gradually / with a delay, instead of affecting the entire + * cone area at once. + */ +/datum/action/cooldown/spell/cone/staggered + + /// The delay between each cone level triggering. + var/delay_between_level = 0.2 SECONDS + +/datum/action/cooldown/spell/cone/staggered/make_cone(list/cone_turfs, atom/caster) + var/level_counter = 0 + for(var/list/turf_list in cone_turfs) + level_counter++ + addtimer(CALLBACK(src, PROC_REF(do_cone_effects), turf_list, caster, level_counter), delay_between_level * level_counter) \ No newline at end of file diff --git a/code/modules/spells/spell_types/cone_spells.dm b/code/modules/spells/spell_types/cone_spells.dm deleted file mode 100644 index 2e4bbd671efd..000000000000 --- a/code/modules/spells/spell_types/cone_spells.dm +++ /dev/null @@ -1,117 +0,0 @@ -/obj/effect/proc_holder/spell/cone - name = "Cone of Nothing" - desc = "Does nothing in a cone! Wow!" - school = "evocation" - charge_max = 100 - clothes_req = FALSE - invocation = "FUKAN NOTHAN" - invocation_type = SPELL_INVOCATION_SAY - sound = 'sound/magic/forcewall.ogg' - action_icon_state = "shield" - range = -1 - cooldown_min = 0.5 SECONDS - ///This controls how many levels the cone has, increase this value to make a bigger cone. - var/cone_levels = 3 - ///This value determines if the cone penetrates walls. - var/respect_density = FALSE - -/obj/effect/proc_holder/spell/cone/choose_targets(mob/user = usr) - perform(null, user=user) - -///This proc creates a list of turfs that are hit by the cone -/obj/effect/proc_holder/spell/cone/proc/cone_helper(turf/starter_turf, dir_to_use, cone_levels = 3) - var/list/turfs_to_return = list() - var/turf/turf_to_use = starter_turf - var/turf/left_turf - var/turf/right_turf - var/right_dir - var/left_dir - switch(dir_to_use) - if(NORTH) - left_dir = WEST - right_dir = EAST - if(SOUTH) - left_dir = EAST - right_dir = WEST - if(EAST) - left_dir = NORTH - right_dir = SOUTH - if(WEST) - left_dir = SOUTH - right_dir = NORTH - - - for(var/i in 1 to cone_levels) - var/list/level_turfs = list() - turf_to_use = get_step(turf_to_use, dir_to_use) - level_turfs += turf_to_use - if(i != 1) - left_turf = get_step(turf_to_use, left_dir) - level_turfs += left_turf - right_turf = get_step(turf_to_use, right_dir) - level_turfs += right_turf - for(var/left_i in 1 to i -calculate_cone_shape(i)) - if(left_turf.density && respect_density) - break - left_turf = get_step(left_turf, left_dir) - level_turfs += left_turf - for(var/right_i in 1 to i -calculate_cone_shape(i)) - if(right_turf.density && respect_density) - break - right_turf = get_step(right_turf, right_dir) - level_turfs += right_turf - turfs_to_return += list(level_turfs) - if(i == cone_levels) - continue - if(turf_to_use.density && respect_density) - break - return turfs_to_return - -/obj/effect/proc_holder/spell/cone/cast(list/targets,mob/user = usr) - var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels) - for(var/list/turf_list in cone_turfs) - do_cone_effects(turf_list) - -///This proc does obj, mob and turf cone effects on all targets in a list -/obj/effect/proc_holder/spell/cone/proc/do_cone_effects(list/target_turf_list, level) - for(var/target_turf in target_turf_list) - if(!target_turf) //if turf is no longer there - continue - do_turf_cone_effect(target_turf, level) - if(isopenturf(target_turf)) - var/turf/open/open_turf = target_turf - for(var/movable_content in open_turf) - if(isobj(movable_content)) - do_obj_cone_effect(movable_content, level) - else if(isliving(movable_content)) - do_mob_cone_effect(movable_content, level) - -///This proc deterimines how the spell will affect turfs. -/obj/effect/proc_holder/spell/cone/proc/do_turf_cone_effect(turf/target_turf, level) - return - -///This proc deterimines how the spell will affect objects. -/obj/effect/proc_holder/spell/cone/proc/do_obj_cone_effect(obj/target_obj, level) - return - -///This proc deterimines how the spell will affect mobs. -/obj/effect/proc_holder/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, level) - return - -///This proc adjusts the cones width depending on the level. -/obj/effect/proc_holder/spell/cone/proc/calculate_cone_shape(current_level) - var/end_taper_start = round(cone_levels * 0.8) - if(current_level > end_taper_start) - return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula. - else - return 2 - -///This type of cone gradually affects each level of the cone instead of affecting the entire area at once. -/obj/effect/proc_holder/spell/cone/staggered - -/obj/effect/proc_holder/spell/cone/staggered/cast(list/targets,mob/user = usr) - var/level_counter = 0 - var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels) - for(var/list/turf_list in cone_turfs) - level_counter++ - addtimer(CALLBACK(src, PROC_REF(do_cone_effects), turf_list, level_counter), 2 * level_counter) diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm deleted file mode 100644 index 0d06e7a24eaa..000000000000 --- a/code/modules/spells/spell_types/conjure.dm +++ /dev/null @@ -1,103 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure - name = "Conjure" - desc = "This spell conjures objs of the specified types in range." - - var/list/summon_type = list() //determines what exactly will be summoned - //should be text, like list("/mob/living/simple_animal/bot/ed209") - - var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds - var/summon_amt = 1 //amount of objects summoned - var/summon_ignore_density = FALSE //if set to TRUE, adds dense tiles to possible spawn places - var/summon_ignore_prev_spawn_points = TRUE //if set to TRUE, each new object is summoned on a new spawn point - - var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet - //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example - - var/cast_sound = 'sound/items/welder.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/conjure/cast(list/targets,mob/user = usr) - playsound(get_turf(user), cast_sound, 50,1) - for(var/turf/T in targets) - if(T.density && !summon_ignore_density) - targets -= T - - for(var/i=0,i 0) + QDEL_IN(summoned_object, summon_lifespan) + + post_summon(summoned_object, cast_on) + +/// Called on atoms summoned after they are created, allows extra variable editing and such of created objects +/datum/action/cooldown/spell/conjure/proc/post_summon(atom/summoned_object, atom/cast_on) + return \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/bees.dm b/code/modules/spells/spell_types/conjure/bees.dm new file mode 100644 index 000000000000..db0bc9875990 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/bees.dm @@ -0,0 +1,18 @@ +/datum/action/cooldown/spell/conjure/bee + name = "Lesser Summon Bees" + desc = "This spell magically kicks a transdimensional beehive, \ + instantly summoning a swarm of bees to your location. \ + These bees are NOT friendly to anyone." + button_icon_state = "bee" + sound = 'sound/voice/moth/scream_moth.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "NOT THE BEES" + invocation_type = INVOCATION_SHOUT + + summon_radius = 3 + summon_type = list(/mob/living/simple_animal/hostile/poison/bees/toxin) + summon_amount = 9 diff --git a/code/modules/spells/spell_types/conjure/carp.dm b/code/modules/spells/spell_types/conjure/carp.dm new file mode 100644 index 000000000000..339b507a6971 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/carp.dm @@ -0,0 +1,13 @@ +/datum/action/cooldown/spell/conjure/carp + name = "Summon Carp" + desc = "This spell conjures a simple carp." + sound = 'sound/magic/summon_karp.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 2 MINUTES + + invocation = "NOUK FHUNMM SACP RISSKA" + invocation_type = INVOCATION_SHOUT + + summon_radius = 1 + summon_type = list(/mob/living/simple_animal/hostile/carp) \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/constructs.dm b/code/modules/spells/spell_types/conjure/constructs.dm new file mode 100644 index 000000000000..2af3034f0788 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/constructs.dm @@ -0,0 +1,22 @@ +/datum/action/cooldown/spell/conjure/construct + name = "Summon Construct Shell" + desc = "This spell conjures a construct which may be controlled by Shades." + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "artificer" + sound = 'sound/magic/summonitems_generic.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/obj/structure/constructshell) + +/datum/action/cooldown/spell/conjure/construct/lesser // Used by artificers. + name = "Create Construct Shell" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + cooldown_time = 3 MINUTES diff --git a/code/modules/spells/spell_types/conjure/creatures.dm b/code/modules/spells/spell_types/conjure/creatures.dm new file mode 100644 index 000000000000..876bb662293b --- /dev/null +++ b/code/modules/spells/spell_types/conjure/creatures.dm @@ -0,0 +1,15 @@ +/datum/action/cooldown/spell/conjure/creature + name = "Summon Creature Swarm" + desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth." + sound = 'sound/magic/summonitems_generic.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 2 MINUTES + + invocation = "IA IA" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + + summon_radius = 3 + summon_type = list(/mob/living/simple_animal/hostile/netherworld) + summon_amount = 10 \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/cult_turfs.dm b/code/modules/spells/spell_types/conjure/cult_turfs.dm new file mode 100644 index 000000000000..faaac4728406 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/cult_turfs.dm @@ -0,0 +1,33 @@ +/datum/action/cooldown/spell/conjure/cult_floor + name = "Summon Cult Floor" + desc = "This spell constructs a cult floor." + background_icon_state = "bg_cult" + overlay_icon_state = "bg_cult_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "floorconstruct" + + school = SCHOOL_CONJURATION + cooldown_time = 2 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/turf/open/floor/engine/cult) + +/datum/action/cooldown/spell/conjure/cult_wall + name = "Summon Cult Wall" + desc = "This spell constructs a cult wall." + background_icon_state = "bg_cult" + overlay_icon_state = "bg_cult_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "lesserconstruct" + + school = SCHOOL_CONJURATION + cooldown_time = 10 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/turf/closed/wall/mineral/cult/artificer) // We don't want artificer-based runed metal farms. diff --git a/code/modules/spells/spell_types/conjure/ed_swarm.dm b/code/modules/spells/spell_types/conjure/ed_swarm.dm new file mode 100644 index 000000000000..6ccd29f9ab23 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/ed_swarm.dm @@ -0,0 +1,21 @@ +// test purposes - Also a lot of fun +/datum/action/cooldown/spell/conjure/summon_ed_swarm + name = "Dispense Wizard Justice" + desc = "This spell dispenses wizard justice." + + summon_radius = 3 + summon_type = list(/mob/living/simple_animal/bot/ed209) + summon_amount = 10 + +/datum/action/cooldown/spell/conjure/summon_ed_swarm/post_summon(atom/summoned_object, atom/cast_on) + if(!istype(summoned_object, /mob/living/simple_animal/bot/ed209)) + return + + var/mob/living/simple_animal/bot/ed209/summoned_bot = summoned_object + summoned_bot.name = "Wizard's Justicebot" + + summoned_bot.declare_arrests = FALSE + summoned_bot.emag_act(owner) + + summoned_bot.projectile = /obj/item/projectile/beam/laser + summoned_bot.shoot_sound = 'sound/weapons/laser.ogg' \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/invisible_chair.dm b/code/modules/spells/spell_types/conjure/invisible_chair.dm new file mode 100644 index 000000000000..125cd4cf75ff --- /dev/null +++ b/code/modules/spells/spell_types/conjure/invisible_chair.dm @@ -0,0 +1,36 @@ +/datum/action/cooldown/spell/conjure/invisible_chair + name = "Invisible Chair" + desc = "The mime's performance transmutates a chair into physical reality." + background_icon_state = "bg_mime" + overlay_icon_state = "bg_mime_border" + button_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_chair" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = span_notice("You conjure an invisible chair and sit down.") + invocation_type = INVOCATION_EMOTE + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + summon_radius = 0 + summon_type = list(/obj/structure/chair/mime) + summon_lifespan = 25 SECONDS + +/datum/action/cooldown/spell/conjure/invisible_chair/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] pulls out an invisible chair and sits down.") + +/datum/action/cooldown/spell/conjure/invisible_chair/post_summon(atom/summoned_object, mob/living/carbon/human/cast_on) + if(!isobj(summoned_object)) + return + + var/obj/chair = summoned_object + chair.setDir(cast_on.dir) + chair.buckle_mob(cast_on) diff --git a/code/modules/spells/spell_types/conjure/invisible_wall.dm b/code/modules/spells/spell_types/conjure/invisible_wall.dm new file mode 100644 index 000000000000..a61db7cf74e1 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/invisible_wall.dm @@ -0,0 +1,28 @@ +/datum/action/cooldown/spell/conjure/invisible_wall + name = "Invisible Wall" + desc = "The mime's performance transmutates a wall into physical reality." + background_icon_state = "bg_mime" + overlay_icon_state = "bg_mime_border" + button_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_wall" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = span_notice("You form a wall in front of yourself.") + invocation_type = INVOCATION_EMOTE + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + summon_radius = 0 + summon_type = list(/obj/effect/forcefield/mime) + summon_lifespan = 30 SECONDS + +/datum/action/cooldown/spell/conjure/invisible_wall/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] looks as if a wall is in front of [cast_on.p_them()].") diff --git a/code/modules/spells/spell_types/conjure/link_worlds.dm b/code/modules/spells/spell_types/conjure/link_worlds.dm new file mode 100644 index 000000000000..c04b59353105 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/link_worlds.dm @@ -0,0 +1,15 @@ +/datum/action/cooldown/spell/conjure/link_worlds + name = "Link Worlds" + desc = "A whole new dimension for you to play with! They won't be happy about it, though." + + sound = 'sound/weapons/marauder.ogg' + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "WTF" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + + summon_radius = 1 + summon_type = list(/obj/structure/spawner/nether) + summon_amount = 1 \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/presents.dm b/code/modules/spells/spell_types/conjure/presents.dm new file mode 100644 index 000000000000..aab3d497011a --- /dev/null +++ b/code/modules/spells/spell_types/conjure/presents.dm @@ -0,0 +1,14 @@ +/datum/action/cooldown/spell/conjure/presents + name = "Conjure Presents!" + desc = "This spell lets you reach into S-space and retrieve presents! Yay!" + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 13.75 SECONDS + + invocation = "HO HO HO" + invocation_type = INVOCATION_SHOUT + + summon_radius = 3 + summon_type = list(/obj/item/a_gift) + summon_amount = 5 \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/soulstone.dm b/code/modules/spells/spell_types/conjure/soulstone.dm new file mode 100644 index 000000000000..08bd2b2b0072 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/soulstone.dm @@ -0,0 +1,28 @@ +/datum/action/cooldown/spell/conjure/soulstone + name = "Summon Soulstone" + desc = "This spell reaches into Nar'Sie's realm, summoning one of the legendary fragments across time and space." + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "summonsoulstone" + + school = SCHOOL_CONJURATION + cooldown_time = 4 MINUTES + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/obj/item/soulstone) + +/datum/action/cooldown/spell/conjure/soulstone/cult + name = "Create Nar'sian Soulstone" + cooldown_time = 6 MINUTES + +/datum/action/cooldown/spell/conjure/soulstone/noncult + name = "Create Soulstone" + summon_type = list(/obj/item/soulstone/anybody) + +/datum/action/cooldown/spell/conjure/soulstone/purified + name = "Create Purified Soulstone" + summon_type = list(/obj/item/soulstone/anybody/purified) diff --git a/code/modules/spells/spell_types/conjure/the_traps.dm b/code/modules/spells/spell_types/conjure/the_traps.dm new file mode 100644 index 000000000000..b8650a542e5b --- /dev/null +++ b/code/modules/spells/spell_types/conjure/the_traps.dm @@ -0,0 +1,35 @@ +/datum/action/cooldown/spell/conjure/the_traps + name = "The Traps!" + desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." + button_icon_state = "the_traps" + + cooldown_time = 25 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + invocation = "CAVERE INSIDIAS" + invocation_type = INVOCATION_SHOUT + + summon_radius = 3 + summon_type = list( + /obj/structure/trap/stun, + /obj/structure/trap/fire, + /obj/structure/trap/chill, + /obj/structure/trap/damage, + ) + summon_lifespan = 5 MINUTES + summon_amount = 5 + + /// The amount of charges the traps spawn with. + var/trap_charges = 1 + +/datum/action/cooldown/spell/conjure/the_traps/post_summon(atom/summoned_object, atom/cast_on) + if(!istype(summoned_object, /obj/structure/trap)) + return + + var/obj/structure/trap/summoned_trap = summoned_object + summoned_trap.charges = trap_charges + + if(ismob(cast_on)) + var/mob/mob_caster = cast_on + if(mob_caster.mind) + summoned_trap.immune_minds += owner.mind \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure_item/_conjure_item.dm b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm new file mode 100644 index 000000000000..f12626b248e8 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm @@ -0,0 +1,46 @@ +/datum/action/cooldown/spell/conjure_item + school = SCHOOL_CONJURATION + invocation_type = INVOCATION_NONE + + /// Typepath of whatever item we summon + var/obj/item/item_type + /// If TRUE, we delete any previously created items when we cast the spell + var/delete_old = TRUE + /// List of weakrefs to items summoned + var/list/datum/weakref/item_refs + +/datum/action/cooldown/spell/conjure_item/Destroy() + // If we delete_old, clean up all of our items on delete + if(delete_old) + QDEL_LAZYLIST(item_refs) + + // If we don't delete_old, just let all the items be free + else + LAZYNULL(item_refs) + + return ..() + +/datum/action/cooldown/spell/conjure_item/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/conjure_item/cast(mob/living/carbon/cast_on) + if(delete_old && LAZYLEN(item_refs)) + QDEL_LAZYLIST(item_refs) + + var/obj/item/existing_item = cast_on.get_active_held_item() + if(existing_item) + cast_on.dropItemToGround(existing_item) + + var/obj/item/created = make_item() + if(QDELETED(created)) + CRASH("[type] tried to create an item, but failed. It's item type is [item_type].") + + cast_on.put_in_hands(created, del_on_fail = TRUE) + return ..() + +/// Instantiates the item we're conjuring and returns it. +/// Item is made in nullspace and moved out in cast(). +/datum/action/cooldown/spell/conjure_item/proc/make_item() + var/obj/item/made_item = new item_type() + LAZYADD(item_refs, WEAKREF(made_item)) + return made_item \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure_item/arrow.dm b/code/modules/spells/spell_types/conjure_item/arrow.dm new file mode 100644 index 000000000000..2fc37fa4b81d --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/arrow.dm @@ -0,0 +1,37 @@ +/datum/action/cooldown/spell/conjure_item/arrow + name = "Summon Arrow" + desc = "A spell that summons a sharp arrow in the user's hand, ready to be shot out of a bow. Can be quickly casted by pressing the 'quick-equip' key on an empty hand." + button_icon = 'icons/obj/ammo.dmi' + button_icon_state = "arrow" + invocation_type = INVOCATION_EMOTE + invocation = "snap" + item_type = /obj/item/ammo_casing/reusable/arrow + cooldown_time = 15 SECONDS + cooldown_reduction_per_rank = 3.5 SECONDS + delete_old = FALSE + +/datum/action/cooldown/spell/conjure_item/arrow/Grant(mob/living/user) + . = ..() + RegisterSignal(user, COMSIG_MOB_QUICK_EQUIP, PROC_REF(on_quick_equip)) + +/datum/action/cooldown/spell/conjure_item/arrow/Remove(mob/living/user) + UnregisterSignal(user, COMSIG_MOB_QUICK_EQUIP) + return ..() + +/datum/action/cooldown/spell/conjure_item/arrow/proc/on_quick_equip(obj/item/held_item) + SIGNAL_HANDLER + + if(isitem(held_item) || isitem(owner.get_active_held_item())) + return + + Trigger() + + if(owner.get_active_held_item()) + return COMPONENT_BLOCK_QUICK_EQUIP + +/datum/action/cooldown/spell/conjure_item/arrow/magic + name = "Summon Magic Arrow" + desc = "A spell that summons a homing arrow in the user's hand, ready to be shot out of a bow that quickly becomes dull after hitting something. Can be quickly casted by pressing the 'quick-equip' key on an empty hand." + button_icon_state = "arrow_magic" + item_type = /obj/item/ammo_casing/reusable/arrow/magic + cooldown_reduction_per_rank = 3.75 SECONDS diff --git a/code/modules/spells/spell_types/conjure_item/infinite_guns.dm b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm new file mode 100644 index 000000000000..c9d34174c0e9 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm @@ -0,0 +1,41 @@ +/datum/action/cooldown/spell/conjure_item/infinite_guns + school = SCHOOL_CONJURATION + cooldown_time = 1.25 MINUTES + cooldown_reduction_per_rank = 18.5 SECONDS + + invocation_type = INVOCATION_NONE + + item_type = /obj/item/gun/ballistic/rifle + // Enchanted guns self delete / do wacky stuff, anyways + delete_old = FALSE + +/datum/action/cooldown/spell/conjure_item/infinite_guns/Remove(mob/living/remove_from) + var/obj/item/existing = remove_from.is_holding_item_of_type(item_type) + if(existing) + qdel(existing) + + return ..() + +// Because enchanted guns self-delete and regenerate themselves, +// override make_item here and let's not bother with tracking their weakrefs. +/datum/action/cooldown/spell/conjure_item/infinite_guns/make_item() + return new item_type() + +/datum/action/cooldown/spell/conjure_item/infinite_guns/gun + name = "Lesser Summon Guns" + desc = "Why reload when you have infinite guns? \ + Summons an unending stream of bolt action rifles that deal little damage, \ + but will knock targets down. Requires both hands free to use. \ + Learning this spell makes you unable to learn Arcane Barrage." + button_icon_state = "bolt_action" + + item_type = /obj/item/gun/ballistic/rifle/boltaction/enchanted + +/datum/action/cooldown/spell/conjure_item/infinite_guns/arcane_barrage + name = "Arcane Barrage" + desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. \ + Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. \ + Learning this spell makes you unable to learn Lesser Summon Gun." + button_icon_state = "arcane_barrage" + + item_type = /obj/item/gun/ballistic/rifle/boltaction/enchanted/arcane_barrage diff --git a/code/modules/spells/spell_types/conjure_item/invisible_box.dm b/code/modules/spells/spell_types/conjure_item/invisible_box.dm new file mode 100644 index 000000000000..c5a9b2744c00 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/invisible_box.dm @@ -0,0 +1,44 @@ +/datum/action/cooldown/spell/conjure_item/invisible_box + name = "Invisible Box" + desc = "The mime's performance transmutates a box into physical reality." + background_icon_state = "bg_mime" + overlay_icon_state = "bg_mime_border" + button_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_box" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = span_notice("You conjure up an invisible box, large enough to store a few things.") + invocation_type = INVOCATION_EMOTE + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + delete_old = FALSE + item_type = /obj/item/storage/box/mime + /// How long boxes last before going away + var/box_lifespan = 50 SECONDS + +/datum/action/cooldown/spell/conjure_item/invisible_box/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] moves [cast_on.p_their()] hands in the shape of a cube, pressing a box out of the air.") + +/datum/action/cooldown/spell/conjure_item/invisible_box/make_item() + . = ..() + var/obj/item/made_box = . + made_box.alpha = 255 + addtimer(CALLBACK(src, PROC_REF(cleanup_box), made_box), box_lifespan) + +/// Callback that gets rid out of box and removes the weakref from our list +/datum/action/cooldown/spell/conjure_item/invisible_box/proc/cleanup_box(obj/item/storage/box/box) + if(QDELETED(box) || !istype(box)) + return + + box.emptyStorage() + LAZYREMOVE(item_refs, WEAKREF(box)) + qdel(box) diff --git a/code/modules/spells/spell_types/conjure_item/lightning_packet.dm b/code/modules/spells/spell_types/conjure_item/lightning_packet.dm new file mode 100644 index 000000000000..7c20a1c0297d --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/lightning_packet.dm @@ -0,0 +1,38 @@ +/datum/action/cooldown/spell/conjure_item/spellpacket + name = "Thrown Lightning" + desc = "Forged from eldrich energies, a packet of pure power, \ + known as a spell packet will appear in your hand, that - when thrown - will stun the target." + button_icon_state = "thrownlightning" + + cooldown_time = 1 SECONDS + spell_max_level = 1 + + item_type = /obj/item/spellpacket/lightningbolt + +/datum/action/cooldown/spell/conjure_item/spellpacket/cast(mob/living/carbon/cast_on) + . = ..() + cast_on.throw_mode_on() + +/obj/item/spellpacket/lightningbolt + name = "\improper Lightning bolt Spell Packet" + desc = "Some birdseed wrapped in cloth that crackles with electricity." + icon = 'icons/obj/toy.dmi' + icon_state = "snappop" + w_class = WEIGHT_CLASS_TINY + +/obj/item/spellpacket/lightningbolt/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(.) + return + + if(isliving(hit_atom)) + var/mob/living/hit_living = hit_atom + if(!hit_living.can_block_magic()) + hit_living.electrocute_act(80, src, illusion = TRUE) + qdel(src) + +/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE) + . = ..() + if(ishuman(thrower)) + var/mob/living/carbon/human/human_thrower = thrower + human_thrower.say("LIGHTNINGBOLT!!", forced = "spell") diff --git a/code/modules/spells/spell_types/conjure_item/snowball.dm b/code/modules/spells/spell_types/conjure_item/snowball.dm new file mode 100644 index 000000000000..ebe7c005fb96 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/snowball.dm @@ -0,0 +1,8 @@ +/datum/action/cooldown/spell/conjure_item/snowball + name = "Snowball" + desc = "Concentrates cryokinetic forces to create snowballs, useful for throwing at people." + button_icon = 'icons/obj/toy.dmi' + button_icon_state = "snowball" + + cooldown_time = 1.5 SECONDS + item_type = /obj/item/toy/snowball diff --git a/code/modules/spells/spell_types/construct_spells.dm b/code/modules/spells/spell_types/construct_spells.dm deleted file mode 100644 index e6ebc99a628d..000000000000 --- a/code/modules/spells/spell_types/construct_spells.dm +++ /dev/null @@ -1,336 +0,0 @@ -//////////////////////////////Construct Spells///////////////////////// - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser - charge_max = 1800 - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "artificer" - action_background_icon_state = "bg_demon" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser/cult - clothes_req = TRUE - charge_max = 2500 - - -/obj/effect/proc_holder/spell/aoe_turf/area_conversion - name = "Area Conversion" - desc = "This spell instantly converts a small area around you." - - school = "transmutation" - charge_max = 50 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = 2 - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "areaconvert" - action_background_icon_state = "bg_cult" - -/obj/effect/proc_holder/spell/aoe_turf/area_conversion/cast(list/targets, mob/user = usr) - playsound(get_turf(user), 'sound/items/welder.ogg', 75, 1) - for(var/turf/T in targets) - T.narsie_act(FALSE, TRUE, 100 - (get_dist(user, T) * 25)) - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/floor - name = "Summon Cult Floor" - desc = "This spell constructs a cult floor." - - school = "conjuration" - charge_max = 20 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = 0 - summon_type = list(/turf/open/floor/engine/cult) - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "floorconstruct" - action_background_icon_state = "bg_cult" - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/wall - name = "Summon Cult Wall" - desc = "This spell constructs a cult wall." - - school = "conjuration" - charge_max = 100 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = 0 - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "lesserconstruct" - action_background_icon_state = "bg_cult" - - summon_type = list(/turf/closed/wall/mineral/cult/artificer) //we don't want artificer-based runed metal farms - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/wall/reinforced - name = "Greater Construction" - desc = "This spell constructs a reinforced metal wall." - - school = "conjuration" - charge_max = 300 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = 0 - - summon_type = list(/turf/closed/wall/r_wall) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone - name = "Summon Soulstone" - desc = "This spell reaches into Nar'sie's realm, summoning one of the legendary fragments across time and space." - - school = "conjuration" - charge_max = 2400 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = 0 - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "summonsoulstone" - action_background_icon_state = "bg_demon" - - summon_type = list(/obj/item/soulstone) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/cult - clothes_req = TRUE - charge_max = 3600 - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult - summon_type = list(/obj/item/soulstone/anybody) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult/purified - summon_type = list(/obj/item/soulstone/anybody/purified) - -/obj/effect/proc_holder/spell/targeted/forcewall/cult - name = "Shield" - desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire." - school = "transmutation" - charge_max = 400 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - wall_type = /obj/effect/forcefield/cult - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "cultforcewall" - action_background_icon_state = "bg_demon" - - - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift - name = "Phase Shift" - desc = "This spell allows you to pass through walls." - - school = "transmutation" - charge_max = 250 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = -1 - include_user = TRUE - jaunt_duration = 50 //in deciseconds - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "phaseshift" - action_background_icon_state = "bg_demon" - jaunt_in_time = 12 - jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith - jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/jaunt_steam(mobloc) - return - -/obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser - name = "Lesser Magic Missile" - desc = "This spell fires several, slow moving, magic projectiles at nearby targets." - - school = "evocation" - charge_max = 400 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - max_targets = 6 - action_icon_state = "magicm" - action_background_icon_state = "bg_demon" - proj_type = /obj/item/projectile/magic/spell/magic_missile/lesser - -/obj/item/projectile/magic/spell/magic_missile/lesser - name = "lesser magic missile" - color = "red" //Looks more culty this way - range = 10 - -/obj/item/projectile/magic/spell/magic_missile/lesser/on_hit(target) - if(ismob(target)) - var/mob/M = target - if(iscultist(target))//cultists can't be harmed by their own constructs' spells! - to_chat(target, span_danger("[src] harmlessly dissipates into crimson particles upon contacting your body!")) - return BULLET_ACT_BLOCK - if(M.anti_magic_check()) - M.visible_message(span_warning("[src] vanishes on contact with [target]!")) - return BULLET_ACT_BLOCK - . = ..() - -/obj/effect/proc_holder/spell/targeted/smoke/disable - name = "Paralysing Smoke" - desc = "This spell spawns a cloud of paralysing smoke." - - school = "conjuration" - charge_max = 200 - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = -1 - include_user = TRUE - cooldown_min = 20 //25 deciseconds reduction per rank - - smoke_spread = /datum/effect_system/fluid_spread/smoke/sleeping - smoke_amt = 4 - action_icon_state = "smoke" - action_background_icon_state = "bg_cult" - - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze - name = "Abyssal Gaze" - desc = "This spell instills a deep terror in your target, temporarily chilling and blinding it." - - charge_max = 750 - range = 5 - include_user = FALSE - selection_type = "range" - stat_allowed = FALSE - - school = "evocation" - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_background_icon_state = "bg_demon" - action_icon_state = "abyssal_gaze" - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze/cast(list/targets, mob/user = usr) - if(!LAZYLEN(targets)) - to_chat(user, span_notice("No target found in range.")) - revert_cast() - return - - var/mob/living/carbon/target = targets[1] - - if(!(target in oview(range))) - to_chat(user, span_notice("[target] is too far away!")) - revert_cast() - return - - if(target.anti_magic_check(TRUE, TRUE)) - to_chat(target, span_warning("You feel a freezing darkness closing in on you, but it rapidly dissipates.")) - return - - to_chat(target, span_userdanger("A freezing darkness surrounds you...")) - target.playsound_local(get_turf(target), 'sound/hallucinations/i_see_you1.ogg', 50, 1) - user.playsound_local(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) - target.become_blind(ABYSSAL_GAZE_BLIND) - addtimer(CALLBACK(src, PROC_REF(cure_blindness), target), 40) - target.adjust_bodytemperature(-200) - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze/proc/cure_blindness(mob/target) - if(isliving(target)) - var/mob/living/L = target - L.cure_blind(ABYSSAL_GAZE_BLIND) - -/obj/effect/proc_holder/spell/targeted/dominate - name = "Dominate" - desc = "This spell dominates the mind of a lesser creature to the will of Nar'sie, allying it only to her direct followers." - - charge_max = 600 - range = 7 - include_user = FALSE - selection_type = "range" - stat_allowed = FALSE - - school = "evocation" - clothes_req = FALSE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_background_icon_state = "bg_demon" - action_icon_state = "dominate" - -/obj/effect/proc_holder/spell/targeted/dominate/cast(list/targets, mob/user = usr) - if(!LAZYLEN(targets)) - to_chat(user, span_notice("No target found in range.")) - revert_cast() - return - - var/mob/living/simple_animal/S = targets[1] - - if(S.ckey) - to_chat(user, span_warning("[S] is too intelligent to dominate!")) - revert_cast() - return - - if(S.stat) - to_chat(user, span_warning("[S] is dead!")) - revert_cast() - return - - if(S.sentience_type != SENTIENCE_ORGANIC) - to_chat(user, span_warning("[S] cannot be dominated!")) - revert_cast() - return - - if(!(S in oview(range))) - to_chat(user, span_notice("[S] is too far away!")) - revert_cast() - return - - S.add_atom_colour("#990000", FIXED_COLOUR_PRIORITY) - S.faction = list("cult") - playsound(get_turf(S), 'sound/effects/ghost.ogg', 100, 1) - new /obj/effect/temp_visual/cult/sac(get_turf(S)) - -/obj/effect/proc_holder/spell/targeted/dominate/can_target(mob/living/target) - if(!isanimal(target) || target.stat) - return FALSE - if("cult" in target.faction) - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem - charge_max = 800 - jaunt_in_type = /obj/effect/temp_visual/dir_setting/cult/phase - jaunt_out_type = /obj/effect/temp_visual/dir_setting/cult/phase/out - - -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/juggernaut - name = "Gauntlet Echo" - desc = "Channels energy into your gauntlet - firing its essence forward in a slow moving, yet devastating, attack." - proj_type = /obj/item/projectile/magic/spell/juggernaut - charge_max = 350 - clothes_req = FALSE - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "cultfist" - action_background_icon_state = "bg_demon" - sound = 'sound/weapons/resonator_blast.ogg' - -/obj/item/projectile/magic/spell/juggernaut - name = "Gauntlet Echo" - icon_state = "cultfist" - alpha = 180 - damage = 30 - damage_type = BRUTE - knockdown = 50 - hitsound = 'sound/weapons/punch3.ogg' - trigger_range = 0 - check_holy = TRUE - ignored_factions = list("cult") - range = 15 - speed = 7 - -/obj/item/projectile/magic/spell/juggernaut/on_hit(atom/target, blocked) - . = ..() - var/turf/T = get_turf(src) - playsound(T, 'sound/weapons/resonator_blast.ogg', 100, FALSE) - new /obj/effect/temp_visual/cult/sac(T) - for(var/obj/O in range(src,1)) - if(O.density && !istype(O, /obj/structure/destructible/cult)) - O.take_damage(90, BRUTE, MELEE, 0) - new /obj/effect/temp_visual/cult/turf/floor(get_turf(O)) diff --git a/code/modules/spells/spell_types/devil.dm b/code/modules/spells/spell_types/devil.dm index ff7d07700499..fa10bbc0f116 100644 --- a/code/modules/spells/spell_types/devil.dm +++ b/code/modules/spells/spell_types/devil.dm @@ -1,112 +1,117 @@ -/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork +/datum/action/cooldown/spell/conjure_item/summon_pitchfork name = "Summon Pitchfork" desc = "A devil's weapon of choice. Use this to summon/unsummon your pitchfork." - invocation_type = SPELL_INVOCATION_NONE - include_user = TRUE - range = -1 - clothes_req = FALSE - item_type = /obj/item/twohanded/pitchfork/demonic + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "pitchfork" + background_icon_state = "bg_demon" + + school = SCHOOL_CONJURATION + invocation_type = INVOCATION_NONE - school = "conjuration" - charge_max = 150 - cooldown_min = 10 - action_icon = 'icons/mob/actions/actions_minor_antag.dmi' - action_icon_state = "pitchfork" - action_background_icon_state = "bg_demon" + item_type = /obj/item/twohanded/pitchfork/demonic + cooldown_time = 15 SECONDS + spell_requirements = NONE -/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/greater +/datum/action/cooldown/spell/conjure_item/summon_pitchfork/greater item_type = /obj/item/twohanded/pitchfork/demonic/greater -/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/ascended +/datum/action/cooldown/spell/conjure_item/summon_pitchfork/ascended item_type = /obj/item/twohanded/pitchfork/demonic/ascended -/obj/effect/proc_holder/spell/targeted/conjure_item/violin - item_type = /obj/item/instrument/violin/golden - desc = "A devil's instrument of choice. Use this to summon/unsummon your golden violin." - invocation_type = SPELL_INVOCATION_WHISPER - invocation = "I aint have this much fun since Georgia." - action_icon_state = "golden_violin" +/datum/action/cooldown/spell/conjure_item/violin name = "Summon golden violin" - action_icon = 'icons/mob/actions/actions_minor_antag.dmi' - action_background_icon_state = "bg_demon" + desc = "A devil's instrument of choice. \n\ + Use this to summon/unsummon your golden violin." + button_icon_state = "golden_violin" + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + background_icon_state = "bg_demon" + + invocation = "I aint have this much fun since Georgia." + invocation_type = INVOCATION_WHISPER + + item_type = /obj/item/instrument/violin/golden -/obj/effect/proc_holder/spell/targeted/summon_contract +/datum/action/cooldown/spell/pointed/summon_contract name = "Summon infernal contract" desc = "Skip making a contract by hand, just do it by magic." - invocation_type = SPELL_INVOCATION_WHISPER + button_icon_state = "spell_default" + background_icon_state = "bg_demon" + + school = SCHOOL_CONJURATION invocation = "Just sign on the dotted line." - include_user = FALSE - range = 5 - clothes_req = FALSE - - school = "conjuration" - charge_max = 150 - cooldown_min = 10 - action_icon_state = "spell_default" - action_background_icon_state = "bg_demon" - -/obj/effect/proc_holder/spell/targeted/summon_contract/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/C in targets) - if(C.mind && user.mind) - if(C.stat == DEAD) - if(user.dropItemToGround(user.get_active_held_item())) - var/obj/item/paper/contract/infernal/revive/contract = new(user.loc, C.mind, user.mind) - user.put_in_hands(contract) - else - var/obj/item/paper/contract/infernal/contract // = new(user.loc, C.mind, contractType, user.mind) - var/contractTypeName = input(user, "What type of contract?") in list ("Power", "Wealth", "Prestige", "Magic", "Knowledge", "Friendship") - switch(contractTypeName) - if("Power") - contract = new /obj/item/paper/contract/infernal/power(C.loc, C.mind, user.mind) - if("Wealth") - contract = new /obj/item/paper/contract/infernal/wealth(C.loc, C.mind, user.mind) - if("Prestige") - contract = new /obj/item/paper/contract/infernal/prestige(C.loc, C.mind, user.mind) - if("Magic") - contract = new /obj/item/paper/contract/infernal/magic(C.loc, C.mind, user.mind) - if("Knowledge") - contract = new /obj/item/paper/contract/infernal/knowledge(C.loc, C.mind, user.mind) - if("Friendship") - contract = new /obj/item/paper/contract/infernal/friend(C.loc, C.mind, user.mind) - C.put_in_hands(contract) - else - to_chat(user, span_notice("[C] seems to not be sentient. You cannot summon a contract for [C.p_them()].")) - action.UpdateButtonIcon() - charge_counter = charge_max - recharging = FALSE + invocation_type = INVOCATION_WHISPER + + cast_range = 5 + cooldown_time = 15 SECONDS + spell_requirements = NONE + +/datum/action/cooldown/spell/pointed/summon_contract/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(!.) + return FALSE + if(!iscarbon(target)) + return FALSE + var/mob/living/carbon/carbon_target = target + if(!carbon_target.mind) + to_chat(user, span_notice("[carbon_target] seems to not be sentient. \ + You cannot summon a contract for [carbon_target.p_them()].")) + return FALSE + if(carbon_target.stat == DEAD) + if(carbon_target.dropItemToGround(carbon_target.get_active_held_item())) + var/obj/item/paper/contract/infernal/revive/contract = new(owner.loc, carbon_target.mind, owner.mind) + user.put_in_hands(contract) + return TRUE + return FALSE -/obj/effect/proc_holder/spell/aimed/fireball/hellish + var/obj/item/paper/contract/infernal/contract // = new(user.loc, C.mind, contractType, user.mind) + var/contractTypeName = tgui_input_list(owner, "What type of contract?", "Devilish", list("Power", "Wealth", "Prestige", "Magic", "Knowledge", "Friendship")) + switch(contractTypeName) + if("Power") + contract = new /obj/item/paper/contract/infernal/power(carbon_target.loc, carbon_target.mind, owner.mind) + if("Wealth") + contract = new /obj/item/paper/contract/infernal/wealth(carbon_target.loc, carbon_target.mind, owner.mind) + if("Prestige") + contract = new /obj/item/paper/contract/infernal/prestige(carbon_target.loc, carbon_target.mind, owner.mind) + if("Magic") + contract = new /obj/item/paper/contract/infernal/magic(carbon_target.loc, carbon_target.mind, owner.mind) + if("Knowledge") + contract = new /obj/item/paper/contract/infernal/knowledge(carbon_target.loc, carbon_target.mind, owner.mind) + if("Friendship") + contract = new /obj/item/paper/contract/infernal/friend(carbon_target.loc, carbon_target.mind, owner.mind) + carbon_target.put_in_hands(contract) + + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/fireball/hellish name = "Hellfire" desc = "This spell launches hellfire at the target." + background_icon_state = "bg_demon" - school = "evocation" - charge_max = 80 - clothes_req = FALSE + school = SCHOOL_EVOCATION invocation = "Your very soul will catch fire!" - invocation_type = SPELL_INVOCATION_SAY - range = 2 + invocation_type = INVOCATION_SHOUT - projectile_type = /obj/item/projectile/magic/aoe/fireball/infernal + cooldown_time = 6 SECONDS + cast_range = 2 + spell_requirements = NONE - action_background_icon_state = "bg_demon" + projectile_type = /obj/item/projectile/magic/fireball/infernal -/obj/effect/proc_holder/spell/targeted/infernal_jaunt +/datum/action/cooldown/spell/jaunt/infernal_jaunt name = "Infernal Jaunt" desc = "Use hellfire to phase out of existence." - charge_max = 200 - clothes_req = FALSE - selection_type = "range" - range = -1 - cooldown_min = 0 - overlay = null - include_user = TRUE - action_icon_state = "jaunt" - action_background_icon_state = "bg_demon" - phase_allowed = TRUE - -/obj/effect/proc_holder/spell/targeted/infernal_jaunt/cast(list/targets, mob/living/user = usr) + button_icon_state = "jaunt" + background_icon_state = "bg_demon" + + cooldown_time = 20 SECONDS + spell_requirements = NONE + +/datum/action/cooldown/spell/jaunt/infernal_jaunt/cast(mob/living/user) + . = ..() + if(!.) + return FALSE if(istype(user)) - if(istype(user.loc, /obj/effect/dummy/phased_mob)) + if(is_jaunting(user)) if(valid_location(user)) to_chat(user, span_warning("You are now phasing in.")) if(do_mob(user,user,150)) @@ -117,7 +122,6 @@ else to_chat(user, span_warning("You can only re-appear near a potential signer.")) - revert_cast() return ..() else user.notransform = TRUE @@ -129,11 +133,11 @@ to_chat(user, span_warning("You must remain still while exiting.")) user.notransform = FALSE user.fakefireextinguish() - start_recharge() return - revert_cast() -/obj/effect/proc_holder/spell/targeted/infernal_jaunt/proc/valid_location(mob/living/user = usr) + return TRUE + +/datum/action/cooldown/spell/jaunt/infernal_jaunt/proc/valid_location(mob/living/user = usr) if(istype(get_area(user), /area/shuttle/)) // Can always phase in in a shuttle. return TRUE else @@ -148,7 +152,7 @@ visible_message(span_warning("[src] disappears in a flashfire!")) playsound(get_turf(src), 'sound/magic/enter_blood.ogg', 100, 1, -1) var/obj/effect/dummy/phased_mob/holder = new /obj/effect/dummy/phased_mob(loc) - ExtinguishMob() + extinguish_mob() forceMove(holder) holder = holder notransform = FALSE @@ -165,57 +169,51 @@ playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1) addtimer(CALLBACK(src, PROC_REF(fakefireextinguish)), 15, TIMER_UNIQUE) -/obj/effect/proc_holder/spell/targeted/sintouch +/datum/action/cooldown/spell/aoe/sintouch name = "Sin Touch" desc = "Subtly encourage someone to sin." - charge_max = 1800 - clothes_req = FALSE - selection_type = "range" - range = 2 - cooldown_min = 0 - overlay = null - include_user = FALSE - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "sintouch" - action_background_icon_state = "bg_demon" - phase_allowed = FALSE - random_target = TRUE - random_target_priority = TARGET_RANDOM - max_targets = 3 + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "sintouch" + background_icon_state = "bg_demon" + invocation = "TASTE SIN AND INDULGE!!" - invocation_type = SPELL_INVOCATION_SAY + invocation_type = INVOCATION_SHOUT -/obj/effect/proc_holder/spell/targeted/sintouch/ascended - name = "Greater sin touch" - charge_max = 100 - range = 7 - max_targets = 10 + cooldown_time = 3 MINUTES + aoe_radius = 2 + max_targets = 3 + spell_requirements = NONE -/obj/effect/proc_holder/spell/targeted/sintouch/cast(list/targets, mob/living/user = usr) - for(var/mob/living/carbon/human/H in targets) - if(!H.mind) - continue - if(H.mind.has_antag_datum(/datum/antagonist/sintouched)) - continue - if(H.anti_magic_check(FALSE, TRUE)) - continue - H.mind.add_antag_datum(/datum/antagonist/sintouched) - H.Paralyze(400) +/datum/action/cooldown/spell/aoe/sintouch/ascended + name = "Greater Sin Touch" + cooldown_time = 10 SECONDS + aoe_radius = 7 + max_targets = 10 +/datum/action/cooldown/spell/aoe/sintouch/cast_on_thing_in_aoe(atom/target, atom/caster) + if(!iscarbon(target)) + return + var/mob/living/carbon/target_carbon = target + if(!target_carbon.mind) + return + if(target_carbon.mind.has_antag_datum(/datum/antagonist/sintouched)) + return + if(target_carbon.anti_magic_check(FALSE, TRUE)) + return + target_carbon.mind.add_antag_datum(/datum/antagonist/sintouched) + target_carbon.Paralyze(40 SECONDS) -/obj/effect/proc_holder/spell/targeted/summon_dancefloor +/datum/action/cooldown/spell/summon_dancefloor name = "Summon Dancefloor" desc = "When what a Devil really needs is funk." - include_user = TRUE - range = -1 - clothes_req = FALSE - school = "conjuration" - charge_max = 10 - cooldown_min = 50 //5 seconds, so the smoke can't be spammed - action_icon = 'icons/mob/actions/actions_minor_antag.dmi' - action_icon_state = "funk" - action_background_icon_state = "bg_demon" + school = SCHOOL_CONJURATION + + cooldown_time = 5 SECONDS //so the smoke can't be spammed + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "funk" + background_icon_state = "bg_demon" + spell_requirements = NONE var/list/dancefloor_turfs var/list/dancefloor_turfs_types @@ -223,7 +221,10 @@ var/datum/effect_system/fluid_spread/smoke/transparent/dancefloor_devil/smoke -/obj/effect/proc_holder/spell/targeted/summon_dancefloor/cast(list/targets, mob/user = usr) +/datum/action/cooldown/spell/summon_dancefloor/cast(mob/living/carbon/user) + . = ..() + if(!.) + return FALSE LAZYINITLIST(dancefloor_turfs) LAZYINITLIST(dancefloor_turfs_types) @@ -253,6 +254,8 @@ T.ChangeTurf((i % 2 == 0) ? /turf/open/floor/light/colour_cycle/dancefloor_a : /turf/open/floor/light/colour_cycle/dancefloor_b, flags = CHANGETURF_INHERIT_AIR) i++ + return TRUE + /datum/effect_system/fluid_spread/smoke/transparent/dancefloor_devil effect_type = /obj/effect/particle_effect/fluid/smoke/transparent/dancefloor_devil diff --git a/code/modules/spells/spell_types/devil_boons.dm b/code/modules/spells/spell_types/devil_boons.dm index 3492446d80fd..cb19f261841f 100644 --- a/code/modules/spells/spell_types/devil_boons.dm +++ b/code/modules/spells/spell_types/devil_boons.dm @@ -1,65 +1,70 @@ -/obj/effect/proc_holder/spell/targeted/summon_wealth +/datum/action/cooldown/spell/summon_wealth name = "Summon wealth" desc = "The reward for selling your soul." - invocation_type = SPELL_INVOCATION_NONE - include_user = TRUE - range = -1 - clothes_req = FALSE - school = "conjuration" - charge_max = 100 - cooldown_min = 10 - action_icon = 'icons/mob/actions/actions_minor_antag.dmi' - action_icon_state = "moneybag" + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "moneybag" + school = SCHOOL_CONJURATION + invocation_type = INVOCATION_NONE -/obj/effect/proc_holder/spell/targeted/summon_wealth/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/C in targets) - if(user.dropItemToGround(user.get_active_held_item())) - var/obj/item = pick( - new /obj/item/coin/gold(user.drop_location()), - new /obj/item/coin/diamond(user.drop_location()), - new /obj/item/coin/silver(user.drop_location()), - new /obj/item/clothing/accessory/medal/gold(user.drop_location()), - new /obj/item/stack/sheet/mineral/gold(user.drop_location()), - new /obj/item/stack/sheet/mineral/silver(user.drop_location()), - new /obj/item/stack/sheet/mineral/diamond(user.drop_location()), - new /obj/item/holochip(user.drop_location(), 1000)) - C.put_in_hands(item) + cooldown_time = 10 SECONDS + spell_requirements = NONE -/obj/effect/proc_holder/spell/targeted/view_range +/datum/action/cooldown/spell/summon_wealth/cast(mob/living/carbon/user) + . = ..() + if(!.) + return FALSE + if(user.dropItemToGround(user.get_active_held_item())) + var/obj/item = pick( + new /obj/item/coin/gold(user.drop_location()), + new /obj/item/coin/diamond(user.drop_location()), + new /obj/item/coin/silver(user.drop_location()), + new /obj/item/clothing/accessory/medal/gold(user.drop_location()), + new /obj/item/stack/sheet/mineral/gold(user.drop_location()), + new /obj/item/stack/sheet/mineral/silver(user.drop_location()), + new /obj/item/stack/sheet/mineral/diamond(user.drop_location()), + new /obj/item/holochip(user.drop_location(), 1000)) + user.put_in_hands(item) + + return TRUE + +/datum/action/cooldown/spell/view_range name = "Distant vision" desc = "The reward for selling your soul." - invocation_type = SPELL_INVOCATION_NONE - include_user = TRUE - range = -1 - clothes_req = FALSE - charge_max = 50 - cooldown_min = 10 - action_icon = 'icons/mob/actions/actions_silicon.dmi' - action_icon_state = "camera_jump" + button_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon_state = "camera_jump" + + invocation_type = INVOCATION_NONE + + cooldown_time = 5 SECONDS + spell_requirements = NONE var/ranges = list(7,8,9,10) -/obj/effect/proc_holder/spell/targeted/view_range/cast(list/targets, mob/user = usr) - for(var/mob/C in targets) - if(!C.client) - continue - C.client.view_size.setTo((input("Select view range:", "Range", 4) in ranges) - 7) +/datum/action/cooldown/spell/view_range/cast(mob/living/C) + . = ..() + if(!.) + return FALSE + C.client?.view_size.setTo(tgui_input_list(C, "Select view range:", "Range", ranges)) + + return TRUE -/obj/effect/proc_holder/spell/targeted/summon_friend +/datum/action/cooldown/spell/summon_friend name = "Summon Friend" desc = "The reward for selling your soul." - invocation_type = SPELL_INVOCATION_NONE - include_user = TRUE - range = -1 - clothes_req = FALSE - charge_max = 50 - cooldown_min = 10 - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "sacredflame" + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "sacredflame" + + invocation_type = INVOCATION_NONE + + cooldown_time = 5 SECONDS + spell_requirements = NONE var/mob/living/friend var/obj/effect/mob_spawn/human/demonic_friend/friendShell -/obj/effect/proc_holder/spell/targeted/summon_friend/cast(list/targets, mob/user = usr) +/datum/action/cooldown/spell/summon_friend/cast(mob/living/C) + . = ..() + if(!.) + return FALSE if(!QDELETED(friend)) to_chat(friend, span_userdanger("Your master has deemed you a poor friend. Your durance in hell will now resume.")) friend.dust(TRUE) @@ -68,9 +73,10 @@ if(!QDELETED(friendShell)) qdel(friendShell) return - for(var/C in targets) - var/mob/living/L = C - friendShell = new /obj/effect/mob_spawn/human/demonic_friend(L.loc, L.mind, src) + var/mob/living/L = C + friendShell = new /obj/effect/mob_spawn/human/demonic_friend(L.loc, L.mind, src) + + return TRUE -/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket/robeless - clothes_req = FALSE +/datum/action/cooldown/spell/conjure_item/spellpacket/robeless + spell_requirements = NONE diff --git a/code/modules/spells/spell_types/emplosion.dm b/code/modules/spells/spell_types/emplosion.dm deleted file mode 100644 index a5ba0a39142c..000000000000 --- a/code/modules/spells/spell_types/emplosion.dm +++ /dev/null @@ -1,18 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/emplosion - name = "Emplosion" - desc = "This spell emplodes an area." - - var/emp_heavy = 2 - var/emp_light = 3 - - action_icon_state = "emp" - sound = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/emplosion/cast(list/targets,mob/user = usr) - playsound(get_turf(user), sound, 50,1) - for(var/mob/living/target in targets) - if(target.anti_magic_check()) - continue - empulse(target.loc, emp_heavy, emp_light) - - return \ No newline at end of file diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm deleted file mode 100644 index 021e574b30f0..000000000000 --- a/code/modules/spells/spell_types/ethereal_jaunt.dm +++ /dev/null @@ -1,84 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt - name = "Ethereal Jaunt" - desc = "This spell turns your form ethereal, temporarily making you invisible and able to pass through walls." - - school = "transmutation" - charge_max = 300 - clothes_req = TRUE - invocation = "none" - invocation_type = SPELL_INVOCATION_NONE - range = -1 - cooldown_min = 10 SECONDS //5 seconds reduction per rank - include_user = TRUE - nonabstract_req = TRUE - var/jaunt_duration = 5 SECONDS //in seconds - var/jaunt_in_time = 0.5 SECONDS - var/jaunt_in_type = /obj/effect/temp_visual/wizard - var/jaunt_out_type = /obj/effect/temp_visual/wizard/out - action_icon_state = "jaunt" - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded - play_sound("enter",user) - for(var/mob/living/target in targets) - INVOKE_ASYNC(src, PROC_REF(do_jaunt), target) - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target) - target.notransform = 1 - var/turf/mobloc = get_turf(target) - var/obj/effect/dummy/phased_mob/spell_jaunt/holder = new /obj/effect/dummy/phased_mob/spell_jaunt(mobloc) - new jaunt_out_type(mobloc, target.dir) - target.ExtinguishMob() - target.forceMove(holder) - target.reset_perspective(holder) - target.notransform=0 //mob is safely inside holder now, no need for protection. - jaunt_steam(mobloc) - - sleep(jaunt_duration) - - if(target.loc != holder) //mob warped out of the warp - qdel(holder) - return - mobloc = get_turf(target.loc) - jaunt_steam(mobloc) - target.mobility_flags &= ~MOBILITY_MOVE - holder.reappearing = 1 - play_sound("exit",target) - sleep(2.5 SECONDS - jaunt_in_time) - new jaunt_in_type(mobloc, holder.dir) - target.setDir(holder.dir) - sleep(jaunt_in_time) - qdel(holder) - if(!QDELETED(target)) - if(mobloc.density) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(mobloc, direction) - if(T) - if(target.Move(T)) - break - target.mobility_flags |= MOBILITY_MOVE - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/jaunt_steam(mobloc) - var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread() - steam.set_up(10, 0, mobloc) - steam.start() - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/play_sound(type,mob/living/target) - switch(type) - if("enter") - playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) - if("exit") - playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - -/obj/effect/dummy/phased_mob/spell_jaunt - movespeed = 2 //quite slow. - var/reappearing = FALSE - -/obj/effect/dummy/phased_mob/spell_jaunt/phased_check(mob/living/user, direction) - if(reappearing) - return - . = ..() - if(!.) - return - if (locate(/obj/effect/blessing, .)) - to_chat(user, "Holy energies block your path!") - return null diff --git a/code/modules/spells/spell_types/explosion.dm b/code/modules/spells/spell_types/explosion.dm deleted file mode 100644 index 0ef21ab7869f..000000000000 --- a/code/modules/spells/spell_types/explosion.dm +++ /dev/null @@ -1,16 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/explosion - name = "Explosion" - desc = "This spell explodes an area." - - var/ex_severe = 1 - var/ex_heavy = 2 - var/ex_light = 3 - var/ex_flash = 4 - -/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets,mob/user = usr) - for(var/mob/living/target in targets) - if(target.anti_magic_check()) - continue - explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash) - - return \ No newline at end of file diff --git a/code/modules/spells/spell_types/forcewall.dm b/code/modules/spells/spell_types/forcewall.dm deleted file mode 100644 index b99218e4a793..000000000000 --- a/code/modules/spells/spell_types/forcewall.dm +++ /dev/null @@ -1,41 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/forcewall - name = "Forcewall" - desc = "Create a magical barrier that only you can pass through." - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - invocation = "TARCOL MINTI ZHERI" - invocation_type = SPELL_INVOCATION_SAY - sound = 'sound/magic/forcewall.ogg' - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "shield" - range = -1 - include_user = TRUE - cooldown_min = 50 //12 deciseconds reduction per rank - var/wall_type = /obj/effect/forcefield/wizard - -/obj/effect/proc_holder/spell/targeted/forcewall/cast(list/targets,mob/user = usr) - new wall_type(get_turf(user),user) - if(user.dir == SOUTH || user.dir == NORTH) - new wall_type(get_step(user, EAST),user) - new wall_type(get_step(user, WEST),user) - else - new wall_type(get_step(user, NORTH),user) - new wall_type(get_step(user, SOUTH),user) - - -/obj/effect/forcefield/wizard - var/mob/wizard - -/obj/effect/forcefield/wizard/Initialize(mapload, mob/summoner) - . = ..() - wizard = summoner - -/obj/effect/forcefield/wizard/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(mover == wizard) - return TRUE - if(ismob(mover)) - var/mob/M = mover - if(M.anti_magic_check(chargecost = 0)) - return TRUE diff --git a/code/modules/spells/spell_types/genetic.dm b/code/modules/spells/spell_types/genetic.dm deleted file mode 100644 index b46864891d43..000000000000 --- a/code/modules/spells/spell_types/genetic.dm +++ /dev/null @@ -1,44 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/genetic - name = "Genetic" - desc = "This spell inflicts a set of mutations and disabilities upon the target." - - var/list/active_on = list() - var/list/traits = list() //disabilities - var/list/mutations = list() //mutation defines - var/duration = 100 //deciseconds - /* - Disabilities - 1st bit - ? - 2nd bit - ? - 3rd bit - ? - 4th bit - ? - 5th bit - ? - 6th bit - ? - */ - -/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets,mob/user = usr) - playMagSound() - for(var/mob/living/carbon/target in targets) - if(target.anti_magic_check()) - continue - if(!target.dna) - continue - for(var/A in mutations) - target.dna.add_mutation(A) - for(var/A in traits) - ADD_TRAIT(target, A, GENETICS_SPELL) - active_on += target - addtimer(CALLBACK(src, PROC_REF(remove), target), duration) - -/obj/effect/proc_holder/spell/targeted/genetic/Destroy() - . = ..() - for(var/V in active_on) - remove(V) - -/obj/effect/proc_holder/spell/targeted/genetic/proc/remove(mob/living/carbon/target) - active_on -= target - if(!QDELETED(target)) - for(var/A in mutations) - target.dna.remove_mutation(A) - for(var/A in traits) - REMOVE_TRAIT(target, A, GENETICS_SPELL) \ No newline at end of file diff --git a/code/modules/spells/spell_types/godhand.dm b/code/modules/spells/spell_types/godhand.dm deleted file mode 100644 index cc659303c17c..000000000000 --- a/code/modules/spells/spell_types/godhand.dm +++ /dev/null @@ -1,203 +0,0 @@ -/obj/item/melee/touch_attack - name = "\improper outstretched hand" - desc = "High Five?" - var/catchphrase = "High Five!" - var/on_use_sound = null - var/obj/effect/proc_holder/spell/targeted/touch/attached_spell - icon = 'icons/obj/wizard.dmi' - lefthand_file = 'icons/mob/inhands/misc/touchspell_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/touchspell_righthand.dmi' - icon_state = "disintegrate" - item_state = null - item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL - w_class = WEIGHT_CLASS_HUGE - force = 0 - throwforce = 0 - throw_range = 0 - throw_speed = 0 - var/charges = 1 - -/obj/item/melee/touch_attack/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/obj/item/melee/touch_attack/attack(mob/target, mob/living/carbon/user) - if(!iscarbon(user)) //Look ma, no hands - return - if(!(user.mobility_flags & MOBILITY_USE)) - to_chat(user, span_warning("You can't reach out!")) - return - ..() - -/obj/item/melee/touch_attack/afterattack(atom/target, mob/user, proximity) - . = ..() - user.say(catchphrase, forced = "spell") - playsound(get_turf(user), on_use_sound,50,1) - charges-- - if(charges <= 0) - qdel(src) - -/obj/item/melee/touch_attack/Destroy() - if(attached_spell) - attached_spell.on_hand_destroy(src) - return ..() - -/obj/item/melee/touch_attack/disintegrate - name = "\improper disintegrating touch" - desc = "This hand of mine glows with an awesome power!" - catchphrase = "EI NATH!!" - on_use_sound = 'sound/magic/disintegrate.ogg' - icon_state = "disintegrate" - item_state = "disintegrate" - -/obj/item/melee/touch_attack/disintegrate/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !ismob(target) || !iscarbon(user) || !(user.mobility_flags & MOBILITY_USE)) //exploding after touching yourself would be bad - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/M = target - do_sparks(4, FALSE, M.loc) - for(var/mob/living/L in view(src, 7)) - if(L != user) - L.flash_act(affect_silicon = FALSE) - var/atom/A = M.anti_magic_check() - if(A) - if(isitem(A)) - target.visible_message(span_warning("[target]'s [A] glows brightly as it wards off the spell!")) - user.visible_message(span_warning("The feedback blows [user]'s arm off!"),span_userdanger("The spell bounces from [M]'s skin back into your arm!")) - user.flash_act() - var/obj/item/bodypart/part = user.get_holding_bodypart_of_item(src) - if(part) - part.dismember() - return ..() - var/obj/item/clothing/suit/hooded/bloated_human/suit = M.get_item_by_slot(SLOT_WEAR_SUIT) - if(istype(suit)) - M.visible_message(span_danger("[M]'s [suit] explodes off of them into a puddle of gore!")) - M.dropItemToGround(suit) - qdel(suit) - new /obj/effect/gibspawner(M.loc) - return ..() - M.gib() - return ..() - -/obj/item/melee/touch_attack/fleshtostone - name = "\improper petrifying touch" - desc = "That's the bottom line, because flesh to stone said so!" - catchphrase = "STAUN EI!!" - on_use_sound = 'sound/magic/fleshtostone.ogg' - icon_state = "fleshtostone" - item_state = "fleshtostone" - -/obj/item/melee/touch_attack/fleshtostone/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user)) //getting hard after touching yourself would also be bad - return - if(!(user.mobility_flags & MOBILITY_USE)) - to_chat(user, span_warning("You can't reach out!")) - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/living/M = target - if(M.anti_magic_check()) - to_chat(user, span_warning("The spell can't seem to affect [M]!")) - to_chat(M, span_warning("You feel your flesh turn to stone for a moment, then revert back!")) - ..() - return - M.Stun(40) - M.petrify() - return ..() - -/obj/item/melee/touch_attack/flagellate - name = "\improper flagellating touch" - desc = "I'd like to see them greytide me now." - catchphrase = "RETRIBUTION!!" - on_use_sound = 'sound/magic/wandodeath.ogg' - icon_state = "flagellation" - item_state = "hivehand" - -/obj/item/melee/touch_attack/flagellate/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user)) //flagellating your own mind painfully wouldn't be THAT bad but still bad - return - if(!(user.mobility_flags & MOBILITY_USE)) - to_chat(user, span_warning("You can't reach out!")) - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/living/M = target - if(M.anti_magic_check()) - to_chat(user, span_warning("The spell can't seem to affect [M]!")) - to_chat(M, span_warning("You feel faint energies trying to get into your head, before they suddenly vanish!")) - ..() - return - M.adjustBruteLoss(18) //same as nullrod, but with a large cooldown, so it should be fine - M.blur_eyes(10) - M.confused = max(M.confused, 6) - M.visible_message(span_danger("[M] cringes in pain as they hold their head for a second!")) - M.emote("scream") - to_chat(M, span_warning("You feel an explosion of pain erupt in your mind!")) - return ..() - -/obj/item/melee/touch_attack/pacifism - name = "\improper pacifism touch" - desc = "Yes" - catchphrase = "PAC'FY" - on_use_sound = 'sound/magic/wandodeath.ogg' - icon_state = "flagellation" - item_state = "hivehand" - color = "#FF0000" - -/obj/item/melee/touch_attack/pacifism/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user)) - return - var/mob/living/carbon/human/H = target - if(!H) - return - if(H.anti_magic_check()) - return - H.reagents.add_reagent(/datum/reagent/pax, 5) - H.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 5) - H.ForceContractDisease(new /datum/disease/transformation/gondola(), FALSE, TRUE) - to_chat(H, span_notice("You feel calm...")) - return ..() - -/obj/item/melee/touch_attack/touchofdeath //yogs start - name = "\improper necrotic touch" - desc = "What has a beginning but no end?" - catchphrase = "DIM MAK!!" - on_use_sound = 'sound/magic/wandodeath.ogg' - icon_state = "touchofdeath" - item_state = "touchofdeath" - -/obj/item/melee/touch_attack/touchofdeath/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user) || !(user.mobility_flags & MOBILITY_USE)) - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/living/M = target - do_sparks(4, FALSE, M.loc) - for(var/mob/living/L in view(src, 7)) - if(L != user) - L.flash_act(affect_silicon = FALSE) - var/atom/A = M.anti_magic_check() - if(A) - if(isitem(A)) - target.visible_message(span_warning("[target]'s [A] glows brightly as it wards off the spell!")) - user.visible_message(span_warning("[user]'s arm becomes a pale shade of grey and falls off!"),span_userdanger("The spell bounces from [M]'s skin back into your arm!")) - user.flash_act() - var/obj/item/bodypart/part = user.get_holding_bodypart_of_item(src) - if(part) - part.dismember() - return ..() - var/obj/item/clothing/suit/hooded/bloated_human/suit = M.get_item_by_slot(SLOT_WEAR_SUIT) - if(istype(suit)) - M.visible_message(span_danger("[M]'s [suit] rots away into a pile of goo!")) - M.dropItemToGround(suit) - qdel(suit) - new /obj/effect/decal/cleanable/molten_object(M.loc) - return ..() - M.adjustBruteLoss(max(200-(M.getOxyLoss() + M.getToxLoss() + M.getBruteLoss() + M.getFireLoss()),0)) - M.death(FALSE) - return ..() //yogs end diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm index 21015f2cc511..f975cc3a3870 100644 --- a/code/modules/spells/spell_types/hivemind.dm +++ b/code/modules/spells/spell_types/hivemind.dm @@ -1,169 +1,179 @@ -/obj/effect/proc_holder/spell/target_hive +/* + * ATTENTION ALL FUTURE HIVEMIND CODERS / JANITORS + * + * THINGS ARE LIKE THEY ARE RIGHT NOW BECAUSE THE GAMEMODE + * IS ABANDONED. + * + * IF YOU ARE GOING TO REFACTOR / REWORK / ANYTHING ELSE + * THAT USES THESE SPELLS, PUT THEM IN FORMAL ORDER, + * CHECK THE SPELL BELOW TO SEE HOW IT IS SUPPOSED TO BE + * ORGANIZED, THANK YOU. + * + * IF YOU DON'T DO IT I'LL FUCKING KILL YOU, IT'S BROKEN AS SHIT. + * + * ~ tatax +*/ + +/datum/action/cooldown/spell/aoe/target_hive panel = "Hivemind Abilities" - invocation_type = SPELL_INVOCATION_NONE - selection_type = "range" - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "spell_default" - clothes_req = 0 - human_req = 1 - antimagic_allowed = TRUE - range = 0 //SNOWFLAKE, 0 is unlimited for target_external=0 spells + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "spell_default" + + invocation_type = INVOCATION_NONE + + spell_requirements = SPELL_REQUIRES_HUMAN var/target_external = 0 //Whether or not we select targets inside or outside of the hive -/obj/effect/proc_holder/spell/target_hive/choose_targets(mob/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) +/datum/action/cooldown/spell/aoe/target_hive/cast(atom/cast_on) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive || !hive.hivemembers) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return var/list/possible_targets = list() var/list/targets = list() if(target_external) - for(var/mob/living/carbon/H in view_or_range(range, user, selection_type)) - if(user == H) + for(var/mob/living/carbon/H in view_or_range(aoe_radius, owner, "range")) + if(owner == H) continue - if(!can_target(H)) + if(!is_valid_target(H)) continue if(!hive.is_carbon_member(H)) possible_targets += H else possible_targets = hive.get_carbon_members() - if(range) - possible_targets &= view_or_range(range, user, selection_type) + if(aoe_radius) + possible_targets &= view_or_range(aoe_radius, owner, "range") - var/mob/living/carbon/human/H = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets + var/mob/living/carbon/human/H = tgui_input_list(owner, "Choose the target for the spell.", "Targeting", possible_targets) //arcaic, homeric even if(!H) - revert_cast() return targets += H - perform(targets,user=user) -/obj/effect/proc_holder/spell/target_hive/hive_add + return TRUE + +/datum/action/cooldown/spell/aoe/target_hive/hive_add name = "Assimilate Vessel" desc = "We silently add an unsuspecting target to the hive." - selection_type = "view" - action_icon_state = "add" + button_icon_state = "add" - charge_max = 50 - range = 7 + cooldown_time = 5 SECONDS + aoe_radius = 7 target_external = 1 var/bruteforce = FALSE -/obj/effect/proc_holder/spell/target_hive/hive_add/cast(list/targets, mob/living/user = usr) - var/mob/living/carbon/target = targets[1] - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) +/datum/action/cooldown/spell/aoe/target_hive/hive_add/cast_on_thing_in_aoe(atom/victim, mob/living/carbon/human/caster) + var/mob/living/carbon/target = victim + var/datum/antagonist/hivemind/hive = caster.mind.has_antag_datum(/datum/antagonist/hivemind) if(!target.mind || !target.client || target.stat == DEAD) - to_chat(user, span_notice("We detect no neural activity in this body.")) + to_chat(owner, span_notice("We detect no neural activity in this body.")) var/shielded = HAS_TRAIT(target, TRAIT_MINDSHIELD) var/foiled = target.anti_magic_check(FALSE, FALSE, TRUE, 0) if(shielded && !bruteforce) - to_chat(user, span_warning("Powerful technology protects [target.name]'s mind.")) - revert_cast() + to_chat(owner, span_warning("Powerful technology protects [target.name]'s mind.")) return if((shielded || foiled) && bruteforce) - to_chat(user, span_notice("We [bruteforce ? "bruteforce" : "force"] our way past the mental barriers of [target.name] and begin linking our minds!")) + to_chat(owner, span_notice("We [bruteforce ? "bruteforce" : "force"] our way past the mental barriers of [target.name] and begin linking our minds!")) else - to_chat(user, span_notice("We begin linking our mind with [target.name]!")) + to_chat(owner, span_notice("We begin linking our mind with [target.name]!")) var/multiplier = (!foiled || bruteforce) ? 5 : 10 - if(!do_after(user, multiplier*(1.5**get_dist(user, target)), user, FALSE) || !(target in view(range))) - to_chat(user, span_notice("We fail to connect to [target.name].")) - revert_cast() + if(!do_after(owner, multiplier*(1.5**get_dist(owner, target)), owner, FALSE) || !(target in view(aoe_radius))) + to_chat(owner, span_notice("We fail to connect to [target.name].")) return if((HAS_TRAIT(target, TRAIT_MINDSHIELD) && !bruteforce)) - to_chat(user, span_notice("We fail to connect to [target.name].")) - revert_cast() + to_chat(owner, span_notice("We fail to connect to [target.name].")) return - to_chat(user, span_notice("[target.name] was added to the Hive!")) + to_chat(owner, span_notice("[target.name] was added to the Hive!")) hive.add_to_hive(target) hive.threat_level = max(0, hive.threat_level-0.1) if(bruteforce) if(target.anti_magic_check(FALSE, FALSE, TRUE, 6)) target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) - to_chat(user, span_warning("We are briefly exhausted by the effort required by our enhanced assimilation abilities.")) - user.Immobilize(50) + to_chat(owner, span_warning("We are briefly exhausted by the effort required by our enhanced assimilation abilities.")) + caster.Immobilize(5 SECONDS) SEND_SIGNAL(target, COMSIG_NANITE_SET_VOLUME, 0) for(var/obj/item/implant/mindshield/M in target.implants) qdel(M) else target.anti_magic_check(FALSE, FALSE, TRUE) -/obj/effect/proc_holder/spell/target_hive/hive_remove +/datum/action/cooldown/spell/aoe/target_hive/hive_remove name = "Release Vessel" desc = "We silently remove a nearby target from the hive. We must be close to their body to do so." - selection_type = "view" - action_icon_state = "remove" + button_icon_state = "remove" - charge_max = 50 - range = 7 + cooldown_time = 50 + aoe_radius = 7 -/obj/effect/proc_holder/spell/target_hive/hive_remove/cast(list/targets, mob/living/user = usr) - var/mob/living/carbon/target = targets[1] +/datum/action/cooldown/spell/aoe/target_hive/hive_remove/cast_on_thing_in_aoe(atom/victim, atom/caster) + var/mob/living/carbon/target = victim - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return var/datum/mind/M = target.mind if(!M) - revert_cast() return hive.remove_from_hive(target) hive.calc_size() hive.threat_level += 0.1 - to_chat(user, span_notice("We remove [target.name] from the hive")) + to_chat(owner, span_notice("We remove [target.name] from the hive")) if(hive.active_one_mind) var/datum/antagonist/hivevessel/woke = target.is_wokevessel() if(woke) hive.active_one_mind.remove_member(M) M.remove_antag_datum(/datum/antagonist/hivevessel) -/obj/effect/proc_holder/spell/target_hive/hive_see +/datum/action/cooldown/spell/aoe/target_hive/hive_see name = "Hive Vision" desc = "We use the eyes of one of our vessels. Use again to look through our own eyes once more." - action_icon_state = "see" + button_icon_state = "see" var/mob/living/carbon/vessel var/mob/living/host //Didn't really have any other way to auto-reset the perspective if the other mob got qdeled var/limited = FALSE + var/active = FALSE - charge_max = 20 + cooldown_time = 2 SECONDS -/obj/effect/proc_holder/spell/target_hive/hive_see/on_lose(mob/living/user) - user.reset_perspective() - user.clear_fullscreen("hive_eyes") +/datum/action/cooldown/spell/aoe/target_hive/hive_see/Remove(mob/living/owner) + owner.reset_perspective() + owner.clear_fullscreen("hive_eyes") + return ..() -/obj/effect/proc_holder/spell/target_hive/hive_see/cast(list/targets, mob/living/user = usr) +/datum/action/cooldown/spell/aoe/target_hive/hive_see/cast_on_thing_in_aoe(atom/victim, atom/caster) if(!active) - vessel = targets[1] + vessel = victim if(vessel) if(vessel.anti_magic_check(FALSE, FALSE, TRUE, 0)) if(get_dist(src, vessel) > 42) - to_chat(user, span_warning("We were unable to link our view with [vessel.name]. A barrier of tinfoil prevents us to do so at this distance.")) - revert_cast() + to_chat(owner, span_warning("We were unable to link our view with [vessel.name]. A barrier of tinfoil prevents us to do so at this distance.")) return limited = TRUE - to_chat(user, span_warning("A barrier of tinfoil drastically dampens our link with [vessel.name]. We'll be able to sustain the link as long as they remain within 42 tiles from us.")) - vessel.apply_status_effect(STATUS_EFFECT_BUGGED, user) - user.reset_perspective(vessel) + to_chat(owner, span_warning("A barrier of tinfoil drastically dampens our link with [vessel.name]. We'll be able to sustain the link as long as they remain within 42 tiles from us.")) + vessel.apply_status_effect(STATUS_EFFECT_BUGGED, owner) + owner.reset_perspective(vessel) active = TRUE - host = user - user.clear_fullscreen("hive_mc") - user.overlay_fullscreen("hive_eyes", /atom/movable/screen/fullscreen/hive_eyes) - revert_cast() + host = owner + owner.clear_fullscreen("hive_mc") + owner.overlay_fullscreen("hive_eyes", /atom/movable/screen/fullscreen/hive_eyes) else vessel.remove_status_effect(STATUS_EFFECT_BUGGED) - user.reset_perspective() - user.clear_fullscreen("hive_eyes") - var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in user.mind.spell_list + owner.reset_perspective() + owner.clear_fullscreen("hive_eyes") + var/datum/action/cooldown/spell/aoe/target_hive/hive_control/the_spell = locate(/datum/action/cooldown/spell/aoe/target_hive/hive_control) in owner.actions if(the_spell && the_spell.active) - user.overlay_fullscreen("hive_mc", /atom/movable/screen/fullscreen/hive_mc) + owner.overlay_fullscreen("hive_mc", /atom/movable/screen/fullscreen/hive_mc) active = FALSE limited = FALSE - revert_cast() -/obj/effect/proc_holder/spell/target_hive/hive_see/process() +/datum/action/cooldown/spell/aoe/target_hive/hive_see/process() if(active && (!vessel || !is_hivemember(vessel) || QDELETED(vessel) || (limited && get_dist(vessel, host) > 42))) to_chat(host, span_warning("Our vessel is one of us no more!")) host.reset_perspective() @@ -174,34 +184,28 @@ vessel.remove_status_effect(STATUS_EFFECT_BUGGED) ..() -/obj/effect/proc_holder/spell/target_hive/hive_see/choose_targets(mob/user = usr) - if(!active) - ..() - else - perform(,user) - -/obj/effect/proc_holder/spell/targeted/hive_shock +/datum/action/cooldown/spell/pointed/hive_shock name = "Sensory Shock" desc = "After a short charging time, we overload the mind of one of our vessels with psionic energy, temporarilly disrupting their sight, hearing, and speech." - charge_max = 600 + cooldown_time = 600 panel = "Hivemind Abilities" - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "shock" - antimagic_allowed = TRUE - range = 7 - selection_type = "range" - -/obj/effect/proc_holder/spell/targeted/hive_shock/cast(list/targets, mob/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_HUMAN + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "shock" + cast_range = 7 + +/datum/action/cooldown/spell/pointed/hive_shock/cast(list/targets, mob/owner = usr) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive || !hive.hivemembers) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return var/mob/living/carbon/target = targets[1] - to_chat(user, span_notice("We increase the psionic bandwidth between ourself and the target!")) + to_chat(owner, span_notice("We increase the psionic bandwidth between ourself and the target!")) var/power = 1 if(target.anti_magic_check(FALSE, FALSE, TRUE)) power *= 0.5 @@ -210,44 +214,47 @@ target.blind_eyes(4*power) target.blur_eyes(30*power) target.minimumDeafTicks(15*power) //equivalent to 30s deafness max - target.Jitter(10*power) + target.adjust_jitter(power SECONDS) target.silent += 10*power - target.stuttering += 30*power + target.adjust_stutter(3*power SECONDS) target.Knockdown(1*power) target.stop_pulling() - to_chat(target, span_userdanger("You feel your mind start to burn!")) + to_chat(target, span_ownerdanger("You feel your mind start to burn!")) -/obj/effect/proc_holder/spell/self/hive_scan + return TRUE + +/datum/action/cooldown/spell/hive_scan name = "Psychoreception" desc = "We release a pulse to receive information on any enemies we have previously located via Network Invasion, as well as those currently tracking us." panel = "Hivemind Abilities" - charge_max = 1800 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "scan" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/self/hive_scan/cast(mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + cooldown_time = 1800 + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_HUMAN + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "scan" + +/datum/action/cooldown/spell/hive_scan/cast(mob/living/owner) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return var/message var/distance - for(var/datum/status_effect/hive_track/track in user.status_effects) + for(var/datum/status_effect/hive_track/track in owner.status_effects) var/mob/living/L = track.tracked_by if(!L) continue - if(!do_after(user, 0.5 SECONDS, user, FALSE)) - to_chat(user, span_notice("Our concentration has been broken!")) + if(!do_after(owner, 0.5 SECONDS, owner, FALSE)) + to_chat(owner, span_notice("Our concentration has been broken!")) break - distance = get_dist(user, L) + distance = get_dist(owner, L) message = "[(L.is_real_hivehost()) ? "Someone": "A hivemind host"] tracking us" - if(user.z != L.z || L.stat == DEAD) + if(owner.z != L.z || L.stat == DEAD) message += " could not be found." else var/multiplier = L.anti_magic_check(FALSE, FALSE, TRUE) ? rand(0.6, 1.4) : 1 @@ -260,19 +267,19 @@ message += " isn't too far away." if(28 to INFINITY) message += " is quite far away." - to_chat(user, span_assimilator("[message]")) + to_chat(owner, span_assimilator("[message]")) for(var/datum/antagonist/hivemind/enemy in hive.individual_track_bonus) - if(!do_after(user, 0.5 SECONDS, user, FALSE)) - to_chat(user, span_notice("Our concentration has been broken!")) + if(!do_after(owner, 0.5 SECONDS, owner, FALSE)) + to_chat(owner, span_notice("Our concentration has been broken!")) break var/mob/living/carbon/C = enemy.owner?.current if(!C) continue var/multiplier = C.anti_magic_check(FALSE, FALSE, TRUE) ? rand(0.6, 1.4) : 1 var/mob/living/real_enemy = C.get_real_hivehost() - distance = get_dist(user, real_enemy) + distance = get_dist(owner, real_enemy) message = "A host that we can track for [(hive.individual_track_bonus[enemy])/10] extra seconds" - if(user.z != real_enemy.z || real_enemy.stat == DEAD) + if(owner.z != real_enemy.z || real_enemy.stat == DEAD) message += " could not be found." else multiplier = real_enemy.anti_magic_check(FALSE, FALSE, TRUE) ? rand(0.6, 1.4) : 1 @@ -288,53 +295,56 @@ message += " isn't too far away." if(28 to INFINITY) message += " is quite far away." - to_chat(user, span_assimilator("[message]")) + to_chat(owner, span_assimilator("[message]")) -/obj/effect/proc_holder/spell/self/hive_drain + return TRUE + +/datum/action/cooldown/spell/hive_drain name = "Repair Protocol" desc = "Our many vessels sacrifice a small portion of their mind's vitality to cure us of our physical and mental ailments." panel = "Hivemind Abilities" - charge_max = 600 - clothes_req = 0 - invocation_type = SPELL_INVOCATION_NONE - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "drain" - human_req = 1 - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/self/hive_drain/cast(mob/living/carbon/human/user) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + cooldown_time = 600 + invocation_type = INVOCATION_NONE + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "drain" + spell_requirements = SPELL_REQUIRES_HUMAN + +/datum/action/cooldown/spell/hive_drain/cast(mob/living/carbon/human/owner) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive || !hive.hivemembers) return var/iterations = 0 var/list/carbon_members = hive.get_carbon_members() if(!carbon_members.len) return - if(!user.getBruteLoss() && !user.getFireLoss() && !user.getCloneLoss() && !user.getOrganLoss(ORGAN_SLOT_BRAIN) && !user.getStaminaLoss()) - to_chat(user, span_notice("We cannot heal ourselves any more with this power!")) - revert_cast() - to_chat(user, span_notice("We begin siphoning power from our many vessels!")) + if(!owner.getBruteLoss() && !owner.getFireLoss() && !owner.getCloneLoss() && !owner.getOrganLoss(ORGAN_SLOT_BRAIN) && !owner.getStaminaLoss()) + to_chat(owner, span_notice("We cannot heal ourselves any more with this power!")) + to_chat(owner, span_notice("We begin siphoning power from our many vessels!")) while(iterations < 7) var/mob/living/carbon/target = pick(carbon_members) - if(!do_after(user, 1 SECONDS, user, FALSE)) - to_chat(user, span_warning("Our concentration has been broken!")) + if(!do_after(owner, 1 SECONDS, owner, FALSE)) + to_chat(owner, span_warning("Our concentration has been broken!")) break if(!target) - to_chat(user, span_warning("We have run out of vessels to drain.")) + to_chat(owner, span_warning("We have run out of vessels to drain.")) break var/regen = target.anti_magic_check(FALSE, FALSE, TRUE) ? 5 : 10 target.adjustOrganLoss(ORGAN_SLOT_BRAIN, regen/2) - if(user.getBruteLoss() > user.getFireLoss()) - user.heal_ordered_damage(regen, list(CLONE, BRUTE, BURN, STAMINA)) + if(owner.getBruteLoss() > owner.getFireLoss()) + owner.heal_ordered_damage(regen, list(CLONE, BRUTE, BURN, STAMINA)) else - user.heal_ordered_damage(regen, list(CLONE, BURN, BRUTE, STAMINA)) - if(!user.getBruteLoss() && !user.getFireLoss() && !user.getCloneLoss() && !user.getStaminaLoss()) //If we don't have any of these, stop looping - to_chat(user, span_warning("We finish our healing")) + owner.heal_ordered_damage(regen, list(CLONE, BURN, BRUTE, STAMINA)) + if(!owner.getBruteLoss() && !owner.getFireLoss() && !owner.getCloneLoss() && !owner.getStaminaLoss()) //If we don't have any of these, stop looping + to_chat(owner, span_warning("We finish our healing")) break iterations++ - user.setOrganLoss(ORGAN_SLOT_BRAIN, 0) + owner.setOrganLoss(ORGAN_SLOT_BRAIN, 0) + return TRUE /mob/living/passenger name = "mind control victim" @@ -354,12 +364,12 @@ /mob/living/passenger/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) return -/obj/effect/proc_holder/spell/target_hive/hive_control +/datum/action/cooldown/spell/aoe/target_hive/hive_control name = "Mind Control" desc = "We assume direct control of one of our vessels, leaving our current body for up to a minute. It can be cancelled at any time by casting it again. Powers can be used via our vessel, although if it dies, the entire hivemind will come down with it. Our ability to sense psionic energy is completely nullified while using this power, and it will end immediately should we attempt to move too far from our starting point." - charge_max = 1200 - action_icon_state = "force" - active = FALSE + cooldown_time = 1200 + button_icon_state = "force" + var/active = FALSE var/mob/living/carbon/human/original_body //The original hivemind host var/mob/living/carbon/human/vessel var/mob/living/passenger/backseat //Storage for the mind controlled vessel @@ -369,11 +379,10 @@ var/out_of_range = FALSE var/restricted_range = FALSE -/obj/effect/proc_holder/spell/target_hive/hive_control/proc/release_control() //If the spell is active, force everybody into their original bodies if they exist, ghost them otherwise, delete the backseat +/datum/action/cooldown/spell/aoe/target_hive/hive_control/proc/release_control() //If the spell is active, force everybody into their original bodies if they exist, ghost them otherwise, delete the backseat if(!active) return active = FALSE - charge_counter = max((0.5-(world.time-time_initialized)/power)*charge_max, 0) //Partially refund the power based on how long it was used, up to a max of half the charge time if(!QDELETED(vessel)) vessel.clear_fullscreen("hive_mc") if(vessel.mind) @@ -389,8 +398,8 @@ backseat.ghostize(0) else backseat.mind.transfer_to(vessel,1) - vessel.visible_message(span_userdanger("[src] suddenly wakes up, as though he was under foreign control!")) - vessel.Jitter(3) + vessel.visible_message(span_ownerdanger("[src] suddenly wakes up, as though he was under foreign control!")) + vessel.adjust_jitter(3 SECONDS) message_admins("[ADMIN_LOOKUPFLW(vessel)] is no longer being controlled by [ADMIN_LOOKUPFLW(original_body)] (Hivemind Host).") log_game("[key_name(vessel)] was released from Mind Control by [key_name(original_body)].") @@ -403,144 +412,119 @@ restricted_range = FALSE - -/obj/effect/proc_holder/spell/target_hive/hive_control/on_lose(mob/user) - release_control() - -/obj/effect/proc_holder/spell/target_hive/hive_control/cast(list/targets, mob/living/user = usr) +/datum/action/cooldown/spell/aoe/target_hive/hive_control/cast_on_thing_in_aoe(atom/victim, atom/caster) if(!active) - vessel = targets[1] - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + vessel = victim + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return - original_body = user - vessel = targets[1] - to_chat(user, span_notice("We begin merging our mind with [vessel.name].")) + original_body = owner + vessel = victim + to_chat(owner, span_notice("We begin merging our mind with [vessel.name].")) var/timely = 50 if(vessel.anti_magic_check(FALSE, FALSE, TRUE)) timely = 100 restricted_range = TRUE - if(!do_after(user, timely, user, FALSE)) - to_chat(user, span_notice("We fail to assume control of the target.")) - revert_cast() + if(!do_after(owner, timely, owner, FALSE)) + to_chat(owner, span_notice("We fail to assume control of the target.")) return - if(user.z != vessel.z || (restricted_range && get_dist(vessel, user) > 35)) - to_chat(user, span_notice("Our vessel is too far away to control.")) - revert_cast() + if(owner.z != vessel.z || (restricted_range && get_dist(vessel, owner) > 35)) + to_chat(owner, span_notice("Our vessel is too far away to control.")) return for(var/datum/antagonist/hivemind/H in GLOB.antagonists) - if(H.owner == user.mind) + if(H.owner == owner.mind) continue if(H.owner == vessel.mind) - to_chat(user, span_danger("We have detected a foreign presence within this mind, it would be unwise to merge so intimately with it.")) - revert_cast() + to_chat(owner, span_danger("We have detected a foreign presence within this mind, it would be unwise to merge so intimately with it.")) return backseat = new /mob/living/passenger() if(vessel && vessel.mind && backseat) - var/obj/effect/proc_holder/spell/target_hive/hive_see/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_see) in user.mind.spell_list - if(the_spell && the_spell.active) //Uncast Hive Sight just to make things easier when casting during mind control - the_spell.perform(,user) - message_admins("[ADMIN_LOOKUPFLW(vessel)] has been temporarily taken over by [ADMIN_LOOKUPFLW(user)] (Hivemind Host).") - log_game("[key_name(vessel)] was Mind Controlled by [key_name(user)].") + message_admins("[ADMIN_LOOKUPFLW(vessel)] has been temporarily taken over by [ADMIN_LOOKUPFLW(owner)] (Hivemind Host).") + log_game("[key_name(vessel)] was Mind Controlled by [key_name(owner)].") deadchat_broadcast(" has just been mind controlled!", span_name("[vessel]"), vessel) - original_body = user + original_body = owner backseat.loc = vessel backseat.name = vessel.real_name backseat.real_name = vessel.real_name vessel.mind.transfer_to(backseat, 1) - user.mind.transfer_to(vessel, 1) + owner.mind.transfer_to(vessel, 1) backseat.blind_eyes(power) vessel.overlay_fullscreen("hive_mc", /atom/movable/screen/fullscreen/hive_mc) active = TRUE out_of_range = FALSE starting_spot = get_turf(vessel) time_initialized = world.time - revert_cast() to_chat(vessel, span_assimilator("We can sustain our control for a maximum of [round(power/10)] seconds.")) - if(do_after(user, power, user, FALSE, FALSE)) + if(do_after(owner, power, owner, FALSE, FALSE)) to_chat(vessel, span_warning("We cannot sustain the mind control any longer and release control!")) else to_chat(vessel, span_warning("Our body has been disturbed, interrupting the mind control!")) release_control() else to_chat(usr, span_warning("We detect no neural activity in our vessel!")) - revert_cast() else release_control() -/obj/effect/proc_holder/spell/target_hive/hive_control/revert_cast() - . = ..() - restricted_range = FALSE - -/obj/effect/proc_holder/spell/target_hive/hive_control/process() - if(active) - if(QDELETED(vessel)) //If we've been gibbed or otherwise deleted, ghost both of them and kill the original - original_body.adjustOrganLoss(ORGAN_SLOT_BRAIN, 200) - release_control() - else if(!is_hivemember(backseat)) //If the vessel is no longer a hive member, return to original bodies - to_chat(vessel, span_warning("Our vessel is one of us no more!")) - release_control() - else if(!QDELETED(original_body) && (!backseat.ckey || vessel.stat == DEAD)) //If the original body exists and the vessel is dead/ghosted, return both to body but not before killing the original - original_body.adjustOrganLoss(ORGAN_SLOT_BRAIN, 200) - to_chat(vessel.mind, span_warning("Our vessel is one of us no more!")) - release_control() - else if(!QDELETED(original_body) && original_body.z != vessel.z) //Return to original bodies - release_control() - to_chat(original_body, span_warning("Our vessel is too far away to control!")) - else if(QDELETED(original_body) || original_body.stat == DEAD) //Return vessel to its body, either return or ghost the original - to_chat(vessel, span_userdanger("Our body has been destroyed, the hive cannot survive without its host!")) +/datum/action/cooldown/spell/aoe/target_hive/hive_control/process() + if(QDELETED(vessel)) //If we've been gibbed or otherwise deleted, ghost both of them and kill the original + original_body.adjustOrganLoss(ORGAN_SLOT_BRAIN, 200) + release_control() + else if(!is_hivemember(backseat)) //If the vessel is no longer a hive member, return to original bodies + to_chat(vessel, span_warning("Our vessel is one of us no more!")) + release_control() + else if(!QDELETED(original_body) && (!backseat.ckey || vessel.stat == DEAD)) //If the original body exists and the vessel is dead/ghosted, return both to body but not before killing the original + original_body.adjustOrganLoss(ORGAN_SLOT_BRAIN, 200) + to_chat(vessel.mind, span_warning("Our vessel is one of us no more!")) + release_control() + else if(!QDELETED(original_body) && original_body.z != vessel.z) //Return to original bodies + release_control() + to_chat(original_body, span_warning("Our vessel is too far away to control!")) + else if(QDELETED(original_body) || original_body.stat == DEAD) //Return vessel to its body, either return or ghost the original + to_chat(vessel, span_ownerdanger("Our body has been destroyed, the hive cannot survive without its host!")) + release_control() + else + var/multiplier = restricted_range ? 0.5 : 1 + if(!out_of_range && get_dist(starting_spot, vessel) > 14*multiplier) + out_of_range = TRUE + flash_color(vessel, flash_color="#800080", flash_time=10) + to_chat(vessel, span_warning("Our vessel has been moved too far away from the initial point of control, we will be disconnected if we go much further!")) + addtimer(CALLBACK(src, PROC_REF(range_check), multiplier), 30) + else if(get_dist(starting_spot, vessel) > 21*multiplier) release_control() - else - var/multiplier = restricted_range ? 0.5 : 1 - if(!out_of_range && get_dist(starting_spot, vessel) > 14*multiplier) - out_of_range = TRUE - flash_color(vessel, flash_color="#800080", flash_time=10) - to_chat(vessel, span_warning("Our vessel has been moved too far away from the initial point of control, we will be disconnected if we go much further!")) - addtimer(CALLBACK(src, PROC_REF(range_check), multiplier), 30) - else if(get_dist(starting_spot, vessel) > 21*multiplier) - release_control() ..() -/obj/effect/proc_holder/spell/target_hive/hive_control/proc/range_check(multiplier = 1) - if(!active) - return +/datum/action/cooldown/spell/aoe/target_hive/hive_control/proc/range_check(multiplier = 1) if(get_dist(starting_spot, vessel) > 14 * multiplier) release_control() out_of_range = FALSE -/obj/effect/proc_holder/spell/target_hive/hive_control/choose_targets(mob/user = usr) - if(!active) - ..() - else - perform(,user) - -/obj/effect/proc_holder/spell/targeted/induce_panic +/datum/action/cooldown/spell/pointed/induce_panic name = "Induce Panic" desc = "We unleash a burst of psionic energy, inducing a debilitating fear in those around us and reducing their combat readiness. We can also briefly affect silicon-based life with this burst." panel = "Hivemind Abilities" - charge_max = 600 - range = 7 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - max_targets = 0 - antimagic_allowed = TRUE - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "panic" - -/obj/effect/proc_holder/spell/targeted/induce_panic/cast(list/targets, mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + cooldown_time = 600 + cast_range = 7 + invocation_type = INVOCATION_NONE + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "panic" + +/datum/action/cooldown/spell/pointed/induce_panic/cast(list/targets, mob/living/owner = usr) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return for(var/mob/living/carbon/human/target in targets) if(target.stat == DEAD) continue - target.Jitter(14) + target.adjust_jitter(14 SECONDS) target.apply_damage(35 + rand(0,15), STAMINA, target.get_bodypart(BODY_ZONE_HEAD)) if(target.is_real_hivehost() || target.anti_magic_check(FALSE, FALSE, TRUE)) continue @@ -550,47 +534,48 @@ var/effect = rand(1,4) switch(effect) if(1) - to_chat(target, span_userdanger("You panic and drop everything to the ground!")) + to_chat(target, span_ownerdanger("You panic and drop everything to the ground!")) target.drop_all_held_items() if(2) - to_chat(target, span_userdanger("You panic and flail around!")) + to_chat(target, span_ownerdanger("You panic and flail around!")) target.click_random_mob() - addtimer(CALLBACK(target, "click_random_mob"), 5) - addtimer(CALLBACK(target, "click_random_mob"), 10) - addtimer(CALLBACK(target, "click_random_mob"), 15) - addtimer(CALLBACK(target, "click_random_mob"), 20) - addtimer(CALLBACK(target, "Stun", 30), 25) - target.confused += 10 + addtimer(CALLBACK(target, "click_random_mob"), 0.5 SECONDS) + addtimer(CALLBACK(target, "click_random_mob"), 1 SECONDS) + addtimer(CALLBACK(target, "click_random_mob"), 1.5 SECONDS) + addtimer(CALLBACK(target, "click_random_mob"), 2 SECONDS) + addtimer(CALLBACK(target, "Stun", 3 SECONDS), 2.5 SECONDS) + target.adjust_confusion(10 SECONDS) if(3) - to_chat(target, span_userdanger("You freeze up in fear!")) - target.Stun(70) + to_chat(target, span_ownerdanger("You freeze up in fear!")) + target.Stun(7 SECONDS) if(4) - to_chat(target, span_userdanger("You feel nauseous as dread washes over you!")) - target.Dizzy(15) + to_chat(target, span_ownerdanger("You feel nauseous as dread washes over you!")) + target.adjust_dizzy(15 SECONDS) target.apply_damage(30, STAMINA, target.get_bodypart(BODY_ZONE_HEAD)) - target.hallucination += 45 + target.adjust_hallucinations(45 SECONDS) for(var/mob/living/silicon/target in targets) target.Unconscious(50) -/obj/effect/proc_holder/spell/targeted/pin + return TRUE + +/datum/action/cooldown/spell/pointed/pin name = "Psychic Pin" desc = "We send out a controlled pulse of psionic energy, pinning everyone in sight, and knocking out silicon-based lifeforms. This is weaker against enemy hiveminds." panel = "Hivemind Abilities" - charge_max = 600 - range = 7 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - max_targets = 0 - antimagic_allowed = TRUE - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "pin" - -/obj/effect/proc_holder/spell/targeted/pin/cast(list/targets, mob/living/user = usr) + cooldown_time = 600 + cast_range = 7 + invocation_type = INVOCATION_NONE + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "pin" + +/datum/action/cooldown/spell/pointed/pin/cast(list/targets, mob/living/owner = usr) + . = ..() + if(!.) + return FALSE if(!targets) - to_chat(user, span_notice("Nobody is in sight, it'd be a waste to do that now.")) - revert_cast() + to_chat(owner, span_notice("Nobody is in sight, it'd be a waste to do that now.")) return var/list/victims = list() for(var/mob/living/target in targets) @@ -605,37 +590,36 @@ victim.Knockdown(statustime/4) else victim.Knockdown(statustime) - to_chat(victim, span_userdanger("A sudden force throws you to the ground!")) + to_chat(victim, span_ownerdanger("A sudden force throws you to the ground!")) for(var/mob/living/silicon/victim in victims) victim.Unconscious(statustime) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(victims.len && hive) hive.threat_level += 1 -/obj/effect/proc_holder/spell/target_hive/nightmare +/datum/action/cooldown/spell/aoe/target_hive/nightmare name = "Living nightmares" desc = "The target's fears break out and attack them, obscuring their vision and clawing at them." - range = 7 - charge_max = 1800 - action_icon_state = "nightmare" - -/obj/effect/proc_holder/spell/target_hive/nightmare/cast(list/targets, mob/living/user = usr) - var/mob/living/carbon/target = targets[1] - if(!do_after(user, 3 SECONDS, user, FALSE)) - to_chat(user, span_notice("Our concentration has been broken!")) - revert_cast() + aoe_radius = 7 + cooldown_time = 1800 + button_icon_state = "nightmare" + +/datum/action/cooldown/spell/aoe/target_hive/nightmare/cast_on_thing_in_aoe(atom/victim, atom/caster) + var/mob/living/carbon/target = victim + if(!do_after(owner, 3 SECONDS, owner, FALSE)) + to_chat(owner, span_notice("Our concentration has been broken!")) return - to_chat(target, span_userdanger("You see dark smoke swirling around you!")) + to_chat(target, span_ownerdanger("You see dark smoke swirling around you!")) if(target.anti_magic_check(FALSE, FALSE, TRUE)) - to_chat(user, span_notice("We begin bruteforcing the tinfoil barriers of [target.name] and pulling out their nightmares.")) - if(!do_after(user, 3 SECONDS, user, FALSE) || !(target in view(range))) - to_chat(user, span_notice("Our concentration has been broken!")) + to_chat(owner, span_notice("We begin bruteforcing the tinfoil barriers of [target.name] and pulling out their nightmares.")) + if(!do_after(owner, 3 SECONDS, owner, FALSE) || !(target in view(aoe_radius))) + to_chat(owner, span_notice("Our concentration has been broken!")) return target.apply_status_effect(STATUS_EFFECT_HIVEMIND_CURSE, CURSE_SPAWNING | CURSE_BLINDING) - to_chat(user, span_notice("We have brought forth the targets nightmares!")) + to_chat(owner, span_notice("We have brought forth the targets nightmares!")) deadchat_broadcast(" is suffering corporial nightmares!", span_name("[target]"), target) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(hive) hive.threat_level += 3 @@ -652,73 +636,72 @@ min_reach = -1 item_flags = ABSTRACT | DROPDEL -/obj/effect/proc_holder/spell/self/telekinetic_hand +/datum/action/cooldown/spell/telekinetic_hand name = "Telekinetic hand" desc = "Makes a telekinetic hand to extend the reach of our unarmed combat. Drop to remove." - clothes_req = 0 - invocation_type = SPELL_INVOCATION_NONE - antimagic_allowed = TRUE - charge_max = 200 + invocation_type = INVOCATION_NONE + cooldown_time = 200 panel = "Hivemind Abilities" - action_background_icon_state = "bg_hive" - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_icon_state = "hivehand" + background_icon_state = "bg_hive" + button_icon = 'icons/mob/actions/actions_hive.dmi' + button_icon_state = "hivehand" var/spell_item = /obj/item/extendohand/hivemind -/obj/effect/proc_holder/spell/self/telekinetic_hand/cast(mob/user = usr) - if(user.get_active_held_item()==null) +/datum/action/cooldown/spell/telekinetic_hand/cast(mob/owner = usr) + . = ..() + if(!.) + return FALSE + if(owner.get_active_held_item()==null) var/obj/item/W = new spell_item - user.put_in_hands(W) - to_chat(user, span_notice("You make a telekinetic hand!")) + owner.put_in_hands(W) + to_chat(owner, span_notice("You make a telekinetic hand!")) else - to_chat(user,span_notice("You cannot make a telekinetic hand while holding something!")) - revert_cast() + to_chat(owner,span_notice("You cannot make a telekinetic hand while holding something!")) -/obj/effect/proc_holder/spell/targeted/hive_hack +/datum/action/cooldown/spell/pointed/hive_hack name = "Network Invasion" desc = "We probe the mind of an adjacent target and extract valuable information on any enemy hives they may belong to. Takes longer if the target is not in our hive or wearing tinfoil protection." panel = "Hivemind Abilities" - charge_max = 600 - range = 1 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - max_targets = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "hack" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/targeted/hive_hack/cast(list/targets, mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + cooldown_time = 600 + cast_range = 1 + invocation_type = INVOCATION_NONE + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "hack" + +/datum/action/cooldown/spell/pointed/hive_hack/cast(list/targets, mob/living/owner) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return var/mob/living/carbon/target = targets[1] var/in_hive = hive.is_carbon_member(target) var/list/enemies = list() - to_chat(user, span_notice("We begin probing [target.name]'s mind!")) - if(do_after(user, 10 SECONDS, target, FALSE)) + to_chat(owner, span_notice("We begin probing [target.name]'s mind!")) + if(do_after(owner, 10 SECONDS, target, FALSE)) var/foiled = target.anti_magic_check(FALSE, FALSE, TRUE) if(!in_hive || foiled) var/timely = !in_hive ? 200 : 100 - to_chat(user, span_notice("Their mind slowly opens up to us.")) - if(!do_after(user, timely, target, FALSE)) - to_chat(user, span_notice("Our concentration has been broken!")) - revert_cast() + to_chat(owner, span_notice("Their mind slowly opens up to us.")) + if(!do_after(owner, timely, target, FALSE)) + to_chat(owner, span_notice("Our concentration has been broken!")) return for(var/datum/antagonist/hivemind/enemy in GLOB.antagonists) var/datum/mind/M = enemy.owner if(!M?.current) continue - if(M.current == user) + if(M.current == owner) continue if(enemy.is_carbon_member(target)) hive.add_track_bonus(enemy, TRACKER_BONUS_LARGE) var/mob/living/real_enemy = (M.current.get_real_hivehost()) enemies += real_enemy enemy.remove_from_hive(target) - real_enemy.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, user, hive.get_track_bonus(enemy)) + real_enemy.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, owner, hive.get_track_bonus(enemy)) if(M.current.is_real_hivehost()) //If they were using mind control, too bad real_enemy.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) target.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_enemy, enemy.get_track_bonus(hive)) @@ -726,43 +709,42 @@ if(enemy.owner == M && target.is_real_hivehost()) var/atom/throwtarget - throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(user, src))) - SEND_SOUND(user, sound(pick('sound/hallucinations/turn_around1.ogg','sound/hallucinations/turn_around2.ogg'),0,1,50)) - flash_color(user, flash_color="#800080", flash_time=10) - user.Paralyze(10) - user.throw_at(throwtarget, 5, 1,src) - to_chat(user, span_userdanger("A sudden surge of psionic energy rushes into your mind, only a Hive host could have such power!!")) + throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(owner, src))) + SEND_SOUND(owner, sound(pick('sound/hallucinations/turn_around1.ogg','sound/hallucinations/turn_around2.ogg'),0,1,50)) + flash_color(owner, flash_color="#800080", flash_time=10) + owner.Paralyze(10) + owner.throw_at(throwtarget, 5, 1,src) + to_chat(owner, span_ownerdanger("A sudden surge of psionic energy rushes into your mind, only a Hive host could have such power!!")) return if(enemies.len) hive.track_bonus += TRACKER_BONUS_SMALL - to_chat(user, span_userdanger("In a moment of clarity, we see all. Another hive. Faces. Our nemesis. They have heard our call. They know we are coming.")) - to_chat(user, span_assimilator("This vision has provided us insight on our very nature, improving our sensory abilities, particularly against the hives this vessel belonged to.")) - user.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) + to_chat(owner, span_ownerdanger("In a moment of clarity, we see all. Another hive. Faces. Our nemesis. They have heard our call. They know we are coming.")) + to_chat(owner, span_assimilator("This vision has provided us insight on our very nature, improving our sensory abilities, particularly against the hives this vessel belonged to.")) + owner.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) else - to_chat(user, span_notice("We peer into the inner depths of their mind and see nothing, no enemies lurk inside this mind.")) + to_chat(owner, span_notice("We peer into the inner depths of their mind and see nothing, no enemies lurk inside this mind.")) else - to_chat(user, span_notice("Our concentration has been broken!")) - revert_cast() + to_chat(owner, span_notice("Our concentration has been broken!")) -/obj/effect/proc_holder/spell/targeted/hive_reclaim +/datum/action/cooldown/spell/pointed/hive_reclaim name = "Reclaim" desc = "Allows us to instantly syphon the psionic energy from an adjacent critically injured host, killing them immediately. If it succeeds, we will be able to advance our own powers a great deal." panel = "Hivemind Abilities" - charge_max = 600 - range = 1 - max_targets = 0 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "reclaim" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/targeted/hive_reclaim/cast(list/targets, mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + cooldown_time = 600 + cast_range = 1 + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_HUMAN + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "reclaim" + +/datum/action/cooldown/spell/pointed/hive_reclaim/cast(list/targets, mob/living/owner = usr) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return var/found_target = FALSE var/gibbed = FALSE @@ -782,33 +764,32 @@ found_target = TRUE if(!found_target) - revert_cast() return - flash_color(user, flash_color="#800080", flash_time=10) + flash_color(owner, flash_color="#800080", flash_time=10) if(gibbed) - to_chat(user,span_assimilator("We have reclaimed what gifts weaker minds were squandering and gain ever more insight on our psionic abilities.")) - to_chat(user,span_assimilator("Thanks to this new knowledge, our sensory powers last a great deal longer.")) + to_chat(owner,span_assimilator("We have reclaimed what gifts weaker minds were squandering and gain ever more insight on our psionic abilities.")) + to_chat(owner,span_assimilator("Thanks to this new knowledge, our sensory powers last a great deal longer.")) hive.check_powers() -/obj/effect/proc_holder/spell/self/hive_wake +/datum/action/cooldown/spell/hive_wake name = "Chaos Induction" desc = "A one-use power, we awaken four random vessels within our hive and force them to do our bidding." panel = "Hivemind Abilities" - charge_type = SPELL_CHARGE_TYPE_CHARGES - charge_max = 1 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "chaos" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/self/hive_wake/cast(mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + cooldown_time = 1 + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_HUMAN + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "chaos" + +/datum/action/cooldown/spell/hive_wake/cast(mob/living/owner) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return if(!hive.hivemembers) return @@ -820,14 +801,12 @@ valid_targets += C if(!valid_targets || valid_targets.len < 4) - to_chat(user, span_assimilator("We lack the vessels to use this power.")) - revert_cast() + to_chat(owner, span_assimilator("We lack the vessels to use this power.")) return - var/objective = stripped_input(user, "What objective do you want to give to your vessels?", "Objective") + var/objective = stripped_input(owner, "What objective do you want to give to your vessels?", "Objective") if(!objective || !hive) - revert_cast() return hive.threat_level += 6 @@ -835,63 +814,71 @@ var/mob/living/carbon/C = pick_n_take(valid_targets) C.hive_awaken(objective) -/obj/effect/proc_holder/spell/self/hive_loyal + return TRUE + +/datum/action/cooldown/spell/hive_loyal name = "Bruteforce" desc = "Our ability to assimilate is boosted at the cost of, allowing us to crush the technology shielding the minds of savyy personnel and assimilate them. This power comes at a small price, and we will be immobilized for a few seconds after assimilation." panel = "Hivemind Abilities" - charge_max = 600 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "loyal" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/self/hive_loyal/cast(mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "loyal" + + invocation_type = INVOCATION_NONE + + cooldown_time = 1 MINUTES + spell_requirements = SPELL_REQUIRES_HUMAN + var/active = FALSE + +/datum/action/cooldown/spell/hive_loyal/cast(mob/living/owner) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return - var/obj/effect/proc_holder/spell/target_hive/hive_add/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_add) in user.mind.spell_list + var/datum/action/cooldown/spell/aoe/target_hive/hive_add/the_spell = locate(/datum/action/cooldown/spell/aoe/target_hive/hive_add) in owner.actions if(!the_spell) - to_chat(user, span_notice("This is a bug. Error:HIVE5")) + to_chat(owner, span_notice("This is a bug. Error:HIVE5")) return the_spell.bruteforce = !active - to_chat(user, span_notice("We [active?"let our minds rest and cancel our crushing power.":"prepare to crush mindshielding technology!"]")) + to_chat(owner, span_notice("We [active?"let our minds rest and cancel our crushing power.":"prepare to crush mindshielding technology!"]")) active = !active - if(active) - revert_cast() -/obj/effect/proc_holder/spell/targeted/forcewall/hive + return TRUE + +/datum/action/cooldown/spell/forcewall/hive name = "Telekinetic Field" desc = "Our psionic powers form a barrier around us in the phsyical world that only we can pass through." panel = "Hivemind Abilities" - charge_max = 600 - clothes_req = 0 - human_req = 1 - invocation_type = SPELL_INVOCATION_NONE - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "forcewall" - range = -1 - include_user = 1 - antimagic_allowed = TRUE + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "forcewall" + + invocation_type = INVOCATION_NONE + wall_type = /obj/effect/forcefield/wizard/hive + cooldown_time = 1 MINUTES + spell_requirements = SPELL_REQUIRES_HUMAN var/wall_type_b = /obj/effect/forcefield/wizard/hive/invis -/obj/effect/proc_holder/spell/targeted/forcewall/hive/cast(list/targets,mob/user = usr) - new wall_type(get_turf(user),user) +/datum/action/cooldown/spell/forcewall/hive/cast(list/targets,mob/owner = usr) + . = ..() + if(!.) + return FALSE + new wall_type(get_turf(owner),owner) for(var/dir in GLOB.alldirs) - new wall_type_b(get_step(user, dir),user) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + new wall_type_b(get_step(owner, dir),owner) + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(hive) hive.threat_level += 0.5 + return TRUE + /obj/effect/forcefield/wizard/hive name = "Telekinetic Field" desc = "You think, therefore it is." - timeleft = 300 pixel_x = -32 //Centres the 96x96 sprite pixel_y = -32 icon = 'icons/effects/96x96.dmi' @@ -901,7 +888,7 @@ /obj/effect/forcefield/wizard/hive/CanPass(atom/movable/mover, turf/target) SHOULD_CALL_PARENT(FALSE) - if(mover == wizard) + if(IS_WEAKREF_OF(mover, caster_weakref)) return TRUE return FALSE @@ -912,30 +899,32 @@ pixel_y = 0 invisibility = INVISIBILITY_MAXIMUM -/obj/effect/proc_holder/spell/self/one_mind +/datum/action/cooldown/spell/one_mind name = "One Mind" desc = "Our true power... finally within reach." panel = "Hivemind Abilities" - charge_type = SPELL_CHARGE_TYPE_CHARGES - charge_max = 1 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "assim" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/self/one_mind/cast(mob/living/user = usr) - var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "assim" + + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_HUMAN + + cooldown_time = 0.1 SECONDS + +/datum/action/cooldown/spell/one_mind/cast(mob/living/owner) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) if(!hive) - to_chat(user, span_notice("This is a bug. Error:HIVE1")) + to_chat(owner, span_notice("This is a bug. Error:HIVE1")) return - var/mob/living/boss = user.get_real_hivehost() + var/mob/living/boss = owner.get_real_hivehost() var/datum/objective/protect/new_objective = new /datum/objective/protect - new_objective.target = user.mind + new_objective.target = owner.mind new_objective.explanation_text = "Ensure the One Mind survives under the leadership of [boss.real_name]." - var/datum/team/hivemind/one_mind_team = new /datum/team/hivemind(user.mind) + var/datum/team/hivemind/one_mind_team = new /datum/team/hivemind(owner.mind) hive.active_one_mind = one_mind_team one_mind_team.objectives += new_objective for(var/datum/antagonist/hivevessel/vessel in GLOB.antagonists) @@ -944,13 +933,14 @@ vessel.one_mind = one_mind_team for(var/datum/antagonist/hivemind/enemy in GLOB.antagonists) if(enemy.owner) - enemy.owner.RemoveSpell(new/obj/effect/proc_holder/spell/self/one_mind) + for(var/datum/action/cooldown/spell/one_mind/one_mind in enemy.owner.current.actions) + one_mind.Remove(enemy.owner.current) sound_to_playing_players('sound/effects/one_mind.ogg') hive.glow = mutable_appearance('icons/effects/hivemind.dmi', "awoken", -BODY_BEHIND_LAYER) - addtimer(CALLBACK(user, /atom/proc/add_overlay, hive.glow), 150) - addtimer(CALLBACK(hive, /datum/antagonist/hivemind/proc/awaken), 150) - addtimer(CALLBACK(GLOBAL_PROC, /proc/send_to_playing_players, span_bigassimilator("THE ONE MIND RISES")), 150) - addtimer(CALLBACK(GLOBAL_PROC, /proc/sound_to_playing_players, 'sound/effects/magic.ogg'), 150) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/atom, add_overlay), hive.glow), 150) + addtimer(CALLBACK(hive, TYPE_PROC_REF(/datum/antagonist/hivemind, awaken)), 150) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(send_to_playing_players), span_bigassimilator("THE ONE MIND RISES")), 150) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(sound_to_playing_players), 'sound/effects/magic.ogg'), 150) for(var/datum/mind/M in hive.hivemembers) var/mob/living/carbon/C = M.current if(!C) @@ -959,7 +949,7 @@ continue if(C.stat == DEAD) continue - C.Jitter(15) + C.adjust_jitter(15 SECONDS) C.Unconscious(150) C.anti_magic_check(FALSE, FALSE, TRUE, 6) to_chat(C, span_boldwarning("Something's wrong...")) @@ -969,34 +959,37 @@ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, span_bigassimilator("...there is only us.")), 130) addtimer(CALLBACK(C, /mob/living/proc/hive_awaken, new_objective, one_mind_team), 150) -/obj/effect/proc_holder/spell/self/hive_comms + return TRUE + +/datum/action/cooldown/spell/hive_comms name = "Hive Communication" desc = "Now that we are free we may finally share our thoughts with our many bretheren." panel = "Hivemind Abilities" - charge_max = 100 - invocation_type = SPELL_INVOCATION_NONE - clothes_req = 0 - human_req = 1 - action_icon = 'icons/mob/actions/actions_hive.dmi' - action_background_icon_state = "bg_hive" - action_icon_state = "comms" - antimagic_allowed = TRUE - -/obj/effect/proc_holder/spell/self/hive_comms/cast(mob/living/user = usr) - var/message = stripped_input(user, "What do you want to say?", "Hive Communication") + cooldown_time = 100 + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_HUMAN + button_icon = 'icons/mob/actions/actions_hive.dmi' + background_icon_state = "bg_hive" + button_icon_state = "comms" + +/datum/action/cooldown/spell/hive_comms/cast(mob/living/owner = usr) + . = ..() + if(!.) + return FALSE + var/message = stripped_input(owner, "What do you want to say?", "Hive Communication") if(!message) return var/title = "One Mind" var/span = "changeling" - if(user.mind && user.mind.has_antag_datum(/datum/antagonist/hivemind)) + if(owner.mind && owner.mind.has_antag_datum(/datum/antagonist/hivemind)) span = "assimilator" - var/my_message = "[title] [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]: [message]" + var/my_message = "[title] [findtextEx(owner.name, owner.real_name) ? owner.name : "[owner.real_name] (as [owner.name])"]: [message]" for(var/i in GLOB.player_list) var/mob/M = i if(is_hivehost(M) || is_hivemember(M)) to_chat(M, my_message) else if(M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, user) + var/link = FOLLOW_LINK(M, owner) to_chat(M, "[link] [my_message]") - user.log_talk(message, LOG_SAY, tag="hive") + owner.log_talk(message, LOG_SAY, tag="hive") diff --git a/code/modules/spells/spell_types/infinite_guns.dm b/code/modules/spells/spell_types/infinite_guns.dm deleted file mode 100644 index e07b075a48ba..000000000000 --- a/code/modules/spells/spell_types/infinite_guns.dm +++ /dev/null @@ -1,29 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/infinite_guns - name = "Lesser Summon Guns" - desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage." - invocation_type = SPELL_INVOCATION_NONE - include_user = TRUE - range = -1 - - school = "conjuration" - charge_max = 750 - clothes_req = TRUE - cooldown_min = 10 //Gun wizard - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "bolt_action" - var/summon_path = /obj/item/gun/ballistic/rifle/boltaction/enchanted - -/obj/effect/proc_holder/spell/targeted/infinite_guns/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/C in targets) - C.drop_all_held_items() - var/GUN = new summon_path - C.put_in_hands(GUN) - -/obj/effect/proc_holder/spell/targeted/infinite_guns/gun - -/obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - name = "Arcane Barrage" - desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Lesser Summon Gun." - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "arcane_barrage" - summon_path = /obj/item/gun/ballistic/rifle/boltaction/enchanted/arcane_barrage diff --git a/code/modules/spells/spell_types/inflict_handler.dm b/code/modules/spells/spell_types/inflict_handler.dm deleted file mode 100644 index f590d3b72a07..000000000000 --- a/code/modules/spells/spell_types/inflict_handler.dm +++ /dev/null @@ -1,67 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/inflict_handler - name = "Inflict Handler" - desc = "This spell blinds and/or destroys/damages/heals/ignites and/or knockdowns/stuns the target." - - var/amt_paralyze = 0 - var/amt_unconscious = 0 - var/amt_stun = 0 - - var/inflict_status - var/list/status_params = list() - - //set to negatives for healing - var/amt_dam_fire = 0 - var/amt_dam_brute = 0 - var/amt_dam_oxy = 0 - var/amt_dam_tox = 0 - - var/amt_eye_blind = 0 - var/amt_eye_blurry = 0 - - var/amt_firestacks = 0 - var/ignites = FALSE - - var/destroys = "none" //can be "none", "gib" or "disintegrate" - - var/summon_type = null //this will put an obj at the target's location - - var/check_anti_magic = TRUE - var/check_holy = FALSE - -/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets,mob/user = usr) - for(var/mob/living/target in targets) - playsound(target,sound, 50,1) - if(target.anti_magic_check(check_anti_magic, check_holy)) - return - switch(destroys) - if("gib") - target.gib() - if("disintegrate") - target.dust() - - if(!target) - continue - //damage/healing - target.adjustBruteLoss(amt_dam_brute) - target.adjustFireLoss(amt_dam_fire) - target.adjustToxLoss(amt_dam_tox) - target.adjustOxyLoss(amt_dam_oxy) - //disabling - target.Paralyze(amt_paralyze) - target.Unconscious(amt_unconscious) - target.Stun(amt_stun) - - target.blind_eyes(amt_eye_blind) - target.blur_eyes(amt_eye_blurry) - target.adjust_fire_stacks(amt_firestacks) - //summoning - if(summon_type) - new summon_type(target.loc, target) - - if(inflict_status) - var/list/stat_args = status_params.Copy() - stat_args.Insert(1,inflict_status) - target.apply_status_effect(arglist(stat_args)) - - if(ignites) - target.IgniteMob() diff --git a/code/modules/spells/spell_types/jaunt/_jaunt.dm b/code/modules/spells/spell_types/jaunt/_jaunt.dm new file mode 100644 index 000000000000..08cfd145d301 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/_jaunt.dm @@ -0,0 +1,110 @@ +/** + * ## Jaunt spells + * + * A basic subtype for jaunt related spells. + * Jaunt spells put their caster in a dummy + * phased_mob effect that allows them to float + * around incorporeally. + * + * Doesn't actually implement any behavior on cast to + * enter or exit the jaunt - that must be done via subtypes. + * + * Use enter_jaunt() and exit_jaunt() as wrappers. + */ +/datum/action/cooldown/spell/jaunt + school = SCHOOL_TRANSMUTATION + + invocation_type = INVOCATION_NONE + + /// What dummy mob type do we put jaunters in on jaunt? + var/jaunt_type = /obj/effect/dummy/phased_mob + +/datum/action/cooldown/spell/jaunt/before_cast(atom/cast_on) + return ..() | SPELL_NO_FEEDBACK // Don't do the feedback until after we're jaunting + +/datum/action/cooldown/spell/jaunt/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + var/area/owner_area = get_area(owner) + var/turf/owner_turf = get_turf(owner) + if(!owner_area || !owner_turf) + return FALSE // nullspaced? + + if(owner_area.area_flags & NOTELEPORT || owner_area.noteleport) // ihate this but i'm kinda tired so you deal with it + if(feedback) + to_chat(owner, span_danger("Some dull, universal force is stopping you from jaunting here.")) + return FALSE + + return isliving(owner) + + +/** + * Places the [jaunter] in a jaunt holder mob + * If [loc_override] is supplied, + * the jaunt will be moved to that turf to start at + * + * Returns the holder mob that was created + */ +/datum/action/cooldown/spell/jaunt/proc/enter_jaunt(mob/living/jaunter, turf/loc_override) + SHOULD_CALL_PARENT(TRUE) + + var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(loc_override || get_turf(jaunter), jaunter) + RegisterSignal(jaunt, COMSIG_MOB_EJECTED_FROM_JAUNT, PROC_REF(on_jaunt_exited)) + spell_requirements |= SPELL_CASTABLE_WHILE_PHASED + ADD_TRAIT(jaunter, TRAIT_MAGICALLY_PHASED, REF(src)) + // Don't do the feedback until we have runechat hidden. + // Otherwise the text will follow the jaunt holder, which reveals where our caster is travelling. + spell_feedback() + + // This needs to happen at the end, after all the traits and stuff is handled + SEND_SIGNAL(jaunter, COMSIG_MOB_ENTER_JAUNT, src, jaunt) + return jaunt + +/** + * Ejects the [unjaunter] from jaunt + * The jaunt object in turn should call on_jaunt_exited + * If [loc_override] is supplied, + * the jaunt will be moved to that turf + * before ejecting the unjaunter + * + * Returns TRUE on successful exit, FALSE otherwise + */ +/datum/action/cooldown/spell/jaunt/proc/exit_jaunt(mob/living/unjaunter, turf/loc_override) + SHOULD_CALL_PARENT(TRUE) + + var/obj/effect/dummy/phased_mob/jaunt = unjaunter.loc + if(!istype(jaunt)) + return FALSE + + if(jaunt.jaunter != unjaunter) + CRASH("Jaunt spell attempted to exit_jaunt with an invalid unjaunter, somehow.") + + if(loc_override) + jaunt.forceMove(loc_override) + jaunt.eject_jaunter() + return TRUE + + +/** + * Called when a mob is ejected from the jaunt holder and goes back to normal. + * This is called both fom exit_jaunt() but also if the caster is ejected involuntarily for some reason. + * Use this to clear state data applied when jaunting, such as the trait TRAIT_MAGICALLY_PHASED. + * Arguments + * * jaunt - The mob holder effect the caster has just exited + * * unjaunter - The spellcaster who is no longer jaunting + */ +/datum/action/cooldown/spell/jaunt/proc/on_jaunt_exited(obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter) + SHOULD_CALL_PARENT(TRUE) + spell_requirements &= ~SPELL_CASTABLE_WHILE_PHASED + REMOVE_TRAIT(unjaunter, TRAIT_MAGICALLY_PHASED, REF(src)) + // This needs to happen at the end, after all the traits and stuff is handled + SEND_SIGNAL(unjaunter, COMSIG_MOB_AFTER_EXIT_JAUNT, src) + +/datum/action/cooldown/spell/jaunt/Remove(mob/living/remove_from) + exit_jaunt(remove_from) + if (!is_jaunting(remove_from)) // In case you have made exit_jaunt conditional, as in mirror walk + return ..() + var/obj/effect/dummy/phased_mob/jaunt = remove_from.loc + jaunt.eject_jaunter() + return ..() diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm new file mode 100644 index 000000000000..6c964f9450fb --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -0,0 +1,336 @@ +/** + * ### Blood Crawl + * + * Lets the caster enter and exit pools of blood. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl + name = "Blood Crawl" + desc = "Allows you to phase in and out of existance via pools of blood." + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "bloodcrawl" + + spell_requirements = NONE + + /// The time it takes to enter blood + var/enter_blood_time = 0 SECONDS + /// The time it takes to exit blood + var/exit_blood_time = 2 SECONDS + /// The radius around us that we look for blood in + var/blood_radius = 1 + /// If TRUE, we equip "blood crawl" hands to the jaunter to prevent using items + var/equip_blood_hands = TRUE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/Grant(mob/grant_to) + . = ..() + RegisterSignal(grant_to, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) + +/datum/action/cooldown/spell/jaunt/bloodcrawl/Remove(mob/remove_from) + . = ..() + UnregisterSignal(remove_from, COMSIG_MOVABLE_MOVED) + +/datum/action/cooldown/spell/jaunt/bloodcrawl/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(find_nearby_blood(get_turf(owner))) + return TRUE + if(feedback) + to_chat(owner, span_warning("There must be a nearby source of blood!")) + return FALSE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/cast(mob/living/cast_on) + . = ..() + // Should always return something because we checked that in can_cast_spell before arriving here + var/obj/effect/decal/cleanable/blood_nearby = find_nearby_blood(get_turf(cast_on)) + do_bloodcrawl(blood_nearby, cast_on) + +/// Returns a nearby blood decal, or null if there aren't any +/datum/action/cooldown/spell/jaunt/bloodcrawl/proc/find_nearby_blood(turf/origin) + for(var/obj/effect/decal/cleanable/blood_nearby in range(blood_radius, origin)) + if(blood_nearby.can_bloodcrawl_in()) + return blood_nearby + return null + +/** + * Attempts to enter or exit the passed blood pool. + * Returns TRUE if we successfully entered or exited said pool, FALSE otherwise + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/proc/do_bloodcrawl(obj/effect/decal/cleanable/blood, mob/living/jaunter) + if(is_jaunting(jaunter)) + . = try_exit_jaunt(blood, jaunter) + else + . = try_enter_jaunt(blood, jaunter) + + if(!.) + reset_spell_cooldown() + to_chat(jaunter, span_warning("You are unable to blood crawl!")) + +/** + * Attempts to enter the passed blood pool. + * If forced is TRUE, it will override enter_blood_time. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/proc/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced = FALSE) + if(!forced) + if(enter_blood_time > 0 SECONDS) + blood.visible_message(span_warning("[jaunter] starts to sink into [blood]!")) + if(!do_after(jaunter, enter_blood_time, target = blood)) + return FALSE + + // The actual turf we enter + var/turf/jaunt_turf = get_turf(blood) + + // Begin the jaunt + jaunter.notransform = TRUE + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(jaunter, jaunt_turf) + if(!holder) + jaunter.notransform = FALSE + return FALSE + + RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) + if(equip_blood_hands && iscarbon(jaunter)) + jaunter.drop_all_held_items() + // Give them some bloody hands to prevent them from doing things + var/obj/item/bloodcrawl/left_hand = new(jaunter) + var/obj/item/bloodcrawl/right_hand = new(jaunter) + left_hand.icon_state = "bloodhand_right" // Icons swapped intentionally.. + right_hand.icon_state = "bloodhand_left" // ..because perspective, or something + jaunter.put_in_hands(left_hand) + jaunter.put_in_hands(right_hand) + + blood.visible_message(span_warning("[jaunter] sinks into [blood]!")) + playsound(jaunt_turf, 'sound/magic/enter_blood.ogg', 50, TRUE, -1) + jaunter.extinguish_mob() + + jaunter.notransform = FALSE + return TRUE + +/** + * Attempts to Exit the passed blood pool. + * If forced is TRUE, it will override exit_blood_time, and if we're currently consuming someone. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/proc/try_exit_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced = FALSE) + if(!forced) + if(jaunter.notransform) + to_chat(jaunter, span_warning("You cannot exit yet!!")) + return FALSE + + if(exit_blood_time > 0 SECONDS) + blood.visible_message(span_warning("[blood] starts to bubble...")) + if(!do_after(jaunter, exit_blood_time, target = blood)) + return FALSE + + if(!exit_jaunt(jaunter, get_turf(blood))) + return FALSE + + blood.visible_message(span_boldwarning("[jaunter] rises out of [blood]!")) + return TRUE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/on_jaunt_exited(obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter) + UnregisterSignal(jaunt, COMSIG_MOVABLE_MOVED) + exit_blood_effect(unjaunter) + if(equip_blood_hands && iscarbon(unjaunter)) + for(var/obj/item/bloodcrawl/blood_hand in unjaunter.held_items) + unjaunter.temporarilyRemoveItemFromInventory(blood_hand, force = TRUE) + qdel(blood_hand) + return ..() + +/// Adds an coloring effect to mobs which exit blood crawl. +/datum/action/cooldown/spell/jaunt/bloodcrawl/proc/exit_blood_effect(mob/living/exited) + var/turf/landing_turf = get_turf(exited) + playsound(landing_turf, 'sound/magic/exit_blood.ogg', 50, TRUE, -1) + + // Make the mob have the color of the blood pool it came out of + var/obj/effect/decal/cleanable/came_from = locate() in landing_turf + var/new_color = came_from?.get_blood_color() + if(!new_color) + return + + exited.add_atom_colour(new_color, TEMPORARY_COLOUR_PRIORITY) + // ...but only for a few seconds + addtimer(CALLBACK(exited, TYPE_PROC_REF(/atom/, remove_atom_colour), TEMPORARY_COLOUR_PRIORITY, new_color), 6 SECONDS) + +/** + * Slaughter demon's blood crawl + * Allows the blood crawler to consume people they are dragging. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon + name = "Voracious Blood Crawl" + desc = "Allows you to phase in and out of existance via pools of blood. If you are dragging someone in critical or dead, \ + they will be consumed by you, fully healing you." + /// The sound played when someone's consumed. + var/consume_sound = 'sound/magic/demon_consume.ogg' + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter) + // Save this before the actual jaunt + var/atom/coming_with = jaunter.pulling + + // Does the actual jaunt + . = ..() + if(!.) + return + + var/turf/jaunt_turf = get_turf(jaunter) + // if we're not pulling anyone, or we can't what we're pulling + if(!isliving(coming_with)) + return + + var/mob/living/victim = coming_with + + if(victim.stat == CONSCIOUS) + jaunt_turf.visible_message( + span_warning("[victim] kicks free of [blood] just before entering it!"), + blind_message = span_notice("You hear splashing and struggling."), + ) + return FALSE + + if(SEND_SIGNAL(victim, COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED, src, jaunter, blood) & COMPONENT_STOP_CONSUMPTION) + return FALSE + + victim.forceMove(jaunter) + victim.emote("scream") + jaunt_turf.visible_message( + span_boldwarning("[jaunter] drags [victim] into [blood]!"), + blind_message = span_notice("You hear a splash."), + ) + + jaunter.notransform = TRUE + consume_victim(victim, jaunter) + jaunter.notransform = FALSE + + return TRUE + +/** + * Consumes the [victim] from the [jaunter], fully healing them + * and calling [proc/on_victim_consumed] if successful. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/consume_victim(mob/living/victim, mob/living/jaunter) + on_victim_start_consume(victim, jaunter) + + for(var/i in 1 to 3) + playsound(get_turf(jaunter), consume_sound, 50, TRUE) + if(!do_after(jaunter, 3 SECONDS, victim)) + to_chat(jaunter, span_danger("You lose your victim!")) + return FALSE + if(QDELETED(src)) + return FALSE + + if(SEND_SIGNAL(victim, COMSIG_LIVING_BLOOD_CRAWL_CONSUMED, src, jaunter) & COMPONENT_STOP_CONSUMPTION) + return FALSE + + jaunter.revive(full_heal = TRUE, admin_revive = FALSE) + + // No defib possible after laughter + victim.apply_damage(1000, BRUTE, wound_bonus = CANT_WOUND) + victim.death() + on_victim_consumed(victim, jaunter) + +/** + * Called when a victim starts to be consumed. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_start_consume(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_danger("You begin to feast on [victim]... You can not move while you are doing this.")) + +/** + * Called when a victim is successfully consumed. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_consumed(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_danger("You devour [victim]. Your health is fully restored.")) + qdel(victim) + +/** + * Laughter demon's blood crawl + * All mobs consumed are revived after the demon is killed. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny + name = "Friendly Blood Crawl" + desc = "Allows you to phase in and out of existance via pools of blood. If you are dragging someone in critical or dead - I mean, \ + sleeping, when entering a blood pool, they will be invited to a party and fully heal you!" + consume_sound = 'sound/spookoween/scary_horn.ogg' + + // Keep the people we hug! + var/list/mob/living/consumed_mobs = list() + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/Destroy() + consumed_mobs.Cut() + return ..() + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/Grant(mob/grant_to) + . = ..() + if(owner) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/Remove(mob/living/remove_from) + UnregisterSignal(remove_from, COMSIG_LIVING_DEATH) + return ..() + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_victim_start_consume(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_clown("You invite [victim] to your party! You can not move while you are doing this.")) + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_victim_consumed(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_clown("[victim] joins your party! Your health is fully restored.")) + consumed_mobs += victim + RegisterSignal(victim, COMSIG_MOB_STATCHANGE, PROC_REF(on_victim_statchange)) + RegisterSignal(victim, COMSIG_PARENT_QDELETING, PROC_REF(on_victim_deleted)) + +/** + * Signal proc for COMSIG_LIVING_DEATH and COMSIG_PARENT_QDELETING + * + * If our demon is deleted or destroyed, expel all of our consumed mobs + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/proc/on_death(datum/source) + SIGNAL_HANDLER + + var/turf/release_turf = get_turf(source) + for(var/mob/living/friend as anything in consumed_mobs) + + // Unregister the signals first + UnregisterSignal(friend, list(COMSIG_MOB_STATCHANGE, COMSIG_PARENT_QDELETING)) + + INVOKE_ASYNC(friend, TYPE_PROC_REF(/atom/movable/, forceMove), release_turf) + if(!INVOKE_ASYNC(friend, TYPE_PROC_REF(/mob/living/, revive), TRUE, TRUE)) + continue + playsound(release_turf, consumed_mobs, 50, TRUE, -1) + to_chat(friend, span_clown("You leave [source]'s warm embrace, and feel ready to take on the world.")) + + +/** + * Handle signal from a consumed mob changing stat. + * + * A signal handler for if one of the laughter demon's consumed mobs has + * changed stat. If they're no longer dead (because they were dead when + * swallowed), eject them so they can't rip their way out from the inside. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/proc/on_victim_statchange(mob/living/victim, new_stat) + SIGNAL_HANDLER + + if(new_stat == DEAD) + return + // Someone we've eaten has spontaneously revived; maybe regen coma, maybe a changeling + INVOKE_ASYNC(victim, TYPE_PROC_REF(/atom/movable/, forceMove), get_turf(victim)) + victim.visible_message(span_warning("[victim] falls out of the air, covered in blood, with a confused look on their face.")) + INVOKE_ASYNC(src, PROC_REF(exit_blood_effect), victim) + + consumed_mobs -= victim + UnregisterSignal(victim, COMSIG_MOB_STATCHANGE) + +/** + * Handle signal from a consumed mob being deleted. Clears any references. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/proc/on_victim_deleted(datum/source) + SIGNAL_HANDLER + + consumed_mobs -= source + +/// Bloodcrawl "hands", prevent the user from holding items in bloodcrawl +/obj/item/bloodcrawl + name = "blood crawl" + desc = "You are unable to hold anything while in this form." + icon = 'icons/effects/blood.dmi' + item_flags = ABSTRACT | DROPDEL + +/obj/item/bloodcrawl/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) diff --git a/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm new file mode 100644 index 000000000000..f389ca1144fa --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm @@ -0,0 +1,258 @@ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt + name = "Ethereal Jaunt" + desc = "This spell turns your form ethereal, temporarily making you invisible and able to pass through walls." + button_icon_state = "jaunt" + sound = 'sound/magic/ethereal_enter.ogg' + + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt + + var/exit_jaunt_sound = 'sound/magic/ethereal_exit.ogg' + /// For how long are we jaunting? + var/jaunt_duration = 5 SECONDS + /// For how long we become immobilized after exiting the jaunt. + var/jaunt_in_time = 0.5 SECONDS + /// For how long we become immobilized when using this spell. + var/jaunt_out_time = 0 SECONDS + /// Visual for jaunting + var/obj/effect/jaunt_in_type = /obj/effect/temp_visual/wizard + /// Visual for exiting the jaunt + var/obj/effect/jaunt_out_type = /obj/effect/temp_visual/wizard/out + /// List of valid exit points + var/list/exit_point_list + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/enter_jaunt(mob/living/jaunter) + . = ..() + if(!.) + return + + var/turf/cast_turf = get_turf(.) + new jaunt_out_type(cast_turf, jaunter.dir) + jaunter.extinguish_mob() + do_steam_effects(cast_turf) + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/cast(mob/living/cast_on) + . = ..() + do_jaunt(cast_on) + +/** + * Begin the jaunt, and the entire jaunt chain. + * Puts cast_on in the phased mob holder here. + * + * Calls do_jaunt_out: + * - if jaunt_out_time is set to more than 0, + * Or immediately calls start_jaunt: + * - if jaunt_out_time = 0 + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/do_jaunt(mob/living/cast_on) + // Makes sure they don't die or get jostled or something during the jaunt entry + // Honestly probably not necessary anymore, but better safe than sorry + cast_on.notransform = TRUE + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(cast_on) + cast_on.notransform = FALSE + + if(!holder) + CRASH("[type] attempted do_jaunt but failed to create a jaunt holder via enter_jaunt.") + + if(jaunt_out_time > 0) + ADD_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + addtimer(CALLBACK(src, PROC_REF(do_jaunt_out), cast_on, holder), jaunt_out_time) + else + start_jaunt(cast_on, holder) + +/** + * The wind-up to the jaunt. + * Optional, only called if jaunt_out_time is set. + * + * Calls start_jaunt. + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/do_jaunt_out(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + REMOVE_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + start_jaunt(cast_on, holder) + +/** + * The actual process of starting the jaunt. + * Sets up the signals and exit points and allows + * the caster to actually start moving around. + * + * Calls stop_jaunt after the jaunt runs out. + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/start_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + LAZYINITLIST(exit_point_list) + RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_exit_point), target) + addtimer(CALLBACK(src, PROC_REF(stop_jaunt), cast_on, holder, get_turf(holder)), jaunt_duration) + +/** + * The stopping of the jaunt. + * Unregisters and signals and places + * the jaunter on the turf they will exit at. + * + * Calls do_jaunt_in: + * - immediately, if jaunt_in_time >= 2.5 seconds + * - 2.5 seconds - jaunt_in_time seconds otherwise + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/stop_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/start_point) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + UnregisterSignal(holder, COMSIG_MOVABLE_MOVED) + // The caster escaped our holder somehow? + if(cast_on.loc != holder) + qdel(holder) + return + + // Pick an exit turf to deposit the jaunter + var/turf/found_exit + for(var/turf/possible_exit as anything in exit_point_list) +// if(possible_exit.is_blocked_turf_ignore_climbable()) +// continue + found_exit = possible_exit + break + + // No valid exit was found + if(!found_exit) + // It's possible no exit was found, because we literally didn't even move + if(get_turf(cast_on) != start_point) + to_chat(cast_on, span_danger("Unable to find an unobstructed space, you find yourself ripped back to where you started.")) + // Either way, default to where we started + found_exit = start_point + + exit_point_list = null + holder.forceMove(found_exit) + do_steam_effects(found_exit) + holder.reappearing = TRUE + if(exit_jaunt_sound) + playsound(found_exit, exit_jaunt_sound, 50, TRUE) + + ADD_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + + if(2.5 SECONDS - jaunt_in_time <= 0) + do_jaunt_in(cast_on, holder, found_exit) + else + addtimer(CALLBACK(src, PROC_REF(do_jaunt_in), cast_on, holder, found_exit), 2.5 SECONDS - jaunt_in_time) + +/** + * The wind-up (wind-out?) of exiting the jaunt. + * Optional, only called if jaunt_in_time is above 2.5 seconds. + * + * Calls end_jaunt. + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/do_jaunt_in(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/final_point) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + new jaunt_in_type(final_point, holder.dir) + cast_on.setDir(holder.dir) + + if(jaunt_in_time > 0) + addtimer(CALLBACK(src, PROC_REF(end_jaunt), cast_on, holder, final_point), jaunt_in_time) + else + end_jaunt(cast_on, holder, final_point) + +/** + * Finally, the actual veritable end of the jaunt chains. + * Deletes the phase holder, ejecting the caster at final_point. + * + * If the final_point is dense for some reason, + * tries to put the caster in an adjacent turf. + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/end_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/final_point) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + cast_on.notransform = TRUE + exit_jaunt(cast_on) + cast_on.notransform = FALSE + + REMOVE_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + + if(final_point.density) + var/list/aside_turfs = get_adjacent_open_turfs(final_point) + if(length(aside_turfs)) + cast_on.forceMove(pick(aside_turfs)) + +/** + * Updates the exit point of the jaunt + * + * Called when the jaunting mob holder moves, this updates the backup exit-jaunt + * location, in case the jaunt ends with the mob still in a wall. Five + * spots are kept in the list, in case the last few changed since we passed + * by (doors closing, engineers building walls, etc) + */ +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/update_exit_point(mob/living/source) + SIGNAL_HANDLER + + var/turf/location = get_turf(source) +// if(location.is_blocked_turf_ignore_climbable()) +// return + exit_point_list.Insert(1, location) + if(length(exit_point_list) >= 5) + exit_point_list.Cut(5) + +/// Does some steam effects from the jaunt at passed loc. +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/proc/do_steam_effects(turf/loc) + var/datum/effect_system/steam_spread/steam = new() + steam.set_up(10, FALSE, loc) + steam.start() + + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift + name = "Phase Shift" + desc = "This spell allows you to pass through walls." + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "phaseshift" + + cooldown_time = 25 SECONDS + spell_requirements = NONE + + jaunt_duration = 5 SECONDS + jaunt_in_time = 0.6 SECONDS + jaunt_out_time = 0.6 SECONDS + jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith + jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/do_steam_effects(mobloc) + return + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/angelic + name = "Purified Phase Shift" + jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith/angelic + jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out/angelic + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/mystic + name = "Mystic Phase Shift" + jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith/mystic + jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out/mystic + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/golem + name = "Runic Phase Shift" + cooldown_time = 80 SECONDS + jaunt_in_type = /obj/effect/temp_visual/dir_setting/cult/phase + jaunt_out_type = /obj/effect/temp_visual/dir_setting/cult/phase/out + + +/// The dummy that holds people jaunting. Maybe one day we can replace it. +/obj/effect/dummy/phased_mob/spell_jaunt + movespeed = 2 //quite slow. + /// Whether we're currently reappearing - we can't move if so + var/reappearing = FALSE + +/obj/effect/dummy/phased_mob/spell_jaunt/phased_check(mob/living/user, direction) + if(reappearing) + return + . = ..() + if(!.) + return + if (locate(/obj/effect/blessing) in .) + to_chat(user, span_warning("Holy energies block your path!")) + return null diff --git a/code/modules/spells/spell_types/jaunt/shadow_walk.dm b/code/modules/spells/spell_types/jaunt/shadow_walk.dm new file mode 100644 index 000000000000..1b36a0e69501 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/shadow_walk.dm @@ -0,0 +1,147 @@ +/datum/action/cooldown/spell/jaunt/shadow_walk + name = "Shadow Walk" + desc = "Grants unlimited movement in darkness." + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "ninja_cloak" + + spell_requirements = NONE + jaunt_type = /obj/effect/dummy/phased_mob/shadow + +/datum/action/cooldown/spell/jaunt/shadow_walk/Grant(mob/grant_to) + . = ..() + RegisterSignal(grant_to, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) + +/datum/action/cooldown/spell/jaunt/shadow_walk/Remove(mob/remove_from) + . = ..() + UnregisterSignal(remove_from, COMSIG_MOVABLE_MOVED) + +/datum/action/cooldown/spell/jaunt/shadow_walk/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(is_jaunting(owner)) + return TRUE + var/turf/cast_turf = get_turf(owner) + if(cast_turf.get_lumcount() >= SHADOW_SPECIES_LIGHT_THRESHOLD) + if(feedback) + to_chat(owner, span_warning("It isn't dark enough here!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/jaunt/shadow_walk/cast(mob/living/cast_on) + . = ..() + if(is_jaunting(cast_on)) + exit_jaunt(cast_on) + return + + playsound(get_turf(owner), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) + cast_on.visible_message(span_boldwarning("[cast_on] melts into the shadows!")) + cast_on.SetAllImmobility(0) + cast_on.setStaminaLoss(0, FALSE) + enter_jaunt(cast_on) + +/obj/effect/dummy/phased_mob/shadow + name = "shadows" + /// The amount that shadow heals us per SSobj tick (times delta_time) + var/healing_rate = 1.5 + /// When cooldown is active, you are prevented from moving into tiles that would eject you from your jaunt + COOLDOWN_DECLARE(light_step_cooldown) + /// Has the jaunter recently recieved a warning about light? + var/light_alert_given = FALSE + +/obj/effect/dummy/phased_mob/shadow/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +/obj/effect/dummy/phased_mob/shadow/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/effect/dummy/phased_mob/shadow/process(delta_time) + var/turf/T = get_turf(src) + if(!jaunter || jaunter.loc != src) + qdel(src) + return + + if(check_light_level(T)) + eject_jaunter(TRUE) + + if(!QDELETED(jaunter) && isliving(jaunter)) //heal in the dark + var/mob/living/living_jaunter = jaunter + living_jaunter.heal_overall_damage((healing_rate * delta_time), (healing_rate * delta_time), 0) + +/obj/effect/dummy/phased_mob/shadow/relaymove(mob/living/user, direction) + var/turf/oldloc = loc + . = ..() + if(loc != oldloc) + if(check_light_level(loc)) + eject_jaunter(TRUE) + +/obj/effect/dummy/phased_mob/shadow/phased_check(mob/living/user, direction) + . = ..() + if(. && isspaceturf(.)) + to_chat(user, span_warning("It really would not be wise to go into space.")) + return FALSE + if(check_light_level(.)) + if(!light_step_warning()) + return FALSE + +/obj/effect/dummy/phased_mob/shadow/eject_jaunter(forced_out = FALSE) + var/turf/reveal_turf = get_turf(src) + + if(istype(reveal_turf)) + if(forced_out) + reveal_turf.visible_message(span_boldwarning("[jaunter] is revealed by the light!")) + else + reveal_turf.visible_message(span_boldwarning("[jaunter] emerges from the darkness!")) + playsound(reveal_turf, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + + return ..() + +/** + * Checks the light level. If above the minimum acceptable amount (0.2), returns TRUE. + * + * Checks the light level of a given location to see if it is too bright to + * continue a jaunt in. Returns FALSE if it's acceptably dark, and TRUE if it is too bright. + * + * * location_to_check - The location to have its light level checked. + */ + +/obj/effect/dummy/phased_mob/shadow/proc/check_light_level(location_to_check) + var/turf/T = get_turf(location_to_check) + var/light_amount = T.get_lumcount() + if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) // jaunt ends + return TRUE + +/** + * Checks if the user should recieve a warning that they're moving into light. + * + * Checks the cooldown for the warning message on moving into the light. + * If the message has been displayed, and the cooldown (delay period) is complete, returns TRUE. + */ + +/obj/effect/dummy/phased_mob/shadow/proc/light_step_warning() + if(!light_alert_given) //Give the user a warning that they're leaving the darkness + balloon_alert(jaunter, "leaving the shadows...") + light_alert_given = TRUE + COOLDOWN_START(src, light_step_cooldown, 0.75 SECONDS) + addtimer(CALLBACK(src, PROC_REF(reactivate_light_alert)), 1 SECONDS) //You get a .5 second window to bypass the warning before it comes back + return FALSE + + if(!COOLDOWN_FINISHED(src, light_step_cooldown)) + return FALSE + + light_alert_given = FALSE + return TRUE //Our jaunter is ignoring the warning, so we proceed + +/** + * Sets light_alert_given to false. + * + * Sets light_alert_given to false, making the light alert pop up and intercept movement once again. + * Added in its own proc to reset the alert without having to call light_step_warning(). + */ + +/obj/effect/dummy/phased_mob/shadow/proc/reactivate_light_alert() + light_alert_given = FALSE diff --git a/code/modules/spells/spell_types/knock.dm b/code/modules/spells/spell_types/knock.dm deleted file mode 100644 index 6aece09aed03..000000000000 --- a/code/modules/spells/spell_types/knock.dm +++ /dev/null @@ -1,31 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/knock - name = "Knock" - desc = "This spell opens nearby doors and closets." - - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - invocation = "AULIE OXIN FIERA" - invocation_type = SPELL_INVOCATION_WHISPER - range = 3 - cooldown_min = 20 //20 deciseconds reduction per rank - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "knock" - -/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets,mob/user = usr) - SEND_SOUND(user, sound('sound/magic/knock.ogg')) - for(var/turf/T in targets) - for(var/obj/machinery/door/door in T.contents) - INVOKE_ASYNC(src, PROC_REF(open_door), door) - for(var/obj/structure/closet/C in T.contents) - INVOKE_ASYNC(src, PROC_REF(open_closet), C) - -/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_door(var/obj/machinery/door/door) - if(istype(door, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/A = door - A.locked = FALSE - door.open() - -/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_closet(var/obj/structure/closet/C) - C.locked = FALSE - C.open() diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm deleted file mode 100644 index 9e368e9afa5f..000000000000 --- a/code/modules/spells/spell_types/lichdom.dm +++ /dev/null @@ -1,160 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/lichdom - name = "Bind Soul" - desc = "A dark necromantic pact that can forever bind your soul to an \ - item of your choosing. So long as both your body and the item remain \ - intact and on the same plane you can revive from death, though the time \ - between reincarnations grows steadily with use, along with the weakness \ - that the new skeleton body will experience upon 'birth'. Note that \ - becoming a lich destroys all internal organs except the brain." - school = "necromancy" - charge_max = 10 - clothes_req = FALSE - centcom_cancast = FALSE - invocation = "NECREM IMORTIUM!" - invocation_type = SPELL_INVOCATION_SAY - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 10 - include_user = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "skeleton" - -/obj/effect/proc_holder/spell/targeted/lichdom/cast(list/targets,mob/user = usr) - for(var/mob/M in targets) - var/list/hand_items = list() - if(iscarbon(M)) - hand_items = list(M.get_active_held_item(),M.get_inactive_held_item()) - if(!hand_items.len) - to_chat(M, span_caution("You must hold an item you wish to make your phylactery...")) - return - if(!M.mind.hasSoul) - to_chat(user, span_caution("You do not possess a soul.")) - return - - var/obj/item/marked_item - - for(var/obj/item/item in hand_items) - // I ensouled the nuke disk once. But it's probably a really - // mean tactic, so probably should discourage it. - if((item.item_flags & ABSTRACT) || HAS_TRAIT(item, TRAIT_NODROP) || SEND_SIGNAL(item, COMSIG_ITEM_IMBUE_SOUL, user)) - continue - marked_item = item - to_chat(M, span_warning("You begin to focus your very being into [item]...")) - break - - if(!marked_item) - to_chat(M, span_warning("None of the items you hold are suitable for emplacement of your fragile soul.")) - return - - playsound(user, 'sound/effects/pope_entry.ogg', 100) - - if(!do_after(M, 5 SECONDS, marked_item, FALSE)) - to_chat(M, span_warning("Your soul snaps back to your body as you stop ensouling [marked_item]!")) - return - - marked_item.name = "ensouled [marked_item.name]" - marked_item.desc += "\nA terrible aura surrounds this item, its very existence is offensive to life itself..." - marked_item.add_atom_colour("#003300", ADMIN_COLOUR_PRIORITY) - - new /obj/item/phylactery(marked_item, M.mind) - - to_chat(M, span_userdanger("With a hideous feeling of emptiness you watch in horrified fascination as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! As your organs crumble to dust in your fleshless chest you come to terms with your choice. You're a lich!")) - M.mind.hasSoul = FALSE - M.set_species(/datum/species/skeleton) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.dropItemToGround(H.w_uniform) - H.dropItemToGround(H.wear_suit) - H.dropItemToGround(H.head) - H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), SLOT_WEAR_SUIT) - H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), SLOT_HEAD) - H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), SLOT_W_UNIFORM) - - // you only get one phylactery. - M.mind.RemoveSpell(src) - - -/obj/item/phylactery - name = "phylactery" - desc = "Stores souls. Revives liches. Also repels mosquitos." - icon = 'icons/obj/projectiles.dmi' - icon_state = "bluespace" - color = "#003300" - light_color = "#003300" - light_system = MOVABLE_LIGHT - light_range = 3 - var/resurrections = 0 - var/datum/mind/mind - var/respawn_time = 1800 - - var/static/active_phylacteries = 0 - -/obj/item/phylactery/Initialize(mapload, datum/mind/newmind) - . = ..() - mind = newmind - name = "phylactery of [mind.name]" - - active_phylacteries++ - GLOB.poi_list |= src - START_PROCESSING(SSobj, src) - if(initial(SSticker.mode.round_ends_with_antag_death)) - SSticker.mode.round_ends_with_antag_death = FALSE - -/obj/item/phylactery/Destroy(force=FALSE) - STOP_PROCESSING(SSobj, src) - active_phylacteries-- - GLOB.poi_list -= src - if(!active_phylacteries) - SSticker.mode.round_ends_with_antag_death = initial(SSticker.mode.round_ends_with_antag_death) - . = ..() - -/obj/item/phylactery/process() - if(QDELETED(mind)) - qdel(src) - return - - if(!mind.current || (mind.current && mind.current.stat == DEAD)) - addtimer(CALLBACK(src, PROC_REF(rise)), respawn_time, TIMER_UNIQUE) - -/obj/item/phylactery/proc/rise() - if(mind.current && mind.current.stat != DEAD) - return "[mind] already has a living body: [mind.current]" - - var/turf/item_turf = get_turf(src) - if(!item_turf) - return "[src] is not at a turf? NULLSPACE!?" - - var/mob/old_body = mind.current - var/mob/living/carbon/human/lich = new(item_turf) - - lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(lich), SLOT_SHOES) - lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), SLOT_W_UNIFORM) - lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), SLOT_WEAR_SUIT) - lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), SLOT_HEAD) - - lich.real_name = mind.name - mind.transfer_to(lich) - mind.grab_ghost(force=TRUE) - lich.hardset_dna(null,null,null,lich.real_name,null, new /datum/species/skeleton) - to_chat(lich, span_warning("Your bones clatter and shudder as you are pulled back into this world!")) - var/turf/body_turf = get_turf(old_body) - lich.Paralyze(200 + 200*resurrections) - resurrections++ - if(old_body && old_body.loc) - if(iscarbon(old_body)) - var/mob/living/carbon/C = old_body - for(var/obj/item/W in C) - C.dropItemToGround(W) - for(var/X in C.internal_organs) - var/obj/item/organ/I = X - I.Remove(C) - I.forceMove(body_turf) - var/wheres_wizdo = dir2text(get_dir(body_turf, item_turf)) - if(wheres_wizdo) - old_body.visible_message(span_warning("Suddenly [old_body.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(item_turf,icon_state="lichbeam",time=10+10*resurrections,maxdistance=INFINITY) - old_body.dust() - - - return "Respawn of [mind] successful." diff --git a/code/modules/spells/spell_types/lightning.dm b/code/modules/spells/spell_types/lightning.dm deleted file mode 100644 index 4f7e30a034e5..000000000000 --- a/code/modules/spells/spell_types/lightning.dm +++ /dev/null @@ -1,86 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/tesla - name = "Tesla Blast" - desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down if they do not have shock protection." - charge_type = SPELL_CHARGE_TYPE_RECHARGE - charge_max = 200 - clothes_req = TRUE - invocation = "UN'LTD P'WAH!" - invocation_type = SPELL_INVOCATION_SAY - range = 7 - cooldown_min = 30 - selection_type = "view" - random_target = TRUE - var/ready = FALSE - var/static/mutable_appearance/halo - var/sound/Snd // so far only way i can think of to stop a sound, thank MSO for the idea. - - action_icon_state = "lightning" - -/obj/effect/proc_holder/spell/targeted/tesla/Click() - if(!ready && cast_check()) - StartChargeup() - return TRUE - -/obj/effect/proc_holder/spell/targeted/tesla/proc/StartChargeup(mob/user = usr) - ready = TRUE - to_chat(user, span_notice("You start gathering the power.")) - Snd = new/sound('sound/magic/lightning_chargeup.ogg',channel = 7) - halo = halo || mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER) - user.add_overlay(halo) - playsound(get_turf(user), Snd, 50, 0) - if(do_mob(user,user,100,1)) - if(ready && cast_check(skipcharge=1)) - choose_targets() - else - revert_cast(user, 0) - else - revert_cast(user, 0) - -/obj/effect/proc_holder/spell/targeted/tesla/proc/Reset(mob/user = usr) - ready = FALSE - user.cut_overlay(halo) - -/obj/effect/proc_holder/spell/targeted/tesla/revert_cast(mob/user = usr, message = 1) - if(message) - to_chat(user, span_notice("No target found in range.")) - Reset(user) - ..() - -/obj/effect/proc_holder/spell/targeted/tesla/cast(list/targets, mob/user = usr) - ready = FALSE - var/mob/living/carbon/target = targets[1] - Snd=sound(null, repeat = 0, wait = 1, channel = Snd.channel) //byond, why you suck? - playsound(get_turf(user),Snd,50,0)// Sorry MrPerson, but the other ways just didn't do it the way i needed to work, this is the only way. - if(get_dist(user,target)>range) - to_chat(user, span_notice("[target.p_theyre(TRUE)] too far away!")) - Reset(user) - return - - playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, 1) - user.Beam(target,icon_state="lightning[rand(1,12)]",time=5) - - Bolt(user,target,30,5,user) - Reset(user) - -/obj/effect/proc_holder/spell/targeted/tesla/proc/Bolt(mob/origin,mob/target,bolt_energy,bounces,mob/user = usr) - origin.Beam(target,icon_state="lightning[rand(1,12)]",time=5) - var/mob/living/carbon/current = target - if(current.anti_magic_check()) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - current.visible_message(span_warning("[current] absorbs the spell, remaining unharmed!"), span_userdanger("You absorb the spell, remaining unharmed!")) - else if(bounces < 1) - current.electrocute_act(bolt_energy,"Lightning Bolt",safety=1) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - else - current.electrocute_act(bolt_energy,"Lightning Bolt",safety=1) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - var/list/possible_targets = new - for(var/mob/living/M in view_or_range(range,target,"view")) - if(user == M || target == M && los_check(current,M)) // || origin == M ? Not sure double shockings is good or not - continue - possible_targets += M - if(!possible_targets.len) - return - var/mob/living/next = pick(possible_targets) - if(next) - Bolt(current,next,max((bolt_energy-5),5),bounces-1,user) diff --git a/code/modules/spells/spell_types/list_target/_list_target.dm b/code/modules/spells/spell_types/list_target/_list_target.dm new file mode 100644 index 000000000000..fcbcffab1e4b --- /dev/null +++ b/code/modules/spells/spell_types/list_target/_list_target.dm @@ -0,0 +1,41 @@ +/** + * ## List Target spells + * + * These spells will prompt the user with a tgui list + * of all nearby targets that they select on to cast. + * + * To add effects on cast, override "cast(atom/cast_on)". + * The cast_on atom is the atom that was selected by the list. + */ +/datum/action/cooldown/spell/list_target + /// The message displayed as the title of the tgui target input list. + var/choose_target_message = "Choose a target." + /// Radius around the caster that living targets are picked to choose from + var/target_radius = 7 + +/datum/action/cooldown/spell/list_target/PreActivate(atom/caster) + var/list/list_targets = get_list_targets(caster, target_radius) + if(!length(list_targets)) + caster.balloon_alert(caster, "no targets nearby!") + return FALSE + + var/atom/chosen = tgui_input_list(caster, choose_target_message, name, sortUsernames(list_targets)) + if(QDELETED(src) || QDELETED(caster) || QDELETED(chosen) || !can_cast_spell()) + return FALSE + + if(get_dist(chosen, caster) > target_radius) + caster.balloon_alert(caster, "they're too far!") + return FALSE + + return Activate(chosen) + +/// Get a list of living targets in radius of the center to put in the target list. +/datum/action/cooldown/spell/list_target/proc/get_list_targets(atom/center, target_radius = 7) + var/list/things = list() + for(var/mob/living/nearby_living in view(target_radius, center)) + if(nearby_living == owner || nearby_living == center) + continue + + things += nearby_living + + return things \ No newline at end of file diff --git a/code/modules/spells/spell_types/list_target/telepathy.dm b/code/modules/spells/spell_types/list_target/telepathy.dm new file mode 100644 index 000000000000..fcbb5eab8095 --- /dev/null +++ b/code/modules/spells/spell_types/list_target/telepathy.dm @@ -0,0 +1,51 @@ +/datum/action/cooldown/spell/list_target/telepathy + name = "Telepathy" + desc = "Telepathically transmits a message to the target." + button_icon = 'icons/mob/actions/actions_revenant.dmi' + button_icon_state = "r_transmit" + + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + antimagic_flags = MAGIC_RESISTANCE_MIND + + choose_target_message = "Choose a target to whisper to." + + /// The message we send to the next person via telepathy. + var/message + /// The span surrounding the telepathy message + var/telepathy_span = "notice" + /// The bolded span surrounding the telepathy message + var/bold_telepathy_span = "boldnotice" + +/datum/action/cooldown/spell/list_target/telepathy/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + message = tgui_input_text(owner, "What do you wish to whisper to [cast_on]?", "[src]") + if(QDELETED(src) || QDELETED(owner) || QDELETED(cast_on) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + + if(!message) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/list_target/telepathy/cast(mob/living/cast_on) + . = ..() + log_directed_talk(owner, cast_on, message, LOG_SAY, name) + + var/formatted_message = "[message]" + + to_chat(owner, "You transmit to [cast_on]: [formatted_message]") + if(!cast_on.can_block_magic(antimagic_flags, charge_cost = 0)) //hear no evil + to_chat(cast_on, "You hear something behind you talking... [formatted_message]") + + for(var/mob/dead/ghost as anything in GLOB.dead_mob_list) + if(!isobserver(ghost)) + continue + + var/from_link = FOLLOW_LINK(ghost, owner) + var/from_mob_name = "[owner] [src]:" + var/to_link = FOLLOW_LINK(ghost, cast_on) + var/to_mob_name = span_name("[cast_on]") + + to_chat(ghost, "[from_link] [from_mob_name] [formatted_message] [to_link] [to_mob_name]") diff --git a/code/modules/spells/spell_types/curse.dm b/code/modules/spells/spell_types/madness_curse.dm similarity index 100% rename from code/modules/spells/spell_types/curse.dm rename to code/modules/spells/spell_types/madness_curse.dm diff --git a/code/modules/spells/spell_types/mime.dm b/code/modules/spells/spell_types/mime.dm deleted file mode 100644 index b9886ae77583..000000000000 --- a/code/modules/spells/spell_types/mime.dm +++ /dev/null @@ -1,249 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall - name = "Invisible Wall" - desc = "The mime's performance transmutates a wall into physical reality." - school = "mime" - panel = "Mime" - summon_type = list(/obj/effect/forcefield/mime) - invocation_type = SPELL_INVOCATION_MESSAGE - invocation_emote_self = span_notice("You form a wall in front of yourself.") - summon_lifespan = 300 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall/Click() - var/mob/living/carbon/C = usr - if(usr && usr.mind) - if(C.handcuffed) - to_chat(usr, span_notice("You cannot cast this while handcuffed!")) - return - if(!usr.mind.miming) - to_chat(usr, span_notice("You must dedicate yourself to silence first.")) - return - invocation = "[usr.real_name] looks as if a wall is in front of [usr.p_them()]." - else - invocation_type = SPELL_INVOCATION_NONE - ..() - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair - name = "Invisible Chair" - desc = "The mime's performance transmutates a chair into physical reality." - school = "mime" - panel = "Mime" - summon_type = list(/obj/structure/chair/mime) - invocation_type = SPELL_INVOCATION_MESSAGE - invocation_emote_self = span_notice("You conjure an invisible chair and sit down.") - summon_lifespan = 250 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, span_notice("You must dedicate yourself to silence first.")) - return - invocation = "[usr.real_name] pulls out an invisible chair and sits down." - else - invocation_type = SPELL_INVOCATION_NONE - ..() - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/cast(list/targets,mob/user = usr) - ..() - var/turf/T = user.loc - for (var/obj/structure/chair/A in T) - if (is_type_in_list(A, summon_type)) - A.setDir(user.dir) - A.buckle_mob(user) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box - name = "Invisible Box" - desc = "The mime's performance transmutates a box into physical reality." - school = "mime" - panel = "Mime" - summon_type = list(/obj/item/storage/box/mime) - invocation_type = SPELL_INVOCATION_MESSAGE - invocation_emote_self = span_notice("You conjure up an invisible box, large enough to store a few things.") - summon_lifespan = 250 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/cast(list/targets,mob/user = usr) - ..() - var/turf/T = user.loc - for (var/obj/item/storage/box/mime/B in T) - user.put_in_hands(B) - if(user.get_active_held_item() == B) - B.alpha = 255 - addtimer(CALLBACK(B, TYPE_PROC_REF(/obj/item/storage/box/mime, emptyStorage), FALSE), (summon_lifespan - 1)) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, span_notice("You must dedicate yourself to silence first.")) - return - invocation = "[usr.real_name] moves [usr.p_their()] hands in the shape of a cube, pressing a box out of the air." - else - invocation_type = SPELL_INVOCATION_NONE - ..() - - -/obj/effect/proc_holder/spell/targeted/mime/speak - name = "Speech" - desc = "Make or break a vow of silence." - school = "mime" - panel = "Mime" - clothes_req = FALSE - human_req = TRUE - antimagic_allowed = TRUE - charge_max = 3000 - range = -1 - include_user = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/mime/speak/Click() - if(!usr) - return - if(!ishuman(usr)) - return - var/mob/living/carbon/human/H = usr - if(H.mind.miming) - still_recharging_msg = span_warning("You can't break your vow of silence that fast!") - else - still_recharging_msg = span_warning("You'll have to wait before you can give your vow of silence again!") - ..() - -/obj/effect/proc_holder/spell/targeted/mime/speak/cast(list/targets,mob/user = usr) - for(var/mob/living/carbon/human/H in targets) - H.mind.miming=!H.mind.miming - if(H.mind.miming) - to_chat(H, span_notice("You make a vow of silence.")) - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "vow") - else - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) - to_chat(H, span_notice("You break your vow of silence.")) - -// These spells can only be gotten from the "Guide for Advanced Mimery series" for Mime Traitors. - -/obj/effect/proc_holder/spell/targeted/forcewall/mime - name = "Invisible Blockade" - desc = "Form an invisible three tile wide blockade." - school = "mime" - panel = "Mime" - wall_type = /obj/effect/forcefield/mime/advanced - invocation_type = SPELL_INVOCATION_MESSAGE - invocation_emote_self = span_notice("You form a blockade in front of yourself.") - charge_max = 600 - sound = null - clothes_req = FALSE - antimagic_allowed = TRUE - range = -1 - include_user = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/forcewall/mime/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, span_notice("You must dedicate yourself to silence first.")) - return - invocation = "[usr.real_name] looks as if a blockade is in front of [usr.p_them()]." - else - invocation_type = SPELL_INVOCATION_NONE - ..() - -/obj/effect/proc_holder/spell/aimed/finger_guns - name = "Finger Guns" - desc = "Shoot a mimed bullet from your fingers that silences and does some damage." - school = "mime" - panel = "Mime" - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - invocation_type = SPELL_INVOCATION_MESSAGE - invocation_emote_self = span_dangers("You fire your finger gun!") - range = 20 - projectile_type = /obj/item/projectile/bullet/mime - projectile_amount = 3 - sound = null - active_msg = "You draw your fingers!" - deactive_msg = "You put your fingers at ease. Another time." - active = FALSE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - base_icon_state = "mime" - - -/obj/effect/proc_holder/spell/aimed/finger_guns/Click() - var/mob/living/carbon/human/owner = usr - if(owner.incapacitated()) - to_chat(owner, span_warning("You can't properly point your fingers while incapacitated.")) - return - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, span_notice("You must dedicate yourself to silence first.")) - return - invocation = "[usr.real_name] fires [usr.p_their()] finger gun!" - else - invocation_type = SPELL_INVOCATION_NONE - ..() - - -/obj/item/book/granter/spell/mimery_blockade - spell = /obj/effect/proc_holder/spell/targeted/forcewall/mime - spellname = "Invisible Blockade" - name = "Guide to Advanced Mimery Vol 1" - desc = "The pages don't make any sound when turned." - icon_state ="bookmime" - remarks = list("...") - -/obj/item/book/granter/spell/mimery_blockade/attack_self(mob/user) - . = ..() - if(!.) - return - if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) - -/obj/item/book/granter/spell/mimery_guns - spell = /obj/effect/proc_holder/spell/aimed/finger_guns - spellname = "Finger Guns" - name = "Guide to Advanced Mimery Vol 2" - desc = "There aren't any words written..." - icon_state ="bookmime" - remarks = list("...") - -/obj/item/book/granter/spell/mimery_guns/attack_self(mob/user) - . = ..() - if(!.) - return - if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm deleted file mode 100644 index b9fec8bbcd5d..000000000000 --- a/code/modules/spells/spell_types/mind_transfer.dm +++ /dev/null @@ -1,99 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/mind_transfer - name = "Mind Transfer" - desc = "This spell allows the user to switch bodies with a target." - - school = "transmutation" - charge_max = 600 - clothes_req = FALSE - invocation = "GIN'YU CAPAN" - invocation_type = SPELL_INVOCATION_WHISPER - range = 1 - cooldown_min = 200 //100 deciseconds reduction per rank - var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell - var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mindswap" - -/* -Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. -Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring. -Also, you never added distance checking after target is selected. I've went ahead and did that. -*/ -/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE) - if(!targets.len) - if(!silent) - to_chat(user, span_warning("No mind found!")) - return - - if(targets.len > 1) - if(!silent) - to_chat(user, span_warning("Too many minds! You're not a hive damnit!")) - return - - var/mob/living/target = targets[1] - - var/t_He = target.p_they(TRUE) - var/t_is = target.p_are() - - if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. - if(!silent) - to_chat(user, span_warning("[t_He] [t_is] too far away!")) - return - - if(ismegafauna(target) || (target.status_flags & GODMODE)) - if(!silent) - to_chat(user, span_warning("This creature is too powerful to control!")) - return - - if(target.stat == DEAD) - if(!silent) - to_chat(user, span_warning("You don't particularly want to be dead!")) - return - - if(!target.key || !target.mind) - if(!silent) - to_chat(user, span_warning("[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.")) - return - - if(user.suiciding) - if(!silent) - to_chat(user, span_warning("You're killing yourself! You can't concentrate enough to do this!")) - return - - var/datum/mind/TM = target.mind - if((target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev) || TM.has_antag_datum(/datum/antagonist/darkspawn)) || target.key[1] == "@") - if(!silent) - to_chat(user, span_warning("[target.p_their(TRUE)] mind is resisting your spell!")) - return - - if(istype(target, /mob/living/simple_animal/hostile/guardian)) - var/mob/living/simple_animal/hostile/guardian/stand = target - if(stand.summoner) - if(stand.summoner == user) - if(!silent) - to_chat(user, span_warning("Swapping minds with your own guardian would just put you back into your own head!")) - return - else - target = stand.summoner - - var/mob/living/victim = target//The target of the spell whos body will be transferred to. - var/mob/living/caster = user//The wizard/whomever doing the body transferring. - - //MIND TRANSFER BEGIN - var/mob/dead/observer/ghost = victim.ghostize(0) - caster.mind.transfer_to(victim) - - ghost.mind.transfer_to(caster) - if(ghost.key) - caster.key = ghost.key //have to transfer the key since the mind was not active - qdel(ghost) - - //MIND TRANSFER END - - //Here we knock both mobs out for a time. - caster.Unconscious(unconscious_amount_caster) - victim.Unconscious(unconscious_amount_victim) - SEND_SOUND(caster, sound('sound/magic/mandswap.ogg')) - SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened - return TRUE diff --git a/code/modules/spells/spell_types/personality_commune.dm b/code/modules/spells/spell_types/personality_commune.dm deleted file mode 100644 index f358f2eb9e7f..000000000000 --- a/code/modules/spells/spell_types/personality_commune.dm +++ /dev/null @@ -1,32 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/personality_commune - name = "Personality Commune" - desc = "Sends thoughts to your alternate consciousness." - charge_max = 0 - clothes_req = FALSE - range = -1 - include_user = TRUE - action_icon_state = "telepathy" - action_background_icon_state = "bg_spell" - var/datum/brain_trauma/severe/split_personality/trauma - var/flufftext = "You hear an echoing voice in the back of your head..." - -/obj/effect/proc_holder/spell/targeted/personality_commune/New(datum/brain_trauma/severe/split_personality/T) - . = ..() - trauma = T - -// Pillaged and adapted from telepathy code -/obj/effect/proc_holder/spell/targeted/personality_commune/cast(list/targets, mob/user) - if(!istype(trauma)) - to_chat(user, "Something is wrong; Either due a bug or admemes, you are trying to cast this spell without a split personality!") - return - var/msg = stripped_input(usr, "What would you like to tell your other self?", null , "") - if(!msg) - charge_counter = charge_max - return - to_chat(user, "You concentrate and send thoughts to your other self: [msg]") - to_chat(trauma.owner, "[flufftext] [msg]") - log_directed_talk(user, trauma.owner, msg, LOG_SAY ,"[name]") - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - to_chat(ded, "[FOLLOW_LINK(ded, user)] [user] [name]: \"[msg]\" to [trauma]") diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm new file mode 100644 index 000000000000..a454a207c440 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -0,0 +1,176 @@ +/** + * ## Pointed spells + * + * These spells override the caster's click, + * allowing them to cast the spell on whatever is clicked on. + * + * To add effects on cast, override "cast(atom/cast_on)". + * The cast_on atom is the person who was clicked on. + */ +/datum/action/cooldown/spell/pointed + click_to_activate = TRUE + + /// Message showing to the spell owner upon activating pointed spell. + var/active_msg + /// Message showing to the spell owner upon deactivating pointed spell. + var/deactive_msg + /// The casting range of our spell + var/cast_range = 7 + /// Variable dictating if the spell will use turf based aim assist + var/aim_assist = TRUE + +/datum/action/cooldown/spell/pointed/New(Target) + . = ..() + if(!active_msg) + active_msg = "You prepare to use [src] on a target..." + if(!deactive_msg) + deactive_msg = "You dispel [src]." + +/datum/action/cooldown/spell/pointed/set_click_ability(mob/on_who) + . = ..() + if(!.) + return + + on_activation(on_who) + +// Note: Destroy() calls Remove(), Remove() calls unset_click_ability() if our spell is active. +/datum/action/cooldown/spell/pointed/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + + on_deactivation(on_who, refund_cooldown = refund_cooldown) + +/datum/action/cooldown/spell/pointed/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + on_deactivation(owner, refund_cooldown = FALSE) + + if(owner && get_dist(get_turf(owner), get_turf(cast_on)) > cast_range) + cast_on.balloon_alert(owner, "too far away!") + return . | SPELL_CANCEL_CAST + +/// Called when the spell is activated / the click ability is set to our spell +/datum/action/cooldown/spell/pointed/proc/on_activation(mob/on_who) + SHOULD_CALL_PARENT(TRUE) + + to_chat(on_who, span_notice("[active_msg] Left-click to cast the spell on a target!")) + build_all_button_icons() + return TRUE + +/// Called when the spell is deactivated / the click ability is unset from our spell +/datum/action/cooldown/spell/pointed/proc/on_deactivation(mob/on_who, refund_cooldown = TRUE) + SHOULD_CALL_PARENT(TRUE) + + if(refund_cooldown) + // Only send the "deactivation" message if they're willingly disabling the ability + to_chat(on_who, span_notice("[deactive_msg]")) + build_all_button_icons() + return TRUE + +/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/click_target) + + var/atom/aim_assist_target + if(aim_assist && isturf(click_target)) + // Find any human in the list. We aren't picky, it's aim assist after all + aim_assist_target = locate(/mob/living/carbon/human) in click_target + if(!aim_assist_target) + // If we didn't find a human, we settle for any living at all + aim_assist_target = locate(/mob/living) in click_target + + return ..(caller, params, aim_assist_target || click_target) + +/datum/action/cooldown/spell/pointed/is_valid_target(atom/cast_on) + if(cast_on == owner) + to_chat(owner, span_warning("You cannot cast [src] on yourself!")) + return FALSE + + return TRUE + +/** + * ### Pointed projectile spells + * + * Pointed spells that, instead of casting a spell directly on the target that's clicked, + * will instead fire a projectile pointed at the target's direction. + */ +/datum/action/cooldown/spell/pointed/projectile + /// What projectile we create when we shoot our spell. + var/obj/item/projectile/magic/projectile_type = /obj/item/projectile/magic/teleport + /// How many projectiles we can fire per cast. Not all at once, per click, kinda like charges + var/projectile_amount = 1 + /// How many projectiles we have yet to fire, based on projectile_amount + var/current_amount = 0 + /// How many projectiles we fire every fire_projectile() call. + /// Unwise to change without overriding or extending ready_projectile. + var/projectiles_per_fire = 1 + +/datum/action/cooldown/spell/pointed/projectile/New(Target) + . = ..() + if(projectile_amount > 1) + unset_after_click = FALSE + +/datum/action/cooldown/spell/pointed/projectile/is_valid_target(atom/cast_on) + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/on_activation(mob/on_who) + . = ..() + if(!.) + return + + current_amount = projectile_amount + +/datum/action/cooldown/spell/pointed/projectile/on_deactivation(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(projectile_amount > 1 && current_amount) + StartCooldown(cooldown_time * ((projectile_amount - current_amount) / projectile_amount)) + current_amount = 0 + +// cast_on is a turf, or atom target, that we clicked on to fire at. +/datum/action/cooldown/spell/pointed/projectile/cast(atom/cast_on) + . = ..() + if(!isturf(owner.loc)) + return FALSE + + var/turf/caster_turf = get_turf(owner) + // Get the tile infront of the caster, based on their direction + var/turf/caster_front_turf = get_step(owner, owner.dir) + + fire_projectile(cast_on) + owner.newtonian_move(get_dir(caster_front_turf, caster_turf)) + if(current_amount <= 0) + unset_click_ability(owner, refund_cooldown = FALSE) + + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/after_cast(atom/cast_on) + . = ..() + if(current_amount > 0) + // We still have projectiles to cast! + // Reset our cooldown and let them fire away + reset_spell_cooldown() + +/datum/action/cooldown/spell/pointed/projectile/proc/fire_projectile(atom/target) + current_amount-- + for(var/i in 1 to projectiles_per_fire) + var/obj/item/projectile/to_fire = new projectile_type() + ready_projectile(to_fire, target, owner, i) + SEND_SIGNAL(owner, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) + to_fire.fire() + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/proc/ready_projectile(obj/item/projectile/to_fire, atom/target, mob/user, iteration) + to_fire.firer = owner + to_fire.fired_from = get_turf(owner) + to_fire.preparePixelProjectile(target, owner) + RegisterSignal(to_fire, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_cast_hit)) + + if(istype(to_fire, /obj/item/projectile/magic)) + var/obj/item/projectile/magic/magic_to_fire = to_fire + magic_to_fire.antimagic_flags = antimagic_flags + +/// Signal proc for whenever the projectile we fire hits someone. +/// Pretty much relays to the spell when the projectile actually hits something. +/datum/action/cooldown/spell/pointed/projectile/proc/on_cast_hit(atom/source, mob/firer, atom/hit, angle) + SIGNAL_HANDLER + + SEND_SIGNAL(src, COMSIG_SPELL_PROJECTILE_HIT, hit, firer, source) diff --git a/code/modules/spells/spell_types/pointed/abyssal_gaze.dm b/code/modules/spells/spell_types/pointed/abyssal_gaze.dm new file mode 100644 index 000000000000..9e3171acfac2 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/abyssal_gaze.dm @@ -0,0 +1,52 @@ +/datum/action/cooldown/spell/pointed/abyssal_gaze + name = "Abyssal Gaze" + desc = "This spell instills a deep terror in your target, temporarily chilling and blinding it." + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "abyssal_gaze" + + school = SCHOOL_EVOCATION + cooldown_time = 75 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY + + cast_range = 5 + active_msg = "You prepare to instill a deep terror in a target..." + + /// The duration of the blind on our target + var/blind_duration = 4 SECONDS + /// The amount of temperature we take from our target + var/amount_to_cool = 200 + +/datum/action/cooldown/spell/pointed/abyssal_gaze/is_valid_target(atom/cast_on) + return iscarbon(target) + +/datum/action/cooldown/spell/pointed/abyssal_gaze/cast(mob/living/carbon/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(owner, span_warning("The spell had no effect!")) + to_chat(cast_on, span_warning("You feel a freezing darkness closing in on you, but it rapidly dissipates.")) + return FALSE + + to_chat(cast_on, span_userdanger("A freezing darkness surrounds you...")) + cast_on.playsound_local(get_turf(cast_on), 'sound/hallucinations/i_see_you1.ogg', 50, 1) + owner.playsound_local(get_turf(owner), 'sound/effects/ghost2.ogg', 50, 1) + cast_on.become_blind(ABYSSAL_GAZE_BLIND) + addtimer(CALLBACK(src, PROC_REF(cure_blindness), cast_on), blind_duration) + cast_on.adjust_bodytemperature(-amount_to_cool) + +/** + * cure_blidness: Cures Abyssal Gaze blindness from the target + * + * Arguments: + * * target The mob that is being cured of the blindness. + */ +/datum/action/cooldown/spell/pointed/abyssal_gaze/proc/cure_blindness(mob/living/carbon/cast_on) + if(QDELETED(cast_on) || !istype(cast_on)) + return + + cast_on.cure_blind(ABYSSAL_GAZE_BLIND) diff --git a/code/modules/spells/spell_types/pointed/barnyard.dm b/code/modules/spells/spell_types/pointed/barnyard.dm new file mode 100644 index 000000000000..0dacc365819c --- /dev/null +++ b/code/modules/spells/spell_types/pointed/barnyard.dm @@ -0,0 +1,55 @@ +/datum/action/cooldown/spell/pointed/barnyardcurse + name = "Curse of the Barnyard" + desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." + button_icon_state = "barn" + ranged_mousepointer = 'icons/effects/mouse_pointers/barn_target.dmi' + + school = SCHOOL_TRANSMUTATION + cooldown_time = 15 SECONDS + cooldown_reduction_per_rank = 3 SECONDS + + invocation = "KN'A FTAGHU, PUCK 'BTHNK!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + active_msg = "You prepare to curse a target..." + deactive_msg = "You dispel the curse." + +/datum/action/cooldown/spell/pointed/barnyardcurse/is_valid_target(atom/cast_on) + . = ..() + if(!.) + return FALSE + if(!ishuman(cast_on)) + return FALSE + + var/mob/living/carbon/human/human_target = cast_on + if(!human_target.wear_mask) + return TRUE + + return !(human_target.wear_mask.type in GLOB.cursed_animal_masks) + +/datum/action/cooldown/spell/pointed/barnyardcurse/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + cast_on.visible_message( + span_danger("[cast_on]'s face bursts into flames, which instantly burst outward, leaving [cast_on.p_them()] unharmed!"), + span_danger("Your face starts burning up, but the flames are repulsed by your anti-magic protection!"), + ) + to_chat(owner, span_warning("The spell had no effect!")) + return FALSE + + var/chosen_type = pick(GLOB.cursed_animal_masks) + var/obj/item/clothing/mask/cursed_mask = new chosen_type(get_turf(target)) + + cast_on.visible_message( + span_danger("[target]'s face bursts into flames, and a barnyard animal's head takes its place!"), + span_userdanger("Your face burns up, and shortly after the fire you realise you have the face of a [cursed_mask]!"), + ) + + // Can't drop? Nuke it + if(!cast_on.dropItemToGround(cast_on.wear_mask)) + qdel(cast_on.wear_mask) + + cast_on.equip_to_slot_if_possible(cursed_mask, ITEM_SLOT_MASK, TRUE, TRUE) + cast_on.flash_act() + return TRUE diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm index 9aa3d193960c..f4c9b9c0958a 100644 --- a/code/modules/spells/spell_types/pointed/blind.dm +++ b/code/modules/spells/spell_types/pointed/blind.dm @@ -1,34 +1,43 @@ -/obj/effect/proc_holder/spell/pointed/trigger/blind +/datum/action/cooldown/spell/pointed/blind name = "Blind" desc = "This spell temporarily blinds a single target." - school = "transmutation" - charge_max = 300 - clothes_req = FALSE - invocation = "STI KALY" - invocation_type = SPELL_INVOCATION_WHISPER - message = span_notice("Your eyes cry out in pain!") - cooldown_min = 50 //12 deciseconds reduction per rank + button_icon_state = "blind" ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi' - action_icon_state = "blind" - active_msg = "You prepare to blind a target..." -/obj/effect/proc_holder/spell/targeted/inflict_handler/blind - amt_eye_blind = 10 - amt_eye_blurry = 20 sound = 'sound/magic/blind.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 6.25 SECONDS -/obj/effect/proc_holder/spell/targeted/genetic/blind - mutations = list(BLINDMUT) - duration = 300 - charge_max = 400 // needs to be higher than the duration or it'll be permanent - sound = 'sound/magic/blind.ogg' + invocation = "STI KALY" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + active_msg = "You prepare to blind a target..." -/obj/effect/proc_holder/spell/pointed/trigger/blind/can_target(atom/target, mob/user, silent) + /// The amount of blind to apply + var/eye_blind_duration = 20 SECONDS + /// The amount of blurriness to apply + var/eye_blur_duration = 40 SECONDS + +/datum/action/cooldown/spell/pointed/blind/is_valid_target(atom/cast_on) . = ..() if(!.) return FALSE - if(!isliving(target)) - if(!silent) - to_chat(user, span_warning("You can only blind living beings!")) + if(!ishuman(cast_on)) + return FALSE + + var/mob/living/carbon/human/human_target = cast_on + return !is_blind(human_target) + +/datum/action/cooldown/spell/pointed/blind/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_notice("Your eye itches, but it passes momentarily.")) + to_chat(owner, span_warning("The spell had no effect!")) return FALSE + + to_chat(cast_on, span_warning("Your eyes cry out in pain!")) + cast_on.adjust_blindness(eye_blind_duration) + cast_on.blur_eyes(eye_blur_duration) return TRUE diff --git a/code/modules/spells/spell_types/pointed/dominate.dm b/code/modules/spells/spell_types/pointed/dominate.dm new file mode 100644 index 000000000000..7d2c587a222d --- /dev/null +++ b/code/modules/spells/spell_types/pointed/dominate.dm @@ -0,0 +1,51 @@ +/datum/action/cooldown/spell/pointed/dominate + name = "Dominate" + desc = "This spell dominates the mind of a lesser creature to the will of Nar'Sie, \ + allying it only to her direct followers." + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "dominate" + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + + school = SCHOOL_EVOCATION + cooldown_time = 1 MINUTES + invocation_type = INVOCATION_NONE + spell_requirements = NONE + // An UNHOLY, MAGIC SPELL that INFLUECNES THE MIND - all things work here, logically + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND + + cast_range = 7 + active_msg = "You prepare to dominate the mind of a target..." + +/datum/action/cooldown/spell/pointed/dominate/is_valid_target(atom/cast_on) + if(!isanimal(cast_on)) + return FALSE + + var/mob/living/simple_animal/animal = cast_on + if(animal.mind) + return FALSE + if(animal.stat == DEAD) + return FALSE + if(animal.sentience_type != SENTIENCE_ORGANIC) + return FALSE + if("cult" in animal.faction) + return FALSE + if(HAS_TRAIT(animal, TRAIT_HOLY)) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/dominate/cast(mob/living/simple_animal/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_warning("Your feel someone attempting to subject your mind to terrible machinations!")) + to_chat(owner, span_warning("[cast_on] resists your domination!")) + return FALSE + + var/turf/cast_turf = get_turf(cast_on) + cast_on.add_atom_colour("#990000", FIXED_COLOUR_PRIORITY) + cast_on.faction |= "cult" + playsound(cast_turf, 'sound/effects/ghost.ogg', 100, TRUE) + new /obj/effect/temp_visual/cult/sac(cast_turf) diff --git a/code/modules/spells/spell_types/pointed/finger_guns.dm b/code/modules/spells/spell_types/pointed/finger_guns.dm new file mode 100644 index 000000000000..54c82578dcf5 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/finger_guns.dm @@ -0,0 +1,50 @@ +/datum/action/cooldown/spell/pointed/projectile/finger_guns + name = "Finger Guns" + desc = "Shoot up to three mimed bullets from your fingers that damage and mute their targets. \ + Can't be used if you have something in your hands." + background_icon_state = "bg_mime" + overlay_icon_state = "bg_mime_border" + button_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "finger_guns0" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + + invocation = "" + invocation_type = INVOCATION_EMOTE + invocation_self_message = span_danger("You fire your finger gun!") + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + base_icon_state = "finger_guns" + active_msg = "You draw your fingers!" + deactive_msg = "You put your fingers at ease. Another time." + cast_range = 20 + projectile_type = /obj/item/projectile/bullet/mime + projectile_amount = 3 + +/datum/action/cooldown/spell/pointed/projectile/finger_guns/can_cast_spell(feedback = TRUE) + if(invocation_type == INVOCATION_EMOTE) + if(!ishuman(owner)) + return FALSE + + var/mob/living/carbon/human/human_owner = owner + if(human_owner.incapacitated()) + if(feedback) + to_chat(owner, span_warning("You can't properly point your fingers while incapacitated.")) + return FALSE + if(human_owner.get_active_held_item()) + if(feedback) + to_chat(owner, span_warning("You can't properly fire your finger guns with something in your hand.")) + return FALSE + + return ..() + +/datum/action/cooldown/spell/pointed/projectile/finger_guns/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] fires [cast_on.p_their()] finger gun!") diff --git a/code/modules/spells/spell_types/pointed/fireball.dm b/code/modules/spells/spell_types/pointed/fireball.dm new file mode 100644 index 000000000000..7a23abe6ad21 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/fireball.dm @@ -0,0 +1,23 @@ +/datum/action/cooldown/spell/pointed/projectile/fireball + name = "Fireball" + desc = "This spell fires an explosive fireball at a target." + button_icon_state = "fireball0" + + sound = 'sound/magic/fireball.ogg' + school = SCHOOL_EVOCATION + cooldown_time = 6 SECONDS + cooldown_reduction_per_rank = 1 SECONDS // 1 second reduction per rank + + invocation = "ONI SOMA!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + base_icon_state = "fireball" + active_msg = "You prepare to cast your fireball spell!" + deactive_msg = "You extinguish your fireball... for now." + cast_range = 8 + projectile_type = /obj/item/projectile/magic/fireball + +/datum/action/cooldown/spell/pointed/projectile/fireball/ready_projectile(obj/item/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + to_fire.range = (6 + 2 * spell_level) diff --git a/code/modules/spells/spell_types/pointed/lightning_bolt.dm b/code/modules/spells/spell_types/pointed/lightning_bolt.dm new file mode 100644 index 000000000000..0c69f43eaa4a --- /dev/null +++ b/code/modules/spells/spell_types/pointed/lightning_bolt.dm @@ -0,0 +1,44 @@ +/datum/action/cooldown/spell/pointed/projectile/lightningbolt + name = "Lightning Bolt" + desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." + button_icon_state = "lightning" + active_overlay_icon_state = "bg_spell_border_active_yellow" + + sound = 'sound/magic/lightningbolt.ogg' + school = SCHOOL_EVOCATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 2 SECONDS + + invocation = "P'WAH, UNLIM'TED P'WAH!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + base_icon_state = "lightning" + active_msg = "You energize your hands with arcane lightning!" + deactive_msg = "You let the energy flow out of your hands back into yourself..." + projectile_type = /obj/item/projectile/magic/aoe/lightning + + /// The range the bolt itself (different to the range of the projectile) + var/bolt_range = 15 + /// The power of the bolt itself + var/bolt_power = 20000 + /// The flags the bolt itself takes when zapping someone + var/bolt_flags = TESLA_MOB_DAMAGE + +/datum/action/cooldown/spell/pointed/projectile/lightningbolt/Grant(mob/grant_to) + . = ..() + ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, type) //HELL YEAHHH, LIGHTNING BOLT! + +/datum/action/cooldown/spell/pointed/projectile/lightningbolt/Remove(mob/living/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_SHOCKIMMUNE, type) //FUCK + return ..() + +/datum/action/cooldown/spell/pointed/projectile/lightningbolt/ready_projectile(obj/item/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + if(!istype(to_fire, /obj/item/projectile/magic/aoe/lightning)) + return + + var/obj/item/projectile/magic/aoe/lightning/bolt = to_fire + bolt.tesla_range = bolt_range + bolt.tesla_power = bolt_power + bolt.tesla_flags = bolt_flags diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm new file mode 100644 index 000000000000..2580dc9910a8 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm @@ -0,0 +1,133 @@ +/datum/action/cooldown/spell/pointed/mind_transfer + name = "Mind Swap" + desc = "This spell allows the user to switch bodies with a target next to him." + button_icon_state = "mindswap" + ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi' + + school = SCHOOL_TRANSMUTATION + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 10 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_MIND|SPELL_CASTABLE_AS_BRAIN + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND + + invocation = "GIN'YU CAPAN" + invocation_type = INVOCATION_WHISPER + + active_msg = "You prepare to swap minds with a target..." + deactive_msg = "You dispel mind swap." + cast_range = 1 + + /// If TRUE, we cannot mindswap into mobs with minds if they do not currently have a key / player. + var/target_requires_key = TRUE + /// If TRUE, we cannot mindswap into people without a mind. + /// You may be wondering "What's the point of mindswap if the target has no mind"? + /// Primarily for debugging - targets hit with this set to FALSE will init a mind, then do the swap. + var/target_requires_mind = TRUE + /// For how long is the caster stunned for after the spell + var/unconscious_amount_caster = 40 SECONDS + /// For how long is the victim stunned for after the spell + var/unconscious_amount_victim = 40 SECONDS + /// List of mobs we cannot mindswap into. + var/static/list/mob/living/blacklisted_mobs = typecacheof(list( + /mob/living/brain, + /mob/living/silicon/pai, + /mob/living/simple_animal/slaughter, + /mob/living/simple_animal/hostile/megafauna, + )) + +/datum/action/cooldown/spell/pointed/mind_transfer/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(!isliving(owner)) + return FALSE + if(owner.suiciding) + if(feedback) + to_chat(owner, span_warning("You're killing yourself! You can't concentrate enough to do this!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/mind_transfer/is_valid_target(atom/cast_on) + . = ..() + if(!.) + return FALSE + + if(!isliving(cast_on)) + to_chat(owner, span_warning("You can only swap minds with living beings!")) + return FALSE + if(is_type_in_typecache(cast_on, blacklisted_mobs)) + to_chat(owner, span_warning("This creature is too [pick("powerful", "strange", "arcane", "obscene")] to control!")) + return FALSE + if(isguardian(cast_on)) + var/mob/living/simple_animal/hostile/guardian/stand = cast_on + if(stand.summoner && stand.summoner == owner) + to_chat(owner, span_warning("Swapping minds with your own guardian would just put you back into your own head!")) + return FALSE + + var/mob/living/living_target = cast_on + if(living_target.stat == DEAD) + to_chat(owner, span_warning("You don't particularly want to be dead!")) + return FALSE + if(!living_target.mind && target_requires_mind) + to_chat(owner, span_warning("[living_target.p_theyve(TRUE)] doesn't appear to have a mind to swap into!")) + return FALSE + if(!living_target.key && target_requires_key) + to_chat(owner, span_warning("[living_target.p_theyve(TRUE)] appear[living_target.p_s()] to be catatonic! \ + Not even magic can affect [living_target.p_their()] vacant mind.")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/mind_transfer/cast(mob/living/cast_on) + . = ..() + swap_minds(owner, cast_on) + +/datum/action/cooldown/spell/pointed/mind_transfer/proc/swap_minds(mob/living/caster, mob/living/cast_on) + + var/mob/living/to_swap = cast_on + if(isguardian(cast_on)) + var/mob/living/simple_animal/hostile/guardian/stand = cast_on + if(stand.summoner) + to_swap = stand.summoner + + // Gives the target a mind if we don't require one and they don't have one + if(!to_swap.mind && !target_requires_mind) + to_swap.mind_initialize() + + var/datum/mind/mind_to_swap = to_swap.mind + if(to_swap.can_block_magic(antimagic_flags) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/wizard) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/cult) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/changeling) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/rev) \ + || mind_to_swap.key?[1] == "@" \ + ) + to_chat(caster, span_warning("[to_swap.p_their(TRUE)] mind is resisting your spell!")) + return FALSE + + // MIND TRANSFER BEGIN + + var/datum/mind/caster_mind = caster.mind + var/datum/mind/to_swap_mind = to_swap.mind + + var/to_swap_key = to_swap.key + + caster_mind.transfer_to(to_swap) + to_swap_mind.transfer_to(caster) + + // Just in case the swappee's key wasn't grabbed by transfer_to... + if(to_swap_key) + caster.key = to_swap_key + + // MIND TRANSFER END + + // Now we knock both mobs out for a time. + caster.Unconscious(unconscious_amount_caster) + to_swap.Unconscious(unconscious_amount_victim) + + // Only the caster and victim hear the sounds, + // that way no one knows for sure if the swap happened + SEND_SOUND(caster, sound('sound/magic/mandswap.ogg')) + SEND_SOUND(to_swap, sound('sound/magic/mandswap.ogg')) + + return TRUE diff --git a/code/modules/spells/spell_types/pointed/pointed.dm b/code/modules/spells/spell_types/pointed/pointed.dm deleted file mode 100644 index 3e65690541fd..000000000000 --- a/code/modules/spells/spell_types/pointed/pointed.dm +++ /dev/null @@ -1,105 +0,0 @@ -/obj/effect/proc_holder/spell/pointed - name = "pointed spell" - ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' - action_icon_state = "projectile" - /// Message showing to the spell owner upon deactivating pointed spell. - var/deactive_msg = "You dispel the magic..." - /// Message showing to the spell owner upon activating pointed spell. - var/active_msg = "You prepare to use the spell on a target..." - /// Variable dictating if the user is allowed to cast a spell on himself. - var/self_castable = FALSE - /// Variable dictating if the spell will use turf based aim assist - var/aim_assist = TRUE - -/obj/effect/proc_holder/spell/pointed/Click() - var/mob/living/user = usr - if(!istype(user)) - return - var/msg - if(!can_cast(user)) - msg = span_warning("You can no longer cast [name]!") - remove_ranged_ability(msg) - return - if(active) - msg = span_notice("[deactive_msg]") - remove_ranged_ability(msg) - else - msg = span_notice("[active_msg] Left-click to activate spell on a target!") - add_ranged_ability(user, msg, TRUE) - -/obj/effect/proc_holder/spell/pointed/on_lose(mob/living/user) - remove_ranged_ability() - -/obj/effect/proc_holder/spell/pointed/remove_ranged_ability(msg) - . = ..() - on_deactivation(ranged_ability_user) - -/obj/effect/proc_holder/spell/pointed/add_ranged_ability(mob/living/user, msg, forced) - . = ..() - on_activation(user) - -/** - * on_activation: What happens upon pointed spell activation. - * - * Arguments: - * * user The mob interacting owning the spell. - */ -/obj/effect/proc_holder/spell/pointed/proc/on_activation(mob/user) - return - -/** - * on_activation: What happens upon pointed spell deactivation. - * - * Arguments: - * * user The mob interacting owning the spell. - */ -/obj/effect/proc_holder/spell/pointed/proc/on_deactivation(mob/user) - return - -/obj/effect/proc_holder/spell/pointed/update_icon() - if(!action) - return - if(active) - action.button_icon_state = "[action_icon_state]1" - else - action.button_icon_state = "[action_icon_state]" - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return TRUE - if(aim_assist && isturf(target)) - var/list/possible_targets = list() - for(var/A in target) - if(intercept_check(caller, A, TRUE)) - possible_targets += A - if(possible_targets.len == 1) - target = possible_targets[1] - if(!intercept_check(caller, target)) - return TRUE - if(!cast_check(FALSE, caller)) - return TRUE - perform(list(target), user = caller) - remove_ranged_ability() - return TRUE // Do not do any underlying actions after the spell cast - -/** - * intercept_check: Specific spell checks for InterceptClickOn() targets. - * - * Arguments: - * * user The mob using the ranged spell via intercept. - * * target The atom that is being targeted by the spell via intercept. - * * silent If the checks should produce not any feedback messages for the user. - */ -/obj/effect/proc_holder/spell/pointed/proc/intercept_check(mob/user, atom/target, silent = FALSE) - if(!self_castable && target == user) - if(!silent) - to_chat(user, span_warning("You cannot cast the spell on yourself!")) - return FALSE - if(!(target in view_or_range(range, user, selection_type))) - if(!silent) - to_chat(user, span_warning("[target.p_theyre(TRUE)] too far away!")) - return FALSE - if(!can_target(target, user, silent)) - return FALSE - return TRUE diff --git a/code/modules/spells/spell_types/pointed/spell_cards.dm b/code/modules/spells/spell_types/pointed/spell_cards.dm new file mode 100644 index 000000000000..85621df427db --- /dev/null +++ b/code/modules/spells/spell_types/pointed/spell_cards.dm @@ -0,0 +1,81 @@ +/datum/action/cooldown/spell/pointed/projectile/spell_cards + name = "Spell Cards" + desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" + button_icon_state = "spellcard" + click_cd_override = 1 + + school = SCHOOL_EVOCATION + cooldown_time = 5 SECONDS + cooldown_reduction_per_rank = 1 SECONDS + + invocation = "Sigi'lu M'Fan 'Tasia!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + cast_range = 40 + projectile_type = /obj/item/projectile/magic/spellcard + projectile_amount = 5 + projectiles_per_fire = 7 + + /// A weakref to the mob we're currently targeting with the lockon component. + var/datum/weakref/current_target_weakref + /// The turn rate of the spell cards in flight. (They track onto locked on targets) + var/projectile_turnrate = 10 + /// The homing spread of the spell cards in flight. + var/projectile_pixel_homing_spread = 32 + /// The initial spread of the spell cards when fired. + var/projectile_initial_spread_amount = 30 + /// The location spread of the spell cards when fired. + var/projectile_location_spread_amount = 12 + /// A ref to our lockon component, which is created and destroyed on activation and deactivation. + var/datum/component/lockon_aiming/lockon_component + +/datum/action/cooldown/spell/pointed/projectile/spell_cards/Destroy() + QDEL_NULL(lockon_component) + return ..() + +/datum/action/cooldown/spell/pointed/projectile/spell_cards/on_activation(mob/on_who) + . = ..() + if(!.) + return + + QDEL_NULL(lockon_component) + lockon_component = owner.AddComponent( \ + /datum/component/lockon_aiming, \ + range = 5, \ + typecache = GLOB.typecache_living, \ + amount = 1, \ + when_locked = CALLBACK(src, PROC_REF(on_lockon_component))) + +/datum/action/cooldown/spell/pointed/projectile/spell_cards/proc/on_lockon_component(list/locked_weakrefs) + if(!length(locked_weakrefs)) + current_target_weakref = null + return + current_target_weakref = locked_weakrefs[1] + var/atom/real_target = current_target_weakref.resolve() + if(real_target) + owner.face_atom(real_target) + +/datum/action/cooldown/spell/pointed/projectile/spell_cards/on_deactivation(mob/on_who, refund_cooldown = TRUE) + . = ..() + QDEL_NULL(lockon_component) + +/datum/action/cooldown/spell/pointed/projectile/spell_cards/ready_projectile(obj/item/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + if(current_target_weakref) + var/atom/real_target = current_target_weakref?.resolve() + if(real_target && get_dist(real_target, user) < 7) + to_fire.homing_turn_speed = projectile_turnrate + to_fire.homing_inaccuracy_min = projectile_pixel_homing_spread + to_fire.homing_inaccuracy_max = projectile_pixel_homing_spread + to_fire.set_homing_target(real_target) + + var/rand_spr = rand() + var/total_angle = projectile_initial_spread_amount * 2 + var/adjusted_angle = total_angle - ((projectile_initial_spread_amount / projectiles_per_fire) * 0.5) + var/one_fire_angle = adjusted_angle / projectiles_per_fire + var/current_angle = iteration * one_fire_angle * rand_spr - (projectile_initial_spread_amount / 2) + + to_fire.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) + to_fire.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) + to_fire.preparePixelProjectile(target, user, null, current_angle) diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm deleted file mode 100644 index 3860173e4916..000000000000 --- a/code/modules/spells/spell_types/projectile.dm +++ /dev/null @@ -1,134 +0,0 @@ -/obj/item/projectile/magic/spell - name = "custom spell projectile" - var/list/ignored_factions //Do not hit these - var/check_holy = FALSE - var/check_antimagic = FALSE - var/trigger_range = 0 //How far we do we need to be to hit - var/linger = FALSE //Can't hit anything but the intended target - - var/trail = FALSE //if it leaves a trail - var/trail_lifespan = 0 //deciseconds - var/trail_icon = 'icons/obj/wizard.dmi' - var/trail_icon_state = "trail" - -//todo unify this and magic/aoe under common path -/obj/item/projectile/magic/spell/Range() - if(trigger_range > 1) - for(var/mob/living/L in range(trigger_range, get_turf(src))) - if(can_hit_target(L, ignore_loc = TRUE)) - return Bump(L) - . = ..() - -/obj/item/projectile/magic/spell/Moved(atom/OldLoc, Dir) - . = ..() - if(trail) - create_trail() - -/obj/item/projectile/magic/spell/proc/create_trail() - if(!trajectory) - return - var/datum/point/vector/previous = trajectory.return_vector_after_increments(1,-1) - var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) - trail.pixel_x = previous.return_px() - trail.pixel_y = previous.return_py() - trail.icon = trail_icon - trail.icon_state = trail_icon_state - //might be changed to temp overlay - trail.density = FALSE - trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - QDEL_IN(trail, trail_lifespan) - -/obj/item/projectile/magic/spell/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) - . = ..() - if(linger && target != original) - return FALSE - if(ismob(target)) - var/mob/M = target - if(M.anti_magic_check(check_antimagic, check_holy)) - return FALSE - if(ignored_factions && ignored_factions.len && faction_check(M.faction,ignored_factions)) - return FALSE - -/obj/item/projectile/magic/spell/on_hit(mob/living/carbon/target) - .=..() - if(target.anti_magic_check()) - target.visible_message(span_warning("[src] vanishes on contact with [target]!")) - return BULLET_ACT_BLOCK - - -//NEEDS MAJOR CODE CLEANUP. - -/obj/effect/proc_holder/spell/targeted/projectile - name = "Projectile" - desc = "This spell summons projectiles which try to hit the targets." - - - - var/proj_type = /obj/item/projectile/magic/spell //IMPORTANT use only subtypes of this - - - var/update_projectile = FALSE //So you want to admin abuse magic bullets ? This is for you - //Below only apply if update_projectile is true - var/proj_icon = 'icons/obj/projectiles.dmi' - var/proj_icon_state = "spell" - var/proj_name = "a spell projectile" - var/proj_trail = FALSE //if it leaves a trail - var/proj_trail_lifespan = 0 //deciseconds - var/proj_trail_icon = 'icons/obj/wizard.dmi' - var/proj_trail_icon_state = "trail" - var/proj_lingering = FALSE //if it lingers or disappears upon hitting an obstacle - var/proj_homing = TRUE //if it follows the target - var/proj_insubstantial = FALSE //if it can pass through dense objects or not - var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target) - var/proj_lifespan = 15 //in deciseconds * proj_step_delay - var/proj_step_delay = 1 //lower = faster - var/list/ignore_factions = list() //Faction types that will be ignored - var/check_antimagic = TRUE - var/check_holy = FALSE - -/obj/effect/proc_holder/spell/targeted/projectile/proc/fire_projectile(atom/target, mob/user) - var/obj/item/projectile/magic/spell/projectile = new proj_type() - - if(update_projectile) - //Generally these should already be set on the projectile, this is mostly here for varedited spells. - projectile.icon = proj_icon - projectile.icon_state = proj_icon_state - projectile.name = proj_name - if(proj_insubstantial) - projectile.movement_type |= UNSTOPPABLE - if(proj_homing) - projectile.homing = TRUE - projectile.homing_turn_speed = 360 //Perfect tracking - if(proj_lingering) - projectile.linger = TRUE - projectile.trigger_range = proj_trigger_range - projectile.ignored_factions = ignore_factions - projectile.range = proj_lifespan - projectile.speed = proj_step_delay - projectile.trail = proj_trail - projectile.trail_lifespan = proj_trail_lifespan - projectile.trail_icon = proj_trail_icon - projectile.trail_icon_state = proj_trail_icon_state - - projectile.preparePixelProjectile(target,user) - if(projectile.homing) - projectile.set_homing_target(target) - projectile.fire() - -/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) - playMagSound() - for(var/atom/target in targets) - fire_projectile(target, user) - -//This one just pops one projectile in direction user is facing, irrelevant of max_targets etc -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire - name = "Dumbfire projectile" - -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/choose_targets(mob/user = usr) - var/turf/T = get_turf(user) - for(var/i = 1; i < range; i++) - var/turf/new_turf = get_step(T, user.dir) - if(new_turf.density) - break - T = new_turf - perform(list(T),user = user) diff --git a/code/modules/spells/spell_types/projectile/_basic_projectile.dm b/code/modules/spells/spell_types/projectile/_basic_projectile.dm new file mode 100644 index 000000000000..77a473532821 --- /dev/null +++ b/code/modules/spells/spell_types/projectile/_basic_projectile.dm @@ -0,0 +1,29 @@ +/** + * ## Basic Projectile spell + * + * Simply fires specified projectile type the direction the caster is facing. + * + * Behavior could / should probably be unified with pointed projectile spells + * and aoe projectile spells in the future. + */ +/datum/action/cooldown/spell/basic_projectile + /// How far we try to fire the basic projectile. Blocked by dense objects. + var/projectile_range = 7 + /// The projectile type fired at all people around us + var/obj/item/projectile/projectile_type = /obj/item/projectile/magic/aoe/magic_missile + +/datum/action/cooldown/spell/basic_projectile/cast(atom/cast_on) + . = ..() + var/turf/target_turf = get_turf(cast_on) + for(var/i in 1 to projectile_range - 1) + var/turf/next_turf = get_step(target_turf, cast_on.dir) + if(next_turf.density) + break + target_turf = next_turf + + fire_projectile(target_turf, cast_on) + +/datum/action/cooldown/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster) + var/obj/item/projectile/to_fire = new projectile_type() + to_fire.preparePixelProjectile(target, caster) + to_fire.fire() \ No newline at end of file diff --git a/code/modules/spells/spell_types/projectile/juggernaut.dm b/code/modules/spells/spell_types/projectile/juggernaut.dm new file mode 100644 index 000000000000..6129477325f4 --- /dev/null +++ b/code/modules/spells/spell_types/projectile/juggernaut.dm @@ -0,0 +1,14 @@ +/datum/action/cooldown/spell/basic_projectile/juggernaut + name = "Gauntlet Echo" + desc = "Channels energy into your gauntlet - firing its essence forward in a slow moving, yet devastating, attack." + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "cultfist" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + sound = 'sound/weapons/resonator_blast.ogg' + + cooldown_time = 35 SECONDS + spell_requirements = NONE + + projectile_type = /obj/item/projectile/magic/aoe/juggernaut diff --git a/code/modules/spells/spell_types/right_and_wrong.dm b/code/modules/spells/spell_types/right_and_wrong.dm new file mode 100644 index 000000000000..44df1e16c372 --- /dev/null +++ b/code/modules/spells/spell_types/right_and_wrong.dm @@ -0,0 +1,296 @@ +//In this file: Summon Magic/Summon Guns/Summon Events +//and corresponding datum controller for them + +GLOBAL_DATUM(summon_guns, /datum/summon_things_controller) +GLOBAL_DATUM(summon_magic, /datum/summon_things_controller) + +// 1 in 50 chance of getting something really special. +#define SPECIALIST_MAGIC_PROB 2 + +GLOBAL_LIST_INIT(summoned_guns, list( + /obj/item/gun/energy/disabler, + /obj/item/gun/energy/e_gun, + /obj/item/gun/energy/e_gun/advtaser, + /obj/item/gun/energy/laser, + /obj/item/gun/ballistic/revolver, + /obj/item/gun/ballistic/revolver/detective, + /obj/item/gun/ballistic/automatic/pistol/deagle/camo, + /obj/item/gun/ballistic/automatic/gyropistol, + /obj/item/gun/energy/pulse, + /obj/item/gun/ballistic/automatic/pistol/suppressed, + /obj/item/gun/ballistic/shotgun/doublebarrel, + /obj/item/gun/ballistic/shotgun, + /obj/item/gun/ballistic/shotgun/automatic/combat, + /obj/item/gun/ballistic/automatic/ar, + /obj/item/gun/ballistic/revolver/mateba, + /obj/item/gun/ballistic/rifle/boltaction, +// /obj/item/gun/ballistic/rifle/boltaction/harpoon, + /obj/item/gun/ballistic/automatic/mini_uzi, + /obj/item/gun/energy/lasercannon, + /obj/item/gun/energy/e_gun/nuclear, + /obj/item/gun/ballistic/automatic/proto, + /obj/item/gun/ballistic/automatic/c20r, + /obj/item/gun/ballistic/automatic/l6_saw, + /obj/item/gun/ballistic/automatic/m90, + /obj/item/gun/energy/alien, + /obj/item/gun/energy/e_gun/dragnet, + /obj/item/gun/energy/e_gun/turret, + /obj/item/gun/energy/pulse/carbine, + /obj/item/gun/energy/decloner, + /obj/item/gun/energy/mindflayer, + /obj/item/gun/energy/kinetic_accelerator, + /obj/item/gun/energy/plasmacutter/adv, + /obj/item/gun/energy/wormhole_projector, + /obj/item/gun/ballistic/automatic/wt550, + /obj/item/gun/ballistic/shotgun/bulldog, + /obj/item/gun/ballistic/revolver/grenadelauncher, + /obj/item/gun/ballistic/revolver/golden, + /obj/item/gun/ballistic/automatic/sniper_rifle, + /obj/item/gun/ballistic/rocketlauncher, + /obj/item/gun/medbeam, + /obj/item/gun/energy/laser/scatter, +// /obj/item/gun/energy/laser/thermal, +// /obj/item/gun/energy/laser/thermal/inferno, +// /obj/item/gun/energy/laser/thermal/cryo, + /obj/item/gun/energy/gravity_gun)) + +//if you add anything that isn't covered by the typepaths below, add it to summon_magic_objective_types +GLOBAL_LIST_INIT(summoned_magic, list( + /obj/item/book/granter/action/spell/fireball, + /obj/item/book/granter/action/spell/smoke, + /obj/item/book/granter/action/spell/blind, + /obj/item/book/granter/action/spell/mindswap, + /obj/item/book/granter/action/spell/forcewall, + /obj/item/book/granter/action/spell/knock, + /obj/item/book/granter/action/spell/barnyard, + /obj/item/book/granter/action/spell/charge, + /obj/item/book/granter/action/spell/summonitem, + /obj/item/book/granter/action/spell/lightningbolt, + /obj/item/gun/magic/wand, + /obj/item/gun/magic/wand/death, + /obj/item/gun/magic/wand/resurrection, + /obj/item/gun/magic/wand/polymorph, + /obj/item/gun/magic/wand/teleport, + /obj/item/gun/magic/wand/door, + /obj/item/gun/magic/wand/fireball, + /obj/item/gun/magic/staff/healing, + /obj/item/gun/magic/staff/door, +// /obj/item/gun/magic/staff/babel, + /obj/item/scrying, + /obj/item/warp_whistle, + /obj/item/immortality_talisman, + /obj/item/melee/ghost_sword)) + +GLOBAL_LIST_INIT(summoned_special_magic, list( + /obj/item/gun/magic/staff/change, + /obj/item/gun/magic/staff/animate, + /obj/item/storage/belt/wands/full, + /obj/item/antag_spawner/contract, + /obj/item/gun/magic/staff/chaos, + /obj/item/necromantic_stone)) + +//everything above except for single use spellbooks, because they are counted separately (and are for basic bitches anyways) +GLOBAL_LIST_INIT(summoned_magic_objectives, list( + /obj/item/antag_spawner/contract, + /obj/item/gun/magic, + /obj/item/immortality_talisman, + /obj/item/melee/ghost_sword, + /obj/item/necromantic_stone, + /obj/item/scrying, + /obj/item/spellbook, + /obj/item/storage/belt/wands/full, + /obj/item/warp_whistle)) + +/* + * Gives [to_equip] a random gun from a list. + */ +/proc/give_guns(mob/living/carbon/human/to_equip) + if(!GLOB.summon_guns) + CRASH("give_guns() was called without a summon guns global datum!") + if(to_equip.stat == DEAD || !to_equip.client || !to_equip.mind) + return + if(iswizard(to_equip) || to_equip.mind.has_antag_datum(/datum/antagonist/survivalist/guns)) + return + + if(!length(to_equip.mind.antag_datums) && prob(GLOB.summon_guns.survivor_probability)) + to_equip.mind.add_antag_datum(/datum/antagonist/survivalist/guns) + to_equip.log_message("was made into a survivalist by summon guns, and trusts no one!", LOG_ATTACK, color = "red") + + var/gun_type = pick(GLOB.summoned_guns) + var/obj/item/gun/spawned_gun = new gun_type(get_turf(to_equip)) + if (istype(spawned_gun)) // The list may contain some non-gun type guns which do not have this proc + spawned_gun.unlock() + playsound(get_turf(to_equip), 'sound/magic/summon_guns.ogg', 50, TRUE) + + var/in_hand = to_equip.put_in_hands(spawned_gun) // not always successful + + to_chat(to_equip, span_warning("\A [spawned_gun] appears [in_hand ? "in your hand" : "at your feet"]!")) + +/* + * Gives [to_equip] a random magical spell from a list. + */ +/proc/give_magic(mob/living/carbon/human/to_equip) + if(!GLOB.summon_magic) + CRASH("give_magic() was called without a summon magic global datum!") + if(to_equip.stat == DEAD || !to_equip.client || !to_equip.mind) + return + if(is_wizard(to_equip) || to_equip.mind.has_antag_datum(/datum/antagonist/survivalist/guns)) + return + + if(!length(to_equip.mind.antag_datums) && prob(GLOB.summon_magic.survivor_probability)) + to_equip.mind.add_antag_datum(/datum/antagonist/survivalist/magic) + to_equip.log_message("was made into a survivalist by summon magic, and trusts no one!", LOG_ATTACK, color = "red") + + var/magic_type = prob(SPECIALIST_MAGIC_PROB) ? pick(GLOB.summoned_special_magic) : pick(GLOB.summoned_magic) + + var/obj/item/spawned_magic = new magic_type(get_turf(to_equip)) + playsound(get_turf(to_equip), 'sound/magic/summon_magic.ogg', 50, TRUE) + + var/in_hand = to_equip.put_in_hands(spawned_magic) + + to_chat(to_equip, span_warning("\A [spawned_magic] appears [in_hand ? "in your hand" : "at your feet"]!")) + if(magic_type in GLOB.summoned_special_magic) + to_chat(to_equip, span_notice("You feel incredibly lucky.")) + +/* + * Triggers Summon Ghosts from [user]. + */ +/proc/summon_ghosts(mob/user) + + var/datum/round_event_control/wizard/ghost/ghost_event = locate() in SSevents.control + if(ghost_event) + if(user) + to_chat(user, span_warning("You summoned ghosts!")) + message_admins("[ADMIN_LOOKUPFLW(user)] summoned ghosts!") + user.log_message("summoned ghosts!", LOG_GAME) + else + message_admins("Summon Ghosts was triggered!") + log_game("Summon Ghosts was triggered!") + ghost_event.runEvent() + else + stack_trace("Unable to run summon ghosts, due to being unable to locate the associated event.") + if(user) + to_chat(user, span_warning("You... try to summon ghosts, but nothing seems to happen. Shame.")) + +/* + * Triggers Summon Magic from [user]. + * Can optionally be passed [survivor_probability], to set the chance of creating survivalists. + * If Summon Magic has already been triggered, gives out magic to everyone again. + */ +/proc/summon_magic(mob/user, survivor_probability = 0) + if(user) + to_chat(user, span_warning("You summoned magic!")) + message_admins("[ADMIN_LOOKUPFLW(user)] summoned magic!") + user.log_message("summoned magic!", LOG_GAME) + else + message_admins("Summon Magic was triggered!") + log_game("Summon Magic was triggered!") + + if(GLOB.summon_magic) + GLOB.summon_magic.survivor_probability = survivor_probability + else + GLOB.summon_magic = new /datum/summon_things_controller(/proc/give_magic, survivor_probability) + GLOB.summon_magic.give_out_gear() + +/* + * Triggers Summon Guns from [user]. + * Can optionally be passed [survivor_probability], to set the chance of creating survivalists. + * If Summon Guns has already been triggered, gives out guns to everyone again. + */ +/proc/summon_guns(mob/user, survivor_probability = 0) + if(user) + to_chat(user, span_warning("You summoned guns!")) + message_admins("[ADMIN_LOOKUPFLW(user)] summoned guns!") + user.log_message("summoned guns!", LOG_GAME) + else + message_admins("Summon Guns was triggered!") + log_game("Summon Guns was triggered!") + + if(GLOB.summon_guns) + GLOB.summon_guns.survivor_probability = survivor_probability + else + GLOB.summon_guns = new /datum/summon_things_controller(/proc/give_guns, survivor_probability) + GLOB.summon_guns.give_out_gear() + +/* + * Triggers Summon Events from [user]. + * If Summon Events has already been triggered, speeds up the event timer. + */ +/proc/summon_events(mob/user) + // Already in wiz-mode? Speed er up + if(SSevents.wizardmode) + SSevents.frequency_upper -= 1 MINUTES //The upper bound falls a minute each time, making the AVERAGE time between events lessen + if(SSevents.frequency_upper < SSevents.frequency_lower) //Sanity + SSevents.frequency_upper = SSevents.frequency_lower + + SSevents.reschedule() + if(user) + to_chat(user, span_warning("You have intensified summon events, causing them to occur more often!")) + message_admins("[ADMIN_LOOKUPFLW(user)] intensified summon events!") + user.log_message("intensified events!", LOG_GAME) + else + log_game("Summon Events was intensified!") + + message_admins("Summon Events intensifies, events will now occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes.") + + // Not in wiz-mode? Get this show on the road + else + SSevents.frequency_lower = 1 MINUTES //1 minute lower bound + SSevents.frequency_upper = 5 MINUTES //5 minutes upper bound + SSevents.toggleWizardmode() + SSevents.reschedule() + if(user) + to_chat(user, span_warning("You have cast summon events!")) + message_admins("[ADMIN_LOOKUPFLW(user)] summoned events!") + user.log_message("summoned events!", LOG_GAME) + else + message_admins("Summon Events was triggered!") + log_game("Summon Events was triggered!") + +#undef SPECIALIST_MAGIC_PROB + +/** + * The "Give everyone in the crew and also latejoins a buncha stuff" controller. + * Used for summon magic and summon guns. + */ +/datum/summon_things_controller + /// Prob. chance someone who is given things will be made a survivalist antagonist. + var/survivor_probability = 0 + /// The proc path we call on someone to equip them with stuff. Cannot function without it. + var/give_proc_path + /// The number of equipment we give to latejoiners, to make sure they catch up if it was casted multiple times. + var/num_to_give_to_latejoiners = 0 + +/datum/summon_things_controller/New(give_proc_path, survivor_probability = 0) + . = ..() + if(isnull(give_proc_path)) + CRASH("[type] was created without a give_proc_path (the proc that gives people stuff)!") + + src.survivor_probability = survivor_probability + src.give_proc_path = give_proc_path + + RegisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED, PROC_REF(gear_up_new_crew)) + +/datum/summon_things_controller/Destroy(force, ...) + . = ..() + UnregisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED) + +/// Calls our give_proc_path on all humans in the player list. +/datum/summon_things_controller/proc/give_out_gear() + num_to_give_to_latejoiners++ + for(var/mob/living/carbon/human/to_equip in GLOB.player_list) + var/turf/turf_check = get_turf(to_equip) + if(turf_check && is_away_level(turf_check.z)) + continue + INVOKE_ASYNC(GLOBAL_PROC, give_proc_path, to_equip) + +/// Signal proc from [COMSIG_GLOB_CREWMEMBER_JOINED]. +/// Calls give_proc_path on latejoiners a number of times (based on num_to_give_to_latejoiners) +/datum/summon_things_controller/proc/gear_up_new_crew(datum/source, mob/living/new_crewmember, rank) + SIGNAL_HANDLER + + if(!ishuman(new_crewmember)) + return + + for(var/i in 1 to num_to_give_to_latejoiners) + INVOKE_ASYNC(GLOBAL_PROC, give_proc_path, new_crewmember) diff --git a/code/modules/spells/spell_types/rod_form.dm b/code/modules/spells/spell_types/rod_form.dm deleted file mode 100644 index 091190cf677a..000000000000 --- a/code/modules/spells/spell_types/rod_form.dm +++ /dev/null @@ -1,52 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/rod_form - name = "Rod Form" - desc = "Take on the form of an immovable rod, destroying all in your path. Purchasing this spell multiple times will also increase the rod's damage and travel range." - clothes_req = TRUE - human_req = FALSE - charge_max = 300 - cooldown_min = 100 - range = -1 - include_user = TRUE - invocation = "CLANG!" - invocation_type = SPELL_INVOCATION_SAY - action_icon_state = "immrod" - -/obj/effect/proc_holder/spell/targeted/rod_form/cast(list/targets,mob/user = usr) - for(var/mob/living/M in targets) - var/turf/start = get_turf(M) - var/obj/effect/immovablerod/wizard/W = new(start, get_ranged_target_turf(start, M.dir, (15 + spell_level * 3))) - W.wizard = M - W.max_distance += spell_level * 3 //You travel farther when you upgrade the spell - W.damage_bonus += spell_level * 20 //You do more damage when you upgrade the spell - W.start_turf = start - M.forceMove(W) - M.notransform = TRUE - M.status_flags |= GODMODE - -//Wizard Version of the Immovable Rod - -/obj/effect/immovablerod/wizard - var/max_distance = 13 - var/damage_bonus = 0 - var/turf/start_turf - notify = FALSE - -/obj/effect/immovablerod/wizard/Move() - if(get_dist(start_turf, get_turf(src)) >= max_distance) - qdel(src) - ..() - -/obj/effect/immovablerod/wizard/Destroy() - if(wizard) - wizard.status_flags &= ~GODMODE - wizard.notransform = FALSE - wizard.forceMove(get_turf(src)) - return ..() - -/obj/effect/immovablerod/wizard/penetrate(mob/living/L) - if(L.anti_magic_check()) - L.visible_message(span_danger("[src] hits [L], but it bounces back, then vanishes!") , span_userdanger("[src] hits you... but it bounces back, then vanishes!") , "You hear a weak, sad, CLANG.") - qdel(src) - return - L.visible_message(span_danger("[L] is penetrated by an immovable rod!") , span_userdanger("The rod penetrates you!") , "You hear a CLANG!") - L.adjustBruteLoss(70 + damage_bonus) diff --git a/code/modules/spells/spell_types/santa.dm b/code/modules/spells/spell_types/santa.dm deleted file mode 100644 index ad6673228316..000000000000 --- a/code/modules/spells/spell_types/santa.dm +++ /dev/null @@ -1,16 +0,0 @@ -//Santa spells! -/obj/effect/proc_holder/spell/aoe_turf/conjure/presents - name = "Conjure Presents!" - desc = "This spell lets you reach into S-space and retrieve presents! Yay!" - school = "santa" - charge_max = 600 - clothes_req = FALSE - antimagic_allowed = TRUE - invocation = "HO HO HO" - invocation_type = SPELL_INVOCATION_SAY - range = 3 - cooldown_min = 50 - - summon_type = list("/obj/item/a_gift") - summon_lifespan = 0 - summon_amt = 5 diff --git a/code/modules/spells/spell_types/self/basic_heal.dm b/code/modules/spells/spell_types/self/basic_heal.dm new file mode 100644 index 000000000000..acef65e1638b --- /dev/null +++ b/code/modules/spells/spell_types/self/basic_heal.dm @@ -0,0 +1,27 @@ +// This spell exists mainly for debugging purposes, and also to show how casting works +/datum/action/cooldown/spell/basic_heal + name = "Lesser Heal" + desc = "Heals a small amount of brute and burn damage to the caster." + + sound = 'sound/magic/staff_healing.ogg' + school = SCHOOL_RESTORATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 1.25 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_HUMAN + + invocation = "Victus sano!" + invocation_type = INVOCATION_WHISPER + + /// Amount of brute to heal to the spell caster on cast + var/brute_to_heal = 10 + /// Amount of burn to heal to the spell caster on cast + var/burn_to_heal = 10 + +/datum/action/cooldown/spell/basic_heal/cast(mob/living/cast_on) + . = ..() + cast_on.visible_message( + span_warning("A wreath of gentle light passes over [cast_on]!"), + span_notice("You wreath yourself in healing light!"), + ) + cast_on.adjustBruteLoss(-brute_to_heal, FALSE) + cast_on.adjustFireLoss(-burn_to_heal) \ No newline at end of file diff --git a/code/modules/spells/spell_types/self/charge.dm b/code/modules/spells/spell_types/self/charge.dm new file mode 100644 index 000000000000..46e9c3768cc3 --- /dev/null +++ b/code/modules/spells/spell_types/self/charge.dm @@ -0,0 +1,58 @@ +/datum/action/cooldown/spell/charge + name = "Charge" + desc = "This spell can be used to recharge a variety of things in your hands, \ + from magical artifacts to electrical components. A creative wizard can even use it \ + to grant magical power to a fellow magic user." + button_icon_state = "charge" + + sound = 'sound/magic/charge.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + invocation = "DIRI CEL" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + +/datum/action/cooldown/spell/charge/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/charge/cast(mob/living/cast_on) + . = ..() + + // Charge people we're pulling first and foremost + if(isliving(cast_on.pulling)) + var/mob/living/pulled_living = cast_on.pulling + var/pulled_has_spells = FALSE + + for(var/datum/action/cooldown/spell/spell in pulled_living.actions) + spell.reset_spell_cooldown() + pulled_has_spells = TRUE + + if(pulled_has_spells) + to_chat(pulled_living, span_notice("You feel raw magic flowing through you. It feels good!")) + to_chat(cast_on, span_notice("[pulled_living] suddenly feels very warm!")) + return + + to_chat(pulled_living, span_notice("You feel very strange for a moment, but then it passes.")) + + // Then charge their main hand item, then charge their offhand item + var/obj/item/to_charge = cast_on.get_active_held_item() || cast_on.get_inactive_held_item() + if(!to_charge) + to_chat(cast_on, span_notice("You feel magical power surging through your hands, but the feeling rapidly fades.")) + return + + var/charge_return = SEND_SIGNAL(to_charge, COMSIG_ITEM_MAGICALLY_CHARGED, src, cast_on) + + if(QDELETED(to_charge)) + to_chat(cast_on, span_warning("[src] seems to react adversely with [to_charge]!")) + return + + if(charge_return & COMPONENT_ITEM_BURNT_OUT) + to_chat(cast_on, span_warning("[to_charge] seems to react negatively to [src], becoming uncomfortably warm!")) + + else if(charge_return & COMPONENT_ITEM_CHARGED) + to_chat(cast_on, span_notice("[to_charge] suddenly feels very warm!")) + + else + to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) \ No newline at end of file diff --git a/code/modules/spells/spell_types/self/disable_tech.dm b/code/modules/spells/spell_types/self/disable_tech.dm new file mode 100644 index 000000000000..558e4e6943bf --- /dev/null +++ b/code/modules/spells/spell_types/self/disable_tech.dm @@ -0,0 +1,30 @@ +/datum/action/cooldown/spell/emp + name = "Emplosion" + desc = "This spell emplodes an area." + button_icon_state = "emp" + sound = 'sound/weapons/zapbang.ogg' + + school = SCHOOL_EVOCATION + + /// The heavy radius of the EMP + var/emp_heavy = 2 + /// The light radius of the EMP + var/emp_light = 3 + +/datum/action/cooldown/spell/emp/cast(atom/cast_on) + . = ..() + empulse(get_turf(cast_on), emp_heavy, emp_light) + +/datum/action/cooldown/spell/emp/disable_tech + name = "Disable Tech" + desc = "This spell disables all weapons, cameras and most other technology in range." + sound = 'sound/magic/disable_tech.ogg' + + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + invocation = "NEC CANTIO" + invocation_type = INVOCATION_SHOUT + + emp_heavy = 6 + emp_light = 10 \ No newline at end of file diff --git a/code/modules/spells/spell_types/disguise.dm b/code/modules/spells/spell_types/self/disguise.dm similarity index 56% rename from code/modules/spells/spell_types/disguise.dm rename to code/modules/spells/spell_types/self/disguise.dm index d603bad6d885..0ecf7549f6fb 100644 --- a/code/modules/spells/spell_types/disguise.dm +++ b/code/modules/spells/spell_types/self/disguise.dm @@ -1,45 +1,48 @@ -/obj/effect/proc_holder/spell/disguise +/datum/action/cooldown/spell/disguise name = "Mimicry" - desc = "Why fight your foes when you can simply outwit them? Disguises the caster as a random crewmember. The body-covering shell keeps your form as is, and protects you from body-altering effects." + desc = "Why fight your foes when you can simply outwit them? Disguises the caster as a random crewmember. \ + The body-covering shell keeps your form as is, and protects you from body-altering effects." + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "disguise" + + school = SCHOOL_TRANSMUTATION invocation = "CONJR DIS GUISE" - invocation_type = SPELL_INVOCATION_WHISPER - school = "transmutation" - charge_max = 60 SECONDS - cooldown_min = 50 SECONDS - level_max = 2 - clothes_req = FALSE + invocation_type = INVOCATION_WHISPER + + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + spell_max_level = 2 + spell_requirements = NONE var/is_disguised = FALSE //Tells us if a disguise is currently up. var/wasbeast = FALSE //We need this to make sure on can_cast, if they're found to be human and have this flag we can manually activate the uncloaking proc. - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "disguise" -/obj/effect/proc_holder/spell/disguise/can_cast(mob/user = usr) - if(!ishuman(user)) +/datum/action/cooldown/spell/disguise/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(!ishuman(owner)) //We need to undo the cloak after non-humanoid disguises because when the wizard becomes a non human during the spell, it will mess up their sprite. But since they are non human, we can't actually undo the spell. This leaves our recloaking bugged as hell, and breaks a lot of stuff. return FALSE - if(ishuman(user) && (wasbeast == TRUE)) - addtimer(CALLBACK(src, PROC_REF(undocloak), user), 2) + if(ishuman(owner) && (wasbeast == TRUE)) + addtimer(CALLBACK(src, PROC_REF(undocloak), owner), 0.2 SECONDS) return TRUE -/obj/effect/proc_holder/spell/disguise/choose_targets(mob/user = usr) - perform(user=user) - -/obj/effect/proc_holder/spell/disguise/cast(list/targets, mob/user = usr) +/datum/action/cooldown/spell/disguise/cast(mob/living/user) + . = ..() + if(!.) + return FALSE var/mob/living/carbon/human/C = user //Turns the user into a carbon, we'll need this later. var/list/potentials = list() - for(var/mob/living/carbon/human/H in GLOB.carbon_list) //Checks all the humanoid mobs. - if(H.job) //Checks if they're crew. - potentials += H //Adds the crewmember to the list. - if(potentials.len == 0) - to_chat(C, span_notice("There's nobody to disguise as!")) - revert_cast(user) - return + for(var/mob/living/carbon/human/H in GLOB.carbon_list) + if(H.stat != DEAD) + potentials += H var/mob/living/carbon/human/target = pick(potentials) //Picks a random subject from the viable targets. cloak(C, target, user) + return TRUE - -/obj/effect/proc_holder/spell/disguise/proc/cloak(var/mob/living/carbon/human/C, var/mob/living/carbon/human/target, mob/user) //Code shortcut to enable the disguise. +/datum/action/cooldown/spell/disguise/proc/cloak(var/mob/living/carbon/human/C, var/mob/living/carbon/human/target, mob/user) //Code shortcut to enable the disguise. if(is_disguised) message_admins("[ADMIN_LOOKUPFLW(C)] just tried to disguise while disguised! That shouldn't happen!") return @@ -53,9 +56,9 @@ C.update_inv_hands() log_game("[C.name] has disguised as [target.name]!") is_disguised = TRUE - addtimer(CALLBACK(src, PROC_REF(undocloak), C), (40 SECONDS + (level * 3))) //Sets it up so this is unchanged on default level, and goes up per level invested. + addtimer(CALLBACK(src, PROC_REF(undocloak), C), (40 SECONDS + (spell_level * 3))) //Sets it up so this is unchanged on default level, and goes up per level invested. -/obj/effect/proc_holder/spell/disguise/proc/undocloak(var/mob/living/carbon/human/C) //Code shortcut to disable the disguise. +/datum/action/cooldown/spell/disguise/proc/undocloak(var/mob/living/carbon/human/C) //Code shortcut to disable the disguise. if((ishuman(C) && (C.mind)) || wasbeast == TRUE) //Shapeshift spell takes out your mind, buckles you to a body, and then puts your mind in a summoned animal. We need this bullshit to both check that this is not happening, and then override it when we have to fix the bullshit. new /obj/effect/temp_visual/dir_setting/ninja(get_turf(C), C.dir) //Makes an animation for disguising. C.name_override = null @@ -68,12 +71,5 @@ else wasbeast = TRUE //Unfortunately we need this to counter shapeshifting bullshit, sets up the caster to immediatly revert when becoming human. -/datum/spellbook_entry/disguise //Spellbook entry, needed for the spell to be purchasable in game. - name = "Mimicry" - spell_type = /obj/effect/proc_holder/spell/disguise - category = "Assistance" - cost = 1 - - diff --git a/code/modules/spells/spell_types/self/forcewall.dm b/code/modules/spells/spell_types/self/forcewall.dm new file mode 100644 index 000000000000..7d8e40c87cb4 --- /dev/null +++ b/code/modules/spells/spell_types/self/forcewall.dm @@ -0,0 +1,70 @@ +/datum/action/cooldown/spell/forcewall + name = "Forcewall" + desc = "Create a magical barrier that only you can pass through." + button_icon_state = "shield" + + sound = 'sound/magic/forcewall.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 1.25 SECONDS + + invocation = "TARCOL MINTI ZHERI" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + /// The typepath to the wall we create on cast. + var/wall_type = /obj/effect/forcefield/wizard + +/datum/action/cooldown/spell/forcewall/cast(atom/cast_on) + . = ..() + new wall_type(get_turf(owner), owner) + + if(owner.dir == SOUTH || owner.dir == NORTH) + new wall_type(get_step(owner, EAST), owner, antimagic_flags) + new wall_type(get_step(owner, WEST), owner, antimagic_flags) + + else + new wall_type(get_step(owner, NORTH), owner, antimagic_flags) + new wall_type(get_step(owner, SOUTH), owner, antimagic_flags) + +/datum/action/cooldown/spell/forcewall/cult + name = "Shield" + desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire." + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "cultforcewall" + + cooldown_time = 40 SECONDS + invocation_type = INVOCATION_NONE + + wall_type = /obj/effect/forcefield/cult + +/datum/action/cooldown/spell/forcewall/mime + name = "Invisible Blockade" + desc = "Form an invisible three tile wide blockade." + background_icon_state = "bg_mime" + overlay_icon_state = "bg_mime_border" + button_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_blockade" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 0 SECONDS + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + + invocation = "" + invocation_type = INVOCATION_EMOTE + invocation_self_message = span_notice("You form a blockade in front of yourself.") + spell_max_level = 1 + + wall_type = /obj/effect/forcefield/mime/advanced + +/datum/action/cooldown/spell/forcewall/mime/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] looks as if a blockade is in front of [cast_on.p_them()].") diff --git a/code/modules/spells/spell_types/self/lichdom.dm b/code/modules/spells/spell_types/self/lichdom.dm new file mode 100644 index 000000000000..eddeb119b6f5 --- /dev/null +++ b/code/modules/spells/spell_types/self/lichdom.dm @@ -0,0 +1,83 @@ +/datum/action/cooldown/spell/lichdom + name = "Bind Soul" + desc = "A spell that binds your soul to an item in your hands. \ + Binding your soul to an item will turn you into an immortal Lich. \ + So long as the item remains intact, you will revive from death, \ + no matter the circumstances." + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "skeleton" + + school = SCHOOL_NECROMANCY + cooldown_time = 1 SECONDS + + invocation = "NECREM IMORTIUM!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_STATION|SPELL_REQUIRES_MIND + spell_max_level = 1 + +/datum/action/cooldown/spell/lichdom/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + + // We call this here so we can get feedback if they try to cast it when they shouldn't. + if(!is_valid_target(owner)) + if(feedback) + to_chat(owner, span_warning("You don't have a soul to bind!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/lichdom/is_valid_target(atom/cast_on) + return isliving(cast_on) && !HAS_TRAIT(owner, TRAIT_NO_SOUL) + +/datum/action/cooldown/spell/lichdom/cast(mob/living/cast_on) + var/obj/item/marked_item = cast_on.get_active_held_item() + if(!marked_item || marked_item.item_flags & ABSTRACT) + return + if(HAS_TRAIT(marked_item, TRAIT_NODROP)) + to_chat(cast_on, span_warning("[marked_item] is stuck to your hand - it wouldn't be a wise idea to place your soul into it.")) + return + // I ensouled the nuke disk once. + // But it's a really mean tactic, so we probably should disallow it. + if(SEND_SIGNAL(marked_item, COMSIG_ITEM_IMBUE_SOUL, src, cast_on) & COMPONENT_BLOCK_IMBUE) + to_chat(cast_on, span_warning("[marked_item] is not suitable for emplacement of your fragile soul.")) + return + + . = ..() + playsound(cast_on, 'sound/effects/pope_entry.ogg', 100) + + to_chat(cast_on, span_green("You begin to focus your very being into [marked_item]...")) + if(!do_after(cast_on, 5 SECONDS, target = marked_item, needhand = FALSE)) + to_chat(cast_on, span_warning("Your soul snaps back to your body as you stop ensouling [marked_item]!")) + return + + marked_item.AddComponent(/datum/component/phylactery, cast_on.mind) + + cast_on.set_species(/datum/species/skeleton) + to_chat(cast_on, span_userdanger("With a hideous feeling of emptiness you watch in horrified fascination \ + as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! \ + As your organs crumble to dust in your fleshless chest you come to terms with your choice. \ + You're a lich!")) + + if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_cast_on = cast_on + var/obj/item/organ/brain/lich_brain = carbon_cast_on.getorganslot(ORGAN_SLOT_BRAIN) + if(lich_brain) // This prevents MMIs being used to stop lich revives + lich_brain.organ_flags &= ~ORGAN_VITAL + lich_brain.decoy_override = TRUE + + if(ishuman(cast_on)) + var/mob/living/carbon/human/human_cast_on = cast_on + human_cast_on.dropItemToGround(human_cast_on.w_uniform) + human_cast_on.dropItemToGround(human_cast_on.wear_suit) + human_cast_on.dropItemToGround(human_cast_on.head) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(human_cast_on), ITEM_SLOT_OCLOTHING) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(human_cast_on), ITEM_SLOT_HEAD) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(human_cast_on), ITEM_SLOT_ICLOTHING) + + + // No soul. You just sold it + ADD_TRAIT(cast_on, TRAIT_NO_SOUL, LICH_TRAIT) + // You only get one phylactery. + qdel(src) diff --git a/code/modules/spells/spell_types/self/mime_vow.dm b/code/modules/spells/spell_types/self/mime_vow.dm new file mode 100644 index 000000000000..eef0729c6c1f --- /dev/null +++ b/code/modules/spells/spell_types/self/mime_vow.dm @@ -0,0 +1,25 @@ +/datum/action/cooldown/spell/vow_of_silence + name = "Speech" + desc = "Make (or break) a vow of silence." + background_icon_state = "bg_mime" + overlay_icon_state = "bg_mime_border" + button_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "mime_speech" + panel = "Mime" + + school = SCHOOL_MIME + cooldown_time = 5 MINUTES + spell_requirements = NONE + + spell_max_level = 1 + +/datum/action/cooldown/spell/vow_of_silence/cast(mob/living/carbon/human/cast_on) + . = ..() + cast_on.mind.miming = !cast_on.mind.miming + if(cast_on.mind.miming) + to_chat(cast_on, span_notice("You make a vow of silence.")) + SEND_SIGNAL(cast_on, COMSIG_CLEAR_MOOD_EVENT, "vow") + else + to_chat(cast_on, span_notice("You break your vow of silence.")) + SEND_SIGNAL(cast_on, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) + cast_on.update_mob_action_buttons() diff --git a/code/modules/spells/spell_types/self/mutate.dm b/code/modules/spells/spell_types/self/mutate.dm new file mode 100644 index 000000000000..45abae0e5baa --- /dev/null +++ b/code/modules/spells/spell_types/self/mutate.dm @@ -0,0 +1,49 @@ +/// A spell type that adds mutations to the caster temporarily. +/datum/action/cooldown/spell/apply_mutations + button_icon_state = "mutate" + sound = 'sound/magic/mutate.ogg' + + school = SCHOOL_TRANSMUTATION + + /// A list of all mutations we add on cast + var/list/mutations_to_add = list() + /// The duration the mutations will last afetr cast (keep this above the minimum cooldown) + var/mutation_duration = 10 SECONDS + +/datum/action/cooldown/spell/apply_mutations/New(Target) + . = ..() + spell_requirements |= SPELL_REQUIRES_HUMAN // The spell involves mutations, so it always require human / dna + +/datum/action/cooldown/spell/apply_mutations/Remove(mob/living/remove_from) + remove_mutations(remove_from) + return ..() + +/datum/action/cooldown/spell/apply_mutations/is_valid_target(atom/cast_on) + var/mob/living/carbon/human/human_caster = cast_on // Requires human anyways + return !!human_caster.dna + +/datum/action/cooldown/spell/apply_mutations/cast(mob/living/carbon/human/cast_on) + . = ..() + for(var/mutation in mutations_to_add) + cast_on.dna.add_mutation(mutation) + addtimer(CALLBACK(src, PROC_REF(remove_mutations), cast_on), mutation_duration, TIMER_DELETE_ME) + +/// Removes the mutations we added from casting our spell +/datum/action/cooldown/spell/apply_mutations/proc/remove_mutations(mob/living/carbon/human/cast_on) + if(QDELETED(cast_on) || !is_valid_target(cast_on)) + return + + for(var/mutation in mutations_to_add) + cast_on.dna.remove_mutation(mutation) + +/datum/action/cooldown/spell/apply_mutations/mutate + name = "Mutate" + desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 2.5 SECONDS + + invocation = "BIRUZ BENNAR" + invocation_type = INVOCATION_SHOUT + + mutations_to_add = list(/datum/mutation/human/laser_eyes, /datum/mutation/human/hulk) + mutation_duration = 30 SECONDS diff --git a/code/modules/spells/spell_types/self/night_vision.dm b/code/modules/spells/spell_types/self/night_vision.dm new file mode 100644 index 000000000000..c3b3fc9977fa --- /dev/null +++ b/code/modules/spells/spell_types/self/night_vision.dm @@ -0,0 +1,39 @@ +//Toggle Night Vision +/datum/action/cooldown/spell/night_vision + name = "Toggle Nightvision" + desc = "Toggle your nightvision mode." + + cooldown_time = 1 SECONDS + spell_requirements = NONE + + /// The span the "toggle" message uses when sent to the user + var/toggle_span = "notice" + +/datum/action/cooldown/spell/night_vision/New(Target) + . = ..() + name = "[name] \[ON\]" + +/datum/action/cooldown/spell/night_vision/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/night_vision/cast(mob/living/cast_on) + . = ..() + to_chat(cast_on, "You toggle your night vision.") + + var/next_mode_text = "" + switch(cast_on.lighting_alpha) + if (LIGHTING_PLANE_ALPHA_VISIBLE) + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + next_mode_text = "More" + if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + next_mode_text = "Full" + if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + next_mode_text = "OFF" + else + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + next_mode_text = "ON" + + cast_on.update_sight() + name = "[initial(name)] \[[next_mode_text]\]" \ No newline at end of file diff --git a/code/modules/spells/spell_types/self/personality_commune.dm b/code/modules/spells/spell_types/self/personality_commune.dm new file mode 100644 index 000000000000..5c57dbc8a996 --- /dev/null +++ b/code/modules/spells/spell_types/self/personality_commune.dm @@ -0,0 +1,54 @@ +// This can probably be changed to use mind linker at some point +/datum/action/cooldown/spell/personality_commune + name = "Personality Commune" + desc = "Sends thoughts to your alternate consciousness." + button_icon_state = "telepathy" + cooldown_time = 0 SECONDS + spell_requirements = NONE + + /// Fluff text shown when a message is sent to the pair + var/fluff_text = span_boldnotice("You hear an echoing voice in the back of your head...") + /// The message to send to the corresponding person on cast + var/to_send + +/datum/action/cooldown/spell/personality_commune/New(Target) + . = ..() + if(!istype(target, /datum/brain_trauma/severe/split_personality)) + stack_trace("[type] was created on a target that isn't a /datum/brain_trauma/severe/split_personality, this doesn't work.") + qdel(src) + +/datum/action/cooldown/spell/personality_commune/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/personality_commune/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/datum/brain_trauma/severe/split_personality/trauma = target + if(!istype(trauma)) // hypothetically impossible but you never know + return . | SPELL_CANCEL_CAST + + to_send = tgui_input_text(cast_on, "What would you like to tell your other self?", "Commune") + if(QDELETED(src) || QDELETED(trauma)|| QDELETED(cast_on) || QDELETED(trauma.owner) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!to_send) + reset_cooldown() + return . | SPELL_CANCEL_CAST + + return TRUE + +// Pillaged and adapted from telepathy code +/datum/action/cooldown/spell/personality_commune/cast(mob/living/cast_on) + . = ..() + var/datum/brain_trauma/severe/split_personality/trauma = target + + var/user_message = span_boldnotice("You concentrate and send thoughts to your other self:") + var/user_message_body = span_notice("[to_send]") + to_chat(cast_on, "[user_message] [user_message_body]") + to_chat(trauma.owner, "[fluff_text] [user_message_body]") + log_directed_talk(cast_on, trauma.owner, to_send, LOG_SAY, "[name]") + for(var/dead_mob in GLOB.dead_mob_list) + if(!isobserver(dead_mob)) + continue + to_chat(dead_mob, "[FOLLOW_LINK(dead_mob, cast_on)] [span_boldnotice("[cast_on] [name]:")] [span_notice("\"[to_send]\" to")] [span_name("[trauma]")]") \ No newline at end of file diff --git a/code/modules/spells/spell_types/self/rod_form.dm b/code/modules/spells/spell_types/self/rod_form.dm new file mode 100644 index 000000000000..fb2fe983bbfd --- /dev/null +++ b/code/modules/spells/spell_types/self/rod_form.dm @@ -0,0 +1,158 @@ +/// The base distance a wizard rod will go without upgrades. +#define BASE_WIZ_ROD_RANGE 13 + +/datum/action/cooldown/spell/rod_form + name = "Rod Form" + desc = "Take on the form of an immovable rod, destroying all in your path. \ + Purchasing this spell multiple times will also increase the rod's damage and travel range." + button_icon_state = "immrod" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 25 SECONDS + cooldown_reduction_per_rank = 3.75 SECONDS + + invocation = "CLANG!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_STATION + + /// The extra distance we travel per additional spell level. + var/distance_per_spell_rank = 3 + /// The extra damage we deal per additional spell level. + var/damage_per_spell_rank = 20 + /// The max distance the rod goes on cast + var/rod_max_distance = BASE_WIZ_ROD_RANGE + /// The damage bonus applied to the rod on cast + var/rod_damage_bonus = 0 + +/datum/action/cooldown/spell/rod_form/cast(atom/cast_on) + . = ..() + // The destination turf of the rod - just a bit over the max range we calculated, for safety + var/turf/distant_turf = get_ranged_target_turf(get_turf(cast_on), cast_on.dir, (rod_max_distance + 2)) + + new /obj/effect/immovablerod/wizard( + get_turf(cast_on), + distant_turf, + null, + FALSE, + cast_on, + rod_max_distance, + rod_damage_bonus, + ) + +/datum/action/cooldown/spell/rod_form/level_spell(bypass_cap = FALSE) + . = ..() + if(!.) + return FALSE + + rod_max_distance += distance_per_spell_rank + rod_damage_bonus += damage_per_spell_rank + return TRUE + +/datum/action/cooldown/spell/rod_form/delevel_spell() + . = ..() + if(!.) + return FALSE + + rod_max_distance -= distance_per_spell_rank + rod_damage_bonus -= damage_per_spell_rank + return TRUE + +/// Wizard Version of the Immovable Rod. +/obj/effect/immovablerod/wizard + notify = FALSE + /// The wizard who's piloting our rod. + var/datum/weakref/our_wizard + /// The distance the rod will go. + var/max_distance = BASE_WIZ_ROD_RANGE + /// The damage bonus of the rod when it smacks people. + var/damage_bonus = 0 + /// The turf the rod started from, to calcuate distance. + var/turf/start_turf + +/obj/effect/immovablerod/wizard/Initialize(mapload, atom/target_atom, atom/specific_target, force_looping = FALSE, mob/living/wizard, max_distance = BASE_WIZ_ROD_RANGE, damage_bonus = 0) + . = ..() + if(wizard) + set_wizard(wizard) + start_turf = get_turf(src) + src.max_distance = max_distance + src.damage_bonus = damage_bonus + +/obj/effect/immovablerod/wizard/Destroy(force) + start_turf = null + return ..() + +/obj/effect/immovablerod/wizard/Move() + if(get_dist(start_turf, get_turf(src)) >= max_distance) + stop_travel() + return + return ..() + +/obj/effect/immovablerod/wizard/penetrate(mob/living/penetrated) + if(penetrated.can_block_magic()) + penetrated.visible_message( + span_danger("[src] hits [penetrated], but it bounces back, then vanishes!"), + span_userdanger("[src] hits you... but it bounces back, then vanishes!"), + span_danger("You hear a weak, sad, CLANG.") + ) + stop_travel() + return + + penetrated.visible_message( + span_danger("[penetrated] is penetrated by an immovable rod!"), + span_userdanger("The [src] penetrates you!"), + span_danger("You hear a CLANG!"), + ) + penetrated.adjustBruteLoss(70 + damage_bonus) + +/obj/effect/immovablerod/wizard/suplex_rod(mob/living/strongman) + var/mob/living/wizard = our_wizard?.resolve() + if(QDELETED(wizard)) + return ..() // There's no wizard in this rod? It's pretty much a normal rod at this point + + strongman.visible_message( + span_boldwarning("[src] transforms into [wizard] as [strongman] suplexes them!"), + span_warning("As you grab [src], it suddenly turns into [wizard] as you suplex them!") + ) + to_chat(wizard, span_boldwarning("You're suddenly jolted out of rod-form as [strongman] somehow manages to grab you, slamming you into the ground!")) + stop_travel() + wizard.Stun(6 SECONDS) + wizard.apply_damage(25, BRUTE) + return TRUE + +/** + * Called when the wizard rod reaches it's maximum distance + * or is otherwise stopped by something. + * Dumps out the wizard, and deletes. + */ +/obj/effect/immovablerod/wizard/proc/stop_travel() + eject_wizard() + qdel(src) + +/** + * Set wizard as our_wizard, placing them in the rod + * and preparing them for travel. + */ +/obj/effect/immovablerod/wizard/proc/set_wizard(mob/living/wizard) + our_wizard = WEAKREF(wizard) + + wizard.forceMove(src) + wizard.notransform = TRUE + wizard.status_flags |= GODMODE + ADD_TRAIT(wizard, TRAIT_MAGICALLY_PHASED, REF(src)) + +/** + * Eject our current wizard, removing them from the rod + * and fixing all of the variables we changed. + */ +/obj/effect/immovablerod/wizard/proc/eject_wizard() + var/mob/living/wizard = our_wizard?.resolve() + if(QDELETED(wizard)) + return + + wizard.status_flags &= ~GODMODE + wizard.notransform = FALSE + wizard.forceMove(get_turf(src)) + our_wizard = null + REMOVE_TRAIT(wizard, TRAIT_MAGICALLY_PHASED, REF(src)) + +#undef BASE_WIZ_ROD_RANGE diff --git a/code/modules/spells/spell_types/self/smoke.dm b/code/modules/spells/spell_types/self/smoke.dm new file mode 100644 index 000000000000..c11955a20cd9 --- /dev/null +++ b/code/modules/spells/spell_types/self/smoke.dm @@ -0,0 +1,38 @@ +/// Basic smoke spell. +/datum/action/cooldown/spell/smoke + name = "Smoke" + desc = "This spell spawns a cloud of smoke at your location. \ + People within will begin to choke and drop their items." + button_icon_state = "smoke" + + school = SCHOOL_CONJURATION + cooldown_time = 12 SECONDS + cooldown_reduction_per_rank = 2.5 SECONDS + + invocation_type = INVOCATION_NONE + + smoke_type = /datum/effect_system/fluid_spread/smoke/bad + smoke_amt = 4 + +/// Chaplain smoke. +/datum/action/cooldown/spell/smoke/lesser + name = "Holy Smoke" + desc = "This spell spawns a small cloud of smoke at your location." + + school = SCHOOL_HOLY + cooldown_time = 36 SECONDS + spell_requirements = NONE + + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + +/// Unused smoke that makes people sleep. Used to be for cult? +/datum/action/cooldown/spell/smoke/disable + name = "Paralysing Smoke" + desc = "This spell spawns a cloud of paralysing smoke." + background_icon_state = "bg_cult" + overlay_icon_state = "bg_cult_border" + + cooldown_time = 20 SECONDS + + smoke_type = /datum/effect_system/fluid_spread/smoke/sleeping diff --git a/code/modules/spells/spell_types/self/soultap.dm b/code/modules/spells/spell_types/self/soultap.dm new file mode 100644 index 000000000000..db1be25ef57e --- /dev/null +++ b/code/modules/spells/spell_types/self/soultap.dm @@ -0,0 +1,62 @@ +/** + * SOUL TAP! + * + * Trades 20 max health for a refresh on all of your spells. + * I was considering making it depend on the cooldowns of your spells, but I want to support "Big spell wizard" with this loadout. + * The two spells that sound most problematic with this is mindswap and lichdom, + * but soul tap requires clothes for mindswap and lichdom takes your soul. + */ +/datum/action/cooldown/spell/tap + name = "Soul Tap" + desc = "Fuel your spells using your own soul!" + button_icon_state = "soultap" + + // I could see why this wouldn't be necromancy, but messing with souls or whatever. Ectomancy? + school = SCHOOL_NECROMANCY + cooldown_time = 1 SECONDS + invocation = "AT ANY COST!" + invocation_type = INVOCATION_SHOUT + spell_max_level = 1 + + /// The amount of health we take on tap + var/tap_health_taken = 20 + +/datum/action/cooldown/spell/tap/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + + // We call this here so we can get feedback if they try to cast it when they shouldn't. + if(!is_valid_target(owner)) + if(feedback) + to_chat(owner, span_warning("You have no soul to tap into!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/tap/is_valid_target(atom/cast_on) + return isliving(cast_on) && !HAS_TRAIT(owner, TRAIT_NO_SOUL) + +/datum/action/cooldown/spell/tap/cast(mob/living/cast_on) + . = ..() + cast_on.maxHealth -= tap_health_taken + cast_on.health = min(cast_on.health, cast_on.maxHealth) + + for(var/datum/action/cooldown/spell/spell in cast_on.actions) + spell.reset_spell_cooldown() + + // If the tap took all of our life, we die and lose our soul! + if(cast_on.maxHealth <= 0) + to_chat(cast_on, span_userdanger("Your weakened soul is completely consumed by the tap!")) + ADD_TRAIT(cast_on, TRAIT_NO_SOUL, MAGIC_TRAIT) + + cast_on.visible_message(span_danger("[cast_on] suddenly dies!"), ignored_mobs = cast_on) + cast_on.death() + + // If the next tap will kill us, give us a heads-up + else if(cast_on.maxHealth - tap_health_taken <= 0) + to_chat(cast_on, span_bolddanger("Your body feels incredibly drained, and the burning is hard to ignore!")) + + // Otherwise just give them some feedback + else + to_chat(cast_on, span_danger("Your body feels drained and there is a burning pain in your chest.")) \ No newline at end of file diff --git a/code/modules/spells/spell_types/self/spacetime_distortion.dm b/code/modules/spells/spell_types/self/spacetime_distortion.dm new file mode 100644 index 000000000000..b41560aa1629 --- /dev/null +++ b/code/modules/spells/spell_types/self/spacetime_distortion.dm @@ -0,0 +1,168 @@ +// This could probably be an aoe spell but it's a little cursed, so I'm not touching it +/datum/action/cooldown/spell/spacetime_dist + name = "Spacetime Distortion" + desc = "Entangle the strings of space-time in an area around you, \ + randomizing the layout and making proper movement impossible. The strings vibrate..." + sound = 'sound/effects/magic.ogg' + button_icon_state = "spacetime" + + school = SCHOOL_EVOCATION + cooldown_time = 30 SECONDS + spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_STATION + spell_max_level = 1 + + /// Weather we're ready to cast again yet or not + var/ready = TRUE + /// The radius of the scramble around the caster + var/scramble_radius = 7 + /// The duration of the scramble + var/duration = 15 SECONDS + /// A lazylist of all scramble effects this spell has created. + var/list/effects + +/datum/action/cooldown/spell/spacetime_dist/Destroy() + QDEL_LAZYLIST(effects) + return ..() + +/datum/action/cooldown/spell/spacetime_dist/can_cast_spell(feedback = TRUE) + return ..() && ready + +/datum/action/cooldown/spell/spacetime_dist/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + if(!ready) + .[PANEL_DISPLAY_STATUS] = "NOT READY" + +/datum/action/cooldown/spell/spacetime_dist/cast(atom/cast_on) + . = ..() + var/list/turf/to_switcharoo = get_targets_to_scramble(cast_on) + if(!length(to_switcharoo)) + to_chat(cast_on, span_warning("For whatever reason, the strings nearby aren't keen on being tangled.")) + reset_spell_cooldown() + return + + ready = FALSE + + for(var/turf/swap_a as anything in to_switcharoo) + var/turf/swap_b = to_switcharoo[swap_a] + var/obj/effect/cross_action/spacetime_dist/effect_a = new /obj/effect/cross_action/spacetime_dist(swap_a, antimagic_flags) + var/obj/effect/cross_action/spacetime_dist/effect_b = new /obj/effect/cross_action/spacetime_dist(swap_b, antimagic_flags) + effect_a.linked_dist = effect_b + effect_a.add_overlay(swap_b.photograph()) + effect_b.linked_dist = effect_a + effect_b.add_overlay(swap_a.photograph()) + effect_b.set_light(4, 30, "#c9fff5") + LAZYADD(effects, effect_a) + LAZYADD(effects, effect_b) + +/datum/action/cooldown/spell/spacetime_dist/after_cast() + . = ..() + addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration) + +/// Callback which cleans up our effects list after the duration expires. +/datum/action/cooldown/spell/spacetime_dist/proc/clean_turfs() + QDEL_LAZYLIST(effects) + ready = TRUE + +/** + * Gets a list of turfs around the center atom to scramble. + * + * Returns an assoc list of [turf] to [turf]. These pairs are what turfs are + * swapped between one another when the cast is done. + */ +/datum/action/cooldown/spell/spacetime_dist/proc/get_targets_to_scramble(atom/center) + // Get turfs around the center + var/list/turfs = spiral_range_turfs(scramble_radius, center) + if(!length(turfs)) + return + + var/list/turf_steps = list() + + // Go through the turfs we got and pair them up + // This is where we determine what to swap where + var/num_to_scramble = round(length(turfs) * 0.5) + for(var/i in 1 to num_to_scramble) + turf_steps[pick_n_take(turfs)] = pick_n_take(turfs) + + // If there's any turfs unlinked with a friend, + // just randomly swap it with any turf in the area + if(length(turfs)) + var/turf/loner = pick(turfs) + var/area/caster_area = get_area(center) + turf_steps[loner] = get_turf(pick(caster_area.contents)) + + return turf_steps + + +/obj/effect/cross_action + name = "cross me" + desc = "for crossing" + anchored = TRUE + +/obj/effect/cross_action/spacetime_dist + name = "spacetime distortion" + desc = "A distortion in spacetime. You can hear faint music..." + icon_state = "" + /// A flags which save people from being thrown about + var/antimagic_flags = MAGIC_RESISTANCE + var/obj/effect/cross_action/spacetime_dist/linked_dist + var/busy = FALSE + var/sound + var/walks_left = 50 //prevents the game from hanging in extreme cases (such as minigun fire) + +/obj/effect/cross_action/singularity_act() + return + +/obj/effect/cross_action/singularity_pull() + return + +/obj/effect/cross_action/spacetime_dist/Initialize(mapload, flags = MAGIC_RESISTANCE) + . = ..() + setDir(pick(GLOB.cardinals)) + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + antimagic_flags = flags + +/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) + if(ismob(AM)) + var/mob/M = AM + if(M.can_block_magic(antimagic_flags, charge_cost = 0)) + return + if(linked_dist && walks_left > 0) + flick("purplesparkles", src) + linked_dist.get_walker(AM) + walks_left-- + +/obj/effect/cross_action/spacetime_dist/proc/get_walker(atom/movable/AM) + busy = TRUE + flick("purplesparkles", src) + AM.forceMove(get_turf(src)) + playsound(get_turf(src),sound,70,FALSE) + busy = FALSE + +/obj/effect/cross_action/spacetime_dist/proc/on_entered(datum/source, atom/movable/AM) + SIGNAL_HANDLER + if(!busy) + INVOKE_ASYNC(src, PROC_REF(walk_link), AM) + +/obj/effect/cross_action/spacetime_dist/attackby(obj/item/W, mob/user, params) + if(user.temporarilyRemoveItemFromInventory(W)) + walk_link(W) + else + walk_link(user) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/effect/cross_action/spacetime_dist/attack_hand(mob/user, list/modifiers) + walk_link(user) + +/obj/effect/cross_action/spacetime_dist/attack_paw(mob/user, list/modifiers) + walk_link(user) + +/obj/effect/cross_action/spacetime_dist/Destroy() + busy = TRUE + linked_dist = null + return ..() diff --git a/code/modules/spells/spell_types/self/stop_time.dm b/code/modules/spells/spell_types/self/stop_time.dm new file mode 100644 index 000000000000..86570a242c0e --- /dev/null +++ b/code/modules/spells/spell_types/self/stop_time.dm @@ -0,0 +1,32 @@ +/datum/action/cooldown/spell/timestop + name = "Stop Time" + desc = "This spell stops time for everyone except for you, \ + allowing you to move freely while your enemies and even projectiles are frozen." + button_icon_state = "time" + + school = SCHOOL_FORBIDDEN // Fucking with time is not appreciated by anyone + cooldown_time = 50 SECONDS + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "TOKI YO TOMARE!" + invocation_type = INVOCATION_SHOUT + + /// The radius / range of the time stop. + var/timestop_range = 2 + /// The duration of the time stop. + var/timestop_duration = 10 SECONDS + ///The timestop effect we use + var/timestop_effect = /obj/effect/timestop/wizard + +/datum/action/cooldown/spell/timestop/Grant(mob/grant_to) + . = ..() + if(owner) + ADD_TRAIT(owner, TRAIT_TIME_STOP_IMMUNE, REF(src)) + +/datum/action/cooldown/spell/timestop/Remove(mob/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_TIME_STOP_IMMUNE, REF(src)) + return ..() + +/datum/action/cooldown/spell/timestop/cast(atom/cast_on) + . = ..() + new timestop_effect(get_turf(cast_on), timestop_range, timestop_duration, list(cast_on)) diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm new file mode 100644 index 000000000000..26d960eb4038 --- /dev/null +++ b/code/modules/spells/spell_types/self/summonitem.dm @@ -0,0 +1,154 @@ +/datum/action/cooldown/spell/summonitem + name = "Instant Summons" + desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe." + button_icon_state = "summons" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + + invocation = "GAR YOK" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + spell_max_level = 1 //cannot be improved + + ///The obj marked for recall + var/obj/marked_item + +/datum/action/cooldown/spell/summonitem/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/// Set the passed object as our marked item +/datum/action/cooldown/spell/summonitem/proc/mark_item(obj/to_mark) + name = "Recall [to_mark]" + marked_item = to_mark + RegisterSignal(marked_item, COMSIG_PARENT_QDELETING, PROC_REF(on_marked_item_deleted)) + +/// Unset our current marked item +/datum/action/cooldown/spell/summonitem/proc/unmark_item() + name = initial(name) + UnregisterSignal(marked_item, COMSIG_PARENT_QDELETING) + marked_item = null + +/// Signal proc for COMSIG_PARENT_QDELETING on our marked item, unmarks our item if it's deleted +/datum/action/cooldown/spell/summonitem/proc/on_marked_item_deleted(datum/source) + SIGNAL_HANDLER + + if(owner) + to_chat(owner, span_boldwarning("You sense your marked item has been destroyed!")) + unmark_item() + +/datum/action/cooldown/spell/summonitem/cast(mob/living/cast_on) + . = ..() + if(QDELETED(marked_item)) + try_link_item(cast_on) + return + + if(marked_item == cast_on.get_active_held_item()) + try_unlink_item(cast_on) + return + + try_recall_item(cast_on) + +/// If we don't have a marked item, attempts to mark the caster's held item. +/datum/action/cooldown/spell/summonitem/proc/try_link_item(mob/living/caster) + var/obj/item/potential_mark = caster.get_active_held_item() + if(!potential_mark) + if(caster.get_inactive_held_item()) + to_chat(caster, span_warning("You must hold the desired item in your hands to mark it for recall!")) + else + to_chat(caster, span_warning("You aren't holding anything that can be marked for recall!")) + return FALSE + + var/link_message = "" + if(potential_mark.item_flags & ABSTRACT) + return FALSE + if(SEND_SIGNAL(potential_mark, COMSIG_ITEM_MARK_RETRIEVAL, src, caster) & COMPONENT_BLOCK_MARK_RETRIEVAL) + return FALSE + if(HAS_TRAIT(potential_mark, TRAIT_NODROP)) + link_message += "Though it feels redundant... " + + link_message += "You mark [potential_mark] for recall." + to_chat(caster, span_notice(link_message)) + mark_item(potential_mark) + return TRUE + +/// If we have a marked item and it's in our hand, we will try to unlink it +/datum/action/cooldown/spell/summonitem/proc/try_unlink_item(mob/living/caster) + to_chat(caster, span_notice("You begin removing the mark on [marked_item]...")) + if(!do_after(caster, 5 SECONDS, marked_item)) + to_chat(caster, span_notice("You decide to keep [marked_item] marked.")) + return FALSE + + to_chat(caster, span_notice("You remove the mark on [marked_item] to use elsewhere.")) + unmark_item() + return TRUE + +/// Recalls our marked item to the caster. May bring some unexpected things along. +/datum/action/cooldown/spell/summonitem/proc/try_recall_item(mob/living/caster) + var/obj/item_to_retrieve = marked_item + + if(item_to_retrieve.loc) + // I don't want to know how someone could put something + // inside itself but these are wizards so let's be safe + var/infinite_recursion = 0 + + // if it's in something, you get the whole thing. + while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) + if(isitem(item_to_retrieve.loc)) + var/obj/item/mark_loc = item_to_retrieve.loc + // Being able to summon abstract things because + // your item happened to get placed there is a no-no + if(mark_loc.item_flags & ABSTRACT) + break + + // If its on someone, properly drop it + if(ismob(item_to_retrieve.loc)) + var/mob/holding_mark = item_to_retrieve.loc + + // Items in silicons warp the whole silicon + if(issilicon(holding_mark)) + holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly disappears!")) + holding_mark.forceMove(caster.loc) + holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly appears!")) + item_to_retrieve = null + break + + holding_mark.dropItemToGround(item_to_retrieve) + + else if(isobj(item_to_retrieve.loc)) + var/obj/retrieved_item = item_to_retrieve.loc + // Can't bring anchored things + if(retrieved_item.anchored) + return + // Edge cases for moving certain machinery... + if(istype(retrieved_item, /obj/machinery/portable_atmospherics)) + var/obj/machinery/portable_atmospherics/atmos_item = retrieved_item + atmos_item.disconnect() + atmos_item.update_icon() + + // Otherwise bring the whole thing with us + item_to_retrieve = retrieved_item + + infinite_recursion += 1 + + else + // Organs are usually stored in nullspace + if(isorgan(item_to_retrieve)) + var/obj/item/organ/organ = item_to_retrieve + if(organ.owner) + // If this code ever runs I will be happy + log_combat(caster, organ.owner, "magically removed [organ.name] from", addition = "INTENT: [uppertext(caster.a_intent)]") + organ.Remove(organ.owner) + + if(!item_to_retrieve) + return + + item_to_retrieve.loc?.visible_message(span_warning("[item_to_retrieve] suddenly disappears!")) + + if(isitem(item_to_retrieve) && caster.put_in_hands(item_to_retrieve)) + item_to_retrieve.loc.visible_message(span_warning("[item_to_retrieve] suddenly appears in [caster]'s hand!")) + else + item_to_retrieve.forceMove(caster.drop_location()) + item_to_retrieve.loc.visible_message(span_warning("[item_to_retrieve] suddenly appears!")) + playsound(get_turf(item_to_retrieve), 'sound/magic/summonitems_generic.ogg', 50, TRUE) diff --git a/code/modules/spells/spell_types/self/voice_of_god.dm b/code/modules/spells/spell_types/self/voice_of_god.dm new file mode 100644 index 000000000000..f84d8477d379 --- /dev/null +++ b/code/modules/spells/spell_types/self/voice_of_god.dm @@ -0,0 +1,50 @@ +/datum/action/cooldown/spell/voice_of_god + name = "Voice of God" + desc = "Speak with an incredibly compelling voice, forcing listeners to obey your commands." + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "voice_of_god" + sound = 'sound/magic/clockwork/invoke_general.ogg' + + cooldown_time = 120 SECONDS // Varies depending on command + invocation = "" // Handled by the VOICE OF GOD itself + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + antimagic_flags = NONE + + /// The command to deliver on cast + var/command + /// The modifier to the cooldown, after cast + var/cooldown_mod = 1 + /// The modifier put onto the power of the command + var/power_mod = 1 + /// A list of spans to apply to commands given + var/list/spans = list("colossus", "yell") + +/datum/action/cooldown/spell/voice_of_god/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + command = tgui_input_text(cast_on, "Speak with the Voice of God", "Command") + if(QDELETED(src) || QDELETED(cast_on) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!command) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/voice_of_god/cast(atom/cast_on) + . = ..() + var/command_cooldown = voice_of_god(uppertext(command), cast_on, spans, base_multiplier = power_mod) + cooldown_time = (command_cooldown * cooldown_mod) + +// "Invocation" is done by the actual voice of god proc +/datum/action/cooldown/spell/voice_of_god/invocation() + return + +/datum/action/cooldown/spell/voice_of_god/clown + name = "Voice of Clown" + desc = "Speak with an incredibly funny voice, startling people into obeying you for a brief moment." + sound = 'sound/spookoween/scary_horn.ogg' + cooldown_mod = 0.5 + power_mod = 0.1 + spans = list("clown") diff --git a/code/modules/spells/spell_types/shadow_walk.dm b/code/modules/spells/spell_types/shadow_walk.dm deleted file mode 100644 index 49b9b2151c88..000000000000 --- a/code/modules/spells/spell_types/shadow_walk.dm +++ /dev/null @@ -1,91 +0,0 @@ -#define SHADOW_REGEN_RATE 1.5 - -/obj/effect/proc_holder/spell/targeted/shadowwalk - name = "Shadow Walk" - desc = "Grants unlimited movement in darkness." - charge_max = 0 - clothes_req = FALSE - antimagic_allowed = TRUE - phase_allowed = TRUE - selection_type = "range" - range = -1 - include_user = TRUE - cooldown_min = 0 - overlay = null - action_icon = 'icons/mob/actions/actions_minor_antag.dmi' - action_icon_state = "ninja_cloak" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/spell/targeted/shadowwalk/cast(list/targets,mob/living/user = usr) - var/L = user.loc - if(istype(user.loc, /obj/effect/dummy/phased_mob/shadowling)) - to_chat(user, span_boldwarning("You can't shadow walk while void jaunting!")) - return - if(istype(user.loc, /obj/effect/dummy/phased_mob/shadow)) - var/obj/effect/dummy/phased_mob/shadow/S = L - S.end_jaunt(FALSE) - return - else - var/turf/T = get_turf(user) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1) - visible_message(span_boldwarning("[user] melts into the shadows!")) - user.SetAllImmobility(0) - user.setStaminaLoss(0, 0) - var/obj/effect/dummy/phased_mob/shadow/S2 = new(get_turf(user.loc)) - user.forceMove(S2) - S2.jaunter = user - else - to_chat(user, span_warning("It isn't dark enough here!")) - -/obj/effect/dummy/phased_mob/shadow - var/mob/living/jaunter - -/obj/effect/dummy/phased_mob/shadow/Initialize(mapload) - . = ..() - START_PROCESSING(SSobj, src) - -/obj/effect/dummy/phased_mob/shadow/Destroy() - jaunter = null - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/effect/dummy/phased_mob/shadow/process(delta_time) - var/turf/T = get_turf(src) - var/light_amount = T.get_lumcount() - if(!jaunter || jaunter.loc != src) - qdel(src) - if (light_amount < 0.2 && (!QDELETED(jaunter))) //heal in the dark - jaunter.heal_overall_damage((SHADOW_REGEN_RATE * delta_time), (SHADOW_REGEN_RATE * delta_time), 0, BODYPART_ORGANIC) - check_light_level() - -/obj/effect/dummy/phased_mob/shadow/relaymove(mob/living/user, direction) - var/turf/oldloc = loc - . = ..() - if(loc != oldloc) - check_light_level() - -/obj/effect/dummy/phased_mob/shadow/phased_check(mob/living/user, direction) - . = ..() - if(. && isspaceturf(.)) - to_chat(user, "It really would not be wise to go into space.") - return FALSE - -/obj/effect/dummy/phased_mob/shadow/proc/check_light_level() - var/turf/T = get_turf(src) - var/light_amount = T.get_lumcount() - if(light_amount > 0.2) // jaunt ends - end_jaunt(TRUE) - -/obj/effect/dummy/phased_mob/shadow/proc/end_jaunt(forced = FALSE) - if(jaunter) - if(forced) - visible_message(span_boldwarning("[jaunter] is revealed by the light!")) - else - - visible_message("[jaunter] emerges from the darkness!") - playsound(loc, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - qdel(src) - -#undef SHADOW_REGEN_RATE diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm deleted file mode 100644 index e0c085316bd8..000000000000 --- a/code/modules/spells/spell_types/shapeshift.dm +++ /dev/null @@ -1,181 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/shapeshift - name = "Shapechange" - desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed." - clothes_req = FALSE - human_req = FALSE - charge_max = 200 - cooldown_min = 50 - range = -1 - include_user = TRUE - invocation = "RAC'WA NO!" - invocation_type = SPELL_INVOCATION_SAY - action_icon_state = "shapeshift" - - var/revert_on_death = TRUE - var/die_with_shapeshifted_form = TRUE - var/convert_damage = TRUE //If you want to convert the caster's health to the shift, and vice versa. - var/convert_damage_type = BRUTE //Since simplemobs don't have advanced damagetypes, what to convert damage back into. - - var/shapeshift_type - var/list/possible_shapes = list(/mob/living/simple_animal/mouse,\ - /mob/living/simple_animal/pet/dog/corgi,\ - /mob/living/simple_animal/hostile/carp/ranged/chaos,\ - /mob/living/simple_animal/bot/ed209,\ - /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper,\ - /mob/living/simple_animal/hostile/construct/armored) - -/obj/effect/proc_holder/spell/targeted/shapeshift/cast(list/targets,mob/user = usr) - if(src in user.mob_spell_list) - user.mob_spell_list.Remove(src) - user.mind.AddSpell(src) - if(user.buckled) - user.buckled.unbuckle_mob(src,force=TRUE) - for(var/mob/living/M in targets) - if(!shapeshift_type) - var/list/animal_list = list() - for(var/path in possible_shapes) - var/mob/living/simple_animal/A = path - animal_list[initial(A.name)] = path - var/new_shapeshift_type = input(M, "Choose Your Animal Form!", "It's Morphing Time!", null) as null|anything in animal_list - if(shapeshift_type) - return - shapeshift_type = new_shapeshift_type - if(!shapeshift_type) //If you aren't gonna decide I am! - shapeshift_type = pick(animal_list) - shapeshift_type = animal_list[shapeshift_type] - - var/obj/shapeshift_holder/S = locate() in M - if(S) - Restore(M) - else - Shapeshift(M) - - -/obj/effect/proc_holder/spell/targeted/shapeshift/proc/Shapeshift(mob/living/caster) - var/obj/shapeshift_holder/H = locate() in caster - if(H) - to_chat(caster, span_warning("You're already shapeshifted!")) - return - - if(iscyborg(caster)) - to_chat(caster, span_warning("You cannot shapeshift as a cyborg!")) - return - - var/mob/living/shape = new shapeshift_type(caster.loc) - if(caster.maxHealth - (caster.health + caster.maxHealth)/2 >= shape.maxHealth) // this would instantly kill the caster - to_chat(caster, span_warning("You are too damaged to shapeshift into this form!")) - qdel(shape) - return - - H = new(shape,src,caster) - - clothes_req = FALSE - human_req = FALSE - -/obj/effect/proc_holder/spell/targeted/shapeshift/proc/Restore(mob/living/shape) - var/obj/shapeshift_holder/H = locate() in shape - if(!H) - return - - H.restore() - - clothes_req = initial(clothes_req) - human_req = initial(human_req) - -/obj/effect/proc_holder/spell/targeted/shapeshift/dragon - name = "Dragon Form" - desc = "Take on the shape a lesser ash drake." - invocation = "RAAAAAAAAWR!" - convert_damage = FALSE - - - shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser - - -/obj/shapeshift_holder - name = "Shapeshift holder" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF - var/mob/living/stored - var/mob/living/shape - var/restoring = FALSE - var/datum/soullink/shapeshift/slink - var/obj/effect/proc_holder/spell/targeted/shapeshift/source - -/obj/shapeshift_holder/Initialize(mapload,obj/effect/proc_holder/spell/targeted/shapeshift/source,mob/living/caster) - . = ..() - src.source = source - shape = loc - if(!istype(shape)) - CRASH("shapeshift holder created outside mob/living") - stored = caster - if(stored.mind) - stored.mind.transfer_to(shape) - stored.forceMove(src) - stored.notransform = TRUE - if(source.convert_damage) - var/damapply = (stored.maxHealth - (stored.health + stored.maxHealth)/2) //Carbons go from -100 to 100 naturally, while simplemobs only go from 0 to 100. Can't do a direct conversion. - shape.apply_damage(damapply, source.convert_damage_type, wound_bonus=CANT_WOUND) - slink = soullink(/datum/soullink/shapeshift, stored , shape) - slink.source = src - -/obj/shapeshift_holder/Destroy() - if(!restoring) - restore() - stored = null - shape = null - . = ..() - -/obj/shapeshift_holder/Moved() - . = ..() - if(!restoring || QDELETED(src)) - restore() - -/obj/shapeshift_holder/handle_atom_del(atom/A) - if(A == stored && !restoring) - restore() - -/obj/shapeshift_holder/Exited(atom/movable/AM) - if(AM == stored && !restoring) - restore() - -/obj/shapeshift_holder/proc/casterDeath() - //Something kills the stored caster through direct damage. - if(source.revert_on_death) - restore(death=TRUE) - else - shape.death() - -/obj/shapeshift_holder/proc/shapeDeath() - //Shape dies. - if(source.die_with_shapeshifted_form) - if(source.revert_on_death) - restore(death=TRUE) - else - restore() - -/obj/shapeshift_holder/proc/restore(death=FALSE) - restoring = TRUE - qdel(slink) - stored.forceMove(get_turf(src)) - stored.notransform = FALSE - if(shape.mind) - shape.mind.transfer_to(stored) - if(death) - stored.death() - else if(source.convert_damage) - stored.revive(full_heal = TRUE) - var/damapply = (shape.maxHealth - 2*shape.health) //Since we halved incoming damage, double outgoing. - stored.apply_damage(damapply, source.convert_damage_type, wound_bonus=CANT_WOUND) - qdel(shape) - qdel(src) - -/datum/soullink/shapeshift - var/obj/shapeshift_holder/source - -/datum/soullink/shapeshift/ownerDies(gibbed, mob/living/owner) - if(source) - source.casterDeath(gibbed) - -/datum/soullink/shapeshift/sharerDies(gibbed, mob/living/sharer) - if(source) - source.shapeDeath(gibbed) diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm new file mode 100644 index 000000000000..bbbf507ed4aa --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -0,0 +1,236 @@ +// A status effect for having a mob temporarily (usually) change form into that of another mob. +// When the status effect is removed, the mob is reverted back to their human form in most cases. +// If you want a more permanent polymorph, see [/proc/wabbajack]. +/datum/status_effect/shapechange_mob + id = "shapechange_mob" + alert_type = /atom/movable/screen/alert/status_effect/shapeshifted + // When the mob's deleted on_remove() will handle our references + on_remove_on_mob_delete = TRUE + + /// The caster's mob. Who has transformed into us + /// This reference is handled in [/proc/restore_caster], which is always called if we delete + var/mob/living/caster_mob + /// Whether we're currently undoing the change + var/already_restored = FALSE + +/datum/status_effect/shapechange_mob/on_creation(mob/living/new_owner, mob/living/caster) + // If any type or subtype of shapeshift mob is on the new_owner already throw an error and self-delete + if(locate(type) in new_owner.status_effects) + stack_trace("Mob shapechange status effect applied to a mob which already was shapechanged, which will definitely cause issues.") + qdel(src) + return + + // No caster mob, no one to put in the mob. Self-delete + if(!istype(caster)) + stack_trace("Mob shapechange status effect applied without a proper caster.") + qdel(src) + return + + src.caster_mob = caster + return ..() + +/datum/status_effect/shapechange_mob/on_apply() + caster_mob.mind?.transfer_to(owner) + caster_mob.forceMove(owner) + caster_mob.notransform = TRUE + caster_mob.apply_status_effect(/datum/status_effect/incapacitating/stasis, STASIS_SHAPECHANGE_EFFECT) + + RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, PROC_REF(on_wabbajacked)) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_shape_death)) + RegisterSignal(caster_mob, COMSIG_LIVING_DEATH, PROC_REF(on_caster_death)) + RegisterSignal(caster_mob, COMSIG_PARENT_QDELETING, PROC_REF(on_caster_deleted)) + + SEND_SIGNAL(caster_mob, COMSIG_LIVING_SHAPESHIFTED, owner) + return TRUE + +/datum/status_effect/shapechange_mob/on_remove() + // If the owner was straight up deleted we shouldn't restore anyone + // (the caster's stored in the owner's contents so if it's qdel'd so is the caster) + if(!QDELETED(owner)) + restore_caster() + + // Either restore_caster() or other signals should have handled this reference by now, + // but juuust in case make sure nothing sticks around. + caster_mob = null + +/// Signal proc for [COMSIG_LIVING_PRE_WABBAJACKED] to prevent us from being Wabbajacked and messed up. +/datum/status_effect/shapechange_mob/proc/on_wabbajacked(mob/living/source, randomized) + SIGNAL_HANDLER + + var/mob/living/revealed_mob = caster_mob + source.visible_message(span_warning("[revealed_mob] gets pulled back to their normal form!")) + INVOKE_ASYNC(src, PROC_REF(restore_caster)) + revealed_mob.Paralyze(10 SECONDS, ignore_canstun = TRUE) + return STOP_WABBAJACK + +/// Restores the caster back to their human form. +/// if kill_caster_after is TRUE, the caster will have death() called on them after restoring. +/datum/status_effect/shapechange_mob/proc/restore_caster(kill_caster_after = FALSE) + if(already_restored || !caster_mob) + return + + if(QDELETED(owner)) + CRASH("Mob shapechange effect called restore_caster while the owner was qdeleted, this shouldn't happen.") + + already_restored = TRUE + UnregisterSignal(owner, list(COMSIG_LIVING_PRE_WABBAJACKED, COMSIG_LIVING_DEATH)) + UnregisterSignal(caster_mob, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH)) + + caster_mob.forceMove(owner.loc) + caster_mob.notransform = FALSE + caster_mob.remove_status_effect(/datum/status_effect/incapacitating/stasis, STASIS_SHAPECHANGE_EFFECT) + owner.mind?.transfer_to(caster_mob) + + if(kill_caster_after) + caster_mob.death() + + after_unchange() + caster_mob = null + + // Destroy the owner after all's said and done, this will also destroy our status effect (src) + // retore_caster() should never reach this point while either the owner or the effect is being qdeleted + qdel(owner) + +/// Effects done after the casting mob has reverted to their human form. +/datum/status_effect/shapechange_mob/proc/after_unchange() + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(owner, COMSIG_LIVING_UNSHAPESHIFTED, caster_mob) + +/// Signal proc for [COMSIG_LIVING_DEATH] from our owner. +/// If our owner mob is killed, we should revert back to normal. +/datum/status_effect/shapechange_mob/proc/on_shape_death(datum/source, gibbed) + SIGNAL_HANDLER + + // gibbed = deleted = nothing to restore + if(gibbed) + return + + INVOKE_ASYNC(src, PROC_REF(restore_caster)) + +/// Signal proc for [COMSIG_LIVING_DEATH] from our caster. +/// If our internal caster is killed, kill our owner, too (which causes the above signal). +/// This should very rarely end up being called but you never know +/datum/status_effect/shapechange_mob/proc/on_caster_death(datum/source, gibbed) + SIGNAL_HANDLER + + // Our caster inside was gibbed, mirror the gib to our mob + if(gibbed) + INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/, gib)) + + // Otherwise our caster died, just make our mob die + else + INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/, death)) + +/// Signal proc for [COMSIG_PARENT_QDELETING] from our caster, delete us / our owner if we get deleted +/datum/status_effect/shapechange_mob/proc/on_caster_deleted(datum/source) + SIGNAL_HANDLER + + caster_mob = null + if(QDELETED(owner)) + return + + qdel(owner) + +// A subtype for a shapechange sourced from a shapeshift spell. +/datum/status_effect/shapechange_mob/from_spell + id = "shapechange_from_spell" + /// The shapechange spell that's caused our change + var/datum/weakref/source_weakref + +/datum/status_effect/shapechange_mob/from_spell/on_creation(mob/living/new_owner, mob/living/caster, datum/action/cooldown/spell/shapeshift/source_spell) + if(!istype(source_spell)) + stack_trace("Mob shapechange \"from spell\" status effect applied without a source spell.") + qdel(src) + return + + source_weakref = WEAKREF(source_spell) + return ..() + +/datum/status_effect/shapechange_mob/from_spell/on_apply() + var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve() + if(source_spell.owner == caster_mob) + // Assuming the spell is owned by the caster, give it over to the shapeshifted mob + // so they can actually transform back to their original form + source_spell.Grant(owner) + + if(source_spell.convert_damage) + var/damage_to_apply = owner.maxHealth * ((caster_mob.maxHealth - caster_mob.health) / caster_mob.maxHealth) + + owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, wound_bonus = CANT_WOUND) + owner.blood_volume = caster_mob.blood_volume + + for(var/datum/action/bodybound_action as anything in caster_mob.actions) + if(bodybound_action.target != caster_mob) + continue + bodybound_action.Grant(owner) + + return ..() + +/datum/status_effect/shapechange_mob/from_spell/restore_caster(kill_caster_after) + var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve() + // The owner = owner check here is specifically for edge cases in which the owner of the spell + // is no longer in control of the shapeshifted mob, such as mindswapping out of a shapeshift + if(!QDELETED(source_spell) && source_spell.owner == owner) + source_spell.Grant(caster_mob) + + for(var/datum/action/bodybound_action as anything in owner.actions) + if(bodybound_action.target != caster_mob) + continue + bodybound_action.Grant(caster_mob) + + return ..() + +/datum/status_effect/shapechange_mob/from_spell/after_unchange() + . = ..() + var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref?.resolve() + if(QDELETED(source_spell) || !source_spell.convert_damage) + return + + if(caster_mob.stat != DEAD) + caster_mob.revive(TRUE) + + var/damage_to_apply = caster_mob.maxHealth * ((owner.maxHealth - owner.health) / owner.maxHealth) + caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, wound_bonus = CANT_WOUND) + + caster_mob.blood_volume = owner.blood_volume + +/datum/status_effect/shapechange_mob/from_spell/on_shape_death(datum/source, gibbed) + var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve() + // If our spell dictates our wizard dies when our shape dies, we won't restore by default + if(!QDELETED(source_spell) && source_spell.die_with_shapeshifted_form) + // (But if our spell says we should revert on death anyways, we'll also do that) + if(source_spell.revert_on_death) + INVOKE_ASYNC(src, PROC_REF(restore_caster), TRUE) + // Otherwise, we just do nothing - we dead + return + + return ..() // Restore like normal + +/datum/status_effect/shapechange_mob/from_spell/on_caster_death(datum/source) + var/datum/action/cooldown/spell/shapeshift/source_spell = source_weakref.resolve() + // If our spell does not have revert_on_death, don't do anything when our caster dies + if(!QDELETED(source_spell) && !source_spell.revert_on_death) + return + + return ..() // Kill our owner and revert, like normal + +/atom/movable/screen/alert/status_effect/shapeshifted + name = "Shapeshifted" + desc = "Your form is not your own... you're shapeshifted into another creature! \ + A wizard could turn you back - or maybe you're stuck like this for good?" + icon_state = "shapeshifted" + +/atom/movable/screen/alert/status_effect/shapeshifted/Click(location, control, params) + . = ..() + if(!.) + return + + var/mob/living/living_user = usr + if(!istype(living_user)) + return + + // Clicking the action will try to cast whatever spell shifted us in the first place + for(var/datum/action/cooldown/spell/shapeshift/shift_spell in living_user.actions) + if(istype(living_user, shift_spell.shapeshift_type)) + shift_spell.Trigger() + return TRUE diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm new file mode 100644 index 000000000000..6d26ad8f8591 --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -0,0 +1,181 @@ +// Helper for checking of someone's shapeshifted currently. +#define is_shifted(mob) mob.has_status_effect(/datum/status_effect/shapechange_mob/from_spell) + +/** + * Shapeshift spells. + * + * Allows the caster to transform to and from a different mob type. + */ +/datum/action/cooldown/spell/shapeshift + button_icon_state = "shapeshift" + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + + /// Our spell's requrements before we shapeshifted. Stored on shapeshift so we can restore them after unshifting. + var/pre_shift_requirements + + /// Whether we revert to our human form on death. + var/revert_on_death = TRUE + /// Whether we die when our shapeshifted form is killed + var/die_with_shapeshifted_form = TRUE + /// Whether we convert our health from one form to another + var/convert_damage = TRUE + /// If convert damage is true, the damage type we deal when converting damage back and forth + var/convert_damage_type = BRUTE + + /// Our chosen type. + var/mob/living/shapeshift_type + /// All possible types we can become. + /// This should be implemented even if there is only one choice. + var/list/atom/possible_shapes + +/datum/action/cooldown/spell/shapeshift/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/shapeshift/before_cast(mob/living/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + if(shapeshift_type) + // If another shapeshift spell was casted while we're already shifted, they could technically go to do_unshapeshift(). + // However, we don't really want people casting shapeshift A to un-shapeshift from shapeshift B, + // as it could cause bugs or unintended behavior. So we'll just stop them here. + if(is_shifted(cast_on) && !is_type_in_list(cast_on, possible_shapes)) + to_chat(cast_on, span_warning("This spell won't un-shapeshift you from this form!")) + return . | SPELL_CANCEL_CAST + + return + + if(length(possible_shapes) == 1) + shapeshift_type = possible_shapes[1] + return + + // Not bothering with caching these as they're only ever shown once + var/list/shape_names_to_types = list() + var/list/shape_names_to_image = list() + if(!length(shape_names_to_types) || !length(shape_names_to_image)) + for(var/atom/path as anything in possible_shapes) + var/shape_name = initial(path.name) + shape_names_to_types[shape_name] = path + shape_names_to_image[shape_name] = image(icon = initial(path.icon), icon_state = initial(path.icon_state)) + + var/picked_type = show_radial_menu( + cast_on, + cast_on, + shape_names_to_image, + custom_check = CALLBACK(src, PROC_REF(check_menu), cast_on), + radius = 38, + ) + + if(!picked_type) + return . | SPELL_CANCEL_CAST + + var/atom/shift_type = shape_names_to_types[picked_type] + if(!ispath(shift_type)) + return . | SPELL_CANCEL_CAST + + shapeshift_type = shift_type || pick(possible_shapes) + if(QDELETED(src) || QDELETED(owner) || !can_cast_spell(FALSE)) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/shapeshift/cast(mob/living/cast_on) + . = ..() + cast_on.buckled?.unbuckle_mob(cast_on, force = TRUE) + + var/currently_ventcrawling = (cast_on.movement_type & VENTCRAWLING) + var/mob/living/resulting_mob + + // Do the shift back or forth + if(is_shifted(cast_on)) + resulting_mob = do_unshapeshift(cast_on) + else + resulting_mob = do_shapeshift(cast_on) + + // The shift is done, let's make sure they're in a valid state now + // If we're not ventcrawling, we don't need to mind + if(!currently_ventcrawling || !resulting_mob) + return + + // We are ventcrawling - can our new form support ventcrawling? + if(resulting_mob.ventcrawler == VENTCRAWLER_ALWAYS || resulting_mob.ventcrawler == VENTCRAWLER_NUDE) + return + + // Uh oh. You've shapeshifted into something that can't fit into a vent, while ventcrawling. + eject_from_vents(resulting_mob) + +/// Whenever someone shapeshifts within a vent, +/// and enters a state in which they are no longer a ventcrawler, +/// they are brutally ejected from the vents. In the form of gibs. +/datum/action/cooldown/spell/shapeshift/proc/eject_from_vents(mob/living/cast_on) + var/obj/machinery/atmospherics/pipe_you_die_in = cast_on.loc + var/datum/pipeline/our_pipeline + var/pipenets = pipe_you_die_in.return_pipenets() + if(islist(pipenets)) + our_pipeline = pipenets[1] + else + our_pipeline = pipenets + + to_chat(cast_on, span_userdanger("Casting [src] inside of [pipe_you_die_in] quickly turns you into a bloody mush!")) + var/obj/effect/gib_type = isalien(cast_on) ? /obj/effect/gibspawner/xeno : /obj/effect/gibspawner/generic + + for(var/obj/machinery/atmospherics/components/unary/possible_vent in range(10, get_turf(cast_on))) + if(length(possible_vent.parents) && possible_vent.parents[1] == our_pipeline) + new gib_type(get_turf(possible_vent)) + playsound(possible_vent, 'sound/effects/reee.ogg', 75, TRUE) + + priority_announce("We detected a pipe blockage around [get_area(get_turf(cast_on))], please dispatch someone to investigate.", "Central Command") + // Gib our caster, and make sure to leave nothing behind + // (If we leave something behind, it'll drop on the turf of the pipe, which is kinda wrong.) +// cast_on.investigate_log("has been gibbed by shapeshifting while ventcrawling.", INVESTIGATE_DEATHS) + cast_on.gib(TRUE, TRUE, TRUE) + +/// Callback for the radial that allows the user to choose their species. +/datum/action/cooldown/spell/shapeshift/proc/check_menu(mob/living/caster) + if(QDELETED(src)) + return FALSE + if(QDELETED(caster)) + return FALSE + + return !caster.incapacitated() + +/// Actually does the shapeshift, for the caster. +/datum/action/cooldown/spell/shapeshift/proc/do_shapeshift(mob/living/caster) + var/mob/living/new_shape = create_shapeshift_mob(caster.loc) + var/datum/status_effect/shapechange_mob/shapechange = new_shape.apply_status_effect(/datum/status_effect/shapechange_mob/from_spell, caster, src) + if(!shapechange) + // We failed to shift, maybe because we were already shapeshifted? + // Whatver the case, this shouldn't happen, so throw a stack trace. + to_chat(caster, span_warning("You can't shapeshift in this form!")) + stack_trace("[type] do_shapeshift was called when the mob was already shapeshifted (from a spell).") + return + + // Make sure it's castable even in their new form. + pre_shift_requirements = spell_requirements + spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB) + + return new_shape + +/// Actually does the un-shapeshift, from the caster. (Caster is a shapeshifted mob.) +/datum/action/cooldown/spell/shapeshift/proc/do_unshapeshift(mob/living/caster) + var/datum/status_effect/shapechange_mob/shapechange = caster.has_status_effect(/datum/status_effect/shapechange_mob/from_spell) + if(!shapechange) + // We made it to do_unshapeshift without having a shapeshift status effect, this shouldn't happen. + to_chat(caster, span_warning("You can't un-shapeshift from this form!")) + stack_trace("[type] do_unshapeshift was called when the mob wasn't even shapeshifted (from a spell).") + return + + // Restore the requirements. + spell_requirements = pre_shift_requirements + pre_shift_requirements = null + + var/mob/living/unshapeshifted_mob = shapechange.caster_mob + caster.remove_status_effect(/datum/status_effect/shapechange_mob/from_spell) + return unshapeshifted_mob + +/// Helper proc that instantiates the mob we shapeshift into. +/// Returns an instance of a living mob. Can be overridden. +/datum/action/cooldown/spell/shapeshift/proc/create_shapeshift_mob(atom/loc) + return new shapeshift_type(loc) + +#undef is_shifted diff --git a/code/modules/spells/spell_types/shapeshift/dragon.dm b/code/modules/spells/spell_types/shapeshift/dragon.dm new file mode 100644 index 000000000000..061a342d1d33 --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/dragon.dm @@ -0,0 +1,9 @@ + +/datum/action/cooldown/spell/shapeshift/dragon + name = "Dragon Form" + desc = "Take on the shape a lesser ash drake." + invocation = "RAAAAAAAAWR!" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + + possible_shapes = list(/mob/living/simple_animal/hostile/megafauna/dragon/lesser) diff --git a/code/modules/spells/spell_types/shapeshift/polar_bear.dm b/code/modules/spells/spell_types/shapeshift/polar_bear.dm new file mode 100644 index 000000000000..7a7443ec29c4 --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/polar_bear.dm @@ -0,0 +1,8 @@ +/datum/action/cooldown/spell/shapeshift/polar_bear + name = "Polar Bear Form" + desc = "Take on the shape of a polar bear." + invocation = "RAAAAAAAAWR!" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + +// possible_shapes = list(/mob/living/simple_animal/hostile/asteroid/polarbear/lesser) diff --git a/code/modules/spells/spell_types/shapeshift/shapechange.dm b/code/modules/spells/spell_types/shapeshift/shapechange.dm new file mode 100644 index 000000000000..4d2f89c2c9cc --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -0,0 +1,20 @@ +/datum/action/cooldown/spell/shapeshift/wizard + name = "Wild Shapeshift" + desc = "Take on the shape of another for a time to use their natural abilities. \ + Once you've made your choice, it cannot be changed." + + cooldown_time = 20 SECONDS + cooldown_reduction_per_rank = 3.75 SECONDS + + invocation = "RAC'WA NO!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + possible_shapes = list( + /mob/living/simple_animal/mouse, + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/simple_animal/hostile/carp/ranged/chaos, + /mob/living/simple_animal/bot/ed209, + /mob/living/simple_animal/hostile/poison/giant_spider, + /mob/living/simple_animal/hostile/construct/armored, + ) diff --git a/code/modules/spells/spell_types/soultap.dm b/code/modules/spells/spell_types/soultap.dm deleted file mode 100644 index 830436d74dff..000000000000 --- a/code/modules/spells/spell_types/soultap.dm +++ /dev/null @@ -1,33 +0,0 @@ -#define HEALTH_LOST_PER_SOUL_TAP 20 - -//SOUL TAP!// -//Trades 20 max health for a refresh on all of your spells. I was considering making it depend on the cooldowns of your spells, but I want to support "Big spell wizard" with this loadout. -//the two spells that sound most problematic with this is mindswap and lichdom, but soul tap requires clothes for mindswap and lichdom takes your soul. - -/obj/effect/proc_holder/spell/self/tap - name = "Soul Tap" - desc = "Fuel your spells using your own soul!" - school = "necromancy" //i could see why this wouldn't be necromancy but messing with souls or whatever. ectomancy? - charge_max = 10 - invocation = "AT ANY COST!" - invocation_type = SPELL_INVOCATION_SAY - level_max = 0 - cooldown_min = 10 - - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "soultap" - -/obj/effect/proc_holder/spell/self/tap/cast(mob/living/user = usr) - if(!user.mind.hasSoul) - to_chat(user, span_warning("You do not possess a soul to tap into!")) - return - to_chat(user, span_danger("Your body feels drained and there is a burning pain in your chest.")) - user.maxHealth -= HEALTH_LOST_PER_SOUL_TAP - user.health = min(user.health, user.maxHealth) - if(user.maxHealth <= 0) - to_chat(user, span_userdanger("Your weakened soul is completely consumed by the tap!")) - user.mind.hasSoul = FALSE - for(var/obj/effect/proc_holder/spell/spell in user.mind.spell_list) - spell.charge_counter = spell.charge_max - spell.recharging = FALSE - spell.update_icon() diff --git a/code/modules/spells/spell_types/spacetime_distortion.dm b/code/modules/spells/spell_types/spacetime_distortion.dm deleted file mode 100644 index 9fa913904ddb..000000000000 --- a/code/modules/spells/spell_types/spacetime_distortion.dm +++ /dev/null @@ -1,124 +0,0 @@ -/obj/effect/proc_holder/spell/spacetime_dist - name = "Spacetime Distortion" - desc = "Entangle the strings of space-time in an area around you, randomizing the layout and making proper movement impossible. The strings vibrate..." - charge_max = 300 - var/duration = 150 - range = 7 - var/list/effects - var/ready = TRUE - centcom_cancast = FALSE - sound = 'sound/effects/magic.ogg' - cooldown_min = 300 - level_max = 0 - -/obj/effect/proc_holder/spell/spacetime_dist/can_cast(mob/user = usr) - if(ready) - return ..() - return FALSE - -/obj/effect/proc_holder/spell/spacetime_dist/choose_targets(mob/user = usr) - var/list/turfs = spiral_range_turfs(range, user) - if(!turfs.len) - revert_cast() - return - - ready = FALSE - var/list/turf_steps = list() - var/length = round(turfs.len * 0.5) - for(var/i in 1 to length) - turf_steps[pick_n_take(turfs)] = pick_n_take(turfs) - if(turfs.len > 0) - var/turf/loner = pick(turfs) - var/area/A = get_area(user) - turf_steps[loner] = get_turf(pick(A.contents)) - - perform(turf_steps,user=user) - -/obj/effect/proc_holder/spell/spacetime_dist/after_cast(list/targets) - addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration) - -/obj/effect/proc_holder/spell/spacetime_dist/cast(list/targets, mob/user = usr) - effects = list() - for(var/V in targets) - var/turf/T0 = V - var/turf/T1 = targets[V] - var/obj/effect/cross_action/spacetime_dist/STD0 = new /obj/effect/cross_action/spacetime_dist(T0) - var/obj/effect/cross_action/spacetime_dist/STD1 = new /obj/effect/cross_action/spacetime_dist(T1) - STD0.linked_dist = STD1 - STD0.add_overlay(T1.photograph()) - STD1.linked_dist = STD0 - STD1.add_overlay(T0.photograph()) - STD1.set_light(4, 30, "#c9fff5") - effects += STD0 - effects += STD1 - -/obj/effect/proc_holder/spell/spacetime_dist/proc/clean_turfs() - for(var/effect in effects) - qdel(effect) - effects.Cut() - effects = null - ready = TRUE - -/obj/effect/cross_action - name = "cross me" - desc = "for crossing" - anchored = TRUE - -/obj/effect/cross_action/spacetime_dist - name = "spacetime distortion" - desc = "A distortion in spacetime. You can hear faint music..." - icon_state = "" - var/obj/effect/cross_action/spacetime_dist/linked_dist - var/busy = FALSE - var/sound - var/walks_left = 50 //prevents the game from hanging in extreme cases (such as minigun fire) - -/obj/effect/cross_action/singularity_act() - return - -/obj/effect/cross_action/singularity_pull() - return - -/obj/effect/cross_action/spacetime_dist/Initialize(mapload) - . = ..() - setDir(pick(GLOB.cardinals)) - -/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) - if(ismob(AM)) - var/mob/M = AM - if(M.anti_magic_check(chargecost = 0)) - return - if(linked_dist && walks_left > 0) - flick("purplesparkles", src) - linked_dist.get_walker(AM) - walks_left-- - -/obj/effect/cross_action/spacetime_dist/proc/get_walker(atom/movable/AM) - busy = TRUE - flick("purplesparkles", src) - AM.forceMove(get_turf(src)) - playsound(get_turf(src),sound,70,0) - busy = FALSE - -/obj/effect/cross_action/spacetime_dist/Crossed(atom/movable/AM) - . = ..() - if(!busy) - walk_link(AM) - -/obj/effect/cross_action/spacetime_dist/attackby(obj/item/W, mob/user, params) - if(user.temporarilyRemoveItemFromInventory(W)) - walk_link(W) - else - walk_link(user) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/effect/cross_action/spacetime_dist/attack_hand(mob/user) - walk_link(user) - -/obj/effect/cross_action/spacetime_dist/attack_paw(mob/user) - walk_link(user) - -/obj/effect/cross_action/spacetime_dist/Destroy() - busy = TRUE - linked_dist = null - return ..() diff --git a/code/modules/spells/spell_types/summonitem.dm b/code/modules/spells/spell_types/summonitem.dm deleted file mode 100644 index a5e4644386e7..000000000000 --- a/code/modules/spells/spell_types/summonitem.dm +++ /dev/null @@ -1,118 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/summonitem - name = "Instant Summons" - desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe." - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - invocation = "GAR YOK" - invocation_type = SPELL_INVOCATION_WHISPER - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = TRUE - - var/obj/marked_item - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "summons" - -/obj/effect/proc_holder/spell/targeted/summonitem/cast(list/targets,mob/user = usr) - for(var/mob/living/L in targets) - var/list/hand_items = list(L.get_active_held_item(),L.get_inactive_held_item()) - var/message - - if(!marked_item) //linking item to the spell - message = "" - for(var/obj/item/item in hand_items) - if(item.item_flags & ABSTRACT) - continue - if(HAS_TRAIT(item, TRAIT_NODROP)) - message += "Though it feels redundant, " - marked_item = item - message += "You mark [item] for recall." - name = "Recall [item]" - break - - if(!marked_item) - if(hand_items) - message = span_caution("You aren't holding anything that can be marked for recall.") - else - message = span_notice("You must hold the desired item in your hands to mark it for recall.") - - else if(marked_item && (marked_item in hand_items)) //unlinking item to the spell - message = span_notice("You remove the mark on [marked_item] to use elsewhere.") - name = "Instant Summons" - marked_item = null - - else if(marked_item && QDELETED(marked_item)) //the item was destroyed at some point - message = span_warning("You sense your marked item has been destroyed!") - name = "Instant Summons" - marked_item = null - - else //Getting previously marked item - var/obj/item_to_retrieve = marked_item - var/infinite_recursion = 0 //I don't want to know how someone could put something inside itself but these are wizards so let's be safe - - if(!item_to_retrieve.loc) - if(isorgan(item_to_retrieve)) // Organs are usually stored in nullspace - var/obj/item/organ/organ = item_to_retrieve - if(organ.owner) - // If this code ever runs I will be happy - log_combat(L, organ.owner, "magically removed [organ.name] from", addition="INTENT: [uppertext(L.a_intent)]") - organ.Remove(organ.owner) - else - while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) //if it's in something you get the whole thing. - if(isitem(item_to_retrieve.loc)) - var/obj/item/I = item_to_retrieve.loc - if(I.item_flags & ABSTRACT) //Being able to summon abstract things because your item happened to get placed there is a no-no - break - if(ismob(item_to_retrieve.loc)) //If its on someone, properly drop it - var/mob/M = item_to_retrieve.loc - - if(issilicon(M)) //Items in silicons warp the whole silicon - M.loc.visible_message(span_warning("[M] suddenly disappears!")) - M.forceMove(L.loc) - M.loc.visible_message(span_caution("[M] suddenly appears!")) - item_to_retrieve = null - break - M.dropItemToGround(item_to_retrieve) - - if(iscarbon(M)) //Edge case housekeeping - var/mob/living/carbon/C = M - //yogs start -- Yogs Vorecode - if(C.stomach_contents && (item_to_retrieve in C.stomach_contents)) - C.stomach_contents -= item_to_retrieve - //Yogs end - for(var/X in C.bodyparts) - var/obj/item/bodypart/part = X - if(item_to_retrieve in part.embedded_objects) - C.remove_embedded_object(item_to_retrieve, silent = TRUE, forced = TRUE) - to_chat(C, span_warning("The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!")) - break - - else - if(istype(item_to_retrieve.loc, /obj/machinery/portable_atmospherics/)) //Edge cases for moved machinery - var/obj/machinery/portable_atmospherics/P = item_to_retrieve.loc - P.disconnect() - P.update_icon() - - item_to_retrieve = item_to_retrieve.loc - - infinite_recursion += 1 - - if(!item_to_retrieve) - return - - if(item_to_retrieve.loc) - item_to_retrieve.loc.visible_message(span_warning("The [item_to_retrieve.name] suddenly disappears!")) - if(!L.put_in_hands(item_to_retrieve)) - item_to_retrieve.forceMove(L.drop_location()) - item_to_retrieve.loc.visible_message(span_caution("The [item_to_retrieve.name] suddenly appears!")) - playsound(get_turf(L), 'sound/magic/summonitems_generic.ogg', 50, 1) - else - item_to_retrieve.loc.visible_message(span_caution("The [item_to_retrieve.name] suddenly appears in [L]'s hand!")) - playsound(get_turf(L), 'sound/magic/summonitems_generic.ogg', 50, 1) - - - if(message) - to_chat(L, message) diff --git a/code/modules/spells/spell_types/telepathy.dm b/code/modules/spells/spell_types/telepathy.dm deleted file mode 100644 index 51c613aa9855..000000000000 --- a/code/modules/spells/spell_types/telepathy.dm +++ /dev/null @@ -1,32 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/telepathy - name = "Telepathy" - desc = "Telepathically transmits a message to the target." - charge_max = 0 - clothes_req = 0 - range = 7 - include_user = 0 - action_icon = 'icons/mob/actions/actions_revenant.dmi' - action_icon_state = "r_transmit" - action_background_icon_state = "bg_spell" - var/notice = "notice" - var/boldnotice = "boldnotice" - var/magic_check = FALSE - var/holy_check = FALSE - var/tinfoil_check = TRUE - -/obj/effect/proc_holder/spell/targeted/telepathy/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - for(var/mob/living/M in targets) - var/msg = stripped_input(usr, "What do you wish to tell [M]?", null, "") - if(!msg) - charge_counter = charge_max - return - log_directed_talk(user, M, msg, LOG_SAY, "[name]") - to_chat(user, "You transmit to [M]: [msg]") - if(!M.anti_magic_check(magic_check, holy_check, tinfoil_check, 0)) //hear no evil - to_chat(M, "You hear something behind you talking... [msg]") - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - var/follow_rev = FOLLOW_LINK(ded, user) - var/follow_whispee = FOLLOW_LINK(ded, M) - to_chat(ded, "[follow_rev] [user] [name]: \"[msg]\" to [follow_whispee] [span_name("[M]")]") \ No newline at end of file diff --git a/code/modules/spells/spell_types/teleport/_teleport.dm b/code/modules/spells/spell_types/teleport/_teleport.dm new file mode 100644 index 000000000000..909b94517d53 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/_teleport.dm @@ -0,0 +1,145 @@ + +/** + * ## Teleport Spell + * + * Teleports the caster to a turf selected by get_destinations(). + */ +/datum/action/cooldown/spell/teleport + sound = 'sound/weapons/zapbang.ogg' + + school = SCHOOL_TRANSLOCATION + + /// What channel the teleport is done under. + var/teleport_channel = TELEPORT_CHANNEL_MAGIC + /// Whether we force the teleport to happen (ie, it cannot be blocked by noteleport areas or blessings or whatever) + var/force_teleport = FALSE + /// A list of flags related to determining if our destination target is valid or not. + var/destination_flags = NONE + /// The sound played on arrival, after the teleport. + var/post_teleport_sound = 'sound/weapons/zapbang.ogg' + +/datum/action/cooldown/spell/teleport/cast(atom/cast_on) + . = ..() + var/list/turf/destinations = get_destinations(cast_on) + if(!length(destinations)) + CRASH("[type] failed to find a teleport destination.") + + do_teleport(cast_on, pick(destinations), asoundout = post_teleport_sound, channel = teleport_channel, forced = force_teleport) + +/// Gets a list of destinations that are valid +/datum/action/cooldown/spell/teleport/proc/get_destinations(atom/center) + CRASH("[type] did not implement get_destinations and either has no effects or implemented the spell incorrectly.") + +/// Checks if the passed turf is a valid destination. +/datum/action/cooldown/spell/teleport/proc/is_valid_destination(turf/selected) + if(isspaceturf(selected) && (destination_flags & TELEPORT_SPELL_SKIP_SPACE)) + return FALSE + if(selected.density && (destination_flags & TELEPORT_SPELL_SKIP_DENSE)) + return FALSE +// if(selected.is_blocked_turf(exclude_mobs = TRUE) && (destination_flags & TELEPORT_SPELL_SKIP_BLOCKED)) +// return FALSE + + return TRUE + +/** + * ### Radius Teleport Spell + * + * A subtype of teleport that will teleport the caster + * to a random turf within a radius of themselves. + */ +/datum/action/cooldown/spell/teleport/radius_turf + /// The inner radius around the caster that we can teleport to + var/inner_tele_radius = 1 + /// The outer radius around the caster that we can teleport to + var/outer_tele_radius = 2 + +/datum/action/cooldown/spell/teleport/radius_turf/get_destinations(atom/center) + var/list/valid_turfs = list() + var/list/possibles = RANGE_TURFS(outer_tele_radius, center) + if(inner_tele_radius > 0) + possibles -= RANGE_TURFS(inner_tele_radius, center) + + for(var/turf/nearby_turf as anything in possibles) + if(!is_valid_destination(nearby_turf)) + continue + + valid_turfs += nearby_turf + + // If there are valid turfs around us? + // Screw it, allow 'em to teleport to ANY nearby turf. + return length(valid_turfs) ? valid_turfs : possibles + +/datum/action/cooldown/spell/teleport/radius_turf/is_valid_destination(turf/selected) + . = ..() + if(!.) + return FALSE + + // putting them at the edge is dumb + if(selected.x > world.maxx - outer_tele_radius || selected.x < outer_tele_radius) + return FALSE + if(selected.y > world.maxy - outer_tele_radius || selected.y < outer_tele_radius) + return FALSE + + return TRUE + +/** + * ### Area Teleport Spell + * + * A subtype of teleport that will teleport the caster + * to a random turf within a selected (or random) area. + */ +/datum/action/cooldown/spell/teleport/area_teleport + force_teleport = TRUE // Forced, as the Wizard Den is noteleport and wizards couldn't escape otherwise. + destination_flags = TELEPORT_SPELL_SKIP_BLOCKED + /// The last area we chose to teleport / where we're currently teleporting to, if mid-cast + var/last_chosen_area_name + /// If FALSE, the caster can select the destination area. If TRUE, they will teleport to somewhere randomly instead. + var/randomise_selection = FALSE + /// If the invocation appends the selected area when said. Requires invocation mode shout or whisper. + var/invocation_says_area = TRUE + +/datum/action/cooldown/spell/teleport/area_teleport/get_destinations(atom/center) + var/list/valid_turfs = list() + for(var/turf/possible_destination as anything in get_area_turfs(GLOB.teleportlocs[last_chosen_area_name])) + if(!is_valid_destination(possible_destination)) + continue + + valid_turfs += possible_destination + + return valid_turfs + +/datum/action/cooldown/spell/teleport/area_teleport/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/area/target_area + if(randomise_selection) + target_area = pick(GLOB.teleportlocs) + else + target_area = tgui_input_list(cast_on, "Chose an area to teleport to.", "Teleport", GLOB.teleportlocs) + + if(QDELETED(src) || QDELETED(cast_on) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!target_area || isnull(GLOB.teleportlocs[target_area])) + return . | SPELL_CANCEL_CAST + + last_chosen_area_name = target_area + +/datum/action/cooldown/spell/teleport/area_teleport/cast(atom/cast_on) + if(isliving(cast_on)) + var/mob/living/living_cast_on = cast_on + living_cast_on.buckled?.unbuckle_mob(cast_on, force = TRUE) + return ..() + +/datum/action/cooldown/spell/teleport/area_teleport/invocation() + var/area/last_chosen_area = GLOB.teleportlocs[last_chosen_area_name] + + if(!invocation_says_area || isnull(last_chosen_area)) + return ..() + + switch(invocation_type) + if(INVOCATION_SHOUT) + owner.say("[invocation], [uppertext(last_chosen_area.name)]!", forced = "spell ([src])") + if(INVOCATION_WHISPER) + owner.whisper("[invocation], [uppertext(last_chosen_area.name)].", forced = "spell ([src])") diff --git a/code/modules/spells/spell_types/teleport/blink.dm b/code/modules/spells/spell_types/teleport/blink.dm new file mode 100644 index 000000000000..90c6d5b43903 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/blink.dm @@ -0,0 +1,19 @@ +/datum/action/cooldown/spell/teleport/radius_turf/blink + name = "Blink" + desc = "This spell randomly teleports you a short distance." + button_icon_state = "blink" + sound = 'sound/magic/blink.ogg' + + school = SCHOOL_TRANSLOCATION + cooldown_time = 2 SECONDS + cooldown_reduction_per_rank = 0.4 SECONDS + + invocation_type = INVOCATION_NONE + + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 0 + + inner_tele_radius = 0 + outer_tele_radius = 6 + + post_teleport_sound = 'sound/magic/blink.ogg' \ No newline at end of file diff --git a/code/modules/spells/spell_types/teleport/teleport.dm b/code/modules/spells/spell_types/teleport/teleport.dm new file mode 100644 index 000000000000..63d50aafee02 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/teleport.dm @@ -0,0 +1,52 @@ +/// The wizard's teleport SPELL +/datum/action/cooldown/spell/teleport/area_teleport/wizard + name = "Teleport" + desc = "This spell teleports you to an area of your selection." + button_icon_state = "teleport" + sound = 'sound/magic/teleport_diss.ogg' + + school = SCHOOL_TRANSLOCATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "SCYAR NILA" + invocation_type = INVOCATION_SHOUT + + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + + post_teleport_sound = 'sound/magic/teleport_app.ogg' + +// Santa's teleport, themed as such +/datum/action/cooldown/spell/teleport/area_teleport/wizard/santa + name = "Santa Teleport" + + invocation = "HO HO HO!" + spell_requirements = NONE + antimagic_flags = NONE + + invocation_says_area = FALSE // Santa moves in mysterious ways + +/// Used by the wizard's teleport scroll +/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll + name = "Teleport (scroll)" + cooldown_time = 0 SECONDS + + invocation = null + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + invocation_says_area = FALSE + +/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/IsAvailable(feedback = FALSE) + var/obj/item/teleportation_scroll/scroll = target + return ..() && owner.is_holding(scroll) && scroll.uses + +/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/mob/living/carbon/caster = cast_on + if(caster.incapacitated() || !caster.is_holding(target)) + return . | SPELL_CANCEL_CAST diff --git a/code/modules/spells/spell_types/the_traps.dm b/code/modules/spells/spell_types/the_traps.dm deleted file mode 100644 index 4bbdb786fd0a..000000000000 --- a/code/modules/spells/spell_types/the_traps.dm +++ /dev/null @@ -1,26 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - name = "The Traps!" - desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." - - charge_max = 250 - cooldown_min = 50 - - clothes_req = TRUE - invocation = "CAVERE INSIDIAS" - invocation_type = SPELL_INVOCATION_SAY - range = 3 - - summon_type = list( - /obj/structure/trap/stun, - /obj/structure/trap/fire, - /obj/structure/trap/chill, - /obj/structure/trap/damage - ) - summon_lifespan = 3000 - summon_amt = 5 - - action_icon_state = "the_traps" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps/post_summon(obj/structure/trap/T, mob/user) - T.immune_minds += user.mind - T.charges = 1 diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm new file mode 100644 index 000000000000..69e95e59e3fe --- /dev/null +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -0,0 +1,339 @@ +/** + * ## Touch Spell + * + * Touch spells are spells which function through the power of an item attack. + * + * Instead of the spell triggering when the caster presses the button, + * pressing the button will give them a hand object. + * The spell's effects are cast when the hand object makes contact with something. + * + * To implement a touch spell, all you need is to implement: + * * is_valid_target - to check whether the slapped target is valid + * * cast_on_hand_hit - to implement effects on cast + * + * However, for added complexity, you can optionally implement: + * * on_antimagic_triggered - to cause effects when antimagic is triggered + * * cast_on_secondary_hand_hit - to implement different effects if the caster r-clicked + + * It is not necessarily to touch any of the core functions, and is + * (generally) inadvisable unless you know what you're doing + */ +/datum/action/cooldown/spell/touch + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED + sound = 'sound/items/welder.ogg' + invocation = "High Five!" + invocation_type = INVOCATION_SHOUT + + /// Typepath of what hand we create on initial cast. + var/obj/item/melee/touch_attack/hand_path = /obj/item/melee/touch_attack + /// Ref to the hand we currently have deployed. + var/obj/item/melee/touch_attack/attached_hand + /// The message displayed to the person upon creating the touch hand + var/draw_message = span_notice("You channel the power of the spell to your hand.") + /// The message displayed upon willingly dropping / deleting / cancelling the touch hand before using it + var/drop_message = span_notice("You draw the power out of your hand.") + /// If TRUE, the caster can willingly hit themselves with the hand + var/can_cast_on_self = FALSE + +/datum/action/cooldown/spell/touch/Destroy() + // If we have an owner, the hand is cleaned up in Remove(), which Destroy() calls. + if(!owner) + QDEL_NULL(attached_hand) + return ..() + +/datum/action/cooldown/spell/touch/Remove(mob/living/remove_from) + remove_hand(remove_from) + return ..() + +// PreActivate is overridden to not check is_valid_target on the caster, as it makes less sense. +/datum/action/cooldown/spell/touch/PreActivate(atom/target) + return Activate(target) + +/datum/action/cooldown/spell/touch/is_action_active(atom/movable/screen/movable/action_button/current_button) + return !!attached_hand + +/datum/action/cooldown/spell/touch/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + if(attached_hand) + .[PANEL_DISPLAY_STATUS] = "ACTIVE" + +/datum/action/cooldown/spell/touch/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(!(carbon_owner.mobility_flags & MOBILITY_USE)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/touch/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/** + * Creates a new hand_path hand and equips it to the caster. + * + * If the equipping action fails, reverts the cooldown and returns FALSE. + * Otherwise, registers signals and returns TRUE. + */ +/datum/action/cooldown/spell/touch/proc/create_hand(mob/living/carbon/cast_on) + SHOULD_CALL_PARENT(TRUE) + + var/obj/item/melee/touch_attack/new_hand = new hand_path(cast_on, src) + if(!cast_on.put_in_hands(new_hand, del_on_fail = TRUE)) + reset_spell_cooldown() + if (!cast_on.get_num_arms()) + to_chat(cast_on, span_warning("You dont have any usable hands!")) + else + to_chat(cast_on, span_warning("Your hands are full!")) + return FALSE + + attached_hand = new_hand + RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_hand_hit)) +// RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_hand_hit)) + RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted)) + RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped)) + to_chat(cast_on, draw_message) + return TRUE + +/** + * Unregisters any signals and deletes the hand currently summoned by the spell. + * + * If reset_cooldown_after is TRUE, we will additionally refund the cooldown of the spell. + * If reset_cooldown_after is FALSE, we will instead just start the spell's cooldown + */ +/datum/action/cooldown/spell/touch/proc/remove_hand(mob/living/hand_owner, reset_cooldown_after = FALSE) + SHOULD_CALL_PARENT(TRUE) + + if(!QDELETED(attached_hand)) + UnregisterSignal(attached_hand, list(COMSIG_ITEM_AFTERATTACK, COMSIG_ITEM_AFTERATTACK_SECONDARY, COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED)) + hand_owner?.temporarilyRemoveItemFromInventory(attached_hand) + QDEL_NULL(attached_hand) + + if(reset_cooldown_after) + if(hand_owner) + to_chat(hand_owner, drop_message) + reset_spell_cooldown() + else + StartCooldown() + build_all_button_icons() + +// Touch spells don't go on cooldown OR give off an invocation until the hand is used itself. +/datum/action/cooldown/spell/touch/before_cast(atom/cast_on) + return ..() | SPELL_NO_FEEDBACK | SPELL_NO_IMMEDIATE_COOLDOWN + +/datum/action/cooldown/spell/touch/cast(mob/living/carbon/cast_on) + if(!QDELETED(attached_hand) && (attached_hand in cast_on.held_items)) + remove_hand(cast_on, reset_cooldown_after = TRUE) + return + + create_hand(cast_on) + return ..() + +/** + * Signal proc for [COMSIG_ITEM_AFTERATTACK] from our attached hand. + * + * When our hand hits an atom, we can cast do_hand_hit() on them. + */ +/datum/action/cooldown/spell/touch/proc/on_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters) + SIGNAL_HANDLER + + if(!proximity_flag) + return + if(victim == caster) + return + if(!can_cast_spell(feedback = FALSE)) + return + + INVOKE_ASYNC(src, PROC_REF(do_hand_hit), source, victim, caster) + +/** + * Signal proc for [COMSIG_ITEM_AFTERATTACK_SECONDARY] from our attached hand. + * + * Same as on_hand_hit, but for if right-click was used on hit. + */ +/*/datum/action/cooldown/spell/touch/proc/on_secondary_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters) + SIGNAL_HANDLER + + if(!proximity_flag) + return + if(victim == caster) + return + if(!can_cast_spell(feedback = FALSE)) + return + + INVOKE_ASYNC(src, PROC_REF(do_secondary_hand_hit), source, victim, caster)*/ + + //FUCK COMBAT MODE!!! + +/// Checks if the passed victim can be cast on by the caster. +/datum/action/cooldown/spell/touch/proc/can_hit_with_hand(atom/victim, mob/caster) + if(!can_cast_on_self && victim == caster) + return FALSE + if(!is_valid_target(victim)) + return FALSE + if(!can_cast_spell(feedback = TRUE)) + return FALSE + + return TRUE + +/** + * Calls cast_on_hand_hit() from the caster onto the victim. + * It's worth noting that victim will be guaranteed to be whatever checks are implemented in is_valid_target by this point. + * + * Implements checks for antimagic. + */ +/datum/action/cooldown/spell/touch/proc/do_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + SHOULD_NOT_OVERRIDE(TRUE) // Don't put effects here, put them in cast_on_hand_hit + + SEND_SIGNAL(src, COMSIG_SPELL_TOUCH_HAND_HIT, victim, caster, hand) + + var/mob/mob_victim = victim + if(istype(mob_victim) && mob_victim.can_block_magic(antimagic_flags)) + on_antimagic_triggered(hand, victim, caster) + + else if(!cast_on_hand_hit(hand, victim, caster)) + return + + log_combat(caster, victim, "cast the touch spell [name] on", hand) + spell_feedback() + remove_hand(caster) + +/** + * Calls do_secondary_hand_hit() from the caster onto the victim. + */ +/*/datum/action/cooldown/spell/touch/proc/do_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + var/secondary_result = cast_on_secondary_hand_hit(hand, victim, caster) + switch(secondary_result) + // Continue will remove the hand here and stop + if(SECONDARY_ATTACK_CONTINUE_CHAIN) + log_combat(caster, victim, "cast the touch spell [name] on", hand, "(secondary / alt cast)") + spell_feedback() + remove_hand(caster) + + // Call normal will call the normal cast proc + if(SECONDARY_ATTACK_CALL_NORMAL) + do_hand_hit(hand, victim, caster) + + // Cancel chain will do nothing, + if(SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return */ + + //FUCK COMBAT MODE!!! + +/** + * The actual process of casting the spell on the victim from the caster. + * + * Override / extend this to implement casting effects. + * Return TRUE on a successful cast to use up the hand (delete it) + * Return FALSE to do nothing and let them keep the hand in hand + */ +/datum/action/cooldown/spell/touch/proc/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return FALSE + +/** + * For any special casting effects done if the user right-clicks + * on touch spell instead of left-clicking + * + * Return SECONDARY_ATTACK_CALL_NORMAL to call the normal cast_on_hand_hit + * Return SECONDARY_ATTACK_CONTINUE_CHAIN to prevent the normal cast_on_hand_hit from calling, but still use up the hand + * Return SECONDARY_ATTACK_CANCEL_CHAIN to prevent the spell from being used + */ +/*/datum/action/cooldown/spell/touch/proc/cast_on_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return SECONDARY_ATTACK_CALL_NORMAL*/ + + //FUCK COMBAT MODE!!! + +/** + * Signal proc for [COMSIG_PARENT_QDELETING] from our attached hand. + * + * If our hand is deleted for a reason unrelated to our spell, + * unlink it (clear refs) and revert the cooldown + */ +/datum/action/cooldown/spell/touch/proc/on_hand_deleted(datum/source) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(remove_hand), null, TRUE) + +/** + * Signal proc for [COMSIG_ITEM_DROPPED] from our attached hand. + * + * If our caster drops the hand, remove the hand / revert the cast + * Basically gives them an easy hotkey to lose their hand without needing to click the button + */ +/datum/action/cooldown/spell/touch/proc/on_hand_dropped(datum/source, mob/living/dropper) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(remove_hand), dropper, TRUE) + +/** + * Signal proc for [COMSIG_ITEM_OFFER_TAKEN] from our attached hand. + * + * Giving a high five with our hand makes it cast + */ +/datum/action/cooldown/spell/touch/proc/on_hand_taken(obj/item/source, mob/living/carbon/offerer, mob/living/carbon/taker) + SIGNAL_HANDLER + + if(!can_hit_with_hand(taker, offerer)) + return + + INVOKE_ASYNC(src, PROC_REF(do_hand_hit), source, taker, offerer) + return COMPONENT_OFFER_INTERRUPT + +/** + * Called whenever our spell is cast, but blocked by antimagic. + */ +/datum/action/cooldown/spell/touch/proc/on_antimagic_triggered(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return + +/obj/item/melee/touch_attack + name = "\improper outstretched hand" + desc = "High Five?" + icon = 'icons/obj/wizard.dmi' + lefthand_file = 'icons/mob/inhands/misc/touchspell_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/touchspell_righthand.dmi' + icon_state = "latexballon" + item_state = null + item_flags = NEEDS_PERMIT | ABSTRACT + w_class = WEIGHT_CLASS_HUGE + force = 0 + throwforce = 0 + throw_range = 0 + throw_speed = 0 + /// A weakref to what spell made us. + var/datum/weakref/spell_which_made_us + +/obj/item/melee/touch_attack/Initialize(mapload, datum/action/cooldown/spell/spell) + . = ..() + + if(spell) + spell_which_made_us = WEAKREF(spell) + +/obj/item/melee/touch_attack/attack(mob/target, mob/living/carbon/user) + if(!iscarbon(user)) //Look ma, no hands + return TRUE + if(!(user.mobility_flags & MOBILITY_USE)) + to_chat(user, span_warning("You can't reach out!")) + return TRUE + return ..() + +/** + * When the hand component of a touch spell is qdel'd, (the hand is dropped or otherwise lost), + * the cooldown on the spell that made it is automatically refunded. + * + * However, if you want to consume the hand and not give a cooldown, + * such as adding a unique behavior to the hand specifically, this function will do that. + */ +/obj/item/melee/touch_attack/proc/remove_hand_with_no_refund(mob/holder) + var/datum/action/cooldown/spell/touch/hand_spell = spell_which_made_us?.resolve() + if(!QDELETED(hand_spell)) + hand_spell.remove_hand(holder, reset_cooldown_after = FALSE) + return + + // We have no spell associated for some reason, just delete us as normal. + holder.temporarilyRemoveItemFromInventory(src, force = TRUE) + qdel(src) diff --git a/code/modules/spells/spell_types/touch/duffelbag_curse.dm b/code/modules/spells/spell_types/touch/duffelbag_curse.dm new file mode 100644 index 000000000000..de419efd066e --- /dev/null +++ b/code/modules/spells/spell_types/touch/duffelbag_curse.dm @@ -0,0 +1,85 @@ +/datum/action/cooldown/spell/touch/duffelbag + name = "Bestow Cursed Duffel Bag" + desc = "A spell that summons a duffel bag demon on the target, slowing them down and slowly eating them." + button_icon_state = "duffelbag_curse" + sound = 'sound/magic/mm_hit.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 6 SECONDS + cooldown_reduction_per_rank = 1 SECONDS + + invocation = "HU'SWCH H'ANS!!" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + hand_path = /obj/item/melee/touch_attack/duffelbag + + /// Some meme "elaborate backstories" to use. + var/static/list/elaborate_backstory = list( + "spacewar origin story", + "military background", + "corporate connections", + "life in the colonies", + "anti-government activities", + "upbringing on the space farm", + "fond memories with your buddy Keith", + ) + +/datum/action/cooldown/spell/touch/duffelbag/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/touch/duffelbag/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster) + to_chat(caster, span_warning("The spell can't seem to affect [victim]!")) + to_chat(victim, span_warning("You really don't feel like talking about your [pick(elaborate_backstory)] with complete strangers today.")) + +/datum/action/cooldown/spell/touch/duffelbag/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster) + + // To get it started, stun and knockdown the person being hit + victim.flash_act() + victim.Immobilize(5 SECONDS) + victim.apply_damage(80, STAMINA) + victim.Knockdown(5 SECONDS) + + // If someone's already cursed, don't try to give them another + if(istype(victim.back, /obj/item/storage/backpack/duffelbag/cursed)) + to_chat(caster, span_warning("The burden of [victim]'s duffel bag becomes too much, shoving them to the floor!")) + to_chat(victim, span_warning("The weight of this bag becomes overburdening!")) + return TRUE + + // However if they're uncursed, they're fresh for getting a cursed bag + var/obj/item/storage/backpack/duffelbag/cursed/conjured_duffel = new get_turf(victim) + victim.visible_message( + span_danger("A growling duffel bag appears on [victim]!"), + span_danger("You feel something attaching itself to you, and a strong desire to discuss your [pick(elaborate_backstory)] at length!"), + ) + + conjured_duffel.pickup(victim) + conjured_duffel.forceMove(victim) + + // Put it on their back first + if(victim.dropItemToGround(victim.back)) + victim.equip_to_slot_if_possible(conjured_duffel, ITEM_SLOT_BACK, TRUE, TRUE) + return TRUE + + // If the back equip failed, put it in their hands first + if(victim.put_in_hands(conjured_duffel)) + return TRUE + + // If they had no empty hands, try to put it in their inactive hand first + victim.dropItemToGround(victim.get_inactive_held_item()) + if(victim.put_in_hands(conjured_duffel)) + return TRUE + + // If their inactive hand couldn't be emptied or found, put it in their active hand + victim.dropItemToGround(victim.get_active_held_item()) + if(victim.put_in_hands(conjured_duffel)) + return TRUE + + // Well, we failed to give them the duffel bag, + // but technically we still stunned them so that's something + return TRUE + +/obj/item/melee/touch_attack/duffelbag + name = "\improper burdening touch" + desc = "Where is the bar from here?" + icon_state = "duffelcurse" + item_state = "duffelcurse" diff --git a/code/modules/spells/spell_types/touch/flesh_to_stone.dm b/code/modules/spells/spell_types/touch/flesh_to_stone.dm new file mode 100644 index 000000000000..eff1a8b95407 --- /dev/null +++ b/code/modules/spells/spell_types/touch/flesh_to_stone.dm @@ -0,0 +1,33 @@ +/datum/action/cooldown/spell/touch/flesh_to_stone + name = "Flesh to Stone" + desc = "This spell charges your hand with the power to turn victims into inert statues for a long period of time." + button_icon_state = "statue" + sound = 'sound/magic/fleshtostone.ogg' + + school = SCHOOL_TRANSMUTATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "STAUN EI!!" + + hand_path = /obj/item/melee/touch_attack/flesh_to_stone + +/datum/action/cooldown/spell/touch/flesh_to_stone/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_victim = victim + if(living_victim.can_block_magic(antimagic_flags)) + to_chat(caster, span_warning("The spell can't seem to affect [victim]!")) + to_chat(victim, span_warning("You feel your flesh turn to stone for a moment, then revert back!")) + return TRUE + + living_victim.Stun(4 SECONDS) + living_victim.petrify() + return TRUE + +/obj/item/melee/touch_attack/flesh_to_stone + name = "\improper petrifying touch" + desc = "That's the bottom line, because flesh to stone said so!" + icon_state = "fleshtostone" + item_state = "fleshtostone" diff --git a/code/modules/spells/spell_types/touch/smite.dm b/code/modules/spells/spell_types/touch/smite.dm new file mode 100644 index 000000000000..459daaf299c2 --- /dev/null +++ b/code/modules/spells/spell_types/touch/smite.dm @@ -0,0 +1,55 @@ +/datum/action/cooldown/spell/touch/smite + name = "Smite" + desc = "This spell charges your hand with an unholy energy \ + that can be used to cause a touched victim to violently explode." + button_icon_state = "gib" + sound = 'sound/magic/disintegrate.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "EI NATH!!" + sparks_amt = 4 + + hand_path = /obj/item/melee/touch_attack/smite + +/datum/action/cooldown/spell/touch/smite/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + if(!isliving(victim)) + return FALSE + + do_sparks(sparks_amt, FALSE, get_turf(victim)) + for(var/mob/living/nearby_spectator in view(caster, 7)) + if(nearby_spectator == caster) + continue + nearby_spectator.flash_act(affect_silicon = FALSE) + + var/mob/living/living_victim = victim + if(living_victim.can_block_magic(antimagic_flags)) + caster.visible_message( + span_warning("The feedback blows [caster]'s arm off!"), + span_userdanger("The spell bounces from [living_victim]'s skin back into your arm!"), + ) + caster.flash_act() + var/obj/item/bodypart/to_dismember = caster.get_holding_bodypart_of_item(hand) + to_dismember?.dismember() + return TRUE + + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + var/obj/item/clothing/suit/worn_suit = human_victim.wear_suit + if(istype(worn_suit, /obj/item/clothing/suit/hooded/bloated_human)) + human_victim.visible_message(span_danger("[victim]'s [worn_suit] explodes off of them into a puddle of gore!")) + human_victim.dropItemToGround(worn_suit) + qdel(worn_suit) + new /obj/effect/gibspawner(get_turf(victim)) + return TRUE + + living_victim.gib() + return TRUE + +/obj/item/melee/touch_attack/smite + name = "\improper smiting touch" + desc = "This hand of mine glows with an awesome power!" + icon_state = "disintegrate" + item_state = "disintegrate" diff --git a/code/modules/spells/spell_types/touch_attacks.dm b/code/modules/spells/spell_types/touch_attacks.dm deleted file mode 100644 index f9b9785f2a5f..000000000000 --- a/code/modules/spells/spell_types/touch_attacks.dm +++ /dev/null @@ -1,116 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/touch - var/hand_path = /obj/item/melee/touch_attack - var/obj/item/melee/touch_attack/attached_hand = null - var/drawmessage = "You channel the power of the spell to your hand." - var/dropmessage = "You draw the power out of your hand." - invocation_type = SPELL_INVOCATION_NONE //you scream on connecting, not summoning - include_user = TRUE - range = -1 - -/obj/effect/proc_holder/spell/targeted/touch/proc/remove_hand(recharge = FALSE) - QDEL_NULL(attached_hand) - if(recharge) - charge_counter = charge_max - -/obj/effect/proc_holder/spell/targeted/touch/proc/on_hand_destroy(obj/item/melee/touch_attack/hand) - if(hand != attached_hand) - CRASH("Incorrect touch spell hand.") - //Start recharging. - attached_hand = null - recharging = TRUE - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/targeted/touch/cast(list/targets,mob/user = usr) - if(!QDELETED(attached_hand)) - remove_hand(TRUE) - to_chat(user, span_notice("[dropmessage]")) - return - - for(var/mob/living/carbon/C in targets) - if(!attached_hand) - if(ChargeHand(C)) - recharging = FALSE - return - -/obj/effect/proc_holder/spell/targeted/touch/charge_check(mob/user,silent = FALSE) - if(!QDELETED(attached_hand)) //Charge doesn't matter when putting the hand away. - return TRUE - else - return ..() - -/obj/effect/proc_holder/spell/targeted/touch/proc/ChargeHand(mob/living/carbon/user) - attached_hand = new hand_path(src) - attached_hand.attached_spell = src - if(!user.put_in_hands(attached_hand)) - remove_hand(TRUE) - if (user.get_num_arms() <= 0) - to_chat(user, span_warning("You dont have any usable hands!")) - else - to_chat(user, span_warning("Your hands are full!")) - return FALSE - to_chat(user, span_notice("[drawmessage]")) - return TRUE - - -/obj/effect/proc_holder/spell/targeted/touch/disintegrate - name = "Disintegrate" - desc = "This spell charges your hand with vile energy that can be used to violently explode victims." - hand_path = /obj/item/melee/touch_attack/disintegrate - - school = "evocation" - charge_max = 600 - clothes_req = TRUE - cooldown_min = 200 //100 deciseconds reduction per rank - - action_icon_state = "gib" - -/obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - name = "Flesh to Stone" - desc = "This spell charges your hand with the power to turn victims into inert statues for a long period of time." - hand_path = /obj/item/melee/touch_attack/fleshtostone - - school = "transmutation" - charge_max = 600 - clothes_req = TRUE - cooldown_min = 200 //100 deciseconds reduction per rank - - action_icon_state = "statue" - sound = 'sound/magic/fleshtostone.ogg' - -/obj/effect/proc_holder/spell/targeted/touch/flagellate //doesn't do much, mostly cosmetic punishment tool for chaplains - name = "Flagellate" - desc = "This spell charges your hand with the power of the old gods, allowing you to flagellate heathens." - hand_path = /obj/item/melee/touch_attack/flagellate - clothes_req = FALSE - - charge_max = 300 //its very weak so it doesn't need a super long cooldown. 30 second cooldown - - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "horror" - action_background_icon_state = "bg_ecult" - sound = 'sound/magic/fleshtostone.ogg' - -/obj/effect/proc_holder/spell/targeted/touch/pacify - name = "Pacify" - desc = "This spell charges your hand with pure pacifism, allowing you to temporarily mute and pacify your targets, as well as turn them into gondolas." - hand_path = /obj/item/melee/touch_attack/pacifism - - school = "evocation" - charge_max = 1 MINUTES - clothes_req = FALSE - cooldown_min = 20 SECONDS - action_icon ='icons/mob/gondolas.dmi' - - action_icon_state = "gondola" - -/obj/effect/proc_holder/spell/targeted/touch/touch_of_death //yogs start - name = "Touch of Death" - desc = "This spell charges your hand with necrotic energy that can kill both organic and inorganic beings instantly." - hand_path = /obj/item/melee/touch_attack/touchofdeath - - school = "evocation" - charge_max = 400 - clothes_req = TRUE - cooldown_min = 200 //50 deciseconds reduction per rank - - action_icon_state = "touchofdeath" //yogs end diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm deleted file mode 100644 index 39cff63d98f6..000000000000 --- a/code/modules/spells/spell_types/trigger.dm +++ /dev/null @@ -1,30 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/trigger - name = "Trigger" - desc = "This spell triggers another spell or a few." - - var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly - var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks - -/obj/effect/proc_holder/spell/targeted/trigger/Initialize() - . = ..() - - for(var/spell in starting_spells) - var/spell_to_add = text2path(spell) - new spell_to_add(src) //should result in adding to contents, needs testing - -/obj/effect/proc_holder/spell/targeted/trigger/Destroy() - for(var/spell in contents) - qdel(spell) - linked_spells = null - starting_spells = null - return ..() - -/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets,mob/user = usr) - playMagSound() - for(var/mob/living/target in targets) - for(var/obj/effect/proc_holder/spell/spell in contents) - spell.perform(list(target),0) - for(var/obj/effect/proc_holder/spell/spell in linked_spells) - spell.perform(list(target),0) - - return \ No newline at end of file diff --git a/code/modules/spells/spell_types/turf_teleport.dm b/code/modules/spells/spell_types/turf_teleport.dm deleted file mode 100644 index 14240de49aa6..000000000000 --- a/code/modules/spells/spell_types/turf_teleport.dm +++ /dev/null @@ -1,44 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/turf_teleport - name = "Turf Teleport" - desc = "This spell teleports the target to the turf in range." - nonabstract_req = TRUE - - var/inner_tele_radius = 1 - var/outer_tele_radius = 2 - - var/include_space = FALSE //whether it includes space tiles in possible teleport locations - var/include_dense = FALSE //whether it includes dense tiles in possible teleport locations - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/cast(list/targets,mob/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/turfs = new/list() - for(var/turf/T in range(target,outer_tele_radius)) - if(T in range(target,inner_tele_radius)) - continue - if(isspaceturf(T) && !include_space) - continue - if(T.density && !include_dense) - continue - if(T.x>world.maxx-outer_tele_radius || T.xworld.maxy-outer_tele_radius || T.y HEALTH_THRESHOLD_FULLCRIT && (!beat || beat == BEAT_SLOW)) H.playsound_local(get_turf(H),fastbeat,40,0, channel = CHANNEL_HEARTBEAT) beat = BEAT_FAST @@ -204,7 +204,7 @@ fakingit = FALSE return ..() -/obj/item/organ/heart/vampheart/proc/FakeStart() +/obj/item/organ/heart/vampheart/proc/fake_start_heart() fakingit = TRUE // We're pretending to beat, to fool people. /// Bloodsuckers don't have a heartbeat at all when stopped (default is "an unstable") diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 5633525582e7..41e1c15d46ec 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -257,13 +257,13 @@ var/bz_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/bz)) if(bz_pp > BZ_trip_balls_min) - H.hallucination += 10 + H.adjust_hallucinations(10 SECONDS) H.reagents.add_reagent(/datum/reagent/bz_metabolites,5) if(prob(33)) H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) else if(bz_pp > 0.01) - H.hallucination += 5 + H.adjust_hallucinations(5 SECONDS) H.reagents.add_reagent(/datum/reagent/bz_metabolites,1) @@ -353,7 +353,7 @@ // Hexane gas_breathed = breath.get_moles(/datum/gas/hexane) if(gas_breathed > gas_stimulation_min) - H.hallucination += 50 + H.adjust_hallucinations(50 SECONDS) H.reagents.add_reagent(/datum/reagent/hexane,5) if(prob(33)) H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm index 2cbd2624ed43..fdefdd9181f6 100644 --- a/code/modules/surgery/organs/stomach.dm +++ b/code/modules/surgery/organs/stomach.dm @@ -50,17 +50,17 @@ var/pukeprob = 5 + 0.05 * H.disgust if(H.disgust >= DISGUST_LEVEL_GROSS) if(prob(10)) - H.stuttering += 1 - H.confused += 2 + H.adjust_stutter(1 SECONDS) + H.adjust_confusion(2 SECONDS) if(prob(10) && !H.stat) to_chat(H, span_warning("You feel kind of iffy...")) - H.jitteriness = max(H.jitteriness - 3, 0) + H.adjust_jitter(-6 SECONDS) if(H.disgust >= DISGUST_LEVEL_VERYGROSS) if(prob(pukeprob)) //iT hAndLeS mOrE ThaN PukInG - H.confused += 2.5 - H.stuttering += 1 + H.adjust_confusion(2.5 SECONDS) + H.adjust_stutter(1 SECONDS) H.vomit(10, 0, 1, 0, 1, 0) - H.Dizzy(5) + H.adjust_dizzy(5 SECONDS) if(H.disgust >= DISGUST_LEVEL_DISGUSTED) if(prob(25)) H.blur_eyes(3) //We need to add more shit down here diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index 3e114d870368..d551eb117b5b 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -41,7 +41,7 @@ icon_state = "adamantine_cords" /datum/action/item_action/organ_action/use/adamantine_vocal_cords/Trigger() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) return var/message = input(owner, "Resonate a message to all nearby golems.", "Resonate") if(QDELETED(src) || QDELETED(owner) || !message) @@ -82,7 +82,7 @@ ..() cords = target -/datum/action/item_action/organ_action/colossus/IsAvailable() +/datum/action/item_action/organ_action/colossus/IsAvailable(feedback = FALSE) if(world.time < cords.next_command) return FALSE if(!owner) @@ -98,7 +98,7 @@ /datum/action/item_action/organ_action/colossus/Trigger() . = ..() - if(!IsAvailable()) + if(!IsAvailable(feedback = FALSE)) if(world.time < cords.next_command) to_chat(owner, span_notice("You must wait [DisplayTimeText(cords.next_command - world.time)] before Speaking again.")) return @@ -283,7 +283,7 @@ L.heal_overall_damage(10 * power_multiplier, 10 * power_multiplier) if(iscultist(L)) L.adjust_fire_stacks(1 * power_multiplier) - L.IgniteMob() + L.ignite_mob() //KNOCKDOWN else if(findtext(message, knockdown_words)) @@ -351,7 +351,7 @@ for(var/V in listeners) var/mob/living/L = V L.adjust_fire_stacks(1 * power_multiplier) - L.IgniteMob() + L.ignite_mob() //HOT else if((findtext(message, hot_words))) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index c4642fe1a954..e2b52cfb828e 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -91,7 +91,7 @@ //Get the surgery bed component and adds available surgeries to the list var/datum/component/surgery_bed/SB - for(var/obj/op_table in T.GetAllContents()) + for(var/obj/op_table in T.get_all_contents()) SB = op_table.GetComponent(/datum/component/surgery_bed) if(istype(SB)) break @@ -163,7 +163,7 @@ var/probability = 0.5 var/turf/T = get_turf(target) - for(var/obj/op_table in T.GetAllContents()) + for(var/obj/op_table in T.get_all_contents()) var/datum/component/surgery_bed/SB = op_table.GetComponent(/datum/component/surgery_bed) if(SB) probability = SB.success_chance diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index 30e8d06942a9..853a814c217a 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -269,7 +269,7 @@ ouchie_mod *= ouchie_modifying_chems[R] if(target.stat == UNCONSCIOUS) ouchie_mod *= 0.8 - ouchie_mod *= clamp(1 - target.drunkenness / 100, 0, 1) + ouchie_mod *= clamp(1 - target.get_drunk_amount() / 100, 0, 1) if(!success) ouchie_mod *= 2 var/final_ouchie_chance = SURGERY_FUCKUP_CHANCE * ouchie_mod diff --git a/code/modules/swarmers/swarmer.dm b/code/modules/swarmers/swarmer.dm index c1009713b7a7..0093ba0dfb1c 100644 --- a/code/modules/swarmers/swarmer.dm +++ b/code/modules/swarmers/swarmer.dm @@ -78,7 +78,7 @@ . = ..() remove_verb(src, /mob/living/verb/pulled) 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) /mob/living/simple_animal/hostile/swarmer/med_hud_set_health() var/image/holder = hud_list[DIAG_HUD] @@ -248,7 +248,7 @@ if(ishuman(target)) var/mob/living/carbon/human/victim = target if(!victim.handcuffed) - victim.handcuffed = new /obj/item/restraints/handcuffs/energy/used(victim) + victim.set_handcuffed(new /obj/item/restraints/handcuffs/energy/used(victim)) victim.update_handcuffed() log_combat(src, victim, "handcuffed") diff --git a/code/modules/tgui/states/never.dm b/code/modules/tgui/states/never.dm new file mode 100644 index 000000000000..f8b5faeeb3bf --- /dev/null +++ b/code/modules/tgui/states/never.dm @@ -0,0 +1,15 @@ +/*! + * Copyright (c) 2021 Arm A. Hammer + * SPDX-License-Identifier: MIT + */ + +/** + * tgui state: never_state + * + * Always closes the UI, no matter what. See the ui_state in religious_tool.dm to see an example + */ + +GLOBAL_DATUM_INIT(never_state, /datum/ui_state/never_state, new) + +/datum/ui_state/never_state/can_use_topic(src_object, mob/user) + return UI_CLOSE diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui_input/alert.dm similarity index 100% rename from code/modules/tgui/tgui_alert.dm rename to code/modules/tgui_input/alert.dm diff --git a/code/modules/tgui/tgui_input_list.dm b/code/modules/tgui_input/list.dm similarity index 100% rename from code/modules/tgui/tgui_input_list.dm rename to code/modules/tgui_input/list.dm diff --git a/code/modules/tgui_input/number.dm b/code/modules/tgui_input/number.dm new file mode 100644 index 000000000000..5b654ddaf3f4 --- /dev/null +++ b/code/modules/tgui_input/number.dm @@ -0,0 +1,149 @@ +/** + * Creates a TGUI window with a number input. Returns the user's response as num | null. + * + * This proc should be used to create windows for number entry that the caller will wait for a response from. + * If tgui fancy chat is turned off: Will return a normal input. If a max or min value is specified, will + * validate the input inside the UI and ui_act. + * + * Arguments: + * * user - The user to show the number input to. + * * message - The content of the number input, shown in the body of the TGUI window. + * * title - The title of the number input modal, shown on the top of the TGUI window. + * * default - The default (or current) value, shown as a placeholder. Users can press refresh with this. + * * max_value - Specifies a maximum value. If none is set, any number can be entered. Pressing "max" defaults to 1000. + * * min_value - Specifies a minimum value. Often 0. + * * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout. + * * round_value - whether the inputted number is rounded down into an integer. + */ +/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE) + if (!user) + user = usr + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + // Client does NOT have tgui_input on: Returns regular input + var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value) + number_input.ui_interact(user) + number_input.wait() + if (number_input) + . = number_input.entry + qdel(number_input) + +/** + * # tgui_input_number + * + * Datum used for instantiating and using a TGUI-controlled number input that prompts the user with + * a message and has an input for number entry. + */ +/datum/tgui_input_number + /// Boolean field describing if the tgui_input_number was closed by the user. + var/closed + /// The default (or current) value, shown as a default. Users can press reset with this. + var/default + /// The entry that the user has return_typed in. + var/entry + /// The maximum value that can be entered. + var/max_value + /// The prompt's body, if any, of the TGUI window. + var/message + /// The minimum value that can be entered. + var/min_value + /// Whether the submitted number is rounded down into an integer. + var/round_value + /// The time at which the number input was created, for displaying timeout progress. + var/start_time + /// The lifespan of the number input, after which the window will close and delete itself. + var/timeout + /// The title of the TGUI window + var/title + +/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value) + src.default = default + src.max_value = max_value + src.message = message + src.min_value = min_value + src.title = title + src.round_value = round_value + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + /// Checks for empty numbers - bank accounts, etc. + if(max_value == 0) + src.min_value = 0 + if(default) + src.default = 0 + /// Sanity check + if(default < min_value) + src.default = min_value + if(default > max_value) + CRASH("Default value is greater than max value.") + +/datum/tgui_input_number/Destroy(force, ...) + SStgui.close_uis(src) + return ..() + +/** + * Waits for a user's response to the tgui_input_number's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_input_number/proc/wait() + while (!entry && !closed && !QDELETED(src)) + stoplag(1) + +/datum/tgui_input_number/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NumberInputModal") + ui.open() + +/datum/tgui_input_number/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_input_number/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_input_number/ui_static_data(mob/user) + var/list/data = list() + data["init_value"] = default // Default is a reserved keyword + data["max_value"] = max_value + data["message"] = message + data["min_value"] = min_value + data["title"] = title + data["round_value"] = round_value + return data + +/datum/tgui_input_number/ui_data(mob/user) + var/list/data = list() + if(timeout) + data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS)) + return data + +/datum/tgui_input_number/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("submit") + if(!isnum(params["entry"])) + CRASH("A non number was input into tgui input number by [usr]") + var/choice = round_value ? round(params["entry"]) : params["entry"] + if(choice > max_value) + CRASH("A number greater than the max value was input into tgui input number by [usr]") + if(choice < min_value) + CRASH("A number less than the min value was input into tgui input number by [usr]") + set_entry(choice) + closed = TRUE + SStgui.close_uis(src) + return TRUE + if("cancel") + closed = TRUE + SStgui.close_uis(src) + return TRUE + +/datum/tgui_input_number/proc/set_entry(entry) + src.entry = entry diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm new file mode 100644 index 000000000000..55d50a015b8c --- /dev/null +++ b/code/modules/tgui_input/text.dm @@ -0,0 +1,144 @@ +/** + * Creates a TGUI window with a text input. Returns the user's response. + * + * This proc should be used to create windows for text entry that the caller will wait for a response from. + * If tgui fancy chat is turned off: Will return a normal input. If max_length is specified, will return + * stripped_multiline_input. + * + * Arguments: + * * user - The user to show the text input to. + * * message - The content of the text input, shown in the body of the TGUI window. + * * title - The title of the text input modal, shown on the top of the TGUI window. + * * default - The default (or current) value, shown as a placeholder. + * * max_length - Specifies a max length for input. MAX_MESSAGE_LEN is default (1024) + * * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc. + * * encode - Toggling this determines if input is filtered via html_encode. Setting this to FALSE gives raw input. + * * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout. + */ +/proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length = MAX_MESSAGE_LEN, multiline = FALSE, encode = TRUE, timeout = 0) + if (!user) + user = usr + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + + var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout) + text_input.ui_interact(user) + text_input.wait() + if (text_input) + . = text_input.entry + qdel(text_input) + +/** + * tgui_input_text + * + * Datum used for instantiating and using a TGUI-controlled text input that prompts the user with + * a message and has an input for text entry. + */ +/datum/tgui_input_text + /// Boolean field describing if the tgui_input_text was closed by the user. + var/closed + /// The default (or current) value, shown as a default. + var/default + /// Whether the input should be stripped using html_encode + var/encode + /// The entry that the user has return_typed in. + var/entry + /// The maximum length for text entry + var/max_length + /// The prompt's body, if any, of the TGUI window. + var/message + /// Multiline input for larger input boxes. + var/multiline + /// The time at which the text input was created, for displaying timeout progress. + var/start_time + /// The lifespan of the text input, after which the window will close and delete itself. + var/timeout + /// The title of the TGUI window + var/title + +/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout) + src.default = default + src.encode = encode + src.max_length = max_length + src.message = message + src.multiline = multiline + src.title = title + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + +/datum/tgui_input_text/Destroy(force, ...) + SStgui.close_uis(src) + return ..() + +/** + * Waits for a user's response to the tgui_input_text's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_input_text/proc/wait() + while (!entry && !closed && !QDELETED(src)) + stoplag(1) + +/datum/tgui_input_text/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TextInputModal") + ui.open() + +/datum/tgui_input_text/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_input_text/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_input_text/ui_static_data(mob/user) + var/list/data = list() + data["max_length"] = max_length + data["message"] = message + data["multiline"] = multiline + data["placeholder"] = default // Default is a reserved keyword + data["title"] = title + return data + +/datum/tgui_input_text/ui_data(mob/user) + var/list/data = list() + if(timeout) + data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS)) + return data + +/datum/tgui_input_text/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("submit") + if(max_length) + if(length(params["entry"]) > max_length) + CRASH("[usr] typed a text string longer than the max length") + if(encode && (length(html_encode(params["entry"])) > max_length)) + to_chat(usr, span_notice("Your message was clipped due to special character usage.")) + set_entry(params["entry"]) + closed = TRUE + SStgui.close_uis(src) + return TRUE + if("cancel") + closed = TRUE + SStgui.close_uis(src) + return TRUE + +/** + * Sets the return value for the tgui text proc. + * If html encoding is enabled, the text will be encoded. + * This can sometimes result in a string that is longer than the max length. + * If the string is longer than the max length, it will be clipped. + */ +/datum/tgui_input_text/proc/set_entry(entry) + if(!isnull(entry)) + var/converted_entry = encode ? html_encode(entry) : entry + src.entry = trim(converted_entry, max_length) diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm index b966f6ff9dc5..49ddd7d9d0e3 100644 --- a/code/modules/tooltip/tooltip.dm +++ b/code/modules/tooltip/tooltip.dm @@ -87,13 +87,13 @@ Notes: /datum/tooltip/proc/hide() + queueHide = showing ? TRUE : FALSE + if (queueHide) addtimer(CALLBACK(src, PROC_REF(do_hide)), 1) else do_hide() - queueHide = showing ? TRUE : FALSE - return TRUE /datum/tooltip/proc/do_hide() diff --git a/code/modules/tooltip/tooltip.html b/code/modules/tooltip/tooltip.html index 4a87826e8825..951efc36349f 100644 --- a/code/modules/tooltip/tooltip.html +++ b/code/modules/tooltip/tooltip.html @@ -87,7 +87,7 @@ .clockwork .content {color: #B18B25; border-color: #000000; background-color: #5F380E;} .detective .wrap {border-color: #2c0F0c;} - .detective .wrap {color: #c7b08b; border-color: #2c0F0c; background-color: #221c1a;} + .detective .content {color: #c7b08b; border-color: #2c0F0c; background-color: #221c1a;} @@ -136,21 +136,29 @@ //alert(realIconSize + ' | ' +tooltip.tileSize + ' | ' + resizeRatio); //DEBUG - //Parse out the tile and cursor locations from params (e.g. "icon-x=32;icon-y=29;screen-loc=3:10,15:29") + const parameters = new Object(); + + //Parse out the contents of params (e.g. "icon-x=32;icon-y=29;screen-loc=3:10,15:29") + //It is worth noting that params is not always ordered in the same way. We therefore need to write the code + //To load their values in independantly of their order var paramsA = tooltip.params.cursor.split(';'); - if (paramsA.length < 3) {return false;} //Sometimes screen-loc is never sent ahaha fuck you byond + for (var i = 0; i < paramsA.length; i++) { + var entry = paramsA[i]; + var nameAndValue = entry.split("="); + parameters[nameAndValue[0]] = nameAndValue[1]; + } + + //Sometimes screen-loc is never sent ahaha fuck you byond + if (!parameters["icon-x"] || !parameters["icon-y"] || !parameters["screen-loc"]) { + return false; + } //icon-x - var iconX = paramsA[0]; - iconX = iconX.split('='); - iconX = parseInt(iconX[1]); + var iconX = parseInt(parameters["icon-x"]); //icon-y - var iconY = paramsA[1]; - iconY = iconY.split('='); - iconY = parseInt(iconY[1]); + var iconY = parseInt(parameters["icon-y"]); //screen-loc - var screenLoc = paramsA[2]; - screenLoc = screenLoc.split('='); - screenLoc = screenLoc[1].split(','); + var screenLoc = parameters["screen-loc"]; + screenLoc = screenLoc.split(','); if (screenLoc.length < 2) {return false;} var left = screenLoc[0]; var top = screenLoc[1]; diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index 3fd8801ef810..7fb093b81f9a 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -92,8 +92,8 @@ //ACTION DATUMS /datum/action/vehicle - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/actions_vehicle.dmi' + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS + button_icon = 'icons/mob/actions/actions_vehicle.dmi' button_icon_state = "vehicle_eject" var/obj/vehicle/vehicle_target diff --git a/code/modules/vehicles/wheelchair.dm b/code/modules/vehicles/wheelchair.dm index 3ee2eb3a8dcb..33c194679763 100644 --- a/code/modules/vehicles/wheelchair.dm +++ b/code/modules/vehicles/wheelchair.dm @@ -180,7 +180,7 @@ /datum/action/vehicle/ridden/wheelchair/explosive/kaboom name = "Ding!" desc = "Ring the cute little bell on your wheelchair." - icon_icon = 'icons/obj/bell.dmi' + button_icon = 'icons/obj/bell.dmi' button_icon_state = "bell" var/exploding = FALSE var/explode_delay = 2 SECONDS diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index 70e25b447050..91de62fddc5a 100644 --- a/code/modules/zombie/organs.dm +++ b/code/modules/zombie/organs.dm @@ -141,9 +141,9 @@ return GM.add_zombie(owner.mind) - var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums - if(!Z.evolution.owner) - Z.evolution.Grant(owner) +// var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums +// if(!Z.evolution.owner) +// Z.evolution.Grant(owner) if(owner.handcuffed) var/obj/O = owner.get_item_by_slot(SLOT_HANDCUFFED) diff --git a/icons/UI_Icons/antags/contracts/bluespace.png b/icons/UI_Icons/antags/contracts/bluespace.png new file mode 100644 index 000000000000..7f6c16ed15ea Binary files /dev/null and b/icons/UI_Icons/antags/contracts/bluespace.png differ diff --git a/icons/UI_Icons/antags/contracts/destruction.png b/icons/UI_Icons/antags/contracts/destruction.png new file mode 100644 index 000000000000..4f336ae9a1e1 Binary files /dev/null and b/icons/UI_Icons/antags/contracts/destruction.png differ diff --git a/icons/UI_Icons/antags/contracts/healing.png b/icons/UI_Icons/antags/contracts/healing.png new file mode 100644 index 000000000000..71a301e10fc1 Binary files /dev/null and b/icons/UI_Icons/antags/contracts/healing.png differ diff --git a/icons/UI_Icons/antags/contracts/robeless.png b/icons/UI_Icons/antags/contracts/robeless.png new file mode 100644 index 000000000000..287a84e9b3fd Binary files /dev/null and b/icons/UI_Icons/antags/contracts/robeless.png differ diff --git a/icons/effects/mouse_pointers/barn_target.dmi b/icons/effects/mouse_pointers/barn_target.dmi new file mode 100644 index 000000000000..b5ac050955bc Binary files /dev/null and b/icons/effects/mouse_pointers/barn_target.dmi differ diff --git a/icons/effects/mouse_pointers/bite_target.dmi b/icons/effects/mouse_pointers/bite_target.dmi new file mode 100644 index 000000000000..2580d8855d6c Binary files /dev/null and b/icons/effects/mouse_pointers/bite_target.dmi differ diff --git a/icons/effects/mouse_pointers/cluwne_target.dmi b/icons/effects/mouse_pointers/cluwne_target.dmi new file mode 100644 index 000000000000..fc45b770a08a Binary files /dev/null and b/icons/effects/mouse_pointers/cluwne_target.dmi differ diff --git a/icons/effects/compromise_target.dmi b/icons/effects/mouse_pointers/compromise_target.dmi similarity index 100% rename from icons/effects/compromise_target.dmi rename to icons/effects/mouse_pointers/compromise_target.dmi diff --git a/icons/effects/cult_target.dmi b/icons/effects/mouse_pointers/cult_target.dmi similarity index 100% rename from icons/effects/cult_target.dmi rename to icons/effects/mouse_pointers/cult_target.dmi diff --git a/icons/effects/mouse_pointers/mindswap_target.dmi b/icons/effects/mouse_pointers/mindswap_target.dmi new file mode 100644 index 000000000000..0ed96dac047a Binary files /dev/null and b/icons/effects/mouse_pointers/mindswap_target.dmi differ diff --git a/icons/effects/mouse_pointers/overload_machine_target.dmi b/icons/effects/mouse_pointers/overload_machine_target.dmi new file mode 100644 index 000000000000..2864e1ef596c Binary files /dev/null and b/icons/effects/mouse_pointers/overload_machine_target.dmi differ diff --git a/icons/effects/mouse_pointers/override_machine_target.dmi b/icons/effects/mouse_pointers/override_machine_target.dmi new file mode 100644 index 000000000000..64fa3cf20948 Binary files /dev/null and b/icons/effects/mouse_pointers/override_machine_target.dmi differ diff --git a/icons/effects/mouse_pointers/screen_drag.dmi b/icons/effects/mouse_pointers/screen_drag.dmi new file mode 100644 index 000000000000..48e4fce85f7a Binary files /dev/null and b/icons/effects/mouse_pointers/screen_drag.dmi differ diff --git a/icons/effects/vanguard_target.dmi b/icons/effects/mouse_pointers/vanguard_target.dmi similarity index 100% rename from icons/effects/vanguard_target.dmi rename to icons/effects/mouse_pointers/vanguard_target.dmi diff --git a/icons/effects/volt_target.dmi b/icons/effects/mouse_pointers/volt_target.dmi similarity index 100% rename from icons/effects/volt_target.dmi rename to icons/effects/mouse_pointers/volt_target.dmi diff --git a/icons/effects/mouse_pointers/wrap_target.dmi b/icons/effects/mouse_pointers/wrap_target.dmi new file mode 100644 index 000000000000..2f2bd17bbfe6 Binary files /dev/null and b/icons/effects/mouse_pointers/wrap_target.dmi differ diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi new file mode 100644 index 000000000000..23865a80f035 Binary files /dev/null and b/icons/hud/64x16_actions.dmi differ diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi index df688bd2a400..368462fa78f5 100644 Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ diff --git a/icons/mob/actions/actions_animal.dmi b/icons/mob/actions/actions_animal.dmi index be4cb4143797..f674268fee0e 100644 Binary files a/icons/mob/actions/actions_animal.dmi and b/icons/mob/actions/actions_animal.dmi differ diff --git a/icons/mob/actions/actions_bloodsucker.dmi b/icons/mob/actions/actions_bloodsucker.dmi index 265548073f27..e8dcf8a42f7a 100644 Binary files a/icons/mob/actions/actions_bloodsucker.dmi and b/icons/mob/actions/actions_bloodsucker.dmi differ diff --git a/icons/mob/actions/actions_gangrel_bloodsucker.dmi b/icons/mob/actions/actions_gangrel_bloodsucker.dmi index 30b9532502d8..58b75beafd14 100644 Binary files a/icons/mob/actions/actions_gangrel_bloodsucker.dmi and b/icons/mob/actions/actions_gangrel_bloodsucker.dmi differ diff --git a/icons/mob/actions/actions_lasombra_bloodsucker.dmi b/icons/mob/actions/actions_lasombra_bloodsucker.dmi index bc6c89bb0dfe..105363a1266a 100644 Binary files a/icons/mob/actions/actions_lasombra_bloodsucker.dmi and b/icons/mob/actions/actions_lasombra_bloodsucker.dmi differ diff --git a/icons/mob/actions/actions_mime.dmi b/icons/mob/actions/actions_mime.dmi new file mode 100644 index 000000000000..33383dc56006 Binary files /dev/null and b/icons/mob/actions/actions_mime.dmi differ diff --git a/icons/mob/actions/actions_ratking.dmi b/icons/mob/actions/actions_ratking.dmi deleted file mode 100644 index 4bb092436d48..000000000000 Binary files a/icons/mob/actions/actions_ratking.dmi and /dev/null differ diff --git a/icons/mob/actions/actions_spells.dmi b/icons/mob/actions/actions_spells.dmi index 23b04b4cab68..d9a7fc5d17ad 100644 Binary files a/icons/mob/actions/actions_spells.dmi and b/icons/mob/actions/actions_spells.dmi differ diff --git a/icons/mob/actions/actions_tzimisce_bloodsucker.dmi b/icons/mob/actions/actions_tzimisce_bloodsucker.dmi index e7f504f9baad..c6a8697c45c6 100644 Binary files a/icons/mob/actions/actions_tzimisce_bloodsucker.dmi and b/icons/mob/actions/actions_tzimisce_bloodsucker.dmi differ diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi index 39831fb86615..50a08ab93348 100644 Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ diff --git a/icons/mob/bloodsucker_clan_icons.dmi b/icons/mob/bloodsucker_clan_icons.dmi new file mode 100644 index 000000000000..9d75aa42d9a2 Binary files /dev/null and b/icons/mob/bloodsucker_clan_icons.dmi differ diff --git a/icons/mob/bloodsucker_mobs.dmi b/icons/mob/bloodsucker_mobs.dmi index 20276e91d0cb..2d23e0154a19 100644 Binary files a/icons/mob/bloodsucker_mobs.dmi and b/icons/mob/bloodsucker_mobs.dmi differ diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi index 646065b7efad..f4428204e74b 100644 Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ diff --git a/icons/mob/inhands/equipment/backpack_lefthand.dmi b/icons/mob/inhands/equipment/backpack_lefthand.dmi index 8ffba32f6a3d..15425dabca14 100644 Binary files a/icons/mob/inhands/equipment/backpack_lefthand.dmi and b/icons/mob/inhands/equipment/backpack_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/backpack_righthand.dmi b/icons/mob/inhands/equipment/backpack_righthand.dmi index 11b92f50e7ca..c51285f0867f 100644 Binary files a/icons/mob/inhands/equipment/backpack_righthand.dmi and b/icons/mob/inhands/equipment/backpack_righthand.dmi differ diff --git a/icons/mob/inhands/misc/touchspell_lefthand.dmi b/icons/mob/inhands/misc/touchspell_lefthand.dmi index b32776689e60..5d7d659f93c4 100644 Binary files a/icons/mob/inhands/misc/touchspell_lefthand.dmi and b/icons/mob/inhands/misc/touchspell_lefthand.dmi differ diff --git a/icons/mob/inhands/misc/touchspell_righthand.dmi b/icons/mob/inhands/misc/touchspell_righthand.dmi index 2fb90d471bfd..fec94bcd196e 100644 Binary files a/icons/mob/inhands/misc/touchspell_righthand.dmi and b/icons/mob/inhands/misc/touchspell_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi index 170997bc872d..eca12b815e89 100644 Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi index 5b0b2d210301..20c11e207a5a 100644 Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index 7cf5a6cfafd0..32add7cd533c 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/mob/nonhuman-player/cult.dmi b/icons/mob/nonhuman-player/cult.dmi new file mode 100644 index 000000000000..8ad5dd92eddf Binary files /dev/null and b/icons/mob/nonhuman-player/cult.dmi differ diff --git a/icons/mob/nonhuman-player/holy.dmi b/icons/mob/nonhuman-player/holy.dmi new file mode 100644 index 000000000000..f7a356c8dfbc Binary files /dev/null and b/icons/mob/nonhuman-player/holy.dmi differ diff --git a/icons/mob/radial.dmi b/icons/mob/radial.dmi index 7339c2167b6b..95f37c1fabc2 100644 Binary files a/icons/mob/radial.dmi and b/icons/mob/radial.dmi differ diff --git a/icons/mob/screen_ai.dmi b/icons/mob/screen_ai.dmi index 47c8ba7ad73e..4c4a089595cb 100644 Binary files a/icons/mob/screen_ai.dmi and b/icons/mob/screen_ai.dmi differ diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index 434eccef65e3..d1345ec233b5 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ diff --git a/icons/mob/screen_alien.dmi b/icons/mob/screen_alien.dmi index cf4ab58f766d..b9b94cd39e9b 100644 Binary files a/icons/mob/screen_alien.dmi and b/icons/mob/screen_alien.dmi differ diff --git a/icons/mob/screen_clockwork.dmi b/icons/mob/screen_clockwork.dmi index c083164f7123..c2b94fa2408c 100644 Binary files a/icons/mob/screen_clockwork.dmi and b/icons/mob/screen_clockwork.dmi differ diff --git a/icons/mob/screen_cyborg.dmi b/icons/mob/screen_cyborg.dmi index 219d69785608..64c44062ea14 100644 Binary files a/icons/mob/screen_cyborg.dmi and b/icons/mob/screen_cyborg.dmi differ diff --git a/icons/mob/screen_detective.dmi b/icons/mob/screen_detective.dmi index 3107a4a945c9..5d2243b5233f 100644 Binary files a/icons/mob/screen_detective.dmi and b/icons/mob/screen_detective.dmi differ diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi index 38725c0bcc54..8cc452f225d2 100644 Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ diff --git a/icons/mob/screen_midnight.dmi b/icons/mob/screen_midnight.dmi index 0454d5f2aeb2..5c2c2db6674e 100644 Binary files a/icons/mob/screen_midnight.dmi and b/icons/mob/screen_midnight.dmi differ diff --git a/icons/mob/screen_operative.dmi b/icons/mob/screen_operative.dmi index 15a4f9a1e4e8..2f5275dba07e 100644 Binary files a/icons/mob/screen_operative.dmi and b/icons/mob/screen_operative.dmi differ diff --git a/icons/mob/screen_pai.dmi b/icons/mob/screen_pai.dmi index 4868d26cc081..39f559509d58 100644 Binary files a/icons/mob/screen_pai.dmi and b/icons/mob/screen_pai.dmi differ diff --git a/icons/mob/screen_plasmafire.dmi b/icons/mob/screen_plasmafire.dmi index 3dc1c90f8429..4be80c0be2ee 100644 Binary files a/icons/mob/screen_plasmafire.dmi and b/icons/mob/screen_plasmafire.dmi differ diff --git a/icons/mob/screen_retro.dmi b/icons/mob/screen_retro.dmi index af5c08eb5938..34e1e1b84ada 100644 Binary files a/icons/mob/screen_retro.dmi and b/icons/mob/screen_retro.dmi differ diff --git a/icons/mob/screen_slimecore.dmi b/icons/mob/screen_slimecore.dmi index 8809cf9ce73f..8c1174623cb9 100644 Binary files a/icons/mob/screen_slimecore.dmi and b/icons/mob/screen_slimecore.dmi differ diff --git a/icons/obj/hydroponics/equipment.dmi b/icons/obj/hydroponics/equipment.dmi index 1a10e96ab1b1..4401299d8eed 100644 Binary files a/icons/obj/hydroponics/equipment.dmi and b/icons/obj/hydroponics/equipment.dmi differ diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi index 56a6c72b2fd8..41bd99f0803f 100644 Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ diff --git a/icons/obj/vamp_obj.dmi b/icons/obj/vamp_obj.dmi index 7818e30ed392..ee2d576cda0d 100644 Binary files a/icons/obj/vamp_obj.dmi and b/icons/obj/vamp_obj.dmi differ diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi index 28a278f282e0..e2ebddae7d75 100644 Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ diff --git a/strings/cas_white.txt b/strings/cas_white.txt index b5b49ba6c87d..8a3307ee8516 100644 --- a/strings/cas_white.txt +++ b/strings/cas_white.txt @@ -243,7 +243,7 @@ Medbay stutterwhores. The scrubbers uncontrollably spewing cum. The lawyer's job. Emoting slowly drawing a gun, then slowly cocking the trigger, then slowly preparing to shoot... -NAR-SIE HAS RISEN. +NAR'SIE HAS RISEN. Pierrot's Throat. A clown bomb. Space bees? diff --git a/strings/steve.json b/strings/steve.json new file mode 100644 index 000000000000..06181e85eb89 --- /dev/null +++ b/strings/steve.json @@ -0,0 +1,21 @@ +{ + "ballmer_good_msg": [ + "Hear me out here. What if, and this is just a theory, we made R&D controllable from our PDAs?", + "Hey guys, what if we rolled out a bluespace wiring system so mice can't destroy the powergrid anymore?", + "I dunno about you guys, but IDs and PDAs being separate is clunky as fuck. Maybe we should merge them into a chip in our arms? That way they can't be stolen easily.", + "I'm thinking we should roll out a git repository for our research under the AGPLv3 license so that we can share it among the other stations freely.", + "Why the fuck aren't we just making every pair of shoes into galoshes? We have the technology." + ], + + "ballmer_windows_me_msg": [ + "Who keeps commiting to master?", + "Best idea ever: Disposal pipes instead of hallways.", + "Do you know who ate all the donuts?", + "Dude, radical idea: H.O.N.K mechs but with no bananium required.", + "So like, you know how we separate our codebase from the master copy that runs on our consumer boxes? What if we merged the two and undid the separation between codebase and server?", + "We should store bank records in a webscale datastore, like /dev/null.", + "What if we use a language that was written on a napkin and created over 1 weekend for all of our servers?", + "Yo man, what if, we like, uh, put a webserver that's automatically turned on with default admin passwords into every PDA?", + "You ever wonder if /dev/null supports sharding?" + ] +} diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index d96fc98552c6..8cd5b561f79e 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -7,8 +7,10 @@ // Themes import './styles/main.scss'; import './styles/themes/abductor.scss'; +import './styles/themes/admintickets.scss'; import './styles/themes/clockwork.scss'; import './styles/themes/cardtable.scss'; +import './styles/themes/darkspawn.scss'; import './styles/themes/hackerman.scss'; import './styles/themes/malfunction.scss'; import './styles/themes/ntos.scss'; @@ -22,7 +24,7 @@ import './styles/themes/ntos_spooky.scss'; import './styles/themes/paper.scss'; import './styles/themes/retro.scss'; import './styles/themes/syndicate.scss'; -import './styles/themes/admintickets.scss'; +import './styles/themes/wizard.scss'; import { perf } from 'common/perf'; import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; diff --git a/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx b/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx new file mode 100644 index 000000000000..e8efba04ac52 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx @@ -0,0 +1,203 @@ +import { BooleanLike } from 'common/react'; +import { useBackend } from '../backend'; +import { Box, Collapsible, Divider, LabeledList, Section, Stack } from '../components'; + +import { Window } from '../layouts'; + +type Data = { + color: string; + description: string; + effects: string; + name: string; + objectives: Objectives[]; +}; + +type Objectives = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +const BLOB_COLOR = '#556b2f'; + +export const AntagInfoBlob = (props, context) => { + return ( + + +
+ + + + + + +
+
+
+ ); +}; + +const Overview = (props, context) => { + const { data } = useBackend(context); + const { color, description, effects, name } = data; + + if (!name) { + return ( + + + You haven't revealed your true form yet! + + + You must be succumb to the infection. Find somewhere safe and pop! + + + ); + } + + return ( + + + You are the Blob! + + As the overmind, you can control the blob. + + Your blob reagent is:{' '} + + {name} + + + + The{' '} + + {name} + {' '} + reagent {description} + + {effects && ( + + The{' '} + + {name} + {' '} + reagent {effects} + + )} + + ); +}; + +const Basics = (props, context) => { + return ( + + + + You can expand, which will attack people, damage objects, or place a + Normal Blob if the tile is clear. + + + You will be able to manually place your blob core by pressing the + Place Blob Core button in the bottom right corner of the screen. + + + In addition to the buttons on your HUD, there are a few click + shortcuts to speed up expansion and defense. + + + Click = Expand Blob | Middle Mouse Click = Rally Spores | Ctrl Click = + Create Shield Blob | Alt Click = Remove Blob + + + Attempting to talk will send a message to all other overminds, + allowing you to coordinate with them. + + + + ); +}; + +const Minions = (props, context) => { + return ( + + + + Defenders that can be produced from factories for a cost, and are hard + to kill, powerful, and moderately smart. The factory used to create + one will become fragile and briefly unable to produce spores. + + + Produced automatically from factories, these are weak, but can be + rallied to attack enemies. They will also attack enemies near the + factory and attempt to zombify corpses. + + + + ); +}; + +const Structures = (props, context) => { + return ( + + + Normal Blobs will expand your reach and can be upgraded into special + blobs that perform certain functions. + +
+ You can upgrade normal blobs into the following types of blob: + + + + Strong and expensive blobs which take more damage. In additon, they + are fireproof and can block air, use these to protect yourself from + station fires. Upgrading them again will result in a reflective blob, + capable of reflecting most projectiles at the cost of the strong + blob's extra health. + + + Blobs which produce more resources for you, build as many of these as + possible to consume the station. This type of blob must be placed near + node blobs or your core to work. + + + Blobs that spawn blob spores which will attack nearby enemies. This + type of blob must be placed near node blobs or your core to work. + + + Blobs which grow, like the core. Like the core it can activate + resource and factory blobs. + + +
+ ); +}; + +const ObjectiveDisplay = (props, context) => { + const { data } = useBackend(context); + const { color, objectives } = data; + + return ( + + + {objectives.map(({ explanation }, index) => ( + + {explanation} + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx b/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx new file mode 100644 index 000000000000..a5ef5802ddd6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx @@ -0,0 +1,268 @@ +import { resolveAsset } from '../assets'; +import { BooleanLike } from "../../common/react"; +import { useBackend, useLocalState } from "../../tgui/backend"; +import { Box, Button, Divider, Dropdown, Section, Stack, Tabs } from "../../tgui/components"; +import { Window } from '../../tgui/layouts'; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +} + +type BloodsuckerInformation = { + clan: ClanInfo[]; + in_clan: BooleanLike; + power: PowerInfo[]; +}; + +type ClanInfo = { + clan_name: string; + clan_description: string; + clan_icon: string; +}; + +type PowerInfo = { + power_name: string; + power_explanation: string; + power_icon: string; +}; + +type Info = { + objectives: Objective[]; +}; + +const ObjectivePrintout = (props: any, context: any) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +export const AntagInfoBloodsucker = (props: any, context: any) => { + const [tab, setTab] = useLocalState(context, 'tab', 1); + return ( + + + + setTab(1)}> + Introduction + + setTab(2)}> + Clan & Powers + + + {tab === 1 && } + {tab === 2 && } + + + ); +}; + +const BloodsuckerIntro = () => { + return ( + + +
+ + + You are a Bloodsucker, an undead blood-seeking monster + living aboard Space Station 13 + + + + + +
+
+ +
+ + + + You regenerate your health slowly, you're weak to fire, and + you depend on blood to survive. Don't allow your blood to + run too low, or you'll enter a + + Frenzy!
+ + Beware of your Humanity level! The more Humanity you lose, the + easier it is to fall into a{' '} + Frenzy! + +
+ + Avoid using your Feed ability while near others, or else you + will risk breaking the Masquerade! + +
+
+
+
+ +
+ + + Rest in a Coffin to claim it, and that area, as your lair. +
+ Examine your new structures to see how they function! +
+ Medical and Genetic Analyzers can sell you out, your Masquerade + ability will hide your identity to prevent this. +
+
+ +
+ Other Bloodsuckers are not necessarily your friends, but your + survival may depend on cooperation. Betray them at your own + discretion and peril. +
+
+
+
+
+
+ ); +}; + +const BloodsuckerClan = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { clan, in_clan } = data; + + if (!in_clan) { + return ( +
+ + You are not in a Clan. + + +
+ ); + } + + return ( + + +
+ + + {clan.map((ClanInfo) => ( + <> + + + You are part of the {ClanInfo.clan_name} + + + {ClanInfo.clan_description} + + + ))} + + +
+ +
+
+ ); +}; + +const PowerSection = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { power } = data; + if (!power) { + return
; + } + + const [selectedPower, setSelectedPower] = useLocalState( + context, + 'power', + power[0] + ); + + return ( +
+ }> + + + powers.power_name)} + onSelected={(powerName: string) => + setSelectedPower( + power.find((p) => p.power_name === powerName) || power[0] + ) + } + /> + {selectedPower && ( + + )} + + + + + {selectedPower && selectedPower.power_explanation} + + +
+ ); +}; + diff --git a/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx new file mode 100644 index 000000000000..0977d863eae0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx @@ -0,0 +1,210 @@ +import { useBackend } from '../backend'; +import { Dimmer, Section, Stack, NoticeBox } from '../components'; +import { Window } from '../layouts'; + +const hivestyle = { + fontWeight: 'bold', + color: 'yellow', +}; + +const absorbstyle = { + color: 'red', + fontWeight: 'bold', +}; + +const revivestyle = { + color: 'lightblue', + fontWeight: 'bold', +}; + +const transformstyle = { + color: 'orange', + fontWeight: 'bold', +}; + +const storestyle = { + color: 'lightgreen', + fontWeight: 'bold', +}; + +const hivemindstyle = { + color: 'violet', + fontWeight: 'bold', +}; + +const fallenstyle = { + color: 'black', + fontWeight: 'bold', +}; + +type Objective = { + count: number; + name: string; + explanation: string; +}; + +type Info = { + true_name: string; + stolen_antag_info: string; + objectives: Objective[]; +}; + +export const AntagInfoChangeling = (props, context) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const ObjectivePrintout = (props, context) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +const HivemindSection = (props, context) => { + const { act, data } = useBackend(context); + const { true_name } = data; + return ( +
+ + + All Changelings, regardless of origin, are linked together by the{' '} + hivemind. You may communicate to + other Changelings under your mental alias,{' '} + {true_name}, by starting a message + with :g. Work together, and you + will bring the station to new heights of terror. + + + + Other Changelings are strong allies, but some Changelings may betray + you. Changelings grow in power greatly by absorbing their kind, and + getting absorbed by another Changeling will leave you as a{' '} + Fallen Changeling. There is no + greater humiliation. + + + +
+ ); +}; + +const IntroductionSection = (props, context) => { + const { act, data } = useBackend(context); + const { true_name, objectives } = data; + return ( +
4}> + + + You are {true_name}, a + {"Changeling"}. + + + + + +
+ ); +}; + +const AbilitiesSection = (props, context) => { + const { data } = useBackend(context); + return ( +
+ + + + + Your +  Absorb DNA ability allows + you to steal the DNA and memories of a victim, granting you their + memories or speech patterns and possibly even greater things. + + + + Your +  Reviving Stasis ability + allows you to revive. It means nothing short of a complete body + destruction can stop you! Obviously, this is loud and so should + not be done in front of people you are not planning on silencing. + + + + + + + + Your +  Transform ability allows + you to change into the form of those you have collected DNA from, + lethally and nonlethally. It will also mimic (NOT REAL CLOTHING) + the clothing they were wearing for every slot you have open. + + + + The +  Cellular Emporium is where + you purchase more abilities beyond your starting kit. You have 10 + genetic points to spend on abilities and you are able to readapt + after absorbing a body, refunding your points for different kits. + + + + +
+ ); +}; + +const VictimPatternsSection = (props, context) => { + const { data } = useBackend(context); + const { stolen_antag_info } = data; + return ( +
+ {(!!stolen_antag_info && stolen_antag_info) || ( + Absorb a victim first! + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx b/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx new file mode 100644 index 000000000000..f3e83fbd19b0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx @@ -0,0 +1,126 @@ +import { useBackend } from '../backend'; +import { Box, Section, Stack } from '../components'; +import { BooleanLike } from 'common/react'; +import { Window } from '../layouts'; + +const jauntstyle = { + color: 'lightblue', +}; + +const injurestyle = { + color: 'yellow', +}; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +type Info = { + fluff: string; + explain_attack: BooleanLike; + objectives: Objective[]; +}; + +export const AntagInfoDemon = (props, context) => { + const { data } = useBackend(context); + const { fluff, objectives, explain_attack } = data; + return ( + + + + + + + + + +
2}> + + + {fluff} + + + + + +
+
+ {!!explain_attack && ( + +
+ + + Blood Jaunt: You can + dive in and out of blood to travel anywhere you need to + be. You will gain a speed boost upon leaving the jaunt + for surprise attacks. You can drag victims you have + disabled through the blood, consuming them and restoring + health. + + + + Monstrous strike: You + can launch a devastating slam attack by alt-clicking, + capable of smashing bones in one strike. Great for + preventing the escape of your victims, as their wounds + will slow them. + + +
+
+ )} +
+
+ + + +
+
+
+ ); +}; + +const ObjectivePrintout = (props, context) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + + It is in your nature to accomplish these goals: + + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +const DemonRunes = (props, context) => { + return ( +
+ {/* + shoutout to my boy Yuktopus from Crash Bandicoot: Crash of the Titans. + Damn, that was such a good game. + */} + + Y
U
K
T
O
P
U
S
+ Y
U
K
T
O
P
U
S
+ Y
U
K
T
O
P
U
S
+ Y
U
K
T
O
P
U
S +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx b/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx new file mode 100644 index 000000000000..a4941f8aa04a --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx @@ -0,0 +1,57 @@ +import { useBackend } from '../backend'; +import { Section, Stack } from '../components'; +import { BooleanLike } from 'common/react'; +import { Window } from '../layouts'; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +type Info = { + antag_name: string; + objectives: Objective[]; +}; + +export const AntagInfoGeneric = (props, context) => { + const { data } = useBackend(context); + const { antag_name } = data; + return ( + + +
+ + + You are the {antag_name}! + + + + + +
+
+
+ ); +}; + +const ObjectivePrintout = (props, context) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx b/tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx new file mode 100644 index 000000000000..a660cf9499f0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx @@ -0,0 +1,152 @@ +import { resolveAsset } from '../assets'; +import { BooleanLike } from '../../common/react'; +import { useBackend, useLocalState } from '../../tgui/backend'; +import { Box, Button, Divider, Dropdown, Section, Stack } from '../../tgui/components'; +import { Window } from '../../tgui/layouts'; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +type BloodsuckerInformation = { + power: PowerInfo[]; +}; + +type PowerInfo = { + power_name: string; + power_explanation: string; + power_icon: string; +}; + +type Info = { + objectives: Objective[]; +}; + +const ObjectivePrintout = (props: any, context: any) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +export const AntagInfoRevengeVassal = (props: any, context: any) => { + return ( + + + + + + ); +}; + +const VassalInfo = () => { + return ( + + +
+ + + You are a Vassal tasked with taking revenge for the death of your + Master! + + + + + +
+
+ +
+ + + + You have gained your Master's old Powers, and a brand new + power. You will have to survive and maintain your old + Master's integrity. Bring their old Vassals back into the + fold using your new Ability. + + + +
+
+ + + +
+ ); +}; + +const PowerSection = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { power } = data; + if (!power) { + return
; + } + + const [selectedPower, setSelectedPower] = useLocalState( + context, + 'power', + power[0] + ); + + return ( +
+ }> + + + powers.power_name)} + onSelected={(powerName: string) => + setSelectedPower( + power.find((p) => p.power_name === powerName) || power[0] + ) + } + /> + {selectedPower && ( + + )} + + + + + {selectedPower && selectedPower.power_explanation} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ApprenticeContract.tsx b/tgui/packages/tgui/interfaces/ApprenticeContract.tsx new file mode 100644 index 000000000000..f0077e7f0fca --- /dev/null +++ b/tgui/packages/tgui/interfaces/ApprenticeContract.tsx @@ -0,0 +1,110 @@ +import { multiline } from 'common/string'; +import { resolveAsset } from '../assets'; +import { useBackend } from '../backend'; +import { BlockQuote, Box, Button, Icon, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +export const ApprenticeContract = (props, context) => { + return ( + + + + +
+ If you cannot reach any of your apprentices today, you can feed + the contract back into your spellbook to refund it. +
+
+ + + + + + +
+
+
+ ); +}; + +const ApprenticeSelection = (props, context) => { + const { act } = useBackend(context); + const { iconName, fluffName, schoolTitle, assetName, blurb } = props; + return ( +
+ + + + + + + + + + + + + + {fluffName} + +
+ {blurb} +
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/KindredArchives.js b/tgui/packages/tgui/interfaces/KindredArchives.js deleted file mode 100644 index 5035e2014baa..000000000000 --- a/tgui/packages/tgui/interfaces/KindredArchives.js +++ /dev/null @@ -1,65 +0,0 @@ -import { useBackend } from '../backend'; -import { Button, LabeledList, Section } from '../components'; -import { Window } from '../layouts'; - -export const KindredArchives = (props, context) => { - const { act, data } = useBackend(context); - return ( - - -
- {data.name} - - -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/KindredBook.tsx b/tgui/packages/tgui/interfaces/KindredBook.tsx new file mode 100644 index 000000000000..13eb797c581a --- /dev/null +++ b/tgui/packages/tgui/interfaces/KindredBook.tsx @@ -0,0 +1,42 @@ +import { useBackend } from '../../tgui/backend'; +import { Collapsible, Table, Section } from '../../tgui/components'; +import { Window } from '../../tgui/layouts'; + +type Data = { + clans: ClanInfo[]; +}; + +type ClanInfo = { + clan_name: string; + clan_desc: string; +}; + +export const KindredBook = (props, context) => { + const { data } = useBackend(context); + const { clans } = data; + return ( + + +
+ + + Written by generations of Curators, this holds all information we + the Curators know about the undead threat that looms the + station... + + So, what Clan are you interested in? +
+ + + {clans.map((clan) => ( + + {clan.clan_desc} + + ))} + +
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NumberInputModal.tsx b/tgui/packages/tgui/interfaces/NumberInputModal.tsx new file mode 100644 index 000000000000..76f1306d159d --- /dev/null +++ b/tgui/packages/tgui/interfaces/NumberInputModal.tsx @@ -0,0 +1,118 @@ +import { Loader } from './common/Loader'; +import { InputButtons } from './common/InputButtons'; +import { KEY_ENTER, KEY_ESCAPE } from '../../common/keycodes'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, RestrictedInput, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +type NumberInputData = { + init_value: number; + large_buttons: boolean; + max_value: number | null; + message: string; + min_value: number | null; + timeout: number; + title: string; + round_value: boolean; +}; + +export const NumberInputModal = (props, context) => { + const { act, data } = useBackend(context); + const { init_value, large_buttons, message = '', timeout, title } = data; + const [input, setInput] = useLocalState(context, 'input', init_value); + const onChange = (value: number) => { + if (value === input) { + return; + } + setInput(value); + }; + const onClick = (value: number) => { + if (value === input) { + return; + } + setInput(value); + }; + // Dynamically changes the window height based on the message. + const windowHeight = + 140 + + (message.length > 30 ? Math.ceil(message.length / 3) : 0) + + (message.length && large_buttons ? 5 : 0); + + return ( + + {timeout && } + { + const keyCode = window.event ? event.which : event.keyCode; + if (keyCode === KEY_ENTER) { + act('submit', { entry: input }); + } + if (keyCode === KEY_ESCAPE) { + act('cancel'); + } + }}> +
+ + + {message} + + + + + + + + +
+
+
+ ); +}; + +/** Gets the user input and invalidates if there's a constraint. */ +const InputArea = (props, context) => { + const { act, data } = useBackend(context); + const { min_value, max_value, init_value, round_value } = data; + const { input, onClick, onChange } = props; + return ( + + +
); @@ -242,9 +252,7 @@ const Loadouts = (props, context) => { const { points } = data; return ( - {points < 10 && ( - - )} + {points < 10 && } { the 2550's. Comes with Fireball, Magic Missile, Ei Nath, and Ethereal Jaunt. The key here is that every part of this kit is very easy to pick up and use. - `} /> + `} + /> { author="Jegudiel Worldshaker" blurb={multiline` The power of the mighty Mjolnir! Best not to lose it. - This loadout has Summon Item, Mutate, Blink, and - Force Wall. Mutate is your utility in this case: + This loadout has Summon Item, Mutate, Blink, Force Wall, + Tesla Blast, and Mjolnir. Mutate is your utility in this case: Use it for limited ranged fire and getting out of bad blinks. - `} /> + `} + /> @@ -285,7 +295,8 @@ const Loadouts = (props, context) => { Why kill when others will gladly do it for you? Embrace chaos with your kit: Soulshards, Staff of Change, Necro Stone, Teleport, and Jaunt! Remember, no offense spells! - `} /> + `} + /> { You can recharge very long recharge spells like Ei Nath by jumping into new bodies with Mind Swap and starting Soul Tap anew. - `} /> + `} + /> - ); }; @@ -309,14 +320,13 @@ const lineHeightRandomize = 6; const Randomize = (props, context) => { const { act, data } = useBackend(context); - const { points } = data; + const { points, semi_random_bonus, full_random_bonus } = data; return ( - {points < 10 && ( - - )} - + {points < 10 && } + Semi-Randomize will ensure you at least get some mobility and lethality. + Guaranteed to have {semi_random_bonus} points worth of spells. { fluid icon="dice-three" content="Semi-Randomize!" - onClick={() => act("semirandomize")} /> + onClick={() => act('semirandomize')} + /> Full Random will give you anything. There's no going back, either! + Guaranteed to have {full_random_bonus} points worth of spells. @@ -342,42 +354,37 @@ const Randomize = (props, context) => { color="black" icon="dice" content="Full Random!" - onClick={() => act("randomize")} /> + onClick={() => act('randomize')} + /> ); }; -const widthSection = "466px"; -const heightSection = "456px"; +const widthSection = '466px'; +const heightSection = '456px'; export const Spellbook = (props, context) => { const { act, data } = useBackend(context); - const { - entries, - points, - } = data; - const [ - tabIndex, - setTabIndex, - ] = useLocalState(context, 'tab-index', 1); - const ScrollableCheck = TAB2NAME[tabIndex-1].noScrollable ? false : true; - const ScrollableNextCheck = TAB2NAME[tabIndex-1].noScrollable !== 2; - const TabComponent = TAB2NAME[tabIndex-1].component - ? TAB2NAME[tabIndex-1].component() : null; + const { entries, points } = data; + const [tabIndex, setTabIndex] = useLocalState(context, 'tab-index', 1); + const ScrollableCheck = TAB2NAME[tabIndex - 1].noScrollable ? false : true; + const ScrollableNextCheck = TAB2NAME[tabIndex - 1].noScrollable !== 2; + const TabComponent = TAB2NAME[tabIndex - 1].component + ? TAB2NAME[tabIndex - 1].component() + : null; const TabNextComponent = TAB2NAME[tabIndex].component - ? TAB2NAME[tabIndex].component() : null; - const TabSpells = entries ? entries.filter( - entry => entry.cat === TAB2NAME[tabIndex-1].title) : null; - const TabNextSpells = entries ? entries.filter( - entry => entry.cat === TAB2NAME[tabIndex].title) : null; + ? TAB2NAME[tabIndex].component() + : null; + const TabSpells = entries + ? entries.filter((entry) => entry.cat === TAB2NAME[tabIndex - 1].title) + : null; + const TabNextSpells = entries + ? entries.filter((entry) => entry.cat === TAB2NAME[tabIndex].title) + : null; return ( - + @@ -389,7 +396,7 @@ export const Spellbook = (props, context) => { width={widthSection} height={heightSection} fill - title={TAB2NAME[tabIndex-1].title} + title={TAB2NAME[tabIndex - 1].title} buttons={ <>