Files
Bubberstation/code/modules/projectiles/guns/special/blastcannon.dm
SmArtKar d4ac95a0e1 Nobody expects the span inquisition: replaces most <span>s with macros (#86798)
## About The Pull Request
123 changed files and multiple crashes after writing broken regex, I
replaced most remains of direct spans with macros. This cleans up the
code and makes it easier to work with in general, see justification for
the original PR. I also fixed a bunch of broken and/or unclosed spans
here too.
I intentionally avoided replacing spans with multiple classes (in most
cases) and spans in the middle of strings as it would impact readability
(in my opinion at least) and could be done later if required.

## Why It's Good For The Game

Cleaner code, actually using our macros, fixes borked HTML in some
places. See original PR.

## Changelog
Nothing player-facing
2024-09-26 19:36:13 +00:00

347 lines
13 KiB
Plaintext

/// How much to scale the explosion ranges for blastcannon shots.
#define BLASTCANNON_RANGE_EXP (1 / GLOB.DYN_EX_SCALE)
/// How much to scale the explosion ranges for blastcannon shots.
#define BLASTCANNON_RANGE_SCALE PI
/**
* A gun that consumes a TTV to shoot an projectile with equivalent power.
*
* It's basically an immovable rod launcher.
*/
/obj/item/gun/blastcannon
name = "blast cannon"
desc = "A surprisingly portable device used to concentrate a bomb's blast energy to a narrow wave. Small enough to stow in a bag."
icon = 'icons/obj/weapons/guns/wide_guns.dmi'
icon_state = "blastcannon_empty"
lefthand_file = 'icons/mob/inhands/weapons/64x_guns_left.dmi'
righthand_file = 'icons/mob/inhands/weapons/64x_guns_right.dmi'
inhand_x_dimension = 64
base_pixel_x = -2
pixel_x = -2
inhand_icon_state = "blastcannon_empty"
base_icon_state = "blastcannon"
w_class = WEIGHT_CLASS_NORMAL
force = 10
fire_sound = 'sound/items/weapons/blastcannon.ogg'
item_flags = NONE
clumsy_check = FALSE
randomspread = FALSE
// Firing data.
/// The person who opened the valve on the TTV loaded into this.
var/datum/weakref/cached_firer
/// The target from the last click.
var/datum/weakref/cached_target
/// The modifiers from the last click.
var/cached_modifiers
// Explosion data:
/// The TTV this contains that will be used to create the projectile
var/obj/item/transfer_valve/bomb
// For debugging/badminry
/// Whether you can fire this without a bomb.
var/bombcheck = TRUE
/// The range this defaults to without a bomb for debugging and badminry
var/debug_power = 0
/obj/item/gun/blastcannon/Initialize(mapload)
. = ..()
if(!pin)
pin = new
RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, PROC_REF(channel_blastwave))
AddElement(/datum/element/update_icon_updates_onmob)
/obj/item/gun/blastcannon/Destroy()
if(bomb)
QDEL_NULL(bomb)
UnregisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION)
cached_firer = null
cached_target = null
return ..()
/obj/item/gun/blastcannon/Exited(atom/movable/gone, direction)
. = ..()
if(gone == bomb)
bomb = null
update_appearance()
/obj/item/gun/blastcannon/assume_air(datum/gas_mixture/giver)
qdel(giver)
return null // Required to make the TTV not vent gas directly into the firer.
/obj/item/gun/blastcannon/examine(mob/user)
. = ..()
if(bomb)
. += span_notice("A bomb is loaded inside.")
/obj/item/gun/blastcannon/attack_self(mob/user)
if(bomb)
bomb.forceMove(user.loc)
user.put_in_hands(bomb)
user.visible_message(span_warning("[user] detaches [bomb] from [src]."))
bomb = null
update_appearance()
return ..()
/obj/item/gun/blastcannon/update_icon_state()
icon_state = "[base_icon_state]_[bomb ? "loaded" : "empty"]"
inhand_icon_state = icon_state
return ..()
/obj/item/gun/blastcannon/attackby(obj/item/transfer_valve/bomb_to_attach, mob/user)
if(!istype(bomb_to_attach))
return ..()
if(bomb)
to_chat(user, span_warning("[bomb] is already attached to [src]!"))
return
if(!bomb_to_attach.ready())
to_chat(user, span_warning("What good would an incomplete bomb do?"))
return FALSE
if(!user.transferItemToLoc(bomb_to_attach, src))
to_chat(user, span_warning("[bomb_to_attach] seems to be stuck to your hand!"))
return FALSE
user.visible_message(span_warning("[user] attaches [bomb_to_attach] to [src]!"))
bomb = bomb_to_attach
update_appearance()
return TRUE
/obj/item/gun/blastcannon/try_fire_gun(atom/target, mob/living/user, params)
if((!bomb && bombcheck) || isnull(target) || (get_dist(get_turf(target), get_turf(user)) <= 2))
return ..()
cached_target = WEAKREF(target)
cached_modifiers = params
if(bomb?.valve_open)
user.visible_message(
span_danger("[user] points [src] at [target]!"),
span_danger("You point [src] at [target]!")
)
return FALSE
cached_firer = WEAKREF(user)
if(!bomb)
fire_debug(target, user, params)
return TRUE
playsound(src, dry_fire_sound, 30, TRUE) // *click
user.visible_message(
span_danger("[user] opens [bomb] on [user.p_their()] [src] and points [p_them()] at [target]!"),
span_danger("You open [bomb] on your [src] and point [p_them()] at [target]!")
)
var/turf/current_turf = get_turf(src)
var/turf/target_turf = get_turf(target)
message_admins("Blastcannon transfer valve opened by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(current_turf)] while aiming at [ADMIN_VERBOSEJMP(target_turf)] (target).")
user.log_message("opened blastcannon transfer valve at [AREACOORD(current_turf)] while aiming at [AREACOORD(target_turf)] (target).", LOG_GAME)
bomb.toggle_valve()
update_appearance()
return TRUE
/**
* Channels an internal explosion into a blastwave projectile.
*
* Arguments:
* - [blastwave_data][/list]: A list containing all of the data for the blastwave.
*/
/obj/item/gun/blastcannon/proc/channel_blastwave(atom/source, list/arguments)
SIGNAL_HANDLER
. = COMSIG_CANCEL_EXPLOSION
var/heavy = (arguments[EXARG_KEY_DEV_RANGE]**BLASTCANNON_RANGE_EXP) * BLASTCANNON_RANGE_SCALE
var/medium = (arguments[EXARG_KEY_HEAVY_RANGE]**BLASTCANNON_RANGE_EXP) * BLASTCANNON_RANGE_SCALE
var/light = (arguments[EXARG_KEY_LIGHT_RANGE]**BLASTCANNON_RANGE_EXP) * BLASTCANNON_RANGE_SCALE
var/range = max(heavy, medium, light, 0)
if(!range)
visible_message(span_warning("[src] lets out a little \"phut\"."))
return
if(!ismob(loc))
INVOKE_ASYNC(src, PROC_REF(fire_dropped), heavy, medium, light)
return
var/mob/holding = loc
var/target = cached_target?.resolve()
if(target && (holding.get_active_held_item() == src) && cached_firer && (holding == cached_firer.resolve()))
INVOKE_ASYNC(src, PROC_REF(fire_intentionally), target, holding, heavy, medium, light, cached_modifiers)
else
INVOKE_ASYNC(src, PROC_REF(fire_accidentally), holding, heavy, medium, light)
return
/**
* Prepares and fires a blastwave.
*
* Arguments:
* - [target][/atom]: The thing that the blastwave is being fired at.
* - heavy: The devastation range of the blastwave.
* - medium: The heavy impact range of the blastwave.
* - light: The light impact range of the blastwave.
* - modifiers: Modifiers as a string used while firing this.
* - spread: How inaccurate the blastwave is.
*/
/obj/item/gun/blastcannon/proc/fire_blastwave(atom/target, heavy, medium, light, modifiers, spread = 0)
var/turf/start_turf = get_turf(src)
var/cap_multiplier = SSmapping.level_trait(start_turf.z, ZTRAIT_BOMBCAP_MULTIPLIER)
if(isnull(cap_multiplier))
cap_multiplier = 1
var/capped_heavy = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, heavy)
var/capped_medium = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, medium)
SSexplosions.shake_the_room(start_turf, max(heavy, medium, light, 0), (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium)
var/obj/projectile/blastwave/blastwave = new(loc, heavy, medium, light)
blastwave.preparePixelProjectile(target, start_turf, params2list(modifiers), spread)
blastwave.fire()
cached_firer = null
cached_target = null
cached_modifiers = null
return
/**
* Handles firing the blastcannon intentionally at a specific target.
*
* Arguments:
* - [firer][/mob]: The mob who is firing this weapon.
* - [target][/atom]: The target we are firing the
* - heavy: The devastation range of the blastwave.
* - medium: The heavy impact range of the blastwave.
* - light: The light impact range of the blastwave.
* - modifiers: The modifier string to use when preparing the blastwave.
*/
/obj/item/gun/blastcannon/proc/fire_intentionally(atom/target, mob/firer, heavy, medium, light, modifiers)
firer.visible_message(
span_danger("[firer] fires a blast wave at [target]!"),
span_danger("You fire a blast wave at [target]!")
)
var/turf/start_turf = get_turf(src)
var/turf/target_turf = get_turf(target)
message_admins("Blast wave fired from [ADMIN_VERBOSEJMP(start_turf)] at [ADMIN_VERBOSEJMP(target_turf)] ([target]) by [ADMIN_LOOKUPFLW(firer)] with power [heavy]/[medium]/[light].")
firer.log_message("fired a blast wave from [AREACOORD(start_turf)] at [AREACOORD(target_turf)] ([target]) with power [heavy]/[medium]/[light].", LOG_GAME)
firer.log_message("fired a blast wave from [AREACOORD(start_turf)] at [AREACOORD(target_turf)] ([target]) with power [heavy]/[medium]/[light].", LOG_ATTACK, log_globally = FALSE)
fire_blastwave(target, heavy, medium, light, modifiers)
return
/**
* Handles firing the blastcannon if it was handed to someone else between opening the valve and the bomb exploding.
*
* Arguments:
* - [holder][/mob]: The person who happened to be holding the cannon when it went off.
* - heavy: The devastation range of the blastwave.
* - medium: The heavy impact range of the blastwave.
* - light: The light impact range of the blastwave.
*/
/obj/item/gun/blastcannon/proc/fire_accidentally(mob/holder, heavy, medium, light)
var/turf/target
var/holding
if(holder.is_holding(src))
target = get_edge_target_turf(holder, holder.dir)
holding = TRUE
else
target = get_edge_target_turf(holder, dir)
holding = FALSE
var/mob/firer = cached_firer?.resolve()
var/turf/start_turf = get_turf(src)
holder.visible_message(
span_danger("[src] suddenly goes off[holding ? " in [holder]'s hands" : null]!"),
span_danger("[src] suddenly goes off[holding ? " in your hands" : null]!")
)
message_admins("Blast wave primed by [ADMIN_LOOKUPFLW(firer)] fired from [ADMIN_VERBOSEJMP(start_turf)] roughly towards [ADMIN_VERBOSEJMP(target)] while being held by [ADMIN_LOOKUPFLW(holder)] with power [heavy]/[medium]/[light].")
log_game("Blast wave primed by [key_name(firer)] fired from [AREACOORD(start_turf)] roughly towards [AREACOORD(target)] while being held by [key_name(holder)] with power [heavy]/[medium]/[light].")
return fire_blastwave(target, heavy, medium, light, null, (90 * (rand() - 0.5))) // +- anywhere between 0 and 45 degrees
/**
* Handles firing the blastcannon if it was dropped on the ground or shoved into a backpack.
*
* Arguments:
* - heavy: The devastation range of the blastwave.
* - medium: The heavy impact range of the blastwave.
* - light: The light impact range of the blastwave.
*/
/obj/item/gun/blastcannon/proc/fire_dropped(heavy, medium, light)
src.visible_message(span_danger("[src] suddenly goes off!"))
var/turf/target = get_edge_target_turf(src, dir)
var/mob/firer = cached_firer.resolve()
var/turf/start_turf = get_turf(src)
message_admins("Blast wave primed by [ADMIN_LOOKUPFLW(firer)] fired from [ADMIN_VERBOSEJMP(start_turf)] roughly towards [ADMIN_VERBOSEJMP(target)] at [ADMIN_VERBOSEJMP(loc)] ([loc]) with power [heavy]/[medium]/[light].")
log_game("Blast wave primed by [key_name(firer)] fired from [AREACOORD(start_turf)] roughly towards [AREACOORD(target)] at [AREACOORD(loc)] ([loc]) with power [heavy]/[medium]/[light].")
return fire_blastwave(target, heavy, medium, light, null, 360 * rand())
/**
* Handles firing the blastcannon with debug power.
*
* Arguments:
* - [target][/atom]: The thing the blastcannon is being fired at.
* - [user][/mob]: The person firing the blastcannon.
* - modifiers: A string containing click data.
*/
/obj/item/gun/blastcannon/proc/fire_debug(atom/target, mob/user, modifiers)
fire_intentionally(target, user, debug_power * 0.25, debug_power * 0.5, debug_power, modifiers)
return
/// The projectile used by the blastcannon
/obj/projectile/blastwave
name = "blast wave"
icon_state = "blastwave"
damage = 0
armor_flag = BOMB // Doesn't actually have any functional purpose. But it makes sense.
movement_type = FLYING
projectile_phasing = ALL // just blows up the turfs lmao
phasing_ignore_direct_target = TRUE // If we don't do this the blastcannon shot can be blocked by random items.
/// The maximum distance this will inflict [EXPLODE_DEVASTATE]
var/heavy_ex_range = 0
/// The maximum distance this will inflict [EXPLODE_HEAVY]
var/medium_ex_range = 0
/// The maximum distance this will inflict [EXPLODE_LIGHT]
var/light_ex_range = 0
/// Whether or not to care about the explosion block of the things we are passing through.
var/reactionary
/obj/projectile/blastwave/Initialize(mapload, heavy_ex_range, medium_ex_range, light_ex_range, reactionary = CONFIG_GET(flag/reactionary_explosions))
range = max(heavy_ex_range, medium_ex_range, light_ex_range, 0)
src.heavy_ex_range = heavy_ex_range
src.medium_ex_range = medium_ex_range
src.light_ex_range = light_ex_range
src.reactionary = reactionary
return ..()
// Though the projectile itself is not damaging its effects are
/obj/projectile/blastwave/is_hostile_projectile()
return TRUE
/obj/projectile/blastwave/Range()
. = ..()
if(QDELETED(src))
return
var/decrement = 1
var/atom/location = loc
if (reactionary)
decrement += location.explosive_resistance
range = max(range - decrement + 1, 0) // Already decremented by 1 in the parent. Exists so that if we pass through something with negative block it extends the range.
heavy_ex_range = max(heavy_ex_range - decrement, 0)
medium_ex_range = max(medium_ex_range - decrement, 0)
light_ex_range = max(light_ex_range - decrement, 0)
if (heavy_ex_range)
SSexplosions.highturf += location
else if(medium_ex_range)
SSexplosions.medturf += location
else if(light_ex_range)
SSexplosions.lowturf += location
else
qdel(src)
return
/obj/projectile/blastwave/ex_act()
return FALSE
#undef BLASTCANNON_RANGE_EXP
#undef BLASTCANNON_RANGE_SCALE