Files
Bubberstation/code/datums/actions/mobs/charge.dm
John Willard b5975bd915 Unit tests now catch decals in walls/space (#75189)
## About The Pull Request

Unit tests will now fail if there's a decal in a wall or open space
turf. Open space turf could be limiting to mappers but I don't think it
makes sense for decals (like dirt, glass shards, etc) to be floating
around in space in the exact same spot.

If there's a decal you want to put in space, decals have a
``turf_loc_check`` var that will bypass this.

**Important note: This is not changing existing behavior. Decals already
delete themselves when they spawn in these incorrect locations, we're
just avoiding them from spawning in the first place.**

### Changes I made

- Ash flora are now lava immune, rivers spawn after flora does, so I
decided that it would be easiest (and more flavorful) to have them be
lava-immune rather than to not have them spawn at all.
- Decals can now be spawned in non-turf locations. This is currently
done by mail, which can give you bones as part of the mail. Currently it
will just delete itself instead.
- Trading Card button is now on the same tile as their display, which
now uses an offset. Before it would spawn it on the tile next to it,
which could be a wall in some instances.
- Mirrors now have floating movement type. They ARE floating since
they're attached to the wall, and it prevents them from burning up due
to lava in the Pride ruin.
- I also added a broken mirror subtype because I thought the icon_state
check was terrible.
- Bubblegum called ``DestroySurroundings`` several times on the same
thing, I hopefully fixed some of that. Their charge ability also
registered ``COMSIG_MOB_STATCHANGE`` despite ``/datum/action`` doing it
by default, so I fixed that too.

## Why It's Good For The Game

Decals in walls is already a bad idea, but currently all it does is
delete it on Initialize. It would be better if we ensured they wouldn't
spawn in the first place.

## Changelog

🆑
fix: Lava will no longer burn 6 of the mirrors in pride ruin
fix: Lava will no longer burn plants that spawn in them.
/🆑
2023-06-15 16:39:22 -07:00

286 lines
11 KiB
Plaintext

/datum/action/cooldown/mob_cooldown/charge
name = "Charge"
button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "sniper_zoom"
desc = "Allows you to charge at a chosen position."
cooldown_time = 1.5 SECONDS
/// Delay before the charge actually occurs
var/charge_delay = 0.3 SECONDS
/// The amount of turfs we move past the target
var/charge_past = 2
/// The maximum distance we can charge
var/charge_distance = 50
/// The sleep time before moving in deciseconds while charging
var/charge_speed = 0.5
/// The damage the charger does when bumping into something
var/charge_damage = 30
/// If we destroy objects while charging
var/destroy_objects = TRUE
/// If the current move is being triggered by us or not
var/actively_moving = FALSE
/// List of charging mobs
var/list/charging = list()
/datum/action/cooldown/mob_cooldown/charge/Activate(atom/target_atom)
StartCooldown(360 SECONDS, 360 SECONDS)
charge_sequence(owner, target_atom, charge_delay, charge_past)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/charge/proc/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
do_charge(owner, target_atom, charge_delay, charge_past)
/datum/action/cooldown/mob_cooldown/charge/proc/do_charge(atom/movable/charger, atom/target_atom, delay, past)
if(!target_atom || target_atom == owner)
return
var/chargeturf = get_turf(target_atom)
if(!chargeturf)
return
var/dir = get_dir(charger, target_atom)
var/turf/target = get_ranged_target_turf(chargeturf, dir, past)
if(!target)
return
if(charger in charging)
// Stop any existing charging, this'll clean things up properly
SSmove_manager.stop_looping(charger)
charging += charger
actively_moving = FALSE
SEND_SIGNAL(owner, COMSIG_STARTED_CHARGE)
RegisterSignal(charger, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump), TRUE)
RegisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move), TRUE)
RegisterSignal(charger, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved), TRUE)
charger.setDir(dir)
do_charge_indicator(charger, target)
SLEEP_CHECK_DEATH(delay, charger)
var/time_to_hit = min(get_dist(charger, target), charge_distance) * charge_speed
var/datum/move_loop/new_loop = SSmove_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
if(!new_loop)
return
RegisterSignal(new_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(new_loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
RegisterSignal(new_loop, COMSIG_QDELETING, PROC_REF(charge_end))
// Yes this is disgusting. But we need to queue this stuff, and this code just isn't setup to support that right now. So gotta do it with sleeps
sleep(time_to_hit + charge_speed)
charger.setDir(dir)
return TRUE
/datum/action/cooldown/mob_cooldown/charge/proc/pre_move(datum)
SIGNAL_HANDLER
// If you sleep in Move() you deserve what's coming to you
actively_moving = TRUE
/datum/action/cooldown/mob_cooldown/charge/proc/post_move(datum)
SIGNAL_HANDLER
actively_moving = FALSE
/datum/action/cooldown/mob_cooldown/charge/proc/charge_end(datum/move_loop/source)
SIGNAL_HANDLER
var/atom/movable/charger = source.moving
UnregisterSignal(charger, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED))
SEND_SIGNAL(owner, COMSIG_FINISHED_CHARGE)
actively_moving = FALSE
charging -= charger
/datum/action/cooldown/mob_cooldown/charge/update_status_on_signal(mob/source, new_stat, old_stat)
. = ..()
if(new_stat == DEAD)
SSmove_manager.stop_looping(source) //This will cause the loop to qdel, triggering an end to our charging
/datum/action/cooldown/mob_cooldown/charge/proc/do_charge_indicator(atom/charger, atom/charge_target)
var/turf/target_turf = get_turf(charge_target)
if(!target_turf)
return
new /obj/effect/temp_visual/dragon_swoop/bubblegum(target_turf)
var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(charger.loc, charger)
animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 3)
/datum/action/cooldown/mob_cooldown/charge/proc/on_move(atom/source, atom/new_loc)
SIGNAL_HANDLER
if(!actively_moving)
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
new /obj/effect/temp_visual/decoy/fading(source.loc, source)
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
/datum/action/cooldown/mob_cooldown/charge/proc/on_moved(atom/source)
SIGNAL_HANDLER
playsound(source, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE)
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
/datum/action/cooldown/mob_cooldown/charge/proc/DestroySurroundings(atom/movable/charger)
if(!destroy_objects)
return
if(!isanimal(charger))
return
for(var/dir in GLOB.cardinals)
var/turf/next_turf = get_step(charger, dir)
if(!next_turf)
continue
if(next_turf.Adjacent(charger) && (iswallturf(next_turf) || ismineralturf(next_turf)))
if(!isanimal(charger))
SSexplosions.medturf += next_turf
continue
next_turf.attack_animal(charger)
continue
for(var/obj/object in next_turf.contents)
if(!object.Adjacent(charger))
continue
if(!ismachinery(object) && !isstructure(object))
continue
if(!object.density || object.IsObscured())
continue
if(!isanimal(charger))
SSexplosions.med_mov_atom += target
break
object.attack_animal(charger)
break
/datum/action/cooldown/mob_cooldown/charge/proc/on_bump(atom/movable/source, atom/target)
SIGNAL_HANDLER
if(owner == target)
return
if(destroy_objects)
if(isturf(target))
SSexplosions.medturf += target
if(isobj(target) && target.density)
SSexplosions.med_mov_atom += target
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
hit_target(source, target, charge_damage)
/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, atom/target, damage_dealt)
if(!isliving(target))
return
var/mob/living/living_target = target
living_target.visible_message("<span class='danger'>[source] slams into [living_target]!</span>", "<span class='userdanger'>[source] tramples you into the ground!</span>")
source.forceMove(get_turf(living_target))
living_target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND)
playsound(get_turf(living_target), 'sound/effects/meteorimpact.ogg', 100, TRUE)
shake_camera(living_target, 4, 3)
shake_camera(source, 2, 3)
/datum/action/cooldown/mob_cooldown/charge/basic_charge
name = "Basic Charge"
cooldown_time = 6 SECONDS
charge_delay = 1.5 SECONDS
charge_distance = 4
melee_cooldown_time = 0
/// How long to shake for before charging
var/shake_duration = 1 SECONDS
/// Intensity of shaking animation
var/shake_pixel_shift = 2
/datum/action/cooldown/mob_cooldown/charge/basic_charge/do_charge_indicator(atom/charger, atom/charge_target)
charger.Shake(shake_pixel_shift, shake_pixel_shift, shake_duration)
/datum/action/cooldown/mob_cooldown/charge/basic_charge/hit_target(atom/movable/source, atom/target, damage_dealt)
var/mob/living/living_source
if(isliving(source))
living_source = source
if(!isliving(target))
if(!target.density || target.CanPass(source, get_dir(target, source)))
return
source.visible_message(span_danger("[source] smashes into [target]!"))
if(!living_source)
return
living_source.Stun(6, ignore_canstun = TRUE)
return
var/mob/living/living_target = target
if(ishuman(living_target))
var/mob/living/carbon/human/human_target = living_target
if(human_target.check_shields(source, 0, "the [source.name]", attack_type = LEAP_ATTACK) && living_source)
living_source.Stun(6, ignore_canstun = TRUE)
return
living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!"))
living_target.Knockdown(6)
/datum/action/cooldown/mob_cooldown/charge/triple_charge
name = "Triple Charge"
desc = "Allows you to charge three times at a chosen position."
charge_delay = 0.6 SECONDS
/datum/action/cooldown/mob_cooldown/charge/triple_charge/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
for(var/i in 0 to 2)
do_charge(owner, target_atom, charge_delay - 2 * i, charge_past)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge
name = "Hallucination Charge"
button_icon = 'icons/effects/bubblegum.dmi'
button_icon_state = "smack ya one"
desc = "Allows you to create hallucinations that charge around your target."
cooldown_time = 2 SECONDS
charge_delay = 0.6 SECONDS
/// The damage the hallucinations in our charge do
var/hallucination_damage = 15
/// Check to see if we are enraged, enraged ability does more
var/enraged = FALSE
/// Check to see if we should spawn blood
var/spawn_blood = FALSE
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
if(!enraged || prob(33))
hallucination_charge(target_atom, 6, 8, 0, 6, TRUE)
return
for(var/i in 0 to 2)
hallucination_charge(target_atom, 4, 9 - 2 * i, 0, 4, TRUE)
for(var/i in 0 to 2)
do_charge(owner, target_atom, charge_delay - 2 * i, charge_past)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/do_charge(atom/movable/charger, atom/target_atom, delay, past)
. = ..()
if(charger != owner)
qdel(charger)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/proc/hallucination_charge(atom/target_atom, clone_amount, delay, past, radius, use_self)
var/starting_angle = rand(1, 360)
if(!radius)
return
var/angle_difference = 360 / clone_amount
var/self_placed = FALSE
for(var/i = 1 to clone_amount)
var/angle = (starting_angle + angle_difference * i)
var/turf/place = locate(target_atom.x + cos(angle) * radius, target_atom.y + sin(angle) * radius, target_atom.z)
if(!place)
continue
if(use_self && !self_placed)
owner.forceMove(place)
self_placed = TRUE
continue
var/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/our_clone = new /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination(place)
our_clone.appearance = owner.appearance
our_clone.name = "[owner]'s hallucination"
our_clone.alpha = 127.5
our_clone.move_through_mob = owner
our_clone.spawn_blood = spawn_blood
INVOKE_ASYNC(src, PROC_REF(do_charge), our_clone, target_atom, delay, past)
if(use_self)
do_charge(owner, target_atom, delay, past)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hit_target(atom/movable/source, atom/A, damage_dealt)
var/applied_damage = charge_damage
if(source != owner)
applied_damage = hallucination_damage
. = ..(source, A, applied_damage)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround
name = "Surround Target"
button_icon = 'icons/turf/walls/wall.dmi'
button_icon_state = "wall-0"
desc = "Allows you to create hallucinations that charge around your target."
charge_delay = 0.6 SECONDS
charge_past = 2
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
for(var/i in 0 to 4)
hallucination_charge(target_atom, 2, 8, 2, 2, FALSE)
do_charge(owner, target_atom, charge_delay, charge_past)