mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 08:08:49 +01:00
Adds 5 new Heretic spells. Rebalances some aspects of Heretics. Refactors some spells as well, and makes it so emote spells require free hands. (#71044)
## About The Pull Request - Adds 5 new heretic spells! - For Flesh: Flesh Surgery. This spell is a touch spell that can either be used to heal your minions or extract organs from mobs. - For Void: Cone of Cold. This is a simple spell - it shoots out a cone, of cold, that freezes and damages people caught in it. - For Ash: Volcano Blast. This spell functions like Tesla Blast, but instead of electricity, it shoots out of a beam of fire that hurts to walk over. - For Blade: Realignment. Think of this like "Fleshmend but for stuns / stamcrit". It rapidly regenerates stamina damage and reduces stuns, while making you a pacifist. It can also be cast in rapid succession, but this will increase the cooldown. - For Rust: Rust Construction. Point at a rusted tile, and a wall will be raised where it was instantly. This even damages people and throws them aside - Or, if on a multi-z map, can lift up. - Number of influences has increased. - 5 at 1 heretic - 9 at 2 - 12 at 3 - 14 at 4 - 15 at 5 - 16 at 6, and so on - Heretics are given a 5th sacrifice target, selected randomly. On average an additional sacrifice is needed for their objectives. - Being sacrificed grants you a permanent phobia of the supernatural. Phobia of the supernatural has been expanded to cover heretic items and mobs. - The equation for offhand damage of blade heretics was tweaked. Actual result unchanged, it's just more resilient to future changes now. - Touch spells were refactored a bit, and overall expanded to be easier to use - Charged spells were added, and charged beam spells. Tesla blast uses this. - Cone spells were refactored to be easier to setup. - Jaunting will now hide your runechat when it triggers, to make it less easy to follow. - Heretic Ghouls now take less stamina damage based on how low their health pool is. - Emote based spells now require hands to be unblocked to be cast, like mime spells. - Yes this gets rid of handcuffed invisible walls... Not 100% on this, but I figured it's good for consistency? Open to discussion ## Why It's Good For The Game A lotta feedback has passed through about heretic and it's time to address some of it - Problem: Not enough cool flash spells. Makes Focus not worth it. - Solution: Adds some more spells to encourage focus use. - Problem: Sacrifice targets being too willing or not harmed enough - Solution: A permanent trauma. - Problem: Not enough ways to power up. - Solution: Adding more influences around, though I think there should be more variety in knowledge rituals as well. - Problem: Ash Passage sucks - Solution: Makes it a smidge better to stay hidden with it. - Problem: Heretic Ghouls get one hit by batons - Solution: Stamina modifier should put them on par with unmodified humans. ## Changelog 🆑 Melbert add: Added five new heretic spells, one for each path. They come after the Ritual of Knowledge. add: Cone of Cold, for Void heretics. Shoots out a freezing chill in a cone which deal damage and freezes. add: Flesh Surgery for Flesh heretics. A touch spell which can either heal minions or be used on mobs to extract organs without surgery. add: Volcano Blast for Ash heretics. A beam spell, like Tesla Blast, which fires out a beam of fire that bounces between people. add: Realignment for Blade heretics. Fleshmend, but for stuns and stamina damage. Makes you a pacifist, but rapidly regenerates stamina. add: Rust Construction for Rust heretics. Places a wall of rust on the target rusted flooring. Can even be used to ascend z-levels! balance: Nerfed the cooldown of Cleave slightly, buffed the cooldown of Lesser Cleave slightly. balance: Slightly more influences will spawn on the station per heretic. balance: Heretics require an additional sacrifice on average for ascension, but are given a fifth sacrifice target (randomly selected). balance: Being sacrificed by a heretic now gives you a permanent phobia of spooky things, including heretic mobs and items. balance: Heretic ghouls now take reduced stamina damage, depending on how small their health pool is. balance: Using Jaunts will conceal your runechat for their duration. balance: Spells which require emoting (Mime spells) require your hands not be blocked to use. refactor: Touch Spells were improved a bit. Added some new template spells - Charged spells, and Charged beam spells. fix: Fixes a runtime from losing heretic. /🆑
This commit is contained in:
@@ -181,10 +181,12 @@
|
||||
if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
|
||||
var/mob/living/carbon/human/human_owner = owner
|
||||
if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES))
|
||||
to_chat(owner, span_warning("You don't feel strong enough without your robe!"))
|
||||
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))
|
||||
to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
|
||||
if(feedback)
|
||||
to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
|
||||
return FALSE
|
||||
|
||||
else
|
||||
@@ -208,6 +210,7 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -384,7 +387,7 @@
|
||||
return FALSE
|
||||
|
||||
spell_level++
|
||||
cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0)
|
||||
cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0.25 SECONDS) // 0 second CD starts to break things.
|
||||
update_spell_name()
|
||||
return TRUE
|
||||
|
||||
@@ -400,7 +403,11 @@
|
||||
return FALSE
|
||||
|
||||
spell_level--
|
||||
cooldown_time = min(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time))
|
||||
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))
|
||||
|
||||
update_spell_name()
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* ## 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
|
||||
/// What message do we display when we start chanelling?
|
||||
var/channel_message
|
||||
/// Whether we're currently channelling / charging the spell
|
||||
var/currently_channeling = FALSE
|
||||
/// How long it takes to channel the spell.
|
||||
var/channel_time = 10 SECONDS
|
||||
|
||||
// Overlay optional, applied when we start channelling
|
||||
/// What icon should we use for our overlay
|
||||
var/charge_overlay_icon
|
||||
/// What icon state should we use for our overlay
|
||||
var/charge_overlay_state
|
||||
/// The actual appearance / our overlay. Don't mess with this
|
||||
var/mutable_appearance/charge_overlay_instance
|
||||
|
||||
// Sound optional, played when we start chanelling
|
||||
/// What soundpath should we play when we start chanelling
|
||||
var/charge_sound
|
||||
/// The actual sound we generate, don't mess with this
|
||||
var/sound/charge_sound_instance
|
||||
|
||||
/datum/action/cooldown/spell/charged/New(Target, original)
|
||||
. = ..()
|
||||
if(!channel_message)
|
||||
channel_message = span_notice("You start chanelling [src]...")
|
||||
|
||||
if(charge_sound)
|
||||
charge_sound_instance = sound(charge_sound, channel = CHANNEL_CHARGED_SPELL)
|
||||
|
||||
if(charge_overlay_icon && charge_overlay_state)
|
||||
charge_overlay_instance = mutable_appearance(charge_overlay_icon, charge_overlay_state, EFFECTS_LAYER)
|
||||
|
||||
/datum/action/cooldown/spell/charged/Destroy()
|
||||
if(owner)
|
||||
stop_channel_effect(owner)
|
||||
|
||||
charge_overlay_instance = null
|
||||
charge_sound_instance = null
|
||||
return ..()
|
||||
|
||||
/datum/action/cooldown/spell/charged/Remove(mob/living/remove_from)
|
||||
stop_channel_effect(remove_from)
|
||||
return ..()
|
||||
|
||||
/datum/action/cooldown/spell/charged/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
|
||||
UpdateButtons(status_only = TRUE)
|
||||
if(!do_after(cast_on, channel_time, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM)))
|
||||
stop_channel_effect(cast_on)
|
||||
return . | SPELL_CANCEL_CAST
|
||||
|
||||
/datum/action/cooldown/spell/charged/cast(atom/cast_on)
|
||||
. = ..()
|
||||
stop_channel_effect(cast_on)
|
||||
|
||||
/datum/action/cooldown/spell/charged/set_statpanel_format()
|
||||
. = ..()
|
||||
if(!islist(.))
|
||||
return
|
||||
|
||||
if(currently_channeling)
|
||||
.[PANEL_DISPLAY_STATUS] = "CHANNELING"
|
||||
|
||||
/// Interrupts the chanelling effect, removing any overlay or sound playing (for the passed mob)
|
||||
/datum/action/cooldown/spell/charged/proc/stop_channel_effect(mob/for_who)
|
||||
if(charge_overlay_instance)
|
||||
for_who.cut_overlay(charge_overlay_instance)
|
||||
|
||||
if(charge_sound_instance)
|
||||
for_who.stop_sound_channel(CHANNEL_CHARGED_SPELL)
|
||||
// Play a null sound in to cancel the sound playing, because byond
|
||||
playsound(for_who, sound(null, repeat = 0, channel = CHANNEL_CHARGED_SPELL), 50, FALSE)
|
||||
|
||||
currently_channeling = FALSE
|
||||
UpdateButtons(status_only = TRUE)
|
||||
|
||||
/**
|
||||
* ### 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)
|
||||
@@ -0,0 +1,62 @@
|
||||
/datum/action/cooldown/spell/charged/beam/tesla
|
||||
name = "Tesla Blast"
|
||||
desc = "Charge up a tesla arc and release it at random nearby targets! \
|
||||
You can move freely while it charges. The arc jumps between targets and can knock them down."
|
||||
button_icon_state = "lightning"
|
||||
sound = 'sound/magic/lightningbolt.ogg'
|
||||
|
||||
cooldown_time = 30 SECONDS
|
||||
cooldown_reduction_per_rank = 6.75 SECONDS
|
||||
|
||||
invocation = "UN'LTD P'WAH!"
|
||||
invocation_type = INVOCATION_SHOUT
|
||||
school = SCHOOL_EVOCATION
|
||||
|
||||
channel_message = span_notice("You start gathering power...")
|
||||
charge_overlay_icon = 'icons/effects/effects.dmi'
|
||||
charge_overlay_state = "electricity"
|
||||
charge_sound = 'sound/magic/lightning_chargeup.ogg'
|
||||
target_radius = 7
|
||||
max_beam_bounces = 5
|
||||
|
||||
/// How much energy / damage does the initial bounce
|
||||
var/initial_energy = 30
|
||||
/// How much energy / damage is lost per bounce
|
||||
var/energy_lost_per_bounce = 5
|
||||
|
||||
/// Zaps a target, the bolt originating from origin.
|
||||
/datum/action/cooldown/spell/charged/beam/tesla/send_beam(atom/origin, mob/living/carbon/to_beam, bolt_energy = 30, bounces = 5)
|
||||
origin.Beam(to_beam, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS)
|
||||
playsound(get_turf(to_beam), 'sound/magic/lightningshock.ogg', 50, TRUE, -1)
|
||||
|
||||
if(to_beam.can_block_magic(antimagic_flags))
|
||||
to_beam.visible_message(
|
||||
span_warning("[to_beam] absorbs the spell, remaining unharmed!"),
|
||||
span_userdanger("You absorb the spell, remaining unharmed!"),
|
||||
)
|
||||
|
||||
else
|
||||
to_beam.electrocute_act(bolt_energy, "Lightning Bolt", 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)
|
||||
@@ -70,39 +70,50 @@
|
||||
left_dir = SOUTH
|
||||
right_dir = NORTH
|
||||
|
||||
for(var/i in 1 to cone_levels)
|
||||
// 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
|
||||
if(i != 1)
|
||||
left_turf = get_step(turf_to_use, left_dir)
|
||||
level_turfs += left_turf
|
||||
right_turf = get_step(turf_to_use, right_dir)
|
||||
level_turfs += right_turf
|
||||
for(var/left_i in 1 to i -calculate_cone_shape(i))
|
||||
if(left_turf.density && respect_density)
|
||||
// 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
|
||||
for(var/right_i in 1 to i -calculate_cone_shape(i))
|
||||
if(right_turf.density && respect_density)
|
||||
// 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(i == cone_levels)
|
||||
continue
|
||||
if(turf_to_use.density && respect_density)
|
||||
|
||||
// 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
|
||||
|
||||
///This proc adjusts the cones width depending on the level.
|
||||
/**
|
||||
* 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)
|
||||
var/end_taper_start = round(cone_levels * 0.8)
|
||||
if(current_level > end_taper_start)
|
||||
return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula.
|
||||
else
|
||||
return 2
|
||||
// Default formula: (1 (innate) -> 3 -> 5 -> 5 -> 7 -> 7 -> 9 -> 9 -> ...)
|
||||
return current_level + (current_level % 2) + 1
|
||||
|
||||
/**
|
||||
* ### Staggered Cone
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/datum/action/cooldown/spell/cone/staggered/cone_of_cold
|
||||
name = "Cone of Cold"
|
||||
desc = "Shoots out a freezing cone in front of you."
|
||||
|
||||
school = SCHOOL_EVOCATION
|
||||
cooldown_time = 30 SECONDS
|
||||
cooldown_reduction_per_rank = 4 SECONDS
|
||||
|
||||
invocation = "ISAGE!" // What killed the dinosaurs? THE ICE AGE
|
||||
invocation_type = INVOCATION_SHOUT
|
||||
|
||||
cone_levels = 4
|
||||
respect_density = TRUE
|
||||
delay_between_level = 0.05 SECONDS
|
||||
|
||||
/// What flags do we pass to MakeSlippery when affecting turfs?
|
||||
/// null / NONE / TURF_DRY means the turf is unaffected
|
||||
var/turf_freeze_type = TURF_WET_PERMAFROST
|
||||
/// How long do turfs remain slippery / frozen for?
|
||||
/// 0 seconds means the turf is unaffected, INFINITY means it's made perma-wet
|
||||
var/unfreeze_turf_duration = 45 SECONDS
|
||||
|
||||
/// What status effect do we apply when affecting mobs?
|
||||
/// null means no status effect is applied
|
||||
var/datum/status_effect/frozen_status_effect_path = /datum/status_effect/freon/lasting
|
||||
/// How long do mobs remain frozen for?
|
||||
/// 0 seconds means no status effect is applied, INFINITY means infinite duration (or default duration of the status effect)
|
||||
var/unfreeze_mob_duration = 20 SECONDS
|
||||
/// How much brute do we apply on freeze?
|
||||
var/on_freeze_brute_damage = 10
|
||||
/// How much burn do we apply on freeze?
|
||||
var/on_freeze_burn_damage = 20
|
||||
|
||||
/// How long do objects remain frozen for?
|
||||
/// 0 seconds mean no objects are frozen, INFINITY means infinite duration freeze
|
||||
var/unfreeze_object_duration = 20 SECONDS
|
||||
|
||||
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/do_turf_cone_effect(turf/target_turf, atom/caster, level)
|
||||
if(!turf_freeze_type || unfreeze_turf_duration <= 0 SECONDS) // 0 duration = don't apply the slip
|
||||
return
|
||||
if(!isopenturf(target_turf))
|
||||
return
|
||||
var/turf/open/frozen_floor = target_turf
|
||||
frozen_floor.MakeSlippery(turf_freeze_type, unfreeze_turf_duration, permanent = (unfreeze_turf_duration == INFINITY))
|
||||
|
||||
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/do_mob_cone_effect(mob/living/target_mob, atom/caster, level)
|
||||
if(target_mob.can_block_magic(antimagic_flags) || target_mob == caster)
|
||||
return
|
||||
|
||||
if(ispath(frozen_status_effect_path) && unfreeze_mob_duration > 0 SECONDS) // 0 duration = don't apply the status effect
|
||||
var/datum/status_effect/freeze = target_mob.apply_status_effect(frozen_status_effect_path)
|
||||
if(unfreeze_mob_duration != INFINITY)
|
||||
freeze.duration = world.time + unfreeze_mob_duration
|
||||
|
||||
if(on_freeze_brute_damage || on_freeze_burn_damage)
|
||||
target_mob.take_overall_damage(on_freeze_brute_damage, on_freeze_burn_damage)
|
||||
|
||||
to_chat(target_mob, span_userdanger("You feel a bitter cold!"))
|
||||
|
||||
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/do_obj_cone_effect(obj/target_obj, atom/caster, level)
|
||||
if(unfreeze_object_duration <= 0 SECONDS) // 0 duration = don't apply a freeze
|
||||
return
|
||||
if(!target_obj.freeze())
|
||||
return
|
||||
if(unfreeze_object_duration == INFINITY) // Infinity duration = don't set an unfreeze timer
|
||||
return
|
||||
addtimer(CALLBACK(target_obj, TYPE_PROC_REF(/obj/, unfreeze)), unfreeze_object_duration)
|
||||
@@ -4,6 +4,7 @@
|
||||
background_icon_state = "bg_mime"
|
||||
icon_icon = 'icons/mob/actions/actions_mime.dmi'
|
||||
button_icon_state = "invisible_chair"
|
||||
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
|
||||
panel = "Mime"
|
||||
sound = null
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
background_icon_state = "bg_mime"
|
||||
icon_icon = 'icons/mob/actions/actions_mime.dmi'
|
||||
button_icon_state = "invisible_wall"
|
||||
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
|
||||
panel = "Mime"
|
||||
sound = null
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
background_icon_state = "bg_mime"
|
||||
icon_icon = 'icons/mob/actions/actions_mime.dmi'
|
||||
button_icon_state = "invisible_box"
|
||||
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
|
||||
panel = "Mime"
|
||||
sound = null
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
/// What dummy mob type do we put jaunters in on jaunt?
|
||||
var/jaunt_type = /obj/effect/dummy/phased_mob
|
||||
|
||||
/datum/action/cooldown/spell/jaunt/before_cast(atom/cast_on)
|
||||
return ..() | SPELL_NO_FEEDBACK // Don't do the feedback until after we're jaunting
|
||||
|
||||
/datum/action/cooldown/spell/jaunt/can_cast_spell(feedback = TRUE)
|
||||
. = ..()
|
||||
if(!.)
|
||||
@@ -49,10 +52,16 @@
|
||||
* Returns the holder mob that was created
|
||||
*/
|
||||
/datum/action/cooldown/spell/jaunt/proc/enter_jaunt(mob/living/jaunter, turf/loc_override)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(loc_override || get_turf(jaunter), jaunter)
|
||||
RegisterSignal(jaunt, COMSIG_MOB_EJECTED_FROM_JAUNT, PROC_REF(on_jaunt_exited))
|
||||
spell_requirements |= SPELL_CASTABLE_WHILE_PHASED
|
||||
ADD_TRAIT(jaunter, TRAIT_MAGICALLY_PHASED, REF(src))
|
||||
ADD_TRAIT(jaunter, TRAIT_RUNECHAT_HIDDEN, REF(src))
|
||||
// Don't do the feedback until we have runechat hidden.
|
||||
// Otherwise the text will follow the jaunt holder, which reveals where our caster is travelling.
|
||||
spell_feedback()
|
||||
|
||||
// This needs to happen at the end, after all the traits and stuff is handled
|
||||
SEND_SIGNAL(jaunter, COMSIG_MOB_ENTER_JAUNT, src, jaunt)
|
||||
@@ -68,6 +77,8 @@
|
||||
* 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
|
||||
@@ -92,13 +103,10 @@
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
spell_requirements &= ~SPELL_CASTABLE_WHILE_PHASED
|
||||
REMOVE_TRAIT(unjaunter, TRAIT_MAGICALLY_PHASED, REF(src))
|
||||
REMOVE_TRAIT(unjaunter, TRAIT_RUNECHAT_HIDDEN, REF(src))
|
||||
// This needs to happen at the end, after all the traits and stuff is handled
|
||||
SEND_SIGNAL(unjaunter, COMSIG_MOB_AFTER_EXIT_JAUNT, src)
|
||||
|
||||
/// Simple helper to check if the passed mob is currently jaunting or not
|
||||
/datum/action/cooldown/spell/jaunt/proc/is_jaunting(mob/living/user)
|
||||
return istype(user.loc, /obj/effect/dummy/phased_mob)
|
||||
|
||||
/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
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/// List of valid exit points
|
||||
var/list/exit_point_list
|
||||
|
||||
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/enter_jaunt(mob/living/jaunter)
|
||||
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/enter_jaunt(mob/living/jaunter, turf/loc_override)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
@@ -87,12 +87,17 @@
|
||||
to_chat(owner, span_warning("You cannot cast [src] on yourself!"))
|
||||
return FALSE
|
||||
|
||||
if(get_dist(owner, cast_on) > cast_range)
|
||||
to_chat(owner, span_warning("[cast_on.p_theyre(TRUE)] too far away!"))
|
||||
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
|
||||
*
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
background_icon_state = "bg_mime"
|
||||
icon_icon = 'icons/mob/actions/actions_mime.dmi'
|
||||
button_icon_state = "finger_guns0"
|
||||
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
|
||||
panel = "Mime"
|
||||
sound = null
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
background_icon_state = "bg_mime"
|
||||
icon_icon = 'icons/mob/actions/actions_mime.dmi'
|
||||
button_icon_state = "invisible_blockade"
|
||||
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
|
||||
panel = "Mime"
|
||||
sound = null
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
/datum/action/cooldown/spell/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"
|
||||
|
||||
cooldown_time = 30 SECONDS
|
||||
cooldown_reduction_per_rank = 6.75 SECONDS
|
||||
|
||||
invocation = "UN'LTD P'WAH!"
|
||||
invocation_type = INVOCATION_SHOUT
|
||||
school = SCHOOL_EVOCATION
|
||||
|
||||
/// Whether we're currently channelling a tesla blast or not
|
||||
var/currently_channeling = FALSE
|
||||
/// How long it takes to channel the zap.
|
||||
var/channel_time = 10 SECONDS
|
||||
/// The radius around (either the caster or people shocked) to which the tesla blast can reach
|
||||
var/shock_radius = 7
|
||||
/// The halo that appears around the caster while charging the spell
|
||||
var/static/mutable_appearance/halo
|
||||
/// The sound played while charging the spell
|
||||
/// Quote: "the only way i can think of to stop a sound, thank MSO for the idea."
|
||||
var/sound/charge_sound
|
||||
|
||||
/datum/action/cooldown/spell/tesla/Remove(mob/living/remove_from)
|
||||
reset_tesla(remove_from)
|
||||
return ..()
|
||||
|
||||
/datum/action/cooldown/spell/tesla/set_statpanel_format()
|
||||
. = ..()
|
||||
if(!islist(.))
|
||||
return
|
||||
|
||||
if(currently_channeling)
|
||||
.[PANEL_DISPLAY_STATUS] = "CHANNELING"
|
||||
|
||||
/datum/action/cooldown/spell/tesla/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/tesla/before_cast(atom/cast_on)
|
||||
. = ..()
|
||||
if(. & SPELL_CANCEL_CAST)
|
||||
return
|
||||
|
||||
to_chat(cast_on, span_notice("You start gathering power..."))
|
||||
charge_sound = new /sound('sound/magic/lightning_chargeup.ogg', channel = 7)
|
||||
halo ||= mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER)
|
||||
cast_on.add_overlay(halo)
|
||||
playsound(get_turf(cast_on), charge_sound, 50, FALSE)
|
||||
|
||||
currently_channeling = TRUE
|
||||
if(!do_after(cast_on, channel_time, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM)))
|
||||
reset_tesla(cast_on)
|
||||
return . | SPELL_CANCEL_CAST
|
||||
|
||||
/datum/action/cooldown/spell/tesla/reset_spell_cooldown()
|
||||
reset_tesla(owner)
|
||||
return ..()
|
||||
|
||||
/// Resets the tesla effect.
|
||||
/datum/action/cooldown/spell/tesla/proc/reset_tesla(atom/to_reset)
|
||||
to_reset.cut_overlay(halo)
|
||||
currently_channeling = FALSE
|
||||
|
||||
/datum/action/cooldown/spell/tesla/cast(atom/cast_on)
|
||||
. = ..()
|
||||
|
||||
// byond, why you suck?
|
||||
charge_sound = sound(null, repeat = 0, wait = 1, channel = charge_sound.channel)
|
||||
// Sorry MrPerson, but the other ways just didn't do it the way i needed to work, this is the only way.
|
||||
playsound(get_turf(cast_on), charge_sound, 50, FALSE)
|
||||
|
||||
var/mob/living/carbon/to_zap_first = get_target(cast_on)
|
||||
if(QDELETED(to_zap_first))
|
||||
cast_on.balloon_alert(cast_on, "no targets nearby!")
|
||||
reset_spell_cooldown()
|
||||
return FALSE
|
||||
|
||||
playsound(get_turf(cast_on), 'sound/magic/lightningbolt.ogg', 50, TRUE)
|
||||
zap_target(cast_on, to_zap_first)
|
||||
reset_tesla(cast_on)
|
||||
|
||||
/// Zaps a target, the bolt originating from origin.
|
||||
/datum/action/cooldown/spell/tesla/proc/zap_target(atom/origin, mob/living/carbon/to_zap, bolt_energy = 30, bounces = 5)
|
||||
origin.Beam(to_zap, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS)
|
||||
playsound(get_turf(to_zap), 'sound/magic/lightningshock.ogg', 50, TRUE, -1)
|
||||
|
||||
if(to_zap.can_block_magic(antimagic_flags))
|
||||
to_zap.visible_message(
|
||||
span_warning("[to_zap] absorbs the spell, remaining unharmed!"),
|
||||
span_userdanger("You absorb the spell, remaining unharmed!"),
|
||||
)
|
||||
|
||||
else
|
||||
to_zap.electrocute_act(bolt_energy, "Lightning Bolt", flags = SHOCK_NOGLOVES)
|
||||
|
||||
if(bounces >= 1)
|
||||
var/mob/living/carbon/to_zap_next = get_target(to_zap)
|
||||
if(!QDELETED(to_zap_next))
|
||||
zap_target(to_zap, to_zap_next, max((bolt_energy - 5), 5), bounces - 1)
|
||||
|
||||
/// Get a target in view of us to zap next. Returns a carbon, or null if none were found.
|
||||
/datum/action/cooldown/spell/tesla/proc/get_target(atom/center)
|
||||
var/list/possibles = list()
|
||||
for(var/mob/living/carbon/to_check in view(shock_radius, center))
|
||||
if(to_check == center || to_check == owner)
|
||||
continue
|
||||
if(!length(get_path_to(center, to_check, max_distance = shock_radius, simulated_only = FALSE)))
|
||||
continue
|
||||
|
||||
possibles += to_check
|
||||
|
||||
if(!length(possibles))
|
||||
return null
|
||||
|
||||
return pick(possibles)
|
||||
@@ -1,3 +1,23 @@
|
||||
/**
|
||||
* ## 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'
|
||||
@@ -12,6 +32,8 @@
|
||||
var/draw_message = span_notice("You channel the power of the spell to your hand.")
|
||||
/// The message displayed upon willingly dropping / deleting / cancelling the touch hand before using it
|
||||
var/drop_message = span_notice("You draw the power out of your hand.")
|
||||
/// If TRUE, the caster can willingly hit themselves with the hand
|
||||
var/can_cast_on_self = FALSE
|
||||
|
||||
/datum/action/cooldown/spell/touch/Destroy()
|
||||
// If we have an owner, the hand is cleaned up in Remove(), which Destroy() calls.
|
||||
@@ -23,6 +45,10 @@
|
||||
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/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
|
||||
. = ..()
|
||||
if(!button)
|
||||
@@ -42,6 +68,9 @@
|
||||
. = ..()
|
||||
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
|
||||
@@ -49,8 +78,9 @@
|
||||
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 iscarbon(cast_on)
|
||||
return isliving(cast_on)
|
||||
|
||||
/**
|
||||
* Creates a new hand_path hand and equips it to the caster.
|
||||
@@ -59,6 +89,8 @@
|
||||
* Otherwise, registers signals and returns TRUE.
|
||||
*/
|
||||
/datum/action/cooldown/spell/touch/proc/create_hand(mob/living/carbon/cast_on)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
var/obj/item/melee/touch_attack/new_hand = new hand_path(cast_on, src)
|
||||
if(!cast_on.put_in_hands(new_hand, del_on_fail = TRUE))
|
||||
reset_spell_cooldown()
|
||||
@@ -69,10 +101,7 @@
|
||||
return FALSE
|
||||
|
||||
attached_hand = new_hand
|
||||
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_hand_hit))
|
||||
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_hand_hit))
|
||||
RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted))
|
||||
RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped))
|
||||
register_hand_signals()
|
||||
to_chat(cast_on, draw_message)
|
||||
return TRUE
|
||||
|
||||
@@ -83,8 +112,10 @@
|
||||
* If reset_cooldown_after is FALSE, we will instead just start the spell's cooldown
|
||||
*/
|
||||
/datum/action/cooldown/spell/touch/proc/remove_hand(mob/living/hand_owner, reset_cooldown_after = FALSE)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
if(!QDELETED(attached_hand))
|
||||
UnregisterSignal(attached_hand, list(COMSIG_ITEM_AFTERATTACK, COMSIG_ITEM_AFTERATTACK_SECONDARY, COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED))
|
||||
unregister_hand_signals()
|
||||
hand_owner?.temporarilyRemoveItemFromInventory(attached_hand)
|
||||
QDEL_NULL(attached_hand)
|
||||
|
||||
@@ -95,6 +126,26 @@
|
||||
else
|
||||
StartCooldown()
|
||||
|
||||
/// 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_AFTERATTACK, PROC_REF(on_hand_hit))
|
||||
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_hand_hit))
|
||||
RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped))
|
||||
RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted))
|
||||
|
||||
/// 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_AFTERATTACK,
|
||||
COMSIG_ITEM_AFTERATTACK_SECONDARY,
|
||||
COMSIG_ITEM_DROPPED,
|
||||
COMSIG_PARENT_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
|
||||
@@ -114,12 +165,11 @@
|
||||
*/
|
||||
/datum/action/cooldown/spell/touch/proc/on_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters)
|
||||
SIGNAL_HANDLER
|
||||
SHOULD_NOT_OVERRIDE(TRUE) // DEFINITELY don't put effects here, put them in cast_on_hand_hit
|
||||
|
||||
if(!proximity_flag)
|
||||
return
|
||||
if(victim == caster)
|
||||
return
|
||||
if(!can_cast_spell(feedback = FALSE))
|
||||
if(!can_hit_with_hand(victim, caster))
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(do_hand_hit), source, victim, caster)
|
||||
@@ -131,22 +181,43 @@
|
||||
*/
|
||||
/datum/action/cooldown/spell/touch/proc/on_secondary_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters)
|
||||
SIGNAL_HANDLER
|
||||
SHOULD_NOT_OVERRIDE(TRUE) // DEFINITELY don't put effects here, put them in cast_on_secondary_hand_hit
|
||||
|
||||
if(!proximity_flag)
|
||||
return
|
||||
if(victim == caster)
|
||||
return
|
||||
if(!can_cast_spell(feedback = FALSE))
|
||||
if(!can_hit_with_hand(victim, caster))
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(do_secondary_hand_hit), source, victim, caster)
|
||||
return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/// Checks if the passed victim can be cast on by the caster.
|
||||
/datum/action/cooldown/spell/touch/proc/can_hit_with_hand(atom/victim, mob/caster)
|
||||
if(!can_cast_on_self && victim == caster)
|
||||
return FALSE
|
||||
if(!is_valid_target(victim))
|
||||
return FALSE
|
||||
if(!can_cast_spell(feedback = TRUE))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Calls cast_on_hand_hit() from the caster onto the victim.
|
||||
* It's worth noting that victim will be guaranteed to be whatever checks are implemented in is_valid_target by this point.
|
||||
*
|
||||
* Implements checks for antimagic.
|
||||
*/
|
||||
/datum/action/cooldown/spell/touch/proc/do_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
|
||||
SHOULD_NOT_OVERRIDE(TRUE) // Don't put effects here, put them in cast_on_hand_hit
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_SPELL_TOUCH_HAND_HIT, victim, caster, hand)
|
||||
if(!cast_on_hand_hit(hand, victim, caster))
|
||||
|
||||
var/mob/mob_victim = victim
|
||||
if(istype(mob_victim) && mob_victim.can_block_magic(antimagic_flags))
|
||||
on_antimagic_triggered(hand, victim, caster)
|
||||
|
||||
else if(!cast_on_hand_hit(hand, victim, caster))
|
||||
return
|
||||
|
||||
log_combat(caster, victim, "cast the touch spell [name] on", hand)
|
||||
@@ -155,8 +226,13 @@
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -216,6 +292,22 @@
|
||||
|
||||
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?"
|
||||
@@ -224,7 +316,7 @@
|
||||
righthand_file = 'icons/mob/inhands/items/touchspell_righthand.dmi'
|
||||
icon_state = "latexballon"
|
||||
inhand_icon_state = null
|
||||
item_flags = NEEDS_PERMIT | ABSTRACT
|
||||
item_flags = NEEDS_PERMIT | ABSTRACT | HAND_ITEM
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
force = 0
|
||||
throwforce = 0
|
||||
@@ -243,7 +335,7 @@
|
||||
if(!iscarbon(user)) //Look ma, no hands
|
||||
return TRUE
|
||||
if(!(user.mobility_flags & MOBILITY_USE))
|
||||
to_chat(user, span_warning("You can't reach out!"))
|
||||
user.balloon_alert(user, "can't reach out!")
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
@@ -254,7 +346,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/mansus_fist/proc/remove_hand_with_no_refund(mob/holder)
|
||||
/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)
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
|
||||
hand_path = /obj/item/melee/touch_attack/duffelbag
|
||||
|
||||
/datum/action/cooldown/spell/touch/duffelbag/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
|
||||
if(!iscarbon(victim))
|
||||
return FALSE
|
||||
|
||||
var/mob/living/carbon/duffel_victim = victim
|
||||
/// Some meme "elaborate backstories" to use.
|
||||
var/static/list/elaborate_backstory = list(
|
||||
"spacewar origin story",
|
||||
"military background",
|
||||
@@ -28,52 +24,57 @@
|
||||
"upbringing on the space farm",
|
||||
"fond memories with your buddy Keith",
|
||||
)
|
||||
if(duffel_victim.can_block_magic(antimagic_flags))
|
||||
to_chat(caster, span_warning("The spell can't seem to affect [duffel_victim]!"))
|
||||
to_chat(duffel_victim, span_warning("You really don't feel like talking about your [pick(elaborate_backstory)] with complete strangers today."))
|
||||
return TRUE
|
||||
|
||||
/datum/action/cooldown/spell/touch/duffelbag/is_valid_target(atom/cast_on)
|
||||
return iscarbon(cast_on)
|
||||
|
||||
/datum/action/cooldown/spell/touch/duffelbag/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster)
|
||||
to_chat(caster, span_warning("The spell can't seem to affect [victim]!"))
|
||||
to_chat(victim, span_warning("You really don't feel like talking about your [pick(elaborate_backstory)] with complete strangers today."))
|
||||
|
||||
/datum/action/cooldown/spell/touch/duffelbag/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster)
|
||||
|
||||
// To get it started, stun and knockdown the person being hit
|
||||
duffel_victim.flash_act()
|
||||
duffel_victim.Immobilize(5 SECONDS)
|
||||
duffel_victim.apply_damage(80, STAMINA)
|
||||
duffel_victim.Knockdown(5 SECONDS)
|
||||
victim.flash_act()
|
||||
victim.Immobilize(5 SECONDS)
|
||||
victim.apply_damage(80, STAMINA)
|
||||
victim.Knockdown(5 SECONDS)
|
||||
|
||||
// If someone's already cursed, don't try to give them another
|
||||
if(HAS_TRAIT(duffel_victim, TRAIT_DUFFEL_CURSE_PROOF))
|
||||
to_chat(caster, span_warning("The burden of [duffel_victim]'s duffel bag becomes too much, shoving them to the floor!"))
|
||||
to_chat(duffel_victim, span_warning("The weight of this bag becomes overburdening!"))
|
||||
if(HAS_TRAIT(victim, TRAIT_DUFFEL_CURSE_PROOF))
|
||||
to_chat(caster, span_warning("The burden of [victim]'s duffel bag becomes too much, shoving them to the floor!"))
|
||||
to_chat(victim, span_warning("The weight of this bag becomes overburdening!"))
|
||||
return TRUE
|
||||
|
||||
// However if they're uncursed, they're fresh for getting a cursed bag
|
||||
var/obj/item/storage/backpack/duffelbag/cursed/conjured_duffel = new get_turf(victim)
|
||||
duffel_victim.visible_message(
|
||||
span_danger("A growling duffel bag appears on [duffel_victim]!"),
|
||||
victim.visible_message(
|
||||
span_danger("A growling duffel bag appears on [victim]!"),
|
||||
span_danger("You feel something attaching itself to you, and a strong desire to discuss your [pick(elaborate_backstory)] at length!"),
|
||||
)
|
||||
|
||||
// This duffelbag is now cuuuurrrsseed! Equip it on them
|
||||
ADD_TRAIT(conjured_duffel, TRAIT_DUFFEL_CURSE_PROOF, CURSED_ITEM_TRAIT(conjured_duffel.name))
|
||||
conjured_duffel.pickup(duffel_victim)
|
||||
conjured_duffel.forceMove(duffel_victim)
|
||||
conjured_duffel.pickup(victim)
|
||||
conjured_duffel.forceMove(victim)
|
||||
|
||||
// Put it on their back first
|
||||
if(duffel_victim.dropItemToGround(duffel_victim.back))
|
||||
duffel_victim.equip_to_slot_if_possible(conjured_duffel, ITEM_SLOT_BACK, TRUE, TRUE)
|
||||
if(victim.dropItemToGround(victim.back))
|
||||
victim.equip_to_slot_if_possible(conjured_duffel, ITEM_SLOT_BACK, TRUE, TRUE)
|
||||
return TRUE
|
||||
|
||||
// If the back equip failed, put it in their hands first
|
||||
if(duffel_victim.put_in_hands(conjured_duffel))
|
||||
if(victim.put_in_hands(conjured_duffel))
|
||||
return TRUE
|
||||
|
||||
// If they had no empty hands, try to put it in their inactive hand first
|
||||
duffel_victim.dropItemToGround(duffel_victim.get_inactive_held_item())
|
||||
if(duffel_victim.put_in_hands(conjured_duffel))
|
||||
victim.dropItemToGround(victim.get_inactive_held_item())
|
||||
if(victim.put_in_hands(conjured_duffel))
|
||||
return TRUE
|
||||
|
||||
// If their inactive hand couldn't be emptied or found, put it in their active hand
|
||||
duffel_victim.dropItemToGround(duffel_victim.get_active_held_item())
|
||||
if(duffel_victim.put_in_hands(conjured_duffel))
|
||||
victim.dropItemToGround(victim.get_active_held_item())
|
||||
if(victim.put_in_hands(conjured_duffel))
|
||||
return TRUE
|
||||
|
||||
// Well, we failed to give them the duffel bag,
|
||||
|
||||
@@ -12,14 +12,13 @@
|
||||
|
||||
hand_path = /obj/item/melee/touch_attack/flesh_to_stone
|
||||
|
||||
/datum/action/cooldown/spell/touch/flesh_to_stone/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
|
||||
if(!isliving(victim))
|
||||
return FALSE
|
||||
/datum/action/cooldown/spell/touch/flesh_to_stone/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
|
||||
to_chat(caster, span_warning("The spell can't seem to affect [victim]!"))
|
||||
to_chat(victim, span_warning("You feel your flesh turn to stone for a moment, then revert back!"))
|
||||
|
||||
/datum/action/cooldown/spell/touch/flesh_to_stone/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
|
||||
var/mob/living/living_victim = victim
|
||||
if(living_victim.can_block_magic(antimagic_flags))
|
||||
to_chat(caster, span_warning("The spell can't seem to affect [victim]!"))
|
||||
to_chat(victim, span_warning("You feel your flesh turn to stone for a moment, then revert back!"))
|
||||
return TRUE
|
||||
|
||||
living_victim.Stun(4 SECONDS)
|
||||
|
||||
@@ -14,26 +14,28 @@
|
||||
|
||||
hand_path = /obj/item/melee/touch_attack/smite
|
||||
|
||||
/datum/action/cooldown/spell/touch/smite/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
|
||||
if(!isliving(victim))
|
||||
return FALSE
|
||||
|
||||
/// Smite is pretty extravagant, so whenever we get casted, we blind everyone nearby.
|
||||
/datum/action/cooldown/spell/touch/smite/proc/blind_everyone_nearby(mob/living/victim, atom/center)
|
||||
do_sparks(sparks_amt, FALSE, get_turf(victim))
|
||||
for(var/mob/living/nearby_spectator in view(caster, 7))
|
||||
if(nearby_spectator == caster)
|
||||
for(var/mob/living/nearby_spectator in view(center, 7))
|
||||
if(nearby_spectator == center)
|
||||
continue
|
||||
nearby_spectator.flash_act(affect_silicon = FALSE)
|
||||
|
||||
var/mob/living/living_victim = victim
|
||||
if(living_victim.can_block_magic(antimagic_flags))
|
||||
caster.visible_message(
|
||||
span_warning("The feedback blows [caster]'s arm off!"),
|
||||
span_userdanger("The spell bounces from [living_victim]'s skin back into your arm!"),
|
||||
)
|
||||
caster.flash_act()
|
||||
var/obj/item/bodypart/to_dismember = caster.get_holding_bodypart_of_item(hand)
|
||||
to_dismember?.dismember()
|
||||
return TRUE
|
||||
/datum/action/cooldown/spell/touch/smite/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
|
||||
caster.visible_message(
|
||||
span_warning("The feedback blows [caster]'s arm off!"),
|
||||
span_userdanger("The spell bounces from [victim]'s skin back into your arm!"),
|
||||
)
|
||||
// Off goes the arm we were casting with!
|
||||
var/obj/item/bodypart/to_dismember = caster.get_holding_bodypart_of_item(hand)
|
||||
to_dismember?.dismember()
|
||||
// And do the blind (us included)
|
||||
caster.flash_act()
|
||||
blind_everyone_nearby(caster, caster)
|
||||
|
||||
/datum/action/cooldown/spell/touch/smite/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
|
||||
blind_everyone_nearby(victim, caster)
|
||||
|
||||
if(ishuman(victim))
|
||||
var/mob/living/carbon/human/human_victim = victim
|
||||
@@ -45,8 +47,8 @@
|
||||
new /obj/effect/gibspawner(get_turf(victim))
|
||||
return TRUE
|
||||
|
||||
living_victim.investigate_log("has been gibbed by the smite spell.", INVESTIGATE_DEATHS)
|
||||
living_victim.gib()
|
||||
victim.investigate_log("has been gibbed by the smite spell.", INVESTIGATE_DEATHS)
|
||||
victim.gib()
|
||||
return TRUE
|
||||
|
||||
/obj/item/melee/touch_attack/smite
|
||||
|
||||
Reference in New Issue
Block a user