mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 16:05:07 +00:00
Melee attack chain now has a list passed along with it, `attack_modifiers`, which you can stick force modifiers to change the resulting attack This is basically a soft implementation of damage packets until a more definitive pr, but one that only applies to item attack chain, and not unarmed attacks. This change was done to facilitate a baton refactor - batons no longer hack together their own attack chain, and are now integrated straight into the real attack chain. This refactor itself was done because batons don't send any attack signals, which has been annoying in the past (for swing combat). 🆑 Melbert refactor: Batons have been refactored again. Baton stuns now properly count as an attack, when before it was a nothing. Report any oddities, particularly in regards to harmbatonning vs normal batonning. refactor: The method of adjusting item damage mid-attack has been refactored - some affected items include the Nullblade and knives. Report any strange happenings with damage numbers. refactor: A few objects have been moved to the new interaction chain - records consoles, mawed crucible, alien weeds and space vines, hedges, restaurant portals, and some mobs - to name a few. fix: Spears only deal bonus damage against secure lockers, not all closet types (including crates) /🆑
347 lines
11 KiB
Plaintext
347 lines
11 KiB
Plaintext
|
|
#define PCANNON_FIREALL 1
|
|
#define PCANNON_FILO 2
|
|
#define PCANNON_FIFO 3
|
|
///Defines for the pressure strength of the cannon
|
|
#define LOW_PRESSURE 1
|
|
#define MID_PRESSURE 2
|
|
#define HIGH_PRESSURE 3
|
|
|
|
/obj/item/pneumatic_cannon
|
|
name = "pneumatic cannon"
|
|
desc = "A gas-powered cannon that can fire any object loaded into it."
|
|
w_class = WEIGHT_CLASS_BULKY
|
|
force = 8 //Very heavy
|
|
attack_verb_continuous = list("bludgeons", "smashes", "beats")
|
|
attack_verb_simple = list("bludgeon", "smash", "beat")
|
|
icon = 'icons/obj/weapons/pneumaticCannon.dmi'
|
|
icon_state = "pneumaticCannon"
|
|
inhand_icon_state = "bulldog"
|
|
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
|
|
armor_type = /datum/armor/item_pneumatic_cannon
|
|
var/maxWeightClass = 20 //The max weight of items that can fit into the cannon
|
|
var/loadedWeightClass = 0 //The weight of items currently in the cannon
|
|
var/obj/item/tank/internals/tank = null //The gas tank that is drawn from to fire things
|
|
var/gasPerThrow = 3 //How much gas is drawn from a tank's pressure to fire
|
|
var/list/loadedItems = list() //The items loaded into the cannon that will be fired out
|
|
var/pressure_setting = LOW_PRESSURE //How powerful the cannon is - higher pressure = more gas but more powerful throws
|
|
var/checktank = TRUE
|
|
var/range_multiplier = 1
|
|
var/throw_amount = 1 //How many items to throw per fire
|
|
var/fire_mode = PCANNON_FIFO
|
|
var/automatic = FALSE
|
|
var/needs_air = TRUE
|
|
var/clumsyCheck = TRUE
|
|
var/list/allowed_typecache //Leave as null to allow all.
|
|
var/charge_amount = 1
|
|
var/charge_ticks = 1
|
|
var/charge_tick = 0
|
|
var/charge_type
|
|
var/selfcharge = FALSE
|
|
var/fire_sound = 'sound/items/weapons/sonic_jackhammer.ogg'
|
|
var/spin_item = TRUE //Do the projectiles spin when launched?
|
|
trigger_guard = TRIGGER_GUARD_NORMAL
|
|
|
|
|
|
/datum/armor/item_pneumatic_cannon
|
|
fire = 60
|
|
acid = 50
|
|
|
|
/obj/item/pneumatic_cannon/Initialize(mapload)
|
|
. = ..()
|
|
if(selfcharge)
|
|
init_charge()
|
|
|
|
/obj/item/pneumatic_cannon/proc/init_charge() //wrapper so it can be vv'd easier
|
|
START_PROCESSING(SSobj, src)
|
|
|
|
/obj/item/pneumatic_cannon/process()
|
|
charge_tick++
|
|
if(charge_tick >= charge_ticks && charge_type)
|
|
fill_with_type(charge_type, charge_amount)
|
|
|
|
/obj/item/pneumatic_cannon/Destroy()
|
|
STOP_PROCESSING(SSobj, src)
|
|
return ..()
|
|
|
|
/obj/item/pneumatic_cannon/CanItemAutoclick()
|
|
return automatic
|
|
|
|
/obj/item/pneumatic_cannon/proc/pressure_setting_to_text(pressure_setting)
|
|
switch(pressure_setting)
|
|
if(LOW_PRESSURE)
|
|
return "low"
|
|
if(MID_PRESSURE)
|
|
return "medium"
|
|
if(HIGH_PRESSURE)
|
|
return "high"
|
|
else
|
|
CRASH("Invalid pressure setting: [pressure_setting]!")
|
|
|
|
/obj/item/pneumatic_cannon/examine(mob/user)
|
|
. = ..()
|
|
var/list/out = list()
|
|
if(!in_range(user, src))
|
|
out += span_notice("You'll need to get closer to see any more.")
|
|
return
|
|
for(var/obj/item/I in loadedItems)
|
|
out += span_info("[icon2html(I, user)] It has \a [I] loaded.")
|
|
CHECK_TICK
|
|
if(tank)
|
|
out += span_notice("[icon2html(tank, user)] It has \a [tank] mounted onto it. It could be removed with a <b>screwdriver</b>.")
|
|
if(needs_air == TRUE)
|
|
. += span_notice("Use a <b>wrench</b> to change the pressure level. Current output level is <b>[pressure_setting_to_text(pressure_setting)]</b>.")
|
|
. += out.Join("\n")
|
|
|
|
/obj/item/pneumatic_cannon/screwdriver_act(mob/living/user, obj/item/tool)
|
|
if(tank)
|
|
tool.play_tool_sound(src)
|
|
updateTank(tank, 1, user)
|
|
return TRUE
|
|
|
|
/obj/item/pneumatic_cannon/wrench_act(mob/living/user, obj/item/tool)
|
|
if(needs_air == FALSE)
|
|
return
|
|
playsound(src, 'sound/items/tools/ratchet.ogg', 50, TRUE)
|
|
pressure_setting = pressure_setting >= HIGH_PRESSURE ? LOW_PRESSURE : pressure_setting + 1
|
|
balloon_alert(user, "output level set to [pressure_setting_to_text(pressure_setting)]")
|
|
return TRUE
|
|
|
|
/obj/item/pneumatic_cannon/attackby(obj/item/W, mob/living/user, list/modifiers, list/attack_modifiers)
|
|
if(user.combat_mode)
|
|
return ..()
|
|
if(istype(W, /obj/item/tank/internals))
|
|
if(needs_air == FALSE)
|
|
return
|
|
if(!tank)
|
|
var/obj/item/tank/internals/IT = W
|
|
if(IT.volume <= 3)
|
|
to_chat(user, span_warning("\The [IT] is too small for \the [src]."))
|
|
return
|
|
updateTank(W, 0, user)
|
|
else if(W.type == type)
|
|
to_chat(user, span_warning("You're fairly certain that putting a pneumatic cannon inside another pneumatic cannon would cause a spacetime disruption."))
|
|
else if(loadedWeightClass >= maxWeightClass)
|
|
to_chat(user, span_warning("\The [src] can't hold any more items!"))
|
|
else if(isitem(W))
|
|
var/obj/item/IW = W
|
|
load_item(IW, user)
|
|
|
|
/obj/item/pneumatic_cannon/proc/can_load_item(obj/item/I, mob/user)
|
|
if(!istype(I)) //Players can't load non items, this allows for admin varedit inserts.
|
|
return TRUE
|
|
if(allowed_typecache && !is_type_in_typecache(I, allowed_typecache))
|
|
if(user)
|
|
to_chat(user, span_warning("[I] won't fit into [src]!"))
|
|
return
|
|
if((loadedWeightClass + I.w_class) > maxWeightClass) //Only make messages if there's a user
|
|
if(user)
|
|
to_chat(user, span_warning("\The [I] won't fit into \the [src]!"))
|
|
return FALSE
|
|
if(I.w_class > w_class)
|
|
if(user)
|
|
to_chat(user, span_warning("\The [I] is too large to fit into \the [src]!"))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/pneumatic_cannon/proc/load_item(obj/item/I, mob/user)
|
|
if(!can_load_item(I, user))
|
|
return FALSE
|
|
if(user) //Only use transfer proc if there's a user, otherwise just set loc.
|
|
if(!user.transferItemToLoc(I, src))
|
|
return FALSE
|
|
to_chat(user, span_notice("You load \the [I] into \the [src]."))
|
|
else
|
|
I.forceMove(src)
|
|
loadedItems += I
|
|
if(isitem(I))
|
|
loadedWeightClass += I.w_class
|
|
else
|
|
loadedWeightClass++
|
|
return TRUE
|
|
|
|
/obj/item/pneumatic_cannon/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
if(user.combat_mode)
|
|
return ITEM_INTERACT_SKIP_TO_ATTACK
|
|
Fire(user, interacting_with)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/item/pneumatic_cannon/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
Fire(user, interacting_with)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/item/pneumatic_cannon/proc/Fire(mob/living/user, atom/target)
|
|
if(!istype(user) && !target)
|
|
return
|
|
var/discharge = 0
|
|
if(!can_trigger_gun(user))
|
|
return
|
|
if(!loadedItems || !loadedWeightClass)
|
|
to_chat(user, span_warning("\The [src] has nothing loaded."))
|
|
return
|
|
if(!tank && checktank)
|
|
to_chat(user, span_warning("\The [src] can't fire without a source of gas."))
|
|
return
|
|
if(HAS_TRAIT(user, TRAIT_PACIFISM))
|
|
to_chat(user, span_warning("You can't bring yourself to fire \the [src]! You don't want to risk harming anyone...") )
|
|
return
|
|
if(tank && !tank.remove_air(gasPerThrow * pressure_setting))
|
|
to_chat(user, span_warning("\The [src] lets out a weak hiss and doesn't react!"))
|
|
return
|
|
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(75) && clumsyCheck && iscarbon(user))
|
|
var/mob/living/carbon/C = user
|
|
C.visible_message(span_warning("[C] loses [C.p_their()] grip on [src], causing it to go off!"), span_userdanger("[src] slips out of your hands and goes off!"))
|
|
C.dropItemToGround(src, TRUE)
|
|
if(prob(10))
|
|
target = get_turf(user)
|
|
else
|
|
var/list/possible_targets = range(3,src)
|
|
target = pick(possible_targets)
|
|
discharge = 1
|
|
if(!discharge)
|
|
user.visible_message(span_danger("[user] fires \the [src]!"), \
|
|
span_danger("You fire \the [src]!"))
|
|
log_combat(user, target, "fired at", src)
|
|
var/turf/T = get_target(target, get_turf(src))
|
|
playsound(src, fire_sound, 50, TRUE)
|
|
fire_items(T, user)
|
|
if(pressure_setting >= 3 && iscarbon(user))
|
|
var/mob/living/carbon/C = user
|
|
C.visible_message(span_warning("[C] is thrown down by the force of the cannon!"), span_userdanger("[src] slams into your shoulder, knocking you down!"))
|
|
C.Paralyze(60)
|
|
|
|
/obj/item/pneumatic_cannon/proc/fire_items(turf/target, mob/user)
|
|
if(fire_mode == PCANNON_FIREALL)
|
|
for(var/obj/item/ITD in loadedItems) //Item To Discharge
|
|
if(!throw_item(target, ITD, user))
|
|
break
|
|
else
|
|
for(var/i in 1 to throw_amount)
|
|
if(!loadedItems.len)
|
|
break
|
|
var/atom/movable/I
|
|
if(fire_mode == PCANNON_FILO)
|
|
I = loadedItems[loadedItems.len]
|
|
else
|
|
I = loadedItems[1]
|
|
if(!throw_item(target, I, user))
|
|
break
|
|
|
|
/obj/item/pneumatic_cannon/proc/throw_item(turf/target, atom/movable/AM, mob/user)
|
|
if(!istype(AM))
|
|
return FALSE
|
|
loadedItems -= AM
|
|
if(isitem(AM))
|
|
var/obj/item/I = AM
|
|
loadedWeightClass -= I.w_class
|
|
else
|
|
loadedWeightClass--
|
|
AM.forceMove(get_turf(src))
|
|
AM.throw_at(target, pressure_setting * 10 * range_multiplier, pressure_setting * 2, user, spin_item)
|
|
return TRUE
|
|
|
|
/obj/item/pneumatic_cannon/proc/get_target(turf/target, turf/starting)
|
|
if(range_multiplier == 1)
|
|
return target
|
|
var/x_o = (target.x - starting.x)
|
|
var/y_o = (target.y - starting.y)
|
|
var/new_x = clamp((starting.x + (x_o * range_multiplier)), 0, world.maxx)
|
|
var/new_y = clamp((starting.y + (y_o * range_multiplier)), 0, world.maxy)
|
|
var/turf/newtarget = locate(new_x, new_y, starting.z)
|
|
return newtarget
|
|
|
|
/obj/item/pneumatic_cannon/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
if(loadedItems.Remove(gone))
|
|
var/obj/item/item = gone
|
|
if(istype(item))
|
|
loadedWeightClass -= item.w_class
|
|
else
|
|
loadedWeightClass--
|
|
else if (gone == tank)
|
|
tank = null
|
|
update_appearance()
|
|
|
|
/obj/item/pneumatic_cannon/ghetto //Obtainable by improvised methods; more gas per use, less capacity
|
|
name = "improvised pneumatic cannon"
|
|
desc = "A gas-powered, object-firing cannon made out of common parts."
|
|
force = 5
|
|
maxWeightClass = 10
|
|
gasPerThrow = 5
|
|
|
|
/obj/item/pneumatic_cannon/proc/updateTank(obj/item/tank/internals/thetank, removing = 0, mob/living/carbon/human/user)
|
|
if(removing)
|
|
if(!tank)
|
|
return
|
|
to_chat(user, span_notice("You detach \the [thetank] from \the [src]."))
|
|
tank.forceMove(user.drop_location())
|
|
user.put_in_hands(tank)
|
|
tank = null
|
|
if(!removing)
|
|
if(tank)
|
|
to_chat(user, span_warning("\The [src] already has a tank."))
|
|
return
|
|
if(!user.transferItemToLoc(thetank, src))
|
|
return
|
|
to_chat(user, span_notice("You hook \the [thetank] up to \the [src]."))
|
|
tank = thetank
|
|
update_appearance()
|
|
|
|
/obj/item/pneumatic_cannon/update_overlays()
|
|
. = ..()
|
|
if(!tank)
|
|
return
|
|
. += tank.icon_state
|
|
|
|
/obj/item/pneumatic_cannon/proc/fill_with_type(type, amount)
|
|
if(!ispath(type, /obj) && !ispath(type, /mob))
|
|
return FALSE
|
|
var/loaded = 0
|
|
for(var/i in 1 to amount)
|
|
var/obj/item/I = new type
|
|
if(!load_item(I, null))
|
|
qdel(I)
|
|
return loaded
|
|
loaded++
|
|
CHECK_TICK
|
|
|
|
/obj/item/pneumatic_cannon/pie
|
|
name = "pie cannon"
|
|
desc = "Load cream pie for optimal results."
|
|
force = 10
|
|
icon_state = "piecannon"
|
|
gasPerThrow = 0
|
|
checktank = FALSE
|
|
range_multiplier = 3
|
|
fire_mode = PCANNON_FIFO
|
|
throw_amount = 1
|
|
maxWeightClass = 150 //50 pies. :^)
|
|
needs_air = FALSE
|
|
clumsyCheck = FALSE
|
|
var/static/list/pie_typecache = typecacheof(/obj/item/food/pie)
|
|
|
|
/obj/item/pneumatic_cannon/pie/Initialize(mapload)
|
|
. = ..()
|
|
allowed_typecache = pie_typecache
|
|
|
|
/obj/item/pneumatic_cannon/pie/selfcharge
|
|
automatic = TRUE
|
|
selfcharge = TRUE
|
|
charge_type = /obj/item/food/pie/cream
|
|
maxWeightClass = 60 //20 pies.
|
|
|
|
/obj/item/pneumatic_cannon/pie/selfcharge/cyborg
|
|
name = "low velocity pie cannon"
|
|
automatic = FALSE
|
|
charge_type = /obj/item/food/pie/cream/nostun
|
|
maxWeightClass = 6 //2 pies
|
|
charge_ticks = 2 //4 second/pie
|
|
|
|
#undef PCANNON_FIREALL
|
|
#undef PCANNON_FILO
|
|
#undef PCANNON_FIFO
|
|
#undef LOW_PRESSURE
|
|
#undef MID_PRESSURE
|
|
#undef HIGH_PRESSURE
|