diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index c4c5d3cea4c..50dc5c3b467 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -140,7 +140,6 @@ var/global/list/datum/stack_recipe/wood_recipes = list ( \
new/datum/stack_recipe("wooden barricade", /obj/structure/barricade/wooden, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("bookcase", /obj/structure/bookcase, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("rifle stock", /obj/item/weaponcrafting/stock, 10, time = 40), \
- /*new/datum/stack_recipe("crossbow frame", /obj/item/weapon/crossbowframe, 5, time = 25, one_per_turf = 0, on_floor = 0), \ */
new/datum/stack_recipe("wooden door", /obj/structure/mineral_door/wood, 10, time = 20, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("coffin", /obj/structure/closet/coffin, 5, time = 15, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("easel", /obj/structure/easel, 3, one_per_turf = 1, on_floor = 1), \
diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm
index f676ac14d44..bbdb526c70a 100644
--- a/code/modules/crafting/recipes.dm
+++ b/code/modules/crafting/recipes.dm
@@ -200,4 +200,22 @@
time = 15
reqs = list(/obj/item/stack/sheet/wood = 1,
/obj/item/stack/cable_coil = 5)
- tools = list(/obj/item/weapon/kitchen/knife) // Gotta carve the wood into handles
\ No newline at end of file
+ tools = list(/obj/item/weapon/kitchen/knife) // Gotta carve the wood into handles
+
+/datum/table_recipe/makeshift_bolt
+ name = "Makeshift Bolt"
+ result = /obj/item/weapon/arrow/rod
+ time = 15
+ reqs = list(/obj/item/stack/rods = 1)
+ tools = list(/obj/item/weapon/weldingtool)
+
+/datum/table_recipe/crossbow
+ name = "Powered Crossbow"
+ result = /obj/item/weapon/gun/throw/crossbow
+ time = 300
+ reqs = list(/obj/item/stack/rods = 3,
+ /obj/item/stack/cable_coil = 10,
+ /obj/item/stack/sheet/mineral/plastic = 3,
+ /obj/item/stack/sheet/wood = 5)
+ tools = list(/obj/item/weapon/weldingtool,
+ /obj/item/weapon/screwdriver)
diff --git a/code/modules/projectiles/guns/projectile/rocket.dm b/code/modules/projectiles/guns/rocket.dm
similarity index 94%
rename from code/modules/projectiles/guns/projectile/rocket.dm
rename to code/modules/projectiles/guns/rocket.dm
index 6f0b217de47..72ab89bf40e 100644
--- a/code/modules/projectiles/guns/projectile/rocket.dm
+++ b/code/modules/projectiles/guns/rocket.dm
@@ -24,10 +24,12 @@
qdel(D)
rockets = null
+ return ..()
+
/obj/item/weapon/gun/rocketlauncher/update_icon()
return
-/obj/item/weapon/gun/rocketlauncher/attackby(obj/item/I as obj, mob/user as mob, params)
+/obj/item/weapon/gun/rocketlauncher/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/ammo_casing/rocket))
if(rockets.len < max_rockets)
user.drop_item()
@@ -52,6 +54,5 @@
log_game("[key_name_admin(user)] used a rocket launcher ([name]).")
rockets -= I
qdel(I)
- return
else
to_chat(usr, "[src] is empty.")
diff --git a/code/modules/projectiles/guns/throw.dm b/code/modules/projectiles/guns/throw.dm
new file mode 100644
index 00000000000..d1bcda59067
--- /dev/null
+++ b/code/modules/projectiles/guns/throw.dm
@@ -0,0 +1,93 @@
+/obj/item/weapon/gun/throw
+ name = "abstract item thrower"
+ desc = "This shouldn't be here, yell at a coder."
+ fire_sound = 'sound/weapons/punchmiss.ogg'
+ fire_sound_text = "thwock"
+
+ var/obj/item/to_launch
+ var/list/valid_projectile_type
+ var/max_capacity = 1
+ var/list/loaded_projectiles = list()
+
+ var/projectile_speed = 1
+ var/projectile_range = 1
+
+/obj/item/weapon/gun/throw/proc/notify_ammo_count(mob/user)
+ return
+
+/obj/item/weapon/gun/throw/proc/get_throwrange()
+ return projectile_speed
+
+/obj/item/weapon/gun/throw/proc/get_throwspeed()
+ return projectile_range
+
+/obj/item/weapon/gun/throw/proc/modify_projectile(obj/item/I, on_chamber = 0)
+ return
+
+/obj/item/weapon/gun/throw/proc/get_ammocount(include_loaded = 1)
+ var/count = loaded_projectiles.len
+ if(include_loaded && to_launch)
+ count++
+ return count
+
+/obj/item/weapon/gun/throw/examine(mob/user)
+ ..()
+ to_chat(user, "It is [to_launch ? "loaded with \a [to_launch]" : "not loaded"].")
+ notify_ammo_count(user)
+
+/obj/item/weapon/gun/throw/Destroy()
+ qdel(to_launch)
+ to_launch = null
+ for(var/atom/A in loaded_projectiles)
+ qdel(A)
+ loaded_projectiles = null
+
+ return ..()
+
+/obj/item/weapon/gun/throw/update_icon()
+ return
+
+/obj/item/weapon/gun/throw/attackby(obj/item/I, mob/user, params)
+ if(istype(I, valid_projectile_type) && !(I.flags & NODROP))
+ if(get_ammocount() < max_capacity)
+ user.drop_item()
+ I.forceMove(src)
+ loaded_projectiles += I
+ to_chat(user, "You load [I] into [src].")
+ if(!to_launch)
+ process_chamber()
+ notify_ammo_count(user)
+ else
+ to_chat(user, "[src] cannot hold any more projectiles.")
+ else
+ to_chat(user, "You cannot load [I] into [src]!")
+
+/obj/item/weapon/gun/throw/process_chamber()
+ if(!to_launch && loaded_projectiles.len)
+ to_launch = loaded_projectiles[1]
+ loaded_projectiles -= to_launch
+ return
+
+/obj/item/weapon/gun/throw/can_shoot()
+ if(to_launch)
+ return 1
+ return 0
+
+/obj/item/weapon/gun/throw/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override)
+ add_fingerprint(user)
+ if(semicd)
+ return
+
+ var/obj/item/I = to_launch
+ I.forceMove(get_turf(src))
+ to_launch = null
+ modify_projectile(I)
+ playsound(user, fire_sound, 50, 1)
+ I.throw_at(target, get_throwrange(), get_throwspeed(), user, 1)
+ message_admins("[key_name_admin(user)] fired \a [I] from a [src].")
+ log_game("[key_name_admin(user)] used \a [src].")
+ process_chamber()
+
+ semicd = 1
+ spawn(fire_delay)
+ semicd = 0
diff --git a/code/modules/projectiles/guns/throw/crossbow.dm b/code/modules/projectiles/guns/throw/crossbow.dm
new file mode 100644
index 00000000000..55e76bec6b7
--- /dev/null
+++ b/code/modules/projectiles/guns/throw/crossbow.dm
@@ -0,0 +1,181 @@
+#define XBOW_TENSION_20 "20%"
+#define XBOW_TENSION_40 "40%"
+#define XBOW_TENSION_60 "60%"
+#define XBOW_TENSION_80 "80%"
+#define XBOW_TENSION_FULL "100%"
+
+/obj/item/weapon/gun/throw/crossbow
+ name = "powered crossbow"
+ desc = "A modern twist on an old classic. Pick up that can."
+ icon_state = "crossbow"
+ item_state = "crossbow-solid"
+ fire_sound_text = "a solid thunk"
+ fire_delay = 25
+
+ valid_projectile_type = /obj/item/weapon/arrow
+
+ var/tension = 0
+ var/drawtension = 5
+ var/maxtension = 5
+ var/speed_multiplier = 5
+ var/range_multiplier = 3
+ var/obj/item/weapon/stock_parts/cell/cell = null // Used for firing superheated rods.
+ var/list/possible_tensions = list(XBOW_TENSION_20, XBOW_TENSION_40, XBOW_TENSION_60, XBOW_TENSION_80, XBOW_TENSION_FULL)
+
+/obj/item/weapon/gun/throw/crossbow/emp_act(severity)
+ if(cell && severity)
+ emp_act(severity)
+
+/obj/item/weapon/gun/throw/crossbow/update_icon()
+ if(!tension)
+ if(!to_launch)
+ icon_state = "[initial(icon_state)]"
+ else
+ icon_state = "[initial(icon_state)]-nocked"
+ else
+ icon_state = "[initial(icon_state)]-drawn"
+
+/obj/item/weapon/gun/throw/crossbow/examine(mob/user)
+ ..()
+ if(cell)
+ to_chat(user, "\A [cell] is mounted onto [src]. Battery cell charge: [cell.charge]/[cell.maxcharge]")
+ else
+ to_chat(user, "It has an empty mount for a battery cell.")
+
+/obj/item/weapon/gun/throw/crossbow/modify_projectile(obj/item/I, on_chamber = 0)
+ if(cell && on_chamber && istype(I, /obj/item/weapon/arrow/rod))
+ var/obj/item/weapon/arrow/rod/R = I
+ visible_message("[R] plinks and crackles as it begins to glow red-hot.")
+ R.throwforce = 15
+ R.superheated = 1
+ cell.use(500)
+
+/obj/item/weapon/gun/throw/crossbow/get_throwspeed()
+ return tension * speed_multiplier
+
+/obj/item/weapon/gun/throw/crossbow/get_throwrange()
+ return tension * range_multiplier
+
+/obj/item/weapon/gun/throw/crossbow/process_chamber()
+ ..()
+ if(to_launch)
+ modify_projectile(to_launch, 1)
+ update_icon()
+
+/obj/item/weapon/gun/throw/crossbow/attack_self(mob/living/user)
+ if(tension)
+ if(to_launch)
+ user.visible_message("[user] relaxes the tension on [src]'s string and removes [to_launch].","You relax the tension on [src]'s string and remove [to_launch].")
+ to_launch.forceMove(get_turf(src))
+ var/obj/item/weapon/arrow/A = to_launch
+ to_launch = null
+ A.removed()
+ process_chamber()
+ else
+ user.visible_message("[user] relaxes the tension on [src]'s string.","You relax the tension on [src]'s string.")
+ tension = 0
+ update_icon()
+ else
+ draw(user)
+
+/obj/item/weapon/gun/throw/crossbow/proc/draw(mob/living/user)
+ if(user.incapacitated())
+ return
+ if(!to_launch)
+ to_chat(user, "You can't draw [src] without a bolt nocked.")
+ return
+
+ user.visible_message("[user] begins to draw back the string of [src].","You begin to draw back the string of [src].")
+ if(do_after(user, 25 * drawtension, target = user))
+ tension = drawtension
+ user.visible_message("[usr] draws back the string of [src]!","[src] clunks as you draw the string to its maximum tension!!")
+ update_icon()
+
+/obj/item/weapon/gun/throw/crossbow/attackby(obj/item/W as obj, mob/user as mob, params)
+ if(istype(W, /obj/item/weapon/stock_parts/cell))
+ if(!cell)
+ user.drop_item()
+ W.loc = src
+ cell = W
+ to_chat(user, "You jam [cell] into [src] and wire it to the firing coil.")
+ process_chamber()
+ else
+ to_chat(user, "[src] already has a cell installed.")
+ else if(istype(W, /obj/item/weapon/screwdriver))
+ if(cell)
+ cell.loc = get_turf(src)
+ to_chat(user, "You jimmy [cell] out of [src] with [W].")
+ cell = null
+ else
+ to_chat(user, "[src] doesn't have a cell installed.")
+ else
+ ..()
+
+/obj/item/weapon/gun/throw/crossbow/verb/set_tension()
+ set name = "Adjust Tension"
+ set category = "Object"
+ set src in range(0)
+
+ var/mob/user = usr
+ if(user.incapacitated())
+ return
+ var/choice = input("Select tension to draw to:", "[src]", XBOW_TENSION_FULL) as null|anything in possible_tensions
+ if(!choice)
+ return
+
+ switch(choice)
+ if(XBOW_TENSION_20)
+ drawtension = Ceiling(0.2 * maxtension)
+ if(XBOW_TENSION_40)
+ drawtension = Ceiling(0.4 * maxtension)
+ if(XBOW_TENSION_60)
+ drawtension = Ceiling(0.6 * maxtension)
+ if(XBOW_TENSION_80)
+ drawtension = Ceiling(0.8 * maxtension)
+ if(XBOW_TENSION_FULL)
+ drawtension = maxtension
+
+/obj/item/weapon/gun/throw/crossbow/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override)
+ ..()
+ tension = 0
+ update_icon()
+/obj/item/weapon/gun/throw/crossbow/french
+ name = "french powered crossbow"
+ icon_state = "fcrossbow"
+ valid_projectile_type = /obj/item/weapon/reagent_containers/food/snacks/baguette
+
+/obj/item/weapon/gun/throw/crossbow/french/modify_projectile(obj/item/I, on_chamber = 0)
+ return
+
+/obj/item/weapon/arrow
+ name = "bolt"
+ desc = "It's got a tip for you - get the point?"
+ icon_state = "bolt"
+ item_state = "bolt"
+ throwforce = 20
+ w_class = 3.0
+ sharp = 1
+ edge = 0
+
+/obj/item/weapon/arrow/proc/removed() //Helper for metal rods falling apart.
+ return
+
+/obj/item/weapon/arrow/rod
+ name = "makeshift bolt"
+ desc = "A sharpened metal rod that can be fired out of a crossbow."
+ icon_state = "metal-rod"
+ throwforce = 8
+ var/superheated = 0
+
+/obj/item/weapon/arrow/rod/removed()
+ if(superheated) // The rod has been superheated - we don't want it to be useable when removed from the bow.
+ visible_message("[src] shatters into a scattering of overstressed metal shards as it leaves the crossbow.")
+ var/obj/item/weapon/shard/shrapnel/S = new /obj/item/weapon/shard/shrapnel
+ S.loc = get_turf(src)
+ qdel(src)
+
+#undef XBOW_TENSION_20
+#undef XBOW_TENSION_40
+#undef XBOW_TENSION_60
+#undef XBOW_TENSION_80
+#undef XBOW_TENSION_FULL
diff --git a/code/modules/projectiles/guns/throw/pielauncher.dm b/code/modules/projectiles/guns/throw/pielauncher.dm
new file mode 100644
index 00000000000..f6405f47194
--- /dev/null
+++ b/code/modules/projectiles/guns/throw/pielauncher.dm
@@ -0,0 +1,36 @@
+/obj/item/weapon/gun/throw/piecannon
+ name = "pie cannon"
+ desc = "A projectile weapon that fires pies."
+ icon_state = "piecannon"
+ w_class = 5
+ throw_speed = 2
+ throw_range = 3
+ force = 5
+
+ clumsy_check = 0
+ valid_projectile_type = /obj/item/weapon/reagent_containers/food/snacks/pie
+ max_capacity = 5
+ projectile_speed = 2
+ projectile_range = 30
+
+
+/obj/item/weapon/gun/throw/piecannon/New()
+ ..()
+ for(var/i in 1 to max_capacity)
+ var/obj/item/weapon/reagent_containers/food/snacks/pie/P = new /obj/item/weapon/reagent_containers/food/snacks/pie(src)
+ loaded_projectiles += P
+ process_chamber()
+
+/obj/item/weapon/gun/throw/piecannon/notify_ammo_count(mob/user)
+ to_chat(user, "[src] has [get_ammocount()] of [max_capacity] pies left.")
+
+/obj/item/weapon/gun/throw/piecannon/update_icon()
+ if(to_launch)
+ icon_state = "piecannon1"
+ else
+ icon_state = "piecannon0"
+ item_state = icon_state
+
+/obj/item/weapon/gun/throw/piecannon/process_chamber()
+ ..()
+ update_icon()
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index 38c9a9efd55..8f8809c504f 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -178,6 +178,7 @@
stun = 5
forcedodge = 1
nodamage = 1
+ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
hitsound = 'sound/items/bikehorn.ogg'
icon = 'icons/obj/harvest.dmi'
icon_state = "banana"
diff --git a/icons/mob/inhands/guns_lefthand.dmi b/icons/mob/inhands/guns_lefthand.dmi
index aeefc778615..1064c55f48b 100644
Binary files a/icons/mob/inhands/guns_lefthand.dmi and b/icons/mob/inhands/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/guns_righthand.dmi b/icons/mob/inhands/guns_righthand.dmi
index 392202c343d..93e30e4e812 100644
Binary files a/icons/mob/inhands/guns_righthand.dmi and b/icons/mob/inhands/guns_righthand.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 67313e46bfb..184d1461f3a 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index 1462d26e344..aaed17b8f94 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index 4d7e28b5626..45579065830 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi
index dc40e02aa5b..42c7ed6defc 100644
Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 97f182a3d3a..fb7631ab2ed 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1763,7 +1763,9 @@
#include "code\modules\projectiles\guns\medbeam.dm"
#include "code\modules\projectiles\guns\mounted.dm"
#include "code\modules\projectiles\guns\projectile.dm"
+#include "code\modules\projectiles\guns\rocket.dm"
#include "code\modules\projectiles\guns\syringe_gun.dm"
+#include "code\modules\projectiles\guns\throw.dm"
#include "code\modules\projectiles\guns\energy\laser.dm"
#include "code\modules\projectiles\guns\energy\nuclear.dm"
#include "code\modules\projectiles\guns\energy\pulse.dm"
@@ -1776,11 +1778,12 @@
#include "code\modules\projectiles\guns\projectile\launchers.dm"
#include "code\modules\projectiles\guns\projectile\pistol.dm"
#include "code\modules\projectiles\guns\projectile\revolver.dm"
-#include "code\modules\projectiles\guns\projectile\rocket.dm"
#include "code\modules\projectiles\guns\projectile\saw.dm"
#include "code\modules\projectiles\guns\projectile\shotgun.dm"
#include "code\modules\projectiles\guns\projectile\sniper.dm"
#include "code\modules\projectiles\guns\projectile\toy.dm"
+#include "code\modules\projectiles\guns\throw\crossbow.dm"
+#include "code\modules\projectiles\guns\throw\pielauncher.dm"
#include "code\modules\projectiles\projectile\beams.dm"
#include "code\modules\projectiles\projectile\bullets.dm"
#include "code\modules\projectiles\projectile\energy.dm"