From e790d8c139c0909307e1c9d2b96fdbb84630ef05 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 26 Apr 2026 20:00:52 +0300 Subject: [PATCH 01/42] =?UTF-8?q?=D0=9E=D1=81=D0=BD=D0=BE=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/dcs/signals/signal_actions.dm | 66 ++ code/__DEFINES/dcs/signals/signals_datum.dm | 19 - .../signals/signals_mob/signals_mob_main.dm | 5 +- code/__DEFINES/dcs/signals/signals_object.dm | 5 - code/__DEFINES/dcs/signals/signals_spell.dm | 126 +++ code/__DEFINES/magic.dm | 109 ++ code/__DEFINES/obj_flags.dm | 2 + code/__DEFINES/traits/declarations.dm | 13 + code/datums/actions/spell_action.dm | 930 +++++++++--------- code/datums/emote/emote.dm | 6 +- code/datums/mind.dm | 1 - code/datums/spells/mime.dm | 18 +- code/game/dna/genes/vg_powers.dm | 2 +- .../game/gamemodes/clockwork/clock_actions.dm | 2 +- code/game/gamemodes/cult/cult_actions.dm | 2 +- code/game/gamemodes/wizard/wizloadouts.dm | 2 +- code/game/jobs/job/service.dm | 2 +- code/game/objects/items.dm | 1 + code/game/objects/items/devices/megaphone.dm | 2 +- code/game/objects/structures.dm | 2 +- .../ghost_role_spawners/golems.dm | 2 +- code/modules/clothing/masks/gasmask.dm | 2 +- .../mob/living/carbon/human/human_emote.dm | 10 +- .../mob/living/carbon/human/human_say.dm | 4 +- .../mob/living/carbon/human/species/golem.dm | 2 +- code/modules/mob/living/living_emote.dm | 2 +- code/modules/mob/mob.dm | 15 +- .../projectiles/guns/ballistic/revolvers.dm | 2 +- .../reagents/chemistry/reagents/drinks.dm | 2 +- paradise.dme | 3 + 30 files changed, 836 insertions(+), 523 deletions(-) create mode 100644 code/__DEFINES/dcs/signals/signal_actions.dm create mode 100644 code/__DEFINES/dcs/signals/signals_spell.dm create mode 100644 code/__DEFINES/magic.dm diff --git a/code/__DEFINES/dcs/signals/signal_actions.dm b/code/__DEFINES/dcs/signals/signal_actions.dm new file mode 100644 index 000000000000..7505ba06623e --- /dev/null +++ b/code/__DEFINES/dcs/signals/signal_actions.dm @@ -0,0 +1,66 @@ +// Action signals + +///from base of datum/action/proc/Trigger(): (datum/action) +#define COMSIG_ACTION_TRIGGER "action_trigger" + // Return to block the trigger from occurring + #define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0) +/// From /datum/action/Grant(): (mob/grant_to) +#define COMSIG_ACTION_GRANTED "action_grant" +/// From /datum/action/Grant(): (datum/action) +#define COMSIG_MOB_GRANTED_ACTION "mob_action_grant" +/// From /datum/action/Remove(): (mob/removed_from) +#define COMSIG_ACTION_REMOVED "action_removed" +/// From /datum/action/Remove(): (datum/action) +#define COMSIG_MOB_REMOVED_ACTION "mob_action_removed" +/// From /datum/action/apply_button_overlay() +#define COMSIG_ACTION_OVERLAY_APPLY "action_overlay_applied" +// TODO: spaghetti write comment here +#define COMSIG_ACTION_BUTTON_UPDATE "action_button_update" + #define COMSIG_ACTION_UPDATE_INTERRUPT (1<<0) + + +// Cooldown action signals + +/// From base of /datum/action/cooldown/proc/PreActivate(), sent to the action owner: (datum/action/cooldown/activated) +#define COMSIG_MOB_ABILITY_STARTED "mob_ability_base_started" + /// Return to block the ability from starting / activating + #define COMPONENT_BLOCK_ABILITY_START (1<<0) +/// From base of /datum/action/cooldown/proc/PreActivate(), sent to the action owner: (datum/action/cooldown/finished) +#define COMSIG_MOB_ABILITY_FINISHED "mob_ability_base_finished" + +/// From base of /datum/action/cooldown/proc/set_statpanel_format(): (list/stat_panel_data) +#define COMSIG_ACTION_SET_STATPANEL "ability_set_statpanel" + +// Specific cooldown action signals + +/// From base of /datum/action/cooldown/mob_cooldown/blood_warp/proc/blood_warp(): () +#define COMSIG_BLOOD_WARP "mob_ability_blood_warp" +/// From base of /datum/action/cooldown/mob_cooldown/charge/proc/do_charge(): () +#define COMSIG_STARTED_CHARGE "mob_ability_charge_started" +/// From base of /datum/action/cooldown/mob_cooldown/charge/proc/do_charge(): () +#define COMSIG_FINISHED_CHARGE "mob_ability_charge_finished" +/// From base of /datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(): () +#define COMSIG_SWOOP_INVULNERABILITY_STARTED "mob_swoop_invulnerability_started" +/// From base of /datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(): () +#define COMSIG_LAVA_ARENA_FAILED "mob_lava_arena_failed" + +/// From /datum/action/vehicle/sealed/mecha/mech_toggle_safeties/proc/update_action_icon(): () +#define COMSIG_MECH_SAFETIES_TOGGLE "mech_safeties_toggle" + +/// From /datum/action/vehicle/sealed/mecha//mob_exit(): () +#define COMSIG_MECHA_MOB_EXIT "mecha_mob_exit" + +/// From /datum/action/cooldown/mob_cooldown/assume_form/proc/assume_appearances(), sent to the action owner: (atom/movable/target) +#define COMSIG_ACTION_DISGUISED_APPEARANCE "mob_ability_disguise_appearance" + +/// From /datum/action/cooldown/manual_heart/Activate(): () +#define COMSIG_HEART_MANUAL_PULSE "heart_manual_pulse" + +/// From /datum/action/cooldown/mob_cooldown/capture_photo/Activate(): +#define COMSIG_ACTION_PHOTO_CAPTURED "action_photo_captured" + +/// From /datum/action/vehicle/ridden/wheelchair/bell/Trigger(): +#define COMSIG_WHEELCHAIR_BELL_RANG "wheelchair_bell_rang" + +/// From /datum/action/cooldown/spell/touch/lay_on_hands/proc/determine_if_this_hurts_instead(), sent to the /mob/living/carbon/hurtguy: (/mob/living/carbon/mendicant) +#define COMSIG_ON_LAY_ON_HANDS "mob_ability_lay_on_hands" diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm index 99d19aeeac2e..1d95225e5c5f 100644 --- a/code/__DEFINES/dcs/signals/signals_datum.dm +++ b/code/__DEFINES/dcs/signals/signals_datum.dm @@ -27,25 +27,6 @@ ///from datum/species/on_species_loss(): (datum/species/lost_species) #define COMSIG_SPECIES_LOSS "species_loss" -// /datum/action signals -///from base of datum/action/proc/Trigger(): (datum/action) -#define COMSIG_ACTION_TRIGGER "action_trigger" - #define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0) -/// From /datum/action/Grant(): (mob/grant_to) -#define COMSIG_ACTION_GRANTED "action_grant" -/// From /datum/action/Grant(): (datum/action) -#define COMSIG_MOB_GRANTED_ACTION "mob_action_grant" -/// From /datum/action/Remove(): (mob/removed_from) -#define COMSIG_ACTION_REMOVED "action_removed" -/// From /datum/action/Remove(): (datum/action) -#define COMSIG_MOB_REMOVED_ACTION "mob_action_removed" -/// From /datum/action/apply_button_overlay() -#define COMSIG_ACTION_OVERLAY_APPLY "action_overlay_applied" -// TODO: spaghetti write comment here -#define COMSIG_ACTION_BUTTON_UPDATE "action_button_update" - #define COMSIG_ACTION_UPDATE_INTERRUPT (1<<0) -/// From base of /datum/action/cooldown/proc/set_statpanel_format(): (list/stat_panel_data) -#define COMSIG_ACTION_SET_STATPANEL "ability_set_statpanel" /// From /datum/action/innate/borer/make_larvae/Activate (turf/turf) #define COMSIG_BORER_REPRODUCE "borer_reproduced" ///from /datum/action/armguard_hidden_blade/Trigger(): () diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index bf650f11fb8f..492e98c37168 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -60,6 +60,8 @@ ///from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj #define COMSIG_MOB_ALLOWED "mob_allowed" +///from base of mob/can_cast_magic(): (mob/user, magic_flags, charge_cost) +#define COMSIG_MOB_RESTRICT_MAGIC "mob_cast_magic" ///from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources) #define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" #define COMPONENT_BLOCK_MAGIC (1<<0) @@ -207,7 +209,8 @@ /// From /obj/item/melee/baton/baton_effect(): (datum/source, mob/living/user, /obj/item/melee/baton) #define COMSIG_MOB_BATONED "mob_batoned" - +/// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot) +#define COMSIG_MOB_EQUIPPED_ITEM "mob_equipped_item" /// A mob has just unequipped an item. #define COMSIG_MOB_UNEQUIPPED_ITEM "mob_unequipped_item" /// From base of /mob/proc/update_held_items diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index ec4d80c01fd6..a53fa6e09002 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -43,11 +43,6 @@ #define COMSIG_ITEM_DROPPED "item_drop" ///from base of obj/item/pickup(): (/mob/taker) #define COMSIG_ITEM_PICKUP "item_pickup" -///return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/lichdom/cast(): (mob/user) -#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" -///called before marking an object for retrieval, checked in /obj/effect/proc_holder/spell/summonitem/cast() : (mob/user) -#define COMSIG_ITEM_MARK_RETRIEVAL "item_mark_retrieval" - #define COMPONENT_BLOCK_MARK_RETRIEVAL (1<<0) ///from base of obj/item/hit_reaction(): (list/args) #define COMSIG_ITEM_HIT_REACT "item_hit_react" #define COMPONENT_BLOCK_SUCCESSFUL (1 << 0) diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm new file mode 100644 index 000000000000..ebbdcc4e2bee --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_spell.dm @@ -0,0 +1,126 @@ +// Signals sent to or by spells + +// Generic spell signals + + +/// Sent from /datum/action/cooldown/spell/before_cast() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on) +#define COMSIG_MOB_BEFORE_SPELL_CAST "mob_spell_pre_cast" +/// Sent from /datum/action/cooldown/spell/before_cast() to the spell: (atom/cast_on) +#define COMSIG_SPELL_BEFORE_CAST "spell_pre_cast" + /// Return to prevent the spell cast from continuing. + #define SPELL_CANCEL_CAST (1 << 0) + /// Return from before cast signals to prevent the spell from giving off sound or invocation. + #define SPELL_NO_FEEDBACK (1 << 1) + /// Return from before cast signals to prevent the spell from going on cooldown before aftercast. + #define SPELL_NO_IMMEDIATE_COOLDOWN (1 << 2) + +/// Sent to an mob when a [/datum/action/cooldown/spell] calls try_invoke() to the caster: (datum/action/cooldown/spell/spell, feedback) +#define COMSIG_MOB_TRY_INVOKE_SPELL "try_invoke_spell" + /// The spell gets canceled + #define SPELL_INVOCATION_FAIL SPELL_CANCEL_CAST + /// The spell always succeeds to invoke regardless of following checks + #define SPELL_INVOCATION_ALWAYS_SUCCEED (1 << 1) + +/// Sent from /datum/action/cooldown/spell/set_click_ability() to the caster: (datum/action/cooldown/spell/spell) +#define COMSIG_MOB_SPELL_ACTIVATED "mob_spell_active" + /// Same as spell_cancel_cast, as they're able to be used interchangeably + #define SPELL_CANCEL_ACTIVATION SPELL_CANCEL_CAST + +/// Sent from /datum/action/cooldown/spell/cast() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on) +#define COMSIG_MOB_CAST_SPELL "mob_cast_spell" +/// Sent from /datum/action/cooldown/spell/cast() to the spell: (atom/cast_on) +#define COMSIG_SPELL_CAST "spell_cast" +// Sent from /datum/action/cooldown/spell/after_cast() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on) +#define COMSIG_MOB_AFTER_SPELL_CAST "mob_after_spell_cast" +/// Sent from /datum/action/cooldown/spell/after_cast() to the spell: (atom/cast_on) +#define COMSIG_SPELL_AFTER_CAST "spell_after_cast" +/// Sent from /datum/action/cooldown/spell/reset_spell_cooldown() to the spell: () +#define COMSIG_SPELL_CAST_RESET "spell_cast_reset" +/// Sent from /datum/action/cooldown/spell/proc/invocation() to the mob: (datum/source, /datum/action/cooldown/spell/spell, list/invocation) +#define COMSIG_MOB_PRE_INVOCATION "spell_pre_invocation" + ///index for the invocation message string + #define INVOCATION_MESSAGE 1 + ///index for the invocation type string + #define INVOCATION_TYPE 2 + ///index for the invocation garble probability number + #define INVOCATION_GARBLE_PROB 3 + +// 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) +#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" + +// AOE spells +/// Sent from /datum/action/cooldown/spell/aoe/cast: (list/atoms_affected, atom/caster) +#define COMSIG_SPELL_AOE_ON_CAST "spell_aoe_cast" + +// Cone spells +/// Sent from /datum/action/cooldown/spell/cone/cast: (list/atoms_affected, atom/caster) +#define COMSIG_SPELL_CONE_ON_CAST "spell_cone_cast" +/// Sent from /datum/action/cooldown/spell/cone/do_cone_effects: (list/atoms_affected, atom/caster, level) +#define COMSIG_SPELL_CONE_ON_LAYER_EFFECT "spell_cone_cast_effect" + +// Touch spells +/// Sent from /datum/action/cooldown/spell/touch/do_hand_hit: (atom/hit, mob/living/carbon/caster, obj/item/melee/touch_attack/hand) +#define COMSIG_SPELL_TOUCH_HAND_HIT "spell_touch_hand_cast" +/// Sent from /datum/action/cooldown/spell/touch/cast: (mob/living/carbon/cast_on) +#define COMSIG_TOUCH_HANDLESS_CAST "spell_touch_handless_cast" + /// Return this to prevent the hand spawning/unspawning + #define COMPONENT_CAST_HANDLESS (1<<0) + +// Jaunt Spells +/// Sent from datum/action/cooldown/spell/jaunt/before_cast, before the mob enters jaunting as a pre-check: (datum/action/cooldown/spell/spell) +#define COMSIG_MOB_PRE_JAUNT "spell_mob_pre_jaunt" + #define COMPONENT_BLOCK_JAUNT (1<<0) +/// Sent from datum/action/cooldown/spell/jaunt/enter_jaunt, to the mob jaunting: (obj/effect/dummy/phased_mob/jaunt, datum/action/cooldown/spell/spell) +#define COMSIG_MOB_ENTER_JAUNT "spell_mob_enter_jaunt" +/// Set from /obj/effect/dummy/phased_mob after the mob is ejected from its contents: (obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter) +#define COMSIG_MOB_EJECTED_FROM_JAUNT "spell_mob_eject_jaunt" +/// Sent from datum/action/cooldown/spell/jaunt/exit_jaunt, after the mob exited jaunt: (datum/action/cooldown/spell/spell) +#define COMSIG_MOB_AFTER_EXIT_JAUNT "spell_mob_after_exit_jaunt" +/// Sent from /obj/effect/dummy/phased_mob/proc/phased_check when moving to the holder object: (/obj/effect/dummy/phased_mob, mob/living/phaser, turf/newloc) +#define COMSIG_MOB_PHASED_CHECK "mob_phased_check" + /// Return this to cancel the phased move + #define COMPONENT_BLOCK_PHASED_MOVE (1 << 0) + +/// Sent from/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt, +/// to any unconscious / critical mobs being dragged when the jaunter enters blood: +/// (datum/action/cooldown/spell/jaunt/bloodcrawl/crawl, mob/living/jaunter, obj/effect/decal/cleanable/blood) +#define COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED "living_pre_consumed_by_bloodcrawl" +/// Sent from/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/consume_victim, +/// to the victim being consumed by the slaughter demon. +/// (datum/action/cooldown/spell/jaunt/bloodcrawl/crawl, mob/living/jaunter) +#define COMSIG_LIVING_BLOOD_CRAWL_CONSUMED "living_consumed_by_bloodcrawl" + /// Return at any point to stop the bloodcrawl "consume" process from continuing. + #define COMPONENT_STOP_CONSUMPTION (1 << 0) + +// Signals for specific spells + +// Lichdom +/// Sent from /datum/action/cooldown/spell/lichdom/cast(), to the item being imbued: (datum/action/cooldown/spell/spell, mob/user) +#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" + /// Return to stop the cast and prevent the soul imbue + #define COMPONENT_BLOCK_IMBUE (1 << 0) + +/// Sent from /datum/action/cooldown/spell/aoe/knock/cast(), to every nearby turf (for connect loc): (datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster) +#define COMSIG_ATOM_MAGICALLY_UNLOCKED "atom_magic_unlock" + +// Instant Summons +/// Sent from /datum/action/cooldown/spell/summonitem/cast(), to the item being marked for recall: (datum/action/cooldown/spell/spell, mob/user) +#define COMSIG_ITEM_MARK_RETRIEVAL "item_mark_retrieval" + /// Return to stop the cast and prevent the item from being marked + #define COMPONENT_BLOCK_MARK_RETRIEVAL (1 << 0) +///When an object is retrieved by a magic recall spell. This will apply to all containers, mobs, etc. that are pulled by the spell. +#define COMSIG_MAGIC_RECALL "magic_recall" + + +// Charge +/// Sent from /datum/action/cooldown/spell/charge/cast(), to the item in hand being charged: (datum/action/cooldown/spell/spell, mob/user) +#define COMSIG_ITEM_MAGICALLY_CHARGED "item_magic_charged" + /// Return if an item was successful recharged + #define COMPONENT_ITEM_CHARGED (1 << 0) + /// Return if the item had a negative side effect occur while recharging + #define COMPONENT_ITEM_BURNT_OUT (1 << 1) diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm new file mode 100644 index 000000000000..a44f8d9a59fa --- /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 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_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 ALL_MAGIC_RESISTANCE (MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND|MAGIC_RESISTANCE_HOLY) + +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/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 02d08e16daff..8589c861346b 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -87,6 +87,8 @@ #define FINGERS_COVERED (1<<6) /// prevents from placing on plasmaman helmet or modsuit hat holder #define STACKABLE_HELMET_EXEMPT (1<<7) +/// Usable as casting clothes by wizards (matters for suits, glasses and headwear) +#define CASTING_CLOTHES (1<<8) /// Flags for the pod_flags var on /obj/structure/closet/supplypod #define FIRST_SOUNDS (1<<0) // If it shouldn't play sounds the first time it lands, used for reverse mode diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 3bae792cab76..7611eb238792 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -511,3 +511,16 @@ /// An item is ALWAYS considered baseline reachable and will pipe into CanBeReached(). #define TRAIT_SKIP_BASIC_REACH_CHECK "skip_basic_reach_check" + +/// The mob has an active mime vow of silence, and thus is unable to speak and has other mime things going on +#define TRAIT_MIMING "miming" + +/// This atom can have spells cast from it if a mob is within it +/// This means the "caster" of the spell is changed to the mob's loc +/// Note this doesn't mean all spells are guaranteed to work or the mob is guaranteed to cast +#define TRAIT_CASTABLE_LOC "castable_loc" + +/// Needs above trait to work. +/// This trait makes it so that any cast spells will attempt to transfer to the location's location. +/// For example, a heretic inside the haunted blade's spells would emanate from the mob wielding the sword. +#define TRAIT_SPELLS_TRANSFER_TO_LOC "spells_transfer_to_loc" diff --git a/code/datums/actions/spell_action.dm b/code/datums/actions/spell_action.dm index 92731fdd7dd4..43f7265c00c5 100644 --- a/code/datums/actions/spell_action.dm +++ b/code/datums/actions/spell_action.dm @@ -40,470 +40,472 @@ * - [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 //|AB_CHECK_PHASED -// panel = "Spells" - -// /// 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 = "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 -// /// if true, doesn't garble the invocation sometimes with backticks -// var/garbled_invocation_prob = 50 -// /// 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 - -// /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 - -// . = ..() -// if(!owner) -// 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)) -// RegisterSignals(owner, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(update_status_on_signal)) -// if(invocation_type == INVOCATION_EMOTE) -// RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_EMOTEMUTE), SIGNAL_REMOVETRAIT(TRAIT_EMOTEMUTE)), PROC_REF(update_status_on_signal)) -// if(invocation_type == INVOCATION_SHOUT || invocation_type == INVOCATION_WHISPER) -// RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal)) - -// RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) -// owner.client?.stat_panel.send_message("check_spells") - -// /datum/action/cooldown/spell/Remove(mob/living/remove_from) - -// remove_from.client?.stat_panel.send_message("check_spells") -// UnregisterSignal(remove_from, list( -// COMSIG_MOB_AFTER_EXIT_JAUNT, -// COMSIG_MOB_ENTER_JAUNT, -// COMSIG_MOB_EQUIPPED_ITEM, -// COMSIG_MOB_UNEQUIPPED_ITEM, -// COMSIG_MOVABLE_Z_CHANGED, -// SIGNAL_ADDTRAIT(TRAIT_EMOTEMUTE), -// SIGNAL_REMOVETRAIT(TRAIT_EMOTEMUTE), -// SIGNAL_ADDTRAIT(TRAIT_MUTE), -// SIGNAL_REMOVETRAIT(TRAIT_MUTE), -// )) - -// return ..() - -// /datum/action/cooldown/spell/IsAvailable(feedback = FALSE) -// return ..() && can_cast_spell(feedback) - -// /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 - -// return ..() - -// // Where the cast chain starts -// /datum/action/cooldown/spell/PreActivate(atom/target) -// if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START) -// return FALSE -// if(target == owner) -// target = get_caster_from_target(target) -// if(isnull(target) || !is_valid_target(target)) -// return FALSE - -// return Activate(target) - -// /// 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((spell_requirements & SPELL_REQUIRES_MIND) && !owner.mind) -// // No point in feedback here, as mindless mobs aren't players -// return FALSE - -// if((spell_requirements & SPELL_REQUIRES_MIME_VOW) && !HAS_MIND_TRAIT(owner, TRAIT_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 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 - -// if(!try_invoke(owner, feedback = feedback)) -// return FALSE - -// if(ishuman(owner)) -// if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) -// var/mob/living/carbon/human/human_owner = owner -// if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES) && !ismonkey(human_owner)) // Monkeys don't need robes to cast as they are inherently imbued with power from the banana dimension -// if(feedback) -// to_chat(owner, span_warning("You don't feel strong enough without your robe!")) -// return FALSE -// if(!(human_owner.head?.clothing_flags & CASTING_CLOTHES) && !(human_owner.glasses?.clothing_flags & CASTING_CLOTHES)) -// if(feedback) -// to_chat(owner, span_warning("You don't feel strong enough without your hat!")) -// return FALSE - -// else -// // If you strictly need to be a human, well, goodbye. -// if(spell_requirements & SPELL_REQUIRES_HUMAN) -// if(feedback) -// to_chat(owner, span_warning("[src] can only be cast by humans!")) -// return FALSE - -// // Otherwise, we can check for contents if they have wizardly apparel. This isn't *quite* perfect, but it'll do, especially since many of the edge cases (gorilla holding a wizard hat) still more or less make sense. -// if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) -// var/any_casting = FALSE -// for(var/obj/item/clothing/item in owner) -// if(item.clothing_flags & CASTING_CLOTHES) -// any_casting = TRUE -// break - -// if(!any_casting) -// if(feedback) -// to_chat(owner, span_warning("You don't feel strong enough without your hat!")) -// return FALSE - -// 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 - -// return TRUE - -// /** -// * 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 - -// /** -// * Used to get the cast_on atom if a self cast spell is being cast. -// * -// * Allows for some atoms to be used as casting sources if a spell caster is located within. -// */ -// /datum/action/cooldown/spell/proc/get_caster_from_target(atom/target) -// var/atom/cast_loc = target.loc -// if(isnull(cast_loc)) -// return null // No magic in nullspace - -// if(isturf(cast_loc)) -// return target // They're just standing around, proceed as normal - -// if(HAS_TRAIT(cast_loc, TRAIT_CASTABLE_LOC)) -// if(HAS_TRAIT(cast_loc, TRAIT_SPELLS_TRANSFER_TO_LOC) && ismob(cast_loc.loc)) -// return cast_loc.loc -// else -// return cast_loc -// // They're in an atom which allows casting, so redirect the caster to loc - -// return null - -// // 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 - -// // 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(owner) - -// // Actually cast the spell. Main effects go here -// cast(cast_on) - -// if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) -// // The entire spell is done, start the actual cooldown at its set duration -// StartCooldown() - -// // 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 - -// /** -// * 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) - -// // Bonus invocation check done here: -// // If the caster has no tongue and it's a verbal spell, -// // Or has no hands and is a gesture spell - cancel it, -// // and show a funny message that they tried -// if(ishuman(owner) && !(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION)) -// var/mob/living/carbon/human/caster = owner -// switch(invocation_type) -// if(INVOCATION_WHISPER, INVOCATION_SHOUT) -// if(!caster.get_organ_slot(ORGAN_SLOT_TONGUE)) -// invocation(caster) -// to_chat(caster, span_warning("Your lack of tongue is making it difficult to say the correct words to cast [src]...")) -// StartCooldown(2 SECONDS) -// return SPELL_CANCEL_CAST - -// if(INVOCATION_EMOTE) -// if(caster.usable_hands <= 0) -// var/arm_describer = (caster.num_hands >= 2 ? "arms limply" : (caster.num_hands == 1 ? "arm wildly" : "arm stumps")) -// caster.visible_message( -// span_warning("[caster] wiggles around [caster.p_their()] [arm_describer]."), -// ignored_mobs = caster, -// ) -// to_chat(caster, span_warning("You can't position your hands correctly to invoke [src][caster.num_hands > 0 ? "" : ", as you have none"]...")) -// StartCooldown(2 SECONDS) -// return SPELL_CANCEL_CAST - -// 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 - -// 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() - -// // 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) - -// /// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation -// /datum/action/cooldown/spell/proc/spell_feedback(mob/living/invoker) -// if(!invoker) -// return - -// ///even INVOCATION_NONE should go through this because the signal might change that -// invocation(invoker) -// if(sound) -// playsound(invoker, sound, 50, vary = TRUE) - -// /// The invocation that accompanies the spell, called from spell_feedback() before cast(). -// /datum/action/cooldown/spell/proc/invocation(mob/living/invoker) -// //lists can be sent by reference, a string would be sent by value -// var/list/invocation_list = list(invocation, invocation_type, garbled_invocation_prob) -// SEND_SIGNAL(invoker, COMSIG_MOB_PRE_INVOCATION, src, invocation_list) -// var/used_invocation_message = invocation_list[INVOCATION_MESSAGE] -// var/used_invocation_type = invocation_list[INVOCATION_TYPE] -// var/used_invocation_garble_prob = invocation_list[INVOCATION_GARBLE_PROB] - -// switch(used_invocation_type) -// if(INVOCATION_SHOUT) -// if(prob(used_invocation_garble_prob)) -// invoker.say(replacetext(used_invocation_message," ","`"), forced = "spell ([src])") -// else -// invoker.say(used_invocation_message, forced = "spell ([src])") - -// if(INVOCATION_WHISPER) -// if(prob(used_invocation_garble_prob)) -// invoker.whisper(replacetext(used_invocation_message," ","`"), forced = "spell ([src])") -// else -// invoker.whisper(used_invocation_message, forced = "spell ([src])") - -// if(INVOCATION_EMOTE) -// invoker.visible_message( -// capitalize(REPLACE_PRONOUNS(replacetext(used_invocation_message, "%CASTER", invoker.name), invoker)), -// capitalize(REPLACE_PRONOUNS(replacetext(invocation_self_message, "%CASTER", invoker.name), invoker)), -// visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, -// ) - -// /// 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(mob/living/invoker, feedback = TRUE) -// if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION) -// return TRUE - -// if(invocation_type == INVOCATION_NONE) -// return TRUE - -// // If you want a spell usable by ghosts for some reason, it must be INVOCATION_NONE -// if(!istype(invoker)) -// if(feedback) -// to_chat(invoker, span_warning("You need to be living to invoke [src]!")) -// return FALSE - -// var/invoke_sig_return = SEND_SIGNAL(invoker, COMSIG_MOB_TRY_INVOKE_SPELL, src, feedback) -// if(invoke_sig_return & SPELL_INVOCATION_ALWAYS_SUCCEED) -// return TRUE // skips all of the following checks -// if(invoke_sig_return & SPELL_INVOCATION_FAIL) -// return FALSE - -// if(invocation_type == INVOCATION_EMOTE && HAS_TRAIT(invoker, TRAIT_EMOTEMUTE)) -// if(feedback) -// to_chat(invoker, span_warning("You can't position your hands correctly to invoke [src]!")) -// return FALSE - -// if((invocation_type == INVOCATION_WHISPER || invocation_type == INVOCATION_SHOUT) && !invoker.can_speak()) -// if(feedback) -// to_chat(invoker, span_warning("You can't get the words out to invoke [src]!")) -// return FALSE - -// 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 - -// // 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. -// name = "[get_spell_title()][initial(name)]" -// 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)) - -// name = "[get_spell_title()][initial(name)]" -// build_all_button_icons(UPDATE_BUTTON_NAME) -// return TRUE - -// /// 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 "" +/datum/action/cooldown/spell + name = "Spell" + desc = "A wizard spell." + background_icon_state = "bg_spell" + button_icon = 'icons/mob/actions/actions.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 //|AB_CHECK_PHASED + panel = "Spells" + + /// 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 = "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 + /// if true, doesn't garble the invocation sometimes with backticks + var/garbled_invocation_prob = 50 + /// 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 + +/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 + + . = ..() + if(!owner) + 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, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(update_status_on_signal)) + if(invocation_type == INVOCATION_EMOTE) + RegisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_EMOTE_MUTE), SIGNAL_REMOVETRAIT(TRAIT_EMOTE_MUTE)), PROC_REF(update_status_on_signal)) + if(invocation_type == INVOCATION_SHOUT || invocation_type == INVOCATION_WHISPER) + RegisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal)) + + RegisterSignal(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) + owner.client?.stat_panel.send_message("check_spells") + +/datum/action/cooldown/spell/Remove(mob/living/remove_from) + + remove_from.client?.stat_panel.send_message("check_spells") + UnregisterSignal(remove_from, list( + COMSIG_MOB_AFTER_EXIT_JAUNT, + COMSIG_MOB_ENTER_JAUNT, + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOB_UNEQUIPPED_ITEM, + COMSIG_MOVABLE_Z_CHANGED, + SIGNAL_ADDTRAIT(TRAIT_EMOTE_MUTE), + SIGNAL_REMOVETRAIT(TRAIT_EMOTE_MUTE), + SIGNAL_ADDTRAIT(TRAIT_MUTE), + SIGNAL_REMOVETRAIT(TRAIT_MUTE), + )) + + return ..() + +/datum/action/cooldown/spell/IsAvailable(feedback = FALSE) + return ..() && can_cast_spell(feedback) + +/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 + + return ..() + +// Where the cast chain starts +/datum/action/cooldown/spell/PreActivate(atom/target) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START) + return FALSE + if(target == owner) + target = get_caster_from_target(target) + if(isnull(target) || !is_valid_target(target)) + return FALSE + + return Activate(target) + +/// 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((spell_requirements & SPELL_REQUIRES_MIND) && !owner.mind) + // No point in feedback here, as mindless mobs aren't players + return FALSE + + if((spell_requirements & SPELL_REQUIRES_MIME_VOW) && !HAS_MIND_TRAIT(owner, TRAIT_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 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 + + if(!try_invoke(owner, feedback = feedback)) + return FALSE + + if(ishuman(owner)) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + var/mob/living/carbon/human/human_owner = owner + if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES) && !ismonkey(human_owner)) // Monkeys don't need robes to cast as they are inherently imbued with power from the banana dimension + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your robe!")) + return FALSE + if(!(human_owner.head?.clothing_flags & CASTING_CLOTHES) && !(human_owner.glasses?.clothing_flags & CASTING_CLOTHES)) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your hat!")) + return FALSE + + else + // If you strictly need to be a human, well, goodbye. + if(spell_requirements & SPELL_REQUIRES_HUMAN) + if(feedback) + to_chat(owner, span_warning("[src] can only be cast by humans!")) + return FALSE + + // Otherwise, we can check for contents if they have wizardly apparel. This isn't *quite* perfect, but it'll do, especially since many of the edge cases (gorilla holding a wizard hat) still more or less make sense. + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + var/any_casting = FALSE + for(var/obj/item/clothing/item in owner) + if(item.clothing_flags & CASTING_CLOTHES) + any_casting = TRUE + break + + if(!any_casting) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your hat!")) + return FALSE + + 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 + + return TRUE + +/** + * 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 + +/** + * Used to get the cast_on atom if a self cast spell is being cast. + * + * Allows for some atoms to be used as casting sources if a spell caster is located within. + */ +/datum/action/cooldown/spell/proc/get_caster_from_target(atom/target) + var/atom/cast_loc = target.loc + if(isnull(cast_loc)) + return null // No magic in nullspace + + if(isturf(cast_loc)) + return target // They're just standing around, proceed as normal + + if(HAS_TRAIT(cast_loc, TRAIT_CASTABLE_LOC)) + if(HAS_TRAIT(cast_loc, TRAIT_SPELLS_TRANSFER_TO_LOC) && ismob(cast_loc.loc)) + return cast_loc.loc + else + return cast_loc + // They're in an atom which allows casting, so redirect the caster to loc + + return null + +// 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 + + // 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(owner) + + // Actually cast the spell. Main effects go here + cast(cast_on) + + if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) + // The entire spell is done, start the actual cooldown at its set duration + StartCooldown() + + // 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 + +/** + * 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) + + // Bonus invocation check done h ere: + // If the caster has no tongue and it's a verbal spell, + // Or has no hands and is a gesture spell - cancel it, + // and show a funny message that they tried + if(ishuman(owner) && !(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION)) + var/mob/living/carbon/human/caster = owner + switch(invocation_type) +//We don't have a tongue organ +// if(INVOCATION_WHISPER, INVOCATION_SHOUT) +// if(!caster.get_organ_slot(ORGAN_SLOT_TONGUE)) +// invocation(caster) +// to_chat(caster, span_warning("Your lack of tongue is making it difficult to say the correct words to cast [src]...")) +// StartCooldown(2 SECONDS) +// return SPELL_CANCEL_CAST + + if(INVOCATION_EMOTE) + if(caster.usable_hands <= 0) + var/arm_describer = (caster.num_hands >= 2 ? "arms limply" : (caster.num_hands == 1 ? "arm wildly" : "arm stumps")) + caster.visible_message( + span_warning("[caster] wiggles around [caster.p_their()] [arm_describer]."), + ignored_mobs = caster, + ) + to_chat(caster, span_warning("You can't position your hands correctly to invoke [src][caster.num_hands > 0 ? "" : ", as you have none"]...")) + StartCooldown(2 SECONDS) + return SPELL_CANCEL_CAST + + 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.create_attack_log("cast the spell [name][cast_on != owner ? " on / at [cast_on]":""].") + +/** + * 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 + + 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() + + // 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) + +/// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation +/datum/action/cooldown/spell/proc/spell_feedback(mob/living/invoker) + if(!invoker) + return + + ///even INVOCATION_NONE should go through this because the signal might change that + invocation(invoker) + if(sound) + playsound(invoker, sound, 50, vary = TRUE) + +/// The invocation that accompanies the spell, called from spell_feedback() before cast(). +/datum/action/cooldown/spell/proc/invocation(mob/living/invoker) + //lists can be sent by reference, a string would be sent by value + var/list/invocation_list = list(invocation, invocation_type, garbled_invocation_prob) + SEND_SIGNAL(invoker, COMSIG_MOB_PRE_INVOCATION, src, invocation_list) + var/used_invocation_message = invocation_list[INVOCATION_MESSAGE] + var/used_invocation_type = invocation_list[INVOCATION_TYPE] + var/used_invocation_garble_prob = invocation_list[INVOCATION_GARBLE_PROB] + + switch(used_invocation_type) + if(INVOCATION_SHOUT) + if(prob(used_invocation_garble_prob)) + invoker.say(replacetext(used_invocation_message," ","`")) + else + invoker.say(used_invocation_message) + + if(INVOCATION_WHISPER) + if(prob(used_invocation_garble_prob)) + invoker.whisper(replacetext(used_invocation_message," ","`")) + else + invoker.whisper(used_invocation_message) + +// need localization +// if(INVOCATION_EMOTE) +// invoker.visible_message( +// capitalize(REPLACE_PRONOUNS(replacetext(used_invocation_message, "%CASTER", invoker.name), invoker)), +// capitalize(REPLACE_PRONOUNS(replacetext(invocation_self_message, "%CASTER", invoker.name), invoker)), +// visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, +// ) + +/// 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(mob/living/invoker, feedback) + if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION) + return TRUE + + if(invocation_type == INVOCATION_NONE) + return TRUE + + // If you want a spell usable by ghosts for some reason, it must be INVOCATION_NONE + if(!istype(invoker)) + if(feedback) + to_chat(invoker, span_warning("You need to be living to invoke [src]!")) + return FALSE + + var/invoke_sig_return = SEND_SIGNAL(invoker, COMSIG_MOB_TRY_INVOKE_SPELL, src, feedback) + if(invoke_sig_return & SPELL_INVOCATION_ALWAYS_SUCCEED) + return TRUE // skips all of the following checks + if(invoke_sig_return & SPELL_INVOCATION_FAIL) + return FALSE + + if(invocation_type == INVOCATION_EMOTE && HAS_TRAIT(invoker, TRAIT_EMOTE_MUTE)) + if(feedback) + to_chat(invoker, span_warning("You can't position your hands correctly to invoke [src]!")) + return FALSE + + if((invocation_type == INVOCATION_WHISPER || invocation_type == INVOCATION_SHOUT) && !invoker.can_speak()) + if(feedback) + to_chat(invoker, span_warning("You can't get the words out to invoke [src]!")) + return FALSE + + 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 + + // 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. + name = "[get_spell_title()][initial(name)]" + 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)) + + name = "[get_spell_title()][initial(name)]" + build_all_button_icons(UPDATE_BUTTON_NAME) + return TRUE + +/// 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 "" // Legacy shit diff --git a/code/datums/emote/emote.dm b/code/datums/emote/emote.dm index 37ed90021c3d..2064e90b7c08 100644 --- a/code/datums/emote/emote.dm +++ b/code/datums/emote/emote.dm @@ -215,7 +215,7 @@ for(var/mob/dead/observer/ghost in viewers(user)) ghost.show_message(span_deadsay("[displayed_msg]"), EMOTE_VISIBLE, chat_message_type = MESSAGE_TYPE_LOCALCHAT) - else if((emote_type & (EMOTE_AUDIBLE|EMOTE_SOUND)) && user.mind && !user.mind.miming) + else if((emote_type & (EMOTE_AUDIBLE|EMOTE_SOUND)) && user.mind && !HAS_TRAIT(user.mind, TRAIT_MIMING)) user.audible_message(displayed_msg, deaf_message = span_emote("You see how [user] [msg]")) else user.visible_message(displayed_msg) @@ -359,7 +359,7 @@ */ /datum/emote/proc/select_message_type(mob/user, msg, intentional) . = msg - if(user.mind && user.mind.miming && message_mime) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING) && message_mime) . = islist(message_mime) ? pick(message_mime) : message_mime if(isalienadult(user) && message_alien) . = islist(message_alien) ? pick(message_alien) : message_alien @@ -544,7 +544,7 @@ * If this returns false, any mouth emotes will be replaced with muzzled noises. */ /datum/emote/proc/can_vocalize_emotes(mob/user) - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) // mimes get special treatment; though they can't really "vocalize" we don't want to replace their message. return TRUE if(!muzzle_ignore && !user.can_speak()) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index c2ca0e3586ab..b9eabe2372f3 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -56,7 +56,6 @@ var/has_been_rev = 0//Tracks if this mind has been a rev or not - var/miming = 0 // Mime's vow of silence var/list/antag_datums /// this mind's ANTAG_HUD should have this icon_state diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm index 960d6484ac22..fb1171af1598 100644 --- a/code/datums/spells/mime.dm +++ b/code/datums/spells/mime.dm @@ -15,7 +15,7 @@ /obj/effect/proc_holder/spell/aoe/conjure/build/mime_wall/Click() if(usr?.mind) - if(!usr.mind.miming) + if(!HAS_TRAIT(usr.mind, TRAIT_MIMING)) to_chat(usr, span_warning("Сначала вы должны принять обет молчания!")) return invocation = "[usr] выглядит так, как будто бы перед н[GEND_IM_EI_IM_IMI(usr)] находится стена." @@ -42,7 +42,7 @@ if(!ishuman(usr)) return var/mob/living/carbon/human/user = usr - if(user.mind.miming) + if(HAS_TRAIT(user.mind, TRAIT_MIMING)) still_recharging_msg = span_warning("Вы не можете так быстро нарушить свой обет молчания!") else still_recharging_msg = span_warning("Вам придётся подождать, прежде чем вы сможете снова дать обет молчания!") @@ -54,18 +54,18 @@ if(!target.mind) return - target.mind.miming = !target.mind.miming - - if(target.mind.miming) - to_chat(target, span_notice("Вы даёте обет молчания.")) - else + if(HAS_TRAIT(user.mind, TRAIT_MIMING)) + ADD_TRAIT(user.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(user.mind)) to_chat(target, span_notice("Вы нарушаете свой обет молчания.")) + else + REMOVE_TRAIT(user.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(user.mind)) + to_chat(target, span_notice("Вы даёте обет молчания.")) /obj/effect/proc_holder/spell/mime/speak/mask /obj/effect/proc_holder/spell/mime/speak/mask/on_cooldown_tick() var/mob/living/carbon/human/user = action.owner if(user && cooldown_handler.should_end_cooldown() && !istype(user.wear_mask, /obj/item/clothing/mask/gas/mime)) - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) cast(list(user)) user.mind?.RemoveSpell(src) @@ -87,7 +87,7 @@ /obj/effect/proc_holder/spell/forcewall/mime/Click() if(usr?.mind) - if(!usr.mind.miming) + if(!HAS_TRAIT(usr.mind, TRAIT_MIMING)) to_chat(usr, span_warning("Сначала вы должны принять обет молчания!")) return invocation = "[usr] выглядит так, как будто бы перед н[GEND_IM_EI_IM_IMI(usr)] находится стена." diff --git a/code/game/dna/genes/vg_powers.dm b/code/game/dna/genes/vg_powers.dm index 1f9428a32e48..94aaaab92dda 100644 --- a/code/game/dna/genes/vg_powers.dm +++ b/code/game/dna/genes/vg_powers.dm @@ -217,7 +217,7 @@ /obj/effect/proc_holder/spell/remotetalk/cast(list/targets, mob/living/carbon/human/user = usr) if(!ishuman(user)) return - if(user.mind?.miming) // Dont let mimes telepathically talk + if(user.mind &&HAS_MIND_TRAIT(user, TRAIT_MIMING)) // Dont let mimes telepathically talk to_chat(user, span_warning("Вы не можете общаться, не нарушив свой обет молчания.")) return for(var/mob/living/target in targets) diff --git a/code/game/gamemodes/clockwork/clock_actions.dm b/code/game/gamemodes/clockwork/clock_actions.dm index 9991398ef45c..6b7ab333b5a8 100644 --- a/code/game/gamemodes/clockwork/clock_actions.dm +++ b/code/game/gamemodes/clockwork/clock_actions.dm @@ -28,7 +28,7 @@ return var/prefix = "" - if(HAS_TRAIT(user, TRAIT_MUTE) || user.mind.miming) //Under vow of silence/mute? + if(HAS_TRAIT(user, TRAIT_MUTE) || HAS_MIND_TRAIT(user, TRAIT_MIMING)) //Under vow of silence/mute? user.visible_message( span_notice("[user] appears to whisper to themselves."), span_notice("You begin to whisper to yourself.") diff --git a/code/game/gamemodes/cult/cult_actions.dm b/code/game/gamemodes/cult/cult_actions.dm index d9d4d5c31aaf..7b33ce503afb 100644 --- a/code/game/gamemodes/cult/cult_actions.dm +++ b/code/game/gamemodes/cult/cult_actions.dm @@ -30,7 +30,7 @@ to_chat(user, span_warning("You can't speak!")) return - if(HAS_TRAIT(user, TRAIT_MUTE) || user.mind.miming) //Under vow of silence/mute? + if(HAS_TRAIT(user, TRAIT_MUTE) ||HAS_MIND_TRAIT(user, TRAIT_MIMING)) //Under vow of silence/mute? user.visible_message( span_notice("[user] appears to whisper to themselves."), span_notice("You begin to whisper to yourself.") diff --git a/code/game/gamemodes/wizard/wizloadouts.dm b/code/game/gamemodes/wizard/wizloadouts.dm index b789b7faa258..ffba32487e8f 100644 --- a/code/game/gamemodes/wizard/wizloadouts.dm +++ b/code/game/gamemodes/wizard/wizloadouts.dm @@ -75,7 +75,7 @@ /datum/spellbook_entry/loadout/mimewiz/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) if(user.mind) user.mind.AddSpell(new /obj/effect/proc_holder/spell/mime/speak(null)) - user.mind.miming = TRUE + ADD_TRAIT(user.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(user.mind)) ..() /datum/spellbook_entry/loadout/gunreaper diff --git a/code/game/jobs/job/service.dm b/code/game/jobs/job/service.dm index 218256f833ca..3e446a841ece 100644 --- a/code/game/jobs/job/service.dm +++ b/code/game/jobs/job/service.dm @@ -426,7 +426,7 @@ if(H.mind) H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe/conjure/build/mime_wall(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/mime/speak(null)) - H.mind.miming = TRUE + ADD_TRAIT(H.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(H.mind)) /datum/job/service/janitor title = JOB_TITLE_JANITOR diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 0f697794c4b3..3fd7f0278ab3 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -810,6 +810,7 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/g user.update_equipment_speed_mods() SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) + SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot) return TRUE /// Gives one of our item actions to a mob, when equipped to a certain slot diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm index e288029989f8..db92ada58cf8 100644 --- a/code/game/objects/items/devices/megaphone.dm +++ b/code/game/objects/items/devices/megaphone.dm @@ -29,7 +29,7 @@ return if(ishuman(user)) var/mob/living/carbon/human/H = user - if(H?.mind && H.mind.miming) + if(H?.mind && HAS_TRAIT(H.mind, TRAIT_MIMING)) to_chat(user, span_warning("Your vow of silence prevents you from speaking.")) return if(HAS_TRAIT(H, TRAIT_COMIC) || H.get_int_organ(/obj/item/organ/internal/cyberimp/brain/clown_voice)) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index d3c6e5da1578..1d160266a519 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -141,7 +141,7 @@ if(LAZYIN(user.active_genes, /datum/dna/gene/disability/clumsy)) slopchance += 20 - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) slopchance -= 30 slopchance = clamp(slopchance, 1, 100) diff --git a/code/modules/awaymissions/mission_code/ghost_role_spawners/golems.dm b/code/modules/awaymissions/mission_code/ghost_role_spawners/golems.dm index 7c2941ce140f..89a6f9c62a4b 100644 --- a/code/modules/awaymissions/mission_code/ghost_role_spawners/golems.dm +++ b/code/modules/awaymissions/mission_code/ghost_role_spawners/golems.dm @@ -148,7 +148,7 @@ if(is_species(H, /datum/species/golem/tranquillite) && H.mind) H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe/conjure/build/mime_wall(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/mime/speak(null)) - H.mind.miming = TRUE + ADD_TRAIT(H.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(H.mind)) if(has_owner) new_spawn.mind.assigned_role = "Servant Golem" diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index a5d8931b7fe7..8034575377ca 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -436,7 +436,7 @@ // spell.action.enable_invisibility(TRUE) // return - if(user.mind.miming) + if(HAS_TRAIT(user.mind, TRAIT_MIMING)) spell.cast(list(user)) user.mind.RemoveSpell(spell) diff --git a/code/modules/mob/living/carbon/human/human_emote.dm b/code/modules/mob/living/carbon/human/human_emote.dm index 04227b6fda28..349d6a0a7361 100644 --- a/code/modules/mob/living/carbon/human/human_emote.dm +++ b/code/modules/mob/living/carbon/human/human_emote.dm @@ -16,11 +16,11 @@ /datum/emote/living/carbon/human/select_message_type(mob/living/carbon/human/user, msg, intentional) . = ..() - if(!species_custom_messages || (user.mind?.miming && !species_custom_mime_messages)) + if(!species_custom_messages || ((user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) && !species_custom_mime_messages)) return . var/custom_message - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) custom_message = species_custom_mime_messages[user.dna.species?.name] else custom_message = species_custom_messages[user.dna.species?.name] @@ -29,7 +29,7 @@ return custom_message /datum/emote/living/carbon/human/run_emote(mob/living/carbon/human/user, params, type_override, intentional) - if((emote_type & EMOTE_MOUTH) && !user.mind?.miming) + if((emote_type & EMOTE_MOUTH) && (user.mind && !HAS_TRAIT(user.mind, TRAIT_MIMING))) if(user.getOxyLoss() > 35 || user.AmountLoseBreath() >= 8 SECONDS) // no screaming if you don't have enough breath to scream user.emote("gasp") return TRUE @@ -246,7 +246,7 @@ /datum/emote/living/carbon/human/scream/select_message_type(mob/living/carbon/human/user, msg, intentional) var/scream_verb = user.dna?.species?.scream_verb if(scream_verb) - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) . = "делает вид, что [scream_verb]!" else . = "[scream_verb]!" @@ -369,7 +369,7 @@ if(!target) return message var/msg = message - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) msg = "затягива%(ет,ют)%ся сигаретой и выдыха%(ет,ют)% дым в форме имени \"[target.name]\"." else msg = "говор%(ит,ят)%, \"[target.name], пожалуйста. У них была семья.\" [user.name] затягивается сигаретой и выдыха%(ет,ют)% дым в форме %(своего,их)% имени." diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm index 3cace64f45ec..8dfa20d194fd 100644 --- a/code/modules/mob/living/carbon/human/human_say.dm +++ b/code/modules/mob/living/carbon/human/human_say.dm @@ -82,7 +82,7 @@ /mob/living/carbon/human/IsVocal() var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR) - if(translator?.active && !mind?.miming) + if(translator?.active && (mind && HAS_TRAIT(mind, TRAIT_MIMING))) return TRUE // Cyberimps don't care if you need to breathe at all, but make some respect to mimes if(HAS_TRAIT(src, TRAIT_MUTE)) @@ -98,7 +98,7 @@ return FALSE if(mind) - return !mind.miming + return !HAS_TRAIT(mind, TRAIT_MIMING) return TRUE diff --git a/code/modules/mob/living/carbon/human/species/golem.dm b/code/modules/mob/living/carbon/human/species/golem.dm index 4abbcf94629a..f04a7ac0cc9a 100644 --- a/code/modules/mob/living/carbon/human/species/golem.dm +++ b/code/modules/mob/living/carbon/human/species/golem.dm @@ -1246,7 +1246,7 @@ GLOBAL_LIST_EMPTY(cached_heal_materials) if(H.mind) H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe/conjure/build/mime_wall(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/mime/speak(null)) - H.mind.miming = TRUE + ADD_TRAIT(H.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(H.mind)) /datum/species/golem/tranquillite/get_heal_material_types() return list( diff --git a/code/modules/mob/living/living_emote.dm b/code/modules/mob/living/living_emote.dm index d32ae9d90101..781ba33b4fbd 100644 --- a/code/modules/mob/living/living_emote.dm +++ b/code/modules/mob/living/living_emote.dm @@ -10,7 +10,7 @@ message_postfix = " на %t." /datum/emote/living/should_play_sound(mob/user, intentional) - if(user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) return FALSE // shh return ..() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 88f1acc57193..d5147858e780 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -782,7 +782,7 @@ /mob/mouse_drop_dragged(atom/over_object, mob/living/user, src_location, over_location, params) if(user == src || over_object != user || !HAS_TRAIT(user, TRAIT_CAN_STRIP)) - return + return if(!user.can_strip || isliving(user) && user.mob_size <= MOB_SIZE_SMALL) return // Stops pAI drones and small mobs (borers, parrots, crabs) from stripping people. --DZD if(IsFrozen(src) && !is_admin(user)) @@ -1513,3 +1513,16 @@ GLOBAL_LIST_INIT(holy_areas, typecacheof(list( /mob/compressor_grind() gib() + +/** + * 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 + + var/restrict_magic_flags = SEND_SIGNAL(src, COMSIG_MOB_RESTRICT_MAGIC, magic_flags) + return restrict_magic_flags == NONE diff --git a/code/modules/projectiles/guns/ballistic/revolvers.dm b/code/modules/projectiles/guns/ballistic/revolvers.dm index 4f007f21c085..59d5e8c33db8 100644 --- a/code/modules/projectiles/guns/ballistic/revolvers.dm +++ b/code/modules/projectiles/guns/ballistic/revolvers.dm @@ -88,7 +88,7 @@ return /obj/item/gun/projectile/revolver/fingergun/afterattack(atom/target, mob/user, proximity_flag, list/modifiers, status) - if(!user.mind?.miming) + if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) to_chat(user, span_notice("You must dedicate yourself to silence first. Use your fingers if you wish to holster them.")) return return ..() diff --git a/code/modules/reagents/chemistry/reagents/drinks.dm b/code/modules/reagents/chemistry/reagents/drinks.dm index 8ab2a89b226d..c72431d4214e 100644 --- a/code/modules/reagents/chemistry/reagents/drinks.dm +++ b/code/modules/reagents/chemistry/reagents/drinks.dm @@ -196,7 +196,7 @@ /datum/reagent/consumable/drink/nothing/on_mob_life(mob/living/M) var/update_flags = STATUS_UPDATE_NONE - if(ishuman(M) && M.mind && M.mind.miming) + if(ishuman(M) && M.mind && HAS_TRAIT(M.mind, TRAIT_MIMING)) update_flags |= M.adjustBruteLoss(-1, FALSE, affect_robotic = FALSE) update_flags |= M.adjustFireLoss(-1, FALSE, affect_robotic = FALSE) return ..() | update_flags diff --git a/paradise.dme b/paradise.dme index ec069f70fe7c..b2e996a01c37 100644 --- a/paradise.dme +++ b/paradise.dme @@ -127,6 +127,7 @@ #include "code\__DEFINES\localization.dm" #include "code\__DEFINES\logs.dm" #include "code\__DEFINES\machines.dm" +#include "code\__DEFINES\magic.dm" #include "code\__DEFINES\mappers.dm" #include "code\__DEFINES\mapping.dm" #include "code\__DEFINES\maps.dm" @@ -241,6 +242,7 @@ #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\mapping.dm" +#include "code\__DEFINES\dcs\signals\signal_actions.dm" #include "code\__DEFINES\dcs\signals\signals_alien.dm" #include "code\__DEFINES\dcs\signals\signals_area.dm" #include "code\__DEFINES\dcs\signals\signals_bingle.dm" @@ -274,6 +276,7 @@ #include "code\__DEFINES\dcs\signals\signals_reagent.dm" #include "code\__DEFINES\dcs\signals\signals_screentips.dm" #include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm" +#include "code\__DEFINES\dcs\signals\signals_spell.dm" #include "code\__DEFINES\dcs\signals\signals_strength.dm" #include "code\__DEFINES\dcs\signals\signals_subsystem.dm" #include "code\__DEFINES\dcs\signals\signals_supplypod.dm" From 21589f8584cc92a8195bed0dde525f848e6687e6 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 26 Apr 2026 20:11:01 +0300 Subject: [PATCH 02/42] =?UTF-8?q?=D0=BD=D0=B5=D0=B9=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D0=BB=D0=BE=D0=BF=20=D0=B3=D0=BE=D0=B2=D0=BE=D1=80=D0=B8=D1=82?= =?UTF-8?q?=20=D1=83=D0=BC=D0=BD=D1=8B=D0=B5=20=D0=B2=D0=B5=D1=89=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/datums/actions/spell_action.dm | 2 +- code/datums/spells/mime.dm | 4 ++-- code/modules/mob/living/carbon/human/human_say.dm | 2 +- code/modules/projectiles/guns/ballistic/revolvers.dm | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/datums/actions/spell_action.dm b/code/datums/actions/spell_action.dm index 43f7265c00c5..189636731009 100644 --- a/code/datums/actions/spell_action.dm +++ b/code/datums/actions/spell_action.dm @@ -413,7 +413,7 @@ // ) /// 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(mob/living/invoker, feedback) +/datum/action/cooldown/spell/proc/try_invoke(mob/living/invoker, feedback = TRUE) if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION) return TRUE diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm index fb1171af1598..13d2448c4025 100644 --- a/code/datums/spells/mime.dm +++ b/code/datums/spells/mime.dm @@ -55,10 +55,10 @@ return if(HAS_TRAIT(user.mind, TRAIT_MIMING)) - ADD_TRAIT(user.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(user.mind)) + REMOVE_TRAIT(user.mind, TRAIT_MIMING, "mime_vow") to_chat(target, span_notice("Вы нарушаете свой обет молчания.")) else - REMOVE_TRAIT(user.mind, TRAIT_MIMING, UNIQUE_TRAIT_SOURCE(user.mind)) + ADD_TRAIT(user.mind, TRAIT_MIMING, "mime_vow") to_chat(target, span_notice("Вы даёте обет молчания.")) /obj/effect/proc_holder/spell/mime/speak/mask diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm index 8dfa20d194fd..361d36024334 100644 --- a/code/modules/mob/living/carbon/human/human_say.dm +++ b/code/modules/mob/living/carbon/human/human_say.dm @@ -82,7 +82,7 @@ /mob/living/carbon/human/IsVocal() var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR) - if(translator?.active && (mind && HAS_TRAIT(mind, TRAIT_MIMING))) + if(translator?.active && !(mind && HAS_TRAIT(mind, TRAIT_MIMING))) return TRUE // Cyberimps don't care if you need to breathe at all, but make some respect to mimes if(HAS_TRAIT(src, TRAIT_MUTE)) diff --git a/code/modules/projectiles/guns/ballistic/revolvers.dm b/code/modules/projectiles/guns/ballistic/revolvers.dm index 59d5e8c33db8..607a57c0ca82 100644 --- a/code/modules/projectiles/guns/ballistic/revolvers.dm +++ b/code/modules/projectiles/guns/ballistic/revolvers.dm @@ -88,7 +88,7 @@ return /obj/item/gun/projectile/revolver/fingergun/afterattack(atom/target, mob/user, proximity_flag, list/modifiers, status) - if(user.mind && HAS_MIND_TRAIT(user, TRAIT_MIMING)) + if(user.mind && !HAS_MIND_TRAIT(user, TRAIT_MIMING)) to_chat(user, span_notice("You must dedicate yourself to silence first. Use your fingers if you wish to holster them.")) return return ..() From fc74c092060e6363e262c81b9be5711136baf6ef Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 26 Apr 2026 20:49:05 +0300 Subject: [PATCH 03/42] =?UTF-8?q?=D0=BC=D0=B8=D0=BA=D1=80=D0=BE=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/mob/living/carbon/human/human_say.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm index 361d36024334..07d487a34491 100644 --- a/code/modules/mob/living/carbon/human/human_say.dm +++ b/code/modules/mob/living/carbon/human/human_say.dm @@ -82,7 +82,7 @@ /mob/living/carbon/human/IsVocal() var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR) - if(translator?.active && !(mind && HAS_TRAIT(mind, TRAIT_MIMING))) + if(translator?.active && !(mind && HAS_TRAIT(mind, TRAIT_MIMING))) return TRUE // Cyberimps don't care if you need to breathe at all, but make some respect to mimes if(HAS_TRAIT(src, TRAIT_MUTE)) From 0e9cfa5b4e3e9366bb4500e49199e5eef04cff4b Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 26 Apr 2026 20:56:39 +0300 Subject: [PATCH 04/42] =?UTF-8?q?=D1=82=D1=80=D0=B5=D0=B9=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B2=20=D0=B3=D0=BB=D0=BE=D0=B1=D0=B0=D0=BB=D0=B2=D0=B0=D1=80?= =?UTF-8?q?=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/_globalvars/traits/_traits.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index dc7e9b3cd765..7785835f97ef 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -28,6 +28,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_AREA_SENSITIVE" = TRAIT_AREA_SENSITIVE, "TRAIT_ASHSTORM_IMMUNE" = TRAIT_ASHSTORM_IMMUNE, "TRAIT_BLOBSTORM_IMMUNE" = TRAIT_BLOBSTORM_IMMUNE, + "TRAIT_CASTABLE_LOC" = TRAIT_CASTABLE_LOC, "TRAIT_CHASM_IGNORED" = TRAIT_CHASM_IGNORED, "TRAIT_CHASM_STOPPER" = TRAIT_CHASM_STOPPER, "TRAIT_FACING_TO_MOUSE" = TRAIT_FACING_TO_MOUSE, @@ -48,6 +49,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, "TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE, "TRAIT_SOLARFLARE_IMMUNE" = TRAIT_SOLARFLARE_IMMUNE, + "TRAIT_SPELLS_TRANSFER_TO_LOC" = TRAIT_SPELLS_TRANSFER_TO_LOC, "TRAIT_SUPERMATTER_IMMUNE" = TRAIT_SUPERMATTER_IMMUNE, "TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE, ), @@ -136,6 +138,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_MARTIAL_ARTS_SUPPRESSED" = TRAIT_MARTIAL_ARTS_SUPPRESSED, "TRAIT_MASTER_SURGEON" = TRAIT_MASTER_SURGEON, "TRAIT_MESON_VISION" = TRAIT_MESON_VISION, + "TRAIT_MIMING" = TRAIT_MIMING, "TRAIT_MINDSHIELD_HUD" = TRAIT_MINDSHIELD_HUD, "TRAIT_MULTIZ_SUIT_SENSORS" = TRAIT_MULTIZ_SUIT_SENSORS, "TRAIT_MUTE" = TRAIT_MUTE, From 155df5b2aa34e5ff05dafbde8301e83ec9c771c4 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 26 Apr 2026 21:01:00 +0300 Subject: [PATCH 05/42] =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/datums/actions/spell_action.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/datums/actions/spell_action.dm b/code/datums/actions/spell_action.dm index 189636731009..14a6f392a8df 100644 --- a/code/datums/actions/spell_action.dm +++ b/code/datums/actions/spell_action.dm @@ -44,7 +44,6 @@ name = "Spell" desc = "A wizard spell." background_icon_state = "bg_spell" - button_icon = 'icons/mob/actions/actions.dmi' button_icon_state = "spell_default" overlay_icon_state = "bg_spell_border" active_overlay_icon_state = "bg_spell_border_active_red" From b5f8dc30eac2a857a451fc91ee5f0627223d1a0f Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 28 Apr 2026 19:20:49 +0300 Subject: [PATCH 06/42] =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BA=D0=B0=20spells?= =?UTF-8?q?=20=D0=B2=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/reagents/chemistry/reagents/drinks.dm | 2 +- .../{datums/actions/spell_action.dm => modules/spells/spell.dm} | 0 paradise.dme | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) rename code/{datums/actions/spell_action.dm => modules/spells/spell.dm} (100%) diff --git a/code/modules/reagents/chemistry/reagents/drinks.dm b/code/modules/reagents/chemistry/reagents/drinks.dm index c72431d4214e..d36e04dd0d6b 100644 --- a/code/modules/reagents/chemistry/reagents/drinks.dm +++ b/code/modules/reagents/chemistry/reagents/drinks.dm @@ -196,7 +196,7 @@ /datum/reagent/consumable/drink/nothing/on_mob_life(mob/living/M) var/update_flags = STATUS_UPDATE_NONE - if(ishuman(M) && M.mind && HAS_TRAIT(M.mind, TRAIT_MIMING)) + if(ishuman(M) && M.mind && HAS_MIND_TRAIT(M, TRAIT_MIMING)) update_flags |= M.adjustBruteLoss(-1, FALSE, affect_robotic = FALSE) update_flags |= M.adjustFireLoss(-1, FALSE, affect_robotic = FALSE) return ..() | update_flags diff --git a/code/datums/actions/spell_action.dm b/code/modules/spells/spell.dm similarity index 100% rename from code/datums/actions/spell_action.dm rename to code/modules/spells/spell.dm diff --git a/paradise.dme b/paradise.dme index b132b6cc4c2a..8b214dfc191f 100644 --- a/paradise.dme +++ b/paradise.dme @@ -666,7 +666,6 @@ #include "code\datums\actions\generic_action.dm" #include "code\datums\actions\innate_action.dm" #include "code\datums\actions\item_action.dm" -#include "code\datums\actions\spell_action.dm" #include "code\datums\ai\_ai_behavior.dm" #include "code\datums\ai\_ai_controller.dm" #include "code\datums\ai\_ai_planning_subtree.dm" From 5f3f06817812352a8704b2eb9d45cadd3b528f86 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 28 Apr 2026 19:26:17 +0300 Subject: [PATCH 07/42] =?UTF-8?q?=D0=B7=D0=B0=D0=B1=D1=8B=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paradise.dme | 1 + 1 file changed, 1 insertion(+) diff --git a/paradise.dme b/paradise.dme index 8b214dfc191f..98b62c26f88d 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3876,6 +3876,7 @@ #include "code\modules\spacepods\parts.dm" #include "code\modules\spacepods\spacepod.dm" #include "code\modules\spatial_grid\cell_tracker.dm" +#include "code\modules\spells\spell.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" From 97edf40c6983837bdb8ad2813b1f103bd2b903a2 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 28 Apr 2026 19:28:32 +0300 Subject: [PATCH 08/42] =?UTF-8?q?=D0=B3=D0=B5=D0=BD=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D0=B9=D0=B7=5F=D0=B4=D0=B5=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/spells/spell.dm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 14a6f392a8df..979e942792e4 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -403,13 +403,13 @@ else invoker.whisper(used_invocation_message) -// need localization -// if(INVOCATION_EMOTE) -// invoker.visible_message( -// capitalize(REPLACE_PRONOUNS(replacetext(used_invocation_message, "%CASTER", invoker.name), invoker)), -// capitalize(REPLACE_PRONOUNS(replacetext(invocation_self_message, "%CASTER", invoker.name), invoker)), -// visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, -// ) + if(INVOCATION_EMOTE) + invoker.visible_message( + capitalize(genderize_decode(invoker, replacetext(used_invocation_message, "%CASTER", invoker.name))), + capitalize(genderize_decode(invoker, replacetext(invocation_self_message, "%CASTER", invoker.name))), + visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, + ) + /// 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(mob/living/invoker, feedback = TRUE) From 23a5a2028f3cfabf3f433180a78ebec97b9f6004 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 28 Apr 2026 20:19:20 +0300 Subject: [PATCH 09/42] =?UTF-8?q?=D0=B2=D0=B5=D0=BB=D0=B8=D0=BA=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D0=B5=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BD=D0=B0=D1=80=D0=BE=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spells/cooldown}/spell_charges.dm | 0 .../spells/cooldown}/spell_cooldown.dm | 0 .../spells/handlers}/alien_spell_handler.dm | 0 .../spells/handlers}/devil_spell_handler.dm | 0 .../spells/handlers}/morph_spell_handler.dm | 0 .../spells/handlers}/spell_handler.dm | 0 .../spells/handlers}/vampire_spell_handler.dm | 0 .../spells/targeting}/alive_mobs.dm | 0 .../spells/targeting}/aoe.dm | 0 .../targeting}/click_spell_targeting.dm | 0 .../spells/targeting}/clicked_atom.dm | 0 .../targeting}/matter_eater_targeting.dm | 0 .../spells/targeting}/reachable_turfs.dm | 0 .../spells/targeting}/remoteview_targeting.dm | 0 .../spells/targeting}/self_targeting.dm | 0 .../spells/targeting}/spell_targeting.dm | 0 .../spells/targeting}/spiral_targeting.dm | 0 .../spells/targeting}/targeted.dm | 0 .../spells/targeting}/telepathic.dm | 0 paradise.dme | 38 +++++++++---------- 20 files changed, 19 insertions(+), 19 deletions(-) rename code/{datums/spell_cooldown => modules/spells/cooldown}/spell_charges.dm (100%) rename code/{datums/spell_cooldown => modules/spells/cooldown}/spell_cooldown.dm (100%) rename code/{datums/spell_handler => modules/spells/handlers}/alien_spell_handler.dm (100%) rename code/{datums/spell_handler => modules/spells/handlers}/devil_spell_handler.dm (100%) rename code/{datums/spell_handler => modules/spells/handlers}/morph_spell_handler.dm (100%) rename code/{datums/spell_handler => modules/spells/handlers}/spell_handler.dm (100%) rename code/{datums/spell_handler => modules/spells/handlers}/vampire_spell_handler.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/alive_mobs.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/aoe.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/click_spell_targeting.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/clicked_atom.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/matter_eater_targeting.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/reachable_turfs.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/remoteview_targeting.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/self_targeting.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/spell_targeting.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/spiral_targeting.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/targeted.dm (100%) rename code/{datums/spell_targeting => modules/spells/targeting}/telepathic.dm (100%) diff --git a/code/datums/spell_cooldown/spell_charges.dm b/code/modules/spells/cooldown/spell_charges.dm similarity index 100% rename from code/datums/spell_cooldown/spell_charges.dm rename to code/modules/spells/cooldown/spell_charges.dm diff --git a/code/datums/spell_cooldown/spell_cooldown.dm b/code/modules/spells/cooldown/spell_cooldown.dm similarity index 100% rename from code/datums/spell_cooldown/spell_cooldown.dm rename to code/modules/spells/cooldown/spell_cooldown.dm diff --git a/code/datums/spell_handler/alien_spell_handler.dm b/code/modules/spells/handlers/alien_spell_handler.dm similarity index 100% rename from code/datums/spell_handler/alien_spell_handler.dm rename to code/modules/spells/handlers/alien_spell_handler.dm diff --git a/code/datums/spell_handler/devil_spell_handler.dm b/code/modules/spells/handlers/devil_spell_handler.dm similarity index 100% rename from code/datums/spell_handler/devil_spell_handler.dm rename to code/modules/spells/handlers/devil_spell_handler.dm diff --git a/code/datums/spell_handler/morph_spell_handler.dm b/code/modules/spells/handlers/morph_spell_handler.dm similarity index 100% rename from code/datums/spell_handler/morph_spell_handler.dm rename to code/modules/spells/handlers/morph_spell_handler.dm diff --git a/code/datums/spell_handler/spell_handler.dm b/code/modules/spells/handlers/spell_handler.dm similarity index 100% rename from code/datums/spell_handler/spell_handler.dm rename to code/modules/spells/handlers/spell_handler.dm diff --git a/code/datums/spell_handler/vampire_spell_handler.dm b/code/modules/spells/handlers/vampire_spell_handler.dm similarity index 100% rename from code/datums/spell_handler/vampire_spell_handler.dm rename to code/modules/spells/handlers/vampire_spell_handler.dm diff --git a/code/datums/spell_targeting/alive_mobs.dm b/code/modules/spells/targeting/alive_mobs.dm similarity index 100% rename from code/datums/spell_targeting/alive_mobs.dm rename to code/modules/spells/targeting/alive_mobs.dm diff --git a/code/datums/spell_targeting/aoe.dm b/code/modules/spells/targeting/aoe.dm similarity index 100% rename from code/datums/spell_targeting/aoe.dm rename to code/modules/spells/targeting/aoe.dm diff --git a/code/datums/spell_targeting/click_spell_targeting.dm b/code/modules/spells/targeting/click_spell_targeting.dm similarity index 100% rename from code/datums/spell_targeting/click_spell_targeting.dm rename to code/modules/spells/targeting/click_spell_targeting.dm diff --git a/code/datums/spell_targeting/clicked_atom.dm b/code/modules/spells/targeting/clicked_atom.dm similarity index 100% rename from code/datums/spell_targeting/clicked_atom.dm rename to code/modules/spells/targeting/clicked_atom.dm diff --git a/code/datums/spell_targeting/matter_eater_targeting.dm b/code/modules/spells/targeting/matter_eater_targeting.dm similarity index 100% rename from code/datums/spell_targeting/matter_eater_targeting.dm rename to code/modules/spells/targeting/matter_eater_targeting.dm diff --git a/code/datums/spell_targeting/reachable_turfs.dm b/code/modules/spells/targeting/reachable_turfs.dm similarity index 100% rename from code/datums/spell_targeting/reachable_turfs.dm rename to code/modules/spells/targeting/reachable_turfs.dm diff --git a/code/datums/spell_targeting/remoteview_targeting.dm b/code/modules/spells/targeting/remoteview_targeting.dm similarity index 100% rename from code/datums/spell_targeting/remoteview_targeting.dm rename to code/modules/spells/targeting/remoteview_targeting.dm diff --git a/code/datums/spell_targeting/self_targeting.dm b/code/modules/spells/targeting/self_targeting.dm similarity index 100% rename from code/datums/spell_targeting/self_targeting.dm rename to code/modules/spells/targeting/self_targeting.dm diff --git a/code/datums/spell_targeting/spell_targeting.dm b/code/modules/spells/targeting/spell_targeting.dm similarity index 100% rename from code/datums/spell_targeting/spell_targeting.dm rename to code/modules/spells/targeting/spell_targeting.dm diff --git a/code/datums/spell_targeting/spiral_targeting.dm b/code/modules/spells/targeting/spiral_targeting.dm similarity index 100% rename from code/datums/spell_targeting/spiral_targeting.dm rename to code/modules/spells/targeting/spiral_targeting.dm diff --git a/code/datums/spell_targeting/targeted.dm b/code/modules/spells/targeting/targeted.dm similarity index 100% rename from code/datums/spell_targeting/targeted.dm rename to code/modules/spells/targeting/targeted.dm diff --git a/code/datums/spell_targeting/telepathic.dm b/code/modules/spells/targeting/telepathic.dm similarity index 100% rename from code/datums/spell_targeting/telepathic.dm rename to code/modules/spells/targeting/telepathic.dm diff --git a/paradise.dme b/paradise.dme index 98b62c26f88d..9799950949e2 100644 --- a/paradise.dme +++ b/paradise.dme @@ -992,25 +992,6 @@ #include "code\datums\skins\specter_skins.dm" #include "code\datums\skins\taurus_skins.dm" #include "code\datums\skins\telescopic_baton_skins.dm" -#include "code\datums\spell_cooldown\spell_charges.dm" -#include "code\datums\spell_cooldown\spell_cooldown.dm" -#include "code\datums\spell_handler\alien_spell_handler.dm" -#include "code\datums\spell_handler\devil_spell_handler.dm" -#include "code\datums\spell_handler\morph_spell_handler.dm" -#include "code\datums\spell_handler\spell_handler.dm" -#include "code\datums\spell_handler\vampire_spell_handler.dm" -#include "code\datums\spell_targeting\alive_mobs.dm" -#include "code\datums\spell_targeting\aoe.dm" -#include "code\datums\spell_targeting\click_spell_targeting.dm" -#include "code\datums\spell_targeting\clicked_atom.dm" -#include "code\datums\spell_targeting\matter_eater_targeting.dm" -#include "code\datums\spell_targeting\reachable_turfs.dm" -#include "code\datums\spell_targeting\remoteview_targeting.dm" -#include "code\datums\spell_targeting\self_targeting.dm" -#include "code\datums\spell_targeting\spell_targeting.dm" -#include "code\datums\spell_targeting\spiral_targeting.dm" -#include "code\datums\spell_targeting\targeted.dm" -#include "code\datums\spell_targeting\telepathic.dm" #include "code\datums\spells\area_teleport.dm" #include "code\datums\spells\banana_touch.dm" #include "code\datums\spells\bloodcrawl.dm" @@ -3877,6 +3858,25 @@ #include "code\modules\spacepods\spacepod.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" +#include "code\modules\spells\cooldown\spell_charges.dm" +#include "code\modules\spells\cooldown\spell_cooldown.dm" +#include "code\modules\spells\handlers\alien_spell_handler.dm" +#include "code\modules\spells\handlers\devil_spell_handler.dm" +#include "code\modules\spells\handlers\morph_spell_handler.dm" +#include "code\modules\spells\handlers\spell_handler.dm" +#include "code\modules\spells\handlers\vampire_spell_handler.dm" +#include "code\modules\spells\targeting\alive_mobs.dm" +#include "code\modules\spells\targeting\aoe.dm" +#include "code\modules\spells\targeting\click_spell_targeting.dm" +#include "code\modules\spells\targeting\clicked_atom.dm" +#include "code\modules\spells\targeting\matter_eater_targeting.dm" +#include "code\modules\spells\targeting\reachable_turfs.dm" +#include "code\modules\spells\targeting\remoteview_targeting.dm" +#include "code\modules\spells\targeting\self_targeting.dm" +#include "code\modules\spells\targeting\spell_targeting.dm" +#include "code\modules\spells\targeting\spiral_targeting.dm" +#include "code\modules\spells\targeting\targeted.dm" +#include "code\modules\spells\targeting\telepathic.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" From 67e8e8ec4fc5b50bede5e6153503306499bb8601 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Fri, 1 May 2026 19:27:36 +0300 Subject: [PATCH 10/42] =?UTF-8?q?=D0=B2=D1=80=D0=BE=D0=B4=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BA=D1=83=D0=BB=D0=B4?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=20=D1=85=D0=B5=D0=BD=D0=B4=D0=BB=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/spells/spell.dm | 65 ++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 979e942792e4..5da2ea23891c 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -85,6 +85,18 @@ 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 + /// Which targeting system is used. Set this in create_new_targeting + var/datum/spell_targeting/targeting + /// List with the targeting datums per spell type. Key = src.type, value = the targeting datum created by create_new_targeting() + var/static/list/targeting_datums = list() + /// Which spell_handler is used in addition to the normal spells behaviour, can be null. Set this in create_new_handler if needed + var/datum/spell_handler/custom_handler + /// List with the handler datums per spell type. Key = src.type, value = the handler datum created by create_new_handler() + var/static/list/spell_handlers = list() + /// Handles a given spells cooldowns. Tracks the time until its off cooldown. + var/datum/spell_cooldown/cooldown_handler + var/starts_charged = TRUE + var/still_recharging_msg = span_notice_alt("The spell is still recharging.") /datum/action/cooldown/spell/Grant(mob/grant_to) // If our spell is mind-bound, we only wanna grant it to our mind @@ -108,6 +120,16 @@ RegisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal)) RegisterSignal(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) + if(!targeting_datums[type]) + targeting_datums[type] = create_new_targeting() + if(!targeting_datums[type]) + stack_trace("Spell of type [type] did not implement create_new_targeting") + if(isnull(spell_handlers[type])) + spell_handlers[type] = create_new_handler() + if(spell_handlers[type] != NONE) + custom_handler = spell_handlers[type] + cooldown_handler = create_new_cooldown() + cooldown_handler.cooldown_init(src) owner.client?.stat_panel.send_message("check_spells") /datum/action/cooldown/spell/Remove(mob/living/remove_from) @@ -124,6 +146,8 @@ SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE), )) + QDEL_NULL(cooldown_handler) + QDEL_NULL(custom_handler) return ..() @@ -149,7 +173,7 @@ /// 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) +/datum/action/cooldown/spell/proc/can_cast_spell(feedback = TRUE, charge_check = TRUE) if(!owner) CRASH("[type] - can_cast_spell called on a spell without an owner!") @@ -181,6 +205,12 @@ if(!try_invoke(owner, feedback = feedback)) return FALSE + if(charge_check) + if(cooldown_handler.is_on_cooldown()) + if(feedback) + to_chat(owner, still_recharging_msg) + return FALSE + if(ishuman(owner)) if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) var/mob/living/carbon/human/human_owner = owner @@ -273,6 +303,8 @@ // Actually cast the spell. Main effects go here cast(cast_on) + if(cooldown_time != 0) + cooldown_handler.start_recharge() if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) // The entire spell is done, start the actual cooldown at its set duration @@ -447,7 +479,8 @@ /// 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 + cooldown_handler.revert_cast() + custom_handler?.revert_cast(owner, src) // Basically, ensures that the ability can be used now build_all_button_icons() /** @@ -506,6 +539,34 @@ return "" +/** + * Creates and returns the targeting datum for this spell type. Override this! + * Should return a value of type [/datum/spell_targeting] + */ +/datum/action/cooldown/spell/proc/create_new_targeting() + RETURN_TYPE(/datum/spell_targeting) + return + +/** + * Creates and returns the handler datum for this spell type. + * Override this if you want a custom spell handler. + * Should return a value of type [/datum/spell_handler] or NONE + */ +/datum/action/cooldown/spell/proc/create_new_handler() + RETURN_TYPE(/datum/spell_handler) + return NONE + +/** + * Creates and returns the spells cooldown handler, defaults to the standard recharge handler. + * Override this if you wish to use a different method of cooldown + */ +/datum/action/cooldown/spell/proc/create_new_cooldown() + RETURN_TYPE(/datum/spell_cooldown) + var/datum/spell_cooldown/s_cooldown = new + s_cooldown.recharge_duration = cooldown_time + s_cooldown.starts_off_cooldown = starts_charged + return s_cooldown + // Legacy shit /datum/action/spell_action From 05493be92fb65b8e2369959246bcaab8c7e07ab1 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 3 May 2026 00:37:21 +0300 Subject: [PATCH 11/42] =?UTF-8?q?Revert=20"=D0=B2=D1=80=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BA=D1=83?= =?UTF-8?q?=D0=BB=D0=B4=D0=BE=D0=B2=D0=BD=20=D1=85=D0=B5=D0=BD=D0=B4=D0=BB?= =?UTF-8?q?=D0=B5=D1=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 67e8e8ec4fc5b50bede5e6153503306499bb8601. --- code/modules/spells/spell.dm | 65 ++---------------------------------- 1 file changed, 2 insertions(+), 63 deletions(-) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 5da2ea23891c..979e942792e4 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -85,18 +85,6 @@ 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 - /// Which targeting system is used. Set this in create_new_targeting - var/datum/spell_targeting/targeting - /// List with the targeting datums per spell type. Key = src.type, value = the targeting datum created by create_new_targeting() - var/static/list/targeting_datums = list() - /// Which spell_handler is used in addition to the normal spells behaviour, can be null. Set this in create_new_handler if needed - var/datum/spell_handler/custom_handler - /// List with the handler datums per spell type. Key = src.type, value = the handler datum created by create_new_handler() - var/static/list/spell_handlers = list() - /// Handles a given spells cooldowns. Tracks the time until its off cooldown. - var/datum/spell_cooldown/cooldown_handler - var/starts_charged = TRUE - var/still_recharging_msg = span_notice_alt("The spell is still recharging.") /datum/action/cooldown/spell/Grant(mob/grant_to) // If our spell is mind-bound, we only wanna grant it to our mind @@ -120,16 +108,6 @@ RegisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal)) RegisterSignal(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) - if(!targeting_datums[type]) - targeting_datums[type] = create_new_targeting() - if(!targeting_datums[type]) - stack_trace("Spell of type [type] did not implement create_new_targeting") - if(isnull(spell_handlers[type])) - spell_handlers[type] = create_new_handler() - if(spell_handlers[type] != NONE) - custom_handler = spell_handlers[type] - cooldown_handler = create_new_cooldown() - cooldown_handler.cooldown_init(src) owner.client?.stat_panel.send_message("check_spells") /datum/action/cooldown/spell/Remove(mob/living/remove_from) @@ -146,8 +124,6 @@ SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE), )) - QDEL_NULL(cooldown_handler) - QDEL_NULL(custom_handler) return ..() @@ -173,7 +149,7 @@ /// 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, charge_check = TRUE) +/datum/action/cooldown/spell/proc/can_cast_spell(feedback = TRUE) if(!owner) CRASH("[type] - can_cast_spell called on a spell without an owner!") @@ -205,12 +181,6 @@ if(!try_invoke(owner, feedback = feedback)) return FALSE - if(charge_check) - if(cooldown_handler.is_on_cooldown()) - if(feedback) - to_chat(owner, still_recharging_msg) - return FALSE - if(ishuman(owner)) if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) var/mob/living/carbon/human/human_owner = owner @@ -303,8 +273,6 @@ // Actually cast the spell. Main effects go here cast(cast_on) - if(cooldown_time != 0) - cooldown_handler.start_recharge() if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) // The entire spell is done, start the actual cooldown at its set duration @@ -479,8 +447,7 @@ /// 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) - cooldown_handler.revert_cast() - custom_handler?.revert_cast(owner, src) // Basically, ensures that the ability can be used now + next_use_time -= cooldown_time // Basically, ensures that the ability can be used now build_all_button_icons() /** @@ -539,34 +506,6 @@ return "" -/** - * Creates and returns the targeting datum for this spell type. Override this! - * Should return a value of type [/datum/spell_targeting] - */ -/datum/action/cooldown/spell/proc/create_new_targeting() - RETURN_TYPE(/datum/spell_targeting) - return - -/** - * Creates and returns the handler datum for this spell type. - * Override this if you want a custom spell handler. - * Should return a value of type [/datum/spell_handler] or NONE - */ -/datum/action/cooldown/spell/proc/create_new_handler() - RETURN_TYPE(/datum/spell_handler) - return NONE - -/** - * Creates and returns the spells cooldown handler, defaults to the standard recharge handler. - * Override this if you wish to use a different method of cooldown - */ -/datum/action/cooldown/spell/proc/create_new_cooldown() - RETURN_TYPE(/datum/spell_cooldown) - var/datum/spell_cooldown/s_cooldown = new - s_cooldown.recharge_duration = cooldown_time - s_cooldown.starts_off_cooldown = starts_charged - return s_cooldown - // Legacy shit /datum/action/spell_action From b83cdebe981d3d7404ff1066142795c76eec79ae Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 3 May 2026 00:37:28 +0300 Subject: [PATCH 12/42] =?UTF-8?q?Revert=20"=D0=B2=D0=B5=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=D1=80=D0=BE=D0=B4=D0=BE=D0=B2?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 23a5a2028f3cfabf3f433180a78ebec97b9f6004. --- .../spell_cooldown}/spell_charges.dm | 0 .../spell_cooldown}/spell_cooldown.dm | 0 .../spell_handler}/alien_spell_handler.dm | 0 .../spell_handler}/devil_spell_handler.dm | 0 .../spell_handler}/morph_spell_handler.dm | 0 .../spell_handler}/spell_handler.dm | 0 .../spell_handler}/vampire_spell_handler.dm | 0 .../spell_targeting}/alive_mobs.dm | 0 .../spell_targeting}/aoe.dm | 0 .../spell_targeting}/click_spell_targeting.dm | 0 .../spell_targeting}/clicked_atom.dm | 0 .../matter_eater_targeting.dm | 0 .../spell_targeting}/reachable_turfs.dm | 0 .../spell_targeting}/remoteview_targeting.dm | 0 .../spell_targeting}/self_targeting.dm | 0 .../spell_targeting}/spell_targeting.dm | 0 .../spell_targeting}/spiral_targeting.dm | 0 .../spell_targeting}/targeted.dm | 0 .../spell_targeting}/telepathic.dm | 0 paradise.dme | 38 +++++++++---------- 20 files changed, 19 insertions(+), 19 deletions(-) rename code/{modules/spells/cooldown => datums/spell_cooldown}/spell_charges.dm (100%) rename code/{modules/spells/cooldown => datums/spell_cooldown}/spell_cooldown.dm (100%) rename code/{modules/spells/handlers => datums/spell_handler}/alien_spell_handler.dm (100%) rename code/{modules/spells/handlers => datums/spell_handler}/devil_spell_handler.dm (100%) rename code/{modules/spells/handlers => datums/spell_handler}/morph_spell_handler.dm (100%) rename code/{modules/spells/handlers => datums/spell_handler}/spell_handler.dm (100%) rename code/{modules/spells/handlers => datums/spell_handler}/vampire_spell_handler.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/alive_mobs.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/aoe.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/click_spell_targeting.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/clicked_atom.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/matter_eater_targeting.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/reachable_turfs.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/remoteview_targeting.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/self_targeting.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/spell_targeting.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/spiral_targeting.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/targeted.dm (100%) rename code/{modules/spells/targeting => datums/spell_targeting}/telepathic.dm (100%) diff --git a/code/modules/spells/cooldown/spell_charges.dm b/code/datums/spell_cooldown/spell_charges.dm similarity index 100% rename from code/modules/spells/cooldown/spell_charges.dm rename to code/datums/spell_cooldown/spell_charges.dm diff --git a/code/modules/spells/cooldown/spell_cooldown.dm b/code/datums/spell_cooldown/spell_cooldown.dm similarity index 100% rename from code/modules/spells/cooldown/spell_cooldown.dm rename to code/datums/spell_cooldown/spell_cooldown.dm diff --git a/code/modules/spells/handlers/alien_spell_handler.dm b/code/datums/spell_handler/alien_spell_handler.dm similarity index 100% rename from code/modules/spells/handlers/alien_spell_handler.dm rename to code/datums/spell_handler/alien_spell_handler.dm diff --git a/code/modules/spells/handlers/devil_spell_handler.dm b/code/datums/spell_handler/devil_spell_handler.dm similarity index 100% rename from code/modules/spells/handlers/devil_spell_handler.dm rename to code/datums/spell_handler/devil_spell_handler.dm diff --git a/code/modules/spells/handlers/morph_spell_handler.dm b/code/datums/spell_handler/morph_spell_handler.dm similarity index 100% rename from code/modules/spells/handlers/morph_spell_handler.dm rename to code/datums/spell_handler/morph_spell_handler.dm diff --git a/code/modules/spells/handlers/spell_handler.dm b/code/datums/spell_handler/spell_handler.dm similarity index 100% rename from code/modules/spells/handlers/spell_handler.dm rename to code/datums/spell_handler/spell_handler.dm diff --git a/code/modules/spells/handlers/vampire_spell_handler.dm b/code/datums/spell_handler/vampire_spell_handler.dm similarity index 100% rename from code/modules/spells/handlers/vampire_spell_handler.dm rename to code/datums/spell_handler/vampire_spell_handler.dm diff --git a/code/modules/spells/targeting/alive_mobs.dm b/code/datums/spell_targeting/alive_mobs.dm similarity index 100% rename from code/modules/spells/targeting/alive_mobs.dm rename to code/datums/spell_targeting/alive_mobs.dm diff --git a/code/modules/spells/targeting/aoe.dm b/code/datums/spell_targeting/aoe.dm similarity index 100% rename from code/modules/spells/targeting/aoe.dm rename to code/datums/spell_targeting/aoe.dm diff --git a/code/modules/spells/targeting/click_spell_targeting.dm b/code/datums/spell_targeting/click_spell_targeting.dm similarity index 100% rename from code/modules/spells/targeting/click_spell_targeting.dm rename to code/datums/spell_targeting/click_spell_targeting.dm diff --git a/code/modules/spells/targeting/clicked_atom.dm b/code/datums/spell_targeting/clicked_atom.dm similarity index 100% rename from code/modules/spells/targeting/clicked_atom.dm rename to code/datums/spell_targeting/clicked_atom.dm diff --git a/code/modules/spells/targeting/matter_eater_targeting.dm b/code/datums/spell_targeting/matter_eater_targeting.dm similarity index 100% rename from code/modules/spells/targeting/matter_eater_targeting.dm rename to code/datums/spell_targeting/matter_eater_targeting.dm diff --git a/code/modules/spells/targeting/reachable_turfs.dm b/code/datums/spell_targeting/reachable_turfs.dm similarity index 100% rename from code/modules/spells/targeting/reachable_turfs.dm rename to code/datums/spell_targeting/reachable_turfs.dm diff --git a/code/modules/spells/targeting/remoteview_targeting.dm b/code/datums/spell_targeting/remoteview_targeting.dm similarity index 100% rename from code/modules/spells/targeting/remoteview_targeting.dm rename to code/datums/spell_targeting/remoteview_targeting.dm diff --git a/code/modules/spells/targeting/self_targeting.dm b/code/datums/spell_targeting/self_targeting.dm similarity index 100% rename from code/modules/spells/targeting/self_targeting.dm rename to code/datums/spell_targeting/self_targeting.dm diff --git a/code/modules/spells/targeting/spell_targeting.dm b/code/datums/spell_targeting/spell_targeting.dm similarity index 100% rename from code/modules/spells/targeting/spell_targeting.dm rename to code/datums/spell_targeting/spell_targeting.dm diff --git a/code/modules/spells/targeting/spiral_targeting.dm b/code/datums/spell_targeting/spiral_targeting.dm similarity index 100% rename from code/modules/spells/targeting/spiral_targeting.dm rename to code/datums/spell_targeting/spiral_targeting.dm diff --git a/code/modules/spells/targeting/targeted.dm b/code/datums/spell_targeting/targeted.dm similarity index 100% rename from code/modules/spells/targeting/targeted.dm rename to code/datums/spell_targeting/targeted.dm diff --git a/code/modules/spells/targeting/telepathic.dm b/code/datums/spell_targeting/telepathic.dm similarity index 100% rename from code/modules/spells/targeting/telepathic.dm rename to code/datums/spell_targeting/telepathic.dm diff --git a/paradise.dme b/paradise.dme index ca0e19e9959a..7faf8d89e4fd 100644 --- a/paradise.dme +++ b/paradise.dme @@ -992,6 +992,25 @@ #include "code\datums\skins\specter_skins.dm" #include "code\datums\skins\taurus_skins.dm" #include "code\datums\skins\telescopic_baton_skins.dm" +#include "code\datums\spell_cooldown\spell_charges.dm" +#include "code\datums\spell_cooldown\spell_cooldown.dm" +#include "code\datums\spell_handler\alien_spell_handler.dm" +#include "code\datums\spell_handler\devil_spell_handler.dm" +#include "code\datums\spell_handler\morph_spell_handler.dm" +#include "code\datums\spell_handler\spell_handler.dm" +#include "code\datums\spell_handler\vampire_spell_handler.dm" +#include "code\datums\spell_targeting\alive_mobs.dm" +#include "code\datums\spell_targeting\aoe.dm" +#include "code\datums\spell_targeting\click_spell_targeting.dm" +#include "code\datums\spell_targeting\clicked_atom.dm" +#include "code\datums\spell_targeting\matter_eater_targeting.dm" +#include "code\datums\spell_targeting\reachable_turfs.dm" +#include "code\datums\spell_targeting\remoteview_targeting.dm" +#include "code\datums\spell_targeting\self_targeting.dm" +#include "code\datums\spell_targeting\spell_targeting.dm" +#include "code\datums\spell_targeting\spiral_targeting.dm" +#include "code\datums\spell_targeting\targeted.dm" +#include "code\datums\spell_targeting\telepathic.dm" #include "code\datums\spells\area_teleport.dm" #include "code\datums\spells\banana_touch.dm" #include "code\datums\spells\bloodcrawl.dm" @@ -3859,25 +3878,6 @@ #include "code\modules\spacepods\spacepod.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" -#include "code\modules\spells\cooldown\spell_charges.dm" -#include "code\modules\spells\cooldown\spell_cooldown.dm" -#include "code\modules\spells\handlers\alien_spell_handler.dm" -#include "code\modules\spells\handlers\devil_spell_handler.dm" -#include "code\modules\spells\handlers\morph_spell_handler.dm" -#include "code\modules\spells\handlers\spell_handler.dm" -#include "code\modules\spells\handlers\vampire_spell_handler.dm" -#include "code\modules\spells\targeting\alive_mobs.dm" -#include "code\modules\spells\targeting\aoe.dm" -#include "code\modules\spells\targeting\click_spell_targeting.dm" -#include "code\modules\spells\targeting\clicked_atom.dm" -#include "code\modules\spells\targeting\matter_eater_targeting.dm" -#include "code\modules\spells\targeting\reachable_turfs.dm" -#include "code\modules\spells\targeting\remoteview_targeting.dm" -#include "code\modules\spells\targeting\self_targeting.dm" -#include "code\modules\spells\targeting\spell_targeting.dm" -#include "code\modules\spells\targeting\spiral_targeting.dm" -#include "code\modules\spells\targeting\targeted.dm" -#include "code\modules\spells\targeting\telepathic.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" From 1d68adb5e5eaf1f159f98151d50e5bda5dcfebc5 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Mon, 11 May 2026 02:25:12 +0300 Subject: [PATCH 13/42] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D1=85=20=D1=82?= =?UTF-8?q?=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=B7=D0=B0=D0=BA=D0=BB=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B9=20=D0=B8=D0=B7=20spell=5Ftypes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавил папку spell_types и все базовые типы спеллов оттуда --- code/__DEFINES/action.dm | 6 +- .../signals/signals_mob/signals_mob_living.dm | 10 + .../signals/signals_mob/signals_mob_main.dm | 3 + code/game/area/areas.dm | 28 ++ code/game/objects/effects/phased_mob.dm | 126 ++++++ code/modules/mob/living/damage_procs.dm | 4 + code/modules/mob/mob_transformation_simple.dm | 3 + .../projectiles/projectile/special/magic.dm | 287 +++++++------- .../spell_types/aoe_spell/_aoe_spell.dm | 73 ++++ .../spells/spell_types/charged/_charged.dm | 165 ++++++++ code/modules/spells/spell_types/cone/_cone.dm | 134 +++++++ .../spells/spell_types/conjure/_conjure.dm | 87 +++++ .../spell_types/conjure_item/_conjure_item.dm | 88 +++++ .../spells/spell_types/jaunt/_jaunt.dm | 123 ++++++ .../spell_types/list_target/_list_target.dm | 41 ++ .../spells/spell_types/pointed/_pointed.dm | 186 +++++++++ .../proejctile/_basic_projectile.dm | 35 ++ .../spells/spell_types}/rightandwrong.dm | 0 .../spell_types/shapeshift/_shape_status.dm | 259 +++++++++++++ .../spell_types/shapeshift/_shapeshift.dm | 210 ++++++++++ .../spells/spell_types/teleport/_teleport.dm | 145 +++++++ .../spells/spell_types/touch/_touch.dm | 361 ++++++++++++++++++ paradise.dme | 16 +- sound/misc/reee.ogg | Bin 0 -> 34645 bytes 24 files changed, 2246 insertions(+), 144 deletions(-) create mode 100644 code/game/objects/effects/phased_mob.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm create mode 100644 code/modules/spells/spell_types/charged/_charged.dm create mode 100644 code/modules/spells/spell_types/cone/_cone.dm create mode 100644 code/modules/spells/spell_types/conjure/_conjure.dm create mode 100644 code/modules/spells/spell_types/conjure_item/_conjure_item.dm create mode 100644 code/modules/spells/spell_types/jaunt/_jaunt.dm create mode 100644 code/modules/spells/spell_types/list_target/_list_target.dm create mode 100644 code/modules/spells/spell_types/pointed/_pointed.dm create mode 100644 code/modules/spells/spell_types/proejctile/_basic_projectile.dm rename code/{game/gamemodes/wizard => modules/spells/spell_types}/rightandwrong.dm (100%) create mode 100644 code/modules/spells/spell_types/shapeshift/_shape_status.dm create mode 100644 code/modules/spells/spell_types/shapeshift/_shapeshift.dm create mode 100644 code/modules/spells/spell_types/teleport/_teleport.dm create mode 100644 code/modules/spells/spell_types/touch/_touch.dm create mode 100644 sound/misc/reee.ogg diff --git a/code/__DEFINES/action.dm b/code/__DEFINES/action.dm index b0e519579f5d..684b9027fd9b 100644 --- a/code/__DEFINES/action.dm +++ b/code/__DEFINES/action.dm @@ -9,10 +9,12 @@ #define AB_CHECK_CONSCIOUS (1<<3) ///Action button checks if user is incapacitated (weakened/stunned/stamcrited/paralyzed/sleeping) #define AB_CHECK_INCAPACITATED (1<<4) +///Action button checks if user is jaunting +#define AB_CHECK_PHASED (1<<5) ///Action button checks if user is currently in turf contents -#define AB_CHECK_TURF (1<<5) +#define AB_CHECK_TURF (1<<6) ///Action button checks if it should be given to new mob, after mind transfer -#define AB_TRANSFER_MIND (1<<6) +#define AB_TRANSFER_MIND (1<<7) ///Action button triggered with right click #define TRIGGER_SECONDARY_ACTION (1<<0) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index e7922e9ffa82..6c1995a9a8bf 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -152,3 +152,13 @@ ///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment #define COMSIG_LIVING_ATTACH_LIMB "living_attach_limb" #define COMPONENT_NO_ATTACH (1<<0) +///From /proc/wabbajack(): (randomize_type) +#define COMSIG_LIVING_PRE_WABBAJACKED "living_mob_wabbajacked" + /// Return to stop the rest of the wabbajack from triggering. + #define STOP_WABBAJACK (1<<0) +///From mob/living/proc/on_wabbajack(): (mob/living/new_mob) +#define COMSIG_LIVING_ON_WABBAJACKED "living_wabbajacked" +/// From /datum/status_effect/shapechange_mob/on_apply(): (mob/living/shape) +#define COMSIG_LIVING_SHAPESHIFTED "living_shapeshifted" +/// From /datum/status_effect/shapechange_mob/after_unchange(): (mob/living/caster) +#define COMSIG_LIVING_UNSHAPESHIFTED "living_unshapeshifted" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 492e98c37168..4092ac04a689 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -205,6 +205,9 @@ /// from base of /client/proc/set_eye() : (atom/old_eye, atom/new_eye) #define COMSIG_CLIENT_SET_EYE "client_set_eye" /// from /mob/proc/change_mob_type() : () +#define COMSIG_PRE_MOB_CHANGED_TYPE "pre_mob_changed_type" + #define COMPONENT_BLOCK_MOB_CHANGE (1<<0) +/// from /mob/proc/change_mob_type() : () #define COMSIG_MOB_CHANGED_TYPE "mob_changed_type" /// From /obj/item/melee/baton/baton_effect(): (datum/source, mob/living/user, /obj/item/melee/baton) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index e98e730031d3..1155e894cb07 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -134,6 +134,34 @@ ///Whether this area is iluminated by starlight var/use_starlight = FALSE +/** + * A list of teleport locations + * + * Adding a wizard area teleport list because motherfucking lag -- Urist + * I am far too lazy to make it a proper list of areas so I'll just make it run the usual teleport routine at the start of the game + */ +GLOBAL_LIST_EMPTY(teleportlocs) + +/** + * Generate a list of turfs you can teleport to from the areas list + * + * Includes areas if they're not a shuttle or not not teleport or have no contents + * + * The chosen turf is the first item in the areas contents that is a station level + * + * The returned list of turfs is sorted by name + */ +/proc/process_teleport_locs() + for(var/area/AR as anything in get_sorted_areas()) + if(istype(AR, /area/shuttle) || AR.tele_proof) + continue + if(GLOB.teleportlocs[AR.name]) + continue + if (!AR.has_contained_turfs()) + continue + if (is_station_level(AR.z)) + GLOB.teleportlocs[AR.name] = AR + /area/New(loc, ...) // This interacts with the map loader, so it needs to be set immediately // rather than waiting for atoms to initialize. diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm new file mode 100644 index 000000000000..93b273790182 --- /dev/null +++ b/code/game/objects/effects/phased_mob.dm @@ -0,0 +1,126 @@ +/obj/effect/dummy/phased_mob + name = "ethereal form" + anchored = TRUE +// flags = PREVENT_CONTENTS_EXPLOSION_1 + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + invisibility = INVISIBILITY_OBSERVER + movement_type = FLOATING + /// The movable which is 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 + /// Image we show to our jaunter so they can see where they are + var/image/position_indicator + /// Icon we draw our position indicator from + var/phased_mob_icon = 'icons/obj/weapons/guns/projectiles.dmi' + /// Icon state we use for our position indicator + var/phased_mob_icon_state = "ice_1" + +/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)) + return + var/mob/mob_jaunter = jaunter + position_indicator = image(phased_mob_icon, src, phased_mob_icon_state, ABOVE_LIGHTING_PLANE) + position_indicator.appearance_flags |= RESET_ALPHA + SET_PLANE_EXPLICIT(position_indicator, ABOVE_LIGHTING_PLANE, src) + RegisterSignal(mob_jaunter, COMSIG_MOB_LOGIN, PROC_REF(show_client_image)) + RegisterSignal(mob_jaunter, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change)) + mob_jaunter.reset_perspective(src) + show_client_image(mob_jaunter) + +/// Displays our position indicator to a client +/obj/effect/dummy/phased_mob/proc/show_client_image(mob/show_to) + SIGNAL_HANDLER + show_to.client?.images |= position_indicator + +/obj/effect/dummy/phased_mob/Destroy() + jaunter = null // If a mob was left in the jaunter on qdel, they'll be dumped into nullspace + position_indicator = null + 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 & NOJAUNT) + // 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(z)) + else + jaunter.forceMove(eject_spot) + qdel(src) + +/obj/effect/dummy/phased_mob/Exited(atom/movable/gone, direction) + . = ..() + if(gone == jaunter) + UnregisterSignal(jaunter, COMSIG_MOB_STATCHANGE) + UnregisterSignal(jaunter, COMSIG_MOB_LOGIN) + SEND_SIGNAL(src, COMSIG_MOB_EJECTED_FROM_JAUNT, jaunter) + jaunter = null + +/obj/effect/dummy/phased_mob/ex_act() + return FALSE + +/obj/effect/dummy/phased_mob/bullet_act(obj/projectile/P) + return + +/obj/effect/dummy/phased_mob/relaymove(mob/living/user, direction) + var/turf/newloc = phased_check(user, direction) + if(!newloc) + return + + 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. +/obj/effect/dummy/phased_mob/proc/phased_check(mob/living/user, direction) + RETURN_TYPE(/turf) + if (movedelay > world.time || !direction) + return + var/turf/newloc = get_step_multiz(src,direction) + if(!newloc) + return + var/area/destination_area = newloc.loc + movedelay = world.time + movespeed + + if(SEND_SIGNAL(src, COMSIG_MOB_PHASED_CHECK, user, newloc) & COMPONENT_BLOCK_PHASED_MOVE) + return null + + if(newloc.turf_flags & NOJAUNT) + to_chat(user, span_warning("Some strange aura is blocking the way.")) + return + if(destination_area.area_flags & NOJAUNT) + to_chat(user, span_danger("Some dull, universal force is blocking the way. Its overwhelmingly oppressive force feels dangerous.")) + return + if (direction == UP || direction == DOWN) + newloc = can_z_move(direction, get_turf(src), newloc, ZMOVE_INCAPACITATED_CHECKS | ZMOVE_FEEDBACK | ZMOVE_ALLOW_ANCHORED, user) + + return newloc + +/// Signal proc for [COMSIG_MOB_STATCHANGE], to throw us out of the jaunt if we lose consciousness. +/obj/effect/dummy/phased_mob/proc/on_stat_change(mob/living/source, new_stat, old_stat) + SIGNAL_HANDLER + if(source == jaunter && source.stat != CONSCIOUS) + eject_jaunter() diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 8dabd9bdd814..10d3b49d504b 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -247,6 +247,10 @@ if(BRAIN) return getBrainLoss() +/// return the total damage of all types which update your health +/mob/living/proc/get_total_damage(precision = DAMAGE_PRECISION) + return round(getBruteLoss() + getFireLoss() + getToxLoss() + getOxyLoss(), precision) + /// Applies passed status effect /mob/living/proc/apply_effect(effect = 0, effecttype = STUN, blocked = 0, negate_armor = FALSE) if(HAS_TRAIT(src, TRAIT_GODMODE)) diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index 8d733704c5f1..4f3dbe5f7e22 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -22,6 +22,9 @@ to_chat(usr, span_warning("cannot convert into a new_player mob type.")) return + if (SEND_SIGNAL(src, COMSIG_PRE_MOB_CHANGED_TYPE) & COMPONENT_BLOCK_MOB_CHANGE) + return + var/mob/M if(isturf(location)) M = new new_type( location ) diff --git a/code/modules/projectiles/projectile/special/magic.dm b/code/modules/projectiles/projectile/special/magic.dm index c3c53beb53d3..1aa0d40883f4 100644 --- a/code/modules/projectiles/projectile/special/magic.dm +++ b/code/modules/projectiles/projectile/special/magic.dm @@ -268,156 +268,161 @@ wabbajack(change) /proc/wabbajack(mob/living/M) - if(istype(M) && M.stat != DEAD && !HAS_TRAIT(M, TRAIT_NO_TRANSFORM)) - ADD_TRAIT(M, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) - M.icon = null - M.cut_overlays() - M.invisibility = INVISIBILITY_ABSTRACT - - if(isrobot(M)) - var/mob/living/silicon/robot/Robot = M - QDEL_NULL(Robot.mmi) - Robot.notify_ai(ROBOT_NOTIFY_AI_CONNECTED) - else + if(istype(M) && M.stat == DEAD || HAS_TRAIT(M, TRAIT_NO_TRANSFORM)) + return + + if(SEND_SIGNAL(M, COMSIG_LIVING_PRE_WABBAJACKED, M) & STOP_WABBAJACK) + return + + ADD_TRAIT(M, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT) + M.icon = null + M.cut_overlays() + M.invisibility = INVISIBILITY_ABSTRACT + + if(isrobot(M)) + var/mob/living/silicon/robot/Robot = M + QDEL_NULL(Robot.mmi) + Robot.notify_ai(ROBOT_NOTIFY_AI_CONNECTED) + else + if(ishuman(M)) + var/mob/living/carbon/human/H = M + // Make sure there are no organs or limbs to drop + for(var/t in H.bodyparts) + qdel(t) + for(var/i in H.internal_organs) + qdel(i) + for(var/obj/item/W in M) + M.temporarily_remove_item_from_inventory(W, force = TRUE) + qdel(W) + + var/mob/living/new_mob + var/briefing_msg + var/is_new_mind = FALSE + + var/randomize = pick("РОБОТ", "ТЕРРОР", "КСЕНОМОРФ", "ЧЕЛОВЕК", "ЖИВОТНОЕ") + switch(randomize) + if("РОБОТ") + is_new_mind = TRUE + var/path + if(prob(50)) + path = pick(typesof(/mob/living/silicon/robot/syndicate)) + new_mob = new path(M.loc) + briefing_msg = "" + else + new_mob = new /mob/living/silicon/robot/ert/gamma(M.loc) + briefing_msg = "" + new_mob.gender = M.gender + new_mob.invisibility = 0 + new_mob.job = JOB_TITLE_CYBORG + var/mob/living/silicon/robot/Robot = new_mob if(ishuman(M)) - var/mob/living/carbon/human/H = M - // Make sure there are no organs or limbs to drop - for(var/t in H.bodyparts) - qdel(t) - for(var/i in H.internal_organs) - qdel(i) - for(var/obj/item/W in M) - M.temporarily_remove_item_from_inventory(W, force = TRUE) - qdel(W) - - var/mob/living/new_mob - var/briefing_msg - var/is_new_mind = FALSE - - var/randomize = pick("РОБОТ", "ТЕРРОР", "КСЕНОМОРФ", "ЧЕЛОВЕК", "ЖИВОТНОЕ") - switch(randomize) - if("РОБОТ") - is_new_mind = TRUE - var/path - if(prob(50)) - path = pick(typesof(/mob/living/silicon/robot/syndicate)) - new_mob = new path(M.loc) - briefing_msg = "" - else - new_mob = new /mob/living/silicon/robot/ert/gamma(M.loc) - briefing_msg = "" - new_mob.gender = M.gender - new_mob.invisibility = 0 - new_mob.job = JOB_TITLE_CYBORG - var/mob/living/silicon/robot/Robot = new_mob + Robot.mmi = new /obj/item/mmi(new_mob) + Robot.mmi.transfer_identity(M) //Does not transfer key/client. + else + Robot.mmi = new /obj/item/mmi/robotic_brain(new_mob) + Robot.mmi.brainmob.timeofhostdeath = M.timeofdeath + Robot.mmi.brainmob.set_stat(CONSCIOUS) + Robot.mmi.update_appearance(UPDATE_ICON_STATE|UPDATE_NAME) + Robot.lawupdate = FALSE + Robot.disconnect_from_ai() + Robot.clear_inherent_laws() + Robot.clear_zeroth_law() + if("ТЕРРОР") + is_new_mind = TRUE + var/terror = pick(prob(20); "lurker", prob(20); "knight", prob(20); "drone", prob(15); "widow", prob(15); "reaper", prob(10); "destroyer") + switch(terror) + if("lurker") + new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/lurker(M.loc) + if("knight") + new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/knight(M.loc) + if("drone") + new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/builder(M.loc) + if("widow") + new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/widow(M.loc) + if("reaper") + new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/reaper(M.loc) + if("destroyer") + new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/destroyer(M.loc) + new_mob.universal_speak = TRUE + if("КСЕНОМОРФ") + is_new_mind = TRUE + if(prob(50)) + new_mob = new /mob/living/carbon/alien/humanoid/hunter(M.loc) + else + new_mob = new /mob/living/carbon/alien/humanoid/sentinel(M.loc) + new_mob.universal_speak = TRUE + + briefing_msg = "Вам разрешается убивать нексеноморфов среди вас. Прежде всего вам лучше обнаружить других себеподобных и подготовить место для улья.." + if("ЖИВОТНОЕ") + is_new_mind = TRUE + var/beast = pick("carp", "bear", "statue", "giantspider", "syndiemouse") + switch(beast) + if("carp") + new_mob = new /mob/living/simple_animal/hostile/carp(M.loc) + if("bear") + new_mob = new /mob/living/simple_animal/hostile/bear(M.loc) + if("statue") + new_mob = new /mob/living/simple_animal/hostile/statue(M.loc) + if("giantspider") + var/spiderType = pick("hunterspider","nursespider","basicspider") + switch(spiderType) + if("hunterspider") + new_mob = new /mob/living/simple_animal/hostile/poison/giant_spider/hunter(M.loc) + if("nursespider") + new_mob = new /mob/living/simple_animal/hostile/poison/giant_spider/nurse(M.loc) + if("basicspider") + new_mob = new /mob/living/simple_animal/hostile/poison/giant_spider(M.loc) + if("syndiemouse") + new_mob = new /mob/living/simple_animal/hostile/retaliate/syndirat(M.loc) + briefing_msg = "Вы агрессивное животное, питаемое жаждой голода, вы можете совершать убийства, \ + сбиваться в стаи или следовать своему пути одиночки, но цель всегда будет одна — утолить свой голод." + new_mob.universal_speak = TRUE + if("ЧЕЛОВЕК") + if(prob(50)) + new_mob = new /mob/living/carbon/human(M.loc) + var/mob/living/carbon/human/H = new_mob + var/datum/preferences/A = new() //Randomize appearance for the human + A.species = get_random_species(TRUE) + A.copy_to(new_mob) + randomize = H.dna.species.name if(ishuman(M)) - Robot.mmi = new /obj/item/mmi(new_mob) - Robot.mmi.transfer_identity(M) //Does not transfer key/client. - else - Robot.mmi = new /obj/item/mmi/robotic_brain(new_mob) - Robot.mmi.brainmob.timeofhostdeath = M.timeofdeath - Robot.mmi.brainmob.set_stat(CONSCIOUS) - Robot.mmi.update_appearance(UPDATE_ICON_STATE|UPDATE_NAME) - Robot.lawupdate = FALSE - Robot.disconnect_from_ai() - Robot.clear_inherent_laws() - Robot.clear_zeroth_law() - if("ТЕРРОР") - is_new_mind = TRUE - var/terror = pick(prob(20); "lurker", prob(20); "knight", prob(20); "drone", prob(15); "widow", prob(15); "reaper", prob(10); "destroyer") - switch(terror) - if("lurker") - new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/lurker(M.loc) - if("knight") - new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/knight(M.loc) - if("drone") - new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/builder(M.loc) - if("widow") - new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/widow(M.loc) - if("reaper") - new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/reaper(M.loc) - if("destroyer") - new_mob = new /mob/living/simple_animal/hostile/poison/terror_spider/destroyer(M.loc) - new_mob.universal_speak = TRUE - if("КСЕНОМОРФ") - is_new_mind = TRUE - if(prob(50)) - new_mob = new /mob/living/carbon/alien/humanoid/hunter(M.loc) - else - new_mob = new /mob/living/carbon/alien/humanoid/sentinel(M.loc) - new_mob.universal_speak = TRUE - - briefing_msg = "Вам разрешается убивать нексеноморфов среди вас. Прежде всего вам лучше обнаружить других себеподобных и подготовить место для улья.." - if("ЖИВОТНОЕ") - is_new_mind = TRUE - var/beast = pick("carp", "bear", "statue", "giantspider", "syndiemouse") - switch(beast) - if("carp") - new_mob = new /mob/living/simple_animal/hostile/carp(M.loc) - if("bear") - new_mob = new /mob/living/simple_animal/hostile/bear(M.loc) - if("statue") - new_mob = new /mob/living/simple_animal/hostile/statue(M.loc) - if("giantspider") - var/spiderType = pick("hunterspider","nursespider","basicspider") - switch(spiderType) - if("hunterspider") - new_mob = new /mob/living/simple_animal/hostile/poison/giant_spider/hunter(M.loc) - if("nursespider") - new_mob = new /mob/living/simple_animal/hostile/poison/giant_spider/nurse(M.loc) - if("basicspider") - new_mob = new /mob/living/simple_animal/hostile/poison/giant_spider(M.loc) - if("syndiemouse") - new_mob = new /mob/living/simple_animal/hostile/retaliate/syndirat(M.loc) - briefing_msg = "Вы агрессивное животное, питаемое жаждой голода, вы можете совершать убийства, \ - сбиваться в стаи или следовать своему пути одиночки, но цель всегда будет одна — утолить свой голод." - new_mob.universal_speak = TRUE - if("ЧЕЛОВЕК") - if(prob(50)) - new_mob = new /mob/living/carbon/human(M.loc) - var/mob/living/carbon/human/H = new_mob - var/datum/preferences/A = new() //Randomize appearance for the human - A.species = get_random_species(TRUE) - A.copy_to(new_mob) - randomize = H.dna.species.name - if(ishuman(M)) - briefing_msg = "Вы тот же самый гуманоид, с тем же сознанием и той же памятью, \ - но ваша кожа теперь какая-то другая, да и вы сами теперь какой-то другой." - else - is_new_mind = TRUE - briefing_msg = "Вы превратились в разумного гуманоида, знакомым с устройством мира и НТ." + briefing_msg = "Вы тот же самый гуманоид, с тем же сознанием и той же памятью, \ + но ваша кожа теперь какая-то другая, да и вы сами теперь какой-то другой." else - new_mob = new /mob/living/carbon/human/lesser/monkey(M.loc) - if(ishuman(M)) - briefing_msg = "Вы разумная мартышка, вам хоть и хочется бананов, \ - но у вас по прежнему память о своей прошлой жизни..." - else - is_new_mind = TRUE - briefing_msg = "Вы разумная мартышка, и вам хочется бананов." - + is_new_mind = TRUE + briefing_msg = "Вы превратились в разумного гуманоида, знакомым с устройством мира и НТ." else - return - - add_attack_logs(null, M, "became [new_mob.real_name]", ATKLOG_ALL) + new_mob = new /mob/living/carbon/human/lesser/monkey(M.loc) + if(ishuman(M)) + briefing_msg = "Вы разумная мартышка, вам хоть и хочется бананов, \ + но у вас по прежнему память о своей прошлой жизни..." + else + is_new_mind = TRUE + briefing_msg = "Вы разумная мартышка, и вам хочется бананов." - new_mob.a_intent = INTENT_HARM - if(M.mind) - M.mind.transfer_to(new_mob) - if(is_new_mind) - new_mob.mind.wipe_memory() - if(briefing_msg) - new_mob.mind.store_memory(briefing_msg) else - new_mob.possess_by_player(M.ckey) + return + add_attack_logs(null, M, "became [new_mob.real_name]", ATKLOG_ALL) + SEND_SIGNAL(M, COMSIG_LIVING_ON_WABBAJACKED, new_mob) + new_mob.a_intent = INTENT_HARM + if(M.mind) + M.mind.transfer_to(new_mob) if(is_new_mind) - to_chat(new_mob, span_danger("Вы потеряли свою личность и память! Отыгрывайте новое существо!")) - to_chat(new_mob, span_danger("ТЕПЕРЬ ВЫ [uppertext(randomize)]")) - if(briefing_msg) - to_chat(new_mob, chat_box_red(span_userdanger("[briefing_msg]"))) + new_mob.mind.wipe_memory() + if(briefing_msg) + new_mob.mind.store_memory(briefing_msg) + else + new_mob.possess_by_player(M.ckey) + + if(is_new_mind) + to_chat(new_mob, span_danger("Вы потеряли свою личность и память! Отыгрывайте новое существо!")) + to_chat(new_mob, span_danger("ТЕПЕРЬ ВЫ [uppertext(randomize)]")) + if(briefing_msg) + to_chat(new_mob, chat_box_red(span_userdanger("[briefing_msg]"))) - qdel(M) - return new_mob + qdel(M) + return new_mob // MARK: Animation Bolt /obj/projectile/magic/animate 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..92442bd0e40a --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm @@ -0,0 +1,73 @@ +/** + * ## 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 + /// Should we shuffle the targets lift after getting them via get_things_to_cast_on? + var/shuffle_targets_list = FALSE + /// The radius of the aoe. + var/aoe_radius = 7 + +/datum/action/cooldown/spell/aoe/is_valid_target(atom/cast_on) + return isturf(cast_on.loc) + +// At this point, cast_on == owner. Either works. +// Don't extend this for your spell! Look at cast_on_thing_in_aoe. +/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 set, shuffle the list of things we're going to cast on to remove any existing order + if(shuffle_targets_list) + shuffle_inplace(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 going to be affected by our aoe. + * You should override this on a subtype basis to change what your spell affects. + * + * For example, if you want to only cast on atoms in view instead of range. + * Or, if you only want living mobs in the list. + * + * When using range / view, it's handy to remember the byond optimization they have by casting to an atom type. + * + * Returns a list of atoms. + */ +/datum/action/cooldown/spell/aoe/proc/get_things_to_cast_on(atom/center) + RETURN_TYPE(/list) + + var/list/things = list() + // Default behavior is to get all atoms in range, center and owner not included. + 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.") 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..9bd5b5e562f3 --- /dev/null +++ b/code/modules/spells/spell_types/charged/_charged.dm @@ -0,0 +1,165 @@ +/** + * ## 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 + /// Flags of the do_after + var/channel_flags = DA_IGNORE_USER_LOC_CHANGE|DA_IGNORE_HELD_ITEM + + // 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 = 7) + + 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, timed_action_flags = channel_flags)) + 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(7) + // Play a null sound in to cancel the sound playing, because byond + playsound(for_who, sound(null, repeat = 0, channel = 7), 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/cone/_cone.dm b/code/modules/spells/spell_types/cone/_cone.dm new file mode 100644 index 000000000000..a014fe85a8f6 --- /dev/null +++ b/code/modules/spells/spell_types/cone/_cone.dm @@ -0,0 +1,134 @@ +/** + * ## 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(!isfloorturf(target_turf)) + continue + + for(var/atom/movable/movable_content as anything in target_turf) + if(isobj(movable_content)) + do_obj_cone_effect(movable_content, caster, level) + else if(isliving(movable_content)) + do_mob_cone_effect(movable_content, caster, 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 + + // Go though every level of the cone levels and generate the cone. + for(var/level in 1 to cone_levels) + var/list/level_turfs = list() + // Our center turf always exists, it's straight ahead of the caster. + turf_to_use = get_step(turf_to_use, dir_to_use) + level_turfs += turf_to_use + // Level 1 only ever has 1 turf, it's a cone. + if(level != 1) + var/level_width_in_each_direction = round((calculate_cone_shape(level) - 1) / 2) + left_turf = turf_to_use + right_turf = turf_to_use + // Check turfs to the left... + for(var/left_of_center in 1 to level_width_in_each_direction) + if(respect_density && left_turf.density) + break + left_turf = get_step(left_turf, left_dir) + level_turfs += left_turf + // And turfs to the right. + for(var/right_of_enter in 1 to level_width_in_each_direction) + if(respect_density && right_turf.density) + break + right_turf = get_step(right_turf, right_dir) + level_turfs += right_turf + // Add the list of all turfs on this level to the turfs to return + turfs_to_return += list(level_turfs) + + // If we're at the last level, we're done + if(level == cone_levels) + break + // But if we're not at the last level, we should check that we can keep going + if(respect_density && turf_to_use.density) + break + + return turfs_to_return + +/** + * Adjusts the width of the cone at the passed level. + * This is never called on the first level of the cone (level 1 is always 1 width) + * + * Return a number - the TOTAL width of the cone at the passed level. + */ +/datum/action/cooldown/spell/cone/proc/calculate_cone_shape(current_level) + // Default formula: (1 (innate) -> 3 -> 5 -> 5 -> 7 -> 7 -> 9 -> 9 -> ...) + return current_level + (current_level % 2) + 1 + +/** + * ### 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) diff --git a/code/modules/spells/spell_types/conjure/_conjure.dm b/code/modules/spells/spell_types/conjure/_conjure.dm new file mode 100644 index 000000000000..373bef8844f2 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/_conjure.dm @@ -0,0 +1,87 @@ +/datum/action/cooldown/spell/conjure + sound = 'sound/items/welder.ogg' + school = SCHOOL_CONJURATION + + /// The radius around the caster the items will appear. 0 = spawns on top of the caster. + var/summon_radius = 7 + /// A list of types that will be created on summon. + /// The type is picked from this list, not all provided are guaranteed. + var/list/summon_type = list() + /// How long before the summons will be despawned. Set to 0 for permanent. + var/summon_lifespan = 0 + /// Amount of summons to create. + var/summon_amount = 1 + /// If TRUE, summoned objects will not be spawned in dense turfs. + var/summon_respects_density = FALSE + /// If TRUE, no two summons can be spawned in the same turf. + var/summon_respects_prev_spawn_points = TRUE + /// for how long must we stay still when summoning + var/create_summon_timer + +/datum/action/cooldown/spell/conjure/is_valid_target(atom/cast_on) + return isturf(cast_on.loc) + +/datum/action/cooldown/spell/conjure/cast(atom/cast_on) + . = ..() + if(create_summon_timer && !do_after(owner, create_summon_timer, target = cast_on.loc)) + owner?.balloon_alert(owner, "need to stay still!") + return + var/list/to_summon_in = list() + for(var/turf/summon_turf in range(summon_radius, cast_on)) + if(summon_respects_density && summon_turf.density) + continue + to_summon_in += summon_turf + + for(var/i in 1 to summon_amount) + if(!length(to_summon_in)) + break + + var/atom/summoned_object_type = pick(summon_type) + var/turf/spawn_place = pick(to_summon_in) + if(summon_respects_prev_spawn_points) + to_summon_in -= spawn_place + + if(ispath(summoned_object_type, /turf)) + if(isclosedturf(spawn_place)) + spawn_place.ChangeTurf(summoned_object_type, after_flags = CHANGETURF_IGNORE_AIR) + return + var/turf/simulated/floor/floor_turf = spawn_place + floor_turf.ChangeTurf(summoned_object_type, after_flags = CHANGETURF_IGNORE_AIR) + return + + var/atom/summoned_object = new summoned_object_type(spawn_place) + + summoned_object.flags |= ADMIN_SPAWNED + if(summon_lifespan > 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 + +///limits the amount of summons +/datum/action/cooldown/spell/conjure/limit_summons + ///max number of after images + var/max_summons + ///How many clones do we have summoned + var/number_of_summons = 0 + +/datum/action/cooldown/spell/conjure/limit_summons/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(number_of_summons >= max_summons) + return FALSE + return TRUE + +/datum/action/cooldown/spell/conjure/limit_summons/post_summon(atom/summoned_object, atom/cast_on) + RegisterSignal(summoned_object, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(delete_copy)) + number_of_summons++ + +/datum/action/cooldown/spell/conjure/limit_summons/proc/delete_copy(datum/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + number_of_summons-- 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..26e9648ea068 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm @@ -0,0 +1,88 @@ +/datum/action/cooldown/spell/conjure_item + school = SCHOOL_CONJURATION + invocation_type = INVOCATION_NONE + + /// Typepath of whatever item we summon + var/obj/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 + /// If TRUE, deletes the item if no mob picks it up on cast + var/delete_on_failure = TRUE + /// If TRUE, requires the caster be able to pick it up afterwards + var/requires_hands = FALSE + +/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/can_cast_spell(feedback) + . = ..() + if(!.) + return FALSE + + if(!requires_hands) + return TRUE + + if(!isliving(owner)) + return FALSE + + var/mob/living/living_owner = owner + if(living_owner.usable_hands < 1) + if(feedback) + owner.balloon_alert(owner, "no free hands!") + return FALSE + + return TRUE + +/datum/action/cooldown/spell/conjure_item/is_valid_target(atom/cast_on) + if(!requires_hands) + return TRUE + if(!isliving(cast_on)) + return FALSE + var/mob/living/living_cast_on = cast_on + return living_cast_on.usable_hands >= 1 + +/datum/action/cooldown/spell/conjure_item/cast(atom/cast_on) + if(delete_old && LAZYLEN(item_refs)) + QDEL_LAZYLIST(item_refs) + + var/mob/mob_caster = cast_on + if(istype(mob_caster)) + var/obj/existing_item = mob_caster.get_item_by_slot(mob_caster.get_active_hand()) + if(existing_item) + mob_caster.drop_item_ground(existing_item) + + var/obj/item/created = make_item(cast_on) + if(QDELETED(created)) + CRASH("[type] tried to create an item, but failed. It's item type is [item_type].") + + if(!isitem(created)) + created.forceMove(cast_on.drop_location()) + return + + if(istype(mob_caster)) + mob_caster.put_in_hands(created, qdel_on_fail = delete_on_failure) + + post_created(cast_on, created) + + return ..() + +/// Instantiates the item we're conjuring and returns it. +/// Item is made in at the caster's. +/datum/action/cooldown/spell/conjure_item/proc/make_item(atom/caster) + var/obj/item/made_item = new item_type(caster.loc) + LAZYADD(item_refs, WEAKREF(made_item)) + return made_item + +/// Called after item has been handed to the caster, for any additional presentation +/datum/action/cooldown/spell/conjure_item/proc/post_created(atom/cast_on, atom/created) + return 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..2e881819f282 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/_jaunt.dm @@ -0,0 +1,123 @@ +/** + * ## 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/get_caster_from_target(atom/target) + if(istype(target.loc, jaunt_type)) + return target + + return ..() + +/datum/action/cooldown/spell/jaunt/PreActivate(atom/target) + if(SEND_SIGNAL(target, COMSIG_MOB_PRE_JAUNT, target) & COMPONENT_BLOCK_JAUNT) + return FALSE + . = ..() + +/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? +// We don't have tg teleportation ported yet +/* if(!check_teleport_valid(owner, owner_turf, TELEPORT_CHANNEL_MAGIC)) + if(feedback) + to_chat(owner, span_danger("Some dull, universal force is stopping you from jaunting here.")) + return FALSE*/ + + if(owner_turf?.turf_flags & NOJAUNT) + if(feedback) + to_chat(owner, span_danger("An otherwordly force is preventing 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)) + + // 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(jaunter) + + // 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) + check_flags |= AB_CHECK_PHASED + // 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/list_target/_list_target.dm b/code/modules/spells/spell_types/list_target/_list_target.dm new file mode 100644 index 000000000000..d595552e9879 --- /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, sort_names(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 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..a6a489fd6d74 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -0,0 +1,186 @@ +/** + * ## 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 + /// If aim asisst is used. Disable to disable + 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) + +/// 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/clicker, params, atom/target) + var/atom/aim_assist_target + if(aim_assist) + aim_assist_target = aim_assist(clicker, target) + return ..(clicker, params, aim_assist_target || target) + +/datum/action/cooldown/spell/pointed/proc/aim_assist(mob/living/clicker, atom/target) + if(!isturf(target)) + return + + // Find any human, or if that fails, any living target + return locate(/mob/living/carbon/human) in target || locate(/mob/living) in 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 + +/datum/action/cooldown/spell/pointed/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + 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 + +/** + * ### 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/projectile/projectile_type = /obj/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) + . = ..() + var/atom/caster = get_caster_from_target(owner) + if(!isturf(caster.loc)) + return FALSE + + var/turf/caster_turf = caster.loc + // 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_angle(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/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/projectile/to_fire, atom/target, mob/user, iteration) + to_fire.firer = owner + to_fire.current = get_turf(user) + to_fire.original = target + to_fire.preparePixelProjectile(target, user) + to_fire.fire() + RegisterSignal(to_fire, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_cast_hit)) + +//alistNo antimagic ported yet +/* if(istype(to_fire, /obj/projectile/magic)) + var/obj/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(datum/source, mob/firer, atom/target, angle, hit_limb) + SIGNAL_HANDLER + + SEND_SIGNAL(src, COMSIG_SPELL_PROJECTILE_HIT, source, firer, target, angle, hit_limb) diff --git a/code/modules/spells/spell_types/proejctile/_basic_projectile.dm b/code/modules/spells/spell_types/proejctile/_basic_projectile.dm new file mode 100644 index 000000000000..2637f50aab87 --- /dev/null +++ b/code/modules/spells/spell_types/proejctile/_basic_projectile.dm @@ -0,0 +1,35 @@ +/** + * ## 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/projectile/projectile_type = /obj/projectile/magic + +/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/projectile/to_fire = new projectile_type() + to_fire.firer = owner + to_fire.current = get_turf(caster) + to_fire.original = target + to_fire.preparePixelProjectile(target, caster) + to_fire.fire() + SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) + to_fire.fire() + return to_fire diff --git a/code/game/gamemodes/wizard/rightandwrong.dm b/code/modules/spells/spell_types/rightandwrong.dm similarity index 100% rename from code/game/gamemodes/wizard/rightandwrong.dm rename to code/modules/spells/spell_types/rightandwrong.dm 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..0e3ebf01246e --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -0,0 +1,259 @@ +// 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) + ADD_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(caster_mob, TRAIT_STASIS, TRAIT_STATUS_EFFECT(id)) + RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, PROC_REF(on_pre_wabbajack)) + RegisterSignal(owner, COMSIG_PRE_MOB_CHANGED_TYPE, PROC_REF(on_pre_type_change)) + 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_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 + +/// Called when we're shot by the Wabbajack but before we change into a different mob +/datum/status_effect/shapechange_mob/proc/on_pre_wabbajack(mob/living/source) + SIGNAL_HANDLER + on_mob_transformed(source) + return STOP_WABBAJACK + +/// Called when we're turned into a different mob via the change_mob_type proc +/datum/status_effect/shapechange_mob/proc/on_pre_type_change(mob/living/source) + SIGNAL_HANDLER + on_mob_transformed(source) + return COMPONENT_BLOCK_MOB_CHANGE + +/// Called when the transformed mob tries to change into a different kind of mob, we wouldn't handle this well so we'll just turn back +/datum/status_effect/shapechange_mob/proc/on_mob_transformed(mob/living/source) + var/mob/living/revealed_mob = caster_mob + source.visible_message(span_warning("[revealed_mob] gets pulled back to their normal form!")) + restore_caster() + revealed_mob.Weaken(10 SECONDS, ignore_canweaken = TRUE) + +/// 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_QDELETING, COMSIG_LIVING_DEATH)) + + REMOVE_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(caster_mob, TRAIT_STASIS, TRAIT_STATUS_EFFECT(id)) + + var/atom/former_loc = owner.loc + owner.moveToNullspace() + caster_mob.forceMove(former_loc) // This is to avoid crushing our former cockroach body + + if(kill_caster_after) + caster_mob.death() + + after_unchange() + + // We're about to remove the status effect and clear owner so we need to cache this + var/mob/living/former_body = owner + + // Do this late as it will destroy the status effect we are in and null a bunch of values we are trying to use + owner.mind?.transfer_to(caster_mob) + + // 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(former_body) + +/// 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 + + 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) + owner.gib() + + // Otherwise our caster died, just make our mob die + else + owner.death() + +/// Signal proc for [COMSIG_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.get_total_damage() / caster_mob.maxHealth) + + owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE) + // Only transfer blood if both mobs are supposed to have a blood volume + if (initial(owner.blood_volume) > 0 && initial(caster_mob.blood_volume) > 0) + 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) + if(owner?.contents) + // Prevent round removal and consuming stuff when losing shapeshift + for(var/atom/movable/thing as anything in owner.contents) + if(thing == caster_mob) + continue + thing.forceMove(get_turf(owner)) + + 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 + + caster_mob.rejuvenate() // Remove all of our damage before setting our health to a proportion of the former transformed mob's health + var/damage_to_apply = caster_mob.maxHealth * (owner.get_total_damage() / owner.maxHealth) + caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE) + // Only transfer blood if both mobs are supposed to have a blood volume + if (initial(caster_mob.blood_volume) > 0 && initial(owner.blood_volume) > 0) + 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) + restore_caster(kill_caster_after = 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..4c96222a892c --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -0,0 +1,210 @@ +/// 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/Remove(mob/remove_from) + unshift_owner() + return ..() + +/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(feedback = 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(HAS_TRAIT(resulting_mob, TRAIT_VENTCRAWLER_ALWAYS) || HAS_TRAIT(resulting_mob, TRAIT_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 = choose_gibs(cast_on) + + + for(var/obj/machinery/atmospherics/unary/possible_vent in range(10, get_turf(cast_on))) + if(length(possible_vent.parent) && possible_vent.parent == our_pipeline) + new gib_type(get_turf(possible_vent)) + playsound(possible_vent, 'sound/misc/reee.ogg', 75, TRUE) + + GLOB.minor_announcement.announce("We detected a pipe blockage around [get_area(get_turf(cast_on))], please dispatch someone to investigate.", "[command_name()]") + // 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() + +/datum/action/cooldown/spell/shapeshift/proc/choose_gibs(mob/living/gibbed) + if(is_clown_job(gibbed)) + return /obj/effect/gibspawner/confetti + if(ishuman(gibbed)) + return /obj/effect/gibspawner/human + if(isalien(gibbed)) + return /obj/effect/gibspawner/xeno + if(isrobot(gibbed) || ismachineperson(gibbed) || isbot(gibbed)) + return /obj/effect/gibspawner/robot + return /obj/effect/gibspawner/generic + +/// 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 !HAS_TRAIT(caster, TRAIT_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) + + // Make sure that if you shapechanged into a bot, the AI can't just turn you off. + var/mob/living/simple_animal/bot/polymorph_bot = new_shape + if (istype(polymorph_bot)) + polymorph_bot.emagged = TRUE + polymorph_bot.remote_disabled = FALSE + + 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) + +/// Removes an active shapeshift effect from the owner +/datum/action/cooldown/spell/shapeshift/proc/unshift_owner() + if (isnull(owner)) + return + if (is_shifted(owner)) + do_unshapeshift(owner) + +#undef is_shifted 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..40f5c457cc6c --- /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,*/ bypass_area_flag = force_teleport, ignore_bluespace_interference = TRUE) + +/// 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) || (owner && !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(mob/living/invoker) + 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) + invoker.say("[invocation], [uppertext(last_chosen_area.name)]!") + if(INVOCATION_WHISPER) + invoker.whisper("[invocation], [uppertext(last_chosen_area.name)].") 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..8a119fcbe61d --- /dev/null +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -0,0 +1,361 @@ +/** + * ## 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_alt("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_alt("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 + // Currently, hard checks for carbons. + // If someone wants to add support for simplemobs + // adminbussed to have hands, go for it + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(!(carbon_owner.mobility_flags & MOBILITY_USE)) + return FALSE + return TRUE + +// Checks if the mob slapped with the hand is a valid target. +/datum/action/cooldown/spell/touch/is_valid_target(atom/cast_on) + return isliving(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, qdel_on_fail = TRUE)) + reset_spell_cooldown() + if (cast_on.usable_hands == 0) + 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 + register_hand_signals() + 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)) + unregister_hand_signals() + hand_owner?.temporarily_remove_item_from_inventory(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() + +/// Registers all signal procs for the hand. +/datum/action/cooldown/spell/touch/proc/register_hand_signals() + SHOULD_CALL_PARENT(TRUE) + + RegisterSignal(attached_hand, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(on_hand_hit)) + RegisterSignal(attached_hand, COMSIG_ITEM_INTERACTING_WITH_ATOM_SECONDARY, PROC_REF(on_hand_hit_secondary)) + RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped)) + RegisterSignal(attached_hand, COMSIG_QDELETING, PROC_REF(on_hand_deleted)) + + // TODO: add this to /datum/status_effect/high_five later + // We can high five with our touch hand. It casts the spell on people. Radical + //attached_hand.AddElement(/datum/element/high_fiver) + +/// Unregisters all signal procs for the hand. +/datum/action/cooldown/spell/touch/proc/unregister_hand_signals() + SHOULD_CALL_PARENT(TRUE) + + UnregisterSignal(attached_hand, list( + COMSIG_ITEM_INTERACTING_WITH_ATOM, + COMSIG_ITEM_INTERACTING_WITH_ATOM_SECONDARY, + COMSIG_ITEM_DROPPED, + COMSIG_QDELETING + )) + +// 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(SEND_SIGNAL(cast_on, COMSIG_TOUCH_HANDLESS_CAST, src) & COMPONENT_CAST_HANDLESS) + StartCooldown() + return + + if(!QDELETED(attached_hand) && (attached_hand in cast_on.get_item_by_slot(cast_on.get_active_hand()))) + remove_hand(cast_on, reset_cooldown_after = TRUE) + return + + create_hand(cast_on) + return ..() + +/** + * Signal proc for [COMSIG_ITEM_INTERACTING_WITH_ATOM] 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, mob/living/caster, atom/target, list/modifiers) + SIGNAL_HANDLER + SHOULD_NOT_OVERRIDE(TRUE) // DEFINITELY don't put effects here, put them in cast_on_hand_hit + + if(!can_hit_with_hand(target, caster)) + return NONE + + return do_hand_hit(source, target, caster) + +/** + * Signal proc for [COMSIG_ITEM_INTERACTING_WITH_ATOM_SECONDARY] 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_secondary(datum/source, mob/living/caster, atom/target, list/modifiers) + SIGNAL_HANDLER + SHOULD_NOT_OVERRIDE(TRUE) + + if(!can_hit_with_hand(target, caster)) + return NONE + + return do_secondary_hand_hit(source, target, caster) + +/// 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/living/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 + if(!(caster.mobility_flags & MOBILITY_USE)) + caster.balloon_alert(caster, "can't reach out!") + 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 +// No antimagic yet +// if(istype(mob_victim) && mob_victim.can_block_magic(antimagic_flags)) +// on_antimagic_triggered(hand, victim, caster) + + if(!cast_on_hand_hit(hand, victim, caster)) + return NONE + + add_attack_logs(caster, victim, "cast the touch spell [name] on", hand) + INVOKE_ASYNC(src, PROC_REF(spell_feedback), caster) + caster.do_attack_animation(victim) + caster.changeNext_move(CLICK_CD_MELEE) + victim.add_fingerprint(caster) + remove_hand(caster) + return ITEM_INTERACT_SUCCESS + +/** + * Calls do_secondary_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. + + * Does NOT check for antimagic on its own. Implement your own checks if you want the r-click to abide by it. + */ +/datum/action/cooldown/spell/touch/proc/do_secondary_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_secondary_hand_hit + + 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) + add_attack_logs(caster, victim, "cast the touch spell [name] on", hand, "(secondary / alt cast)") + INVOKE_ASYNC(src, PROC_REF(spell_feedback), caster) + caster.do_attack_animation(victim) + caster.changeNext_move(CLICK_CD_MELEE) + victim.add_fingerprint(caster) + remove_hand(caster) + return ITEM_INTERACT_SUCCESS + + // 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 NONE + +/** + * 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 + +/** + * Signal proc for [COMSIG_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 + + remove_hand(reset_cooldown_after = 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 + + remove_hand(dropper, reset_cooldown_after = TRUE) + +/** + * 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*/ + +/** + * ## Touch attack item + * + * Used for touch spells to have something physical to slap people with. + * + * Try to avoid adding behavior onto these for your touch spells! + * The spells themselves should handle most, if not all, of the casted effects. + * + * These should generally just be dummy objects - holds name and icon stuff. + */ +/obj/item/melee/touch_attack + name = "\improper outstretched hand" + desc = "High Five?" + icon = 'icons/obj/items.dmi' + item_state = "lgloves" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + item_flags = ABSTRACT + needs_permit = TRUE + 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) + +/** + * 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.temporarily_remove_item_from_inventory(src, force = TRUE) + qdel(src) diff --git a/paradise.dme b/paradise.dme index 7faf8d89e4fd..733ac925a1de 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1266,7 +1266,6 @@ #include "code\game\gamemodes\wizard\artefact.dm" #include "code\game\gamemodes\wizard\godhand.dm" #include "code\game\gamemodes\wizard\raginmages.dm" -#include "code\game\gamemodes\wizard\rightandwrong.dm" #include "code\game\gamemodes\wizard\soulstone.dm" #include "code\game\gamemodes\wizard\spellbook.dm" #include "code\game\gamemodes\wizard\wizard.dm" @@ -1470,6 +1469,7 @@ #include "code\game\objects\effects\overlays.dm" #include "code\game\objects\effects\particle_holder.dm" #include "code\game\objects\effects\particle_holder_tgmc.dm" +#include "code\game\objects\effects\phased_mob.dm" #include "code\game\objects\effects\portals.dm" #include "code\game\objects\effects\reach_checker.dm" #include "code\game\objects\effects\shared_particle_holder.dm" @@ -3878,6 +3878,20 @@ #include "code\modules\spacepods\spacepod.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" +#include "code\modules\spells\spell_types\rightandwrong.dm" +#include "code\modules\spells\spell_types\aoe_spell\_aoe_spell.dm" +#include "code\modules\spells\spell_types\charged\_charged.dm" +#include "code\modules\spells\spell_types\cone\_cone.dm" +#include "code\modules\spells\spell_types\conjure\_conjure.dm" +#include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" +#include "code\modules\spells\spell_types\jaunt\_jaunt.dm" +#include "code\modules\spells\spell_types\list_target\_list_target.dm" +#include "code\modules\spells\spell_types\pointed\_pointed.dm" +#include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" +#include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" +#include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" +#include "code\modules\spells\spell_types\teleport\_teleport.dm" +#include "code\modules\spells\spell_types\touch\_touch.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" diff --git a/sound/misc/reee.ogg b/sound/misc/reee.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d8e1cfcaccd1c52ad9080ff3967174b42406a23f GIT binary patch literal 34645 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 Jh7S--!UKn7x+wqv literal 0 HcmV?d00001 From ffc43bf319bf735f37cb0985c677ff69514ca5d5 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Mon, 11 May 2026 13:47:56 +0300 Subject: [PATCH 14/42] =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=BE=D0=B7=20=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Куча фиксов линтера + поменял /obj/item/melee/touch_attack на /obj/item/melee/magic_hand чтобы не было конфликта с god_hand.dm, потом верну назад --- code/game/area/areas.dm | 4 +-- code/game/objects/effects/phased_mob.dm | 7 ++--- .../spell_types/conjure_item/_conjure_item.dm | 1 - .../spells/spell_types/jaunt/_jaunt.dm | 4 +-- .../spell_types/shapeshift/_shapeshift.dm | 6 ++-- .../spells/spell_types/touch/_touch.dm | 31 +++++++++---------- 6 files changed, 24 insertions(+), 29 deletions(-) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 7857200c006c..99828221947c 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -165,9 +165,9 @@ GLOBAL_LIST_EMPTY(teleportlocs) continue if(GLOB.teleportlocs[AR.name]) continue - if (!AR.has_contained_turfs()) + if(!AR.has_contained_turfs()) continue - if (is_station_level(AR.z)) + if(is_station_level(AR.z)) GLOB.teleportlocs[AR.name] = AR /area/New(loc, ...) diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm index 93b273790182..d6f5be8b3578 100644 --- a/code/game/objects/effects/phased_mob.dm +++ b/code/game/objects/effects/phased_mob.dm @@ -1,6 +1,5 @@ /obj/effect/dummy/phased_mob name = "ethereal form" - anchored = TRUE // flags = PREVENT_CONTENTS_EXPLOSION_1 resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF invisibility = INVISIBILITY_OBSERVER @@ -90,14 +89,14 @@ if(!newloc) return - if (direction in GLOB.alldirs) + 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. /obj/effect/dummy/phased_mob/proc/phased_check(mob/living/user, direction) RETURN_TYPE(/turf) - if (movedelay > world.time || !direction) + if(movedelay > world.time || !direction) return var/turf/newloc = get_step_multiz(src,direction) if(!newloc) @@ -114,7 +113,7 @@ if(destination_area.area_flags & NOJAUNT) to_chat(user, span_danger("Some dull, universal force is blocking the way. Its overwhelmingly oppressive force feels dangerous.")) return - if (direction == UP || direction == DOWN) + if(direction == UP || direction == DOWN) newloc = can_z_move(direction, get_turf(src), newloc, ZMOVE_INCAPACITATED_CHECKS | ZMOVE_FEEDBACK | ZMOVE_ALLOW_ANCHORED, user) return newloc diff --git a/code/modules/spells/spell_types/conjure_item/_conjure_item.dm b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm index 26e9648ea068..d5ad0aa2fe43 100644 --- a/code/modules/spells/spell_types/conjure_item/_conjure_item.dm +++ b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm @@ -1,6 +1,5 @@ /datum/action/cooldown/spell/conjure_item school = SCHOOL_CONJURATION - invocation_type = INVOCATION_NONE /// Typepath of whatever item we summon var/obj/item_type diff --git a/code/modules/spells/spell_types/jaunt/_jaunt.dm b/code/modules/spells/spell_types/jaunt/_jaunt.dm index 2e881819f282..76045a4146e5 100644 --- a/code/modules/spells/spell_types/jaunt/_jaunt.dm +++ b/code/modules/spells/spell_types/jaunt/_jaunt.dm @@ -14,8 +14,6 @@ /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 @@ -116,7 +114,7 @@ /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 + 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() diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm index 4c96222a892c..51f2980e69ac 100644 --- a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -172,7 +172,7 @@ // Make sure that if you shapechanged into a bot, the AI can't just turn you off. var/mob/living/simple_animal/bot/polymorph_bot = new_shape - if (istype(polymorph_bot)) + if(istype(polymorph_bot)) polymorph_bot.emagged = TRUE polymorph_bot.remote_disabled = FALSE @@ -202,9 +202,9 @@ /// Removes an active shapeshift effect from the owner /datum/action/cooldown/spell/shapeshift/proc/unshift_owner() - if (isnull(owner)) + if(isnull(owner)) return - if (is_shifted(owner)) + if(is_shifted(owner)) do_unshapeshift(owner) #undef is_shifted diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm index 8a119fcbe61d..7c7f5c675989 100644 --- a/code/modules/spells/spell_types/touch/_touch.dm +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -25,9 +25,9 @@ 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 + var/obj/item/melee/magic_hand/hand_path = /obj/item/melee/magic_hand /// Ref to the hand we currently have deployed. - var/obj/item/melee/touch_attack/attached_hand + var/obj/item/melee/magic_hand/attached_hand /// The message displayed to the person upon creating the touch hand var/draw_message = span_notice_alt("You channel the power of the spell to your hand.") /// The message displayed upon willingly dropping / deleting / cancelling the touch hand before using it @@ -87,10 +87,10 @@ /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) + var/obj/item/melee/magic_hand/new_hand = new hand_path(cast_on, src) if(!cast_on.put_in_hands(new_hand, qdel_on_fail = TRUE)) reset_spell_cooldown() - if (cast_on.usable_hands == 0) + if(cast_on.usable_hands == 0) to_chat(cast_on, span_warning("You dont have any usable hands!")) else to_chat(cast_on, span_warning("Your hands are full!")) @@ -211,7 +211,7 @@ * * 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) +/datum/action/cooldown/spell/touch/proc/do_hand_hit(obj/item/melee/magic_hand/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) @@ -238,7 +238,7 @@ * Does NOT check for antimagic on its own. Implement your own checks if you want the r-click to abide by it. */ -/datum/action/cooldown/spell/touch/proc/do_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) +/datum/action/cooldown/spell/touch/proc/do_secondary_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) SHOULD_NOT_OVERRIDE(TRUE) // Don't put effects here, put them in cast_on_secondary_hand_hit var/secondary_result = cast_on_secondary_hand_hit(hand, victim, caster) @@ -268,7 +268,7 @@ * 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) +/datum/action/cooldown/spell/touch/proc/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) return FALSE /** @@ -279,7 +279,7 @@ * 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) +/datum/action/cooldown/spell/touch/proc/cast_on_secondary_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) return SECONDARY_ATTACK_CALL_NORMAL /** @@ -307,7 +307,7 @@ /** * 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) +/*/datum/action/cooldown/spell/touch/proc/on_antimagic_triggered(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) return*/ /** @@ -320,24 +320,23 @@ * * These should generally just be dummy objects - holds name and icon stuff. */ -/obj/item/melee/touch_attack + +// TODO: change back to touch_attack + +/obj/item/melee/magic_hand name = "\improper outstretched hand" desc = "High Five?" - icon = 'icons/obj/items.dmi' item_state = "lgloves" lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' righthand_file = 'icons/mob/inhands/items_righthand.dmi' item_flags = ABSTRACT - needs_permit = TRUE 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) +/obj/item/melee/magic_hand/Initialize(mapload, datum/action/cooldown/spell/spell) . = ..() if(spell) @@ -350,7 +349,7 @@ * 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) +/obj/item/melee/magic_hand/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) From 171022452fe7f3b3b89e964388c7560924a67973 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Mon, 11 May 2026 13:54:41 +0300 Subject: [PATCH 15/42] =?UTF-8?q?=D0=B5=D1=89=D0=B5=20=D0=BB=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐖💨 --- code/modules/mob/mob_transformation_simple.dm | 2 +- code/modules/spells/spell_types/pointed/_pointed.dm | 2 +- code/modules/spells/spell_types/shapeshift/_shape_status.dm | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index 4f3dbe5f7e22..5ebe903140b3 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -22,7 +22,7 @@ to_chat(usr, span_warning("cannot convert into a new_player mob type.")) return - if (SEND_SIGNAL(src, COMSIG_PRE_MOB_CHANGED_TYPE) & COMPONENT_BLOCK_MOB_CHANGE) + if(SEND_SIGNAL(src, COMSIG_PRE_MOB_CHANGED_TYPE) & COMPONENT_BLOCK_MOB_CHANGE) return var/mob/M diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index a6a489fd6d74..99df97758073 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -50,7 +50,7 @@ /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!")) + to_chat(on_who, span_notice("[active_msg] Left-click to cast the spell on a target!")) build_all_button_icons() return TRUE diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm index 0e3ebf01246e..1c6314c30d27 100644 --- a/code/modules/spells/spell_types/shapeshift/_shape_status.dm +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -174,7 +174,7 @@ owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE) // Only transfer blood if both mobs are supposed to have a blood volume - if (initial(owner.blood_volume) > 0 && initial(caster_mob.blood_volume) > 0) + if(initial(owner.blood_volume) > 0 && initial(caster_mob.blood_volume) > 0) owner.blood_volume = caster_mob.blood_volume for(var/datum/action/bodybound_action as anything in caster_mob.actions) @@ -214,7 +214,7 @@ var/damage_to_apply = caster_mob.maxHealth * (owner.get_total_damage() / owner.maxHealth) caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE) // Only transfer blood if both mobs are supposed to have a blood volume - if (initial(caster_mob.blood_volume) > 0 && initial(owner.blood_volume) > 0) + if(initial(caster_mob.blood_volume) > 0 && initial(owner.blood_volume) > 0) caster_mob.blood_volume = owner.blood_volume /datum/status_effect/shapechange_mob/from_spell/on_shape_death(datum/source, gibbed) From 5a67bfad65d5ef0520d45d65a2c01041381bba3f Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 17 May 2026 00:32:31 +0300 Subject: [PATCH 16/42] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B5=D0=BB=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Куча разных правок, пара новых хелперов и первый перенесенный спелл - кнок --- code/__DEFINES/is_helpers.dm | 5 +++ code/modules/clothing/shoes/miscellaneous.dm | 1 + code/modules/clothing/spacesuits/plasmamen.dm | 1 + code/modules/clothing/suits/wiz_robe.dm | 2 + .../clothing/under/jobs/plasmamen/antags.dm | 1 + code/modules/spells/spell.dm | 6 ++- .../spells/spell_types/aoe_spell/knock.dm | 41 +++++++++++++++++++ paradise.dme | 1 + 8 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 code/modules/spells/spell_types/aoe_spell/knock.dm diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 2d13e94dee0a..93993a8cb7ed 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -158,6 +158,8 @@ GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur"))) #define is_cash(A) (istype(A, /obj/item/coin) || istype(A, /obj/item/stack/spacecash)) +#define is_door(A) (istype(A, /obj/machinery/door)) + #define is_airlock(A) (istype(A, /obj/machinery/door/airlock)) #define is_camera(A) (istype(A, /obj/item/camera)) @@ -433,3 +435,6 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define is_cargo_shelf(A) (istype(A, /obj/structure/cargo_shelf)) #define is_crate(A) (istype(A, /obj/structure/closet/crate)) + +#define is_closet(A) (istype(A, /obj/structure/closet)) +#define is_secure_closet(A) (istype(A, /obj/structure/closet/secure_closet)) diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 8e6f60370039..1f93f93a6481 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -50,6 +50,7 @@ strip_delay = 50 put_on_delay = 50 magical = TRUE + clothing_flags = CASTING_CLOTHES /obj/item/clothing/shoes/sandal/marisa desc = "A pair of magic, black shoes." diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index 579ee0caff99..d32b91cfa978 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -754,6 +754,7 @@ armor = list(MELEE = 30, BULLET = 20, LASER = 20, ENERGY = 20, BOMB = 20, BIO = 100, FIRE = 100, ACID = 100) resistance_flags = FIRE_PROOF | ACID_PROOF magical = TRUE + clothing_flags = CASTING_CLOTHES /obj/item/clothing/head/helmet/space/plasmaman/wizard/get_ru_names() return list( diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 8c800b706fa6..1bad0ac2f0d0 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -11,6 +11,7 @@ put_on_delay = 50 magical = TRUE dog_fashion = /datum/dog_fashion/head/blue_wizard + clothing_flags = CASTING_CLOTHES /obj/item/clothing/head/wizard/red name = "red wizard hat" @@ -160,6 +161,7 @@ put_on_delay = 50 resistance_flags = FIRE_PROOF | ACID_PROOF magical = TRUE + clothing_flags = CASTING_CLOTHES /obj/item/clothing/suit/wizrobe/red name = "red wizard robe" diff --git a/code/modules/clothing/under/jobs/plasmamen/antags.dm b/code/modules/clothing/under/jobs/plasmamen/antags.dm index f51a3fa5c80b..a2cb9ca71a6b 100644 --- a/code/modules/clothing/under/jobs/plasmamen/antags.dm +++ b/code/modules/clothing/under/jobs/plasmamen/antags.dm @@ -4,6 +4,7 @@ icon_state = "wizard_envirosuit" item_state = "wizard_envirosuit" item_color = "wizard_envirosuit" + clothing_flags = CASTING_CLOTHES /obj/item/clothing/under/plasmaman/wizard/get_ru_names() return list( diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 979e942792e4..8f0f82ec55c9 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -188,10 +188,14 @@ if(feedback) to_chat(owner, span_warning("You don't feel strong enough without your robe!")) return FALSE - if(!(human_owner.head?.clothing_flags & CASTING_CLOTHES) && !(human_owner.glasses?.clothing_flags & CASTING_CLOTHES)) + if(!(human_owner.head?.clothing_flags & CASTING_CLOTHES)) if(feedback) to_chat(owner, span_warning("You don't feel strong enough without your hat!")) return FALSE + if(!(human_owner.shoes?.clothing_flags & CASTING_CLOTHES)) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your shoes!")) + return FALSE else // If you strictly need to be a human, well, goodbye. 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..834a85c65d80 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/knock.dm @@ -0,0 +1,41 @@ +/datum/action/cooldown/spell/aoe/knock + name = "Knock" + desc = "This spell opens nearby doors and does not require wizard garb." + + school = "transmutation" + cooldown_time = 2 SECONDS //20 deciseconds reduction per rank + spell_requirements = null + invocation = "AULIE OXIN FIERA" + invocation_type = INVOCATION_WHISPER + + button_icon_state = "knock" + sound = 'sound/magic/knock.ogg' + aoe_radius = 3 + +/datum/action/cooldown/spell/aoe/knock/get_things_to_cast_on(atom/center) + var/list/things = list() + + for(var/obj/machinery/door/door in range(aoe_radius, center)) + if(istype(door, /obj/machinery/door/airlock/hatch/gamma)) + continue + things += door + + for(var/obj/structure/closet/closet in range(aoe_radius, center)) + things += closet + + return things + +/datum/action/cooldown/spell/aoe/knock/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(is_door(victim)) + var/obj/machinery/door/door = victim + if(is_airlock(door)) + var/obj/machinery/door/airlock/airlock = door + airlock.unlock(TRUE) + door.open() + return + if(is_closet(victim)) + var/obj/structure/closet/closet = victim + if(is_secure_closet(closet)) + var/obj/structure/closet/secure_closet/s_closet = closet + s_closet.locked = FALSE + closet.open() diff --git a/paradise.dme b/paradise.dme index 62e253f446d0..501ed94693dd 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3941,6 +3941,7 @@ #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\rightandwrong.dm" #include "code\modules\spells\spell_types\aoe_spell\_aoe_spell.dm" +#include "code\modules\spells\spell_types\aoe_spell\knock.dm" #include "code\modules\spells\spell_types\charged\_charged.dm" #include "code\modules\spells\spell_types\cone\_cone.dm" #include "code\modules\spells\spell_types\conjure\_conjure.dm" From 9c33a7674635b70784b20f228c8ef866b43120d7 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 17 May 2026 02:09:20 +0300 Subject: [PATCH 17/42] =?UTF-8?q?=D0=BC=D0=B0=D0=BA=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/datums/spells/knock.dm | 2 +- code/game/gamemodes/blob/blob_report.dm | 2 +- code/game/gamemodes/clockwork/clockwork_items.dm | 2 +- code/game/mecha/equipment/tools/medical_tools.dm | 2 +- code/game/objects/items/weapons/twohanded.dm | 2 +- code/modules/mob/living/silicon/pai/software/pai_apps.dm | 2 +- code/modules/projectiles/projectile/special/magic.dm | 2 +- paradise.dme | 1 + 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/code/datums/spells/knock.dm b/code/datums/spells/knock.dm index 02d73b4ce70d..3c2a654cc15b 100644 --- a/code/datums/spells/knock.dm +++ b/code/datums/spells/knock.dm @@ -36,7 +36,7 @@ door.open() /obj/effect/proc_holder/spell/aoe/knock/proc/try_open_closet(obj/structure/closet/closet) - if(istype(closet, /obj/structure/closet/secure_closet)) + if(is_secure_closet(closet)) var/obj/structure/closet/secure_closet/s_closet = closet s_closet.locked = FALSE closet.open() diff --git a/code/game/gamemodes/blob/blob_report.dm b/code/game/gamemodes/blob/blob_report.dm index 7b7c90bc8fa9..ea1ad240d2b0 100644 --- a/code/game/gamemodes/blob/blob_report.dm +++ b/code/game/gamemodes/blob/blob_report.dm @@ -110,7 +110,7 @@ var/obj/structure/grille/grille_object = object if(!grille_object.broken) grille++ - else if(istype(object, /obj/machinery/door)) + else if(is_door(object)) door++ else if(ismachinery(object)) mach++ diff --git a/code/game/gamemodes/clockwork/clockwork_items.dm b/code/game/gamemodes/clockwork/clockwork_items.dm index 298b6e579c18..54caa53ad646 100644 --- a/code/game/gamemodes/clockwork/clockwork_items.dm +++ b/code/game/gamemodes/clockwork/clockwork_items.dm @@ -170,7 +170,7 @@ deplete_spell() else if(iscloset(target)) var/obj/structure/closet/closet = target - if(istype(closet, /obj/structure/closet/secure_closet)) + if(is_secure_closet(closet)) var/obj/structure/closet/secure_closet/SC = closet SC.locked = FALSE playsound(get_turf(usr), 'sound/magic/knock.ogg', 20, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/game/mecha/equipment/tools/medical_tools.dm b/code/game/mecha/equipment/tools/medical_tools.dm index 280bb5fb8807..262eeb0cb0cc 100644 --- a/code/game/mecha/equipment/tools/medical_tools.dm +++ b/code/game/mecha/equipment/tools/medical_tools.dm @@ -508,7 +508,7 @@ if(!action_checks(target)) return FALSE if(isobj(target)) - if(!istype(target, /obj/machinery/door))//early return if we're not trying to open a door + if(!is_door(target))//early return if we're not trying to open a door return FALSE set_ready_state(FALSE) var/obj/machinery/door/D = target //the door we want to open diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm index 0d716917b550..4f6a5162be91 100644 --- a/code/game/objects/items/weapons/twohanded.dm +++ b/code/game/objects/items/weapons/twohanded.dm @@ -1240,7 +1240,7 @@ if(istype(target, /obj/structure/girder)) var/obj/structure/G = target G.take_damage(extra_girder_damage) - else if(istype(target, /obj/machinery/door)) + else if(is_door(target)) var/obj/machinery/D = target D.take_damage(extra_door_damage) user.adjustStaminaLoss(stamina_drain) diff --git a/code/modules/mob/living/silicon/pai/software/pai_apps.dm b/code/modules/mob/living/silicon/pai/software/pai_apps.dm index fc497d267633..35f491898c3c 100644 --- a/code/modules/mob/living/silicon/pai/software/pai_apps.dm +++ b/code/modules/mob/living/silicon/pai/software/pai_apps.dm @@ -368,7 +368,7 @@ cleanup_hack() return if(cable && cable.machine == machinery && cable.machine == hackmachine) - if(istype(machinery, /obj/machinery/door)) + if(is_door(machinery)) var/obj/machinery/door/D = machinery D.open() else if(isapc(machinery)) diff --git a/code/modules/projectiles/projectile/special/magic.dm b/code/modules/projectiles/projectile/special/magic.dm index d01a8223a379..7ffd0ce85390 100644 --- a/code/modules/projectiles/projectile/special/magic.dm +++ b/code/modules/projectiles/projectile/special/magic.dm @@ -226,7 +226,7 @@ CreateDoor(target) else if(isturf(T) && T.density) CreateDoor(T) - else if(istype(target, /obj/machinery/door)) + else if(is_door(target)) OpenDoor(target) else if(iscloset(target)) OpenCloset(target) diff --git a/paradise.dme b/paradise.dme index 501ed94693dd..8d46a7c476d0 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3953,6 +3953,7 @@ #include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" #include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" #include "code\modules\spells\spell_types\teleport\_teleport.dm" +#include "code\modules\spells\spell_types\teleport\area_teleport.dm" #include "code\modules\spells\spell_types\touch\_touch.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" From e6c190882fc7eadf2e2b4aa25a318e2abce2ee36 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sat, 23 May 2026 01:48:02 +0300 Subject: [PATCH 18/42] =?UTF-8?q?GLOB.teleportlocs=20=D0=B8=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B5=D0=BB=D0=BB=20area=5Fteleport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/controllers/subsystem/mapping.dm | 15 +-------------- code/datums/spells/area_teleport.dm | 6 +++--- .../miniantags/abduction/machinery/pad.dm | 2 +- .../weapons/implants/implant_death_alarm.dm | 2 +- code/game/objects/items/weapons/scrolls.dm | 4 ++-- code/modules/flufftext/Hallucination.dm | 2 +- .../simple_animal/hostile/floorcluwne.dm | 6 +++--- code/modules/shuttle/assault_pod.dm | 4 ++-- .../spell_types/teleport/area_teleport.dm | 18 ++++++++++++++++++ code/modules/station_goals/bsa.dm | 2 +- paradise.dme | 2 +- 11 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 code/modules/spells/spell_types/teleport/area_teleport.dm diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 145abaa53620..2cb755b221e6 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -13,8 +13,6 @@ SUBSYSTEM_DEF(mapping) var/datum/map/next_map /// Waht map to fallback var/datum/map/fallback_map = new /datum/map/delta - /// List of all areas that can be accessed via IC means - var/list/teleportlocs /// List of all areas that can be accessed via IC and OOC means var/list/ghostteleportlocs ///What do we have as the lavaland theme today? @@ -211,18 +209,7 @@ SUBSYSTEM_DEF(mapping) generate_z_level_linkages(GLOB.space_manager.z_list) // Now we make a list of areas for teleport locs - teleportlocs = list() - for(var/area/AR as anything in get_sorted_areas()) - if(AR.no_teleportlocs) - continue - if(teleportlocs[AR.name]) - continue - if(!AR.has_contained_turfs()) - continue - if(is_station_level(AR.z)) - teleportlocs[AR.name] = AR - - teleportlocs = sortAssoc(teleportlocs) + process_teleport_locs() ghostteleportlocs = list() for(var/area/AR as anything in get_sorted_areas()) diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm index 9b8dd2b3542d..cf07482c2c45 100644 --- a/code/datums/spells/area_teleport.dm +++ b/code/datums/spells/area_teleport.dm @@ -20,15 +20,15 @@ var/A if(!randomise_selection) - A = tgui_input_list(user, "Area to teleport to", "Teleport", SSmapping.teleportlocs) + A = tgui_input_list(user, "Area to teleport to", "Teleport", GLOB.teleportlocs) else - A = pick(SSmapping.teleportlocs) + A = pick(GLOB.teleportlocs) if(!A) smoke_type = SMOKE_NONE return - var/area/thearea = SSmapping.teleportlocs[A] + var/area/thearea = GLOB.teleportlocs[A] if(thearea.tele_proof && !istype(thearea, /area/wizard_station)) to_chat(user, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.") diff --git a/code/game/gamemodes/miniantags/abduction/machinery/pad.dm b/code/game/gamemodes/miniantags/abduction/machinery/pad.dm index c78d0e34e1a7..4c4828e61b45 100644 --- a/code/game/gamemodes/miniantags/abduction/machinery/pad.dm +++ b/code/game/gamemodes/miniantags/abduction/machinery/pad.dm @@ -12,7 +12,7 @@ /obj/machinery/abductor/pad/proc/Send() if(teleport_target == null) - teleport_target = SSmapping.teleportlocs[pick(SSmapping.teleportlocs)] + teleport_target = GLOB.teleportlocs[pick(GLOB.teleportlocs)] flick("alien-pad", src) for(var/mob/living/target in loc) target.forceMove(teleport_target) diff --git a/code/game/objects/items/weapons/implants/implant_death_alarm.dm b/code/game/objects/items/weapons/implants/implant_death_alarm.dm index b843e055ce87..576dfafe8387 100644 --- a/code/game/objects/items/weapons/implants/implant_death_alarm.dm +++ b/code/game/objects/items/weapons/implants/implant_death_alarm.dm @@ -25,7 +25,7 @@ message = "[mobname] умер-ррр-р-р во-во-во..." destroy = TRUE if("emp") - var/name = prob(50) ? mob_area.name : pick(SSmapping.teleportlocs) + var/name = prob(50) ? mob_area.name : pick(GLOB.teleportlocs) message = "Потерян сигнал жизнедеятельности от [mobname] в [name]!" else if(is_type_in_typecache(mob_area, stealth_areas)) diff --git a/code/game/objects/items/weapons/scrolls.dm b/code/game/objects/items/weapons/scrolls.dm index 8bba8c78b4cd..2c477417953d 100644 --- a/code/game/objects/items/weapons/scrolls.dm +++ b/code/game/objects/items/weapons/scrolls.dm @@ -49,12 +49,12 @@ var/A - A = tgui_input_list(user, "Area to jump to", "BOOYEA", SSmapping.teleportlocs) + A = tgui_input_list(user, "Area to jump to", "BOOYEA", GLOB.teleportlocs) if(!A) return - var/area/thearea = SSmapping.teleportlocs[A] + var/area/thearea = GLOB.teleportlocs[A] if(user.incapacitated() || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) return diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index 79cdb2a27337..cc029824826a 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -792,7 +792,7 @@ GLOBAL_LIST_INIT(non_fakeattack_weapons, list(/obj/item/gun/projectile, /obj/ite . = ..() target = T var/speak_messages = list("Я слежу за тобой…","[target.name]!","Уйди!","Ты слышал это?","Что ты натворил?","Почему?","Отдай!","Хонк!","ПОМОГИТЕ!!","БЕГИТЕ!!","УБЕЙТЕ МЕНЯ!","О бидай набора се'сма!","EI NATH!!","Kchck-Chkck? Kchchck!") - var/radio_messages = list("Ксеноморфы!","Синга вышла!","Телекомы упали!","Они взвели нюку!","Они убили Иана!","П-помогите!","[pick("Культисты", "Маг", "Генка", "Нюкеры", "Ревенант", "Труп", "Драка", "Я слышал флешку", "Нужна помощь")] в [pick(SSmapping.teleportlocs)][prob(50)?"!":"!!"]","Где [target.name]?","[target.name] мёртв!","Вызывайте шаттл!","ИИ взломан!") + var/radio_messages = list("Ксеноморфы!","Синга вышла!","Телекомы упали!","Они взвели нюку!","Они убили Иана!","П-помогите!","[pick("Культисты", "Маг", "Генка", "Нюкеры", "Ревенант", "Труп", "Драка", "Я слышал флешку", "Нужна помощь")] в [pick(GLOB.teleportlocs)][prob(50)?"!":"!!"]","Где [target.name]?","[target.name] мёртв!","Вызывайте шаттл!","ИИ взломан!") var/list/mob/living/carbon/people = list() var/mob/living/carbon/person = null diff --git a/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm b/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm index 8e7f3c0f9cf7..a9cf16d3a6f4 100644 --- a/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm +++ b/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm @@ -79,8 +79,8 @@ pixel_y = 8 if(is_type_in_typecache(get_area(loc), invalid_area_typecache)) - var/area = pick(SSmapping.teleportlocs) - var/area/tp = SSmapping.teleportlocs[area] + var/area = pick(GLOB.teleportlocs) + var/area/tp = GLOB.teleportlocs[area] forceMove(pick(get_area_turfs(tp.type))) if((!current_victim && !admincluwne) || QDELETED(current_victim)) @@ -412,7 +412,7 @@ /obj/effect/temp_visual/fcluwne_manifest/Initialize(mapload) . = ..() - + playsound(src, 'sound/spookoween/scary_clown_appear.ogg', 100, TRUE) #undef STAGE_HAUNT diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm index 47342efa9d4a..71d0b2acf847 100644 --- a/code/modules/shuttle/assault_pod.dm +++ b/code/modules/shuttle/assault_pod.dm @@ -37,10 +37,10 @@ /obj/item/assault_pod/attack_self(mob/living/user) var/target_area - target_area = tgui_input_list(user, "Area to land", "Select a Landing Zone", SSmapping.teleportlocs) + target_area = tgui_input_list(user, "Area to land", "Select a Landing Zone", GLOB.teleportlocs) if(!target_area) return - var/area/picked_area = SSmapping.teleportlocs[target_area] + var/area/picked_area = GLOB.teleportlocs[target_area] if(!src || QDELETED(src)) return diff --git a/code/modules/spells/spell_types/teleport/area_teleport.dm b/code/modules/spells/spell_types/teleport/area_teleport.dm new file mode 100644 index 000000000000..e56dcc98a05d --- /dev/null +++ b/code/modules/spells/spell_types/teleport/area_teleport.dm @@ -0,0 +1,18 @@ +/datum/action/cooldown/spell/teleport/area_teleport/wizard + name = "Teleport" + desc = "This spell teleports you to an area of your selection." + button_icon_state = "spell_teleport" + sound = 'sound/magic/teleport_diss.ogg' + + school = SCHOOL_TRANSLOCATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 20 SECONDS + spell_max_level = 3 + + invocation = "SCYAR NILA" // gets punctuation auto applied + invocation_type = INVOCATION_SHOUT + + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + + post_teleport_sound = 'sound/magic/teleport_app.ogg' diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index 2a1d2d98062b..d2a0fe2d62cc 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -678,7 +678,7 @@ GLOBAL_LIST_EMPTY(BSA_modes_list) gps_locators[G.gpstag] = G var/list/options = gps_locators if(area_aim) - options += target_all_areas ? SSmapping.ghostteleportlocs : SSmapping.teleportlocs + options += target_all_areas ? SSmapping.ghostteleportlocs : GLOB.teleportlocs var/choose = tgui_input_list(user, "Выберите цель", "Наведение", options) if(!choose) return diff --git a/paradise.dme b/paradise.dme index 8d46a7c476d0..007641d309b8 100644 --- a/paradise.dme +++ b/paradise.dme @@ -595,8 +595,8 @@ #include "code\controllers\subsystem\processing\obj.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\projectiles.dm" -#include "code\controllers\subsystem\processing\station.dm" #include "code\controllers\subsystem\processing\singulo.dm" +#include "code\controllers\subsystem\processing\station.dm" #include "code\controllers\subsystem\processing\supermatter_cascade.dm" #include "code\controllers\subsystem\processing\transittube.dm" #include "code\controllers\subsystem\processing\wet_floors.dm" From e1b91e7c3e1fa06080a199d50d3b637e970d4f55 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 26 May 2026 12:07:58 +0300 Subject: [PATCH 19/42] magic_missile & blink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавляет заклинания magic_missile и blink а также projectile/magic/aoe --- .../projectiles/projectile/special/magic.dm | 70 ++++++++++++++++++ code/modules/spells/spell.dm | 8 +- .../spell_types/aoe_spell/magic_missile.dm | 54 ++++++++++++++ .../spells/spell_types/teleport/blink.dm | 23 ++++++ icons/obj/magic.dmi | Bin 4253 -> 43823 bytes paradise.dme | 4 +- 6 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 code/modules/spells/spell_types/aoe_spell/magic_missile.dm create mode 100644 code/modules/spells/spell_types/teleport/blink.dm diff --git a/code/modules/projectiles/projectile/special/magic.dm b/code/modules/projectiles/projectile/special/magic.dm index 7ffd0ce85390..93b407c33ea9 100644 --- a/code/modules/projectiles/projectile/special/magic.dm +++ b/code/modules/projectiles/projectile/special/magic.dm @@ -21,6 +21,76 @@ PREPOSITIONAL = "разряде пустоты", ) + +/// Gives magic projectiles an area of effect radius that will bump into any nearby mobs +/obj/projectile/magic/aoe + damage = 0 + + /// 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/magic.dmi' + /// The icon state the trail uses. + var/trail_icon_state = "arrow" + /// Can we spawn a trail effect again? + COOLDOWN_DECLARE(trail_cooldown) + +/obj/projectile/magic/aoe/Range() + 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/projectile/magic/aoe/prehit(atom/target) + if(can_only_hit_target && target != original) + return + return ..() + +/obj/projectile/magic/aoe/magic_missile + name = "magic missile" + icon_state = "magicm" + range = 100 + speed = 15 + trigger_range = 0 + can_only_hit_target = TRUE + paralyze = 6 SECONDS + hitsound = 'sound/magic/mm_hit.ogg' + + trail = TRUE + trail_lifespan = 0.5 SECONDS + trail_icon_state = "magicmd" + +/obj/projectile/magic/aoe/magic_missile/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if(!trail || paused || QDELETED(src) || !COOLDOWN_FINISHED(src, trail_cooldown)) + return + + var/obj/effect/overlay/trail_effect = new /obj/effect/overlay(loc) + trail_effect.pixel_x = pixel_x + trail_effect.pixel_y = pixel_y + trail_effect.icon = trail_icon + trail_effect.icon_state = trail_icon_state + trail_effect.set_density(FALSE) + trail_effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + QDEL_IN(trail_effect, trail_lifespan) + COOLDOWN_START(src, trail_cooldown, trail_lifespan) + +/obj/projectile/magic/aoe/magic_missile/lesser + color = "red" //Looks more culty this way + range = 10 + // MARK: Death Bolt /obj/projectile/magic/death name = "bolt of death" diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 8f0f82ec55c9..391068dc2ea2 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -101,13 +101,13 @@ 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, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(update_status_on_signal)) + RegisterSignals(owner, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(update_status_on_signal)) if(invocation_type == INVOCATION_EMOTE) - RegisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_EMOTE_MUTE), SIGNAL_REMOVETRAIT(TRAIT_EMOTE_MUTE)), PROC_REF(update_status_on_signal)) + RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_EMOTE_MUTE), SIGNAL_REMOVETRAIT(TRAIT_EMOTE_MUTE)), PROC_REF(update_status_on_signal)) if(invocation_type == INVOCATION_SHOUT || invocation_type == INVOCATION_WHISPER) - RegisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal)) + RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal)) - RegisterSignal(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) + RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) owner.client?.stat_panel.send_message("check_spells") /datum/action/cooldown/spell/Remove(mob/living/remove_from) 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..383838b8690c --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -0,0 +1,54 @@ +/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/projectile/projectile_type = /obj/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/projectile/to_fire = new projectile_type() + to_fire.firer = caster + to_fire.current = get_turf(caster) + to_fire.original = victim + to_fire.preparePixelProjectile(victim, caster) + SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, victim, to_fire) + 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 + shuffle_targets_list = TRUE + projectile_type = /obj/projectile/magic/aoe/magic_missile/lesser 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..77a864a72020 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/blink.dm @@ -0,0 +1,23 @@ +/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' + +/datum/action/cooldown/spell/teleport/radius_turf/blink/slow + name = "Minor Blink" + desc = "This spell randomly teleports you a short distance, you're still practising doing it quickly." + cooldown_time = 8 SECONDS diff --git a/icons/obj/magic.dmi b/icons/obj/magic.dmi index f92243d6bc1afce4da6b4551974c8c64ccc1a4e6..9bcb886d8535e2943ed86cd23c01c8cf21290305 100644 GIT binary patch literal 43823 zcmc$FRajJC*zeFviV7$#AxNrp46TTibeA+kOLvDTDIhs0-Q8W%4Barq(A@(J%$fOr z&v$!n&c(Ud&&;f~WAOl$$gSh1pqLN(c2?jR7O7i$N50Kg-CIex^Z`|Xnu$b=fl0AC0^KbOKFNb&TQXK0o-m0lLO zO4ao23J5c1NsRR7l)~#2sP49!ttb~yV^j|hGl~nkBF6r;NMjkSS&4`7VN@rkX@BFM z^zV;Hn3|eyFT8$!EEQBs#eCE+*c@B)LZV|)#1j8!OJKkCGjw9@G%3 zlV_s)RYy4u$XC$lz{>DF2!HTzfOV3dwT?}aPT=98LY$S!t@ z*bbFi0V1N9j-)av$Q$7=$mP=iyjt%z>OT)+Zx8=~X#KiT1d`xT?gG23F10cCEaYEo zLiz>1y}YsDlB9IO9Ja8Rjew^Dp~0^@+`3i>$|&$X4L$mMN$4<0v*?&G9GTs199_pZ z42Jzt*Rt9@rUrneT4>AQv}~o~fbpl|^g1S7>kyI0qCMwQtO26CERJ}0Ap2H9Mu-(AEuJR7z+ z0`9ukM3uaic>YI@<--c~uJ&I8rmpc zR(1{eoohYDp%s_Chsz@`uqLE#K%y0p&b|f-X6-i<-cuMO5jyqWN`HPVX(@)lHao#6 z%3FJE!9#(s@+;#I?71um7Ipbk787Y76#_~nj#w>0S!;`6+Q_1 z4cj3;2w=B&eV{i6d>Kj#%G5(D@WJKRj@{o#E035Bt2nFo};mx8TX2eo-w#5TtGN z`0A(w1N-;LlJUTvqd9icDlmfa6zhq@C#{cvdCo?b8lQ~gj7u+U4T2={(nKKy+w_LP za*oTsu~<$|`V-px*9lxhanj=)w#Y*xNM59b9tLP+P37N_ukTN7o!u)K2nItUZ zdH$Ve!Dk%C;EL+#Kr@N4HbaKnnmcLs_E#Oagn|JdTC9kgzQ;VmxH;Uzh$U>3ap`x^ zB9bU6j;!;^@FcW7#kmpb)Mp;NI-$a}?Fa~L=j@k&aVfoLuW*0Di#>-5THGb<>?hs- z^93EPZ;d+?-~MmU|BnVY>3y~iuWwCJ;1l6Nhes6{Sd^#APXXSIyQYqO{#KtZLrn}k zshwM*^$DZ{kXvx%G^P`UI48i&)5HA{;J%(6u0!la!>@H_2}={I0=ypDAKu8i+5Mn0 zK;juQV+vm^f4p7x;ikW83YAd?3(B_g)`mA565+ekR)PLv;>~BAzRJs$%X* zeb^jeTOZGP#M&xN zqc{c5n)Y7ogvYjF5TCZJ9^`h;hsSFBUsNr@JRS-W&YEC$Yzd;B{Xg4&)A$7A*xp)r zZokm;+#(c^z^dYQaW{6>8b2NPjrR=XEcxV_2s+pSA)gm1PRma5#wnP1b3ViWO6&6w zF=TPuer!u22=(9?#S{{_UHh9aCd3k`qqK!ihrIqljeE$JgpTOgy<;THDfjvZKjvg< z)B)MYlE9l?YfjJ6euvcXOwyfQa4y~p5CRp~$=%e!#qfdqce^=lWuyyD%m|olG}80i zG*97-KT7M#Ur(u}4?zS`|{+~SSZ2E$%4W#yYZR)VqI0a3Y= zwiX!k(ka9IXWacqd8uSPbAbS!vcTq>0lxu>p(aKvKT8^{5l9mw3_foceG=b3eU@-I zx@zef=C}M3is9>5!XB|z84gi9q_Q@GlsuuwTThOq947d3@^klb*M}&_+|>r-TV-We z9e-_`3c-8KU+qjliK{fmZ$G7_0WhsoMk5EeT}t^oU&F0AD<#V%ydzlo5_s4(v3U!2 zH}-4>9{J%TxH_{>LJ)QYYP4*3u5f%OKEFliC$Z9-?E)@zA}Vr|93yuG@ZD!)Vf2vv z73d%Nutfa$N1?J_=cuD>tThR#&Xqj9{Ucz$AFumnV|P=9U^kh}ImOxFT~ZniLJdCP zM^Xr52@9fK3V^rLt`|p36ZdfTHQ`*W#?(0-T95S=;E9hv=R?$_^{e>JWSEhNAI5(P z+TyLT0+omE9EY&Pket$S7s(w`DZw7G~veV0=nWNX!K+cDb>NwvMeu!Y5iavu2n zFhMP;i3Iq#a~W|^qkAZJ`MqngnFLrCm|eyzTi$83dwz8wKjNM>h=L;Ueagl^7h0x>p?UbPCjNIK$l8D6C`Vri0*`5o8K=9?$s~ z&WM`YzPzp7dtgOUEN9$J{^?|!wvb;db|0zVjAdamLC7-8U263W&sj0%!)F99_sA|Q zlEKht1x85A?3Zh*5|qbVGi6)}d(YC~#bv`9fX(vQfzR(mN~*12nL2aXLPt!aWcn-2=}{w08VdU0fNGhY@(yJ>JkVj~yQh{q>%0^r5Zyj6 zT#U!!BZ-}&4xA>xT^eG zkxoH;L!%d9A`1Gyto29O!GF~D^$Rq*VPM$@pd%4IOL#q`}gkWqBakFK1*a0#SUD<)UkoEQDvh^4dU$2({re7M=W2XLL04m29 zqxv|Zy#-BwWH$yUwpqHWKqv-{tZ8+S3~a=3;F+dE0`8xpjX^M^e>#uiG#XyX# z-XOH9l)Il1lx^cA`m}?z5I5)Zj9i8Y-p6fF!89Qh-e5=>DI1D_1rrY5y?U&_sn%ikD9)NLAB0KnJgYza&>y5R%yQ>+@2cSJs zFy8U`0qFg@Y;VGx9MA(6ZPea>INS@fuoEJ-l#X81xVMVzU0c(u=Nd_NhDMP`^Q5Hv z5)Sxvwp);I*VAu{uvJW(16GaSry%yGzw!G&^=a3MXoisby}u+AOc>xfSqryTnRs?^ zEz-1B-FwbWcO_qRa$6MNe$-6@;TAN6zC^saZndg(wTCrh1TY=n_E97m`YMndjR(dU z-~c$clUO|vEK$`h1$apGQ0jegSu^1T%dv7SRkG4&<>b|B`xL z_d*ho8|UZW*tiT^8W6krMpRCx>-qAO0<;IL*A_J2dI{Oe1N8uGLr^mXyb zZ7_xI+4;Ga%g$TiQBUNsd8saQ@!FUz20LGlTOQx*`lW~oXZlf?5o$E8Ixk!3lhXVI zGn}Z+z%UFJ1OdJhul^UuziWt~jZ3|7!Ek@|@02BHoQP-By>F`Ha0Fd7rfsq|HW%}M@8r~qgxA^DGu`t)DG~9doY==e9-2r){m4A4$e*%zrO$hvcln*0r7B5iGg~k zA62!JsSUILP?8+Gu*+{^fK4yOmp2^;E(vwQyL^@X-qZ1m1Xb9NIRBNw@B%n8DOC5w z3?&}&XA35ueV*}s4I!wZi|$B?lA2&`y*R9oe2qlLBW2}A3bu{J4g`>ACgQCPJW=O* z{in$=e>J?)kOoS znCr`rTH4=8*@Bj*j!T}99F6?GY=vnexNrS@ZIfx z%A}{^@7|Lk9InUekKc0Ed@kQ_rG0Mf8z)zHC#J(UuYriP;1M{!y$<9>YWZCDO40Q0 z;6jpFGnA|5hsV{rWl)#uIPYS>a}Wzsan&`X)X3k=#hE=1f*C@JSiQo?@(D93hDEu5 zlwD8RRgJ7*yc=kKZ~~PNJ)V>mmd>y8FL0GCMFu+sso;6|NB=o)a z6&B!h8)BKzF0Ojh%J13ad5%qB`5GC6Z|j=UP*h_qxA8A{!w=y^djqa**sQ3LOL)FAdW7Zz}w@&dcKQQ7O|D# z_@Pqrla{yN*XcCQ*9Zz&xh*+6VF4D~?+KK}jk#HV=HQo?lcx?E0s?MOPb?3fAd1zF zr=?O^abTebY=BAJz31^T{KS{m_$#YUVB0`MQ>3#iMa86YY}~Px)c?WfYT~uxSAa@D zU+TzG0i_~D8y^BLZ}2>%<_DiTg z!)-KEqLZEqVey<6%j2WZi)Bo)G(#3(TI%;d`P-_Z)t zM5Erm8p9Y?s<%kHGhICEz+*WI8yprE z3f|njshW^JdW0-Mp<<%ZlbhC{4-c_**UQ)Vz2fV8)0gslvoS8PS6WhN7|Ly@5voGX zqu(x>6n?@Nlgh6k;g#!8X4x28gPSe_wXKtulc6$$W{!5` z!(G)(sT$i23H9&Kh;k>WKXRa|kB%-I4;jI(C8jr#)d^teI6iH0bRJ3NBeNV$*I6lB zHfw|s-klBYvKx9?7wHmf=sYQ5wi&^9`}cnSpr}H(M^l-7Y39M~hN|sL5_+=A!gbj7 zBE@q%C*311<%6}yN4K<|u9|_vSPn)sXix+q{zi-Vrogj29x$0yqxqv~@@yiAN?bto z?g5TKq~~^aj$iPvL#BBIbFp(DiB@K1jEJK%iQdD~w`cQ*bSEtxO}OcSf4r|0n09TZ z>-;aLeg#Bq~Fu$<#>P`kFe*X>d%ok zEMF2m9Daf$jtf#rj0!}PjSWu-ZRS-8#GQQ6eqrxnwIznDpD07y`5S)&lp*4=3@yvh zJwnG=1k#-rLO>=knvMae6)Hmm#N?X?bWl;?U|(F=&$P9sLeQYXa&u>DEgosUc{jb) zzy+FSOSz(8!Q&N4rXD+38pvL6H6dVv;{v|r-v4NEvtt}^tO(tN00rWRf|xyV{rvaF zl*J2ge3l}gr3CEQ(5Ii~D{<019M;nn+#m+a44|g>y^qO9OEi7t0QTq@dStm&X-Y@5 z+>RvT0~^u26hazgQdl7``}OMUFaYdr0b~TfU4hTfTApM{`CRH|rn(6+{WgF=j+8mu zGXp;{Ed6;U0RkK@_0W-F!u~WZc2a(v_JMZ=Ew(Qo7eHf9l!-9*KyanVcELX->j8{n z3+P*B4>>z~^T&0cf2Kou6l!P~#Lq6L?U)q;CRr*Dt(FLOaCcoerHSsdj8-!Iy3JzP&$n%cnCf;~+-51~g8=hfS1 z1iw#a1=55-g`&LVa_cDUliE+7*2(z%+x<}c5DiQ<5f0?sng8X3L)Q~pg}CZNNj#EEn*cwp5t@SOM&poqiDTXHaSTPclS@z7GYZLH4jtB^Cs zsxRJ6q&U#`KwBeF4D+d@T0*1AB&O!A5$DBidh<;w&O&+@1Rjn0yb!|`0>1<42qr*D z!IiX#$RNlo;2qVI~KRwz^!0FI*A_1SYpr&{dRX=uJUys>wqy5BudXGYhW4RS8WE2niA#n#o#<#|Z#?5ra`nxh&#w_r^Cx`p zGQ1)U?5RB+!R@;et&c^V=Y2%Lw=Vx}Vy1lyWPDapoReZdA$|9!_JpPLn##LZV4fSU z6@UJD8`Dz%VNkvnp7iU$RmLZ>Jbe@gx2H&s54^2#?RTv!QG_A8(p38sE*4nCrj&-D zMK|`i_e>Y>4?taLYuQeL&Su1B@Y)CmM4_5Iy3I8yd3&e?j>$GTwll@9Cs4i8SkZ$w zv;}YaE*`ZVD)SRTmelMn$M-wi+PDW?7-|Gjh*kD5gdgVYgllf-C2vf`4|IIxf)M1K zE5+mcH7dl8*XqI+)o29PCvt1`QP~l^v(kEZN>`?@!uVANu0vM=ynkhZ%A9O0EG)dj z!d^c<3&_N4eW3+Zh#7momt0x%T)POuAz1i9$@f_vH&mY+-@D|t-owyi$abs7;>!+S zaKv)6`$Q3!fkkWIIi}uCeh6GGKdB1>Q9k78+Ov|S&BLyScenlfcs+k+*71%&kgtI) z>-F^s`|0H3!KgTq&%xeKM_<`%-L+&9S|LC0=vM>RZAJj&8r+9pO)(VeJ<62)?*51WmejA)L>DdV%k94jq}$p`c5lzk)_iEoA^v4MWXp;ju1g=X>8XFzrW_J#V zYoFlvHb7APFq;#^ObGOFT$tDz*+|C4lkKyGKG+Oj9hOg&ClHcQ^0TkOO5WW1TWSQo z(TUc**}VFbWHgFrFyQcpJ3Kr;zZzfC_NGG?r><$(6MhFlo;RnKtS$rK4mt z_V(^}J(^5=ohKMJyF83wk@{Y!lNf{v&BNUf6;Eh5_@=vYEht} z9=Wu5warbVqgs{B)gCSGFSZCfK5iFeW$lU+br9;`9FG5seddp5(82BP$SRAG^{HF; z>7X)E#t7u)><4+hnon6VclGKpyl@|SD7zW>_jdUf#C;doRc z(MY>6KU6bk%C{xp2*a5~+^79byYmq>r>3r+uS05Cl4grExI&3(~_o5#QjLXmBIF*M;Wvh$Z0PWm1HNJEg-&@L||)B+w z6O+%Lgyu~2u9E#?*$zuJa%_2!l3#0QV?`yobz{6{e0)c*}sw1>09gVtsB;CKr^G$)GlB{t5XkPVbme z>QkPgzz3ygzS4oYYT%fIoHU6YZS7v9-}6~cd9XAj1tczX5-c`ld;+~0k#!R7V7r>p zl$N3qUaQ#-!qA%XUP(z(8*ht><0$kLng0HzrRBM$u)}$a#CL(O0uE)G#HCX_(yni+ zboU`!qr6f-FF}V57pwJK-yL!D`TV?^MQ*lFTTY@9v$AL>ot>j|wW4W;*xE0dfYXSO z?NFl_3UW5{d_iGO<+DeE>ng{UeOoBGDI33UEzqH#NulKD{?MYj@rMa|=cnUw9V2_N zMysQHdxM@FIfP!YS`bxeRn_|}8@@2o?a|UR+bj46!4Ls$@@BG3+J)l`=EP!94%R=g zaP4#QC>%gU0||01{9@B?Ax8&D^3U!sIhw&3EVOuv%HjPGaXVoA)6$Yoe)$J^0})|2 z@;Oy3-tKHroC@NkeVLSYm-NNZP>I6^@S^G4Hi+H(;)_OxC6QZc#49|@$64fl9Y2HS zP%=-Fl{0a_H;V=Q)E_0PF^g``no{Ws_Fm+%h^I-KjI?IE7TFQxFr^*gn2g}8MsR;2 zo;mv?i^8VMg7Jd5cz_5MBbR|LsK_7c8zqXpGQHHpz{h7ZW#v?=+nTsySq^x{N<`l? zh;ECp|IFl`eqb9CYL7DZB!nRZ)?8K%e?uBkYU>*#l0TEeOC$xFO5%nEA-H#iSrVvz zvWSc)aU+5d1iQeLS-DnP`yu62H%5iIjDfYxAqN=js|W98I1b);^Z!@dYNAJ5RO5CG z21M}!2V4&Vw7wUE^Yhh~me+f^O)iW7YcNt9Cas`rV}x^f|JPbrV7n0RXy)?BRdafH zLH!E;H!`jEccw=J-qGoTD&Hz2ml1Mu*Bs@{?g}<0k50q^VSuM>|GO5TQp9a1>CTEI zti&J<##XlDd43D6w%b+hHS=hFcXEH-4DAMIu0S*pG6%TahN8c1^+Zg%?6ppxRZ?9H zup}09mwWG*>Z5+kjHGJl&{saRUYEF8Pd-Z{=R&*$Fyt#1b4bji_Jp$gcL}?Y8c&3c zlD)VEBU`%dsr)W>^pj7Imu@J(Ouv8fM4rr`SKMs5$t=s}R05zqUA;}Mbt=oPM@|5c zzCQL3=1_0&TI~oeLzNxot2W8b+?9NM>T0j|B5TXr+Xw(*Fqpcg;AT#)oio1!_?7e# zUQy4{WE>-3y_O4hD+A*`md4k5i{alkhy2~9rsnchmr~a!C&ChmbERp#g$oRYt$4)h zt1n(f zm6#e%$(U8lw5??+tbTIyj|zz!aCzKz=YIggg8H|Mkm6eYK^B*b;Q**mgt7TIALwZ{ z9?@WtDE|QEiTy;*KtAKg5;t6bs7f~Na*J`Na?b+uU#imukn%U`K8ygZjekFNOvZkS zi?!Ws<;p9Qf|4Wb+kgF<34!tm3jQX;ZIxUi>e(Zrd$+#EDW8NO7jrg?nDxD0uJ^mW z;LaFOZUZq$_MDZOuFzTv)*jACA2M2gx`S+5O5L_Las;7c{{yur(^YWb;o?@>$mlF&2hd_>6vmm zqUa6H>tk_99NCv?<1rTSg#nMnutgK>t=D{&cJxMrayW^R5E}T=FeelR4&V~{p`?6_ zM?w;lKAt_>cQp5Ig>1$O4fpXjX$3mfO|jvbWFKWon;oFPG%|!oDy}jy9NKQyEstBy z=>RWU%m$tSakTe!^0 zKzu+N1qXZ}6LK9HX}x|wCQULgl6Cnr#(@}+mdph-KlJ4{c;aG-OmAq=kkF820%)Vu zn7=FX;T#YU!3_kuJ!c%v(EIT4nuNmOIO7>b{odqSw-zRV6gua7m)CQ&D1ajzU^p8Y zPo|w_eoEG^XBFTcHB8Xe;Vzad=KA|8BPo*!%lxTl zy&`OF_eQ>#Z$Rr*e7;_IZh8!t8K^d2*`Hohsql|VsV~*B9L{#7sZt8;*~62C%kVediA3J7vaHX0+%EvGl$?HX?ZvI-3N?`Alh{+x#IZ9kXYd&GXZB^Sz3bKa~xf+9a|z~S%| zONdww1u`~0J!E=(JPKx*wz_r-o=@7_H&nbh6}m8t^ef&aHf-^zh%Y~$7Dq0fn6!FL zW2$aAtSkxVyB&&oo$7V7i{IC*ZphNnd~l%hK7*~tNJ)Kc3KHKO;?9S7@8%Ykt;S=e z+%`vnGjE%*E~cQIh^Z+k&CX1|?r}b~qGShd;zfWwtqiIA8fi63-!0mB|;op5Ukww??@*p)N z6!~y{nJpVrwf)X&tt$`|A3vje@AGw?F|Deqqzq_DwD$NhXHcfsUSSZ}?=}(`BT45w zWjQiesh21wU$R}wu9;q$=D#zBw_jtwd^=O9mjCG=rsUL*ml-pPDOJk5)vbTCgX_0p zF!t6mTvX1pRd4EgzfrADJ0Esr0^9s`|jh(k6Xxk6x@z)8e&7ORy@JvRN3cksv*9?Pojx9C|>bkoAPF8@V@ zo}S+80rANzE!7(z5MlM<2c3hK5%kib-H!O=xzIj>!CNa82 zd~3$4O%uC;-JOs!7L{goJ~&*S@9v5ttohRpX=M1#&F?2zUgte-<&@j3#R}kzBER=K zTy$tpq>?F#G~Hr=NX3azw&lm5)mJdtM8WJ41l&fsUW^^1AnF4q3Q8* z%Y0IfZ`>#%8e7gbkhZiB+S+UxUGAV2fBON`w2lK`sK~39Tybt084pn=u}T zwfO>mC!O^HiS*Q@MgTY;GjnLfF{%WxvhMlH?fU4kGY(*;Cz-fl9uw6qDCDv;RzG54 zKNrXN?d$bX2JhxtICrd%HfPp1ce^vS#*Fs-oSb3!|V@iT?SCV%EeU&+um+BRIiCreE2U|r;qfAcBn3ve!N__&1-0AK*WeY zyzss{_*{28m@@szQv3mK3V8DU`*#jhDz-*RA4%9$A@}&PQx4?-ITwdc6*hnk)jNnp zCFftQ-Yg$lu5@byj69@*iZlWb2W%QDEJi3L5D!D#Ms4X07K&eGY;0IL+t#GR#R@Ep zbbITS6%-Q0>ul#$2mCh&6G>B2RMr?}!^YN&`AOWDVFO?gDKt|>g=pZ1RD<`meLD=M z*$wqB2YJ{YVAa5{A|k%D9)ZbE4^c#AH4!x%p90v9R$8fODP}p`&R}ln5SNK^s9$LX z6)v-o8g+am$JR4`m#`f=siULQuAItijrQj^Rm(p838Frt74y+Pl*GIy*XH%UoYvBE z-F?4*P@+?Hp*W^$LySjt$Coc-M0v!_pI zm{`j1P%Ok1#G){#sMl^bD9=I7yae=8cXdgJA?(w6wjTkoNoYkqM&PF+4|Axq-tJDe z=ad?~-=sO&O5+|pR$NCucuzlQyzjS56*!g!SUsnuZL{B-!b&El7OXf8CTYtpHHt@C z54ryBZIvuK`czS_lFVf=14(t=+500M&TnA`8$VvIa^BPdM7#jrz60Hl*Ec9Q{dE9(TXEAh5q4i}iBM-F8J^BFI=322?umt}^_03F3n5lI=+UA+su@3=~Y)l?n1-Ds>lj0m*Nh>4g@7Y*iShtr6OdZP&`VAWMpuz~DK6s!KhKa_eIpOEl2 zTKBu?=4z+4jR7^)Hbk5lV5*$n|I6E;&^C1ix!N0rCqOIOP&F%c_U~sQY9BM*%+EM~ ztjEgun4cJNclI;Ohh4}ksMmJAPf3bdXuH?As^v5NXFDn&t@?@iLo7?}O(q`SkL1PA8snxY#Y8dC@;ZZxZ>FaL4 z*=j`~pc{zd|>dl(?FsU)uS&`&CXOubs0#D!k||AwU1=Q=Bt051tF z^z9=^J(Dnu3{~;;*sF$!d0!$Qjx&dG=Qk{hwsA)?7Mo!Q^VPa)&HVxFOHHo(r#J-U z3aTkaqg&A=9@opsLnty;&Q}|?#Divle^6@-z-$G2zQ2hGNq&nq8p!$iwarX%sSeEg zqnI-t4eBx%VMH1)>0S63B#$M7j~44JM(A|masjo_-|u3?-}}H%bd*K^3<7e3XL-G3 zWUxsELMdYbp!S{~*;H8>8K1Gn>LQN6{nnEnjW2btQJMZQe(5=HNi%&aKMgK`V10v6 zL!%cg4kiK|pS2j5!!bL~`0XYD#!l+L@`~7=+M=cSQhhEVzPE(4XZy~)TH>Y705{HJ z*ydIn-(@6={hZ#@<|@$awI&I5!`M<=YaW1VAPEqF*B3)quHl10FT%jUh~8Ml#O{W+ zGbf)LXIsiE#>XG*{i+k@cbngvoBJ>Mx2gWFV-FnG2V^-ZDEt6=)`H>_W=rLi4C~#x zLYBjuUB+P|EIbr|M@P{1c2qYkQwOEOx3b^8OKoZ$CwIv{T5euU(2{4n;UCWQh5MWS zvntxl*#KnM*-_=A{zw4)S^$+^u02uDeD+L7m{`JsU$=+iiFYR8tL{q7%a^In3%dZ$ z6rlkW+hgpAPCDT#Q)BV5PI&zeaDr>fS2%#@RukerfJM^tl)&XCK{-0@BaQUZ?&MI3 zZ<3qjT-0U^kUv#Bi@mFBCH0{lIsK}9eh|m4#)krsB!0alvw)f5dE?A}c0E?!mikWJ z#D=h72v!k_hGj0hz&4VQ@tN{kALCkkVo*OZ3Gz4X#r9OW5hvZeu2=V*N301HK|vWrSV#GXKd07oPUpZA}Mfnn(Ml)hw@MA5TjPQS(o6v z#S}g02rP6S(bDYur%T2~>C_jkkwjcQfQ$+bIyxO1i$DQ#iGK7>s!Fz4f_FQ0nD34G zrqzJh0czn_HCsi{0Fwygm}rd25A&;c7b{I$fSE37|n6a8SV zFs#~m_!$ZWRFL|EvhJn^B@3S_?XKXJ;ALpXN+d8K8=Mwreo)o_T$vFNYZKC|kudGc zQ>+Tq5UOaq^&1!{ox84R-1iM5Y=r9KEFB##l3{sYpYXJKH3o(rS!o~sE#DdYlUk^Z zZ5H;=+6Ms<=f(y!X$4bO_|oBW8;LzHV8d34LV4XFtyhosxn7O07ed7JO3cNXJPL7y zA|pX_)GF~|yt&qsJi1pOf|7MR#GXq`cZ+sHl+|u&2L#97JNjMyqq>{7V|sCRd$^dY zFX&S3JdIE|PmP$G8vJxW2lby-cZT4OPX*W+K0~=Sk8VU1H|`ah0aLn{wvw_AMePpX z2iZPiF5qPSt4e0y$~ynPNr}|PO3St*aPFmc@i%JxC$4!@=-o$N;x66%ZJQXN@Gh+p zRe~~;wWqAqtB*FAsXLs@ov4Yj*CmQb0oagmpf%N>f`UhYQ6I!4ygd0x|fRF9lE58h@Ap zWxoDWr!iQnFu{8H!|u-kigMFSvZA=U{VhA5ypz*rmbd3@nSrf!?b8{h-xZ6xuImBr zP+y%9zmR74{jTp#*XW)+ zkwBKue5iTDzE2@6t2bK3Qp$!A$`P@XD{neStx(6afJXc@)E#MF9tc^Y4RgCjim9OI zPb=co>v(i8s>~~C{rvpqgK^DKH60*)`ilN1%I@kz2?$?dXIMUyo-~PH?Pje?wT0_D zRAZ^xuJy%Msz6P$N4}yB_!{ z^8F1QUIKo%QT+QSm0BbyaO;Q1W+Dp=yMwZ8V?EVQr|zJp^!id>&f4zm^mHDz@DNHg zIvUMV78+4$x@eEs$u!U?Ny8~2WbZ7{4 zX9+g=BcC=8Vs%$?d3+5dcSPr1Sdz!9b@}(ynzA29S%flddIXN{jy0x{ccG!71En?+ zJ;_`wuTgdF9<5TG{k!!jP4>ba=dWL_cbWh5Ep{A&!Nl=T<>d#;i#2@0ZRcVcSlWUM zESPF=l?KYSqqVcWO;)l*JaFjAQq1;)sZW;eVb<^Y3{0 z1Ucj33Vj*YLjI7Ucrhh~_oSmIv{(81Bz-z<>$iYPyQATX;OrI{`6iy~h2pb1$mD9fS zxZEQ>_@(lbz{GsE>{zGxJvKmTy}s^Z1kGv&OVvTe{hb9~AQ72f}uBn%vQ7pVfJB6ZZqCH6qe@=ra6^GczHI>CiOVU!M%YBT6qBUI%g3 zE2XJQDxx`miKIrsclB|peOlGsEpCA&!Ja)?Wm1h0x zOo?30l`wnJ2DgE3^XoU(rTSYvdj+iXdv8I;-g*^8zqM6p5|WcEEtVSWoKv3F6?MRL z)2muJ1@oqv(k|cLqKGrN_4MC^BFZ_cn15Es)&UC&yS}J1hR>IT!%*=iClND}S)qaP zIytoKs&17DDqVoBHDO9G`hY6yxtD4^pmN{{Zq3|2gt*2DgZwU!n$EO#?4K8y0I9q7BO^n=1yVf?z@Z9{o z=@MC+IUVqb_NDeC@quw>tck1`apIWedIx;=lH+gs<`oPUcUM|E1PkPIfw}1?@iQlVmv(`np*u z-(|TlA3+=I08{%2&&A6iWat?*EiNP9Z;-n9hk~6tsl8Ik&v66vQYE5pSq2};XsqRo z5>Rhi_A_E|#^=l9l2(1@NwaJYve9~z+?JQi8cr)A_vZae2{tWji|@9YkPPo%+QZJf zpc1`7uQzAtwRx)Y!xckMDl6(jVu6lV3}AmP!7A-+TKW|1yxYrt_RU&h{3bd+g7s%t z1zP2qrDU|Q&!!rudOD|}3W>nI>0*}THd*PRu6WXoJ|t)(T}U?KPT|eZn1&akK|w*b zm!>llcXdXeKkqA^Z`})8jizVzP$uu+$m7yMP%2@tbi&{;y}tY##vs{ z51;+WfWRG&N3w4nuMX@>TuWzb`A%j773nI-hle2!YaFLIwiIr|uu4-jwUBq1gDnC-41=qCyQnemp`W&eWD5?iq_13q^@qg1Ca}4Yeeyx=EW%FjF`{ z_qvR2Zn&8 z7DWiUtoq_hjDHAVe|m7#@B#%;m9%Xv-s9}=tJ*Z739aao0HC>47F7TP{?2X52t_T% z94xb8aM$2)n$c5p>MzGC&+I z)^8KjiWbZzRh#xj)1n|Ev^TL&rhDe}UTv&aPoN#x><+R~zExhYcLfXGi8)?>D-cj` zrjHm`@es#<1ee#Ma{N@2GHZ5_(_Y-R8Q@b;qcknrI%@xN(_yd3OhuZw~; z1wXwqTxN<;&5N4J+`q|D9m|O2obB(S#kii#6v4$kTHF-DoZ7VkckK_iJUJ}IPn#|e zURHSb5%=c)rU}fp4?|m^rF@4KgPcUXS0&pT1JHVhtlB)9841LKu88jV&P=hEBIh!~ zy29@MAdqn;m6j`PG(*JO|KVmk_sVA`?l`(5PA9VYhNuDeJGmp8q12%YX;!C)&z?<& z`2Dos$Rh*ZT^6Hgg7=3Hd{aZ1?4uCKEL$78Q9fqrN=(9_+4a2Hdv}7lbbYbZ{aE*+ z->w|)cC<9#4Byiz_BGiQ2DYsfqIqrXuJ?xHi)+llkoGpl7M>M z>@ALMl$KHG2gKFU^1dVULgsRFlwG9OaYO{Y+v1VkqW$g~3leb-k++LXUiw+>eSP#5 zm=|${--mMZ42!~%<163B*gvkJE)^^CQR^Sz;mv874TSV?pk!eCf7b%E*DxFQQgE;E z0BdI$)ro50+)+7P2E275^bG<0$W6|dezMsUx~ibMd2I{5GKykOt5fg8ns*YHu`lUM zPzEaTYoX%LkDKiXzkI%qS8bxr_?nAbhs&DlE%(L%vW)PO9-vs(=&Uah*qM9u6FoFT zBeWM`wa4*(qX%^X*9Y_8wCufOA|kRnH~s;IeE$7VvhDc1|J8G*!U5X$zVUE5*V^L% zB@ySs70cuVh-!!80E3#kvq#JKk+HE!uDmd>A5V8ofbOX1%lEyzTB@rBal0maKH3Gm zCIE7k?mp$d|tEL7>r=H~ifK?8QU zv4I6>uCs0IY%8;f{C3qDN&Rl!q!xi1N6ypuRJY^!mtX42w4MIVh|P^RgK>=>9`5gk z22O{FCSaLk@tjf%m_-&^@kM~@pjRUYfUf4YOGtc30Mxy^txl<{awYcRqQTNhW> z^0|T4%k=aV(pMmiY8@hXd;$WD@t~Y~_T?9KdFypab{2F?U#&xR>+V`66wQYGK_mD$ zN4}1;pMc^z`zmiX2WD6yc@ve!bm^B9XFQ+ZIC4z=IQg8!>Ug}f!%(kFG(?< zXx#oYi^V#Ymao@4JLZ&>lzD|~EzZ9WE4^U?61xLoy`7+By!1JwFYJHCA^7z4^v1cl zxkJ!n8trMcc-He|g}YZ>U*DeVyHYXqn4UyVQ!F@HZK`yJW>bhqTyq*=jn5ACb z^s^27BA8u z*B>Ry*-r1L4a_Y>(3E18Xxl9#fj$fn>SmZ}i#)3#5Xg^genLC~JiNLv^fu1LQ-g`y zlUVldx}?D6DxKQR|MJ+esHmtbeUa-d_SJYz8XT+qdPPv!=LI+vd`=A*WnXD z(rn{+q3}DRZf~nM9BjQknDT$}4f@C$TyL|?HssFIG>`S8l>+KTb!1Dg#@e{X=~^)3 z$?V2l9s1g#rD&BJpXPZOsRUZ*;i>tlQpYcAGhRlPs-U$&IIb3{sFKB=)cj4%X>Eyo zpY_<5SWFDGV+{#FIkp+2>v9HkdEK<>-*Q4APCP&}{U2 zPP^DnLXR@imr73OixmeS_&7MPzO6x1L+WdmL;!OJWj|5>r7Ed=GRyyDRH4?eO)|jb z)xv_Z)%|RhmsJlXOkG22xl9)k1q(iN)BAs(v#%oP+qXMtIP9sYm@cUq5OCw^#W#G8 zPfo6c$N|-Aqsp?fvdIl?r-g-!VNn2h?#U0Dol;}&9`uxob)E@&J+?=t`Z_Z<_QAJv zV_@K9!GG_Y+`?wkwyWzD07S6m1g*C)5pXHd#81bRR4}CD zfERdpFa=MS*y6BU)9p#hYyb1!>==4uET}tPBoSVP+5_tE_@2;bW>#${)VflvLDzq2 zL`YnpdVf63%ShH!`ww)TljIs*=ltHOY_gA^{t0Xh45qW_$40&H4S~hK<^05LA$e7> zE)e|YDvno_*U-Sr>Zxp_c&n&j|3BZdRC4C_heqb@zfy?zX2bYTy|HXX@e!9!W8;0Z^sq4-3MB^s+yPa&?%R) z#e50aYFZ&z?z7dln=j47&Dmd@rNvG25B7q&AQvkQhv!1^@S;_U6Ft_-TECr0ELAsK zoK03_8Gg(W{2_{h7DpWLuzBC4w2t(JAx0RHr+OW^U)ZDFD!T-)Zg*W&c zu+)34M+*9Agmv1O61mBGxtCtS5a_hOS?e7s^DWHYM}e{iaQ z|KQ+wbv^4YuOfuvM;?;ydpXI`%G}hdcnO?{v#dV_3-sFo_B%5Ke4&Ba>c{b1dG-E2 zPpHq%n04UMs5#{}$zw|h>QcO${cC7X@~*jC`h>IAp@}IHKfk&df38?nyc2^U5>FGP z+?4-}Qz`$Lf@|Wp`jgkH(%;92+-=kE_VBEP)2hNhk%G5fLsZk@LTfi~q5b6xlp72j z^If~9Y|l19Z+zl~R+%0Z*bp~r6NWBzli1&vE_drP5}4n{##Qym*YAM=&P#S}hoYRE z%2mAQe;3MAGN9YN#mBS}hg4UYzIOiSS5Z_;OAG(l9X-rx1-;K|i}Q@HkB<*l4=1*% zTzki_x<%&9W=k-j!vQ(Rsmp&klnUv-dD#O`8qhS%`Ns4j4ccg;LB}@rt`3(Vr2X4< zVNZ){`@i?k_i@?#X1=&o_v&o5 zI-By>`O&;}`b@6SHGB8Jen~?8uSQ)@FwEWm#Tzu*2#!|`1bNAKrc)KdK9~98Tc@9v zsyilQoS|Wz2-s5&@l`i=X7gJ9$$IUV-EU9r0H}AH!Df!acq&I1w;hZ7)8)9v3gxbz zOKb$~tMOUC-HCK~;p^dw`3uF_{rb2u)f4>w=H{^Dpc2K+s$EG2OiMq% z$O2(AYX3J{Y%s^Hm;^}Y*6vpwe=iCgqyz1vedt!_05=mvC$159^U=Q(2i6RZoQ>D3 zuF5F|tk>M$%^e;mK(GUArVW7ywc{!S8-2{)wbupSy{+^5e?QqT6e+aTxM`R;@Q0+I zYo)zFi_X$y_1XYGc(0m9X#R_gLGux54c%4IwIa$IewbusnI|8;Yy7stm!2NRMO^%1 zYn$eEZNyxRI=mlR4s1N{#dn>8yxBZ41xm-_30mDfc6rRRUMs_I! z6)m}Q%l?H+c}ST-qbWPh^U>q<&s{E9t_ zLZYJ+t2|Aq6`NEQ_+#Rt?dmuC(gczIOaCz;rzE2mZh?U4e1B8tm;Vd7^v8REzrOlr zD{wZzH%l8V+IXDClAU zdvq^Dm-0GW_x%vCH;Q%7D|n^VU?@VK8uMQjgm`1>$BZr;mg@iD33|qOgxzR#ZPvc; zk!Gn&Ik0_y`iH{RH`If%Go3F1i!3v={P6VXcE&-ERw_$#TLn^xDeFOJ_$F=QvgHd{ zvQBOJ|AoqK1npfr%!H$itgNi`hHly^h9Di4p3dsPu?<|mcE$B6XK?L}SquE>7hBf# zqyPSI8&fw7%=ZQFAwnw23=<4cf(mtDWlWuo__nTeWr&Y;E`^%R+`hz`OrHEvcgv@BB;de?&+~&nGn0|2ti&6fU-rii2em z71VQ!3!uUhCkHeE7XYdntLKQlq##59J9w!>{)rO60{93>mG4mm;=#6}Lij+R04M-F zm{uT!0JH|*))w>k%L3;UEK;(l2E)~J(eV>+k})%27Nd71bbbBetd*<+i->>#08s;UVf@)uSXx&P#iBD)XmD-f4tg z7K$xis}dlfp8I!y9{`w0J}6|<;^Joa{f!fx@_!wvsVK#jUCMrRah!bufaR4Q#-|nBj~-*?zZ?P@ zBR~icRg^0|XIMxupp%MK=n@Lys3KLklKR~V|8 zx}MulL_3U{q=o-n7chSXT}a|B#{z1Amm!SFqGdtd6;;7wPl0H?%vjaaIAH#8e(7yo zXYs3l6d4I8YJhA&00|L+wq{bc0mE6TQY;l>WP}JB6-louc*n#OH>M~^umbLn!ILjR z;b#+MG}JX%2>AdPpXmoBs4##4>@JK`(@K>9RqT}1bHpn>)KJ^ktln*yLH{k(LJnF6 z>K_xvKaB${lDS(?CpE8wfH3EXsz?|N04uCnEHYs#63x5bf*>XJc0y1G1cC`+fJ+^w zD|IhH!6n1q%G^Usg#HQ@Gz>@xk#Yx6GxTXzhF1PTRx5f6g-&G>6YoU^$Usz`DmWJ5 zR5^HDb7#h@@G7}OdL*ghy*}}P80$QX^H%3oGypR|A6-9ZH^ZVzziKPpfu zKp2=WD(y=5J5tct2DPT2zFgfqs8 z$&>8@ssNYjrn-NM0Fpl~v?6-z>S}N=i5?sL-I_=h106yrd z5#{^5mk-j=2aUR2i`}pu>9z-mds%0}7rO4k11X?;SprobF6MoJ&v#RM_$yyOBA8GB z&)Bp=2X0D|(>zT#jY;u8$@lg5UTu1pLQ zU~Na3LwCU~O!E^15mLoi8QHyu%m+D0;Jx}&Jr-xfWMZfd!U47<@HVNy=K6?*%rxSD z&JK24fXfaKG-Y5XCKV85BjHq2k)RV|W)=tx0OU7#1hQbUJ_mqJo{#~hKX;juYbI3Y zXD;Ub;lZKrETENnQS=vd?3A48A-Yn3sHFJMVtPO~RSx<=2GkRl=>6PE)23W0iB|L= zUjO!XA0?2VExhe3Hh9>GwsW(91vAd9>M96mI+o;tQ=o>ZD@h9Zremih8JBbZiYDfZ zB^!N&uIvsm1*)zYTt@TsX`=P6NnHkH!Rpket438)FPyT8UsfhYq9412+>N)@M+q}` zo`a+wD`c3`we-rZ?!k4o(WQET8X5+{E^N?RPA@$o@|`ilr8BBc+Xfo-I|2k9*t2ol zN zh8`pUhgAB$-B|F4HMdW%^Y5EjajQ|3J{}i z6ZMlD1HcDR_=i7TR0km+Tbg+RW)4LJ^~%~`TIfxz*rJv8bwlZC9S7WPR_O$W-cuIP zo!kmk8+BG(?lo!0;HN@)m%_0Tm1ZWJKaBagbyw`53RKJaK)82suC~K>xg4b!W-9-; z34oM)Sz(gE<+_TQ^MKQto$gw!3=F`8$ZJ9vrFA3%0gnuQ11+Hqh9LkK2$f4{(vRXz zk}pbz1gaq@6Gj?pOrUnGSvYZI#F}pa>aymaed_aK+ckFs-lo1rsz>Zw5& zwEr^UfcpfxHA_48h{fzNqLeAAPS^GSknnSc8qMbzE5p>4+}bgFkypN#7e_}=6vild z^TA>P3TmSu#1QyezV1*Haq<}zS@w)v@)G^Mqn#v8%QjJgDAjX&} zYLshYa3LO=+USf#BUWvZLhCqHj6JQUl_ngZj9#z97|Xe47^{>3m;Q|l3@b$}eR9tS zLI*U%{sZ<>gZo<$du0I~R45%S9vIZ~uIoT7*nDPb?50Tcc~@PkcHXz7={}t&BPpR7 z3qU;}0Kt@fP=bl5*UnB%=jTHTOX3JX2?Y(+!7*&B*97kLRtqKcyLs|F)Ta_b4K(ak z_hmRp+@+NqdVvm1Gdea;u2KonqoZTH`_Gw^=C&rmWs+tTv24NNB3=ar)R65D<@CDp zhJ(CvLv4TpK98<%?EYFlc@L2nwD@JM=sbT7AEPYOO)oiCzO;_Zo?g`X zv%GxehT;;fLi~{))2ERMC&p<|!Em3#3X4h;O<6bJN5nk&w+JxR+NLBO2E-pQS$K~U zVjTJa@7&iRt6ZRM_p5w#S>5bW5bN%^S-etCm|3_M&md;p-{-+-wzok7I;JP<$G9qq zX(+VCTW7l`K+SE;Xy#MB)k_78+LviC)$cTtc@;-+IXzv|34%29C6Y8}1+v@k=#Zu* zEcjXk>A5y|$otRNaLGovS89|aTCAbVnn8aZlqBqBaSrJWDQ<8-1=`$vH=HRmNQFA) zK^0(zg&5*K@5&oVM9vED0uaaj>&gE2Pbbxr`wT*4#47;MNYA3zjZ58*Hmop>)aE;9>E`DwY-Jg&eNE{D`f&utbER8(`+07!`g z;Rb*=sH`OH0ebvw|J>KR{M8LCFq5L3<{#W+`e)L-SCl4q(%h4l_wx5;!+p!Q_qPGa z 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 literal 4253 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 Date: Tue, 26 May 2026 16:29:20 +0300 Subject: [PATCH 20/42] banana touch, no clothes and honk missle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавляет заклинания banana touch, honk missle и no clothes. Изменяет работу последнего на выдачу элемента который выдает телу владельца статус эффект --- code/__DEFINES/status_effects.dm | 2 + code/_globalvars/traits/_traits.dm | 2 +- code/datums/elements/no_clothes.dm | 27 +++++++++ code/datums/emote/emote.dm | 2 +- code/datums/spells/banana_touch.dm | 29 --------- code/datums/spells/mime.dm | 8 +-- code/game/objects/items/devices/megaphone.dm | 2 +- code/modules/clothing/masks/gasmask.dm | 2 +- .../mob/living/carbon/human/human_emote.dm | 2 +- .../projectiles/projectile/special/magic.dm | 15 ++++- code/modules/spells/spell.dm | 2 +- .../spell_types/aoe_spell/magic_missile.dm | 9 +++ .../spells/spell_types/self/no_clothes.dm | 29 +++++++++ .../spells/spell_types/touch/banana_touch.dm | 56 ++++++++++++++++++ icons/mob/screen_alert.dmi | Bin 85688 -> 86481 bytes paradise.dme | 3 + 16 files changed, 148 insertions(+), 42 deletions(-) create mode 100644 code/datums/elements/no_clothes.dm create mode 100644 code/modules/spells/spell_types/self/no_clothes.dm create mode 100644 code/modules/spells/spell_types/touch/banana_touch.dm diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 5107feb453eb..12f696aa444b 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -102,6 +102,8 @@ /// Prevents you from automatically grabbing walls to stop moving in space. #define STATUS_EFFECT_UNBALANCED /datum/status_effect/unbalanced +#define STATUS_EFFECT_NO_CLOTHES /datum/status_effect/no_clothes + ///////////// // DEBUFFS // ///////////// diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 1916c159bda8..76f219cf5130 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -11,6 +11,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( /datum/mind = list( "TRAIT_BAD_SOUL" = TRAIT_BAD_SOUL, "TRAIT_HIJACK" = TRAIT_HIJACK, + "TRAIT_MIMING" = TRAIT_MIMING ), /datum/hud = list( @@ -152,7 +153,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_MARTIAL_ARTS_SUPPRESSED" = TRAIT_MARTIAL_ARTS_SUPPRESSED, "TRAIT_MASTER_SURGEON" = TRAIT_MASTER_SURGEON, "TRAIT_MESON_VISION" = TRAIT_MESON_VISION, - "TRAIT_MIMING" = TRAIT_MIMING, "TRAIT_MINDSHIELD_HUD" = TRAIT_MINDSHIELD_HUD, "TRAIT_MULTIZ_SUIT_SENSORS" = TRAIT_MULTIZ_SUIT_SENSORS, "TRAIT_MUTE" = TRAIT_MUTE, diff --git a/code/datums/elements/no_clothes.dm b/code/datums/elements/no_clothes.dm new file mode 100644 index 000000000000..69f67341d438 --- /dev/null +++ b/code/datums/elements/no_clothes.dm @@ -0,0 +1,27 @@ +/datum/element/no_clothes + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + var/mob/living/body + var/datum/mind/owner + +/datum/element/no_clothes/Attach(datum/target) + .=..() + owner = target + body = owner.current + RegisterSignals(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED), PROC_REF(add_status_effect)) + body.apply_status_effect(STATUS_EFFECT_NO_CLOTHES) + +/datum/element/no_clothes/Detach(datum/source) + .=..() + UnregisterSignal(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED)) + +/datum/element/no_clothes/proc/add_status_effect() + if(check_new_body()) + body.apply_status_effect(STATUS_EFFECT_NO_CLOTHES) + +/datum/element/no_clothes/proc/check_new_body() + if(owner.current == body) + return FALSE + UnregisterSignal(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED)) + body = owner.current + RegisterSignals(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED), PROC_REF(add_status_effect)) + return TRUE diff --git a/code/datums/emote/emote.dm b/code/datums/emote/emote.dm index 2064e90b7c08..cabf0188e541 100644 --- a/code/datums/emote/emote.dm +++ b/code/datums/emote/emote.dm @@ -215,7 +215,7 @@ for(var/mob/dead/observer/ghost in viewers(user)) ghost.show_message(span_deadsay("[displayed_msg]"), EMOTE_VISIBLE, chat_message_type = MESSAGE_TYPE_LOCALCHAT) - else if((emote_type & (EMOTE_AUDIBLE|EMOTE_SOUND)) && user.mind && !HAS_TRAIT(user.mind, TRAIT_MIMING)) + else if((emote_type & (EMOTE_AUDIBLE|EMOTE_SOUND)) && user.mind && !HAS_MIND_TRAIT(user, TRAIT_MIMING)) user.audible_message(displayed_msg, deaf_message = span_emote("You see how [user] [msg]")) else user.visible_message(displayed_msg) diff --git a/code/datums/spells/banana_touch.dm b/code/datums/spells/banana_touch.dm index 1048cba8d4b9..a56102a9acd1 100644 --- a/code/datums/spells/banana_touch.dm +++ b/code/datums/spells/banana_touch.dm @@ -30,32 +30,3 @@ var/mob/living/carbon/human/h_target = target h_target.bananatouched() return ..() - -/mob/living/carbon/human/proc/bananatouched() - to_chat(src, "HONK") - Weaken(14 SECONDS) - Stuttering(30 SECONDS) - do_jitter_animation(15) - - if(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE)) //Wizards get non-cursed clown robes and magical mask. - drop_item_ground(shoes, force = TRUE) - drop_item_ground(wear_mask, force = TRUE) - drop_item_ground(head, force = TRUE) - drop_item_ground(wear_suit, force = TRUE) - equip_to_slot_or_del(new /obj/item/clothing/head/wizard/clown, ITEM_SLOT_HEAD) - equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/clown, ITEM_SLOT_CLOTH_OUTER) - equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes/magical) - equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clownwiz, ITEM_SLOT_MASK) - else - qdel(shoes) - qdel(wear_mask) - qdel(w_uniform) - equip_to_slot_or_del(new /obj/item/clothing/under/rank/clown/nodrop, ITEM_SLOT_CLOTH_INNER) - equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes/nodrop, ITEM_SLOT_FEET) - equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat/nodrop, ITEM_SLOT_MASK) - force_gene_block(GLOB.clumsyblock, TRUE) - force_gene_block(GLOB.comicblock, TRUE) - if(!(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE))) //Mutations are permanent on non-wizards. Can still be removed by genetics fuckery but not mutadone. - LAZYOR(dna.default_blocks, GLOB.clumsyblock) - LAZYOR(dna.default_blocks, GLOB.comicblock) - diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm index 13d2448c4025..c96e920d2ac8 100644 --- a/code/datums/spells/mime.dm +++ b/code/datums/spells/mime.dm @@ -15,7 +15,7 @@ /obj/effect/proc_holder/spell/aoe/conjure/build/mime_wall/Click() if(usr?.mind) - if(!HAS_TRAIT(usr.mind, TRAIT_MIMING)) + if(!HAS_MIND_TRAIT(usr, TRAIT_MIMING)) to_chat(usr, span_warning("Сначала вы должны принять обет молчания!")) return invocation = "[usr] выглядит так, как будто бы перед н[GEND_IM_EI_IM_IMI(usr)] находится стена." @@ -42,7 +42,7 @@ if(!ishuman(usr)) return var/mob/living/carbon/human/user = usr - if(HAS_TRAIT(user.mind, TRAIT_MIMING)) + if(HAS_MIND_TRAIT(user, TRAIT_MIMING)) still_recharging_msg = span_warning("Вы не можете так быстро нарушить свой обет молчания!") else still_recharging_msg = span_warning("Вам придётся подождать, прежде чем вы сможете снова дать обет молчания!") @@ -54,7 +54,7 @@ if(!target.mind) return - if(HAS_TRAIT(user.mind, TRAIT_MIMING)) + if(HAS_MIND_TRAIT(user, TRAIT_MIMING)) REMOVE_TRAIT(user.mind, TRAIT_MIMING, "mime_vow") to_chat(target, span_notice("Вы нарушаете свой обет молчания.")) else @@ -87,7 +87,7 @@ /obj/effect/proc_holder/spell/forcewall/mime/Click() if(usr?.mind) - if(!HAS_TRAIT(usr.mind, TRAIT_MIMING)) + if(!HAS_MIND_TRAIT(usr, TRAIT_MIMING)) to_chat(usr, span_warning("Сначала вы должны принять обет молчания!")) return invocation = "[usr] выглядит так, как будто бы перед н[GEND_IM_EI_IM_IMI(usr)] находится стена." diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm index 79cc77c32b05..dd4b69776049 100644 --- a/code/game/objects/items/devices/megaphone.dm +++ b/code/game/objects/items/devices/megaphone.dm @@ -29,7 +29,7 @@ return if(ishuman(user)) var/mob/living/carbon/human/H = user - if(H?.mind && HAS_TRAIT(H.mind, TRAIT_MIMING)) + if(H?.mind && HAS_MIND_TRAIT(H, TRAIT_MIMING)) to_chat(user, span_warning("Your vow of silence prevents you from speaking.")) return if(HAS_TRAIT(H, TRAIT_COMIC) || H.get_int_organ(/obj/item/organ/internal/cyberimp/brain/clown_voice)) diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index dda2d762429b..2492c4649d97 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -444,7 +444,7 @@ // spell.action.enable_invisibility(TRUE) // return - if(HAS_TRAIT(user.mind, TRAIT_MIMING)) + if(HAS_MIND_TRAIT(user, TRAIT_MIMING)) spell.cast(list(user)) user.mind.RemoveSpell(spell) diff --git a/code/modules/mob/living/carbon/human/human_emote.dm b/code/modules/mob/living/carbon/human/human_emote.dm index 4b3ce7c4f603..1f0241c6adb7 100644 --- a/code/modules/mob/living/carbon/human/human_emote.dm +++ b/code/modules/mob/living/carbon/human/human_emote.dm @@ -29,7 +29,7 @@ return custom_message /datum/emote/living/carbon/human/run_emote(mob/living/carbon/human/user, params, type_override, intentional) - if((emote_type & EMOTE_MOUTH) && (user.mind && !HAS_TRAIT(user.mind, TRAIT_MIMING))) + if((emote_type & EMOTE_MOUTH) && (user.mind && !HAS_MIND_TRAIT(user, TRAIT_MIMING))) if(user.getOxyLoss() > 35 || user.AmountLoseBreath() >= 8 SECONDS) // no screaming if you don't have enough breath to scream user.emote("gasp") return TRUE diff --git a/code/modules/projectiles/projectile/special/magic.dm b/code/modules/projectiles/projectile/special/magic.dm index 93b407c33ea9..123611ba6d57 100644 --- a/code/modules/projectiles/projectile/special/magic.dm +++ b/code/modules/projectiles/projectile/special/magic.dm @@ -69,7 +69,7 @@ hitsound = 'sound/magic/mm_hit.ogg' trail = TRUE - trail_lifespan = 0.5 SECONDS + trail_lifespan = 20 trail_icon_state = "magicmd" /obj/projectile/magic/aoe/magic_missile/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) @@ -91,6 +91,16 @@ color = "red" //Looks more culty this way range = 10 +/obj/projectile/magic/aoe/magic_missile/honk + name = "A bike horn" + icon = 'icons/obj/items.dmi' + icon_state = "bike_horn" + + trail_icon = 'icons/obj/items.dmi' + trail = TRUE + trail_lifespan = 5 + trail_icon_state = "bike_horn" + // MARK: Death Bolt /obj/projectile/magic/death name = "bolt of death" @@ -475,7 +485,6 @@ return add_attack_logs(null, M, "became [new_mob.real_name]", ATKLOG_ALL) - SEND_SIGNAL(M, COMSIG_LIVING_ON_WABBAJACKED, new_mob) new_mob.a_intent = INTENT_HARM if(M.mind) M.mind.transfer_to(new_mob) @@ -491,7 +500,7 @@ to_chat(new_mob, span_danger("ТЕПЕРЬ ВЫ [uppertext(randomize)]")) if(briefing_msg) to_chat(new_mob, chat_box_red(span_userdanger("[briefing_msg]"))) - + SEND_SIGNAL(M, COMSIG_LIVING_ON_WABBAJACKED, new_mob) qdel(M) return new_mob diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 391068dc2ea2..e7dba7c94436 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -182,7 +182,7 @@ return FALSE if(ishuman(owner)) - if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB & !owner.has_status_effect(/datum/status_effect/no_clothes)) var/mob/living/carbon/human/human_owner = owner if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES) && !ismonkey(human_owner)) // Monkeys don't need robes to cast as they are inherently imbued with power from the banana dimension if(feedback) diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm index 383838b8690c..6103e6448ff4 100644 --- a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -52,3 +52,12 @@ max_targets = 6 shuffle_targets_list = TRUE projectile_type = /obj/projectile/magic/aoe/magic_missile/lesser + +/datum/action/cooldown/spell/aoe/magic_missile/honk_missile + name = "Honk Missile" + desc = "This spell fires several, slow moving, magic bikehorns at nearby targets." + projectile_type = /obj/projectile/magic/aoe/magic_missile/honk + cooldown_reduction_per_rank = 2.5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "HONK GY AMA" + sound = 'sound/items/bikehorn.ogg' diff --git a/code/modules/spells/spell_types/self/no_clothes.dm b/code/modules/spells/spell_types/self/no_clothes.dm new file mode 100644 index 000000000000..d0994be4865c --- /dev/null +++ b/code/modules/spells/spell_types/self/no_clothes.dm @@ -0,0 +1,29 @@ +/datum/action/cooldown/spell/no_clothes + name = "No Clothes" + desc = "This always-on spell allows you to cast magic without your garments." + button_icon_state = "no_clothes" + +/datum/action/cooldown/spell/no_clothes/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/no_clothes/cast(atom/cast_on) + . = ..() + var/mob/living/caster = cast_on + to_chat(caster, span_notice("Вы активировали заклинание, отныне вам не нужна одежда для использования заклинаний!")) + caster.mind.AddElement(/datum/element/no_clothes) + qdel(src) + +/datum/status_effect/no_clothes + id = "no_clothes" + alert_type = /atom/movable/screen/alert/status_effect/no_clothes + on_remove_on_mob_delete = TRUE + +/datum/status_effect/no_clothes/on_apply() + return TRUE + +/datum/status_effect/no_clothes/on_remove() + +/atom/movable/screen/alert/status_effect/no_clothes + name = "Усиление магии" + desc = "Вам больше не нужна одежда для использования заклинаний!" + icon_state = "no_clothes" diff --git a/code/modules/spells/spell_types/touch/banana_touch.dm b/code/modules/spells/spell_types/touch/banana_touch.dm new file mode 100644 index 000000000000..b9ca713eab1f --- /dev/null +++ b/code/modules/spells/spell_types/touch/banana_touch.dm @@ -0,0 +1,56 @@ +/datum/action/cooldown/spell/touch/banana_touch + name = "Banana Touch" + desc = "A spell popular at wizard birthday parties, this spell will put on a clown costume on the target, \ + stun them with a loud HONK, and mutate them to make them more entertaining! \ + Warning : Effects are permanent on non-wizards." + hand_path = /obj/item/melee/magic_hand/banana_touch + school = "transmutation" + invocation = "NWOLC YRGNA" + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 5 SECONDS //50 deciseconds reduction per rank + button_icon_state = "clown" + +/obj/item/melee/magic_hand/banana_touch + name = "banana touch" + desc = "It's time to start clowning around." + icon_state = "banana_touch" + item_state = "banana_touch" + +/datum/action/cooldown/spell/touch/banana_touch/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + var/datum/effect_system/fluid_spread/smoke/smoke = new + smoke.set_up(amount = 5, location = victim) + smoke.start() + to_chat(victim, "HONK") + var/mob/living/carbon/human/h_target = victim + h_target.bananatouched() + remove_hand(reset_cooldown_after = TRUE) + playsound(victim, 'sound/items/AirHorn.ogg', 50, TRUE) + +/mob/living/carbon/human/proc/bananatouched() + to_chat(src, "HONK") + Weaken(14 SECONDS) + Stuttering(30 SECONDS) + do_jitter_animation(15) + + if(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE)) //Wizards get non-cursed clown robes and magical mask. + drop_item_ground(shoes, force = TRUE) + drop_item_ground(wear_mask, force = TRUE) + drop_item_ground(head, force = TRUE) + drop_item_ground(wear_suit, force = TRUE) + equip_to_slot_or_del(new /obj/item/clothing/head/wizard/clown, ITEM_SLOT_HEAD) + equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/clown, ITEM_SLOT_CLOTH_OUTER) + equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes/magical) + equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clownwiz, ITEM_SLOT_MASK) + else + qdel(shoes) + qdel(wear_mask) + qdel(w_uniform) + equip_to_slot_or_del(new /obj/item/clothing/under/rank/clown/nodrop, ITEM_SLOT_CLOTH_INNER) + equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes/nodrop, ITEM_SLOT_FEET) + equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat/nodrop, ITEM_SLOT_MASK) + force_gene_block(GLOB.clumsyblock, TRUE) + force_gene_block(GLOB.comicblock, TRUE) + if(!(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE))) //Mutations are permanent on non-wizards. Can still be removed by genetics fuckery but not mutadone. + LAZYOR(dna.default_blocks, GLOB.clumsyblock) + LAZYOR(dna.default_blocks, GLOB.comicblock) + diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index fe4eeed443159d41cfc234aef3fc5ce6f3c805e3..ecf1958901cae726ca61a11dcd0853759c793a08 100644 GIT binary patch delta 12844 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 diff --git a/paradise.dme b/paradise.dme index b4bd4b04c4cb..c9da6f053c43 100644 --- a/paradise.dme +++ b/paradise.dme @@ -920,6 +920,7 @@ #include "code\datums\elements\material_heal.dm" #include "code\datums\elements\misfire_weapon.dm" #include "code\datums\elements\movetype_handler.dm" +#include "code\datums\elements\no_clothes.dm" #include "code\datums\elements\nutrition_effects.dm" #include "code\datums\elements\openspace_item_click_handler.dm" #include "code\datums\elements\pet_bonus.dm" @@ -3956,12 +3957,14 @@ #include "code\modules\spells\spell_types\list_target\_list_target.dm" #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" +#include "code\modules\spells\spell_types\self\no_clothes.dm" #include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" #include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" #include "code\modules\spells\spell_types\teleport\_teleport.dm" #include "code\modules\spells\spell_types\teleport\area_teleport.dm" #include "code\modules\spells\spell_types\teleport\blink.dm" #include "code\modules\spells\spell_types\touch\_touch.dm" +#include "code\modules\spells\spell_types\touch\banana_touch.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" From 996499ffade780b235830122d87264e599333909 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 26 May 2026 21:50:09 +0300 Subject: [PATCH 21/42] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit теперь работает на process() вместо сигналов --- code/datums/elements/no_clothes.dm | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/code/datums/elements/no_clothes.dm b/code/datums/elements/no_clothes.dm index 69f67341d438..1f0a36fa99fb 100644 --- a/code/datums/elements/no_clothes.dm +++ b/code/datums/elements/no_clothes.dm @@ -6,22 +6,18 @@ /datum/element/no_clothes/Attach(datum/target) .=..() owner = target - body = owner.current - RegisterSignals(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED), PROC_REF(add_status_effect)) - body.apply_status_effect(STATUS_EFFECT_NO_CLOTHES) + START_PROCESSING(SSprocessing, src) /datum/element/no_clothes/Detach(datum/source) .=..() - UnregisterSignal(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED)) - -/datum/element/no_clothes/proc/add_status_effect() - if(check_new_body()) - body.apply_status_effect(STATUS_EFFECT_NO_CLOTHES) + body.remove_status_effect(STATUS_EFFECT_NO_CLOTHES) + STOP_PROCESSING(SSprocessing, src) -/datum/element/no_clothes/proc/check_new_body() - if(owner.current == body) - return FALSE - UnregisterSignal(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED)) +/datum/element/no_clothes/process(seconds_per_tick) + if(!owner.current || owner.current == body) + return + if(body) + body.remove_status_effect(STATUS_EFFECT_NO_CLOTHES) body = owner.current - RegisterSignals(body, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_MOB_CHANGED_TYPE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_UNSHAPESHIFTED), PROC_REF(add_status_effect)) - return TRUE + body.apply_status_effect(STATUS_EFFECT_NO_CLOTHES) + From 92707d49facc6345f0c0ffc9bbe9523b944e310f Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 26 May 2026 23:08:25 +0300 Subject: [PATCH 22/42] mutation spell & linters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit немного чинит линтеры и добавляет спелл мутации, также спелл мутации теперь работает через статус эффект --- code/__DEFINES/status_effects.dm | 2 + .../gamemodes/clockwork/clockwork_items.dm | 2 +- .../projectiles/projectile/special/magic.dm | 2 - .../spell_types/aoe_spell/magic_missile.dm | 9 --- .../modules/spells/spell_types/self/mutate.dm | 52 ++++++++++++++++++ .../spells/spell_types/self/no_clothes.dm | 2 - .../spell_types/teleport/area_teleport.dm | 5 -- .../spells/spell_types/teleport/blink.dm | 6 -- icons/mob/screen_alert.dmi | Bin 86481 -> 87457 bytes paradise.dme | 1 + 10 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 code/modules/spells/spell_types/self/mutate.dm diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 12f696aa444b..19a58575ddf8 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -104,6 +104,8 @@ #define STATUS_EFFECT_NO_CLOTHES /datum/status_effect/no_clothes +#define STATUS_EFFECT_MUTATION /datum/status_effect/mutation + ///////////// // DEBUFFS // ///////////// diff --git a/code/game/gamemodes/clockwork/clockwork_items.dm b/code/game/gamemodes/clockwork/clockwork_items.dm index 54caa53ad646..f40066e383cd 100644 --- a/code/game/gamemodes/clockwork/clockwork_items.dm +++ b/code/game/gamemodes/clockwork/clockwork_items.dm @@ -158,7 +158,7 @@ if(KNOCK_SPELL) if(!proximity_flag) //magical key only works if you're close enough return - if(istype(target, /obj/machinery/door)) + if(is_door(target)) var/obj/machinery/door/door = target if(istype(door, /obj/machinery/door/airlock/hatch/gamma)) return diff --git a/code/modules/projectiles/projectile/special/magic.dm b/code/modules/projectiles/projectile/special/magic.dm index 123611ba6d57..326a1191e1e7 100644 --- a/code/modules/projectiles/projectile/special/magic.dm +++ b/code/modules/projectiles/projectile/special/magic.dm @@ -24,7 +24,6 @@ /// Gives magic projectiles an area of effect radius that will bump into any nearby mobs /obj/projectile/magic/aoe - damage = 0 /// The AOE radius that the projectile will trigger on people. var/trigger_range = 1 @@ -97,7 +96,6 @@ icon_state = "bike_horn" trail_icon = 'icons/obj/items.dmi' - trail = TRUE trail_lifespan = 5 trail_icon_state = "bike_horn" diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm index 6103e6448ff4..9c37a40b2b15 100644 --- a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -3,16 +3,11 @@ 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/projectile/projectile_type = /obj/projectile/magic/aoe/magic_missile @@ -40,15 +35,11 @@ /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 shuffle_targets_list = TRUE projectile_type = /obj/projectile/magic/aoe/magic_missile/lesser 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..bdb6edd13f40 --- /dev/null +++ b/code/modules/spells/spell_types/self/mutate.dm @@ -0,0 +1,52 @@ +/datum/action/cooldown/spell/mutate + name = "Mutate" + desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." + + school = "transmutation" + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 2.5 SECONDS + invocation = "BIRUZ BENNAR" + invocation_type = INVOCATION_SHOUT + + button_icon_state = "mutate" + sound = 'sound/magic/mutate.ogg' + +/datum/action/cooldown/spell/mutate/cast(atom/cast_on) + .=..() + if(!ishuman(cast_on)) + return + var/mob/living/carbon/human/target = cast_on + if(!target.dna) + to_chat(target, span_warning("У вас нет ДНК, вы не можете мутировать!")) + return + + target.apply_status_effect(STATUS_EFFECT_MUTATION) + +/datum/action/cooldown/spell/mutate/Remove(mob/living/remove_from) + . = ..() + remove_from.remove_status_effect(STATUS_EFFECT_MUTATION) + +/datum/status_effect/mutation + id = "mutation" + alert_type = /atom/movable/screen/alert/status_effect/mutation + on_remove_on_mob_delete = TRUE + duration = 30 SECONDS + var/mob/living/carbon/human/mutated + +/datum/status_effect/mutation/on_apply() + mutated = owner + mutated.force_gene_block(GLOB.hulkblock, TRUE) + ADD_TRAIT(mutated, TRAIT_LASEREYES, TRAIT_STATUS_EFFECT(id)) + mutated.regenerate_icons() + to_chat(mutated, span_notice_alt("You feel strong! You feel a pressure building behind your eyes!")) + return TRUE + +/datum/status_effect/mutation/on_remove() + mutated.force_gene_block(GLOB.hulkblock, FALSE) + REMOVE_TRAIT(mutated, TRAIT_LASEREYES, TRAIT_STATUS_EFFECT(id)) + mutated.regenerate_icons() + +/atom/movable/screen/alert/status_effect/mutation + name = "Мутация" + desc = "Вы можете стать халком и стрелять лазерами из глаз!" + icon_state = "mutation" diff --git a/code/modules/spells/spell_types/self/no_clothes.dm b/code/modules/spells/spell_types/self/no_clothes.dm index d0994be4865c..870407cb0afd 100644 --- a/code/modules/spells/spell_types/self/no_clothes.dm +++ b/code/modules/spells/spell_types/self/no_clothes.dm @@ -21,8 +21,6 @@ /datum/status_effect/no_clothes/on_apply() return TRUE -/datum/status_effect/no_clothes/on_remove() - /atom/movable/screen/alert/status_effect/no_clothes name = "Усиление магии" desc = "Вам больше не нужна одежда для использования заклинаний!" diff --git a/code/modules/spells/spell_types/teleport/area_teleport.dm b/code/modules/spells/spell_types/teleport/area_teleport.dm index e56dcc98a05d..945c294196ff 100644 --- a/code/modules/spells/spell_types/teleport/area_teleport.dm +++ b/code/modules/spells/spell_types/teleport/area_teleport.dm @@ -3,16 +3,11 @@ desc = "This spell teleports you to an area of your selection." button_icon_state = "spell_teleport" sound = 'sound/magic/teleport_diss.ogg' - - school = SCHOOL_TRANSLOCATION cooldown_time = 1 MINUTES cooldown_reduction_per_rank = 20 SECONDS spell_max_level = 3 - invocation = "SCYAR NILA" // gets punctuation auto applied invocation_type = INVOCATION_SHOUT - smoke_type = /datum/effect_system/fluid_spread/smoke smoke_amt = 2 - post_teleport_sound = 'sound/magic/teleport_app.ogg' diff --git a/code/modules/spells/spell_types/teleport/blink.dm b/code/modules/spells/spell_types/teleport/blink.dm index 77a864a72020..0cab0a328787 100644 --- a/code/modules/spells/spell_types/teleport/blink.dm +++ b/code/modules/spells/spell_types/teleport/blink.dm @@ -3,15 +3,9 @@ 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 diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index ecf1958901cae726ca61a11dcd0853759c793a08..3220bbb7b58b340369c5d93837ebde9541d921a3 100644 GIT binary patch delta 14025 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 o`y6^ob6Ftr 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 Vg(UipyBG-hQ&rSdD3LP__#Zrx7!v>h delta 13041 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 diff --git a/paradise.dme b/paradise.dme index c9da6f053c43..9b5a8be4567b 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3957,6 +3957,7 @@ #include "code\modules\spells\spell_types\list_target\_list_target.dm" #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" +#include "code\modules\spells\spell_types\self\mutate.dm" #include "code\modules\spells\spell_types\self\no_clothes.dm" #include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" #include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" From e781741aaf199e5fae3bfa1072925f4791b03f7a Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 26 May 2026 23:56:32 +0300 Subject: [PATCH 23/42] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=B3?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D0=BE=20=D1=81=20=D1=8D=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=BC=20+=20=D0=BF=D0=BE=D1=87=D0=B8?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=20=D1=82=D0=B5=D1=81=D1=82(=D0=BD=D0=B0?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D0=BE=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/status_effects.dm | 2 -- code/__DEFINES/traits/declarations.dm | 2 ++ code/_globalvars/traits/_traits.dm | 3 ++- code/datums/elements/no_clothes.dm | 23 ------------------ .../gamemodes/wizard}/rightandwrong.dm | 0 code/modules/spells/spell.dm | 4 +-- .../spells/spell_types/self/no_clothes.dm | 15 +----------- icons/mob/screen_alert.dmi | Bin 87457 -> 86888 bytes paradise.dme | 3 +-- 9 files changed, 8 insertions(+), 44 deletions(-) delete mode 100644 code/datums/elements/no_clothes.dm rename code/{modules/spells/spell_types => game/gamemodes/wizard}/rightandwrong.dm (100%) diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 19a58575ddf8..58d6e4ac659d 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -102,8 +102,6 @@ /// Prevents you from automatically grabbing walls to stop moving in space. #define STATUS_EFFECT_UNBALANCED /datum/status_effect/unbalanced -#define STATUS_EFFECT_NO_CLOTHES /datum/status_effect/no_clothes - #define STATUS_EFFECT_MUTATION /datum/status_effect/mutation ///////////// diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 0e8969fbdf7c..948e4dfa2146 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -588,3 +588,5 @@ #define TRAIT_DISCO_DANCER "disco_dancer" #define TRAIT_GIVE_READY "give_ready" + +#define TRAIT_NO_WIZARD_CLOTHES "no_wizard_clothes" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 76f219cf5130..f2d5934fd002 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -11,7 +11,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( /datum/mind = list( "TRAIT_BAD_SOUL" = TRAIT_BAD_SOUL, "TRAIT_HIJACK" = TRAIT_HIJACK, - "TRAIT_MIMING" = TRAIT_MIMING + "TRAIT_MIMING" = TRAIT_MIMING, + "TRAIT_NO_WIZARD_CLOTHES" = TRAIT_NO_WIZARD_CLOTHES ), /datum/hud = list( diff --git a/code/datums/elements/no_clothes.dm b/code/datums/elements/no_clothes.dm deleted file mode 100644 index 1f0a36fa99fb..000000000000 --- a/code/datums/elements/no_clothes.dm +++ /dev/null @@ -1,23 +0,0 @@ -/datum/element/no_clothes - element_flags = ELEMENT_DETACH_ON_HOST_DESTROY - var/mob/living/body - var/datum/mind/owner - -/datum/element/no_clothes/Attach(datum/target) - .=..() - owner = target - START_PROCESSING(SSprocessing, src) - -/datum/element/no_clothes/Detach(datum/source) - .=..() - body.remove_status_effect(STATUS_EFFECT_NO_CLOTHES) - STOP_PROCESSING(SSprocessing, src) - -/datum/element/no_clothes/process(seconds_per_tick) - if(!owner.current || owner.current == body) - return - if(body) - body.remove_status_effect(STATUS_EFFECT_NO_CLOTHES) - body = owner.current - body.apply_status_effect(STATUS_EFFECT_NO_CLOTHES) - diff --git a/code/modules/spells/spell_types/rightandwrong.dm b/code/game/gamemodes/wizard/rightandwrong.dm similarity index 100% rename from code/modules/spells/spell_types/rightandwrong.dm rename to code/game/gamemodes/wizard/rightandwrong.dm diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index e7dba7c94436..c5a9708a46e9 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -182,7 +182,7 @@ return FALSE if(ishuman(owner)) - if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB & !owner.has_status_effect(/datum/status_effect/no_clothes)) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB && !HAS_MIND_TRAIT(owner, TRAIT_NO_WIZARD_CLOTHES)) var/mob/living/carbon/human/human_owner = owner if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES) && !ismonkey(human_owner)) // Monkeys don't need robes to cast as they are inherently imbued with power from the banana dimension if(feedback) @@ -205,7 +205,7 @@ return FALSE // Otherwise, we can check for contents if they have wizardly apparel. This isn't *quite* perfect, but it'll do, especially since many of the edge cases (gorilla holding a wizard hat) still more or less make sense. - if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB && !HAS_MIND_TRAIT(owner, TRAIT_NO_WIZARD_CLOTHES)) var/any_casting = FALSE for(var/obj/item/clothing/item in owner) if(item.clothing_flags & CASTING_CLOTHES) diff --git a/code/modules/spells/spell_types/self/no_clothes.dm b/code/modules/spells/spell_types/self/no_clothes.dm index 870407cb0afd..4a73b4d6013a 100644 --- a/code/modules/spells/spell_types/self/no_clothes.dm +++ b/code/modules/spells/spell_types/self/no_clothes.dm @@ -10,18 +10,5 @@ . = ..() var/mob/living/caster = cast_on to_chat(caster, span_notice("Вы активировали заклинание, отныне вам не нужна одежда для использования заклинаний!")) - caster.mind.AddElement(/datum/element/no_clothes) + ADD_TRAIT(caster.mind, TRAIT_NO_WIZARD_CLOTHES, MAGIC_TRAIT) qdel(src) - -/datum/status_effect/no_clothes - id = "no_clothes" - alert_type = /atom/movable/screen/alert/status_effect/no_clothes - on_remove_on_mob_delete = TRUE - -/datum/status_effect/no_clothes/on_apply() - return TRUE - -/atom/movable/screen/alert/status_effect/no_clothes - name = "Усиление магии" - desc = "Вам больше не нужна одежда для использования заклинаний!" - icon_state = "no_clothes" diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index 3220bbb7b58b340369c5d93837ebde9541d921a3..76c3aa94e850f6c592f8573cbeb451de9bd16264 100644 GIT binary patch delta 12591 zcmYLPbzD>L_a`N!<3mdgY3c4RDJd!G?rttEUDDDZF&Y#Egb64i-5p9d5`z)jZ+`#z zZU5}vyVv%dd){%*d2U%9=0O!^ItbOhg~be=np0;)XTKr0F5NzBw&SRfGT|5B;;~-Fpv1&h?)Qq)X1h#y zM>b-EgCWBMU^>zEpyBW#(qwLVGEy$i5Fiv%&eEY_R7qG%H(=^8Vn65#dwA5a`D=|; z=KnzJYCS+cC80p#>~ntg)xR;G z75ue8BgT^O2-xjV1(BwJNt`RTdJ*_V_BDk;wx5hy$|Z2QG7uj{E5D=;`*E2yv0MWK z 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�~H*+vfH zMNU3Vt=4;2mA6k3o)+^VhD>;K^mKMvbIm0KFhUjx3reT@|?t6D5x5El$ zB56;aiNX?przd13;`Ei6nrn2%ttsKc{p_|YSQQz$2Vt!RB(gzCNg3GEF*i?nFr(!D z-|h^bt*V!@VI5!i1|{!T9-CoLpLIo4EQ^PmCQbumVfeIAiiz<{ddFub;<3s9Q{$%-8qFUKrO-eZG_O>tV*GC8s=h_S7C2?+(3(=N(4iHvvJu z$bmWvHo1%o-cKtcAd|f4%d{5Rw7rq2{J%{BBFl8$h2oEI$~@Ua?THm+_o@B-gz8N@ z#C;BDK6NoLMtJ^?s5~V@Zl#zwt<4*S+I ztcX(Kw{83@MxZ4KNlaDo6R6Vwa$!AJU%KMX)*fYCprK8W(F$~I5^*CUbv7kg_Rt(g zxAm@Umqud}dZSQ+X7_dHw!NoTyHRU*(e1&MB;$_j+G-9ZMDf3>S&4X-?9Bl;1`f^* zv!#(-!c+!-YaLTrd<@CT%>lO4U^3OZ_yTfV5zvpo!U_I*6&0y&go1pjDQPJ-%3DVL E9~HVH>Hq)$ delta 13165 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 Date: Wed, 27 May 2026 19:51:28 +0300 Subject: [PATCH 24/42] =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=BE=D0=BE=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit форсвал оба варианта, ослепление, дым, эмп и куча конжуров --- .../fluid_spread/effects_smoke.dm | 5 ++++ code/modules/clothing/suits/wiz_robe.dm | 2 ++ .../spells/spell_types/aoe_spell/blind.dm | 26 +++++++++++++++++ .../spells/spell_types/conjure/carp.dm | 10 +++++++ .../spells/spell_types/conjure/construct.dm | 13 +++++++++ .../spells/spell_types/conjure/creature.dm | 14 +++++++++ .../spells/spell_types/conjure/timestop.dm | 20 +++++++++++++ .../spells/spell_types/self/emplosion.dm | 22 ++++++++++++++ .../spells/spell_types/self/forcewall.dm | 29 +++++++++++++++++++ code/modules/spells/spell_types/self/smoke.dm | 10 +++++++ .../spell_types/teleport/area_teleport.dm | 2 +- paradise.dme | 8 +++++ 12 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 code/modules/spells/spell_types/aoe_spell/blind.dm create mode 100644 code/modules/spells/spell_types/conjure/carp.dm create mode 100644 code/modules/spells/spell_types/conjure/construct.dm create mode 100644 code/modules/spells/spell_types/conjure/creature.dm create mode 100644 code/modules/spells/spell_types/conjure/timestop.dm create mode 100644 code/modules/spells/spell_types/self/emplosion.dm create mode 100644 code/modules/spells/spell_types/self/forcewall.dm create mode 100644 code/modules/spells/spell_types/self/smoke.dm 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 3d0a681d2162..6ac3cdb10fd8 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 @@ -154,6 +154,11 @@ /obj/effect/particle_effect/fluid/smoke/transparent opacity = FALSE +///////////////////////////////////////////// +// Bad smoke +///////////////////////////////////////////// +/datum/effect_system/fluid_spread/smoke/bad + effect_type = /obj/effect/particle_effect/fluid/smoke/bad /** * A helper proc used to spawn small puffs of smoke. * diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 1bad0ac2f0d0..9eee24313942 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -327,6 +327,7 @@ flags_inv = HIDESHOES|HIDEJUMPSUIT|HIDETAIL slowdown = 0 magical = TRUE + clothing_flags = CASTING_CLOTHES /obj/item/clothing/head/helmet/space/hardsuit/wizard name = "battlemage helmet" @@ -340,6 +341,7 @@ resistance_flags = FIRE_PROOF | ACID_PROOF actions_types = null //No inbuilt light magical = TRUE + clothing_flags = CASTING_CLOTHES /obj/item/clothing/head/helmet/space/hardsuit/wizard/attack_self(mob/user) return diff --git a/code/modules/spells/spell_types/aoe_spell/blind.dm b/code/modules/spells/spell_types/aoe_spell/blind.dm new file mode 100644 index 000000000000..6925a13060c7 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/blind.dm @@ -0,0 +1,26 @@ +/datum/action/cooldown/spell/aoe/blind + name = "Blind" + desc = "This spell temporarily blinds people near you and does not require wizard garb." + school = SCHOOL_TRANSMUTATION + button_icon_state = "blind" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "STI KALY" + invocation_type = INVOCATION_WHISPER + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 1.2 SECONDS + max_targets = 10 + aoe_radius = 10 + +/datum/action/cooldown/spell/aoe/blind/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/blind/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + victim.AdjustEyeBlind(8 SECONDS) + to_chat(victim, span_notice_alt("Your eyes cry out in pain!")) 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..04cc5fddb781 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/carp.dm @@ -0,0 +1,10 @@ +/datum/action/cooldown/spell/conjure/carp + name = "Summon Carp" + desc = "This spell conjures a simple carp." + school = SCHOOL_CONJURATION + cooldown_time = 2 MINUTES + invocation = "NOUK FHUNMM SACP RISSKA" + invocation_type = INVOCATION_SHOUT + summon_type = list(/mob/living/simple_animal/hostile/carp) + sound = 'sound/magic/summon_karp.ogg' + summon_radius = 1 diff --git a/code/modules/spells/spell_types/conjure/construct.dm b/code/modules/spells/spell_types/conjure/construct.dm new file mode 100644 index 000000000000..7482c144232d --- /dev/null +++ b/code/modules/spells/spell_types/conjure/construct.dm @@ -0,0 +1,13 @@ +/datum/action/cooldown/spell/conjure/construct + name = "Artificer" + desc = "This spell conjures a construct which may be controlled by Shades" + + school = SCHOOL_CONJURATION + cooldown_time = 60 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + summon_type = list(/obj/structure/constructshell) + + button_icon_state = "artificer" + sound = 'sound/magic/summonitems_generic.ogg' + summon_radius = 0 diff --git a/code/modules/spells/spell_types/conjure/creature.dm b/code/modules/spells/spell_types/conjure/creature.dm new file mode 100644 index 000000000000..0a4cb820332f --- /dev/null +++ b/code/modules/spells/spell_types/conjure/creature.dm @@ -0,0 +1,14 @@ +/datum/action/cooldown/spell/conjure/creature + name = "Summon Creature Swarm" + desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth" + + school = SCHOOL_CONJURATION + cooldown_time = 2 MINUTES + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "IA IA" + invocation_type = INVOCATION_SHOUT + summon_amount = 10 + + summon_type = list(/mob/living/simple_animal/hostile/creature) + sound = 'sound/magic/summonitems_generic.ogg' + summon_radius = 3 diff --git a/code/modules/spells/spell_types/conjure/timestop.dm b/code/modules/spells/spell_types/conjure/timestop.dm new file mode 100644 index 000000000000..dcba7cf79dc0 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/timestop.dm @@ -0,0 +1,20 @@ +/datum/action/cooldown/spell/conjure/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." + cooldown_time = 50 SECONDS + cooldown_reduction_per_rank = 10 SECONDS + invocation = "TOKI WO TOMARE" + invocation_type = INVOCATION_SHOUT + + button_icon_state = "time" + + summon_type = list(/obj/effect/timestop/wizard) + summon_radius = 0 + +/datum/action/cooldown/spell/conjure/timestop/Grant(mob/grant_to) + . = ..() + ADD_TRAIT(grant_to, TRAIT_TIME_STOP_IMMUNE, UNIQUE_TRAIT_SOURCE(src)) + +/datum/action/cooldown/spell/conjure/timestop/Remove(mob/living/remove_from) + . = ..() + REMOVE_TRAIT(remove_from, TRAIT_TIME_STOP_IMMUNE, UNIQUE_TRAIT_SOURCE(src)) diff --git a/code/modules/spells/spell_types/self/emplosion.dm b/code/modules/spells/spell_types/self/emplosion.dm new file mode 100644 index 000000000000..7f5080dd6218 --- /dev/null +++ b/code/modules/spells/spell_types/self/emplosion.dm @@ -0,0 +1,22 @@ +/datum/action/cooldown/spell/emplosion + name = "Emplosion" + desc = "This spell emplodes an area." + button_icon_state = "emp" + var/emp_heavy = 2 + var/emp_light = 3 + +/datum/action/cooldown/spell/emplosion/cast(atom/cast_on) + . = ..() + empulse(cast_on.loc, emp_heavy, emp_light, TRUE) + +/datum/action/cooldown/spell/emplosion/disable_tech + name = "Disable Tech" + desc = "This spell disables all weapons, cameras and most other technology in range." + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 0.5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "NEC CANTIO" + invocation_type = INVOCATION_SHOUT + emp_heavy = 6 + emp_light = 10 + sound = 'sound/magic/disable_tech.ogg' 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..73e8b332630f --- /dev/null +++ b/code/modules/spells/spell_types/self/forcewall.dm @@ -0,0 +1,29 @@ +/datum/action/cooldown/spell/forcewall + name = "Force Wall" + desc = "This spell creates a small unbreakable wall that only you can pass through, and does not need wizard garb. Lasts 30 seconds." + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 1.2 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "TARCOL MINTI ZHERI" + invocation_type = INVOCATION_WHISPER + sound = 'sound/magic/forcewall.ogg' + button_icon_state = "shield" + var/wall_type = /obj/effect/forcefield/wizard + +/datum/action/cooldown/spell/forcewall/cast(atom/cast_on) + . = ..() + new wall_type(get_turf(cast_on), cast_on) + +/datum/action/cooldown/spell/forcewall/greater + name = "Greater Force Wall" + desc = "Create a larger magical barrier that only you can pass through, but requires wizard garb. Lasts 30 seconds." + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC | SPELL_REQUIRES_WIZARD_GARB + invocation = "TARCOL GRANDI ZHERI" + invocation_type = INVOCATION_SHOUT + button_icon_state = "shield_greater" + +/datum/action/cooldown/spell/forcewall/greater/cast(atom/cast_on) + . = ..() + new wall_type(get_step(cast_on, turn(cast_on.dir, 90)), cast_on) //Extra THICK + new wall_type(get_step(cast_on, turn(cast_on.dir, 270)), cast_on) 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..d7ff24af535f --- /dev/null +++ b/code/modules/spells/spell_types/self/smoke.dm @@ -0,0 +1,10 @@ +/datum/action/cooldown/spell/smoke + name = "Smoke" + desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb." + school = SCHOOL_CONJURATION + cooldown_time = 12 SECONDS + cooldown_reduction_per_rank = 2.5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + smoke_type = /datum/effect_system/fluid_spread/smoke/bad + smoke_amt = 10 + button_icon_state = "smoke" diff --git a/code/modules/spells/spell_types/teleport/area_teleport.dm b/code/modules/spells/spell_types/teleport/area_teleport.dm index 945c294196ff..6d5226a90be0 100644 --- a/code/modules/spells/spell_types/teleport/area_teleport.dm +++ b/code/modules/spells/spell_types/teleport/area_teleport.dm @@ -4,7 +4,7 @@ button_icon_state = "spell_teleport" sound = 'sound/magic/teleport_diss.ogg' cooldown_time = 1 MINUTES - cooldown_reduction_per_rank = 20 SECONDS + cooldown_reduction_per_rank = 10 SECONDS spell_max_level = 3 invocation = "SCYAR NILA" // gets punctuation auto applied invocation_type = INVOCATION_SHOUT diff --git a/paradise.dme b/paradise.dme index 5b89ebcce1f4..5e39fb043b3d 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3946,18 +3946,26 @@ #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\aoe_spell\_aoe_spell.dm" +#include "code\modules\spells\spell_types\aoe_spell\blind.dm" #include "code\modules\spells\spell_types\aoe_spell\knock.dm" #include "code\modules\spells\spell_types\aoe_spell\magic_missile.dm" #include "code\modules\spells\spell_types\charged\_charged.dm" #include "code\modules\spells\spell_types\cone\_cone.dm" #include "code\modules\spells\spell_types\conjure\_conjure.dm" +#include "code\modules\spells\spell_types\conjure\carp.dm" +#include "code\modules\spells\spell_types\conjure\construct.dm" +#include "code\modules\spells\spell_types\conjure\creature.dm" +#include "code\modules\spells\spell_types\conjure\timestop.dm" #include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" #include "code\modules\spells\spell_types\jaunt\_jaunt.dm" #include "code\modules\spells\spell_types\list_target\_list_target.dm" #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" +#include "code\modules\spells\spell_types\self\emplosion.dm" +#include "code\modules\spells\spell_types\self\forcewall.dm" #include "code\modules\spells\spell_types\self\mutate.dm" #include "code\modules\spells\spell_types\self\no_clothes.dm" +#include "code\modules\spells\spell_types\self\smoke.dm" #include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" #include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" #include "code\modules\spells\spell_types\teleport\_teleport.dm" From e72d190fe5acbc04232e3af76292f13fa6271124 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sat, 30 May 2026 13:41:13 +0300 Subject: [PATCH 25/42] =?UTF-8?q?=D1=81=D0=B2=D1=8F=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=20+=20=D0=BF=D0=B0=D1=87=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B5=D0=BB=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit полностью заменил на экшн благословление святоши а также добавил спеллы репульс, святое пламя, фаербол и нуллбокс --- code/datums/spells/chaplain.dm | 50 ------------------- code/game/jobs/job/service.dm | 4 +- .../fluid_spread/effects_smoke.dm | 5 -- code/modules/spells/spell.dm | 3 +- .../spells/spell_types/aoe_spell/repulse.dm | 45 +++++++++++++++++ .../spell_types/aoe_spell/sacred_flame.dm | 25 ++++++++++ .../spells/spell_types/conjure/carp.dm | 1 - .../spells/spell_types/conjure/construct.dm | 1 - .../spells/spell_types/conjure/creature.dm | 1 - .../spells/spell_types/pointed/chaplain.dm | 42 ++++++++++++++++ .../spells/spell_types/pointed/fireball.dm | 14 ++++++ .../spells/spell_types/self/nullbox.dm | 29 +++++++++++ paradise.dme | 6 ++- 13 files changed, 164 insertions(+), 62 deletions(-) delete mode 100644 code/datums/spells/chaplain.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/repulse.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/sacred_flame.dm create mode 100644 code/modules/spells/spell_types/pointed/chaplain.dm create mode 100644 code/modules/spells/spell_types/pointed/fireball.dm create mode 100644 code/modules/spells/spell_types/self/nullbox.dm diff --git a/code/datums/spells/chaplain.dm b/code/datums/spells/chaplain.dm deleted file mode 100644 index 0126444deab2..000000000000 --- a/code/datums/spells/chaplain.dm +++ /dev/null @@ -1,50 +0,0 @@ -/obj/effect/proc_holder/spell/chaplain_bless - name = "Bless" - desc = "Blesses a single person." - - school = "transmutation" - base_cooldown = 6 SECONDS - clothes_req = FALSE - selection_activated_message = span_notice_alt("You prepare a blessing. Click on a target to start blessing.") - selection_deactivated_message = span_notice_alt("The crew will be blessed another time.") - cooldown_min = 2 SECONDS - action_icon_state = "shield" - need_active_overlay = TRUE - -/obj/effect/proc_holder/spell/chaplain_bless/create_new_targeting() - var/datum/spell_targeting/click/T = new() - T.range = 1 - T.click_radius = -1 - return T - -/obj/effect/proc_holder/spell/chaplain_bless/valid_target(mob/living/carbon/human/target, mob/user) - return target.mind && target.ckey && !target.stat - -/obj/effect/proc_holder/spell/chaplain_bless/cast(list/targets, mob/living/user = usr) - if(!istype(user)) - to_chat(user, "Somehow, you are not a living mob. This should never happen. Report this bug.") - revert_cast() - return - - if(!user.mind) - to_chat(user, "Somehow, you are mindless. This should never happen. Report this bug.") - revert_cast() - return - - if(!user.mind.isholy) - to_chat(user, "Somehow, you are not holy enough to use this ability. This should never happen. Report this bug.") - revert_cast() - return - - var/mob/living/carbon/human/target = targets[1] - - spawn(0) // allows cast to complete even if recipient ignores the prompt - if(tgui_alert(target, "[user] wants to bless you, in the name of [user.p_their()] religion. Accept?", "Accept Blessing?", list("Yes", "No")) == "Yes") // prevents forced conversions - user.visible_message("[user] starts blessing [target] in the name of [SSticker.Bible_deity_name].", span_notice("You start blessing [target] in the name of [SSticker.Bible_deity_name].")) - if(do_after(user, 15 SECONDS, target)) - user.visible_message("[user] has blessed [target] in the name of [SSticker.Bible_deity_name].", span_notice("You have blessed [target] in the name of [SSticker.Bible_deity_name].")) - if(!target.mind.isblessed) - target.mind.isblessed = TRUE - user.mind.num_blessed++ - ADD_TRAIT(target, TRAIT_HEALS_FROM_HOLY_PYLONS, INNATE_TRAIT) - diff --git a/code/game/jobs/job/service.dm b/code/game/jobs/job/service.dm index 9387c1d73d86..2cda7e795792 100644 --- a/code/game/jobs/job/service.dm +++ b/code/game/jobs/job/service.dm @@ -563,8 +563,8 @@ new_deity = deity_name bible.deity_name = new_deity SSblackbox.record_feedback("text", "religion_deity", 1, "[new_deity]", 1) - - user.AddSpell(new /obj/effect/proc_holder/spell/chaplain_bless(null)) + var/datum/action/cooldown/spell/pointed/bless/b_action = new() + b_action.Grant(user) if(SSticker) SSticker.Bible_deity_name = bible.deity_name 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 6ac3cdb10fd8..3d0a681d2162 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 @@ -154,11 +154,6 @@ /obj/effect/particle_effect/fluid/smoke/transparent opacity = FALSE -///////////////////////////////////////////// -// Bad smoke -///////////////////////////////////////////// -/datum/effect_system/fluid_spread/smoke/bad - effect_type = /obj/effect/particle_effect/fluid/smoke/bad /** * A helper proc used to spawn small puffs of smoke. * diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index c5a9708a46e9..667bc54ab3e2 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -46,7 +46,8 @@ background_icon_state = "bg_spell" button_icon_state = "spell_default" overlay_icon_state = "bg_spell_border" - active_overlay_icon_state = "bg_spell_border_active_red" + active_overlay_icon_state = "targeting" + background_icon_state_active = "bg_spell" check_flags = AB_CHECK_CONSCIOUS //|AB_CHECK_PHASED panel = "Spells" 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..edabd108b1f7 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/repulse.dm @@ -0,0 +1,45 @@ +/datum/action/cooldown/spell/aoe/repulse + name = "Repulse" + desc = "This spell throws everything around the user away." + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 6.25 SECONDS + invocation = "GITTAH WEIGH" + invocation_type = INVOCATION_SHOUT + + sound = 'sound/magic/repulse.ogg' + var/maxthrow = 5 + var/sparkle_path = /obj/effect/temp_visual/gravpush + button_icon_state = "repulse" + aoe_radius = 5 + var/stun_amt = 3 SECONDS + var/throwtarget + var/distfromcaster + +/datum/action/cooldown/spell/aoe/repulse/get_things_to_cast_on(atom/center) + var/list/thrownatoms = list() + for(var/turf/T in range(center, aoe_radius)) + for(var/atom/movable/AM in T) + thrownatoms += AM + return thrownatoms + +/datum/action/cooldown/spell/aoe/repulse/cast_on_thing_in_aoe(atom/movable/victim, atom/caster) + if(victim == caster || victim.anchored || victim.move_resist == INFINITY) + return + + throwtarget = get_edge_target_turf(caster, get_dir(caster, get_step_away(victim, caster))) + distfromcaster = get_dist(caster, victim) + if(distfromcaster == 0) + if(!isliving(victim)) + return + var/mob/living/victim_mob = victim + victim_mob.Weaken(10 SECONDS) + victim_mob.adjustBruteLoss(5) + to_chat(victim_mob, span_userdanger("You're slammed into the floor by a mystical force!")) + else + new sparkle_path(get_turf(victim), get_dir(caster, victim)) + if(isliving(victim)) + var/mob/living/victim_mob = victim + victim_mob.Weaken(stun_amt) + to_chat(victim_mob, span_userdanger("You're thrown back by a mystical force!")) + spawn(0) + victim.throw_at(throwtarget, ((clamp((maxthrow - (clamp(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1) 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..e6d75c4a1c04 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm @@ -0,0 +1,25 @@ +/datum/action/cooldown/spell/aoe/sacred_flame + name = "Sacred Flame" + desc = "Makes everyone around you more flammable, and lights yourself on fire." + cooldown_time = 6 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "FI'RAN DADISKO" + invocation_type = INVOCATION_SHOUT + button_icon_state = "sacredflame" + sound = 'sound/magic/fireball.ogg' + +/datum/action/cooldown/spell/aoe/sacred_flame/get_things_to_cast_on(atom/center) + var/list/mobs = list() + for(var/mob/living/target in range(center, aoe_radius)) + mobs += target + return mobs + +/datum/action/cooldown/spell/aoe/sacred_flame/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + victim.adjust_fire_stacks(20) + victim.IgniteMob() + if(isliving(caster)) + if(!HAS_TRAIT(caster, TRAIT_RESIST_HEAT)) + ADD_TRAIT(caster, TRAIT_RESIST_HEAT, MAGIC_TRAIT) + to_chat(caster, span_notice("Ты стал огнестойким.")) + var/mob/living/ign_caster = caster + ign_caster.IgniteMob() diff --git a/code/modules/spells/spell_types/conjure/carp.dm b/code/modules/spells/spell_types/conjure/carp.dm index 04cc5fddb781..ed1a4f6f2bc6 100644 --- a/code/modules/spells/spell_types/conjure/carp.dm +++ b/code/modules/spells/spell_types/conjure/carp.dm @@ -1,7 +1,6 @@ /datum/action/cooldown/spell/conjure/carp name = "Summon Carp" desc = "This spell conjures a simple carp." - school = SCHOOL_CONJURATION cooldown_time = 2 MINUTES invocation = "NOUK FHUNMM SACP RISSKA" invocation_type = INVOCATION_SHOUT diff --git a/code/modules/spells/spell_types/conjure/construct.dm b/code/modules/spells/spell_types/conjure/construct.dm index 7482c144232d..99c377ae15df 100644 --- a/code/modules/spells/spell_types/conjure/construct.dm +++ b/code/modules/spells/spell_types/conjure/construct.dm @@ -2,7 +2,6 @@ name = "Artificer" desc = "This spell conjures a construct which may be controlled by Shades" - school = SCHOOL_CONJURATION cooldown_time = 60 SECONDS spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC diff --git a/code/modules/spells/spell_types/conjure/creature.dm b/code/modules/spells/spell_types/conjure/creature.dm index 0a4cb820332f..82ef47e93666 100644 --- a/code/modules/spells/spell_types/conjure/creature.dm +++ b/code/modules/spells/spell_types/conjure/creature.dm @@ -2,7 +2,6 @@ name = "Summon Creature Swarm" desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth" - school = SCHOOL_CONJURATION cooldown_time = 2 MINUTES spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC invocation = "IA IA" diff --git a/code/modules/spells/spell_types/pointed/chaplain.dm b/code/modules/spells/spell_types/pointed/chaplain.dm new file mode 100644 index 000000000000..016bfb9c3a5a --- /dev/null +++ b/code/modules/spells/spell_types/pointed/chaplain.dm @@ -0,0 +1,42 @@ +/datum/action/cooldown/spell/pointed/bless + name = "Bless" + desc = "Blesses a single person." + school = SCHOOL_HOLY + cooldown_time = 6 SECONDS + cooldown_reduction_per_rank = 1 SECONDS + spell_requirements = NONE + active_msg = span_notice_alt("You prepare a blessing. Click on a target to start blessing.") + deactive_msg = span_notice_alt("The crew will be blessed another time.") + button_icon_state = "shield" + +/datum/action/cooldown/spell/pointed/bless/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + var/mob/living/carbon/human/target = cast_on + return target.mind && target.ckey && !target.stat + +/datum/action/cooldown/spell/pointed/bless/cast(atom/cast_on) + . = ..() + if(!ishuman(cast_on)) + to_chat(owner, "Somehow, you are not a living mob. This should never happen. Report this bug.") + return + + var/mob/living/carbon/human/target = cast_on + + if(!owner.mind) + to_chat(owner, "Somehow, you are mindless. This should never happen. Report this bug.") + return + + if(!owner.mind.isholy) + to_chat(owner, "Somehow, you are not holy enough to use this ability. This should never happen. Report this bug.") + return + + spawn(0) // allows cast to complete even if recipient ignores the prompt + if(tgui_alert(target, "[owner] wants to bless you, in the name of [owner.p_their()] religion. Accept?", "Accept Blessing?", list("Yes", "No")) == "Yes") // prevents forced conversions + owner.visible_message("[owner] starts blessing [target] in the name of [SSticker.Bible_deity_name].", span_notice("You start blessing [target] in the name of [SSticker.Bible_deity_name].")) + if(do_after(owner, 15 SECONDS, target)) + owner.visible_message("[owner] has blessed [target] in the name of [SSticker.Bible_deity_name].", span_notice("You have blessed [target] in the name of [SSticker.Bible_deity_name].")) + if(!target.mind.isblessed) + target.mind.isblessed = TRUE + owner.mind.num_blessed++ + ADD_TRAIT(target, TRAIT_HEALS_FROM_HOLY_PYLONS, INNATE_TRAIT) 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..cdde1973ed26 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/fireball.dm @@ -0,0 +1,14 @@ +/datum/action/cooldown/spell/pointed/projectile/fireball + name = "Fireball" + desc = "This spell fires a fireball at a target and does not require wizard garb." + cooldown_time = 6 SECONDS + cooldown_reduction_per_rank = 1 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "ONI SOMA" + invocation_type = INVOCATION_SHOUT + active_msg = span_notice_alt("Your prepare to cast your fireball spell! Left-click to cast at a target!") + deactive_msg = span_notice_alt("You extinguish your fireball...for now.") + projectile_type = /obj/projectile/magic/fireball + button_icon_state = "fireball0" + background_icon_state_active = "bg_spell" + sound = 'sound/magic/fireball.ogg' diff --git a/code/modules/spells/spell_types/self/nullbox.dm b/code/modules/spells/spell_types/self/nullbox.dm new file mode 100644 index 000000000000..ac0ad4e182ec --- /dev/null +++ b/code/modules/spells/spell_types/self/nullbox.dm @@ -0,0 +1,29 @@ +/datum/action/cooldown/spell/nullbox + name = "Призыв блюспейс коробки" + desc = "Позволяет призывать блюспейс коробку, способную проходить сквозь пол и потолок между этажами, как если бы вы находились в космосе. \ + Если у станции один этаж, коробка бесполезна. Нельзя перейти на другой этаж, если в целевом месте кто-то или что-то находится." + cooldown_time = 25 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + button_icon_state = "move_up_down" + sound = 'sound/magic/magic_missile.ogg' + +/datum/action/cooldown/spell/nullbox/cast(atom/cast_on) + . = ..() + var/mutable_appearance/fake_box = mutable_appearance('icons/obj/cardboard_boxes.dmi', "agentbox") + fake_box.color = RANDOM_COLOUR + fake_box.pixel_z = 30 + var/atom/movable/flick_visual/fake_box_visual = cast_on.flick_overlay_view(fake_box, 0.4 SECONDS) + animate(fake_box_visual, pixel_z = 0, time = 0.3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(finish_hide), cast_on, fake_box), 0.3 SECONDS) + +/datum/action/cooldown/spell/nullbox/proc/finish_hide(mob/user, mutable_appearance/fake_box) + if(!isturf(user.loc)) + user.balloon_alert(user, "нужно больше места!") + return + + // Spawn the actual box + var/obj/structure/closet/cardboard/agent/nullspace/box = new(user.loc) + box.color = fake_box.color + box.implant_user_UID = user.UID() + box.create_fake_box() + user.forceMove(box) diff --git a/paradise.dme b/paradise.dme index 5e39fb043b3d..290d8b3b3921 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1045,7 +1045,6 @@ #include "code\datums\spells\area_teleport.dm" #include "code\datums\spells\banana_touch.dm" #include "code\datums\spells\bloodcrawl.dm" -#include "code\datums\spells\chaplain.dm" #include "code\datums\spells\charge.dm" #include "code\datums\spells\charge_up.dm" #include "code\datums\spells\charge_up_bounce.dm" @@ -3949,6 +3948,8 @@ #include "code\modules\spells\spell_types\aoe_spell\blind.dm" #include "code\modules\spells\spell_types\aoe_spell\knock.dm" #include "code\modules\spells\spell_types\aoe_spell\magic_missile.dm" +#include "code\modules\spells\spell_types\aoe_spell\repulse.dm" +#include "code\modules\spells\spell_types\aoe_spell\sacred_flame.dm" #include "code\modules\spells\spell_types\charged\_charged.dm" #include "code\modules\spells\spell_types\cone\_cone.dm" #include "code\modules\spells\spell_types\conjure\_conjure.dm" @@ -3960,11 +3961,14 @@ #include "code\modules\spells\spell_types\jaunt\_jaunt.dm" #include "code\modules\spells\spell_types\list_target\_list_target.dm" #include "code\modules\spells\spell_types\pointed\_pointed.dm" +#include "code\modules\spells\spell_types\pointed\chaplain.dm" +#include "code\modules\spells\spell_types\pointed\fireball.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" #include "code\modules\spells\spell_types\self\emplosion.dm" #include "code\modules\spells\spell_types\self\forcewall.dm" #include "code\modules\spells\spell_types\self\mutate.dm" #include "code\modules\spells\spell_types\self\no_clothes.dm" +#include "code\modules\spells\spell_types\self\nullbox.dm" #include "code\modules\spells\spell_types\self\smoke.dm" #include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" #include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" From 60906ab668e129082934b6664158b9be2bef580b Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sat, 30 May 2026 20:47:04 +0300 Subject: [PATCH 26/42] =?UTF-8?q?=D0=B1=D0=BB=D0=B0=D0=B4=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/datums/spells/bloodcrawl.dm | 294 ------------ code/datums/spells/devil.dm | 4 +- .../demons/shadow_demon/shadow_demon.dm | 9 +- .../demons/slaughter_demon/slaughter_demon.dm | 12 +- .../antagonists/_common/antag_spawner.dm | 2 +- code/modules/antagonists/devil/devil_rank.dm | 4 +- code/modules/events/slaughterevent.dm | 11 +- .../spells/spell_types/jaunt/bloodcrawl.dm | 454 ++++++++++++++++++ .../spells/spell_types/pointed/fireball.dm | 1 - paradise.dme | 2 +- 10 files changed, 476 insertions(+), 317 deletions(-) delete mode 100644 code/datums/spells/bloodcrawl.dm create mode 100644 code/modules/spells/spell_types/jaunt/bloodcrawl.dm diff --git a/code/datums/spells/bloodcrawl.dm b/code/datums/spells/bloodcrawl.dm deleted file mode 100644 index 655e50efddb7..000000000000 --- a/code/datums/spells/bloodcrawl.dm +++ /dev/null @@ -1,294 +0,0 @@ -/obj/effect/proc_holder/spell/bloodcrawl - name = "Кровавый путь" - desc = "Используйте лужи крови, чтобы исчезнуть из реальности." - base_cooldown = 0 - clothes_req = FALSE - human_req = FALSE - phase_allowed = TRUE - should_recharge_after_cast = FALSE - overlay = null - action_icon_state = "bloodcrawl" - action_background_icon_state = "bg_demon" - var/allowed_type = /obj/effect/decal/cleanable - var/phased = FALSE - -/obj/effect/proc_holder/spell/bloodcrawl/create_new_targeting() - var/datum/spell_targeting/targeted/T = new() - T.selection_type = SPELL_SELECTION_RANGE - T.allowed_type = allowed_type - T.random_target = TRUE - T.range = 1 - T.use_turf_of_user = TRUE - return T - -/obj/effect/proc_holder/spell/bloodcrawl/valid_target(obj/effect/decal/cleanable/target, user) - return target.can_bloodcrawl_in() - -/obj/effect/proc_holder/spell/bloodcrawl/can_cast(mob/living/user, charge_check, show_message) - . = ..() - if(!.) - return - if(!isliving(user)) - return FALSE - -/obj/effect/proc_holder/spell/bloodcrawl/cast(list/targets, mob/living/user) - var/atom/target = targets[1] - if(phased) - if(phasein(target, user)) - phased = FALSE - else - if(phaseout(target, user)) - phased = TRUE - cooldown_handler.start_recharge() - -/obj/item/bloodcrawl - name = "blood crawl" - desc = "Вы не можете держать что-либо в этой форме." - icon = 'icons/effects/blood.dmi' - item_flags = ABSTRACT - -/obj/item/bloodcrawl/get_ru_names() - return list( - NOMINATIVE = "кровавый путь", - GENITIVE = "кровавого пути", - DATIVE = "кровавому пути", - ACCUSATIVE = "кровавый путь", - INSTRUMENTAL = "кровавым путём", - PREPOSITIONAL = "кровавом пути", - ) - -/obj/item/bloodcrawl/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/obj/effect/dummy/slaughter //Can't use the wizard one, blocked by jaunt/slow - name = "odd blood" - icon_state = "nothing" - invisibility = 60 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/dummy/slaughter/get_ru_names() - return list( - NOMINATIVE = "странная кровь", - GENITIVE = "странной крови", - DATIVE = "странной крови", - ACCUSATIVE = "странную кровь", - INSTRUMENTAL = "странной кровью", - PREPOSITIONAL = "странной крови", - ) - -/obj/effect/dummy/slaughter/relaymove(mob/user, direction) - forceMove(get_step(src, direction)) - -/obj/effect/dummy/slaughter/ex_act() - return - -/obj/effect/dummy/slaughter/bullet_act() - return - -/obj/effect/dummy/slaughter/singularity_act() - return - -/obj/effect/proc_holder/spell/bloodcrawl/proc/block_hands(mob/living/carbon/user) - if(user.l_hand || user.r_hand) - to_chat(user, span_warning("Вы не можете держать предметы, пока используете Кровавый путь!")) - return FALSE - - var/obj/item/bloodcrawl/left_hand = new(user) - var/obj/item/bloodcrawl/right_hand = new(user) - left_hand.icon_state = "bloodhand_left" - right_hand.icon_state = "bloodhand_right" - user.put_in_l_hand(left_hand) - user.put_in_r_hand(right_hand) - user.regenerate_icons() - return TRUE - -/obj/effect/temp_visual/dir_setting/bloodcrawl - icon = 'icons/mob/mob.dmi' - icon_state = "blank" // Flicks are used instead - duration = 0.6 SECONDS - -/obj/effect/temp_visual/dir_setting/bloodcrawl/Initialize(mapload, set_dir, animation_state) - . = ..() - flick(animation_state, src) // Setting the icon_state to the animation has timing issues and can cause frame skips - -/obj/effect/proc_holder/spell/bloodcrawl/proc/sink_animation(atom/enter_point, mob/living/user) - var/turf/mob_loc = get_turf(user) - visible_message(span_danger("[user] погружа[PLUR_ET_YUT(user)]ся в [enter_point.declent_ru(ACCUSATIVE)].")) - playsound(mob_loc, 'sound/misc/enter_blood.ogg', 100, TRUE, -1) - new /obj/effect/temp_visual/dir_setting/bloodcrawl(mob_loc, user.dir, "jaunt") - -/obj/effect/proc_holder/spell/bloodcrawl/proc/handle_consumption(mob/living/user, mob/living/victim, atom/enter_point, obj/effect/dummy/slaughter/holder) - if(!HAS_TRAIT(user, TRAIT_BLOODCRAWL_EAT)) - return - - if(!istype(victim)) - return - - if(victim.stat == CONSCIOUS) - enter_point.visible_message(span_warning("[victim] вырыва[PLUR_ET_YUT(victim)]ся из [enter_point.declent_ru(GENITIVE)] в последний момент!")) - user.stop_pulling() - return - - victim.emote("scream") - victim.forceMove(holder) - enter_point.visible_message(span_warning("[user] затягива[PLUR_ET_YUT(user)] [victim] в [enter_point.declent_ru(ACCUSATIVE)]!")) - if(user.type == /mob/living/simple_animal/demon/slaughter/laughter) - to_chat(user, "Вы хватаете [victim.declent_ru(ACCUSATIVE)] и начинаете безжалостную щекотку! Вы не можете двигаться, пока делаете это.") - enter_point.visible_message(span_clown("Из крови доносятся крики и дикий хохот...")) - else - to_chat(user, "Вы начинаете пожирать [victim.declent_ru(ACCUSATIVE)]. Вы не можете двигаться, пока делаете это.") - enter_point.visible_message(span_warning("Из крови доносятся громкие звуки трапезы...")) - var/sound - if(isslaughterdemon(user)) - var/mob/living/simple_animal/demon/slaughter/demon = user - sound = demon.feast_sound - else - sound = 'sound/misc/demon_consume.ogg' - - for(var/i in 1 to 3) - playsound(get_turf(user), sound, 100, TRUE) - sleep(3 SECONDS) - - if(!victim) - to_chat(user, span_danger("Вы с радостью пожираете... ничего? Ваша добыча куда-то исчезла!")) - return - - if(ishuman(victim) || isrobot(victim)) - if(user.type == /mob/living/simple_animal/demon/slaughter/laughter) - to_chat(user, span_clown("Вы зарядились весёлой энергией от [victim.declent_ru(GENITIVE)]. Ваши силы восстановлены.")) - else - to_chat(user, span_warning("Вы пожираете [victim.declent_ru(ACCUSATIVE)]. Ваши силы восстановлены.")) - user.heal_damages(brute = 1000, burn = 1000, tox = 1000, oxy = 1000) - else - if(user.type == /mob/living/simple_animal/demon/slaughter/laughter) - to_chat(user, span_clown("Вы заставляете [victim.declent_ru(ACCUSATIVE)] смеяться до слёз, но [GEND_HIS_HER(victim)] страдания лишь слегка подпитывают вашу радость!")) - else - to_chat(user, span_warning("Вы пожираете [victim.declent_ru(ACCUSATIVE)], но эта скудная добыча едва утоляет ваш голод!")) - user.heal_damages(brute = 25, burn = 25) - - if(isslaughterdemon(user)) - var/mob/living/simple_animal/demon/slaughter/demon = user - demon.devoured++ - to_chat(victim, span_userdanger("Вы чувствуете, как чьи-то зубы впиваются в вашу плоть, и...")) - var/obj/item/organ/internal/regenerative_core/legion/core = victim.get_int_organ(/obj/item/organ/internal/regenerative_core/legion) - if(core) - core.remove(victim) - qdel(core) - victim.adjustBruteLoss(1000) - victim.forceMove(demon) - LAZYADD(demon.consumed_mobs, victim) - //ADD_TRAIT(victim, TRAIT_UNREVIVABLE, "demon") - if(ishuman(victim)) - var/mob/living/carbon/human/h_victim = victim - if(h_victim.w_uniform && istype(h_victim.w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/uniform = h_victim.w_uniform - uniform.sensor_mode = SENSOR_OFF - else - victim.ghostize() - qdel(victim) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/post_phase_in(mob/living/user, obj/effect/dummy/slaughter/holder) - REMOVE_TRAIT(user, TRAIT_NO_TRANSFORM, UNIQUE_TRAIT_SOURCE(src)) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/phaseout(obj/effect/decal/cleanable/enter_point, mob/living/carbon/user) - - if(istype(user) && !block_hands(user)) - return FALSE - - ADD_TRAIT(user, TRAIT_NO_TRANSFORM, UNIQUE_TRAIT_SOURCE(src)) - INVOKE_ASYNC(src, PROC_REF(async_phase), enter_point, user) - return TRUE - -/obj/effect/proc_holder/spell/bloodcrawl/proc/async_phase(obj/effect/decal/cleanable/enter_point, mob/living/user) - var/turf/mobloc = get_turf(user) - sink_animation(enter_point, user) - var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(mobloc) - var/victim = user.pulling - user.forceMove(holder) - user.ExtinguishMob() - handle_consumption(user, victim, enter_point, holder) - post_phase_in(user, holder) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/rise_animation(turf/tele_loc, mob/living/user, atom/exit_point) - new /obj/effect/temp_visual/dir_setting/bloodcrawl(tele_loc, user.dir, "jauntup") - if(prob(25) && isdemon(user)) - var/list/voice = list('sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/i_see_you1.ogg') - playsound(tele_loc, pick(voice), 50, TRUE, -1) - exit_point.visible_message(span_warning("[DECLENT_RU_CAP(user, NOMINATIVE)] возникает из [exit_point.declent_ru(GENITIVE)]!")) - playsound(get_turf(tele_loc), 'sound/misc/exit_blood.ogg', 100, TRUE, -1) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/unblock_hands(mob/living/carbon/user) - if(!istype(user)) - return - for(var/obj/item/bloodcrawl/item in user) - qdel(item) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/rise_message(atom/exit_point) - exit_point.visible_message(span_warning("[DECLENT_RU_CAP(exit_point, NOMINATIVE)] начинает пузыриться...")) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/post_phase_out(atom/exit_point, mob/living/user) - if(isslaughterdemon(user)) - user.add_movespeed_modifier(/datum/movespeed_modifier/slaughter_boost) - addtimer(CALLBACK(user, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/slaughter_boost), 6 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) - user.color = exit_point.color - addtimer(VARSET_CALLBACK(user, color, null), 6 SECONDS) - -/obj/effect/proc_holder/spell/bloodcrawl/proc/phasein(atom/enter_point, mob/living/user) - if(HAS_TRAIT_NOT_FROM(user, TRAIT_NO_TRANSFORM, UNIQUE_TRAIT_SOURCE(src))) - return FALSE - if(HAS_TRAIT_FROM(user, TRAIT_NO_TRANSFORM, UNIQUE_TRAIT_SOURCE(src))) - if(user.type == /mob/living/simple_animal/demon/slaughter/laughter) - to_chat(user, span_warning("Сначала закончите щекотку!")) - else - to_chat(user, span_warning("Сначала закончите трапезу!")) - return FALSE - rise_message(enter_point) - if(!do_after(user, 2 SECONDS, enter_point)) - return FALSE - if(!enter_point) - return FALSE - var/turf/tele_loc = isturf(enter_point) ? enter_point : enter_point.loc - var/holder = user.loc - user.forceMove(tele_loc) - user.client.set_eye(user) - - rise_animation(tele_loc, user, enter_point) - - unblock_hands(user) - - QDEL_NULL(holder) - - post_phase_out(enter_point, user) - return TRUE - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl - name = "Теневой путь" - desc = "Воспользуйтесь тьмой, чтобы раствориться в реальности." - action_background_icon_state = "shadow_demon_bg" - action_icon_state = "shadow_crawl" - allowed_type = /turf - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/valid_target(turf/target, user) - return target.get_lumcount() < 0.2 - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_message(atom/exit_point) - return - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_animation(turf/tele_loc, mob/living/user, atom/exit_point) - new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(user), user.dir, "shadowwalk_appear") - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/handle_consumption(mob/living/L, mob/living/victim, atom/enter_point, obj/effect/dummy/slaughter/holder) - return - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/sink_animation(atom/enter_point, mob/living/user) - enter_point.visible_message(span_danger("[user] погружается во тьму...")) - new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(user), user.dir, "shadowwalk_disappear") - -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/post_phase_in(mob/living/user, obj/effect/dummy/slaughter/holder) - ..() - if(!istype(user, /mob/living/simple_animal/demon/shadow)) - return - var/mob/living/simple_animal/demon/shadow/demon = user - demon.RegisterSignal(holder, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness)) - diff --git a/code/datums/spells/devil.dm b/code/datums/spells/devil.dm index 971723eb9a81..2b35b71b729d 100644 --- a/code/datums/spells/devil.dm +++ b/code/datums/spells/devil.dm @@ -213,7 +213,7 @@ return FALSE /obj/effect/proc_holder/spell/infernal_jaunt/cast(list/targets, mob/living/user = usr) - if(istype(user.loc, /obj/effect/dummy/slaughter)) + if(istype(user.loc, /obj/effect/dummy/phased_mob/blood)) var/continuing = 0 if(istype(get_area(user), /area/shuttle)) // Can always phase in in a shuttle. continuing = TRUE @@ -251,7 +251,7 @@ visible_message(span_warning("[DECLENT_RU_CAP(src, NOMINATIVE)] исчезает в огненной вспышке!")) playsound(get_turf(src), 'sound/misc/enter_blood.ogg', 100, TRUE, -1) - var/obj/effect/dummy/slaughter/s_holder = new(loc) + var/obj/effect/dummy/phased_mob/blood/s_holder = new(loc) ExtinguishMob() forceMove(s_holder) diff --git a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm index 989d584210bc..cc609817a705 100644 --- a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm +++ b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm @@ -30,12 +30,11 @@ remove_from_all_data_huds() AddSpell(new /obj/effect/proc_holder/spell/fireball/shadow_grapple) ADD_TRAIT(src, TRAIT_HEALS_FROM_HELL_RIFTS, INNATE_TRAIT) - var/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/crawl = new - AddSpell(crawl) + var/datum/action/cooldown/spell/jaunt/bloodcrawl/shadow_crawl/action = new() + action.Grant(src) whisper_action.button_icon_state = "shadow_whisper" whisper_action.background_icon_state = "shadow_demon_bg" - if(istype(loc, /obj/effect/dummy/slaughter)) - crawl.phased = TRUE + if(istype(loc, /obj/effect/dummy/phased_mob/blood)) RegisterSignal(loc, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness)) RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(check_darkness)) add_overlay(emissive_appearance(icon, "shadow_demon_eye_glow_overlay", src)) @@ -43,7 +42,7 @@ /mob/living/simple_animal/demon/shadow/Life(seconds, times_fired) . = ..() var/lum_count = check_darkness() - var/damage_mod = istype(loc, /obj/effect/dummy/slaughter) ? 0.5 : 1 + var/damage_mod = istype(loc, /obj/effect/dummy/phased_mob/blood) ? 0.5 : 1 if(lum_count > 0.2) adjustBruteLoss(30 * damage_mod) // 20 seconds in light and you are done SEND_SOUND(src, sound('sound/weapons/sear.ogg')) diff --git a/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm index 3224248d65fd..519168b8b269 100644 --- a/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm +++ b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm @@ -37,10 +37,8 @@ remove_from_all_data_huds() ADD_TRAIT(src, TRAIT_BLOODCRAWL_EAT, TRAIT_BLOODCRAWL_EAT) ADD_TRAIT(src, TRAIT_HEALS_FROM_HELL_RIFTS, INNATE_TRAIT) - var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new - AddSpell(bloodspell) - if(istype(loc, /obj/effect/dummy/slaughter)) - bloodspell.phased = TRUE + var/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/action = new() + action.Grant(src) /mob/living/simple_animal/demon/slaughter/Destroy() // Only execute the below if we successfully died @@ -213,13 +211,15 @@ /obj/item/organ/internal/heart/demon/slaughter/insert(mob/living/carbon/M, special = ORGAN_MANIPULATION_DEFAULT) . = ..() - M?.mind?.AddSpell(new /obj/effect/proc_holder/spell/bloodcrawl(null)) + var/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/action = new() + action.Grant(M) /obj/item/organ/internal/heart/demon/slaughter/remove(mob/living/carbon/M, special = ORGAN_MANIPULATION_DEFAULT) if(M.mind) REMOVE_TRAIT(M, TRAIT_BLOODCRAWL, TRAIT_BLOODCRAWL) REMOVE_TRAIT(M, TRAIT_BLOODCRAWL_EAT, TRAIT_BLOODCRAWL_EAT) - M.mind.RemoveSpell(/obj/effect/proc_holder/spell/bloodcrawl) + var/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/action = locate() in M.actions + action.Remove(M) . = ..() /** diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 1d679f88dd70..e6aebdd20d81 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -205,7 +205,7 @@ to_chat(user, span_notice("The demons do not respond to your summon. Perhaps you should try again later.")) /obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, type = "", mob/user) - var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(T) + var/obj/effect/dummy/phased_mob/blood/holder = new /obj/effect/dummy/phased_mob/blood(T) var/mob/living/simple_animal/demon/demon = new demon_type(holder) demon.vialspawned = TRUE demon.holder = holder diff --git a/code/modules/antagonists/devil/devil_rank.dm b/code/modules/antagonists/devil/devil_rank.dm index e685292a67da..6074e769af8c 100644 --- a/code/modules/antagonists/devil/devil_rank.dm +++ b/code/modules/antagonists/devil/devil_rank.dm @@ -145,7 +145,7 @@ /datum/devil_rank/true_devil/apply_rank() var/mob/devil_mob = devil.owner.current - if(istype(devil_mob.loc, /obj/effect/dummy/slaughter)) + if(istype(devil_mob.loc, /obj/effect/dummy/phased_mob/blood)) devil_mob.forceMove(get_turf(devil_mob)) if(isdevil(devil_mob)) to_chat(devil_mob, span_revenbignotice("Вы чувствуете, как ваше тело меняется.")) @@ -170,7 +170,7 @@ /datum/devil_rank/ascend/apply_rank() var/mob/devil_mob = devil.owner.current - if(istype(devil_mob.loc, /obj/effect/dummy/slaughter)) + if(istype(devil_mob.loc, /obj/effect/dummy/phased_mob/blood)) devil_mob.forceMove(get_turf(devil_mob)) var/mob/living/carbon/true_devil/ascended/true_devil if(isascendeddevil(devil_mob)) diff --git a/code/modules/events/slaughterevent.dm b/code/modules/events/slaughterevent.dm index 61fb1f71b354..1d47331e41c9 100644 --- a/code/modules/events/slaughterevent.dm +++ b/code/modules/events/slaughterevent.dm @@ -23,9 +23,10 @@ var/datum/mind/player_mind = new /datum/mind(key_of_slaughter) player_mind.active = TRUE var/turf/spawn_loc = get_spawn_loc(player_mind.current) - var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(spawn_loc) + var/obj/effect/dummy/phased_mob/blood/holder = new /obj/effect/dummy/phased_mob/blood(spawn_loc) var/mob/living/simple_animal/demon/new_demon = new demon(holder) new_demon.holder = holder + holder.jaunter = new_demon player_mind.transfer_to(new_demon) player_mind.assigned_role = ROLE_DEMON player_mind.special_role = SPECIAL_ROLE_DEMON @@ -49,10 +50,10 @@ return pick(spawn_locs) /datum/event/spawn_slaughter/start() - if(num_station_players() <= minplayers) - var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MAJOR] - EC.next_event_time = world.time + (60 * 10) - return //we don't spawn demons on lowpop. Instead, we reroll! + // if(num_station_players() <= minplayers) + // var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MAJOR] + // EC.next_event_time = world.time + (60 * 10) + // return //we don't spawn demons on lowpop. Instead, we reroll! INVOKE_ASYNC(src, PROC_REF(get_slaughter)) 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..98ae8e3903e8 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -0,0 +1,454 @@ +/** + * ### 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 existence via pools of blood." + background_icon_state = "bg_demon" + button_icon_state = "bloodcrawl" + background_icon_state_active = "bg_demon" + spell_requirements = NONE + + jaunt_type = /obj/effect/dummy/phased_mob/blood + + /// 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 + var/list/mob/living/consumed_mobs = list() + +/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 + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(jaunter, jaunt_turf) + if(!holder) + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + 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/misc/enter_blood.ogg', 50, TRUE, -1) + jaunter.ExtinguishMob() + + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + 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(HAS_TRAIT(jaunter, TRAIT_NO_TRANSFORM)) + 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.get_equipped_items(INCLUDE_HELD)) + unjaunter.temporarily_remove_item_from_inventory(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/misc/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/blood/came_from = locate() in landing_turf + var/new_color = came_from?.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 existence 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/misc/demon_consume.ogg' + /// Apply damage every 20 seconds if we bloodcrawling + var/jaunt_damage_timer + /// When demon first appears, it does not take damage while in Jaunt. He also doesn't take damage while he's eating someone. + var/resist_jaunt_damage = TRUE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/Grant(mob/grant_to) + . = ..() +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/Remove(mob/remove_from) + UnregisterSignal(remove_from, COMSIG_LIVING_DEATH) + return ..() + +/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 + + jaunt_damage_timer = addtimer(CALLBACK(src, PROC_REF(damage_for_lazy_demon), jaunter), 20 SECONDS, TIMER_STOPPABLE) + + var/turf/jaunt_turf = get_turf(jaunter) + // if we're not pulling anyone, or we can't what we're pulling + if(!ishuman(coming_with)) + return + + var/mob/living/carbon/human/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) + consumed_mobs += victim + victim.emote("scream") + jaunt_turf.visible_message( + span_boldwarning("[jaunter] drags [victim] into [blood]!"), + blind_message = span_notice("You hear a splash."), + ) + + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + consume_victim(victim, jaunter) + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + return TRUE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/on_jaunt_exited(obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter) + deltimer(jaunt_damage_timer) + resist_jaunt_damage = FALSE + return ..() + +/** + * Apply damage to demon when he using bloodcrawl. + * Every 20 SECONDS check if demon still crawling and update timer. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/damage_for_lazy_demon(mob/living/lazy_demon) + if(QDELETED(lazy_demon)) + return + if(resist_jaunt_damage) + return + if(isturf(lazy_demon.loc)) + return + if(isnull(jaunt_damage_timer)) + return + lazy_demon.apply_damage(lazy_demon.maxHealth * 0.05, BRUTE) + jaunt_damage_timer = addtimer(CALLBACK(src, PROC_REF(damage_for_lazy_demon), lazy_demon), 20 SECONDS, TIMER_STOPPABLE) + to_chat(lazy_demon, span_warning("You feel your flesh dissolving into the sea of blood. You shouldn't stay in Blood Crawl for too long!")) + +/** + * 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.rejuvenate() + + // No defib possible after laughter + victim.apply_damage(1000, BRUTE) + if(victim.stat != DEAD) + victim.investigate_log("has been killed by being consumed by a slaughter demon.", INVESTIGATE_DEATHS) + victim.death() + on_victim_consumed(victim, jaunter) + + if(!istype(jaunter, /mob/living/simple_animal/demon/slaughter)) + return + var/mob/living/simple_animal/demon/slaughter/demon = jaunter + LAZYADD(demon.consumed_mobs, victim) + +/** + * 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) + if(!iscarbon(jaunter)) + resist_jaunt_damage = TRUE + deltimer(jaunt_damage_timer) + 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) + if(!iscarbon(jaunter)) + resist_jaunt_damage = FALSE + jaunt_damage_timer = addtimer(CALLBACK(src, PROC_REF(damage_for_lazy_demon), jaunter), 20 SECONDS, TIMER_STOPPABLE) + to_chat(jaunter, span_danger("You devour [victim]. Your health is fully restored.")) + consumed_mobs += victim + RegisterSignal(victim, COMSIG_MOB_STATCHANGE, PROC_REF(on_victim_statchange)) + RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(on_victim_deleted)) + + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_death(datum/source) + SIGNAL_HANDLER + + var/turf/release_turf = get_turf(source) + for(var/mob/living/dead as anything in consumed_mobs) + + // Unregister the signals first + UnregisterSignal(dead, list(COMSIG_MOB_STATCHANGE, COMSIG_QDELETING)) + + dead.forceMove(release_turf) + // Heals them back to state one + dead.playsound_local(release_turf, 'sound/misc/exit_blood.ogg', 50, TRUE, -1) + +/** + * Handle signal from a consumed mob being deleted. Clears any references. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_deleted(datum/source) + SIGNAL_HANDLER + + consumed_mobs -= source + +/** + * Handle signal from a consumed mob changing stat. + * + * A signal handler for if one of the slaughter 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/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 + victim.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.")) + exit_blood_effect(victim) + + consumed_mobs -= victim + UnregisterSignal(victim, COMSIG_MOB_STATCHANGE) + +/** + * 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 existence 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_horn2.ogg' + +/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) + . = ..() + +/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_QDELETING, PROC_REF(on_victim_deleted)) + +/** + * Signal proc for COMSIG_LIVING_DEATH and COMSIG_QDELETING + * + * If our demon is deleted or destroyed, expel all of our consumed mobs + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_death(datum/source) + + 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_QDELETING)) + + friend.forceMove(release_turf) + // Heals them back to state one + friend.rejuvenate() + friend.playsound_local(release_turf, 'sound/misc/exit_blood.ogg', 50, TRUE, -1) + to_chat(friend, span_clown("You leave [source]'s warm embrace, and feel ready to take on the world.")) + +/// 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) + +/// Different graphic for the position indicator +/obj/effect/dummy/phased_mob/blood + phased_mob_icon_state = "mini_leaper" + +/datum/action/cooldown/spell/jaunt/bloodcrawl/shadow_crawl + name = "Теневой путь" + desc = "Воспользуйтесь тьмой, чтобы раствориться в реальности." + background_icon_state = "shadow_demon_bg" + background_icon_state_active = "shadow_demon_bg" + button_icon_state = "shadow_crawl" + +/datum/action/cooldown/spell/jaunt/bloodcrawl/shadow_crawl/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced) + var/turf/jaunt_loc = jaunter.loc + ADD_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(jaunter, jaunt_loc) + if(!holder) + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + return FALSE + + RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) + + jaunt_loc.visible_message(span_danger("[jaunter] погружается во тьму...")) + new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(jaunter), jaunter.dir, "shadowwalk_disappear") + playsound(jaunt_loc, 'sound/misc/enter_blood.ogg', 50, TRUE, -1) + jaunter.ExtinguishMob() + + REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + if(!istype(jaunter, /mob/living/simple_animal/demon/shadow)) + return TRUE + var/mob/living/simple_animal/demon/shadow/demon = jaunter + demon.RegisterSignal(holder, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness)) + return TRUE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/shadow_crawl/can_cast_spell(feedback) + var/turf/turf_to_check = get_turf(owner) + if(turf_to_check.get_lumcount() < 0.2) + return TRUE + if(feedback) + to_chat(owner, span_warning("Слишком светло!")) + return FALSE + +/datum/action/cooldown/spell/jaunt/bloodcrawl/shadow_crawl/try_exit_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced) + if(!forced) + if(HAS_TRAIT(jaunter, TRAIT_NO_TRANSFORM)) + to_chat(jaunter, span_warning("You cannot exit yet!!")) + return FALSE + + if(!exit_jaunt(jaunter)) + return FALSE + + new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(jaunter), jaunter.dir, "shadowwalk_appear") + return TRUE + +/obj/effect/temp_visual/dir_setting/bloodcrawl + icon = 'icons/mob/mob.dmi' + icon_state = "blank" // Flicks are used instead + duration = 0.6 SECONDS + +/obj/effect/temp_visual/dir_setting/bloodcrawl/Initialize(mapload, set_dir, animation_state) + . = ..() + flick(animation_state, src) // Setting the icon_state to the animation has timing issues and can cause frame skips diff --git a/code/modules/spells/spell_types/pointed/fireball.dm b/code/modules/spells/spell_types/pointed/fireball.dm index cdde1973ed26..61f7f16f5abe 100644 --- a/code/modules/spells/spell_types/pointed/fireball.dm +++ b/code/modules/spells/spell_types/pointed/fireball.dm @@ -10,5 +10,4 @@ deactive_msg = span_notice_alt("You extinguish your fireball...for now.") projectile_type = /obj/projectile/magic/fireball button_icon_state = "fireball0" - background_icon_state_active = "bg_spell" sound = 'sound/magic/fireball.ogg' diff --git a/paradise.dme b/paradise.dme index 90639e75cdc3..d392974f02df 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1047,7 +1047,6 @@ #include "code\datums\spell_targeting\telepathic.dm" #include "code\datums\spells\area_teleport.dm" #include "code\datums\spells\banana_touch.dm" -#include "code\datums\spells\bloodcrawl.dm" #include "code\datums\spells\charge.dm" #include "code\datums\spells\charge_up.dm" #include "code\datums\spells\charge_up_bounce.dm" @@ -3963,6 +3962,7 @@ #include "code\modules\spells\spell_types\conjure\timestop.dm" #include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" #include "code\modules\spells\spell_types\jaunt\_jaunt.dm" +#include "code\modules\spells\spell_types\jaunt\bloodcrawl.dm" #include "code\modules\spells\spell_types\list_target\_list_target.dm" #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\pointed\chaplain.dm" From 3aacf95b1a312d0b5333566a734f9ccf1456c3e7 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sat, 30 May 2026 20:56:10 +0300 Subject: [PATCH 27/42] =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/spells/spell_types/jaunt/bloodcrawl.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm index 98ae8e3903e8..6749752485af 100644 --- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -263,7 +263,7 @@ victim.death() on_victim_consumed(victim, jaunter) - if(!istype(jaunter, /mob/living/simple_animal/demon/slaughter)) + if(!isslaughterdemon(jaunter)) return var/mob/living/simple_animal/demon/slaughter/demon = jaunter LAZYADD(demon.consumed_mobs, victim) From c9250a7878e20afe0e4e7a03c391bc3b2ec49a6b Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Wed, 3 Jun 2026 19:12:34 +0300 Subject: [PATCH 28/42] =?UTF-8?q?=D1=81=D0=BF=D0=B5=D0=BB=D0=BB=D1=8B=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20+=20vv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавил спеллы конструктов, заклинания зарядки и проклятия клувней а также возможность выдавать заклинания-экшны через vv --- code/__DEFINES/vv.dm | 1 + code/datums/spell.dm | 1 + code/datums/spells/cluwne.dm | 71 ------ code/game/objects/effects/phased_mob.dm | 4 - code/modules/admin/admin_verbs.dm | 15 ++ code/modules/events/slaughterevent.dm | 8 +- code/modules/mob/mob_vv.dm | 4 + .../spells/spell_types/conjure/build.dm | 109 +++++++++ .../spells/spell_types/conjure/construct.dm | 11 + .../spell_types/conjure_item/soulstone.dm | 10 + .../spells/spell_types/jaunt/bloodcrawl.dm | 2 +- .../spell_types/jaunt/ethereal_jaunt.dm | 212 ++++++++++++++++++ .../modules/spells/spell_types/self/charge.dm | 41 ++++ .../spells/spell_types/touch/cluwne.dm | 111 +++++++++ icons/obj/weapons/guns/projectiles.dmi | Bin 62471 -> 65070 bytes paradise.dme | 7 +- 16 files changed, 526 insertions(+), 81 deletions(-) create mode 100644 code/modules/spells/spell_types/conjure/build.dm create mode 100644 code/modules/spells/spell_types/conjure_item/soulstone.dm create mode 100644 code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm create mode 100644 code/modules/spells/spell_types/self/charge.dm create mode 100644 code/modules/spells/spell_types/touch/cluwne.dm diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index a7223bb449dd..5c303df5b9d4 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -131,6 +131,7 @@ #define VV_HK_GIVE_MOB_ACTION "give_mob_action" #define VV_HK_REMOVE_MOB_ACTION "remove_mob_action" #define VV_HK_GIVE_SPELL "give_spell" +#define VV_HK_GIVE_SPELL_A "give_spell(A)" #define VV_HK_REMOVE_SPELL "remove_spell" #define VV_HK_GIVE_DISEASE "give_disease" #define VV_HK_GODMODE "godmode" diff --git a/code/datums/spell.dm b/code/datums/spell.dm index 1f618630bb14..84b8890c260f 100644 --- a/code/datums/spell.dm +++ b/code/datums/spell.dm @@ -10,6 +10,7 @@ return GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) +GLOBAL_LIST_INIT(spells_a, typesof(/datum/action/cooldown/spell)) /obj/effect/proc_holder/proc/InterceptClickOn(mob/user, params, atom/target) if(user.ranged_ability != src) diff --git a/code/datums/spells/cluwne.dm b/code/datums/spells/cluwne.dm index a717ff6366d3..5b07f1ea354f 100644 --- a/code/datums/spells/cluwne.dm +++ b/code/datums/spells/cluwne.dm @@ -10,74 +10,3 @@ action_icon_state = "cluwne" -/mob/living/carbon/human/proc/makeCluwne() - if(!get_int_organ(/obj/item/organ/internal/brain/cluwne)) - var/obj/item/organ/internal/brain/cluwne/idiot_brain = new - internal_organs |= idiot_brain //Well, everything's for recursion prevention. - idiot_brain.insert(src, special = ORGAN_MANIPULATION_NOEFFECT, make_cluwne = FALSE) - idiot_brain.dna = dna.Clone() - else - return - to_chat(src, span_danger("You feel funny.")) - setBrainLoss(80) - set_nutrition(9000) - overeatduration = 9000 - Confused(60 SECONDS) - if(mind) - mind.assigned_role = "Cluwne" - - var/obj/item/organ/internal/honktumor/cursed/tumor = new - tumor.insert(src) - force_gene_block(GLOB.nervousblock, TRUE) - rename_character(newname = "cluwne") - - drop_item_ground(w_uniform, force = TRUE) - drop_item_ground(shoes, force = TRUE) - drop_item_ground(gloves, force = TRUE) - if(!istype(wear_mask, /obj/item/clothing/mask/cursedclown)) //Infinite loops otherwise - drop_item_ground(wear_mask, force = TRUE) - equip_to_slot_or_del(new /obj/item/clothing/under/cursedclown, ITEM_SLOT_CLOTH_INNER) - equip_to_slot_or_del(new /obj/item/clothing/gloves/cursedclown, ITEM_SLOT_GLOVES) - equip_to_slot_or_del(new /obj/item/clothing/mask/cursedclown, ITEM_SLOT_MASK) - equip_to_slot_or_del(new /obj/item/clothing/shoes/cursedclown, ITEM_SLOT_FEET) - grant_mimicking() - -/mob/living/carbon/human/proc/makeAntiCluwne() - to_chat(src, span_danger("You don't feel very funny.")) - adjustBrainLoss(-120) - set_nutrition(NUTRITION_LEVEL_STARVING) - overeatduration = 0 - SetConfused(0) - SetJitter(0) - if(mind) - mind.assigned_role = "Lawyer" - - var/obj/item/organ/internal/honktumor/cursed/tumor = get_int_organ(/obj/item/organ/internal/honktumor/cursed) - if(tumor) - tumor.remove(src) - else - force_gene_block(GLOB.comicblock, FALSE) - force_gene_block(GLOB.clumsyblock, FALSE) - force_gene_block(GLOB.nervousblock, FALSE) - - var/obj/item/clothing/under/U = w_uniform - drop_item_ground(w_uniform, force = TRUE) - if(U) - qdel(U) - - var/obj/item/clothing/shoes/S = shoes - drop_item_ground(shoes, force = TRUE) - if(S) - qdel(S) - - if(istype(wear_mask, /obj/item/clothing/mask/cursedclown)) - drop_item_ground(wear_mask, force = TRUE) - - if(istype(gloves, /obj/item/clothing/gloves/cursedclown)) - var/obj/item/clothing/gloves/G = gloves - drop_item_ground(gloves, force = TRUE) - qdel(G) - - equip_to_slot_or_del(new /obj/item/clothing/under/lawyer/black, ITEM_SLOT_CLOTH_INNER) - equip_to_slot_or_del(new /obj/item/clothing/shoes/color/black, ITEM_SLOT_FEET) - diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm index d6f5be8b3578..23f51a5e86c1 100644 --- a/code/game/objects/effects/phased_mob.dm +++ b/code/game/objects/effects/phased_mob.dm @@ -101,7 +101,6 @@ var/turf/newloc = get_step_multiz(src,direction) if(!newloc) return - var/area/destination_area = newloc.loc movedelay = world.time + movespeed if(SEND_SIGNAL(src, COMSIG_MOB_PHASED_CHECK, user, newloc) & COMPONENT_BLOCK_PHASED_MOVE) @@ -110,9 +109,6 @@ if(newloc.turf_flags & NOJAUNT) to_chat(user, span_warning("Some strange aura is blocking the way.")) return - if(destination_area.area_flags & NOJAUNT) - to_chat(user, span_danger("Some dull, universal force is blocking the way. Its overwhelmingly oppressive force feels dangerous.")) - return if(direction == UP || direction == DOWN) newloc = can_z_move(direction, get_turf(src), newloc, ZMOVE_INCAPACITATED_CHECKS | ZMOVE_FEEDBACK | ZMOVE_ALLOW_ANCHORED, user) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 13f400031d88..90bcff7cdc0f 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -342,6 +342,21 @@ ADMIN_VERB(give_spell, R_EVENT, "Give Spell", ADMIN_VERB_NO_DESCRIPTION, ADMIN_C BLACKBOX_LOG_ADMIN_VERB("Give Spell") log_and_message_admins("gave [key_name_log(T)] the spell [S].") +ADMIN_VERB(give_spell_a, R_EVENT, "Give Spell(A)", ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEGORY_HIDDEN, mob/T in GLOB.mob_list) + var/list/spell_list = list() + var/type_length = length("/datum/action/cooldown/spell") + 2 + for(var/A in GLOB.spells_a) + spell_list[copytext("[A]", type_length)] = A + var/datum/action/cooldown/spell/S = tgui_input_list(user, "Choose the spell to give to that guy", "ABRAKADABRA", spell_list) + if(!S) + return + S = spell_list[S] + S = new S + S.Grant(T) + + BLACKBOX_LOG_ADMIN_VERB("Give Spell") + log_and_message_admins("gave [key_name_log(T)] the spell [S].") + ADMIN_VERB(give_disease, R_EVENT, "Give Disease", ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEGORY_HIDDEN, mob/target in GLOB.mob_list) var/choosen_disease = tgui_input_list(user, "Choose the disease to give to that guy", "ACHOO", GLOB.diseases) if(!choosen_disease) diff --git a/code/modules/events/slaughterevent.dm b/code/modules/events/slaughterevent.dm index 1d47331e41c9..cd03ba8bc5c2 100644 --- a/code/modules/events/slaughterevent.dm +++ b/code/modules/events/slaughterevent.dm @@ -50,10 +50,10 @@ return pick(spawn_locs) /datum/event/spawn_slaughter/start() - // if(num_station_players() <= minplayers) - // var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MAJOR] - // EC.next_event_time = world.time + (60 * 10) - // return //we don't spawn demons on lowpop. Instead, we reroll! + if(num_station_players() <= minplayers) + var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MAJOR] + EC.next_event_time = world.time + (60 * 10) + return //we don't spawn demons on lowpop. Instead, we reroll! INVOKE_ASYNC(src, PROC_REF(get_slaughter)) diff --git a/code/modules/mob/mob_vv.dm b/code/modules/mob/mob_vv.dm index 2417f2660fd7..f0ac143f332e 100644 --- a/code/modules/mob/mob_vv.dm +++ b/code/modules/mob/mob_vv.dm @@ -14,6 +14,7 @@ VV_DROPDOWN_OPTION(VV_HK_DROP_ALL, "Drop Everything") VV_DROPDOWN_OPTION(VV_HK_GIVE_DISEASE, "Give Disease") VV_DROPDOWN_OPTION(VV_HK_GIVE_SPELL, "Give Spell") + VV_DROPDOWN_OPTION(VV_HK_GIVE_SPELL_A, "Give Spell(A)") VV_DROPDOWN_OPTION(VV_HK_GODMODE, "Toggle Godmode") VV_DROPDOWN_OPTION(VV_HK_REGEN_ICONS, "Regenerate Icons") VV_DROPDOWN_OPTION(VV_HK_GIB, "Gib") @@ -152,6 +153,9 @@ if(href_list[VV_HK_GIVE_SPELL]) return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_spell, src) + if(href_list[VV_HK_GIVE_SPELL_A]) + return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_spell_a, src) + if(href_list[VV_HK_MOB_PLAYER_PANEL]) return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/vuap_personal, src) diff --git a/code/modules/spells/spell_types/conjure/build.dm b/code/modules/spells/spell_types/conjure/build.dm new file mode 100644 index 000000000000..962c8611bb2a --- /dev/null +++ b/code/modules/spells/spell_types/conjure/build.dm @@ -0,0 +1,109 @@ +/datum/action/cooldown/spell/conjure/floor + name = "Summon Cult Floor" + desc = "This spell constructs a cult floor" + button_icon_state = "floorconstruct" + background_icon_state = "bg_cult" + background_icon_state_active = "bg_cult" + school = SCHOOL_CONJURATION + cooldown_time = 2 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + summon_type = list(/turf/simulated/floor/engine/cult) + summon_radius = 0 + +/datum/action/cooldown/spell/conjure/floor/can_cast_spell(feedback) + var/turf/caster_t = get_turf(owner) + if(is_admin_level(caster_t.z)) //Stop crashing the server by spawning turfs on transit tiles + return FALSE + . = ..() + +/datum/action/cooldown/spell/conjure/floor/holy + name = "Summon Holy Floor" + desc = "Это заклинание создаст святой пол." + button_icon_state = "holyfloorconstruct" + background_icon_state = "bg_spell" + background_icon_state_active = "bg_spell" + summon_type = list(/turf/simulated/floor/engine/cult/holy) + +/datum/action/cooldown/spell/conjure/wall + name = "Summon Cult Wall" + desc = "This spell constructs a cult wall" + button_icon_state = "cultforcewall" + background_icon_state = "bg_cult" + background_icon_state_active = "bg_cult" + school = "conjuration" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel + summon_type = list(/turf/simulated/wall/cult/artificer) //we don't want artificer-based runed metal farms + summon_radius = 0 + cooldown_time = 10 SECONDS + +/datum/action/cooldown/spell/conjure/wall/holy + name = "Summon Holy Wall" + desc = "Это заклинание создаст святую стену, способную сдержать врагов. Впрочем, вы можете легко её разрушить." + button_icon_state = "holyforcewall" + background_icon_state = "bg_spell" + background_icon_state_active = "bg_spell" + summon_type = list(/turf/simulated/wall/cult/artificer/holy) + +/datum/action/cooldown/spell/conjure/wall/reinforced + name = "Greater Construction" + desc = "This spell constructs a reinforced metal wall" + cooldown_time = 30 SECONDS + create_summon_timer = 5 SECONDS + //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel + summon_type = list(/turf/simulated/wall/r_wall) + +/datum/action/cooldown/spell/conjure/pylon + name = "Cult Pylon" + desc = "This spell conjures a fragile crystal from Redspace. Makes for a convenient light source." + button_icon_state = "pylon" + background_icon_state = "bg_cult" + background_icon_state_active = "bg_cult" + school = "conjuration" + cooldown_time = 20 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel + summon_type = list(/obj/structure/cult/functional/pylon) + summon_radius = 0 + +/datum/action/cooldown/spell/conjure/pylon/holy + name = "Holy Pylon" + desc = "Это заклинание создаст уязвимый к повреждениям кристалл, что будет немного лечить иных коснтруктов" + button_icon_state = "holy_pylon" + background_icon_state = "bg_spell" + background_icon_state_active = "bg_spell" + summon_type = list(/obj/structure/cult/functional/pylon/holy) + +/datum/action/cooldown/spell/conjure/lesserforcewall + name = "Shield" + desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire" + button_icon_state = "cultforcewall" + background_icon_state = "bg_cult" + background_icon_state_active = "bg_cult" + school = "transmutation" + cooldown_time = 30 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel + summon_lifespan = 20 SECONDS + summon_type = list(/obj/effect/forcefield/cult) + summon_radius = 0 + +/datum/action/cooldown/spell/conjure/lesserforcewall/holy + button_icon_state = "holyforcewall" + background_icon_state = "bg_spell" + background_icon_state_active = "bg_spell" + summon_type = list(/obj/effect/forcefield/holy) + +/obj/effect/forcefield/cult + desc = "That eerie looking obstacle seems to have been pulled from another dimension through sheer force" + name = "eldritch wall" + icon = 'icons/effects/cult_effects.dmi' + icon_state = "m_shield_cult" + light_color = LIGHT_COLOR_INTENSE_RED + +/obj/effect/forcefield/holy + desc = "Этот щит так и светится! Не похоже что его можно будет убрать так просто." + name = "holy field" + icon = 'icons/effects/cult_effects.dmi' + icon_state = "holy_field" + light_color = LIGHT_COLOR_DARK_BLUE diff --git a/code/modules/spells/spell_types/conjure/construct.dm b/code/modules/spells/spell_types/conjure/construct.dm index 99c377ae15df..0fd466d30fa4 100644 --- a/code/modules/spells/spell_types/conjure/construct.dm +++ b/code/modules/spells/spell_types/conjure/construct.dm @@ -10,3 +10,14 @@ button_icon_state = "artificer" sound = 'sound/magic/summonitems_generic.ogg' summon_radius = 0 + +/datum/action/cooldown/spell/conjure/construct/lesser + cooldown_time = 3 MINUTES + background_icon_state = "bg_cult" + background_icon_state_active = "bg_cult" + +/datum/action/cooldown/spell/conjure/construct/lesser/holy + button_icon_state = "artificer_holy" + background_icon_state = "bg_spell" + background_icon_state_active = "bg_spell" + summon_type = list(/obj/structure/constructshell/holy) diff --git a/code/modules/spells/spell_types/conjure_item/soulstone.dm b/code/modules/spells/spell_types/conjure_item/soulstone.dm new file mode 100644 index 000000000000..abc36a6d58e2 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/soulstone.dm @@ -0,0 +1,10 @@ +/datum/action/cooldown/spell/conjure_item/soulstone + name = "Summon Soulstone" + desc = "This spell reaches into Redspace, summoning one of the legendary fragments across time and space" + button_icon_state = "summonsoulstone" + background_icon_state = "bg_cult" + school = SCHOOL_CONJURATION + cooldown_time = 5 MINUTES + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + delete_old = FALSE + item_type = /obj/item/soulstone diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm index 6749752485af..b01ea95e54c3 100644 --- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -393,7 +393,7 @@ /// Different graphic for the position indicator /obj/effect/dummy/phased_mob/blood - phased_mob_icon_state = "mini_leaper" + phased_mob_icon_state = "blood_bolt" /datum/action/cooldown/spell/jaunt/bloodcrawl/shadow_crawl name = "Теневой путь" 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..ea0560d6c338 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm @@ -0,0 +1,212 @@ +/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 + /// 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, turf/loc_override) + . = ..() + if(!.) + return + + var/turf/cast_turf = get_turf(.) + new jaunt_out_type(cast_turf, jaunter.dir) + jaunter.ExtinguishMob() + 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. + * Sets up the signals and exit points and + * starts the timer until the end. + */ +/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 + ADD_TRAIT(cast_on, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(cast_on) + REMOVE_TRAIT(cast_on, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + + if(!holder) + CRASH("[type] attempted do_jaunt but failed to create a jaunt holder via enter_jaunt.") + + 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, MAGIC_TRAIT) + + 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) + cast_on.Immobilize(jaunt_in_time, ignore_canstun = TRUE) + 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 + + ADD_TRAIT(cast_on, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + exit_jaunt(cast_on) + REMOVE_TRAIT(cast_on, TRAIT_NO_TRANSFORM, MAGIC_TRAIT) + + REMOVE_TRAIT(cast_on, TRAIT_IMMOBILIZED, MAGIC_TRAIT) + + 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() + +/// 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 +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift + name = "Phase Shift" + desc = "This spell allows you to pass through walls" + button_icon_state = "phaseshift" + background_icon_state = "bg_cult" + background_icon_state_active = "bg_cult" + cooldown_time = 20 SECONDS + cooldown_reduction_per_rank = 0 + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel + 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 + jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt/red + + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/do_steam_effects(turf/loc) + return + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/holy + jaunt_in_type = /obj/effect/temp_visual/dir_setting/holy_shift + jaunt_out_type = /obj/effect/temp_visual/dir_setting/holy_shift/out + button_icon_state = "holyphaseshift" + background_icon_state = "bg_spell" + jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt + +/// Red coloured variant +/obj/effect/dummy/phased_mob/spell_jaunt/red + phased_mob_icon_state = "red_2" 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..0206ad05901e --- /dev/null +++ b/code/modules/spells/spell_types/self/charge.dm @@ -0,0 +1,41 @@ +/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." + school = SCHOOL_TRANSMUTATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "DIRI CEL" + invocation_type = INVOCATION_WHISPER + button_icon_state = "charge" + +/datum/action/cooldown/spell/charge/cast(atom/cast_on) + . = ..() + var/charge_result = NONE + var/atom/charge_target_name + + var/mob/living/living = cast_on + + if(living.pulling) + charge_target_name = living.pulling.name + charge_result = living.pulling.magic_charge_act(living) + + if(!(charge_result & RECHARGE_SUCCESSFUL)) + var/list/hand_items = list(living.get_active_hand(), living.get_inactive_hand()) + + for(var/obj/item in hand_items) + charge_target_name = item.name + charge_result = item.magic_charge_act(living) + + if(charge_result & RECHARGE_SUCCESSFUL) + break + + if(!(charge_result & RECHARGE_SUCCESSFUL)) + to_chat(living, span_notice("You feel magical power surging to your hands, but the feeling rapidly fades...")) + return + + if(charge_result & RECHARGE_BURNOUT) + to_chat(living, span_caution("[charge_target_name] is reacting poorly to the spell!")) + return + + to_chat(living, span_notice("[charge_target_name] suddenly feels very warm!")) diff --git a/code/modules/spells/spell_types/touch/cluwne.dm b/code/modules/spells/spell_types/touch/cluwne.dm new file mode 100644 index 000000000000..3104bc8105eb --- /dev/null +++ b/code/modules/spells/spell_types/touch/cluwne.dm @@ -0,0 +1,111 @@ +/datum/action/cooldown/spell/touch/cluwne + name = "Curse of the Cluwne" + desc = "Turns the target into a fat and cursed monstrosity of a clown." + hand_path = /obj/item/melee/magic_hand/cluwne + invocation = "NWOLC EGNEVER" + school = SCHOOL_TRANSMUTATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + button_icon_state = "cluwne" + +/obj/item/melee/magic_hand/cluwne + name = "cluwne touch" + desc = "It's time to start clowning around." + icon_state = "cluwnecurse" + item_state = "cluwnecurse" + +/datum/action/cooldown/spell/touch/cluwne/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + if(victim == caster || !ishuman(victim) || caster.incapacitated()) + return + + if(iswizard(victim)) + to_chat(caster, span_warning("The spell has no effect on [victim].")) + return + + var/datum/effect_system/fluid_spread/smoke/s = new + s.set_up(amount = 5, location = victim) + s.start() + + var/mob/living/carbon/human/H = victim + if(H.mind) + if(H.mind.assigned_role != "Cluwne") + H.makeCluwne() + else + H.makeAntiCluwne() + remove_hand(reset_cooldown_after = TRUE) + playsound(victim, 'sound/misc/sadtrombone.ogg', 50, TRUE) + ..() + + +/mob/living/carbon/human/proc/makeCluwne() + if(!get_int_organ(/obj/item/organ/internal/brain/cluwne)) + var/obj/item/organ/internal/brain/cluwne/idiot_brain = new + internal_organs |= idiot_brain //Well, everything's for recursion prevention. + idiot_brain.insert(src, special = ORGAN_MANIPULATION_NOEFFECT, make_cluwne = FALSE) + idiot_brain.dna = dna.Clone() + else + return + to_chat(src, span_danger("You feel funny.")) + setBrainLoss(80) + set_nutrition(9000) + overeatduration = 9000 + Confused(60 SECONDS) + if(mind) + mind.assigned_role = "Cluwne" + + var/obj/item/organ/internal/honktumor/cursed/tumor = new + tumor.insert(src) + force_gene_block(GLOB.nervousblock, TRUE) + rename_character(newname = "cluwne") + + drop_item_ground(w_uniform, force = TRUE) + drop_item_ground(shoes, force = TRUE) + drop_item_ground(gloves, force = TRUE) + if(!istype(wear_mask, /obj/item/clothing/mask/cursedclown)) //Infinite loops otherwise + drop_item_ground(wear_mask, force = TRUE) + equip_to_slot_or_del(new /obj/item/clothing/under/cursedclown, ITEM_SLOT_CLOTH_INNER) + equip_to_slot_or_del(new /obj/item/clothing/gloves/cursedclown, ITEM_SLOT_GLOVES) + equip_to_slot_or_del(new /obj/item/clothing/mask/cursedclown, ITEM_SLOT_MASK) + equip_to_slot_or_del(new /obj/item/clothing/shoes/cursedclown, ITEM_SLOT_FEET) + grant_mimicking() + +/mob/living/carbon/human/proc/makeAntiCluwne() + to_chat(src, span_danger("You don't feel very funny.")) + adjustBrainLoss(-120) + set_nutrition(NUTRITION_LEVEL_STARVING) + overeatduration = 0 + SetConfused(0) + SetJitter(0) + if(mind) + mind.assigned_role = "Lawyer" + + var/obj/item/organ/internal/honktumor/cursed/tumor = get_int_organ(/obj/item/organ/internal/honktumor/cursed) + if(tumor) + tumor.remove(src) + else + force_gene_block(GLOB.comicblock, FALSE) + force_gene_block(GLOB.clumsyblock, FALSE) + force_gene_block(GLOB.nervousblock, FALSE) + + var/obj/item/clothing/under/U = w_uniform + drop_item_ground(w_uniform, force = TRUE) + if(U) + qdel(U) + + var/obj/item/clothing/shoes/S = shoes + drop_item_ground(shoes, force = TRUE) + if(S) + qdel(S) + + if(istype(wear_mask, /obj/item/clothing/mask/cursedclown)) + drop_item_ground(wear_mask, force = TRUE) + + if(istype(gloves, /obj/item/clothing/gloves/cursedclown)) + var/obj/item/clothing/gloves/G = gloves + drop_item_ground(gloves, force = TRUE) + qdel(G) + + equip_to_slot_or_del(new /obj/item/clothing/under/lawyer/black, ITEM_SLOT_CLOTH_INNER) + equip_to_slot_or_del(new /obj/item/clothing/shoes/color/black, ITEM_SLOT_FEET) + diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi index 4e022038ea24f737c8cc7578b165df4247e46070..1334667d1284f9f5669604ce10b8b4a563526508 100644 GIT binary patch literal 65070 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 literal 62471 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
    (=I;iGUtyUGz_7opr5Cg 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�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 Wx%)529`ODa;ivveN4ZAPD(rvxN7(=X diff --git a/paradise.dme b/paradise.dme index d392974f02df..9ae5b9e4572d 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3828,10 +3828,10 @@ #include "code\modules\reagents\chemistry\recipes\food.dm" #include "code\modules\reagents\chemistry\recipes\medicine.dm" #include "code\modules\reagents\chemistry\recipes\others.dm" +#include "code\modules\reagents\chemistry\recipes\paint.dm" #include "code\modules\reagents\chemistry\recipes\pyrotechnics.dm" #include "code\modules\reagents\chemistry\recipes\slime_extracts.dm" #include "code\modules\reagents\chemistry\recipes\toxins.dm" -#include "code\modules\reagents\chemistry\recipes\paint.dm" #include "code\modules\reagents\reagent_containers\applicator.dm" #include "code\modules\reagents\reagent_containers\borghydro.dm" #include "code\modules\reagents\reagent_containers\bottle.dm" @@ -3956,18 +3956,22 @@ #include "code\modules\spells\spell_types\charged\_charged.dm" #include "code\modules\spells\spell_types\cone\_cone.dm" #include "code\modules\spells\spell_types\conjure\_conjure.dm" +#include "code\modules\spells\spell_types\conjure\build.dm" #include "code\modules\spells\spell_types\conjure\carp.dm" #include "code\modules\spells\spell_types\conjure\construct.dm" #include "code\modules\spells\spell_types\conjure\creature.dm" #include "code\modules\spells\spell_types\conjure\timestop.dm" #include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" +#include "code\modules\spells\spell_types\conjure_item\soulstone.dm" #include "code\modules\spells\spell_types\jaunt\_jaunt.dm" #include "code\modules\spells\spell_types\jaunt\bloodcrawl.dm" +#include "code\modules\spells\spell_types\jaunt\ethereal_jaunt.dm" #include "code\modules\spells\spell_types\list_target\_list_target.dm" #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\pointed\chaplain.dm" #include "code\modules\spells\spell_types\pointed\fireball.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" +#include "code\modules\spells\spell_types\self\charge.dm" #include "code\modules\spells\spell_types\self\emplosion.dm" #include "code\modules\spells\spell_types\self\forcewall.dm" #include "code\modules\spells\spell_types\self\mutate.dm" @@ -3981,6 +3985,7 @@ #include "code\modules\spells\spell_types\teleport\blink.dm" #include "code\modules\spells\spell_types\touch\_touch.dm" #include "code\modules\spells\spell_types\touch\banana_touch.dm" +#include "code\modules\spells\spell_types\touch\cluwne.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" From 8b7ade2758244ef39b35e1f01f1e7f531986422d Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Wed, 3 Jun 2026 21:04:05 +0300 Subject: [PATCH 29/42] =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=81=D1=82=D1=80?= =?UTF-8?q?=D1=83=D0=BA=D1=82=D1=8B=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавил спелл ночного видения и полностью перенес спеллы конструктов на экшны --- _maps/common.dm | 2 +- code/datums/spells/construct_spells.dm | 186 ------------------ code/datums/spells/night_vision.dm | 30 --- .../gamemodes/miniantags/revenant/revenant.dm | 3 +- .../mob/living/simple_animal/constructs.dm | 47 ++--- .../living/simple_animal/hostile/hellhound.dm | 3 +- .../living/simple_animal/hostile/statue.dm | 3 +- .../spells/spell_types/conjure/build.dm | 1 - .../spell_types/conjure_item/soulstone.dm | 6 +- .../spells/spell_types/self/nightvision.dm | 27 +++ code/modules/spells/spell_types/self/smoke.dm | 8 + paradise.dme | 3 +- 12 files changed, 72 insertions(+), 247 deletions(-) delete mode 100644 code/datums/spells/construct_spells.dm delete mode 100644 code/datums/spells/night_vision.dm create mode 100644 code/modules/spells/spell_types/self/nightvision.dm diff --git a/_maps/common.dm b/_maps/common.dm index 60f9496f97b1..c34e5b358843 100644 --- a/_maps/common.dm +++ b/_maps/common.dm @@ -9,7 +9,7 @@ z3 = engineering stuff (called Z4, dont question it) z4 = lavaland */ -//#define FAST_LOAD +#define FAST_LOAD #define MAP_TRANSITION_CONFIG list() diff --git a/code/datums/spells/construct_spells.dm b/code/datums/spells/construct_spells.dm deleted file mode 100644 index e44aba02de12..000000000000 --- a/code/datums/spells/construct_spells.dm +++ /dev/null @@ -1,186 +0,0 @@ -//////////////////////////////Construct Spells///////////////////////// - -/obj/effect/proc_holder/spell/aoe/conjure/construct/lesser - base_cooldown = 3 MINUTES - action_background_icon_state = "bg_cult" - human_req = FALSE - -/obj/effect/proc_holder/spell/aoe/conjure/construct/lesser/holy - action_icon_state = "artificer_holy" - action_background_icon_state = "bg_spell" - summon_type = list(/obj/structure/constructshell/holy) - -/obj/effect/proc_holder/spell/aoe/conjure/build - aoe_range = 0 - one_per_turf = TRUE - -/obj/effect/proc_holder/spell/aoe/conjure/build/floor - name = "Summon Cult Floor" - desc = "This spell constructs a cult floor" - action_icon_state = "floorconstruct" - action_background_icon_state = "bg_cult" - school = "conjuration" - base_cooldown = 2 SECONDS - clothes_req = FALSE - human_req = FALSE - centcom_cancast = FALSE //Stop crashing the server by spawning turfs on transit tiles - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - summon_type = list(/turf/simulated/floor/engine/cult) - -/obj/effect/proc_holder/spell/aoe/conjure/build/floor/holy - name = "Summon Holy Floor" - desc = "Это заклинание создаст святой пол." - action_icon_state = "holyfloorconstruct" - action_background_icon_state = "bg_spell" - summon_type = list(/turf/simulated/floor/engine/cult/holy) - -/obj/effect/proc_holder/spell/aoe/conjure/build/wall - name = "Summon Cult Wall" - desc = "This spell constructs a cult wall" - action_icon_state = "cultforcewall" - action_background_icon_state = "bg_cult" - school = "conjuration" - clothes_req = FALSE - human_req = FALSE - centcom_cancast = FALSE //Stop crashing the server by spawning turfs on transit tiles - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - summon_type = list(/turf/simulated/wall/cult/artificer) //we don't want artificer-based runed metal farms - -/obj/effect/proc_holder/spell/aoe/conjure/build/wall/holy - name = "Summon Holy Wall" - desc = "Это заклинание создаст святую стену, способную сдержать врагов. Впрочем, вы можете легко её разрушить." - action_icon_state = "holyforcewall" - action_background_icon_state = "bg_spell" - summon_type = list(/turf/simulated/wall/cult/artificer/holy) - -/obj/effect/proc_holder/spell/aoe/conjure/build/wall/reinforced - name = "Greater Construction" - desc = "This spell constructs a reinforced metal wall" - base_cooldown = 30 SECONDS - delay = 5 SECONDS - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - summon_type = list(/turf/simulated/wall/r_wall) - -/obj/effect/proc_holder/spell/aoe/conjure/build/soulstone - name = "Summon Soulstone" - desc = "This spell reaches into Redspace, summoning one of the legendary fragments across time and space" - action_icon_state = "summonsoulstone" - action_background_icon_state = "bg_cult" - school = "conjuration" - base_cooldown = 5 MINUTES - clothes_req = FALSE - human_req = FALSE - holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - one_per_turf = FALSE - summon_type = list(/obj/item/soulstone) - -/obj/effect/proc_holder/spell/aoe/conjure/build/soulstone/holy - action_icon_state = "summonsoulstone_holy" - action_background_icon_state = "bg_spell" - summon_type = list(/obj/item/soulstone/anybody/purified) - -/obj/effect/proc_holder/spell/aoe/conjure/build/pylon - name = "Cult Pylon" - desc = "This spell conjures a fragile crystal from Redspace. Makes for a convenient light source." - action_icon_state = "pylon" - action_background_icon_state = "bg_cult" - school = "conjuration" - base_cooldown = 20 SECONDS - clothes_req = FALSE - human_req = FALSE - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - summon_type = list(/obj/structure/cult/functional/pylon) - -/obj/effect/proc_holder/spell/aoe/conjure/build/pylon/holy - name = "Holy Pylon" - desc = "Это заклинание создаст уязвимый к повреждениям кристалл, что будет немного лечить иных коснтруктов" - action_icon_state = "holy_pylon" - action_background_icon_state = "bg_spell" - summon_type = list(/obj/structure/cult/functional/pylon/holy) - -/obj/effect/proc_holder/spell/aoe/conjure/build/lesserforcewall - name = "Shield" - desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire" - action_icon_state = "cultforcewall" - action_background_icon_state = "bg_cult" - school = "transmutation" - base_cooldown = 30 SECONDS - clothes_req = FALSE - human_req = FALSE - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - summon_lifespan = 20 SECONDS - summon_type = list(/obj/effect/forcefield/cult) - -/obj/effect/proc_holder/spell/aoe/conjure/build/lesserforcewall/holy - action_icon_state = "holyforcewall" - action_background_icon_state = "bg_spell" - summon_type = list(/obj/effect/forcefield/holy) - -/obj/effect/forcefield/cult - desc = "That eerie looking obstacle seems to have been pulled from another dimension through sheer force" - name = "eldritch wall" - icon = 'icons/effects/cult_effects.dmi' - icon_state = "m_shield_cult" - light_color = LIGHT_COLOR_INTENSE_RED - -/obj/effect/forcefield/holy - desc = "Этот щит так и светится! Не похоже что его можно будет убрать так просто." - name = "holy field" - icon = 'icons/effects/cult_effects.dmi' - icon_state = "holy_field" - light_color = LIGHT_COLOR_DARK_BLUE - -/obj/effect/proc_holder/spell/ethereal_jaunt/shift - name = "Phase Shift" - desc = "This spell allows you to pass through walls" - action_icon_state = "phaseshift" - action_background_icon_state = "bg_cult" - base_cooldown = 20 SECONDS - clothes_req = FALSE - human_req = FALSE - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - 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/ethereal_jaunt/shift/do_jaunt(mob/living/target) - target.set_light_on(FALSE) - ..() - if(isconstruct(target)) - var/mob/living/simple_animal/hostile/construct/construct = target - if(construct.holy) - construct.set_light_range_power_color(3, 5, LIGHT_COLOR_DARK_BLUE) - else - construct.set_light_range_power_color(2, 3, SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC) - -/obj/effect/proc_holder/spell/ethereal_jaunt/shift/jaunt_steam(mobloc) - return - -/obj/effect/proc_holder/spell/ethereal_jaunt/shift/holy - jaunt_in_type = /obj/effect/temp_visual/dir_setting/holy_shift - jaunt_out_type = /obj/effect/temp_visual/dir_setting/holy_shift/out - action_icon_state = "holyphaseshift" - action_background_icon_state = "bg_spell" - -/obj/effect/proc_holder/spell/projectile/magic_missile/lesser - name = "Lesser Magic Missile" - action_background_icon_state = "bg_cult" - base_cooldown = 40 SECONDS - clothes_req = FALSE - human_req = FALSE - //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - proj_lifespan = 10 - max_targets = 6 - proj_type = "/obj/effect/proc_holder/spell/inflict_handler/magic_missile/lesser" - -/obj/effect/proc_holder/spell/inflict_handler/magic_missile/lesser - amt_weakened = 4 SECONDS - -/obj/effect/proc_holder/spell/smoke/disable - name = "Paralysing Smoke" - desc = "This spell spawns a cloud of paralysing smoke." - action_icon_state = "parasmoke" - action_background_icon_state = "bg_cult" - base_cooldown = 20 SECONDS - holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel - smoke_type = SMOKE_SLEEPING diff --git a/code/datums/spells/night_vision.dm b/code/datums/spells/night_vision.dm deleted file mode 100644 index 306b26d4536c..000000000000 --- a/code/datums/spells/night_vision.dm +++ /dev/null @@ -1,30 +0,0 @@ -/obj/effect/proc_holder/spell/night_vision - name = "Toggle Nightvision" - desc = "Toggle your nightvision mode." - - base_cooldown = 1 SECONDS - clothes_req = FALSE - human_req = FALSE - - message = span_notice_alt("You toggle your night vision!") - -/obj/effect/proc_holder/spell/night_vision/create_new_targeting() - return new /datum/spell_targeting/self - -/obj/effect/proc_holder/spell/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() - diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm index 03b033363ddc..fa211e923eaf 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant.dm @@ -217,7 +217,8 @@ to_chat(src, chat_box_red(messages.Join("
    "))) /mob/living/simple_animal/revenant/proc/giveSpells() - mind.AddSpell(new /obj/effect/proc_holder/spell/night_vision/revenant(null)) + var/datum/action/cooldown/spell/nightvision/nightvision = new() + nightvision.Grant(src) mind.AddSpell(new /obj/effect/proc_holder/spell/revenant_transmit(null)) mind.AddSpell(new /obj/effect/proc_holder/spell/aoe/revenant/defile(null)) mind.AddSpell(new /obj/effect/proc_holder/spell/aoe/revenant/malfunction(null)) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 660ea5a901bd..1d50ca7a1b44 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -44,7 +44,8 @@ icon_state = SSticker.cultdat.get_icon(construct_type) for(var/spell in construct_spells) - AddSpell(new spell(null)) + var/datum/action/cooldown/spell/spell_to_add = new spell + spell_to_add.Grant(src) set_light_range_power_color(2, 3, SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC) @@ -130,7 +131,7 @@ mob_size = MOB_SIZE_LARGE move_resist = MOVE_FORCE_STRONG pull_force = MOVE_FORCE_STRONG - construct_spells = list(/obj/effect/proc_holder/spell/night_vision, /obj/effect/proc_holder/spell/aoe/conjure/build/lesserforcewall) + construct_spells = list(/datum/action/cooldown/spell/nightvision, /datum/action/cooldown/spell/conjure/lesserforcewall) force_threshold = 11 playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, \ create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." @@ -161,7 +162,7 @@ faction = list("neutral") icon_state = "holy_juggernaut" icon_living = "holy_juggernaut" - construct_spells = list(/obj/effect/proc_holder/spell/night_vision, /obj/effect/proc_holder/spell/aoe/conjure/build/lesserforcewall/holy) + construct_spells = list(/datum/action/cooldown/spell/nightvision, /datum/action/cooldown/spell/conjure/lesserforcewall/holy) holy = TRUE ////////////////////////Wraith///////////////////////////////////////////// @@ -180,7 +181,7 @@ attacktext = "рубит" attack_sound = 'sound/weapons/bladeslice.ogg' construct_type = "wraith" - construct_spells = list(/obj/effect/proc_holder/spell/night_vision, /obj/effect/proc_holder/spell/ethereal_jaunt/shift) + construct_spells = list(/datum/action/cooldown/spell/nightvision, /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift) retreat_distance = 2 //AI wraiths will move in and out of combat playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls." tts_seed = "Kelthuzad" @@ -198,7 +199,7 @@ icon_state = "holy_shifter" icon_living = "holy_shifter" holy = TRUE - construct_spells = list(/obj/effect/proc_holder/spell/night_vision, /obj/effect/proc_holder/spell/ethereal_jaunt/shift/holy) + construct_spells = list(/datum/action/cooldown/spell/nightvision, /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/holy) /////////////////////////////Artificer///////////////////////// @@ -223,13 +224,13 @@ attack_sound = 'sound/weapons/punch2.ogg' construct_type = "builder" construct_spells = list( - /obj/effect/proc_holder/spell/night_vision, - /obj/effect/proc_holder/spell/projectile/magic_missile/lesser, - /obj/effect/proc_holder/spell/aoe/conjure/construct/lesser, - /obj/effect/proc_holder/spell/aoe/conjure/build/wall, - /obj/effect/proc_holder/spell/aoe/conjure/build/floor, - /obj/effect/proc_holder/spell/aoe/conjure/build/pylon, - /obj/effect/proc_holder/spell/aoe/conjure/build/soulstone, + /datum/action/cooldown/spell/nightvision, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/conjure/wall, + /datum/action/cooldown/spell/conjure/floor, + /datum/action/cooldown/spell/conjure/pylon, + /datum/action/cooldown/spell/conjure_item/soulstone, ) playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ @@ -287,13 +288,13 @@ icon_living = "holy_artificer" holy = TRUE construct_spells = list( - /obj/effect/proc_holder/spell/night_vision, - /obj/effect/proc_holder/spell/projectile/magic_missile/lesser, - /obj/effect/proc_holder/spell/aoe/conjure/construct/lesser/holy, - /obj/effect/proc_holder/spell/aoe/conjure/build/wall/holy, - /obj/effect/proc_holder/spell/aoe/conjure/build/floor/holy, - /obj/effect/proc_holder/spell/aoe/conjure/build/pylon/holy, - /obj/effect/proc_holder/spell/aoe/conjure/build/soulstone/holy, + /datum/action/cooldown/spell/nightvision, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/cooldown/spell/conjure/construct/lesser/holy, + /datum/action/cooldown/spell/conjure/wall/holy, + /datum/action/cooldown/spell/conjure/floor/holy, + /datum/action/cooldown/spell/conjure/pylon/holy, + /datum/action/cooldown/spell/conjure_item/soulstone/holy ) /////////////////////////////Behemoth///////////////////////// @@ -344,10 +345,10 @@ attack_sound = 'sound/weapons/tap.ogg' construct_type = "harvester" construct_spells = list( - /obj/effect/proc_holder/spell/night_vision, - /obj/effect/proc_holder/spell/aoe/conjure/build/wall, - /obj/effect/proc_holder/spell/aoe/conjure/build/floor, - /obj/effect/proc_holder/spell/smoke/disable, + /datum/action/cooldown/spell/nightvision, + /datum/action/cooldown/spell/conjure/wall, + /datum/action/cooldown/spell/conjure/floor, + /datum/action/cooldown/spell/smoke/disable, ) retreat_distance = 2 //AI harvesters will move in and out of combat, like wraiths, but shittier playstyle_string = "You are a Harvester. You are not strong, but your powers of domination will assist you in your role: \ diff --git a/code/modules/mob/living/simple_animal/hostile/hellhound.dm b/code/modules/mob/living/simple_animal/hostile/hellhound.dm index 8d94cec1241c..ed6b3c37bcc3 100644 --- a/code/modules/mob/living/simple_animal/hostile/hellhound.dm +++ b/code/modules/mob/living/simple_animal/hostile/hellhound.dm @@ -130,7 +130,8 @@ /mob/living/simple_animal/hostile/hellhound/greater/Initialize(mapload) . = ..() // Movement - AddSpell(new /obj/effect/proc_holder/spell/ethereal_jaunt/shift) + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/j_spell = new + j_spell.Grant(src) var/obj/effect/proc_holder/spell/area_teleport/teleport/telespell = new telespell.clothes_req = FALSE telespell.human_req = FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm index 8f2eb84fc5f7..53b04c73ba50 100644 --- a/code/modules/mob/living/simple_animal/hostile/statue.dm +++ b/code/modules/mob/living/simple_animal/hostile/statue.dm @@ -56,7 +56,8 @@ // Give spells AddSpell(new /obj/effect/proc_holder/spell/aoe/flicker_lights(null)) AddSpell(new /obj/effect/proc_holder/spell/aoe/blindness(null)) - AddSpell(new /obj/effect/proc_holder/spell/night_vision(null)) + var/datum/action/cooldown/spell/nightvision/nightvision = new() + nightvision.Grant(src) // Set creator if(creator) diff --git a/code/modules/spells/spell_types/conjure/build.dm b/code/modules/spells/spell_types/conjure/build.dm index 962c8611bb2a..99a747a4a6e9 100644 --- a/code/modules/spells/spell_types/conjure/build.dm +++ b/code/modules/spells/spell_types/conjure/build.dm @@ -4,7 +4,6 @@ button_icon_state = "floorconstruct" background_icon_state = "bg_cult" background_icon_state_active = "bg_cult" - school = SCHOOL_CONJURATION cooldown_time = 2 SECONDS spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC summon_type = list(/turf/simulated/floor/engine/cult) diff --git a/code/modules/spells/spell_types/conjure_item/soulstone.dm b/code/modules/spells/spell_types/conjure_item/soulstone.dm index abc36a6d58e2..4e734bbe4a19 100644 --- a/code/modules/spells/spell_types/conjure_item/soulstone.dm +++ b/code/modules/spells/spell_types/conjure_item/soulstone.dm @@ -3,8 +3,12 @@ desc = "This spell reaches into Redspace, summoning one of the legendary fragments across time and space" button_icon_state = "summonsoulstone" background_icon_state = "bg_cult" - school = SCHOOL_CONJURATION cooldown_time = 5 MINUTES spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC delete_old = FALSE item_type = /obj/item/soulstone + +/datum/action/cooldown/spell/conjure_item/soulstone/holy + button_icon_state = "summonsoulstone_holy" + background_icon_state = "bg_spell" + item_type = /obj/item/soulstone/anybody/purified diff --git a/code/modules/spells/spell_types/self/nightvision.dm b/code/modules/spells/spell_types/self/nightvision.dm new file mode 100644 index 000000000000..3d1552253634 --- /dev/null +++ b/code/modules/spells/spell_types/self/nightvision.dm @@ -0,0 +1,27 @@ +/datum/action/cooldown/spell/nightvision + name = "Toggle Nightvision" + desc = "Toggle your nightvision mode." + cooldown_time = 1 SECONDS + spell_requirements = NONE + +/datum/action/cooldown/spell/nightvision/cast(atom/cast_on) + . = ..() + if(!isliving(cast_on)) + return + var/mob/living/caster = cast_on + switch(caster.lighting_alpha) + if(LIGHTING_PLANE_ALPHA_VISIBLE) + caster.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + name = "Toggle Nightvision \[More]" + if(LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + caster.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + name = "Toggle Nightvision \[Full]" + if(LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + caster.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + name = "Toggle Nightvision \[OFF]" + else + caster.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + name = "Toggle Nightvision \[ON]" + caster.update_sight() + to_chat(caster, span_notice("You toggle your night vision!")) + diff --git a/code/modules/spells/spell_types/self/smoke.dm b/code/modules/spells/spell_types/self/smoke.dm index d7ff24af535f..4608f36f95d2 100644 --- a/code/modules/spells/spell_types/self/smoke.dm +++ b/code/modules/spells/spell_types/self/smoke.dm @@ -8,3 +8,11 @@ smoke_type = /datum/effect_system/fluid_spread/smoke/bad smoke_amt = 10 button_icon_state = "smoke" + +/datum/action/cooldown/spell/smoke/disable + name = "Paralysing Smoke" + desc = "This spell spawns a cloud of paralysing smoke." + button_icon_state = "parasmoke" + background_icon_state = "bg_cult" + cooldown_time = 20 SECONDS + smoke_type = /datum/effect_system/fluid_spread/smoke/sleeping diff --git a/paradise.dme b/paradise.dme index 924dcc197abc..d1ec39ad4744 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1058,7 +1058,6 @@ #include "code\datums\spells\cluwne.dm" #include "code\datums\spells\conjure.dm" #include "code\datums\spells\conjure_item.dm" -#include "code\datums\spells\construct_spells.dm" #include "code\datums\spells\devil.dm" #include "code\datums\spells\devil_boons.dm" #include "code\datums\spells\emplosion.dm" @@ -1078,7 +1077,6 @@ #include "code\datums\spells\mime_malaise.dm" #include "code\datums\spells\mimic.dm" #include "code\datums\spells\mind_transfer.dm" -#include "code\datums\spells\night_vision.dm" #include "code\datums\spells\projectile.dm" #include "code\datums\spells\rathens.dm" #include "code\datums\spells\rod_form.dm" @@ -3980,6 +3978,7 @@ #include "code\modules\spells\spell_types\self\emplosion.dm" #include "code\modules\spells\spell_types\self\forcewall.dm" #include "code\modules\spells\spell_types\self\mutate.dm" +#include "code\modules\spells\spell_types\self\nightvision.dm" #include "code\modules\spells\spell_types\self\no_clothes.dm" #include "code\modules\spells\spell_types\self\nullbox.dm" #include "code\modules\spells\spell_types\self\smoke.dm" From cade0aadcfb305cd23ba485a623604dbdee7b188 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Wed, 3 Jun 2026 21:05:44 +0300 Subject: [PATCH 30/42] =?UTF-8?q?=D0=B7=D0=B0=D0=B1=D1=8B=D0=BB=20=D1=83?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20=D1=84=D0=B0=D1=81=D1=82=D0=BB?= =?UTF-8?q?=D0=BE=D0=B0=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _maps/common.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_maps/common.dm b/_maps/common.dm index c34e5b358843..60f9496f97b1 100644 --- a/_maps/common.dm +++ b/_maps/common.dm @@ -9,7 +9,7 @@ z3 = engineering stuff (called Z4, dont question it) z4 = lavaland */ -#define FAST_LOAD +//#define FAST_LOAD #define MAP_TRANSITION_CONFIG list() From e59249f39993c34b75ff0a6e6ffe8cca81b52a88 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Fri, 5 Jun 2026 23:20:14 +0300 Subject: [PATCH 31/42] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D1=8B=20=D0=B4=D1=8C=D0=B2=D0=BE=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit заменил все заклинания с контрактов дьявола --- code/datums/spells/devil_boons.dm | 197 ------------------ code/game/dna/genes/powers.dm | 9 +- .../antagonists/devil/contracts/friend.dm | 6 +- .../antagonists/devil/devil_contracts.dm | 26 ++- .../spells/spell_types/conjure/build.dm | 2 - .../spell_types/conjure_item/contract_gun.dm | 21 ++ .../spell_types/conjure_item/summon_wealth.dm | 22 ++ .../spells/spell_types/self/summon_friend.dm | 27 +++ .../spells/spell_types/self/view_range.dm | 59 ++++++ .../spells/spell_types/touch/revive_touch.dm | 55 +++++ paradise.dme | 6 +- 11 files changed, 215 insertions(+), 215 deletions(-) delete mode 100644 code/datums/spells/devil_boons.dm create mode 100644 code/modules/spells/spell_types/conjure_item/contract_gun.dm create mode 100644 code/modules/spells/spell_types/conjure_item/summon_wealth.dm create mode 100644 code/modules/spells/spell_types/self/summon_friend.dm create mode 100644 code/modules/spells/spell_types/self/view_range.dm create mode 100644 code/modules/spells/spell_types/touch/revive_touch.dm diff --git a/code/datums/spells/devil_boons.dm b/code/datums/spells/devil_boons.dm deleted file mode 100644 index b3d2f8a749d9..000000000000 --- a/code/datums/spells/devil_boons.dm +++ /dev/null @@ -1,197 +0,0 @@ -#define REVIVE_SPELL_TIME 5 MINUTES -#define REVIVE_SPELL_NECROSIS_PROB 30 - -/obj/effect/proc_holder/spell/summon_wealth - name = "Призвать богатство" - desc = "Ваша награда за продажу души." - - invocation_type = "whisper" - invocation = "Divitiae, da mihi divitias" - - school = "conjuration" - clothes_req = FALSE - cooldown_min = 1 SECONDS - action_icon_state = "moneybag" - -/obj/effect/proc_holder/spell/summon_wealth/create_new_targeting() - var/datum/spell_targeting/targeted/T = new() - T.range = 7 - return T - -/obj/effect/proc_holder/spell/summon_wealth/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/C in targets) - if(user.drop_from_active_hand()) - var/obj/item = pick( - new /obj/item/coin/gold(user.loc), - new /obj/item/coin/diamond(user.loc), - new /obj/item/coin/silver(user.loc), - new /obj/item/stack/sheet/mineral/gold(user.loc), - new /obj/item/stack/sheet/mineral/silver(user.loc), - new /obj/item/stack/sheet/mineral/diamond(user.loc), - new /obj/item/stack/spacecash/c1000(user.loc)) - C.put_in_active_hand(item) - -/obj/effect/proc_holder/spell/view_range - name = "Дальний взор" - desc = "Ваша награда за продажу души." - - invocation_type = "whisper" - invocation = "Da mihi divinum aspectum" - - clothes_req = FALSE - base_cooldown = 5 SECONDS - cooldown_min = 1 SECONDS - action_icon_state = "camera_jump" - /// Currently selected view range - var/selected_view = "default" - /// View ranges to apply - var/static/list/view_ranges = list( - "default", - "17x17", - "19x19", - "21x21", - ) - -/obj/effect/proc_holder/spell/view_range/Destroy(force) - if(action) - UnregisterSignal(action.owner, COMSIG_LIVING_DEATH) - if(selected_view == "default" || QDELETED(action.owner) || !action.owner.client) - return ..() - INVOKE_ASYNC(action.owner.client, TYPE_PROC_REF(/client, change_view), action.owner.client.prefs.viewrange) - return ..() - -/obj/effect/proc_holder/spell/view_range/proc/make_view_normal(mob/user) - SIGNAL_HANDLER - if(!QDELETED(user) && user.client) - INVOKE_ASYNC(user.client, TYPE_PROC_REF(/client, change_view), user.client.prefs.viewrange) - -/obj/effect/proc_holder/spell/view_range/create_new_targeting() - return new /datum/spell_targeting/self - -/obj/effect/proc_holder/spell/view_range/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE) - if(!user.client) - return FALSE - return ..() - -/obj/effect/proc_holder/spell/view_range/on_spell_gain(mob/user = usr) - RegisterSignal(user, COMSIG_LIVING_DEATH, TYPE_PROC_REF(/obj/effect/proc_holder/spell/view_range, make_view_normal)) - -/obj/effect/proc_holder/spell/view_range/cast(list/targets, mob/user = usr) - var/new_view = tgui_input_list(user, "Выберите область видимости:", "Видимость", view_ranges, "default") - if(isnull(new_view) || !user.client) - return - if(new_view == "default") - new_view = user.client.prefs.viewrange - selected_view = new_view - user.client.change_view(new_view) - -/obj/effect/proc_holder/spell/view_range/genetic - desc = "Позволяет вам выбрать, как далеко вы будете видеть." - invocation_type = "none" - invocation = null - -/obj/effect/proc_holder/spell/summon_friend - name = "Призвать друга" - desc = "Ваша награда за продажу души." - - invocation_type = "whisper" - invocation = "Amicus meus fidelis infernalis, suus ' vicis" - - action_icon_state = "sacredflame" - clothes_req = FALSE - base_cooldown = 5 SECONDS - cooldown_min = 1 SECONDS - var/mob/living/friend - var/obj/effect/mob_spawn/human/demonic_friend/friendShell - -/obj/effect/proc_holder/spell/summon_friend/create_new_targeting() - return new /datum/spell_targeting/self - -/obj/effect/proc_holder/spell/summon_friend/cast(list/targets, mob/user = usr) - if(!QDELETED(friend)) - to_chat(friend, span_userdanger("Твой хозяин посчитал тебя плохим другом. Тебе пора обратно в ад.")) - to_chat(user, span_notice("Вы изгоняете вашего друга туда, откуда [GEND_HE_SHE(friend)] при[GEND_SHEL(friend)].")) - friend.dust() - QDEL_NULL(friendShell) - return - if(!QDELETED(friendShell)) - QDEL_NULL(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) - -/obj/effect/proc_holder/spell/touch/revive_touch - name = "Воскрешающее косание" - desc = "Чрезвычайно могущественное некромантическое заклинание" - hand_path = /obj/item/melee/touch_attack/revive_touch - school = "transmutation" - - base_cooldown = 1 MINUTES - clothes_req = FALSE - cooldown_min = 10 SECONDS //50 deciseconds reduction per rank - action_icon_state = "revive" - -/obj/item/melee/touch_attack/revive_touch - name = "воскрешающее касание" - desc = "Воскрешает тело умершего на определенное время." - catchphrase = "Surge e lecto" - on_use_sound = 'sound/magic/staff_healing.ogg' - icon_state = "disintegrate" - color = "#acb78e" - -/obj/item/melee/touch_attack/revive_touch/get_ru_names() - return alist( - NOMINATIVE = "воскрешающее касание", - GENITIVE = "воскрешающего касания", - DATIVE = "воскрешающему касанию", - ACCUSATIVE = "воскрешающее касание", - INSTRUMENTAL = "воскрешающим касанием", - PREPOSITIONAL = "воскрешающем касании", - ) - -/obj/item/melee/touch_attack/revive_touch/afterattack(atom/target, mob/user, proximity_flag, list/modifiers, status) - . = ..() - - if(!isliving(target)) - return . - - var/mob/living/mob = target - - if(mob.stat != DEAD || !(mob.mind.is_revivable())) - return . - - mob.revive() - - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(late_death), mob), REVIVE_SPELL_TIME) - -/proc/late_death(mob/living/mob) - mob.death() - - if(!ishuman(mob)) - return - - necrotize_body(mob, REVIVE_SPELL_NECROSIS_PROB) - -/obj/effect/proc_holder/spell/conjure_item/contract_gun - name = "Призвать верное оружие" - desc = "Призвать оружие, полученное в обмен на душу." - - invocation_type = "whisper" - invocation = "Amicus meus, suus ' vicis" - - action_icon_state = "bolt_action_old" - action_background_icon_state = "bg_demon" - -/obj/effect/proc_holder/spell/conjure_item/contract_gun/Initialize(mapload, weapon_type) - . = ..() - item_type = weapon_type - -/obj/effect/proc_holder/spell/conjure_item/contract_gun/update_item(obj/item/item) - item.origin_tech = list() - item.materials = list() - ADD_TRAIT(item, TRAIT_NODROP, INNATE_TRAIT) - ADD_TRAIT(item, TRAIT_NOT_TURRET_GUN, INNATE_TRAIT) - -#undef REVIVE_SPELL_TIME -#undef REVIVE_SPELL_NECROSIS_PROB diff --git a/code/game/dna/genes/powers.dm b/code/game/dna/genes/powers.dm index 6534f052ec98..4ad5695f35fd 100644 --- a/code/game/dna/genes/powers.dm +++ b/code/game/dna/genes/powers.dm @@ -203,10 +203,15 @@ /datum/dna/gene/basic/farvision/activate(mob/living/mutant, flags) . = ..() - mutant.AddSpell(new /obj/effect/proc_holder/spell/view_range/genetic) + var/datum/action/cooldown/spell/view_range/genetic/spell = new + spell.Grant(mutant) /datum/dna/gene/basic/farvision/deactivate(mob/living/mutant, flags) . = ..() - mutant.RemoveSpell(/obj/effect/proc_holder/spell/view_range/genetic) + var/datum/action/cooldown/spell/view_range/genetic/spell = locate() in mutant.actions + if(!spell) + return + spell.Remove(mutant) + qdel(spell) #undef HULK_HUMANBODY_MULTIPLIER diff --git a/code/modules/antagonists/devil/contracts/friend.dm b/code/modules/antagonists/devil/contracts/friend.dm index df93f9f0d890..f0c811b500b9 100644 --- a/code/modules/antagonists/devil/contracts/friend.dm +++ b/code/modules/antagonists/devil/contracts/friend.dm @@ -10,11 +10,11 @@ random = TRUE id_job = "SuperFriend" id_access = "assistant" - var/obj/effect/proc_holder/spell/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/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 description = "Be someone's loyal friend/slave. If they die, you die as well." //best I could think of in the moment, not sure how this role plays in practise, have never seen it. @@ -31,7 +31,7 @@ L.name = L.real_name soullink(/datum/soullink/oneway/devilfriend, owner.current, L) spell.friend = L - spell.revert_cast() + spell.ResetCooldown() L.mind.hasSoul = FALSE var/mob/living/carbon/human/H = L var/obj/item/worn = H.wear_id diff --git a/code/modules/antagonists/devil/devil_contracts.dm b/code/modules/antagonists/devil/devil_contracts.dm index 2300ea7cbef9..8e65c7374466 100644 --- a/code/modules/antagonists/devil/devil_contracts.dm +++ b/code/modules/antagonists/devil/devil_contracts.dm @@ -66,7 +66,8 @@ GLOBAL_LIST_INIT(devil_guns, (GLOB.summoned_guns - NOT_DEVIL_GUNS + DEVIL_GUNS)) return TRUE /datum/devil_contract/wealth/fulfill_contract(mob/living/carbon/human/user) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/summon_wealth(null)) + var/datum/action/cooldown/spell/summon_friend/spell = new() + spell.Grant(user) /datum/devil_contract/prestige name = "контракт престижа" @@ -201,7 +202,9 @@ GLOBAL_LIST_INIT(devil_guns, (GLOB.summoned_guns - NOT_DEVIL_GUNS + DEVIL_GUNS)) ADD_TRAIT(user, TRAIT_XRAY, UNIQUE_TRAIT_SOURCE(src)) user.update_sight() user.update_misc_effects() - user.mind.AddSpell(new /obj/effect/proc_holder/spell/view_range(null)) + var/datum/action/cooldown/spell/view_range/spell = new() + spell.Grant(user) + /datum/devil_contract/friendship name = "контракт дружбы" @@ -215,7 +218,9 @@ GLOBAL_LIST_INIT(devil_guns, (GLOB.summoned_guns - NOT_DEVIL_GUNS + DEVIL_GUNS)) return TRUE /datum/devil_contract/friendship/fulfill_contract(mob/living/carbon/human/user) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/summon_friend(null)) + var/datum/action/cooldown/spell/summon_friend/spell = new() + spell.Grant(user) + /datum/devil_contract/unwilling name = "контракт рабства" @@ -259,10 +264,11 @@ GLOBAL_LIST_INIT(devil_guns, (GLOB.summoned_guns - NOT_DEVIL_GUNS + DEVIL_GUNS)) return TRUE /datum/devil_contract/return_dead/fulfill_contract(mob/living/carbon/human/user) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/touch/revive_touch(null)) - var/obj/effect/proc_holder/spell/lichdom/spell = new(null) - spell.create_lich(user) - qdel(spell) + var/datum/action/cooldown/spell/touch/revive_touch/spell = new() + spell.Grant(user) + var/obj/effect/proc_holder/spell/lichdom/lich_spell = new(null) + lich_spell.create_lich(user) + qdel(lich_spell) /datum/devil_contract/gun name = "контракт оружия" @@ -276,9 +282,9 @@ GLOBAL_LIST_INIT(devil_guns, (GLOB.summoned_guns - NOT_DEVIL_GUNS + DEVIL_GUNS)) return TRUE /datum/devil_contract/gun/fulfill_contract(mob/living/carbon/human/user) - var/gun_type = safepick(GLOB.devil_guns) - var/spell = new /obj/effect/proc_holder/spell/conjure_item/contract_gun(null, gun_type) - user.mind.AddSpell(spell) + var/datum/action/cooldown/spell/conjure_item/contract_gun/spell = new() + spell.Grant(user) + #undef MAGIC_SPELLS_COUNT #undef HULK_COOLDOWN diff --git a/code/modules/spells/spell_types/conjure/build.dm b/code/modules/spells/spell_types/conjure/build.dm index 99a747a4a6e9..272955172532 100644 --- a/code/modules/spells/spell_types/conjure/build.dm +++ b/code/modules/spells/spell_types/conjure/build.dm @@ -29,7 +29,6 @@ button_icon_state = "cultforcewall" background_icon_state = "bg_cult" background_icon_state_active = "bg_cult" - school = "conjuration" spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel summon_type = list(/turf/simulated/wall/cult/artificer) //we don't want artificer-based runed metal farms @@ -58,7 +57,6 @@ button_icon_state = "pylon" background_icon_state = "bg_cult" background_icon_state_active = "bg_cult" - school = "conjuration" cooldown_time = 20 SECONDS spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel diff --git a/code/modules/spells/spell_types/conjure_item/contract_gun.dm b/code/modules/spells/spell_types/conjure_item/contract_gun.dm new file mode 100644 index 000000000000..667b6d57ffc3 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/contract_gun.dm @@ -0,0 +1,21 @@ +/datum/action/cooldown/spell/conjure_item/contract_gun + name = "Призвать верное оружие" + desc = "Призвать оружие, полученное в обмен на душу." + + invocation_type = INVOCATION_WHISPER + invocation = "Amicus meus, suus ' vicis" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + button_icon_state = "bolt_action_old" + background_icon_state = "bg_demon" + +/datum/action/cooldown/spell/conjure_item/contract_gun/Grant(mob/grant_to) + . = ..() + item_type = safepick(GLOB.devil_guns) + +/datum/action/cooldown/spell/conjure_item/contract_gun/post_created(atom/cast_on, atom/created) + . = ..() + var/obj/item/gun/projectile/automatic/weapon = created + weapon.origin_tech = list() + weapon.materials = list() + ADD_TRAIT(weapon, TRAIT_NODROP, INNATE_TRAIT) + ADD_TRAIT(weapon, TRAIT_NOT_TURRET_GUN, INNATE_TRAIT) diff --git a/code/modules/spells/spell_types/conjure_item/summon_wealth.dm b/code/modules/spells/spell_types/conjure_item/summon_wealth.dm new file mode 100644 index 000000000000..8161f5cb018f --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/summon_wealth.dm @@ -0,0 +1,22 @@ +/datum/action/cooldown/spell/conjure_item/summon_wealth + name = "Призвать богатство" + desc = "Ваша награда за продажу души." + invocation_type = INVOCATION_WHISPER + invocation = "Divitiae, da mihi divitias" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + button_icon_state = "moneybag" + item_type = /obj/item/coin/gold + delete_old = FALSE + cooldown_time = 10 SECONDS + var/list/wealth = list( + /obj/item/coin/gold, + /obj/item/coin/diamond, + /obj/item/coin/silver, + /obj/item/stack/sheet/mineral/gold, + /obj/item/stack/sheet/mineral/silver, + /obj/item/stack/sheet/mineral/diamond, + /obj/item/stack/spacecash/c1000 + ) + +/datum/action/cooldown/spell/conjure_item/summon_wealth/post_created(atom/cast_on, atom/created) + item_type = pick(wealth) diff --git a/code/modules/spells/spell_types/self/summon_friend.dm b/code/modules/spells/spell_types/self/summon_friend.dm new file mode 100644 index 000000000000..321326e47deb --- /dev/null +++ b/code/modules/spells/spell_types/self/summon_friend.dm @@ -0,0 +1,27 @@ +/datum/action/cooldown/spell/summon_friend + name = "Призвать друга" + desc = "Ваша награда за продажу души." + invocation_type = INVOCATION_WHISPER + invocation = "Amicus meus fidelis infernalis, suus ' vicis" + + button_icon_state = "sacredflame" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC | SPELL_REQUIRES_MIND + cooldown_time = 5 SECONDS + var/mob/living/friend + var/obj/effect/mob_spawn/human/demonic_friend/friendShell + +/datum/action/cooldown/spell/summon_friend/cast(atom/cast_on) + . = ..() + if(!ismob(cast_on)) + return + var/mob/living/caster = cast_on + if(!QDELETED(friend)) + to_chat(friend, span_userdanger("Твой хозяин посчитал тебя плохим другом. Тебе пора обратно в ад.")) + to_chat(caster, span_notice("Вы изгоняете вашего друга туда, откуда [GEND_HE_SHE(friend)] при[GEND_SHEL(friend)].")) + friend.dust() + QDEL_NULL(friendShell) + return + if(!QDELETED(friendShell)) + QDEL_NULL(friendShell) + return + friendShell = new /obj/effect/mob_spawn/human/demonic_friend(caster.loc, caster.mind, src) diff --git a/code/modules/spells/spell_types/self/view_range.dm b/code/modules/spells/spell_types/self/view_range.dm new file mode 100644 index 000000000000..16da86f4e5b8 --- /dev/null +++ b/code/modules/spells/spell_types/self/view_range.dm @@ -0,0 +1,59 @@ +/datum/action/cooldown/spell/view_range + name = "Дальний взор" + desc = "Ваша награда за продажу души." + + invocation_type = INVOCATION_WHISPER + invocation = "Da mihi divinum aspectum" + school = SCHOOL_PSYCHIC + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + cooldown_time = 5 SECONDS + button_icon_state = "camera_jump" + /// Currently selected view range + var/selected_view = "default" + /// View ranges to apply + var/static/list/view_ranges = list( + "default", + "17x17", + "19x19", + "21x21", + ) + +/datum/action/cooldown/spell/view_range/Destroy(force) + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + if(selected_view == "default" || QDELETED(owner) || !owner.client) + return ..() + INVOKE_ASYNC(owner.client, TYPE_PROC_REF(/client, change_view), owner.client.prefs.viewrange) + return ..() + +/datum/action/cooldown/spell/view_range/proc/make_view_normal(mob/user) + SIGNAL_HANDLER + if(!QDELETED(user) && user.client) + INVOKE_ASYNC(user.client, TYPE_PROC_REF(/client, change_view), user.client.prefs.viewrange) + +/datum/action/cooldown/spell/view_range/can_cast_spell(feedback) + . = ..() + if(!owner.client) + return FALSE + return ..() + +/datum/action/cooldown/spell/view_range/Grant(mob/grant_to) + . = ..() + RegisterSignal(grant_to, COMSIG_LIVING_DEATH, PROC_REF(make_view_normal)) + +/datum/action/cooldown/spell/view_range/cast(atom/cast_on) + . = ..() + if(!ismob(cast_on)) + return + var/mob/user = cast_on + var/new_view = tgui_input_list(user, "Выберите область видимости:", "Видимость", view_ranges, "default") + if(isnull(new_view) || !user.client) + return + if(new_view == "default") + new_view = user.client.prefs.viewrange + selected_view = new_view + user.client.change_view(new_view) + +/datum/action/cooldown/spell/view_range/genetic + desc = "Позволяет вам выбрать, как далеко вы будете видеть." + invocation_type = INVOCATION_NONE + invocation = null diff --git a/code/modules/spells/spell_types/touch/revive_touch.dm b/code/modules/spells/spell_types/touch/revive_touch.dm new file mode 100644 index 000000000000..57ea447282bf --- /dev/null +++ b/code/modules/spells/spell_types/touch/revive_touch.dm @@ -0,0 +1,55 @@ +#define REVIVE_SPELL_TIME 5 MINUTES +#define REVIVE_SPELL_NECROSIS_PROB 30 + +/datum/action/cooldown/spell/touch/revive_touch + name = "Воскрешающее косание" + desc = "Чрезвычайно могущественное некромантическое заклинание" + hand_path = /obj/item/melee/magic_hand/revive_touch + school = SCHOOL_TRANSMUTATION + invocation = "Surge e lecto!" + cooldown_time = 1 MINUTES + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + button_icon_state = "revive" + +/obj/item/melee/magic_hand/revive_touch + name = "воскрешающее касание" + desc = "Воскрешает тело умершего на определенное время." + icon_state = "disintegrate" + color = "#acb78e" + +/obj/item/melee/magic_hand/revive_touch/get_ru_names() + return alist( + NOMINATIVE = "воскрешающее касание", + GENITIVE = "воскрешающего касания", + DATIVE = "воскрешающему касанию", + ACCUSATIVE = "воскрешающее касание", + INSTRUMENTAL = "воскрешающим касанием", + PREPOSITIONAL = "воскрешающем касании", + ) + +/datum/action/cooldown/spell/touch/revive_touch/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + . = ..() + + if(!isliving(victim)) + return . + + var/mob/living/mob = victim + + if(mob.stat != DEAD || !(mob.mind?.is_revivable())) + return . + + mob.revive() + playsound(victim, 'sound/magic/staff_healing.ogg', 50, TRUE) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(late_death), mob), REVIVE_SPELL_TIME) + remove_hand(reset_cooldown_after = TRUE) + +/proc/late_death(mob/living/mob) + mob.death() + + if(!ishuman(mob)) + return + + necrotize_body(mob, REVIVE_SPELL_NECROSIS_PROB) + +#undef REVIVE_SPELL_TIME +#undef REVIVE_SPELL_NECROSIS_PROB diff --git a/paradise.dme b/paradise.dme index d1ec39ad4744..3cf2f478aa68 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1059,7 +1059,6 @@ #include "code\datums\spells\conjure.dm" #include "code\datums\spells\conjure_item.dm" #include "code\datums\spells\devil.dm" -#include "code\datums\spells\devil_boons.dm" #include "code\datums\spells\emplosion.dm" #include "code\datums\spells\ethereal_jaunt.dm" #include "code\datums\spells\explosion.dm" @@ -3965,7 +3964,9 @@ #include "code\modules\spells\spell_types\conjure\creature.dm" #include "code\modules\spells\spell_types\conjure\timestop.dm" #include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" +#include "code\modules\spells\spell_types\conjure_item\contract_gun.dm" #include "code\modules\spells\spell_types\conjure_item\soulstone.dm" +#include "code\modules\spells\spell_types\conjure_item\summon_wealth.dm" #include "code\modules\spells\spell_types\jaunt\_jaunt.dm" #include "code\modules\spells\spell_types\jaunt\bloodcrawl.dm" #include "code\modules\spells\spell_types\jaunt\ethereal_jaunt.dm" @@ -3982,6 +3983,8 @@ #include "code\modules\spells\spell_types\self\no_clothes.dm" #include "code\modules\spells\spell_types\self\nullbox.dm" #include "code\modules\spells\spell_types\self\smoke.dm" +#include "code\modules\spells\spell_types\self\summon_friend.dm" +#include "code\modules\spells\spell_types\self\view_range.dm" #include "code\modules\spells\spell_types\shapeshift\_shape_status.dm" #include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" #include "code\modules\spells\spell_types\teleport\_teleport.dm" @@ -3990,6 +3993,7 @@ #include "code\modules\spells\spell_types\touch\_touch.dm" #include "code\modules\spells\spell_types\touch\banana_touch.dm" #include "code\modules\spells\spell_types\touch\cluwne.dm" +#include "code\modules\spells\spell_types\touch\revive_touch.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\brs.dm" From 4fb1c84cf2dd875bbc93afabc99c5379c17218c7 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Fri, 5 Jun 2026 23:25:05 +0300 Subject: [PATCH 32/42] =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/mob/living/simple_animal/constructs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 1d50ca7a1b44..4f895fdd45e8 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -294,7 +294,7 @@ /datum/action/cooldown/spell/conjure/wall/holy, /datum/action/cooldown/spell/conjure/floor/holy, /datum/action/cooldown/spell/conjure/pylon/holy, - /datum/action/cooldown/spell/conjure_item/soulstone/holy + /datum/action/cooldown/spell/conjure_item/soulstone/holy, ) /////////////////////////////Behemoth///////////////////////// From f95f73a9b6f44c46b9892e8c94ee6a845fcac27a Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sat, 6 Jun 2026 00:34:07 +0300 Subject: [PATCH 33/42] =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D0=BB=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=B8=D1=87=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antagonists/devil/devil_contracts.dm | 3 +- code/modules/spells/spell.dm | 2 +- .../spells/spell_types/self/lichdom.dm | 154 ++++++++++++++++++ .../spells/spell_types/touch/revive_touch.dm | 2 +- paradise.dme | 1 + 5 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 code/modules/spells/spell_types/self/lichdom.dm diff --git a/code/modules/antagonists/devil/devil_contracts.dm b/code/modules/antagonists/devil/devil_contracts.dm index 8e65c7374466..bc1f7625bb6a 100644 --- a/code/modules/antagonists/devil/devil_contracts.dm +++ b/code/modules/antagonists/devil/devil_contracts.dm @@ -266,7 +266,8 @@ GLOBAL_LIST_INIT(devil_guns, (GLOB.summoned_guns - NOT_DEVIL_GUNS + DEVIL_GUNS)) /datum/devil_contract/return_dead/fulfill_contract(mob/living/carbon/human/user) var/datum/action/cooldown/spell/touch/revive_touch/spell = new() spell.Grant(user) - var/obj/effect/proc_holder/spell/lichdom/lich_spell = new(null) + var/datum/action/cooldown/spell/lichdom/lich_spell = new + lich_spell.Grant(user) lich_spell.create_lich(user) qdel(lich_spell) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 667bc54ab3e2..18bcd9f63b5b 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -48,7 +48,7 @@ overlay_icon_state = "bg_spell_border" active_overlay_icon_state = "targeting" background_icon_state_active = "bg_spell" - check_flags = AB_CHECK_CONSCIOUS //|AB_CHECK_PHASED + check_flags = AB_CHECK_CONSCIOUS | AB_TRANSFER_MIND//|AB_CHECK_PHASED panel = "Spells" /// The sound played on cast. 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..02206214b155 --- /dev/null +++ b/code/modules/spells/spell_types/self/lichdom.dm @@ -0,0 +1,154 @@ +/datum/action/cooldown/spell/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." + school = SCHOOL_NECROMANCY + cooldown_time = 1 SECONDS + check_flags = AB_TRANSFER_MIND + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC | SPELL_REQUIRES_STATION | SPELL_REQUIRES_HUMAN + invocation = "NECREM IMORTIUM!" + invocation_type = INVOCATION_SHOUT + spell_max_level = 0 //cannot be improved + var/phylactery_made = FALSE + var/obj/item/marked_item + var/mob/living/current_body + var/resurrections = 0 + var/existence_stops_round_end = FALSE + var/focusing = FALSE + + button_icon_state = "skeleton" + +/datum/action/cooldown/spell/lichdom/Destroy() + marked_item = null + current_body = null + for(var/datum/mind/w_mind in SSticker.mode.wizards) //Make sure no other bones are about + for(var/obj/effect/proc_holder/spell/lichdom/spell in w_mind.spell_list) + if(spell != src && spell.existence_stops_round_end) + return ..() + + if(existence_stops_round_end) + CONFIG_SET(flag/continuous_rounds, FALSE) + + return ..() + +/datum/action/cooldown/spell/lichdom/can_cast_spell(feedback) + if(focusing) + return FALSE + + if(phylactery_made && owner.stat != DEAD) + return FALSE + return ..() + +/datum/action/cooldown/spell/lichdom/cast(atom/cast_on) + . = ..() + if(!ishuman(cast_on)) + return + var/mob/living/carbon/human/user = cast_on + + if(phylactery_made) //Death is not my end! + if(!user.stat) + to_chat(user, span_notice("You aren't dead enough to revive!")) //Usually a good problem to have + ResetCooldown() + return + + if(QDELETED(marked_item)) //Wait nevermind + to_chat(user, span_warning("Your phylactery is gone!")) + return + + var/turf/user_turf = get_turf(user) + var/turf/item_turf = get_turf(marked_item) + + if(user_turf.z != item_turf.z) + to_chat(user, span_warning("Your phylactery is out of range!")) + return + + var/mob/living/carbon/human/lich = new(item_turf) + + lich.real_name = user.mind.name + lich.set_species(/datum/species/skeleton) // Wizard variant + user.mind.transfer_to(lich) + to_chat(lich, span_warning("Your bones clatter and shutter as they're pulled back into this world!")) + cooldown_time += 1 MINUTES + ResetCooldown() + + var/mob/old_body = current_body + var/turf/body_turf = get_turf(old_body) + current_body = lich + var/stun_time = (1 + resurrections) STATUS_EFFECT_CONSTANT + lich.Weaken(stun_time) + resurrections++ + equip_lich(lich) + + if(old_body?.loc) + if(iscarbon(old_body)) + for(var/obj/item/item in old_body.contents) + old_body.drop_item_ground(item) + + 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",icon='icons/effects/effects.dmi',time=stun_time,maxdistance=INFINITY) + + old_body.dust() + + return + + //linking item to the spell + var/obj/item/item = user.get_active_hand() + if(!item) + to_chat(user, span_warning("You must hold an item you wish to make your phylactery...")) + return + + if((item.item_flags & ABSTRACT) || HAS_TRAIT(item, TRAIT_NODROP)) + to_chat(user, span_warning("[item.name] can not be used for the ritual...")) + return + + to_chat(user, span_warning("You begin to focus your very being into [item]...")) + + focusing = TRUE + if(!do_after(user, 5 SECONDS, user)) + focusing = FALSE + return + focusing = FALSE + + if(QDELETED(item) || item.loc != user) //I changed my mind I don't want to put my soul in a cheeseburger! + to_chat(user, span_warning("Your soul snaps back to your body since [item] is out of reach!")) + return + + if(!CONFIG_GET(flag/continuous_rounds)) + existence_stops_round_end = TRUE + CONFIG_SET(flag/continuous_rounds, TRUE) + + name = "RISE!" + desc = "Rise from the dead! You will reform at the location of your phylactery and your old body will crumble away." + build_all_button_icons() + + cooldown_time = 3 MINUTES + ResetCooldown() + phylactery_made = TRUE + + current_body = user.mind.current + marked_item = item + marked_item.name = "Ensouled [marked_item.name]" + marked_item.desc = "A terrible aura surrounds this item, its very existence is offensive to life itself..." + marked_item.color = "#003300" + to_chat(user, 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(!ishuman(user)) + return + + create_lich(user) + +/datum/action/cooldown/spell/lichdom/proc/create_lich(mob/living/carbon/human/user) + user.set_species(/datum/species/skeleton) + user.drop_item_ground(user.wear_suit) + user.drop_item_ground(user.head) + user.drop_item_ground(user.shoes) + user.drop_item_ground(user.head) + equip_lich(user) + +/datum/action/cooldown/spell/lichdom/proc/equip_lich(mob/living/carbon/human/user) + user.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(user), ITEM_SLOT_CLOTH_OUTER) + user.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(user), ITEM_SLOT_HEAD) + user.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(user), ITEM_SLOT_FEET) + user.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(user), ITEM_SLOT_CLOTH_INNER) + diff --git a/code/modules/spells/spell_types/touch/revive_touch.dm b/code/modules/spells/spell_types/touch/revive_touch.dm index 57ea447282bf..ab098d5bb088 100644 --- a/code/modules/spells/spell_types/touch/revive_touch.dm +++ b/code/modules/spells/spell_types/touch/revive_touch.dm @@ -5,7 +5,7 @@ name = "Воскрешающее косание" desc = "Чрезвычайно могущественное некромантическое заклинание" hand_path = /obj/item/melee/magic_hand/revive_touch - school = SCHOOL_TRANSMUTATION + school = SCHOOL_NECROMANCY invocation = "Surge e lecto!" cooldown_time = 1 MINUTES spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC diff --git a/paradise.dme b/paradise.dme index 3cf2f478aa68..85b4de86e1cd 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3978,6 +3978,7 @@ #include "code\modules\spells\spell_types\self\charge.dm" #include "code\modules\spells\spell_types\self\emplosion.dm" #include "code\modules\spells\spell_types\self\forcewall.dm" +#include "code\modules\spells\spell_types\self\lichdom.dm" #include "code\modules\spells\spell_types\self\mutate.dm" #include "code\modules\spells\spell_types\self\nightvision.dm" #include "code\modules\spells\spell_types\self\no_clothes.dm" From 9ce4312f210a5dc495f8dabec8086d767339ca09 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sat, 6 Jun 2026 01:19:09 +0300 Subject: [PATCH 34/42] =?UTF-8?q?=D0=BC=D0=BE=D0=BB=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BC=D0=B0=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit перенес ТГ вариант чтобы не заебываться --- .../spells/spell_types/charged/lightning.dm | 73 +++++++++++++++++++ paradise.dme | 1 + 2 files changed, 74 insertions(+) create mode 100644 code/modules/spells/spell_types/charged/lightning.dm diff --git a/code/modules/spells/spell_types/charged/lightning.dm b/code/modules/spells/spell_types/charged/lightning.dm new file mode 100644 index 000000000000..542b888c3784 --- /dev/null +++ b/code/modules/spells/spell_types/charged/lightning.dm @@ -0,0 +1,73 @@ +/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/lightningshock.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_alt("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)]", icon = 'icons/effects/effects.dmi', 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", flags = SHOCK_NOGLOVES) + + // 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, max_distance = target_radius, simulated_only = FALSE))) + continue + + possibles += to_check + + if(!length(possibles)) + return null + + return pick(possibles) + +/datum/action/cooldown/spell/charged/beam/tesla/lightnian + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation_type = INVOCATION_NONE + +/datum/action/cooldown/spell/charged/beam/tesla/guardian + name = "Малая молния" + desc = "Копит в себе заряд электричества, поражающий всех врагов. Не так силен как оригинал, но все ещё опасен." + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "За С+инд+ик+ат!" + energy_lost_per_bounce = 10 diff --git a/paradise.dme b/paradise.dme index 85b4de86e1cd..220e79a3e492 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3956,6 +3956,7 @@ #include "code\modules\spells\spell_types\aoe_spell\repulse.dm" #include "code\modules\spells\spell_types\aoe_spell\sacred_flame.dm" #include "code\modules\spells\spell_types\charged\_charged.dm" +#include "code\modules\spells\spell_types\charged\lightning.dm" #include "code\modules\spells\spell_types\cone\_cone.dm" #include "code\modules\spells\spell_types\conjure\_conjure.dm" #include "code\modules\spells\spell_types\conjure\build.dm" From a83f778ff7bcf409434cc3f2f1c276610f5245c5 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Sun, 7 Jun 2026 23:58:30 +0300 Subject: [PATCH 35/42] =?UTF-8?q?touch=20=D1=81=D0=BF=D0=B5=D0=BB=D0=BB?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit пачка touch спеллов, небольшие исправления а также род форм --- _maps/common.dm | 2 +- code/datums/spells/rod_form.dm | 72 -------------- code/modules/spells/spell.dm | 2 +- .../spells/spell_types/pointed/horsemask.dm | 38 ++++++++ .../spells/spell_types/self/rod_form.dm | 96 +++++++++++++++++++ .../spells/spell_types/touch/_touch.dm | 1 + .../spells/spell_types/touch/banana_touch.dm | 9 +- .../spells/spell_types/touch/cluwne.dm | 31 +++--- .../spells/spell_types/touch/disintergate.dm | 21 ++++ .../spell_types/touch/flesh_to_stone.dm | 25 +++++ .../spells/spell_types/touch/revive_touch.dm | 10 +- paradise.dme | 4 + 12 files changed, 211 insertions(+), 100 deletions(-) create mode 100644 code/modules/spells/spell_types/pointed/horsemask.dm create mode 100644 code/modules/spells/spell_types/self/rod_form.dm create mode 100644 code/modules/spells/spell_types/touch/disintergate.dm create mode 100644 code/modules/spells/spell_types/touch/flesh_to_stone.dm diff --git a/_maps/common.dm b/_maps/common.dm index 60f9496f97b1..c34e5b358843 100644 --- a/_maps/common.dm +++ b/_maps/common.dm @@ -9,7 +9,7 @@ z3 = engineering stuff (called Z4, dont question it) z4 = lavaland */ -//#define FAST_LOAD +#define FAST_LOAD #define MAP_TRANSITION_CONFIG list() diff --git a/code/datums/spells/rod_form.dm b/code/datums/spells/rod_form.dm index 9accf1ff145e..6535a787a08c 100644 --- a/code/datums/spells/rod_form.dm +++ b/code/datums/spells/rod_form.dm @@ -1,6 +1,4 @@ -/// The base distance a wizard rod will go without upgrades. #define BASE_WIZ_ROD_RANGE 15 - /obj/effect/proc_holder/spell/rod_form name = "Rod Form" desc = "Take on the form of an immovable rod, destroying all in your path." @@ -30,74 +28,4 @@ var/flight_dist = rod_max_distance + spell_level * 3 var/turf/distant_turf = get_ranged_target_turf(start, user.dir, flight_dist) new /obj/effect/immovablerod/wizard(start, distant_turf, null, rod_delay, FALSE, user, flight_dist) - -/** - * Wizard Version of the Immovable Rod - */ -/obj/effect/immovablerod/wizard - notify = FALSE - /// The wizard who's piloting our rod. - var/mob/living/wizard - /// The distance the rod will go. - var/max_distance = BASE_WIZ_ROD_RANGE - /// 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, move_delay = 1, force_looping = FALSE, mob/living/wizard, max_distance = BASE_WIZ_ROD_RANGE) - . = ..() - if(wizard) - set_wizard(wizard) - src.start_turf = get_turf(src) - src.max_distance = max_distance - -/obj/effect/immovablerod/wizard/Destroy(force) - start_turf = null - if(wizard) - eject_wizard() - return ..() - -/obj/effect/immovablerod/wizard/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE) - if(get_dist(start_turf, get_turf(src)) >= max_distance) - qdel(src) - return - return ..() - -/// Should never happen, but better safe than sorry -/obj/effect/immovablerod/wizard/penetrate(mob/living/smeared_mob) - if(smeared_mob == wizard) - return - return ..() - -/** - * 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) - setDir(wizard.dir) - src.wizard = wizard - wizard.forceMove(src) - wizard.add_traits(list(TRAIT_GODMODE, TRAIT_NO_TRANSFORM), UNIQUE_TRAIT_SOURCE(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() - if(QDELETED(wizard)) - wizard = null - return - wizard.remove_traits(list(TRAIT_GODMODE, TRAIT_NO_TRANSFORM), UNIQUE_TRAIT_SOURCE(src)) - wizard.forceMove(get_turf(src)) - wizard = null - -/obj/effect/immovablerod/wizard/suplex_effect(mob/living/carbon/human/human) - human.visible_message( - span_boldwarning("[DECLENT_RU_CAP(src, NOMINATIVE)] превраща[PLUR_ET_YUT(src)]ся в [wizard.declent_ru(ACCUSATIVE)] из-за того что [human.declent_ru(NOMINATIVE)] схватил[GEND_A_O_I(human)] его!"), - span_warning("Вы хватаете [declent_ru(ACCUSATIVE)], и [declent_ru(NOMINATIVE)] внезапно превращается в [wizard.declent_ru(INSTRUMENTAL)].") - ) - to_chat(wizard, span_boldwarning("Вас внезапно выдернуло из формы жезла, когда [human.declent_ru(NOMINATIVE)] каким-то образом сумел[GEND_A_O_I(human)] схватить вас!")) - wizard.Knockdown(6 SECONDS) - wizard.apply_damage(25, BRUTE) - qdel(src) - #undef BASE_WIZ_ROD_RANGE diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 18bcd9f63b5b..8fa1118dc70a 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -48,7 +48,7 @@ overlay_icon_state = "bg_spell_border" active_overlay_icon_state = "targeting" background_icon_state_active = "bg_spell" - check_flags = AB_CHECK_CONSCIOUS | AB_TRANSFER_MIND//|AB_CHECK_PHASED + check_flags = AB_CHECK_CONSCIOUS | AB_TRANSFER_MIND | AB_CHECK_PHASED panel = "Spells" /// The sound played on cast. diff --git a/code/modules/spells/spell_types/pointed/horsemask.dm b/code/modules/spells/spell_types/pointed/horsemask.dm new file mode 100644 index 000000000000..dc2d3fcfa438 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/horsemask.dm @@ -0,0 +1,38 @@ +/datum/action/cooldown/spell/pointed/horsemask + name = "Curse of the Horseman" + desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes." + school = SCHOOL_TRANSMUTATION + cooldown_time = 15 SECONDS + cooldown_reduction_per_rank = 3 SECONDS //30 deciseconds reduction per rank + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "KN'A FTAGHU, PUCK 'BTHNK!" + invocation_type = INVOCATION_SHOUT + active_msg = span_notice_alt("You start to quietly neigh an incantation. Click on or near a target to cast the spell.") + deactive_msg = span_notice_alt("You stop neighing to yourself.") + + button_icon_state = "barn" + sound = 'sound/magic/HorseHead_curse.ogg' + +/datum/action/cooldown/spell/pointed/horsemask/is_valid_target(atom/cast_on) + if(!iscarbon(cast_on)) + return FALSE + . = ..() + + +/datum/action/cooldown/spell/pointed/horsemask/cast(atom/cast_on) + . = ..() + + var/mob/living/carbon/human/target = cast_on + var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead + magichead.item_flags |= DROPDEL //curses! + ADD_TRAIT(magichead, TRAIT_NODROP, CURSED_ITEM_TRAIT(magichead.type)) + magichead.flags_inv &= ~HIDENAME //so you can still see their face + magichead.voicechange = TRUE //NEEEEIIGHH + target.visible_message( span_danger("[target]'s face lights up in fire, and after the event a horse's head takes its place!"), \ + span_danger("Your face burns up, and shortly after the fire you realise you have the face of a horse!")) + if(!target.drop_item_ground(target.wear_mask)) + qdel(target.wear_mask) + target.equip_to_slot_or_del(magichead, ITEM_SLOT_MASK) + + target.flash_eyes() + 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..5f57c5f3640d --- /dev/null +++ b/code/modules/spells/spell_types/self/rod_form.dm @@ -0,0 +1,96 @@ +/// The base distance a wizard rod will go without upgrades. +#define BASE_WIZ_ROD_RANGE 15 +/datum/action/cooldown/spell/rod_form + name = "Rod Form" + desc = "Take on the form of an immovable rod, destroying all in your path." + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 8 SECONDS + invocation = "CLANG!" + invocation_type = INVOCATION_SHOUT + button_icon_state = "immrod" + sound = 'sound/effects/whoosh.ogg' + /// The max distance the rod goes on cast + var/rod_max_distance = BASE_WIZ_ROD_RANGE + /// Rod speed + var/rod_delay = 2 + +/datum/action/cooldown/spell/rod_form/cast(atom/cast_on) + . = ..() + var/turf/start = get_turf(cast_on) + if(is_admin_level(start)) + return + + var/flight_dist = rod_max_distance + spell_level * 3 + var/turf/distant_turf = get_ranged_target_turf(start, cast_on.dir, flight_dist) + new /obj/effect/immovablerod/wizard(start, distant_turf, null, rod_delay, FALSE, cast_on, flight_dist) + +/** + * Wizard Version of the Immovable Rod + */ +/obj/effect/immovablerod/wizard + notify = FALSE + /// The wizard who's piloting our rod. + var/mob/living/wizard + /// The distance the rod will go. + var/max_distance = BASE_WIZ_ROD_RANGE + /// 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, move_delay = 1, force_looping = FALSE, mob/living/wizard, max_distance = BASE_WIZ_ROD_RANGE) + . = ..() + if(wizard) + set_wizard(wizard) + src.start_turf = get_turf(src) + src.max_distance = max_distance + +/obj/effect/immovablerod/wizard/Destroy(force) + start_turf = null + if(wizard) + eject_wizard() + return ..() + +/obj/effect/immovablerod/wizard/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE) + if(get_dist(start_turf, get_turf(src)) >= max_distance) + qdel(src) + return + return ..() + +/// Should never happen, but better safe than sorry +/obj/effect/immovablerod/wizard/penetrate(mob/living/smeared_mob) + if(smeared_mob == wizard) + return + return ..() + +/** + * 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) + setDir(wizard.dir) + src.wizard = wizard + wizard.forceMove(src) + wizard.add_traits(list(TRAIT_GODMODE, TRAIT_NO_TRANSFORM), UNIQUE_TRAIT_SOURCE(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() + if(QDELETED(wizard)) + wizard = null + return + wizard.remove_traits(list(TRAIT_GODMODE, TRAIT_NO_TRANSFORM), UNIQUE_TRAIT_SOURCE(src)) + wizard.forceMove(get_turf(src)) + wizard = null + +/obj/effect/immovablerod/wizard/suplex_effect(mob/living/carbon/human/human) + human.visible_message( + span_boldwarning("[DECLENT_RU_CAP(src, NOMINATIVE)] превраща[PLUR_ET_YUT(src)]ся в [wizard.declent_ru(ACCUSATIVE)] из-за того что [human.declent_ru(NOMINATIVE)] схватил[GEND_A_O_I(human)] его!"), + span_warning("Вы хватаете [declent_ru(ACCUSATIVE)], и [declent_ru(NOMINATIVE)] внезапно превращается в [wizard.declent_ru(INSTRUMENTAL)].") + ) + to_chat(wizard, span_boldwarning("Вас внезапно выдернуло из формы жезла, когда [human.declent_ru(NOMINATIVE)] каким-то образом сумел[GEND_A_O_I(human)] схватить вас!")) + wizard.Knockdown(6 SECONDS) + wizard.apply_damage(25, BRUTE) + qdel(src) + +#undef BASE_WIZ_ROD_RANGE diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm index 7c7f5c675989..db12a957eb84 100644 --- a/code/modules/spells/spell_types/touch/_touch.dm +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -229,6 +229,7 @@ caster.do_attack_animation(victim) caster.changeNext_move(CLICK_CD_MELEE) victim.add_fingerprint(caster) + playsound(victim, sound, 50, TRUE) remove_hand(caster) return ITEM_INTERACT_SUCCESS diff --git a/code/modules/spells/spell_types/touch/banana_touch.dm b/code/modules/spells/spell_types/touch/banana_touch.dm index b9ca713eab1f..3e53f5268439 100644 --- a/code/modules/spells/spell_types/touch/banana_touch.dm +++ b/code/modules/spells/spell_types/touch/banana_touch.dm @@ -4,7 +4,8 @@ stun them with a loud HONK, and mutate them to make them more entertaining! \ Warning : Effects are permanent on non-wizards." hand_path = /obj/item/melee/magic_hand/banana_touch - school = "transmutation" + school = SCHOOL_TRANSMUTATION + sound = 'sound/items/AirHorn.ogg' invocation = "NWOLC YRGNA" cooldown_time = 30 SECONDS cooldown_reduction_per_rank = 5 SECONDS //50 deciseconds reduction per rank @@ -16,6 +17,9 @@ icon_state = "banana_touch" item_state = "banana_touch" +/datum/action/cooldown/spell/touch/banana_touch/is_valid_target(atom/cast_on) + return ishuman(cast_on) + /datum/action/cooldown/spell/touch/banana_touch/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) var/datum/effect_system/fluid_spread/smoke/smoke = new smoke.set_up(amount = 5, location = victim) @@ -23,8 +27,7 @@ to_chat(victim, "HONK") var/mob/living/carbon/human/h_target = victim h_target.bananatouched() - remove_hand(reset_cooldown_after = TRUE) - playsound(victim, 'sound/items/AirHorn.ogg', 50, TRUE) + return TRUE /mob/living/carbon/human/proc/bananatouched() to_chat(src, "HONK") diff --git a/code/modules/spells/spell_types/touch/cluwne.dm b/code/modules/spells/spell_types/touch/cluwne.dm index 3104bc8105eb..c09204d51afa 100644 --- a/code/modules/spells/spell_types/touch/cluwne.dm +++ b/code/modules/spells/spell_types/touch/cluwne.dm @@ -4,9 +4,9 @@ hand_path = /obj/item/melee/magic_hand/cluwne invocation = "NWOLC EGNEVER" school = SCHOOL_TRANSMUTATION + sound = 'sound/misc/sadtrombone.ogg' cooldown_time = 1 MINUTES cooldown_reduction_per_rank = 10 SECONDS - button_icon_state = "cluwne" /obj/item/melee/magic_hand/cluwne @@ -15,27 +15,26 @@ icon_state = "cluwnecurse" item_state = "cluwnecurse" -/datum/action/cooldown/spell/touch/cluwne/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) - if(victim == caster || !ishuman(victim) || caster.incapacitated()) - return +/datum/action/cooldown/spell/touch/cluwne/is_valid_target(atom/cast_on) + return ishuman(cast_on) - if(iswizard(victim)) +/datum/action/cooldown/spell/touch/cluwne/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + var/mob/living/carbon/human/H = victim + if(iswizard(H)) to_chat(caster, span_warning("The spell has no effect on [victim].")) return + if(!H.mind) + return FALSE + + if(H.mind.assigned_role != "Cluwne") + H.makeCluwne() + else + H.makeAntiCluwne() var/datum/effect_system/fluid_spread/smoke/s = new - s.set_up(amount = 5, location = victim) + s.set_up(amount = 5, location = H) s.start() - - var/mob/living/carbon/human/H = victim - if(H.mind) - if(H.mind.assigned_role != "Cluwne") - H.makeCluwne() - else - H.makeAntiCluwne() - remove_hand(reset_cooldown_after = TRUE) - playsound(victim, 'sound/misc/sadtrombone.ogg', 50, TRUE) - ..() + return TRUE /mob/living/carbon/human/proc/makeCluwne() diff --git a/code/modules/spells/spell_types/touch/disintergate.dm b/code/modules/spells/spell_types/touch/disintergate.dm new file mode 100644 index 000000000000..5f84ffd6196c --- /dev/null +++ b/code/modules/spells/spell_types/touch/disintergate.dm @@ -0,0 +1,21 @@ +/datum/action/cooldown/spell/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/magic_hand/disintegrate + invocation = "EI NATH!!" + sound = 'sound/magic/disintegrate.ogg' + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 10 SECONDS //100 deciseconds reduction per rank + button_icon_state = "gib" + +/obj/item/melee/magic_hand/disintegrate + name = "disintegrating touch" + desc = "This hand of mine glows with an awesome power!" + icon_state = "disintegrate" + item_state = "disintegrate" + +/datum/action/cooldown/spell/touch/disintegrate/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + var/mob/M = victim + do_sparks(4, FALSE, M.loc) //no idea what the 0 is + M.gib() + return TRUE 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..caef30138036 --- /dev/null +++ b/code/modules/spells/spell_types/touch/flesh_to_stone.dm @@ -0,0 +1,25 @@ +/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." + hand_path = /obj/item/melee/magic_hand/flesh_to_stone + invocation = "STAUN EI!!" + sound = 'sound/magic/fleshtostone.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 10 SECONDS //100 deciseconds reduction per rank + button_icon_state = "statue" + +/obj/item/melee/magic_hand/flesh_to_stone + name = "petrifying touch" + desc = "That's the bottom line, because flesh to stone said so!" + icon_state = "fleshtostone" + item_state = "fleshtostone" + +/datum/action/cooldown/spell/touch/flesh_to_stone/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/touch/flesh_to_stone/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + var/mob/living/target = victim + target.Stun(4 SECONDS) + new /obj/structure/closet/statue(target.loc, target) + return TRUE diff --git a/code/modules/spells/spell_types/touch/revive_touch.dm b/code/modules/spells/spell_types/touch/revive_touch.dm index ab098d5bb088..0bd38f228388 100644 --- a/code/modules/spells/spell_types/touch/revive_touch.dm +++ b/code/modules/spells/spell_types/touch/revive_touch.dm @@ -10,6 +10,7 @@ cooldown_time = 1 MINUTES spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC button_icon_state = "revive" + sound = 'sound/magic/staff_healing.ogg' /obj/item/melee/magic_hand/revive_touch name = "воскрешающее касание" @@ -28,20 +29,15 @@ ) /datum/action/cooldown/spell/touch/revive_touch/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) - . = ..() - - if(!isliving(victim)) - return . - var/mob/living/mob = victim if(mob.stat != DEAD || !(mob.mind?.is_revivable())) - return . + return FALSE mob.revive() playsound(victim, 'sound/magic/staff_healing.ogg', 50, TRUE) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(late_death), mob), REVIVE_SPELL_TIME) - remove_hand(reset_cooldown_after = TRUE) + return TRUE /proc/late_death(mob/living/mob) mob.death() diff --git a/paradise.dme b/paradise.dme index 220e79a3e492..348697f86fd4 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3975,6 +3975,7 @@ #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\pointed\chaplain.dm" #include "code\modules\spells\spell_types\pointed\fireball.dm" +#include "code\modules\spells\spell_types\pointed\horsemask.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" #include "code\modules\spells\spell_types\self\charge.dm" #include "code\modules\spells\spell_types\self\emplosion.dm" @@ -3984,6 +3985,7 @@ #include "code\modules\spells\spell_types\self\nightvision.dm" #include "code\modules\spells\spell_types\self\no_clothes.dm" #include "code\modules\spells\spell_types\self\nullbox.dm" +#include "code\modules\spells\spell_types\self\rod_form.dm" #include "code\modules\spells\spell_types\self\smoke.dm" #include "code\modules\spells\spell_types\self\summon_friend.dm" #include "code\modules\spells\spell_types\self\view_range.dm" @@ -3995,6 +3997,8 @@ #include "code\modules\spells\spell_types\touch\_touch.dm" #include "code\modules\spells\spell_types\touch\banana_touch.dm" #include "code\modules\spells\spell_types\touch\cluwne.dm" +#include "code\modules\spells\spell_types\touch\disintergate.dm" +#include "code\modules\spells\spell_types\touch\flesh_to_stone.dm" #include "code\modules\spells\spell_types\touch\revive_touch.dm" #include "code\modules\station_goals\bfl.dm" #include "code\modules\station_goals\bluespace_tap.dm" From e1e2262d0f5f57e781f113d15e2b90ff0344d518 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Mon, 8 Jun 2026 00:02:54 +0300 Subject: [PATCH 36/42] =?UTF-8?q?=D1=81=D1=83=D0=BA=D0=B0=20=D1=84=D0=B0?= =?UTF-8?q?=D1=81=D1=82=20=D0=BB=D0=BE=D0=B0=D0=B4=20=D0=B7=D0=B0=D0=B1?= =?UTF-8?q?=D1=8B=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _maps/common.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_maps/common.dm b/_maps/common.dm index c34e5b358843..60f9496f97b1 100644 --- a/_maps/common.dm +++ b/_maps/common.dm @@ -9,7 +9,7 @@ z3 = engineering stuff (called Z4, dont question it) z4 = lavaland */ -#define FAST_LOAD +//#define FAST_LOAD #define MAP_TRANSITION_CONFIG list() From aa0df14e470b26b01db417f9983d414e32397a08 Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Mon, 8 Jun 2026 00:58:44 +0300 Subject: [PATCH 37/42] lavaland spells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавил заклинания с лаваленда(кроме лечащего касания) --- code/__DEFINES/magic.dm | 2 + code/modules/spells/spell.dm | 2 +- .../spell_types/conjure/legion_skulls.dm | 18 +++++++ .../spell_types/conjure_item/infinity_guns.dm | 8 ++++ .../spell_types/pointed/goliath_dash.dm | 47 +++++++++++++++++++ .../spell_types/pointed/goliath_tentacles.dm | 13 +++++ .../spell_types/pointed/watchers_look.dm | 27 +++++++++++ paradise.dme | 5 ++ 8 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 code/modules/spells/spell_types/conjure/legion_skulls.dm create mode 100644 code/modules/spells/spell_types/conjure_item/infinity_guns.dm create mode 100644 code/modules/spells/spell_types/pointed/goliath_dash.dm create mode 100644 code/modules/spells/spell_types/pointed/goliath_tentacles.dm create mode 100644 code/modules/spells/spell_types/pointed/watchers_look.dm diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm index a44f8d9a59fa..b1b35b807919 100644 --- a/code/__DEFINES/magic.dm +++ b/code/__DEFINES/magic.dm @@ -30,6 +30,8 @@ #define SCHOOL_FORBIDDEN "forbidden" /// Blood magic, involves vampirism, draining blood, etc. #define SCHOOL_SANGUINE "sanguine" +///Lavaland magic +#define SCHOOL_LAVALAND "lavaland" // 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. diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 8fa1118dc70a..9915862b5c11 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -48,7 +48,7 @@ overlay_icon_state = "bg_spell_border" active_overlay_icon_state = "targeting" background_icon_state_active = "bg_spell" - check_flags = AB_CHECK_CONSCIOUS | AB_TRANSFER_MIND | AB_CHECK_PHASED + check_flags = AB_CHECK_CONSCIOUS | AB_TRANSFER_MIND | AB_CHECK_PHASED | AB_CHECK_INCAPACITATED panel = "Spells" /// The sound played on cast. diff --git a/code/modules/spells/spell_types/conjure/legion_skulls.dm b/code/modules/spells/spell_types/conjure/legion_skulls.dm new file mode 100644 index 000000000000..93dfe68cebf0 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/legion_skulls.dm @@ -0,0 +1,18 @@ +/datum/action/cooldown/spell/conjure/legion_skulls + name = "Summon Skulls" + desc = "This spell summons three friendly to you legion's skulls." + school = SCHOOL_LAVALAND + cooldown_time = 15 SECONDS + invocation = "TRAKI SUMON!" + invocation_type = INVOCATION_SHOUT + check_flags = AB_CHECK_CONSCIOUS | AB_TRANSFER_MIND | AB_CHECK_PHASED + button_icon_state = "sumon_skulls" + summon_type = list(/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/magic) + summon_amount = 3 + summon_radius = 1 + sound = 'sound/magic/forcewall.ogg' + +/datum/action/cooldown/spell/conjure/legion_skulls/post_summon(atom/summoned_object, atom/cast_on) + var/mob/skull = summoned_object + var/mob/caster = cast_on + skull.faction += caster.faction diff --git a/code/modules/spells/spell_types/conjure_item/infinity_guns.dm b/code/modules/spells/spell_types/conjure_item/infinity_guns.dm new file mode 100644 index 000000000000..dc4fa99af007 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/infinity_guns.dm @@ -0,0 +1,8 @@ +/datum/action/cooldown/spell/conjure_item/infinity_gunss + name = "Lesser Summon Guns" + desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles. Requires both hands free to use." + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 11.8 SECONDS //Gun wizard + delete_old = FALSE + button_icon_state = "bolt_action" + item_type = /obj/item/gun/projectile/shotgun/boltaction/enchanted diff --git a/code/modules/spells/spell_types/pointed/goliath_dash.dm b/code/modules/spells/spell_types/pointed/goliath_dash.dm new file mode 100644 index 000000000000..f903c952852c --- /dev/null +++ b/code/modules/spells/spell_types/pointed/goliath_dash.dm @@ -0,0 +1,47 @@ +/datum/action/cooldown/spell/pointed/goliath_dash + name = "Goliath Dash" + desc = "Make a dash followed by an attack with the tentacles of goliath" + school = SCHOOL_LAVALAND + cooldown_time = 20 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "RAGET'RE BRAN!" + invocation_type = INVOCATION_SHOUT + button_icon_state = "goliath_dash" + +/datum/action/cooldown/spell/pointed/goliath_dash/cast(atom/cast_on) + . = ..() + + owner.stop_pulling() + owner.unbuckle_all_mobs(TRUE) + owner.buckled?.unbuckle_mob(owner, TRUE) + owner.pulledby?.stop_pulling() + + owner.layer = LOW_LANDMARK_LAYER + + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, MAGIC_TRAIT) + + for(var/i in 1 to 7) + if(QDELETED(owner)) + return + + var/direction = get_dir(owner, cast_on) + var/turf/next_step = get_step(owner, direction) + owner.face_atom(cast_on) + + if(!is_path_exist(owner, next_step, PASSTABLE|PASSFENCE)) + break + + owner.forceMove(next_step) + playsound(owner.loc, pick('sound/effects/footstep/heavy1.ogg', 'sound/effects/footstep/heavy2.ogg'), 100, TRUE) + sleep(0.05 SECONDS) + + if(QDELETED(owner)) + return + + owner.layer = initial(owner.layer) + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, MAGIC_TRAIT) + owner.visible_message(span_warning("[owner] unleashes tentacles from the ground around it!")) + + for(var/d in GLOB.alldirs) + var/turf/E = get_step(owner, d) + new /obj/effect/temp_visual/goliath_tentacle(E, owner) diff --git a/code/modules/spells/spell_types/pointed/goliath_tentacles.dm b/code/modules/spells/spell_types/pointed/goliath_tentacles.dm new file mode 100644 index 000000000000..23fff6460682 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/goliath_tentacles.dm @@ -0,0 +1,13 @@ +/datum/action/cooldown/spell/pointed/goliath_tentacles + name = "Summon Tentacles" + desc = "Summons a goliath tentacle attack on clicked tile" + school = SCHOOL_LAVALAND + cooldown_time = 15 SECONDS + invocation = "SOGESE DE RAGET'RE!" + invocation_type = INVOCATION_SHOUT + button_icon_state = "goliath_tentacles" + +/datum/action/cooldown/spell/pointed/goliath_tentacles/cast(atom/cast_on) + . = ..() + var/turf/target_turf = get_turf(cast_on) + new /obj/effect/temp_visual/goliath_tentacle/full_cross(target_turf, owner) diff --git a/code/modules/spells/spell_types/pointed/watchers_look.dm b/code/modules/spells/spell_types/pointed/watchers_look.dm new file mode 100644 index 000000000000..b1dd9d083add --- /dev/null +++ b/code/modules/spells/spell_types/pointed/watchers_look.dm @@ -0,0 +1,27 @@ +/datum/action/cooldown/spell/pointed/projectile/watchers_look + name = "Watcher's Look" + desc = "Shoot one of the watcher's beams. To change the mode, use right click on the icon." + invocation = "ONI DRAKT'CEHOR!" + invocation_type = INVOCATION_SHOUT + cooldown_time = 3 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + school = SCHOOL_LAVALAND + button_icon_state = "watcher_normal" + background_icon_state = "" + var/projectiles_icons = list( + "watcher_normal" = /obj/projectile/watcher, + "watcher_fire" = /obj/projectile/temp/basilisk/magmawing, + "watcher_ice" = /obj/projectile/temp/basilisk/icewing, + ) + var/selected_projectile = 1 + projectile_type = /obj/projectile/watcher + +/datum/action/cooldown/spell/pointed/projectile/watchers_look/Trigger(mob/clicker, trigger_flags, atom/target) + if(trigger_flags & TRIGGER_SECONDARY_ACTION) + //switch to next type of projectile and update action's icon + selected_projectile = selected_projectile % length(projectiles_icons) + 1 + projectile_type = projectiles_icons[projectiles_icons[selected_projectile]] + button_icon_state = projectiles_icons[selected_projectile] + UpdateButtonIcon() + return CLICK_ACTION_SUCCESS + . = ..() diff --git a/paradise.dme b/paradise.dme index 348697f86fd4..a70b5ca5017c 100644 --- a/paradise.dme +++ b/paradise.dme @@ -3963,9 +3963,11 @@ #include "code\modules\spells\spell_types\conjure\carp.dm" #include "code\modules\spells\spell_types\conjure\construct.dm" #include "code\modules\spells\spell_types\conjure\creature.dm" +#include "code\modules\spells\spell_types\conjure\legion_skulls.dm" #include "code\modules\spells\spell_types\conjure\timestop.dm" #include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" #include "code\modules\spells\spell_types\conjure_item\contract_gun.dm" +#include "code\modules\spells\spell_types\conjure_item\infinity_guns.dm" #include "code\modules\spells\spell_types\conjure_item\soulstone.dm" #include "code\modules\spells\spell_types\conjure_item\summon_wealth.dm" #include "code\modules\spells\spell_types\jaunt\_jaunt.dm" @@ -3975,7 +3977,10 @@ #include "code\modules\spells\spell_types\pointed\_pointed.dm" #include "code\modules\spells\spell_types\pointed\chaplain.dm" #include "code\modules\spells\spell_types\pointed\fireball.dm" +#include "code\modules\spells\spell_types\pointed\goliath_dash.dm" +#include "code\modules\spells\spell_types\pointed\goliath_tentacles.dm" #include "code\modules\spells\spell_types\pointed\horsemask.dm" +#include "code\modules\spells\spell_types\pointed\watchers_look.dm" #include "code\modules\spells\spell_types\proejctile\_basic_projectile.dm" #include "code\modules\spells\spell_types\self\charge.dm" #include "code\modules\spells\spell_types\self\emplosion.dm" From 6bf4d167ec941c9a39d917412b5d47ff220b691f Mon Sep 17 00:00:00 2001 From: L343-CJ Date: Tue, 9 Jun 2026 20:00:10 +0300 Subject: [PATCH 38/42] =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=BE=D0=B7=20=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D1=88=D0=B8=D1=85=D1=81=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BF=D0=B5=D0=BB=D0=BB=D0=BE=D0=B2=20=D0=BC=D0=B0=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit форма животного, целебное касание, майндсвап, аппендиксы, искажение пространства, грейт кнок --- _maps/common.dm | 2 +- code/datums/spells/spacetime_dist.dm | 61 ------- code/game/objects/effects/warp_effects.dm | 10 ++ .../food/foods/lavaland_food.dm | 2 +- code/modules/mining/ash_walkers/ash_rune.dm | 2 +- .../spell_types/aoe_spell/rathens_secret.dm | 56 +++++++ .../spell_types/aoe_spell/spacetime_dist.dm | 149 ++++++++++++++++++ .../spells/spell_types/conjure/build.dm | 3 +- .../spells/spell_types/pointed/chaplain.dm | 2 +- .../spells/spell_types/pointed/fireball.dm | 2 +- .../spells/spell_types/pointed/horsemask.dm | 2 +- .../spells/spell_types/pointed/mindswap.dm | 80 ++++++++++ .../spells/spell_types/self/greater_knock.dm | 26 +++ .../spells/spell_types/self/healtouch.dm | 55 +++++++ .../spells/spell_types/self/nightvision.dm | 1 + .../spells/spell_types/self/summon_item.dm | 110 +++++++++++++ .../spell_types/shapeshift/animal_form.dm | 13 ++ icons/mob/screen_alert.dmi | Bin 85504 -> 88454 bytes paradise.dme | 7 + 19 files changed, 515 insertions(+), 68 deletions(-) create mode 100644 code/modules/spells/spell_types/aoe_spell/rathens_secret.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/spacetime_dist.dm create mode 100644 code/modules/spells/spell_types/pointed/mindswap.dm create mode 100644 code/modules/spells/spell_types/self/greater_knock.dm create mode 100644 code/modules/spells/spell_types/self/healtouch.dm create mode 100644 code/modules/spells/spell_types/self/summon_item.dm create mode 100644 code/modules/spells/spell_types/shapeshift/animal_form.dm diff --git a/_maps/common.dm b/_maps/common.dm index 60f9496f97b1..c34e5b358843 100644 --- a/_maps/common.dm +++ b/_maps/common.dm @@ -9,7 +9,7 @@ z3 = engineering stuff (called Z4, dont question it) z4 = lavaland */ -//#define FAST_LOAD +#define FAST_LOAD #define MAP_TRANSITION_CONFIG list() diff --git a/code/datums/spells/spacetime_dist.dm b/code/datums/spells/spacetime_dist.dm index 708c9df7c81f..55eee5ac8a33 100644 --- a/code/datums/spells/spacetime_dist.dm +++ b/code/datums/spells/spacetime_dist.dm @@ -71,64 +71,3 @@ QDEL_LIST(effects) ready = TRUE -/obj/effect/cross_action - name = "cross me" - desc = "for crossing" - -/obj/effect/cross_action/spacetime_dist - name = "spacetime distortion" - desc = "A distortion in spacetime. You can hear faint music..." - icon_state = "nothing" - /// A flags which save people from being thrown about - var/obj/effect/cross_action/spacetime_dist/linked_dist - /// Used to prevent an infinite loop in the space time continuum - var/cant_teleport = FALSE - var/walks_left = 50 //prevents the game from hanging in extreme cases - -/obj/effect/cross_action/spacetime_dist/Initialize(mapload) - . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/cross_action/singularity_act() - return - -/obj/effect/cross_action/singularity_pull(atom/singularity, current_size) - return - -/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) - 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) - cant_teleport = TRUE - flick("purplesparkles", src) - AM.forceMove(get_turf(src)) - cant_teleport = FALSE - -/obj/effect/cross_action/spacetime_dist/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) - SIGNAL_HANDLER - - if(!cant_teleport) - walk_link(arrived) - -/obj/effect/cross_action/spacetime_dist/attackby(obj/item/I, mob/user, params) - . = ATTACK_CHAIN_BLOCKED_ALL - if(user.drop_item_ground(I)) - walk_link(I) - 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/Destroy() - cant_teleport = TRUE - linked_dist = null - return ..() - diff --git a/code/game/objects/effects/warp_effects.dm b/code/game/objects/effects/warp_effects.dm index efcc48744122..60353c9a2aa7 100644 --- a/code/game/objects/effects/warp_effects.dm +++ b/code/game/objects/effects/warp_effects.dm @@ -52,3 +52,13 @@ transform = scale_matrix animate(src, transform = scale_matrix * 8, time = 0.8 SECONDS, alpha = 0) QDEL_IN(src, 0.8 SECONDS) + +// MARK: Rathens +/obj/effect/warp_effect/rathens + +/obj/effect/warp_effect/rathens/Initialize(mapload) + . = ..() + var/matrix/scale_matrix = matrix() * 0.5 + transform = scale_matrix + animate(src, transform = scale_matrix * 12, time = 1.2 SECONDS, alpha = 0) + QDEL_IN(src, 1.2 SECONDS) diff --git a/code/modules/food_and_drinks/food/foods/lavaland_food.dm b/code/modules/food_and_drinks/food/foods/lavaland_food.dm index 933992834a35..069dbcc94cfc 100644 --- a/code/modules/food_and_drinks/food/foods/lavaland_food.dm +++ b/code/modules/food_and_drinks/food/foods/lavaland_food.dm @@ -436,7 +436,7 @@ /obj/item/reagent_containers/food/snacks/lavaland_food/cure_curse/attackby(obj/item/I, mob/user, params) . = ..() - if(!istype(I, /obj/item/melee/touch_attack/healtouch)) + if(!istype(I, /obj/item/melee/magic_hand/healtouch/shaman)) return ..() if(active) balloon_alert(user, "уже активно!") diff --git a/code/modules/mining/ash_walkers/ash_rune.dm b/code/modules/mining/ash_walkers/ash_rune.dm index 20001de5fe9e..8761310d4cab 100644 --- a/code/modules/mining/ash_walkers/ash_rune.dm +++ b/code/modules/mining/ash_walkers/ash_rune.dm @@ -26,7 +26,7 @@ icon_state = "runaash_[number]" /obj/effect/decal/ash_rune_centre/attackby(obj/item/I, mob/user, params) - if(!istype(I, /obj/item/melee/touch_attack/healtouch)) + if(!istype(I, /obj/item/melee/magic_hand/healtouch/shaman)) return ..() if(transforming) return ..() diff --git a/code/modules/spells/spell_types/aoe_spell/rathens_secret.dm b/code/modules/spells/spell_types/aoe_spell/rathens_secret.dm new file mode 100644 index 000000000000..605a5e66245c --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/rathens_secret.dm @@ -0,0 +1,56 @@ +/datum/action/cooldown/spell/aoe/rathens_secret + name = "Rathen's Secret" + desc = "Summons a powerful shockwave around you that tears the appendix out of enemies, and occasionally removes their limbs." + cooldown_time = 50 SECONDS + cooldown_reduction_per_rank = 7.5 SECONDS + invocation = "APPEN NATH!" + invocation_type = INVOCATION_SHOUT + button_icon_state = "lungpunch" + aoe_radius = 10 + max_targets = INFINITY + +/datum/action/cooldown/spell/aoe/rathens_secret/get_things_to_cast_on(atom/center) + var/list/targets = list() + for(var/mob/living/carbon/human/target in range(aoe_radius, center)) + if(target == owner) + continue + targets += target + return targets + +/datum/action/cooldown/spell/aoe/rathens_secret/cast(atom/cast_on) + . = ..() + new /obj/effect/warp_effect/rathens(cast_on.loc) + +/datum/action/cooldown/spell/aoe/rathens_secret/cast_on_thing_in_aoe(atom/victim, atom/caster) + var/mob/living/carbon/human/target = victim + var/datum/effect_system/fluid_spread/smoke/s = new + s.set_up(amount = 5, location = target) + s.start() + var/obj/item/organ/internal/appendix/A = target.get_int_organ(/obj/item/organ/internal/appendix) + if(A) + A.remove(target) + A.forceMove(get_turf(target)) + spawn() + A.throw_at(get_edge_target_turf(target, pick(GLOB.alldirs)), rand(1, 10), 5) + target.visible_message( + span_danger("[target]'s [A.name] flies out of their body in a magical explosion!"),\ + span_danger("Your [A.name] flies out of your body in a magical explosion!") + ) + target.Weaken(4 SECONDS) + else + var/obj/effect/decal/cleanable/blood/gibs/G = new/obj/effect/decal/cleanable/blood/gibs(get_turf(target)) + spawn() + G.throw_at(get_edge_target_turf(target, pick(GLOB.alldirs)), rand(1, 10), 5) + target.apply_damage(10, BRUTE, BODY_ZONE_CHEST) + to_chat(target, span_userdanger("You have no appendix, but something had to give! Holy shit, what was that?")) + target.Weaken(6 SECONDS) + for(var/obj/item/organ/external/E as anything in target.bodyparts) + if(istype(E, /obj/item/organ/external/head)) + continue + if(ischest(E)) + continue + if(isgroin(E)) + continue + if(prob(7)) + to_chat(target, span_userdanger("Your [E] was severed by the explosion!")) + E.droplimb(1, DROPLIMB_SHARP, 0, 1) diff --git a/code/modules/spells/spell_types/aoe_spell/spacetime_dist.dm b/code/modules/spells/spell_types/aoe_spell/spacetime_dist.dm new file mode 100644 index 000000000000..fc5547762404 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/spacetime_dist.dm @@ -0,0 +1,149 @@ +// This could probably be an aoe spell but it's a little cursed, so I'm not touching it +/datum/action/cooldown/spell/aoe/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... \ + Upgrading the spell increases range, it does not lower cooldown." + sound = 'sound/magic/strings.ogg' + button_icon_state = "spacetime" + school = SCHOOL_TRANSMUTATION + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 0 //No reduction, just more range. + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC | SPELL_REQUIRES_WIZARD_GARB | SPELL_REQUIRES_STATION //Prevent people from getting to centcom + spell_max_level = 3 + aoe_radius = 10 + /// Whether we're ready to cast again yet or not. In the event someone lowers their cooldown with charge. + var/ready = TRUE + /// 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/aoe/spacetime_dist/Destroy() + QDEL_LIST(effects) + return ..() + +/datum/action/cooldown/spell/aoe/spacetime_dist/level_spell(bypass_cap) + . = ..() + aoe_radius += 3 + +/datum/action/cooldown/spell/aoe/spacetime_dist/can_cast_spell(feedback) + return ..() && ready + +/datum/action/cooldown/spell/aoe/spacetime_dist/get_things_to_cast_on(atom/center) + var/list/turfs = spiral_range_turfs(aoe_radius, center) + if(!length(turfs)) + return + + var/list/targets = 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) + targets[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) + targets[loner] = get_turf(pick(caster_area.contents)) + return targets + +/datum/action/cooldown/spell/aoe/spacetime_dist/cast(atom/cast_on) + var/list/turf/to_switcharoo = get_things_to_cast_on(cast_on) + if(!length(to_switcharoo)) + to_chat(cast_on, span_warning("For whatever reason, the strings nearby aren't keen on being tangled.")) + return + + ready = FALSE + effects = list() + + 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) + var/obj/effect/cross_action/spacetime_dist/effect_b = new /obj/effect/cross_action/spacetime_dist(swap_b) + 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_range_power_color(4, 30, "#c9fff5") + effects += effect_a + effects += effect_b + after_cast(cast_on) + .=..() + +/datum/action/cooldown/spell/aoe/spacetime_dist/cast_on_thing_in_aoe(atom/victim, atom/caster) + return + +/datum/action/cooldown/spell/aoe/spacetime_dist/after_cast(atom/cast_on) + . = ..() + addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration) + +/// Callback which cleans up our effects list after the duration expires. +/datum/action/cooldown/spell/aoe/spacetime_dist/proc/clean_turfs() + QDEL_LIST(effects) + ready = TRUE + +/obj/effect/cross_action + name = "cross me" + desc = "for crossing" + +/obj/effect/cross_action/spacetime_dist + name = "spacetime distortion" + desc = "A distortion in spacetime. You can hear faint music..." + icon_state = "nothing" + /// A flags which save people from being thrown about + var/obj/effect/cross_action/spacetime_dist/linked_dist + /// Used to prevent an infinite loop in the space time continuum + var/cant_teleport = FALSE + var/walks_left = 50 //prevents the game from hanging in extreme cases + +/obj/effect/cross_action/spacetime_dist/Initialize(mapload) + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/effect/cross_action/singularity_act() + return + +/obj/effect/cross_action/singularity_pull(atom/singularity, current_size) + return + +/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) + 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) + cant_teleport = TRUE + flick("purplesparkles", src) + AM.forceMove(get_turf(src)) + cant_teleport = FALSE + +/obj/effect/cross_action/spacetime_dist/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + SIGNAL_HANDLER + + if(!cant_teleport) + walk_link(arrived) + +/obj/effect/cross_action/spacetime_dist/attackby(obj/item/I, mob/user, params) + . = ATTACK_CHAIN_BLOCKED_ALL + if(user.drop_item_ground(I)) + walk_link(I) + 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/Destroy() + cant_teleport = TRUE + linked_dist = null + return ..() + diff --git a/code/modules/spells/spell_types/conjure/build.dm b/code/modules/spells/spell_types/conjure/build.dm index 272955172532..c4f4087462bf 100644 --- a/code/modules/spells/spell_types/conjure/build.dm +++ b/code/modules/spells/spell_types/conjure/build.dm @@ -77,7 +77,8 @@ button_icon_state = "cultforcewall" background_icon_state = "bg_cult" background_icon_state_active = "bg_cult" - school = "transmutation" + school = SCHOOL_TRANSMUTATION + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_PHASED | AB_CHECK_INCAPACITATED cooldown_time = 30 SECONDS spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC //holy_area_cancast = FALSE //Stops cult magic from working on holy ground eg: chapel diff --git a/code/modules/spells/spell_types/pointed/chaplain.dm b/code/modules/spells/spell_types/pointed/chaplain.dm index 016bfb9c3a5a..441f29b17c58 100644 --- a/code/modules/spells/spell_types/pointed/chaplain.dm +++ b/code/modules/spells/spell_types/pointed/chaplain.dm @@ -5,7 +5,7 @@ cooldown_time = 6 SECONDS cooldown_reduction_per_rank = 1 SECONDS spell_requirements = NONE - active_msg = span_notice_alt("You prepare a blessing. Click on a target to start blessing.") + active_msg = span_notice_alt("You prepare a blessing.") deactive_msg = span_notice_alt("The crew will be blessed another time.") button_icon_state = "shield" diff --git a/code/modules/spells/spell_types/pointed/fireball.dm b/code/modules/spells/spell_types/pointed/fireball.dm index 61f7f16f5abe..d62381ab36a5 100644 --- a/code/modules/spells/spell_types/pointed/fireball.dm +++ b/code/modules/spells/spell_types/pointed/fireball.dm @@ -6,7 +6,7 @@ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC invocation = "ONI SOMA" invocation_type = INVOCATION_SHOUT - active_msg = span_notice_alt("Your prepare to cast your fireball spell! Left-click to cast at a target!") + active_msg = span_notice_alt("Your prepare to cast your fireball spell!") deactive_msg = span_notice_alt("You extinguish your fireball...for now.") projectile_type = /obj/projectile/magic/fireball button_icon_state = "fireball0" diff --git a/code/modules/spells/spell_types/pointed/horsemask.dm b/code/modules/spells/spell_types/pointed/horsemask.dm index dc2d3fcfa438..96381dd918b3 100644 --- a/code/modules/spells/spell_types/pointed/horsemask.dm +++ b/code/modules/spells/spell_types/pointed/horsemask.dm @@ -7,7 +7,7 @@ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC invocation = "KN'A FTAGHU, PUCK 'BTHNK!" invocation_type = INVOCATION_SHOUT - active_msg = span_notice_alt("You start to quietly neigh an incantation. Click on or near a target to cast the spell.") + active_msg = span_notice_alt("You start to quietly neigh an incantation.") deactive_msg = span_notice_alt("You stop neighing to yourself.") button_icon_state = "barn" diff --git a/code/modules/spells/spell_types/pointed/mindswap.dm b/code/modules/spells/spell_types/pointed/mindswap.dm new file mode 100644 index 000000000000..926369c6016c --- /dev/null +++ b/code/modules/spells/spell_types/pointed/mindswap.dm @@ -0,0 +1,80 @@ +/datum/action/cooldown/spell/pointed/mindswap + name = "Mind Transfer" + desc = "This spell allows the user to switch bodies with a target." + + school = SCHOOL_TRANSMUTATION + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 10 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "GIN'YU CAPAN" + invocation_type = INVOCATION_WHISPER + active_msg = span_notice_alt("You prepare to transfer your mind.") + deactive_msg = span_notice_alt("You decide that your current form is good enough.") + var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell + var/paralysis_amount_caster = 40 SECONDS //how much the caster is paralysed for after the spell + var/paralysis_amount_victim = 40 SECONDS //how much the victim is paralysed for after the spell + button_icon_state = "mindswap" + cast_range = 1 + +/datum/action/cooldown/spell/pointed/mindswap/is_valid_target(atom/cast_on) + . = ..() + if(!isliving(cast_on)) + return FALSE + var/mob/living/target = cast_on + return target.stat != DEAD && target.key && target.mind + +/* +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 transfering. +Also, you never added distance checking after target is selected. I've went ahead and did that. +*/ +/datum/action/cooldown/spell/pointed/mindswap/cast(atom/cast_on) + . = ..() + var/mob/living/target = cast_on + + if(owner.suiciding) + to_chat(owner, span_warning("You're killing yourself! You can't concentrate enough to do this!")) + return + + if(target.mind.special_role in protected_roles) + to_chat(owner, "Their mind is resisting your spell.") + return + + if(issilicon(target)) + to_chat(owner, "You feel this enslaved being is just as dead as its cold, hard exoskeleton.") + return + + var/mob/living/victim = target//The target of the spell whos body will be transferred to. + var/mob/living/caster = owner//The wizard/whomever doing the body transferring. + + //MIND TRANSFER BEGIN + if(length(caster.mind.special_verbs))//If the caster had any special verbs, remove them from the mob verb list. + for(var/V in caster.mind.special_verbs)//Since the caster is using an object spell system, this is mostly moot. + remove_verb(caster, V)//But a safety nontheless. + + if(length(victim.mind.special_verbs))//Now remove all of the victim's verbs. + for(var/V in victim.mind.special_verbs) + remove_verb(victim, V) + + var/mob/dead/observer/ghost = victim.ghostize(0) + caster.mind.transfer_to(victim) + + if(length(victim.mind.special_verbs))//To add all the special verbs for the original caster. + for(var/V in caster.mind.special_verbs)//Not too important but could come into play. + add_verb(caster, V) + + ghost.mind.transfer_to(caster) + if(ghost.key) + GLOB.non_respawnable_keys -= ghost.ckey //ghostizing with an argument of 0 will make them unable to respawn forever, which is bad + caster.possess_by_player(ghost.ckey) //have to transfer the key since the mind was not active + qdel(ghost) + + if(length(caster.mind.special_verbs))//If they had any special verbs, we add them here. + for(var/V in caster.mind.special_verbs) + add_verb(caster, V) + //MIND TRANSFER END + + //Here we paralyze both mobs and knock them out for a time. + caster.Paralyse(paralysis_amount_caster) + victim.Paralyse(paralysis_amount_victim) + diff --git a/code/modules/spells/spell_types/self/greater_knock.dm b/code/modules/spells/spell_types/self/greater_knock.dm new file mode 100644 index 000000000000..5f44d55949a1 --- /dev/null +++ b/code/modules/spells/spell_types/self/greater_knock.dm @@ -0,0 +1,26 @@ +/datum/action/cooldown/spell/greater_knock + name = "Greater Knock" + desc = "On first cast, will remove access restrictions on all airlocks on the station, and announce this spell's use to the station. On any further cast, will open all doors in sight. Cannot be refunded once bought!" + button_icon_state = "greater_knock" + cooldown_time = 20 SECONDS + invocation = "MAIOR OXIN FIERA" + invocation_type = INVOCATION_SHOUT + spell_max_level = 0 //Cannot be improved, quality of life since can't be refunded + var/used = FALSE + +/datum/action/cooldown/spell/greater_knock/can_cast_spell(feedback) + return ..() && !used + + +/datum/action/cooldown/spell/greater_knock/cast(atom/cast_on) + . = ..() + used = TRUE + for(var/obj/machinery/door/airlock/A in GLOB.airlocks) + if(is_station_level(A.z)) + A.req_access = list() + GLOB.major_announcement.announce( + message = "Мы убрали все доступы с шлюзов на вашей станции. Вы сможете поблагодарить нас позже!", + new_title = "Послание Федерации Космических Волшебников.", + new_sound = 'sound/misc/notice2.ogg', + new_subtitle = "Приветствуем!" + ) diff --git a/code/modules/spells/spell_types/self/healtouch.dm b/code/modules/spells/spell_types/self/healtouch.dm new file mode 100644 index 000000000000..77a614407945 --- /dev/null +++ b/code/modules/spells/spell_types/self/healtouch.dm @@ -0,0 +1,55 @@ +//basic touch ability that heals basic damage types accessed by the ashwalker shaman +/datum/action/cooldown/spell/touch/healtouch + name = "Целебное касание" + desc = "Это заклинание заряжает вашу руку целительной энергиец, позволяя вам лечить некоторые повреждения." + hand_path = /obj/item/melee/magic_hand/healtouch + cooldown_time = 20 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "ИСЦЕЛЕНИЕ!" + school = SCHOOL_RESTORATION + can_cast_on_self = TRUE + button_icon_state = "healtouch" + sound = 'sound/magic/staff_healing.ogg' + //total of 40 assuming they're hurt by both brute and burn + var/brute = 20 + var/burn = 20 + var/tox = 10 + var/oxy = 50 + +/obj/item/melee/magic_hand/healtouch + name = "healing touch" + desc = "Целительная аура, вырывающаяся из вашей руки. При прикосновении к гуманоиду заживляет его раны." + icon_state = "disintegrate" //ironic huh + item_state = "disintegrate" + +/obj/item/melee/magic_hand/healtouch/get_ru_names() + return alist( + NOMINATIVE = "целебное касание", + GENITIVE = "целебного касания", + DATIVE = "целебному касанию", + ACCUSATIVE = "целебное касание", + INSTRUMENTAL = "целебным касанием", + PREPOSITIONAL = "целебном касании", + ) + +/datum/action/cooldown/spell/touch/healtouch/cast_on_hand_hit(obj/item/melee/magic_hand/hand, atom/victim, mob/living/carbon/caster) + var/mob/living/M = victim + new /obj/effect/temp_visual/heal(get_turf(M), "#899d39") + var/update = NONE + update |= M.heal_overall_damage(brute, burn, updating_health = FALSE) + update |= M.heal_damages(tox = tox, oxy = oxy, updating_health = FALSE) + if(update) + M.updatehealth("healing touch") + for(var/datum/disease/D in M.diseases) + if(D.curable) + D.cure(need_immunity = FALSE) + return TRUE + +/datum/action/cooldown/spell/touch/healtouch/shaman + name = "Прикосновение шамана" + desc = "Это заклинание заряжает вашу руку энергией Некрополя, позволяя вам лечить некоторые повреждения и взаимодействовать с некоторыми предметами." + school = SCHOOL_LAVALAND + can_cast_on_self = FALSE + hand_path = /obj/item/melee/magic_hand/healtouch/shaman + +/obj/item/melee/magic_hand/healtouch/shaman diff --git a/code/modules/spells/spell_types/self/nightvision.dm b/code/modules/spells/spell_types/self/nightvision.dm index 3d1552253634..fe9faeea318e 100644 --- a/code/modules/spells/spell_types/self/nightvision.dm +++ b/code/modules/spells/spell_types/self/nightvision.dm @@ -3,6 +3,7 @@ desc = "Toggle your nightvision mode." cooldown_time = 1 SECONDS spell_requirements = NONE + check_flags = NONE /datum/action/cooldown/spell/nightvision/cast(atom/cast_on) . = ..() diff --git a/code/modules/spells/spell_types/self/summon_item.dm b/code/modules/spells/spell_types/self/summon_item.dm new file mode 100644 index 000000000000..a026ca2e93f1 --- /dev/null +++ b/code/modules/spells/spell_types/self/summon_item.dm @@ -0,0 +1,110 @@ +/datum/action/cooldown/spell/summon_item + name = "Instant Summons" + desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe." + school = SCHOOL_PSYCHIC + cooldown_time = 10 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation = "GAR YOK" + invocation_type = INVOCATION_WHISPER + spell_max_level = 0 //cannot be improved + + var/obj/marked_item + /// List of objects which will result in the spell stopping with the recursion search + var/static/list/blacklisted_summons = list(/obj/machinery/computer/cryopod = TRUE, /obj/machinery/atmospherics = TRUE, /obj/structure/disposalholder = TRUE, /obj/machinery/disposal = TRUE) + button_icon_state = "summons" + +/datum/action/cooldown/spell/summon_item/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/target = cast_on + var/list/hand_items = list(target.get_active_hand(), target.get_inactive_hand()) + var/message + + if(!marked_item) //linking item to the spell + message = "" + for(var/obj/item in hand_items) + if(is_internal_organ_brain(item)) //Yeah, sadly this doesn't work due to the organ system. + break + if(isitem(item)) + var/obj/item/I = item + if(I.item_flags & ABSTRACT) + continue + if(HAS_TRAIT(item, TRAIT_NODROP)) + message += "This feels very redundant, but you go through with it anyway.
    " + 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 && !marked_item.loc) //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 + + while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) //if it's in something you get the whole thing. + if(ismob(item_to_retrieve.loc)) //If its on someone, properly drop it + var/mob/M = item_to_retrieve.loc + + if(issilicon(M) || !M.drop_item_ground(item_to_retrieve)) //Items in silicons warp the whole silicon + var/turf/target_turf = get_turf(target) + if(!target_turf) + return + + M.visible_message(span_warning("[M] suddenly disappears!"), span_danger("A force suddenly pulls you away!")) + M.forceMove(target_turf) + M.loc.visible_message(span_caution("[M] suddenly appears!")) + item_to_retrieve = null + break + + if(ishuman(M)) //Edge case housekeeping + var/mob/living/carbon/human/human = M + if(human.remove_embedded_object(item_to_retrieve)) + to_chat(human, span_warning("The [item_to_retrieve] that was embedded into you has mysteriously vanished. How fortunate!")) + + 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() + if(is_type_in_typecache(item_to_retrieve.loc, blacklisted_summons)) + break + item_to_retrieve = item_to_retrieve.loc + if(ismodstorage(item_to_retrieve)) + var/obj/item/storage/backpack/modstorage/bag = item_to_retrieve + if(bag.source && bag.source.mod) + item_to_retrieve = bag.source.mod //Grab the modsuit. + + infinite_recursion += 1 + + if(!item_to_retrieve) + return + + var/turf/target_turf = get_turf(target) + if(!target_turf) + return + + item_to_retrieve.loc.visible_message(span_warning("The [item_to_retrieve.name] suddenly disappears!")) + playsound(target_turf, 'sound/magic/summonitems_generic.ogg', 50, TRUE) + + if(!target.put_in_active_hand(item_to_retrieve) && !target.put_in_inactive_hand(item_to_retrieve)) + item_to_retrieve.loc = target_turf + item_to_retrieve.loc.visible_message(span_caution("The [item_to_retrieve.name] suddenly appears!")) + else + item_to_retrieve.loc.visible_message(span_caution("The [item_to_retrieve.name] suddenly appears in [target]'s hand!")) + + if(message) + to_chat(target, message) diff --git a/code/modules/spells/spell_types/shapeshift/animal_form.dm b/code/modules/spells/spell_types/shapeshift/animal_form.dm new file mode 100644 index 000000000000..18899e1aae8b --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/animal_form.dm @@ -0,0 +1,13 @@ +/datum/action/cooldown/spell/shapeshift/animal_form + 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." + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + cooldown_time = 20 SECONDS + cooldown_reduction_per_rank = 3.75 SECONDS + invocation = "RAC'WA NO!" + invocation_type = INVOCATION_SHOUT + button_icon_state = "shapeshift" + var/chosen = FALSE + possible_shapes = list(/mob/living/simple_animal/mouse, + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/simple_animal/hostile/construct/armoured) diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index d08c659535e5c18eae19cbab2ed8b1c93049de85..a7748a29b39e1e3bab4912d9b2caa27b1d696773 100644 GIT binary patch literal 88454 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