Files
Aurora.3/code/modules/projectiles/guns/launcher/crossbow.dm
Cody BrittainandGitHub 23789d4113 Sorts out existing projectile weapons (#22059)
This PR is intended to sort out our existing guns into something
resembling organization. In addition, ancient decrepit guns are removed,
and abstract paths are labeled as such.

In addition, all guns are now standardized to refer to their model.
Considering the huge number of weapons we have, a `pistol` is not
descriptive enough.

NOTE: At over 200 files touched, this is being split. Not everything was
sorted, but this is already a monster to sort through. The rest can
wait.
2026-03-25 15:37:50 +00:00

366 lines
12 KiB
Plaintext

//AMMUNITION
/obj/item/arrow
name = "bolt"
desc = "It's got a tip for you - get the point?"
icon = 'icons/obj/weapons.dmi'
icon_state = "bolt"
item_state = "bolt"
throwforce = 8
w_class = WEIGHT_CLASS_NORMAL
sharp = TRUE
edge = FALSE
drop_sound = 'sound/items/drop/sword.ogg'
pickup_sound = SFX_PICKUP_SWORD
/obj/item/arrow/proc/removed() //Helper for metal rods falling apart.
return
/obj/item/arrow/quill
name = "alien quill"
desc = "A wickedly barbed quill from some bizarre animal."
icon = 'icons/obj/weapons.dmi'
icon_state = "quill"
item_state = "quill"
throwforce = 8
drop_sound = 'sound/items/drop/knife.ogg'
pickup_sound = 'sound/items/pickup/knife.ogg'
/obj/item/arrow/rod
name = "metal rod"
desc = "Don't cry for me, Orithena."
icon_state = "metal-rod"
/obj/item/arrow/rod/removed(mob/user)
if(throwforce == 15) // The rod has been superheated - we don't want it to be useable when removed from the bow.
to_chat(user, SPAN_WARNING("\The [src] shatters into a scattering of unusable overstressed metal shards as it leaves the crossbow."))
qdel(src)
/obj/item/gun/launcher/crossbow
name = "powered crossbow"
desc = "A 2457AD twist on an old classic. Pick up that can."
icon = 'icons/obj/guns/crossbow.dmi'
icon_state = "powered_crossbow"
item_state = "crossbow-solid"
fire_sound = 'sound/weapons/crossbow.ogg'
fire_sound_text = "a solid thunk"
fire_delay = 25
slot_flags = SLOT_BACK
needspin = FALSE
has_safety = FALSE
var/obj/item/bolt
var/tension = 0 // Current draw on the bow.
var/max_tension = 5 // Highest possible tension.
var/release_speed = 5 // Speed per unit of tension.
var/obj/item/cell/cell = null // Used for firing superheated rods.
var/current_user // Used to check if the crossbow has changed hands since being drawn.
var/draw_time = 20 // How long it takes to increase the draw on the bow by one "tension"
/obj/item/gun/launcher/crossbow/update_release_force()
release_force = tension*release_speed
/obj/item/gun/launcher/crossbow/consume_next_projectile(mob/user)
if(tension <= 0)
to_chat(user, SPAN_WARNING("\The [src] is not drawn back!"))
return null
return bolt
/obj/item/gun/launcher/crossbow/handle_post_fire(mob/user, atom/target)
bolt = null
tension = 0
update_icon()
..()
/obj/item/gun/launcher/crossbow/unique_action(mob/living/user)
if(tension)
if(bolt)
user.visible_message("<b>[user]</b> relaxes the tension on \the [src]'s string and removes \the [bolt].", SPAN_NOTICE("You relax the tension on \the [src]'s string and remove \the [bolt]."))
playsound(loc, 'sound/weapons/holster/tactiholsterout.ogg', 50, FALSE)
bolt.forceMove(get_turf(src))
var/obj/item/arrow/A = bolt
bolt = null
A.removed(user)
else
user.visible_message("<b>[user]</b> relaxes the tension on \the [src]'s string.", SPAN_NOTICE("You relax the tension on \the [src]'s string."))
playsound(loc, 'sound/weapons/holster/tactiholsterout.ogg', 50, FALSE)
tension = 0
update_icon()
else
draw(user)
/obj/item/gun/launcher/crossbow/proc/draw(var/mob/user)
if(!bolt)
to_chat(user, SPAN_WARNING("You don't have anything nocked to \the [src]."))
return
if(user.restrained())
return
current_user = user
user.visible_message("<b>[user]</b> begins to draw back the string of \the [src].", SPAN_NOTICE("You begin to draw back the string of \the [src]."))
tension = 1
while(bolt && tension && loc == current_user)
if(!do_after(user, draw_time)) //crossbow strings don't just magically pull back on their own.
user.visible_message("<b>[user]</b> stops drawing and relaxes the string of \the [src].", SPAN_WARNING("You stop drawing back and relax the string of \the [src]."))
playsound(loc, 'sound/weapons/holster/tactiholsterout.ogg', 50, FALSE)
tension = 0
update_icon()
return
//double check that the user hasn't removed the bolt in the meantime
if(!(bolt && tension && loc == current_user))
return
tension++
update_icon()
if(tension >= max_tension)
tension = max_tension
playsound(loc, 'sound/weapons/unjam.ogg', 50, FALSE)
to_chat(usr, SPAN_NOTICE("\The [src] clunks as you draw the string to its maximum tension!"))
return
user.visible_message("<b>[user]</b> draws back the string of \the [src]!", SPAN_NOTICE("You continue drawing back the string of \the [src]!"))
playsound(loc, 'sound/weapons/reload_clip.ogg', 50, FALSE)
/obj/item/gun/launcher/crossbow/attackby(obj/item/attacking_item, mob/user)
if(!bolt)
if (istype(attacking_item, /obj/item/arrow))
user.drop_from_inventory(attacking_item, src)
bolt = attacking_item
user.visible_message("<b>[user]</b> slides \the [bolt] into \the [src].", SPAN_NOTICE("You slide \the [bolt] into \the [src]."))
update_icon()
if(istype(attacking_item, /obj/item/arrow/rod) && attacking_item.throwforce < 15)
// un-heated converted arrow rod
superheat_rod(user)
return
else if(istype(attacking_item, /obj/item/stack/rods))
var/obj/item/stack/rods/R = attacking_item
if (R.use(1))
bolt = new /obj/item/arrow/rod(src)
bolt.fingerprintslast = src.fingerprintslast
bolt.forceMove(src)
update_icon()
user.visible_message("<b>[user]</b> jams \the [bolt] into \the [src].", SPAN_NOTICE("You jam \the [bolt] into \the [src]."))
superheat_rod(user)
return
if(istype(attacking_item, /obj/item/cell))
if(!cell)
user.drop_from_inventory(attacking_item, src)
cell = attacking_item
to_chat(user, SPAN_NOTICE("You jam \the [cell] into \the [src] and wire it to the firing coil."))
superheat_rod(user)
else
to_chat(user, SPAN_NOTICE("\The [src] already has a cell installed."))
else if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
if(cell)
var/obj/item/C = cell
C.forceMove(get_turf(user))
to_chat(user, SPAN_NOTICE("You jimmy \the [cell] out of \the [src] with \the [attacking_item]."))
cell = null
else
to_chat(user, SPAN_NOTICE("\The [src] doesn't have a cell installed."))
else if(istype(attacking_item, /obj/item/rfd))
attacking_item.attackby(src, user)
else
..()
/obj/item/gun/launcher/crossbow/proc/superheat_rod(var/mob/user)
if(!user || !cell || !bolt)
return
if(cell.charge < 500)
return
if(bolt.throwforce >= 15)
return
if(!istype(bolt,/obj/item/arrow/rod))
return
to_chat(user, SPAN_WARNING("\The [bolt] plinks and crackles as it begins to glow red-hot."))
bolt.throwforce = 20
bolt.icon_state = "metal-rod-superheated"
cell.use(500)
/obj/item/gun/launcher/crossbow/update_icon()
if(tension > 1)
icon_state = "[initial(icon_state)]-drawn"
else if(bolt)
icon_state = "[initial(icon_state)]-nocked"
else
icon_state = initial(icon_state)
// Crossbow construction.
/obj/item/crossbowframe
name = "crossbow frame"
desc = "A half-finished crossbow."
icon_state = "crossbowframe0"
item_state = "crossbow-solid"
icon = 'icons/obj/weapons.dmi'
var/buildstate = 0
/obj/item/crossbowframe/update_icon()
icon_state = "crossbowframe[buildstate]"
/obj/item/crossbowframe/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
. = ..()
switch(buildstate)
if(1)
. += "It has a loose rod frame in place."
if(2)
. += "It has a steel backbone welded in place."
if(3)
. += "It has a steel backbone and a cell mount installed."
if(4)
. += "It has a steel backbone, plastic limbs and a cell mount installed."
if(5)
. += "It has a steel cable loosely strung across the limbs."
/obj/item/crossbowframe/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/stack/rods))
if(buildstate == 0)
var/obj/item/stack/rods/R = attacking_item
if(R.use(3))
to_chat(user, SPAN_NOTICE("You assemble a backbone of rods around the wooden stock."))
buildstate++
update_icon()
else
to_chat(user, SPAN_NOTICE("You need at least three rods to complete this task."))
return
else if(attacking_item.tool_behaviour == TOOL_WELDER)
if(buildstate == 1)
var/obj/item/weldingtool/T = attacking_item
if(T.use(0,user))
if(!src || !T.isOn()) return
playsound(src.loc, 'sound/items/welder_pry.ogg', 100, 1)
to_chat(user, SPAN_NOTICE("You weld the rods into place."))
buildstate++
update_icon()
return
else if(attacking_item.tool_behaviour == TOOL_CABLECOIL)
var/obj/item/stack/cable_coil/C = attacking_item
if(buildstate == 2)
if(C.use(5))
to_chat(user, SPAN_NOTICE("You wire a crude cell mount into the top of the crossbow."))
buildstate++
update_icon()
else
to_chat(user, SPAN_NOTICE("You need at least five segments of cable coil to complete this task."))
return
else if(buildstate == 4)
if(C.use(5))
to_chat(user, SPAN_NOTICE("You string a steel cable across the crossbow's limbs."))
buildstate++
update_icon()
else
to_chat(user, SPAN_NOTICE("You need at least five segments of cable coil to complete this task."))
return
else if(istype(attacking_item,/obj/item/stack/material) && attacking_item.get_material_name() == MATERIAL_PLASTIC)
if(buildstate == 3)
var/obj/item/stack/material/P = attacking_item
if(P.use(3))
to_chat(user, SPAN_NOTICE("You assemble and install heavy plastic limbs onto the crossbow."))
buildstate++
update_icon()
else
to_chat(user, SPAN_NOTICE("You need at least three plastic sheets to complete this task."))
return
else if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
if(buildstate == 5)
to_chat(user, SPAN_NOTICE("You secure the crossbow's various parts."))
new /obj/item/gun/launcher/crossbow(get_turf(src))
qdel(src)
return
else
..()
/*////////////////////
// RFD Crossbow //
*/////////////////////
/obj/item/arrow/RFD
name = "flashforged bolt"
desc = "The ultimate ghetto deconstruction implement."
throwforce = 10
/obj/item/gun/launcher/crossbow/RFD
name = "Rapid-Fabrication-Device Crossbow"
desc = "A hacked together RFD turns an innocent tool into the penultimate destruction tool. Flashforges bolts using matter units when the string is drawn back."
icon = 'icons/obj/guns/rxb.dmi'
icon_state = "rxb"
item_state = "rxb"
slot_flags = null
draw_time = 10
has_safety = FALSE
var/stored_matter = 0
var/max_stored_matter = 40
var/boltcost = 10
/obj/item/gun/launcher/crossbow/RFD/proc/genBolt(var/mob/user)
if(stored_matter >= boltcost && !bolt)
bolt = new/obj/item/arrow/RFD(src)
stored_matter -= boltcost
to_chat(user, SPAN_NOTICE("The RFD flashforges a new bolt!"))
playsound(loc, 'sound/weapons/kinetic_reload.ogg', 50, FALSE)
update_icon()
else
to_chat(user, SPAN_WARNING("The \'Low Ammo\' light on the device blinks yellow."))
playsound(loc, 'sound/items/rfd_empty.ogg', 50, FALSE)
flick("[icon_state]-empty", src)
/obj/item/gun/launcher/crossbow/RFD/unique_action(mob/living/user as mob)
if(tension)
user.visible_message("<b>[user]</b> relaxes the tension on \the [src]'s string.", SPAN_NOTICE("You relax the tension on \the [src]'s string."))
playsound(loc, 'sound/weapons/holster/tactiholsterout.ogg', 50, FALSE)
tension = 0
update_icon()
else
genBolt(user)
draw(user)
/obj/item/gun/launcher/crossbow/RFD/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/rfd_ammo))
if((stored_matter + 10) > max_stored_matter)
to_chat(user, SPAN_NOTICE("The RFD can't hold that many additional matter-units."))
return
stored_matter += 10
qdel(attacking_item)
playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
to_chat(user, SPAN_NOTICE("The RFD now holds <b>[stored_matter]/[max_stored_matter]</b> matter-units."))
update_icon()
return
if(istype(attacking_item, /obj/item/arrow/RFD))
var/obj/item/arrow/RFD/A = attacking_item
if((stored_matter + 5) > max_stored_matter)
to_chat(user, SPAN_NOTICE("Unable to reclaim flashforged bolt. The RFD can't hold that many additional matter-units."))
return
stored_matter += 5
qdel(A)
playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
to_chat(user, SPAN_NOTICE("Flashforged bolt reclaimed. The RXD now holds <b>[stored_matter]/[max_stored_matter]</b> matter-units."))
update_icon()
return
/obj/item/gun/launcher/crossbow/RFD/update_icon()
overlays.Cut()
if(bolt)
overlays += "rxb-bolt"
var/ratio = 0
if(stored_matter < boltcost)
ratio = 0
else
ratio = stored_matter / max_stored_matter
ratio = max(round(ratio, 0.25) * 100, 25)
overlays += "rxb-[ratio]"
if(tension > 1)
icon_state = "rxb-drawn"
else
icon_state = "rxb"
/obj/item/gun/launcher/crossbow/RFD/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
. = ..()
. += "It currently holds <b>[stored_matter]/[max_stored_matter]</b> matter-units."