diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm
index 12c417054d22..fd768d45c5da 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm
@@ -1224,10 +1224,10 @@
dir = 4
},
/obj/structure/table/wood,
-/obj/item/storage/belt/quiver/ashwalker{
+/obj/item/storage/belt/quiver/weaver/ashwalker{
pixel_y = 2
},
-/obj/item/storage/belt/quiver/ashwalker{
+/obj/item/storage/belt/quiver/weaver/ashwalker{
pixel_y = -2
},
/obj/item/gun/ballistic/bow/ashen{
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_meteorite.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_meteorite.dmm
index 7d7515ffae0b..03eb0f3cdc21 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_meteorite.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_meteorite.dmm
@@ -277,7 +277,7 @@
"fO" = (
/obj/structure/fluff/grave/empty,
/obj/effect/mob_spawn/human/corpse/damaged/legioninfested,
-/obj/item/ammo_casing/caseless/arrow/ash{
+/obj/item/ammo_casing/reusable/arrow/ash{
pixel_x = 11;
pixel_y = 2
},
@@ -809,7 +809,7 @@
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/lavaland/surface/outdoors)
"ov" = (
-/obj/item/ammo_casing/caseless/arrow/ash{
+/obj/item/ammo_casing/reusable/arrow/ash{
pixel_x = -10;
pixel_y = 7
},
@@ -1755,23 +1755,23 @@
pixel_x = 1;
pixel_y = 1
},
-/obj/item/storage/belt/quiver/ashwalker{
+/obj/item/storage/belt/quiver/weaver/ashwalker{
pixel_x = -9;
pixel_y = -3
},
/obj/item/circular_saw/bone{
pixel_x = 7
},
-/obj/item/ammo_casing/caseless/arrow/ash,
-/obj/item/ammo_casing/caseless/arrow/ash{
+/obj/item/ammo_casing/reusable/arrow/ash,
+/obj/item/ammo_casing/reusable/arrow/ash{
pixel_x = 4;
pixel_y = -4
},
-/obj/item/ammo_casing/caseless/arrow/ash{
+/obj/item/ammo_casing/reusable/arrow/ash{
pixel_x = -5;
pixel_y = 2
},
-/obj/item/ammo_casing/caseless/arrow/ash{
+/obj/item/ammo_casing/reusable/arrow/ash{
pixel_x = 9;
pixel_y = -6
},
@@ -2449,11 +2449,11 @@
/area/lavaland/surface/outdoors)
"RF" = (
/obj/structure/marker_beacon,
-/obj/item/ammo_casing/caseless/arrow/ash{
+/obj/item/ammo_casing/reusable/arrow/ash{
pixel_x = 6;
pixel_y = -11
},
-/obj/item/ammo_casing/caseless/arrow/ash,
+/obj/item/ammo_casing/reusable/arrow/ash,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/lavaland/surface/outdoors)
"RG" = (
diff --git a/_maps/RandomRuins/StationRuins/maint/10x5/10x5_bamboo.dmm b/_maps/RandomRuins/StationRuins/maint/10x5/10x5_bamboo.dmm
index a8498db09ba3..8ea25ddeeb29 100644
--- a/_maps/RandomRuins/StationRuins/maint/10x5/10x5_bamboo.dmm
+++ b/_maps/RandomRuins/StationRuins/maint/10x5/10x5_bamboo.dmm
@@ -1,14 +1,14 @@
//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
"b" = (
-/obj/item/ammo_casing/caseless/arrow/bamboo{
+/obj/item/ammo_casing/reusable/arrow/bamboo{
pixel_x = 2;
pixel_y = -1
},
-/obj/item/ammo_casing/caseless/arrow/bamboo{
+/obj/item/ammo_casing/reusable/arrow/bamboo{
pixel_x = -3;
pixel_y = 2
},
-/obj/item/ammo_casing/caseless/arrow/bamboo{
+/obj/item/ammo_casing/reusable/arrow/bamboo{
pixel_x = -7;
pixel_y = 5
},
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index bb50fdf4f295..d609fefc1e26 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -201,6 +201,15 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
#define REFLECT_NORMAL (1<<0)
#define REFLECT_FAKEPROJECTILE (1<<1)
+// Casing Flags //
+/* Flags for /obj/item/ammo_casing */
+/// If the ammo casing doesn't have a different live and spent icon, it will just use the non-live sprite instead
+#define CASINGFLAG_NO_LIVE_SPRITE (1<<0)
+/// If the ammo casing should be force eject when fired even when the gun is not semi-auto, useful for casings that delete themselves. Only works with balistic weapons
+#define CASINGFLAG_FORCE_CLEAR_CHAMBER (1<<1)
+/// If the ammo casing should not spin when thrown
+#define CASINGFLAG_NOT_HEAVY_METAL (1<<2)
+
//Object/Item sharpness
#define SHARP_NONE 0
#define SHARP_EDGED 1
@@ -250,15 +259,15 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
//bullet_act() return values
/// It's a successful hit, whatever that means in the context of the thing it's hitting.
-#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting.
+#define BULLET_ACT_HIT (1<<0) //It's a successful hit, whatever that means in the context of the thing it's hitting.
/// It's a blocked hit, whatever that means in the context of the thing it's hitting.
-#define BULLET_ACT_BLOCK "BLOCK"
+#define BULLET_ACT_BLOCK (1<<1)
/// It pierces through the object regardless of the bullet being piercing by default.
-#define BULLET_ACT_FORCE_PIERCE "PIERCE"
+#define BULLET_ACT_FORCE_PIERCE (1<<2)
/// It hit us but it should hit something on the same turf too. Usually used for turfs.
-#define BULLET_ACT_TURF "TURF"
+#define BULLET_ACT_TURF (1<<3)
/// It hit something, but it should just keep going until it hit something else
-#define BULLET_ACT_PENETRATE "PENETRATE"
+#define BULLET_ACT_PENETRATE (1<<4)
// Weather immunities //
#define WEATHER_STORM "storm"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
index ff4622c7a168..3b0e66067243 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
@@ -141,6 +141,10 @@
///Called on user, from base of /datum/strippable_item/try_(un)equip() (atom/target, obj/item/equipping?)
#define COMSIG_TRY_STRIP "try_strip"
#define COMPONENT_CANT_STRIP (1<<0)
+///Called on user by /mob/verb/quick_equip() (atom/target, obj/item/equipping?)
+#define COMSIG_MOB_QUICK_EQUIP "quick_equip"
+ /// return this if you want to stop the rest of the quick equip logic
+ #define COMPONENT_BLOCK_QUICK_EQUIP (1<<0)
///From /datum/component/creamed/Initialize()
#define COMSIG_MOB_CREAMED "mob_creamed"
///From /obj/item/gun/proc/check_botched()
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 77a76f9fc773..4fb8922c3088 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -146,6 +146,24 @@
///from base of obj/item/hit_reaction(): (list/args)
#define COMSIG_ITEM_HIT_REACT "item_hit_react"
#define COMPONENT_HIT_REACTION_BLOCK (1<<0)
+
+/// Called before an item is embedded (mob/living/carbon/target = carbon that it is getting embedded into)
+#define COMSIG_ITEM_EMBEDDED "mob_carbon_embedded"
+ // Prevents the embed
+ #define COMSIG_ITEM_BLOCK_EMBED (1 << 0)
+
+/// Called before an item is removed from being embedded (mob/living/carbon/embedded = carbon that is currently embedded)
+#define COMSIG_ITEM_EMBED_REMOVAL "mob_carbon_embed_removal"
+ // Prevents the removal of the embed
+ #define COMSIG_ITEM_BLOCK_EMBED_REMOVAL (1 << 0)
+ // Qdels the object when it is removed instead of droping it
+ #define COMSIG_ITEM_QDEL_EMBED_REMOVAL (1 << 1)
+
+/// Called every life tick for the embedded mob when the item is embedded (mob/living/carbon/embedded = carbon that is currently embedded)
+#define COMSIG_ITEM_EMBED_TICK "mob_carbon_embed_tick"
+ // Prevents the rest of the tick logic for the item from proccessing
+ #define COMSIG_ITEM_BLOCK_EMBED_TICK (1 << 0)
+
///from base of item/sharpener/attackby(): (amount, max)
#define COMSIG_ITEM_SHARPEN_ACT "sharpen_act"
#define COMPONENT_BLOCK_SHARPEN_APPLIED (1<<0)
diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm
index de3fb8cf58ff..4f50fc79d426 100644
--- a/code/__DEFINES/inventory.dm
+++ b/code/__DEFINES/inventory.dm
@@ -38,7 +38,8 @@
#define ITEM_SLOT_NECK (1<<13)
#define ITEM_SLOT_HANDS (1<<14)
#define ITEM_SLOT_BACKPACK (1<<15)
-#define ITEM_SLOT_SUIT (1<<16) //yogs: suit storage inventory check
+/// Prevents items from being stored in suit storage
+#define ITEM_SLOT_DENY_S_STORE (1<<16)
//SLOTS
#define SLOT_BACK 1
@@ -100,8 +101,6 @@
. = ITEM_SLOT_HANDS
if(SLOT_IN_BACKPACK)
. = ITEM_SLOT_BACKPACK
- if(SLOT_S_STORE) //yogs: suit storage inventory check
- . = ITEM_SLOT_SUIT //yogs
//Bit flags for the flags_inv variable, which determine when a piece of clothing hides another. IE a helmet hiding glasses.
diff --git a/code/__DEFINES/spells.dm b/code/__DEFINES/spells.dm
new file mode 100644
index 000000000000..87541bde31f3
--- /dev/null
+++ b/code/__DEFINES/spells.dm
@@ -0,0 +1,11 @@
+// Invocation Defines
+#define SPELL_INVOCATION_NONE 0 // Doesn't have an invocation
+#define SPELL_INVOCATION_SAY 1 // Forces the user to say the invocation message
+#define SPELL_INVOCATION_WHISPER 2 // Forces the user to whisper the invocation message
+#define SPELL_INVOCATION_EMOTE 3 // Forces the user to emote the invocation message
+#define SPELL_INVOCATION_MESSAGE 4 // The user creates a visible message, with the invocation being visaible to others and invocation_emote_self being visible to the user
+
+// Charge Type Defines
+#define SPELL_CHARGE_TYPE_RECHARGE 0 // Spell needs to recharge between uses
+#define SPELL_CHARGE_TYPE_CHARGES 1 // Spell has a set number of uses
+#define SPELL_CHARGE_TYPE_HOLDERVAR 2 // Spell adjusts the users 'holder_var_type' by 'holder_var_amount' on use
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 44f39c625768..63252f4aa02d 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -14,16 +14,17 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/choice
var/next_page = FALSE
var/tooltips = FALSE
+ var/active = FALSE
/atom/movable/screen/radial/slice/MouseEntered(location, control, params)
. = ..()
- icon_state = "radial_slice_focus"
+ icon_state = "radial_slice[active ? "_active" : ""]_focus"
if(tooltips)
openToolTip(usr, src, params, title = name, content = desc)
/atom/movable/screen/radial/slice/MouseExited(location, control, params)
. = ..()
- icon_state = "radial_slice"
+ icon_state = "radial_slice[active ? "_active" : ""]"
if(tooltips)
closeToolTip(usr)
@@ -217,6 +218,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/datum/radial_menu_choice/choice_datum = choice_datums[choice_id]
if(choice_datum && istext(choice_datum.info))
E.desc = choice_datum.info
+ if(choice_datum.active)
+ E.active = TRUE
+ E.icon_state = "radial_slice_active"
E.choice = choice_id
E.maptext = null
@@ -349,6 +353,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
/// If provided, will display an info button that will put this text in your chat
var/info
+ /// If the radial slice should use the active icon
+ var/active = FALSE
+
/datum/radial_menu_choice/Destroy(force, ...)
. = ..()
QDEL_NULL(image)
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index 587e986da296..5a85d21e0824 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -42,6 +42,9 @@
for(var/i in 1 to amount)
new sinew (T)
meat.guaranteed_butcher_results.Remove(sinew)
+ var/mob/living/carbon/C = meat
+ if(istype(C))
+ C.remove_all_embedded_objects()
if(butcher)
meat.visible_message(span_notice("[butcher] butchers [meat]."))
ButcherEffects(meat)
diff --git a/code/datums/components/crafting/antag.dm b/code/datums/components/crafting/antag.dm
index e724ecb0d309..53be3491f07c 100644
--- a/code/datums/components/crafting/antag.dm
+++ b/code/datums/components/crafting/antag.dm
@@ -104,39 +104,77 @@
always_available = FALSE
/datum/crafting_recipe/bola_arrow
- name = "Bola arrow"
- result = /obj/item/ammo_casing/caseless/arrow/bola
- time = 3 SECONDS
- reqs = list(/obj/item/ammo_casing/caseless/arrow = 1,
- /obj/item/stack/sheet/silk = 1,
+ name = "Bola Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow = 1,
+ /obj/item/restraints/handcuffs/cable = 1,
/obj/item/restraints/legcuffs/bola = 1)
- parts = list(/obj/item/ammo_casing/caseless/arrow = 1, /obj/item/restraints/legcuffs/bola = 1)
+ blacklist = list(/obj/item/ammo_casing/reusable/arrow/toy)
+ parts = list(/obj/item/ammo_casing/reusable/arrow = 1, /obj/item/restraints/handcuffs/cable = 1, /obj/item/restraints/legcuffs/bola = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
always_available = FALSE
-/*
/datum/crafting_recipe/explosive_arrow
- name = "Explosive arrow"
- result = /obj/item/ammo_casing/caseless/arrow/explosive
- time = 3 SECONDS
- reqs = list(/obj/item/ammo_casing/caseless/arrow = 1,
- /obj/item/stack/sheet/silk = 1,
+ name = "Explosive Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow = 1,
+ /obj/item/restraints/handcuffs/cable = 1,
/obj/item/grenade = 1)
- parts = list(/obj/item/ammo_casing/caseless/arrow = 1, /obj/item/grenade = 1)
+ blacklist = list(/obj/item/ammo_casing/reusable/arrow/toy)
+ parts = list(/obj/item/ammo_casing/reusable/arrow = 1, /obj/item/restraints/handcuffs/cable = 1, /obj/item/grenade = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+ always_available = FALSE
+
+/datum/crafting_recipe/flaming_arrow
+ name = "Fire Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow = 1,
+ /obj/item/stack/sheet/cloth = 1,
+ /datum/reagent/fuel = 10)
+ blacklist = list(/obj/item/ammo_casing/reusable/arrow/toy)
+ parts = list(/obj/item/ammo_casing/reusable/arrow = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+ always_available = FALSE
+
+/datum/crafting_recipe/syringe_arrow
+ name = "Syringe Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow = 1,
+ /obj/item/restraints/handcuffs/cable = 1,
+ /obj/item/reagent_containers/syringe = 1)
+ blacklist = list(/obj/item/ammo_casing/reusable/arrow/toy)
+ parts = list(/obj/item/ammo_casing/reusable/arrow = 1, /obj/item/restraints/handcuffs/cable = 1, /obj/item/reagent_containers/syringe = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+ always_available = FALSE
+/*
+/datum/crafting_recipe/supermatter_sliver_arrow
+ name = "Supermatter Sliver Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/supermatter/sliver
+ time = 5 SECONDS // Need to be very careful
+ reqs = list(/obj/item/nuke_core/supermatter_sliver = 1,
+ /obj/item/scalpel/supermatter = 1, // Needed so the sliver doesn't destroy the rod and so atmos techs can't mass produce instant dust arrows
+ /obj/item/stack/rods = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+ always_available = FALSE
+
+/datum/crafting_recipe/singularity_shard_arrow
+ name = "Singularity Shard Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/singulo
+ time = 5 SECONDS
+ reqs = list(/obj/item/singularity_shard = 1,
+ /obj/item/stack/rods = 1,
+ /obj/item/stack/cable_coil = 3)
+ parts = list(/obj/item/singularity_shard = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
always_available = FALSE
*/
-
-/datum/crafting_recipe/flaming_arrow
- name = "Flaming arrow"
- result = /obj/item/ammo_casing/caseless/arrow/flaming
- time = 3 SECONDS
- reqs = list(/obj/item/ammo_casing/caseless/arrow = 1,
- /obj/item/stack/sheet/cloth = 1,
- /datum/reagent/fuel = 10)
- parts = list(/obj/item/ammo_casing/caseless/arrow = 1)
- category = CAT_WEAPONRY
- subcategory = CAT_AMMO
- always_available = FALSE
diff --git a/code/datums/components/crafting/tailoring.dm b/code/datums/components/crafting/tailoring.dm
index 51e7a3a89786..a456e389a16f 100644
--- a/code/datums/components/crafting/tailoring.dm
+++ b/code/datums/components/crafting/tailoring.dm
@@ -541,7 +541,7 @@
subcategory = CAT_EQUIPMENT
/datum/crafting_recipe/resinband
- name = "Resin armband"
+ name = "Resin Armband"
result = /obj/item/clothing/accessory/resinband
time = 2 SECONDS
reqs = list(/obj/item/stack/sheet/ashresin = 3)
@@ -573,11 +573,18 @@
subcategory = CAT_EQUIPMENT
/datum/crafting_recipe/quiver
- name = "Quiver"
+ name = "Leather Quiver"
result = /obj/item/storage/belt/quiver
time = 8 SECONDS
- reqs = list(/obj/item/stack/sheet/leather = 3,
- /obj/item/stack/sheet/sinew = 4)
+ reqs = list(/obj/item/stack/sheet/leather = 4)
+ category = CAT_APPAREL
+ subcategory = CAT_EQUIPMENT
+
+/datum/crafting_recipe/chitinquiver
+ name = "Chitin Quiver"
+ result = /obj/item/storage/belt/quiver/weaver
+ time = 8 SECONDS
+ reqs = list(/obj/item/stack/sheet/animalhide/weaver_chitin = 4) //Just one spider!
category = CAT_APPAREL
subcategory = CAT_EQUIPMENT
@@ -589,3 +596,12 @@
/obj/item/stack/sheet/animalhide/goliath_hide = 1)
category = CAT_APPAREL
subcategory = CAT_EQUIPMENT
+
+/datum/crafting_recipe/chitingloves
+ name = "Weaver Chitin Gloves"
+ result = /obj/item/clothing/gloves/fingerless/weaver
+ time = 2 SECONDS
+ reqs = list(/obj/item/stack/sheet/leather = 1,
+ /obj/item/stack/sheet/animalhide/weaver_chitin = 3) //Also just one spider!
+ category = CAT_APPAREL
+ subcategory = CAT_EQUIPMENT
diff --git a/code/datums/components/crafting/weapons.dm b/code/datums/components/crafting/weapons.dm
index e80e7c42a58e..ea03e590a527 100644
--- a/code/datums/components/crafting/weapons.dm
+++ b/code/datums/components/crafting/weapons.dm
@@ -145,7 +145,7 @@
reqs = list(/obj/item/pipe = 5,
/obj/item/stack/sheet/plastic = 5,
/obj/item/weaponcrafting/silkstring = 1)
- time = 45 SECONDS
+ time = 9 SECONDS
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
@@ -155,7 +155,7 @@
reqs = list(/obj/item/pipe = 5,
/obj/item/stack/tape = 3,
/obj/item/stack/cable_coil = 10)
- time = 45 SECONDS
+ time = 10 SECONDS
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
@@ -165,7 +165,7 @@
reqs = list(/obj/item/stack/sheet/mineral/wood = 8,
/obj/item/stack/sheet/metal = 2,
/obj/item/weaponcrafting/silkstring = 1)
- time = 12 SECONDS
+ time = 7 SECONDS
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
@@ -178,7 +178,7 @@
/obj/item/weaponcrafting/receiver = 1,
/obj/item/weaponcrafting/stock = 1)
tools = list(TOOL_SCREWDRIVER)
- time = 16 SECONDS
+ time = 10 SECONDS
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
@@ -264,7 +264,7 @@
subcategory = CAT_WEAPON
/datum/crafting_recipe/goliathshield
- name = "Goliath shield"
+ name = "Goliath Shield"
result = /obj/item/shield/riot/goliath
time = 6 SECONDS
reqs = list(/obj/item/stack/sheet/bone = 4,
@@ -284,12 +284,22 @@
/datum/crafting_recipe/bone_bow
name = "Bone Bow"
result = /obj/item/gun/ballistic/bow/ashen
- time = 20 SECONDS
+ time = 8 SECONDS
reqs = list(/obj/item/stack/sheet/bone = 8,
/obj/item/stack/sheet/sinew = 4)
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
+/datum/crafting_recipe/bone_crossbow
+ name = "Bone Crossbow"
+ result = /obj/item/gun/ballistic/bow/crossbow/ashen
+ time = 10 SECONDS
+ reqs = list(/obj/item/gun/ballistic/bow/ashen = 1,
+ /obj/item/claymore/bone = 1,
+ /obj/item/stack/sheet/sinew = 3)
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
/datum/crafting_recipe/bonedagger
name = "Bone Dagger"
result = /obj/item/kitchen/knife/combat/bone
@@ -460,37 +470,37 @@
/datum/crafting_recipe/wood_arrow
name = "Wood Arrow"
- result = /obj/item/ammo_casing/caseless/arrow/wood
+ result = /obj/item/ammo_casing/reusable/arrow/wood
time = 3 SECONDS
reqs = list(/obj/item/stack/sheet/mineral/wood = 1,
- /obj/item/stack/sheet/silk = 1,
- /obj/item/stack/rods = 1) //1 metal sheet = 2 rods= 2 arrows
+ /obj/item/stack/sheet/cloth = 1,
+ /obj/item/stack/rods = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
/datum/crafting_recipe/ashen_arrow
- name = "Fire hardened arrow"
- result = /obj/item/ammo_casing/caseless/arrow/ash
+ name = "Ashen Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/ash
tools = list(TOOL_WELDER)
- time = 3 SECONDS
- reqs = list(/obj/item/ammo_casing/caseless/arrow/wood = 1)
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow/wood = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
/datum/crafting_recipe/bone_tipped_arrow
- name = "Bone Tipped Arrow"
- result = /obj/item/ammo_casing/caseless/arrow/bone_tipped
- time = 3 SECONDS
- reqs = list(/obj/item/stack/sheet/bone = 1,
- /obj/item/stack/sheet/sinew = 1,
- /obj/item/ammo_casing/caseless/arrow/ash = 1)
+ name = "Bone-Tipped Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/bone_tipped
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow/ash = 1,
+ /obj/item/stack/sheet/bone = 1,
+ /obj/item/stack/sheet/sinew = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
/datum/crafting_recipe/bone_arrow
name = "Bone Arrow"
- result = /obj/item/ammo_casing/caseless/arrow/bone
- time = 3 SECONDS
+ result = /obj/item/ammo_casing/reusable/arrow/bone
+ time = 1.5 SECONDS
reqs = list(/obj/item/stack/sheet/bone = 1,
/obj/item/stack/sheet/sinew = 1)
category = CAT_WEAPONRY
@@ -498,37 +508,37 @@
/datum/crafting_recipe/chitin_arrow
name = "Chitin Arrow"
- result = /obj/item/ammo_casing/caseless/arrow/chitin
- time = 3 SECONDS
- reqs = list(/obj/item/ammo_casing/caseless/arrow/bone = 1,
+ result = /obj/item/ammo_casing/reusable/arrow/chitin
+ time = 1.5 SECONDS
+ reqs = list(/obj/item/ammo_casing/reusable/arrow/bone = 1,
/obj/item/stack/sheet/sinew = 1,
/obj/item/stack/sheet/ashresin = 1,
- /obj/item/stack/sheet/animalhide/weaver_chitin = 2)
+ /obj/item/stack/sheet/animalhide/weaver_chitin = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
/datum/crafting_recipe/bamboo_arrow
name = "Bamboo Arrow"
- result = /obj/item/ammo_casing/caseless/arrow/bamboo
- time = 3 SECONDS
+ result = /obj/item/ammo_casing/reusable/arrow/bamboo
+ time = 1.5 SECONDS
reqs = list(/obj/item/stack/sheet/mineral/bamboo = 2)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
/datum/crafting_recipe/bronze_arrow
- name = "Bronze arrow"
- result = /obj/item/ammo_casing/caseless/arrow/bronze
- time = 3 SECONDS
+ name = "Bronze Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/bronze
+ time = 1.5 SECONDS
reqs = list(/obj/item/stack/sheet/mineral/wood = 1,
- /obj/item/stack/tile/bronze = 1,
- /obj/item/stack/sheet/silk = 1)
+ /obj/item/stack/sheet/cloth = 1,
+ /obj/item/stack/tile/bronze = 1)
category = CAT_WEAPONRY
subcategory = CAT_AMMO
/datum/crafting_recipe/glass_arrow
- name = "Glass arrow"
- result = /obj/item/ammo_casing/caseless/arrow/glass
- time = 3 SECONDS
+ name = "Glass Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/glass
+ time = 1.5 SECONDS
reqs = list(/obj/item/shard = 1,
/obj/item/stack/rods = 1,
/obj/item/stack/cable_coil = 3)
@@ -536,9 +546,9 @@
subcategory = CAT_AMMO
/datum/crafting_recipe/plasma_glass_arrow
- name = "Plasma glass arrow"
- result = /obj/item/ammo_casing/caseless/arrow/glass/plasma
- time = 3 SECONDS
+ name = "Plasmaglass Arrow"
+ result = /obj/item/ammo_casing/reusable/arrow/glass/plasma
+ time = 1.5 SECONDS
reqs = list(/obj/item/shard/plasma = 1,
/obj/item/stack/rods = 1,
/obj/item/stack/cable_coil = 3)
diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm
index 76648771097c..4cdb72a2eb3f 100644
--- a/code/datums/components/storage/concrete/_concrete.dm
+++ b/code/datums/components/storage/concrete/_concrete.dm
@@ -137,6 +137,7 @@
//Being destroyed, just move to nullspace now (so it's not in contents for the icon update)
AM.moveToNullspace()
refresh_mob_views()
+ SEND_SIGNAL(parent, COMSIG_STORAGE_REMOVED, AM, new_location)
if(isobj(parent))
var/obj/O = parent
O.update_icon()
@@ -189,6 +190,7 @@
parent.add_fingerprint(M)
if(!prevent_warning)
mob_item_insertion_feedback(usr, M, I)
+ SEND_SIGNAL(parent, COMSIG_STORAGE_INSERTED, I, M)
update_icon()
return TRUE
diff --git a/code/datums/embedding_behavior.dm b/code/datums/embedding_behavior.dm
index d4181f94344c..ee57265ba072 100644
--- a/code/datums/embedding_behavior.dm
+++ b/code/datums/embedding_behavior.dm
@@ -8,7 +8,8 @@
embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
- embedded_ignore_throwspeed_threshold = FALSE)
+ embedded_ignore_throwspeed_threshold = FALSE,
+ embedded_bleed_rate = 0.5)
. = locate(EMBEDID)
if (!.)
. = new /datum/embedding_behavior(embed_chance, embedded_fall_chance, embedded_pain_chance, embedded_pain_multiplier, embedded_fall_pain_multiplier, embedded_impact_pain_multiplier, embedded_unsafe_removal_pain_multiplier, embedded_unsafe_removal_time, embedded_ignore_throwspeed_threshold)
@@ -23,6 +24,7 @@
var/embedded_unsafe_removal_pain_multiplier //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class)
var/embedded_unsafe_removal_time //A time in ticks, multiplied by the w_class.
var/embedded_ignore_throwspeed_threshold //if we don't give a damn about EMBED_THROWSPEED_THRESHOLD
+ var/embedded_bleed_rate // How much bleeding is caused while this is embeded
/datum/embedding_behavior/New(embed_chance = EMBED_CHANCE,
embedded_fall_chance = EMBEDDED_ITEM_FALLOUT,
@@ -32,7 +34,8 @@
embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
- embedded_ignore_throwspeed_threshold = FALSE)
+ embedded_ignore_throwspeed_threshold = FALSE,
+ embedded_bleed_rate = 0.5)
src.embed_chance = embed_chance
src.embedded_fall_chance = embedded_fall_chance
src.embedded_pain_chance = embedded_pain_chance
diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm
index a2bf1dc56b4a..2eab0f87ac7f 100644
--- a/code/datums/mutations/actions.dm
+++ b/code/datums/mutations/actions.dm
@@ -96,7 +96,7 @@ obj/effect/proc_holder/spell/aimed/firebreath/fire_projectile(mob/user)
antimagic_allowed = TRUE
charge_max = 600
invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
action_icon_state = "void_magnet"
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5e40cc94ea62..0ef80399bd82 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -432,7 +432,9 @@
* Default behaviour is to send the COMSIG_ATOM_BULLET_ACT and then call on_hit() on the projectile
*/
/atom/proc/bullet_act(obj/item/projectile/P, def_zone)
- SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
+ var/sig_return = SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
+ if(sig_return != NONE)
+ return sig_return
. = P.on_hit(src, 0, def_zone)
///Return true if we're inside the passed in atom
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index a0e5a6055251..2ae6d6d70ca2 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -137,6 +137,7 @@
id = /obj/item/card/id/syndicate/nuke_leader
gloves = /obj/item/clothing/gloves/krav_maga/combatglovesplus
r_hand = /obj/item/nuclear_challenge
+ neck = /obj/item/clothing/neck/cloak/nukie
command_radio = TRUE
/datum/outfit/syndicate/no_crystals
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 99f6717d8c6f..8e830026b2db 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -255,7 +255,8 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
. += "[src] is made of cold-resistant materials."
if(resistance_flags & FIRE_PROOF)
. += "[src] is made of fire-retardant materials."
-
+ if(taped)
+ . += "[src] seems to be covered in tape."
if(!user.research_scanner)
return
@@ -905,24 +906,6 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
if(ismob(loc))
var/mob/mob_loc = loc
mob_loc.regenerate_icons()
-
-/**
- * Called when this object is first embedded into a carbon
- */
-/obj/item/proc/on_embed(mob/living/carbon/human/embedde, obj/item/bodypart/part)
- return TRUE
-
-/**
- * Called when this object is no longer embedded into a carbon
- */
-/obj/item/proc/on_embed_removal(mob/living/carbon/human/embedde)
- return TRUE
-
-/**
- * Called every life tick when the object is embedded in a carbon
- */
-/obj/item/proc/embed_tick(mob/living/carbon/human/embedde, obj/item/bodypart/part)
- return
/obj/item/proc/do_pickup_animation(atom/target)
if(!istype(loc, /turf))
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index 057959175173..bcbe5577e106 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -543,7 +543,7 @@
/obj/item/book/granter/crafting_recipe/weapons
name = "makeshift weapons 101"
desc = "A book filled with directions on how to make various weaponry."
- crafting_recipe_types = list(/datum/crafting_recipe/metal_baseball_bat, /datum/crafting_recipe/lance, /datum/crafting_recipe/knifeboxing, /datum/crafting_recipe/pipebomb, /datum/crafting_recipe/makeshiftpistol, /datum/crafting_recipe/makeshiftmagazine, /datum/crafting_recipe/makeshiftsuppressor, /datum/crafting_recipe/makeshiftcrowbar, /datum/crafting_recipe/makeshiftwrench, /datum/crafting_recipe/makeshiftwirecutters, /datum/crafting_recipe/makeshiftweldingtool, /datum/crafting_recipe/makeshiftmultitool, /datum/crafting_recipe/makeshiftscrewdriver, /datum/crafting_recipe/makeshiftknife, /datum/crafting_recipe/makeshiftpickaxe, /datum/crafting_recipe/makeshiftradio, /datum/crafting_recipe/bola_arrow, /datum/crafting_recipe/flaming_arrow, /datum/crafting_recipe/makeshiftemag)
+ crafting_recipe_types = list(/datum/crafting_recipe/metal_baseball_bat, /datum/crafting_recipe/lance, /datum/crafting_recipe/knifeboxing, /datum/crafting_recipe/pipebomb, /datum/crafting_recipe/makeshiftpistol, /datum/crafting_recipe/makeshiftmagazine, /datum/crafting_recipe/makeshiftsuppressor, /datum/crafting_recipe/makeshiftcrowbar, /datum/crafting_recipe/makeshiftwrench, /datum/crafting_recipe/makeshiftwirecutters, /datum/crafting_recipe/makeshiftweldingtool, /datum/crafting_recipe/makeshiftmultitool, /datum/crafting_recipe/makeshiftscrewdriver, /datum/crafting_recipe/makeshiftknife, /datum/crafting_recipe/makeshiftpickaxe, /datum/crafting_recipe/makeshiftradio, /datum/crafting_recipe/bola_arrow, /datum/crafting_recipe/explosive_arrow, /datum/crafting_recipe/syringe_arrow, /datum/crafting_recipe/flaming_arrow, /datum/crafting_recipe/makeshiftemag)
icon_state = "bookCrafting"
oneuse = TRUE
@@ -560,7 +560,9 @@
desc = "A book filled with directions on how to make various tribal clothes and weapons."
icon_state = "stone_tablet"
crafting_recipe_types = list(/datum/crafting_recipe/bola_arrow,
+ /datum/crafting_recipe/explosive_arrow,
/datum/crafting_recipe/flaming_arrow,
+ /datum/crafting_recipe/syringe_arrow,
/datum/crafting_recipe/raider_leather,
/datum/crafting_recipe/tribal_wraps,
/datum/crafting_recipe/ash_robe,
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index 224185c46b24..871471d43b7e 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -427,7 +427,7 @@
name = "gonbola"
desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy."
icon_state = "gonbola"
- item_state = "ebola"
+ item_state = "bola_r"
breakouttime = 300
slowdown = 0
var/datum/status_effect/gonbolaPacify/effectReference
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 122101d3f1d8..1081315879b4 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -518,7 +518,6 @@
projectile_type = /obj/item/projectile/bullet/reusable/gumball
click_cooldown_override = 2
-
/obj/item/projectile/bullet/reusable/gumball
name = "gumball"
desc = "Oh noes! A fast-moving gumball!"
@@ -526,12 +525,10 @@
ammo_type = /obj/item/reagent_containers/food/snacks/gumball/cyborg
nodamage = TRUE
-/obj/item/projectile/bullet/reusable/gumball/handle_drop()
- if(!dropped)
- var/turf/T = get_turf(src)
- var/obj/item/reagent_containers/food/snacks/gumball/S = new ammo_type(T)
- S.color = color
- dropped = TRUE
+/obj/item/projectile/bullet/reusable/gumball/Initialize()
+ . = ..()
+ ammo_type = new ammo_type(src)
+ color = ammo_type.color
/obj/item/ammo_casing/caseless/lollipop //NEEDS RANDOMIZED COLOR LOGIC.
name = "Lollipop"
@@ -550,18 +547,12 @@
/obj/item/projectile/bullet/reusable/lollipop/Initialize()
. = ..()
var/obj/item/reagent_containers/food/snacks/lollipop/S = new ammo_type(src)
+ ammo_type = S
color2 = S.headcolor
var/mutable_appearance/head = mutable_appearance('icons/obj/projectiles.dmi', "lollipop_2")
head.color = color2
add_overlay(head)
-/obj/item/projectile/bullet/reusable/lollipop/handle_drop()
- if(!dropped)
- var/turf/T = get_turf(src)
- var/obj/item/reagent_containers/food/snacks/lollipop/S = new ammo_type(T)
- S.change_head_color(color2)
- dropped = TRUE
-
#define PKBORG_DAMPEN_CYCLE_DELAY 20
//Peacekeeper Cyborg Projectile Dampenening Field
diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm
index f4f69af217f0..e4cc6808d00c 100644
--- a/code/game/objects/items/stacks/tape.dm
+++ b/code/game/objects/items/stacks/tape.dm
@@ -52,7 +52,7 @@
return
to_chat(user, span_info("You wrap [I] with [src]."))
use(1)
- I.embedding = I.embedding.setRating(100, fall_chance, 0, 0, 0, 0, removal_pain, removal_time, TRUE)
+ I.embedding = I.embedding.setRating(100, fall_chance, 0, 0, 0, 0, removal_pain, removal_time, TRUE, 0)
I.taped = TRUE
/obj/item/stack/tape/guerrilla
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index ca0b6fcb6e8b..9d5b959c88f3 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -606,7 +606,7 @@
item_state = "security"
content_overlays = TRUE // This won't end well
-/obj/item/storage/belt/military/snack/ComponentInitialize()
+/obj/item/storage/belt/admin/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_items = 1000
@@ -805,23 +805,249 @@
desc = "A quiver made from the hide of some animal. Used to hold arrows."
icon_state = "quiver"
item_state = "quiver"
+ content_overlays = TRUE
+ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
+
+/obj/item/storage/belt/quiver/update_icon()
+ ..()
+ if(content_overlays && ismob(loc))
+ var/mob/M = loc
+ M.update_inv_belt()
+ M.update_inv_back()
+
+/obj/item/storage/belt/quiver/build_worn_icon(default_layer = 0, default_icon_file = null, isinhands = FALSE, femaleuniform = NO_FEMALE_UNIFORM, override_state = null)
+ if(!override_state && !isinhands && !(locate(/obj/item/ammo_casing/reusable/arrow) in contents))
+ override_state = "[icon_state]_empty"
+ return ..()
/obj/item/storage/belt/quiver/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
- STR.max_items = 15
+ STR.max_items = 20
+ STR.max_combined_w_class = 20
STR.display_numerical_stacking = TRUE
STR.set_holdable(list(
- /obj/item/ammo_casing/caseless/arrow,
+ /obj/item/ammo_casing/reusable/arrow,
/obj/item/stand_arrow,
/obj/item/throwing_star/magspear
))
-/obj/item/storage/belt/quiver/ashwalker/PopulateContents()
+/obj/item/storage/belt/quiver/full/PopulateContents()
for(var/i in 1 to 10)
- new /obj/item/ammo_casing/caseless/arrow/bone(src)
+ new /obj/item/ammo_casing/reusable/arrow(src)
+
+/obj/item/storage/belt/quiver/unlimited
+ name = "quiver of unlimited arrows"
+ desc = "Gives +1 to holding arrows. Also contains unlimited arrows."
+ var/new_arrow_type = /obj/item/ammo_casing/reusable/arrow
+
+/obj/item/storage/belt/quiver/unlimited/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_STORAGE_REMOVED, PROC_REF(check_arrow_refresh))
+
+/obj/item/storage/belt/quiver/unlimited/PopulateContents()
+ new new_arrow_type(src)
+
+/obj/item/storage/belt/quiver/unlimited/proc/check_arrow_refresh()
+ var/list/inv = list()
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_RETURN_INVENTORY, inv)
+ for(var/item in inv)
+ if(istype(item, new_arrow_type))
+ return
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, new new_arrow_type(), null, TRUE, TRUE)
+ playsound(src, 'sound/magic/blink.ogg', 10, 1)
+
+/obj/item/storage/belt/quiver/returning
+ name = "quiver of returning"
+ desc = "A quiver that uses magic to return arrows after a few seconds of them being removed. The arrow doesn't return if the wearer is holding it still."
+ /// The type that are returned to this quiver after being fired
+ var/return_type = /obj/item/ammo_casing/reusable/arrow
+ /// The time it takes for an arrow to return
+ var/return_time = 5 SECONDS
+ /// If the return is blocked by anti-magic
+ var/anti_magic_check = TRUE
+
+/obj/item/storage/belt/quiver/returning/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_STORAGE_REMOVED, PROC_REF(mark_arrow_return))
+
+/obj/item/storage/belt/quiver/returning/proc/mark_arrow_return(target, atom/movable/AM, atom/new_location)
+ if(!istype(AM, return_type))
+ return
+ addtimer(CALLBACK(src, .proc/check_arrow_return, AM), return_time)
+
+/obj/item/storage/belt/quiver/returning/proc/check_arrow_return(atom/movable/arrow)
+ if(!istype(arrow, return_type) || arrow.loc == src || (ismob(loc) && (loc == arrow.loc) || (istype(arrow.loc) && loc == arrow.loc.loc)))
+ return
+ if(ismob(arrow.loc))
+ var/mob/arrow_holder = arrow.loc
+ if(anti_magic_check && arrow_holder.anti_magic_check(TRUE, FALSE ,FALSE, 0))
+ to_chat(arrow_holder, span_notice("You feel [arrow] tugging on you."))
+ return
+ var/mob/living/carbon/carbon = arrow.loc
+
+ if(iscarbon(carbon))
+ var/obj/item/bodypart/part = carbon.get_embedded_part(arrow)
+ if(part)
+ if(!carbon.remove_embedded_object(src, unsafe = TRUE))
+ to_chat(carbon, span_notice("You feel [arrow] tugging on you."))
+ return
+ to_chat(carbon, span_userdanger("[arrow] suddenly rips out of you!"))
+ else if(istype(arrow.loc, /obj/item/ammo_box))
+ var/obj/item/ammo_box/box = arrow.loc
+ box.stored_ammo -= arrow
+ if(istype(box.loc, /obj/item/gun/ballistic/bow))
+ var/obj/item/gun/ballistic/bow/bow = box.loc
+ if(bow.chambered == arrow)
+ bow.chambered = null
+ bow.update_slowdown()
+ bow.update_icon()
+
+ if(!SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, arrow, null, TRUE, TRUE))
+ return
+
+ if(ismob(loc))
+ to_chat(loc, span_notice("[arrow] suddenly returns to your [src]!"))
+ playsound(src, 'sound/magic/blink.ogg', 10, 1)
+
+/obj/item/storage/belt/quiver/returning/bone
+ name = "ash-covered quiver"
+ desc = "A quiver caked in ash, it seems to have a magical aura."
+ icon_state = "quiver_weaver"
+ item_state = "quiver_weaver"
+ resistance_flags = FIRE_PROOF
+ return_type = /obj/item/ammo_casing/reusable/arrow/bone
+
+/obj/item/storage/belt/quiver/returning/bone/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/ammo_casing/reusable/arrow/bone(src)
+
+/obj/item/storage/belt/quiver/returning/holding
+ name = "quiver of holding"
+ desc = "The pinnacle of conventional archery technology, can store a vast amount of arrows and return those removed after a short while using bluespace micro tags and short-ranged teleportation. Probably safe."
+ icon_state = "quiver_holding"
+ item_state = "quiver_holding"
+ content_overlays = FALSE // The arrows are stored in the quiver, so none of it hangs out
+ anti_magic_check = FALSE
+
+/obj/item/storage/belt/quiver/returning/holding/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_items = 50
+ STR.max_combined_w_class = 50
+
+/obj/item/storage/belt/quiver/anomaly
+ name = "anomaly quiver"
+ desc = "A specialized quiver with an empty slot for an anomaly core to give it a special function."
+ icon_state = "quiver_anomaly_empty"
+ item_state = "quiver_anomaly_empty"
+
+/obj/item/storage/belt/quiver/anomaly/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_items = 10 // Less space for arrows due to all the parts inside
+ STR.max_combined_w_class = 10
+
+/obj/item/storage/belt/quiver/anomaly/attackby(obj/item/I, mob/user, params)
+ ..()
+ var/static/list/anomaly_quiver_types = list(
+ /obj/effect/anomaly/grav = /obj/item/storage/belt/quiver/anomaly/vacuum,
+ /obj/effect/anomaly/pyro = /obj/item/storage/belt/quiver/anomaly/pyro,
+ /obj/effect/anomaly/bluespace = /obj/item/storage/belt/quiver/returning/holding
+ )
+
+ if(istype(I, /obj/item/assembly/signaler/anomaly))
+ var/obj/item/assembly/signaler/anomaly/A = I
+ var/quiver_path = anomaly_quiver_types[A.anomaly_type]
+ if(!quiver_path)
+ to_chat(user, span_warning("[A] can't be used in \the [src]."))
+ return
+ var/list/inv = list()
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_RETURN_INVENTORY, inv)
+ if(inv.len) // Want to tell them after so that they don't empty the quiver just to find out they can't use the core
+ to_chat(user, span_warning("You need to empty [src] before [A] can be inserted."))
+ return
+ to_chat(user, span_notice("You insert [A] into \the [src], and it gently hums to life."))
+ new quiver_path(get_turf(src))
+ qdel(src)
+ qdel(A)
+
+/obj/item/storage/belt/quiver/anomaly/vacuum
+ name = "vacuum quiver"
+ desc = "A specialized quiver with a gravitational anomaly core inside, sucking in arrows towards the user and pulling them inside."
+ icon_state = "quiver_anomaly"
+ item_state = "quiver_anomaly"
+
+/obj/item/storage/belt/quiver/anomaly/vacuum/Initialize()
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/item/storage/belt/quiver/anomaly/vacuum/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/storage/belt/quiver/anomaly/vacuum/process(delta_time)
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ var/turf/T = loc ? get_turf(loc) : get_turf(src)
+ var/processed = 0
+ for(var/thing in T)
+ if(processed > 50) // So we dont kill the server with tons of items
+ return
+ var/obj/I = thing
+ if(I && SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, I))
+ ++processed
+
+ for(var/thing in orange(5, src))
+ if(processed > 50)
+ return
+ var/obj/I = thing
+ if(I && STR.can_be_inserted(I))
+ step_towards(I, T)
+ ++processed
+
+/obj/item/storage/belt/quiver/anomaly/pyro
+ name = "incendiary quiver"
+ desc = "A specialized quiver with a pyroclastic anomaly core inside, igniting arrows when the user removes them."
+ icon_state = "quiver_anomaly"
+ item_state = "quiver_anomaly"
+ /// Time after igniting an arrow for it to allow you to light another
+ var/ignite_cooldown = 1 SECONDS
+
+/obj/item/storage/belt/quiver/anomaly/pyro/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_STORAGE_REMOVED, PROC_REF(ignite_arrow))
+
+/obj/item/storage/belt/quiver/anomaly/pyro/proc/ignite_arrow(target, obj/item/ammo_casing/reusable/arrow/arrow, atom/new_location)
+ if(!istype(arrow) || arrow.flaming || TIMER_COOLDOWN_CHECK(src, ignite_cooldown))
+ return
+ TIMER_COOLDOWN_START(src, "ignite_cooldown", ignite_cooldown)
+ arrow.add_flame()
+ visible_message(span_notice("\The [src] ignites \the [arrow]."))
+
+/obj/item/storage/belt/quiver/anomaly/pyro/emp_act(severity)
+ . = ..()
+ if((. & EMP_PROTECT_SELF) || TIMER_COOLDOWN_CHECK(src, ignite_cooldown))
+ return
+ TIMER_COOLDOWN_START(src, "ignite_cooldown", ignite_cooldown)
+ visible_message(span_danger("\The [src] backfires and spews fire!"))
+ fire_act()
+ if(istype(loc))
+ loc.fire_act()
+
+/obj/item/storage/belt/quiver/weaver
+ name = "weaver chitin quiver"
+ desc = "A fireproof quiver made from the chitin of a marrow weaver. Used to hold arrows."
+ icon_state = "quiver_weaver"
+ item_state = "quiver_weaver"
+ resistance_flags = FIRE_PROOF
+
+/obj/item/storage/belt/quiver/weaver/ashwalker/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/ammo_casing/reusable/arrow/bone(src)
/obj/item/storage/belt/quiver/admin
+ name = "admin quiver"
+ content_overlays = FALSE
w_class = WEIGHT_CLASS_TINY
/obj/item/storage/belt/quiver/admin/ComponentInitialize()
@@ -831,12 +1057,32 @@
STR.max_items = 100
/obj/item/storage/belt/quiver/admin/full/PopulateContents()
- for(var/arrow in typesof(/obj/item/ammo_casing/caseless/arrow))
- if(istype(arrow, /obj/item/ammo_casing/caseless/arrow/energy))
+ for(var/arrow in typesof(/obj/item/ammo_casing/reusable/arrow))
+ if(ispath(arrow, /obj/item/ammo_casing/reusable/arrow/energy))
continue
for(var/i in 1 to 10)
new arrow(src)
+/obj/item/storage/belt/quiver/blue
+ name = "toy blue quiver"
+ desc = "A quiver that holds toy arrows that look suspiciously like the pulse arrows fabricated by certain hardlight bows."
+ icon_state = "quiver_blue"
+ item_state = "quiver_blue"
+
+/obj/item/storage/belt/quiver/blue/full/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/ammo_casing/reusable/arrow/toy/pulse(src)
+
+/obj/item/storage/belt/quiver/red
+ name = "toy red quiver"
+ desc = "A strange quiver filled with toy energy arrows, meant to be used in games of pretend."
+ icon_state = "quiver_red"
+ item_state = "quiver_red"
+
+/obj/item/storage/belt/quiver/red/full/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/ammo_casing/reusable/arrow/toy/energy(src)
+
/obj/item/storage/belt/fannypack
name = "fannypack"
desc = "A dorky fannypack for keeping small items in."
diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm
index f823ff0d8b81..ca1fdea9e757 100644
--- a/code/game/objects/structures/ghost_role_spawners.dm
+++ b/code/game/objects/structures/ghost_role_spawners.dm
@@ -91,7 +91,7 @@
uniform = /obj/item/clothing/under/ash_robe/hunter
suit = /obj/item/clothing/suit/hooded/cloak/goliath/desert
back = /obj/item/gun/ballistic/bow/ashen
- belt = /obj/item/storage/belt/quiver/ashwalker
+ belt = /obj/item/storage/belt/quiver/weaver/ashwalker
shoes = /obj/item/clothing/shoes/xeno_wraps
/datum/outfit/ashwalker/warrior
diff --git a/code/modules/antagonists/demon/sins/envy.dm b/code/modules/antagonists/demon/sins/envy.dm
index aa4ae1aeb7f8..243d5e2dc9a4 100644
--- a/code/modules/antagonists/demon/sins/envy.dm
+++ b/code/modules/antagonists/demon/sins/envy.dm
@@ -6,7 +6,7 @@
charge_max = 150
clothes_req = FALSE
invocation = "ETERNAL FLAMES"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "transform"
action_background_icon_state = "bg_demon"
diff --git a/code/modules/antagonists/demon/sins/gluttony.dm b/code/modules/antagonists/demon/sins/gluttony.dm
index 009b61132633..795a568df846 100644
--- a/code/modules/antagonists/demon/sins/gluttony.dm
+++ b/code/modules/antagonists/demon/sins/gluttony.dm
@@ -5,7 +5,7 @@
charge_max = 150
clothes_req = FALSE
invocation = "INDULGE"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
sound = 'sound/magic/forcewall.ogg'
action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
action_icon_state = "blob"
diff --git a/code/modules/antagonists/demon/sins/greed.dm b/code/modules/antagonists/demon/sins/greed.dm
index 248f88b7119d..4d795e1b434a 100644
--- a/code/modules/antagonists/demon/sins/greed.dm
+++ b/code/modules/antagonists/demon/sins/greed.dm
@@ -2,7 +2,7 @@
name = "Summon Slotmachine"
desc = "Summon forth a temporary slot machine of greed, allowing you to offer patrons a deadly game where the price is their life (and some money if you'd like) and the possible prize is a one use die of fate."
invocation = "Just one game?"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
charge_max = 600
cooldown_min = 200
diff --git a/code/modules/antagonists/demon/sins/pride.dm b/code/modules/antagonists/demon/sins/pride.dm
index 1bde7d4bb18a..99a66d1bef2c 100644
--- a/code/modules/antagonists/demon/sins/pride.dm
+++ b/code/modules/antagonists/demon/sins/pride.dm
@@ -2,7 +2,7 @@
name = "Summon Mirror"
desc = "Summon forth a temporary mirror of sin that will allow you and others to change anything they want about themselves."
invocation = "Aren't I so amazing?"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
charge_max = 600
cooldown_min = 200
diff --git a/code/modules/antagonists/demon/sins/wrath.dm b/code/modules/antagonists/demon/sins/wrath.dm
index 3bd0a042b263..536bb31d300d 100644
--- a/code/modules/antagonists/demon/sins/wrath.dm
+++ b/code/modules/antagonists/demon/sins/wrath.dm
@@ -17,7 +17,7 @@
charge_max = 600
clothes_req = FALSE
invocation = "BURN IN HELL!!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
message = span_notice("You ignite in a flash of hellfire!")
cooldown_min = 75
ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
@@ -30,7 +30,7 @@
desc = "This spell sets a person on fire from range."
school = "transmutation"
invocation = "BURN IN HELL!!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
charge_max = 600
clothes_req = FALSE
action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
index ad701b8bc3a5..13a3993c02f0 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
@@ -86,7 +86,7 @@
charge_max = 300 //twice as long as mansus grasp
clothes_req = FALSE
invocation = "RUST TO RUST"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = 3
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "corrode"
@@ -114,7 +114,7 @@
charge_max = 150
clothes_req = FALSE
invocation = "ETERNAL FLAMES"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "blood_siphon"
action_background_icon_state = "bg_ecult"
@@ -165,7 +165,7 @@
action_icon_state = "rust_wave"
action_background_icon_state = "bg_ecult"
invocation = "FACE INEVITABILITY"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
/obj/item/projectile/magic/spell/rust_wave
name = "patron's reach"
@@ -212,7 +212,7 @@
charge_max = 350
clothes_req = FALSE
invocation = "RIP AND TEAR"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = 9
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "cleave"
@@ -266,7 +266,7 @@
school = "transmutation"
charge_max = 150
clothes_req = FALSE
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 2
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "mad_touch"
@@ -299,7 +299,7 @@
desc = "Fires five blasts of fire in angles away from you, dealing heavy damage to anything they hit."
school = "transmutation"
invocation = "IGNITE"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
charge_max = 300
range = 15
clothes_req = FALSE
@@ -362,7 +362,7 @@
/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch
invocation = "BEND MY FORM"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
possible_shapes = list(/mob/living/simple_animal/mouse,\
@@ -376,7 +376,7 @@
/obj/effect/proc_holder/spell/targeted/emplosion/eldritch
name = "Entropic Pulse"
invocation = "ENTROPIC PULSE"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
range = -1
@@ -392,7 +392,7 @@
charge_max = 300 //twice as long as mansus grasp
clothes_req = FALSE
invocation = "CONFLAGRATE"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 4
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "fire_ring"
@@ -418,7 +418,7 @@
/obj/effect/proc_holder/spell/targeted/telepathy/eldritch
invocation = ""
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
@@ -426,7 +426,7 @@
name = "Oath of Fire"
desc = "Engulf yourself in a cloak of flames for a minute. The flames are harmless to you, but dangerous to anyone else."
invocation = "FUEL FOR THE FIRE"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
range = -1
@@ -464,7 +464,7 @@
/obj/effect/proc_holder/spell/targeted/worm_contract
name = "Force Contract"
desc = "Forces all the worm parts to collapse onto a single turf"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
range = -1
@@ -495,7 +495,7 @@
name = "Nightwatcher's Rebirth"
desc = "Drains the health of nearby combusting individuals, healing you 10 of each damage type for every victim. If a victim is in critical condition, they will be finished off."
invocation = "ASHES TO ASHES"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
range = -1
@@ -530,7 +530,7 @@
charge_max = 300
clothes_req = FALSE
invocation = "HEAR MY VOICE"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = 10
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "mansus_link"
@@ -633,7 +633,7 @@
desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinding them (increasing with range) and poisoning them (decreasing with range), while also spreading rust in the path of the plume."
school = "illusion"
invocation = "GUST OF RUST"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
clothes_req = FALSE
action_background_icon_state = "bg_ecult"
action_icon = 'icons/mob/actions/actions_ecult.dmi'
diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm
index b9dac7e77672..f7aceab8e506 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -202,7 +202,7 @@
nukeop_outfit = /datum/outfit/syndicate/leader
always_new_team = TRUE
var/title
- preview_outfit = /datum/outfit/nuclear_operative
+ preview_outfit = /datum/outfit/nuclear_operative/leader
preview_outfit_behind = null
/datum/antagonist/nukeop/leader/memorize_code()
@@ -262,6 +262,10 @@
return capitalize(newname)
+/datum/outfit/nuclear_operative/leader
+ name = "Nuclear Operative Leader (Preview only)"
+ neck = /obj/item/clothing/neck/cloak/nukie
+
/datum/antagonist/nukeop/lone
name = "Lone Operative"
always_new_team = TRUE
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index 1968d60bc8d3..14d35ceded49 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -95,7 +95,7 @@
S = new spell_type()
var/dat =""
dat += "[initial(S.name)]"
- if(S.charge_type == "recharge")
+ if(S.charge_type == SPELL_CHARGE_TYPE_RECHARGE)
dat += " Cooldown:[S.charge_max/10]"
dat += " Cost:[cost]
"
dat += "[S.desc][desc]
"
@@ -224,6 +224,11 @@
cost = 3
no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage
+/datum/spellbook_entry/magic_arrows
+ name = "Summon Magic Arrows"
+ spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/arrow/magic
+ cost = 1
+
/datum/spellbook_entry/arcane_barrage
name = "Arcane Barrage"
spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage
@@ -307,6 +312,17 @@
desc = "A sword capable of firing blasts of energy which rip targets limb from limb."
item_path = /obj/item/gun/magic/staff/spellblade
+/datum/spellbook_entry/item/breakbow
+ name = "Break Bow"
+ desc = "A bladed bow that can be split into two swords which attack simultaneously as well as return to their thrower. Comes with a quiver of unlimited, powerful arrows."
+ item_path = /obj/item/gun/ballistic/bow/break_bow
+ cost = 2
+
+/datum/spellbook_entry/item/breakbow/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
+ . = ..()
+ if(.)
+ new /obj/item/storage/belt/quiver/unlimited(get_turf(user)) // Quiver of limitless arrows
+
/datum/spellbook_entry/item/staffdoor
name = "Staff of Door Creation"
desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass."
@@ -364,6 +380,13 @@
new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them.
new /obj/item/clothing/gloves/color/purple(get_turf(user))//To complete the outfit
+/datum/spellbook_entry/item/ranger_cloak
+ name = "Ranger Cloak"
+ desc = "A cape that makes the wearer quickly invisible while standing still, permitting them to dodge ranged attacks. Moving or dodging projectiles reduces the effect."
+ item_path = /obj/item/clothing/neck/cloak/ranger
+ cost = 2
+ category = "Defensive"
+
/datum/spellbook_entry/item/contract
name = "Contract of Apprenticeship"
desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side."
diff --git a/code/modules/assembly/voicebox.dm b/code/modules/assembly/voicebox.dm
new file mode 100644
index 000000000000..29de2b0b768f
--- /dev/null
+++ b/code/modules/assembly/voicebox.dm
@@ -0,0 +1,79 @@
+#define VOICEBOX_OPTION_ADD "(ADD NEW)"
+#define VOICEBOX_OPTION_CLEAR "(CLEAR ALL)"
+#define VOICEBOX_OPTION_CLOSE "(CLOSE)"
+
+/obj/item/assembly/voice_box
+ name = "voice box"
+ desc = "A device that says a message when activated. Often used in toys."
+ icon_state = "control"
+
+ /// List of messages, a random one will be selected when arctivated
+ var/list/messages
+ /// How long before another message can be sent
+ var/cooldown_time = 0 SECONDS
+
+/obj/item/assembly/voice_box/multitool_act(mob/living/user, obj/item/I)
+ . = ..()
+ edit_mode(user)
+
+/obj/item/assembly/voice_box/proc/edit_mode(mob/living/user)
+ var/keep_open = TRUE
+ var/list/choices
+ var/list/menu_choices = list(VOICEBOX_OPTION_ADD, VOICEBOX_OPTION_CLEAR, VOICEBOX_OPTION_CLOSE)
+ while(keep_open)
+ choices = messages ? sortList(messages.Copy(), /proc/cmp_text_asc) : list()
+ LAZYADD(choices, menu_choices)
+ var/choice = sanitize_inlist(input("Select message to edit", "Edit Voicebox Messages") as null|anything in choices, choices, VOICEBOX_OPTION_CLOSE)
+
+ switch(choice)
+ if(VOICEBOX_OPTION_CLOSE)
+ keep_open = FALSE
+ if(VOICEBOX_OPTION_CLEAR)
+ if(tgui_alert(user, "Are you sure you want to clear all messages?",, list("Yes", "No")) == "Yes")
+ messages = list()
+ else
+ if(choice == VOICEBOX_OPTION_ADD)
+ choice = "Hello"
+ else
+ LAZYREMOVE(messages, choice)
+ var/new_message = sanitize(stripped_input(user, "What would you like the new message to be?", "[src]", choice, MAX_MESSAGE_LEN))
+ if(isnotpretty(new_message)) // This proc has an awful name
+ message_admins("[ADMIN_LOOKUPFLW(user)] just attempted to add a bad message to a voice box: '[new_message]'")
+ to_chat(user, span_warning("Invalid message!"))
+ else if(new_message in menu_choices)
+ to_chat(user, span_warning("Invalid message!"))
+ else if(new_message in messages)
+ to_chat(user, span_warning("Message already exists!"))
+ else
+ LAZYADD(messages, new_message)
+ to_chat(user, span_notice("\"[new_message]\" added to message list."))
+ log_game("[key_name(user)] added a new message '[new_message]' to [src]")
+
+
+/obj/item/assembly/voice_box/activate()
+ if(TIMER_COOLDOWN_CHECK(src, "message") || !LAZYLEN(messages))
+ return
+ TIMER_COOLDOWN_START(src, "message", cooldown_time)
+ var/atom/movable/speaker = loc
+ if(istype(speaker))
+ speaker.say(pick(messages))
+ else
+ say(pick(messages))
+
+
+/obj/item/assembly/voice_box/bow
+ cooldown_time = 1 SECONDS
+
+// Good god all the below need to be changed
+/obj/item/assembly/voice_box/bow/nanotrasen
+ messages = list("Death to the Syndicate!!", "Glory to Nanotrasen!!", "Die Syndie scum!!", "Eat hardlight!!")
+
+/obj/item/assembly/voice_box/bow/syndie
+ messages = list("Death to Nanotrasen!!", "Glory to the Syndicate!!", "Die NT scum!!", "Eat hardlight!!")
+
+/obj/item/assembly/voice_box/bow/clockwork
+ messages = list("For Ratvar!!", "Death to the non-believers!!", "Glory to Ratvar!!")
+
+#undef VOICEBOX_OPTION_ADD
+#undef VOICEBOX_OPTION_CLEAR
+#undef VOICEBOX_OPTION_CLOSE
diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm
index dae3aafe6ec4..633e383dddbf 100644
--- a/code/modules/awaymissions/mission_code/Academy.dm
+++ b/code/modules/awaymissions/mission_code/Academy.dm
@@ -373,7 +373,7 @@
charge_max = 100
clothes_req = 0
invocation = "JE VES"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = -1
level_max = 0 //cannot be improved
cooldown_min = 100
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index be7d3bec4282..a15eaa46724e 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -2726,6 +2726,63 @@
the_toy = /obj/item/toy/plush/lizard/azeel
new the_toy(.)
+/datum/supply_pack/costumes_toys/archery
+ name = "Archery Crate"
+ desc = "Shoot apples of peoples heads with this kit containing everything you need to start your archery carrier."
+ cost = 1000
+ contains = list(/obj/item/gun/ballistic/bow,
+ /obj/item/storage/belt/quiver/full
+ )
+ crate_name = "archery crate"
+ crate_type = /obj/structure/closet/crate/wooden
+
+/datum/supply_pack/costumes_toys/randomised/toy_bow
+ name = "Toy Bow Crate"
+ desc = "A crate containing one random toy bow of four to impress your friends with, collect them all!"
+ cost = 500
+ num_contained = 1
+ contains = list(/obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/white,
+ /obj/item/gun/ballistic/bow/toy/clockwork
+ )
+ crate_name = "toy bow crate"
+ crate_type = /obj/structure/closet/crate/wooden
+
+/datum/supply_pack/costumes_toys/archery_war
+ name = "Archery War Crate"
+ desc = "Set up an all out archery war with this simple kit!"
+ cost = 5000
+ contains = list(/obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/gun/ballistic/bow/toy/blue,
+ /obj/item/storage/belt/quiver/blue/full,
+ /obj/item/storage/belt/quiver/blue/full,
+ /obj/item/storage/belt/quiver/blue/full,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/gun/ballistic/bow/toy/red,
+ /obj/item/storage/belt/quiver/red/full,
+ /obj/item/storage/belt/quiver/red/full,
+ /obj/item/storage/belt/quiver/red/full,
+ /obj/item/ammo_box/arrow/toy/disabler,
+ /obj/item/ammo_box/arrow/toy/energy,
+ /obj/item/ammo_box/arrow/toy/pulse,
+ /obj/item/ammo_box/arrow/toy/xray,
+ /obj/item/ammo_box/arrow/toy/shock,
+ /obj/item/ammo_box/arrow/toy/magic
+ )
+ crate_name = "archery war crate"
+ crate_type = /obj/structure/closet/crate/wooden
+
/datum/supply_pack/costumes_toys/wizard
name = "Wizard Costume Crate"
desc = "Pretend to join the Wizard Federation with this full wizard outfit! Nanotrasen would like to remind its employees that actually joining the Wizard Federation is subject to termination of job and life."
diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm
index 597c2685ffb3..36004f6171a7 100644
--- a/code/modules/clothing/gloves/color.dm
+++ b/code/modules/clothing/gloves/color.dm
@@ -65,6 +65,7 @@
desc = "Rudimentary gloves that aid in carrying."
icon_state = "goligloves"
item_state = "goligloves"
+ can_be_cut = FALSE
/obj/item/clothing/gloves/color/black/goliath/equipped(mob/user, slot)
..()
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index 4a4dd9d10cf3..2fdca06b83ab 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -48,6 +48,13 @@
..()
REMOVE_TRAIT(user, carrytrait, CLOTHING_TRAIT)
+/obj/item/clothing/gloves/fingerless/weaver
+ name = "weaver chitin gloves"
+ desc = "Grey gloves without fingertips made from the hide of a dead arachnid found on lavaland. Increases the work speed of the wearer."
+ icon_state = "weaver_chitin"
+ item_state = "weaver_chitin"
+ tacticalspeed = 0.8
+
/obj/item/clothing/gloves/botanic_leather
name = "botanist's leather gloves"
desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm."
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 1f324a2fa422..819114d46a6a 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -327,6 +327,11 @@
desc = "Worn by the right hand of the captain. It smells faintly of bureaucracy."
icon_state = "hopcloak"
+/obj/item/clothing/neck/cloak/nukie
+ name = "tactical ablative shawl"
+ desc = "Worn by the leader of an elite team of nuclear operatives. Commit mass murder in style!"
+ icon_state = "nukie_cloak"
+
/obj/item/clothing/neck/cloak/tribalmantle
name = "ornate mantle"
desc = "An ornate mantle commonly worn by a shaman or chieftain."
@@ -344,3 +349,118 @@
w_class = WEIGHT_CLASS_SMALL
icon_state = "falcon"
item_state = "falcon"
+// Stealth cloaks
+
+/obj/item/clothing/neck/cloak/ranger
+ name = "ranger cloak"
+ desc = "A cape that uses light-altering magic to make the wearer invisible and allow them to dodge projectiles. The illusion weakens the more the wearer moves."
+ icon_state = "ranger_cloak"
+
+ /// The mob currently wearing this
+ var/mob/current_user
+ /// How much the user is cloaked as a percentage, which effects the wearer's transparency and dodge chance
+ var/cloak = 0
+ /// What cloak is capped to
+ var/max_cloak = 100
+ /// How much the cloak charges per process
+ var/cloak_charge_rate = 35
+ /// How much the cloak decreases when moving
+ var/cloak_move_loss = 5
+ /// How much the cloak decreases on a successful dodge
+ var/cloak_dodge_loss = 30
+
+/obj/item/clothing/neck/cloak/ranger/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_ITEM_POST_UNEQUIP, .proc/on_unequip)
+
+/obj/item/clothing/neck/cloak/ranger/equipped(mob/user, slot)
+ . = ..()
+ update_signals()
+
+/obj/item/clothing/neck/cloak/ranger/dropped(mob/user)
+ . = ..()
+ update_signals()
+
+/obj/item/clothing/neck/cloak/ranger/proc/on_unequip(force, newloc, no_move, invdrop = TRUE, silent = FALSE)
+ update_signals()
+
+/obj/item/clothing/neck/cloak/ranger/proc/update_signals(user)
+ if((!user || (current_user == user)) && current_user == loc && istype(current_user) && current_user.get_item_by_slot(SLOT_NECK) == src)
+ return TRUE
+
+ set_cloak(0)
+ UnregisterSignal(current_user, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_BULLET_ACT))
+ if(user)
+ UnregisterSignal(user, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_BULLET_ACT))
+
+ var/mob/new_user = loc
+ if(istype(new_user) && new_user.get_item_by_slot(SLOT_NECK) == src)
+ current_user = new_user
+ RegisterSignal(current_user, COMSIG_MOVABLE_MOVED, .proc/on_move)
+ RegisterSignal(current_user, COMSIG_ATOM_BULLET_ACT, .proc/on_projectile_hit)
+ START_PROCESSING(SSobj, src)
+ else
+ STOP_PROCESSING(SSobj, src)
+
+/obj/item/clothing/neck/cloak/ranger/proc/set_cloak(ammount)
+ cloak = clamp(ammount, 0, max_cloak)
+ var/mob/user = loc
+ if(istype(user))
+ animate(user, alpha = round(clamp(255 * (1 - (cloak * 0.01)), 0, 255)), time = 0.5 SECONDS)
+
+/obj/item/clothing/neck/cloak/ranger/process(delta_time)
+ if(!update_signals())
+ return
+ var/mob/user = loc
+ if(!istype(user) || !user.get_item_by_slot(SLOT_NECK) == src)
+
+ return
+ set_cloak(cloak + (cloak_charge_rate * delta_time))
+
+/obj/item/clothing/neck/cloak/ranger/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ if(dodge(owner, hitby, attack_text))
+ return TRUE
+ return ..()
+
+/obj/item/clothing/neck/cloak/ranger/proc/on_move(mob/user, Dir, Forced = FALSE)
+ if(update_signals(user))
+ set_cloak(cloak - cloak_move_loss)
+
+/obj/item/clothing/neck/cloak/ranger/proc/on_projectile_hit(mob/living/carbon/human/user, obj/item/projectile/P, def_zone)
+ if(dodge(user, P, "[P]"))
+ return BULLET_ACT_FORCE_PIERCE
+
+/obj/item/clothing/neck/cloak/ranger/proc/dodge(mob/living/carbon/human/user, atom/movable/hitby, attack_text)
+ if(!update_signals(user) || current_user.incapacitated(check_immobilized = TRUE) || !prob(cloak))
+ return FALSE
+
+ set_cloak(cloak - cloak_dodge_loss)
+ current_user.SpinAnimation(7,1)
+ current_user.balloon_alert_to_viewers("Dodged!", "Dodged!", COMBAT_MESSAGE_RANGE)
+ current_user.visible_message(span_danger("[current_user] dodges [attack_text]!"), span_userdanger("You dodge [attack_text]"), null, COMBAT_MESSAGE_RANGE)
+ return TRUE
+
+/obj/item/clothing/neck/cloak/ranger/syndie
+ name = "shadow cloak"
+ desc = "A dark red cape that uses advanced chameleon technology to make the wearer nearly invisible and aid them in dodging projectiles. Unable to sustain its image under distress or EMP."
+ icon_state = "syndie_cloak"
+ max_cloak = 75 //Max 75% dodge is a little quirky
+ cloak_charge_rate = 20
+ cloak_dodge_loss = 40
+ var/cloak_emp_disable_duration = 10 SECONDS
+ var/cloak_emp_loss = 25
+
+/obj/item/clothing/neck/cloak/ranger/syndie/emp_act(severity)
+ . = ..()
+ if(CHECK_BITFIELD(., EMP_PROTECT_SELF))
+ return
+ if(severity == EMP_HEAVY)
+ set_cloak(0)
+ TIMER_COOLDOWN_START(src, "cloak_emp_disable", cloak_emp_disable_duration)
+ else
+ set_cloak(cloak - cloak_emp_loss)
+
+/obj/item/clothing/neck/cloak/ranger/syndie/process(delta_time)
+ if(TIMER_COOLDOWN_CHECK(src, "cloak_emp_disable"))
+ return
+ return ..()
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index ae1cb4dcb7c8..f8446f852f36 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -137,34 +137,37 @@
/obj/item/clothing/suit/armor/bone
name = "bone armor"
- desc = "A tribal armor plate, crafted from animal bone."
+ desc = "A mass of bones wrapped together into a protective shell. Not as effective as modern protection, but it still offers notable protection."
+ allowed = list (/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/twohanded/spear, /obj/item/twohanded/bonespear, /obj/item/claymore/bone, /obj/item/gun/ballistic/bow, /obj/item/organ/regenerative_core/legion, /obj/item/kitchen/knife/combat)
icon_state = "bonearmor"
item_state = "bonearmor"
blood_overlay_type = "armor"
- armor = list(MELEE = 35, BULLET = 25, LASER = 25, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 10)
+ armor = list(MELEE = 45, BULLET = 15, LASER = 15, ENERGY = 5, BOMB = 35, BIO = 0, RAD = 0, FIRE = 10, ACID = 10, WOUND = 15)
+ slowdown = 0.2
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS
/obj/item/clothing/suit/armor/bone/heavy
name = "heavy bone armor"
- desc = "A heavy tribal armor plate, crafted from a lot animal bone."
+ desc = "A hefty set of bones that covers most of the body. Slowing, but able to repel considerable blows."
icon_state = "hbonearmor"
item_state = "hbonearmor"
- armor = list(MELEE = 40, BULLET = 30, LASER = 30, ENERGY = 15, BOMB = 20, BIO = 0, RAD = 0, FIRE = 60, ACID = 30, WOUND = 20)
- slowdown = 0.20
+ armor = list(MELEE = 55, BULLET = 20, LASER = 20, ENERGY = 10, BOMB = 65, BIO = 0, RAD = 0, FIRE = 20, ACID = 20, WOUND = 20)
+ slowdown = 0.4
/obj/item/clothing/suit/armor/tribalcoat
name = "tribal coat"
- desc = "A light yet tough leather coat reinforced with bone pauldrons."
+ desc = "A light, yet tough leather coat, reinforced with bone pauldrons. Often worn by tribal leaders."
+ allowed = list (/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/twohanded/spear, /obj/item/twohanded/bonespear, /obj/item/claymore/bone, /obj/item/gun/ballistic/bow, /obj/item/organ/regenerative_core/legion, /obj/item/kitchen/knife/combat)
icon_state = "tribalcoat"
item_state = "tribalcoat"
blood_overlay_type = "armor"
- armor = list(MELEE = 25, BULLET = 25, LASER = 25, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 10)
+ armor = list(MELEE = 35, BULLET = 15, LASER = 15, ENERGY = 5, BOMB = 50, BIO = 0, RAD = 0, FIRE = 30, ACID = 30, WOUND = 10) //Better against bomb than goliath, but worse in other ways
body_parts_covered = CHEST|GROIN|LEGS|ARMS
resistance_flags = FLAMMABLE
/obj/item/clothing/suit/armor/pathfinder
name = "pathfinder cloak"
- desc = "A thick cloak woven from sinew and hides meant to protect its wearer from hazardous weather."
+ desc = "A thick cloak woven from sinew and hides, designed to protect its wearer from hazardous weather."
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/pickaxe, /obj/item/twohanded/spear, /obj/item/twohanded/bonespear, /obj/item/claymore/bone, /obj/item/gun/ballistic/bow, /obj/item/organ/regenerative_core/legion, /obj/item/kitchen/knife/combat)
icon_state = "pathcloak"
item_state = "pathcloak"
@@ -334,9 +337,9 @@
/obj/item/clothing/suit/hooded/cloak/goliath
name = "goliath cloak"
icon_state = "goliath_cloak"
- desc = "A staunch, practical cape made out of numerous monster materials, it is coveted amongst exiles & hermits."
+ desc = "A staunch, practical cape made out of numerous monster materials. It is coveted amongst exiles and hermits."
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/pickaxe, /obj/item/twohanded/spear, /obj/item/twohanded/bonespear, /obj/item/claymore/bone, /obj/item/gun/ballistic/bow, /obj/item/organ/regenerative_core/legion, /obj/item/kitchen/knife/combat)
- armor = list(MELEE = 35, BULLET = 25, LASER = 25, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 10) //a fair alternative to bone armor, requiring alternative materials and gaining a suit slot
+ armor = list(MELEE = 35, BULLET = 25, LASER = 25, ENERGY = 10, BOMB = 35, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 10) //a fair alternative to bone armor, requiring alternative materials and gaining a suit slot
resistance_flags = FIRE_PROOF
hoodtype = /obj/item/clothing/head/hooded/cloakhood/goliath
body_parts_covered = CHEST|GROIN|ARMS
@@ -344,15 +347,15 @@
/obj/item/clothing/head/hooded/cloakhood/goliath
name = "goliath cloak hood"
icon_state = "golhood"
- desc = "A protective & concealing hood."
- armor = list(MELEE = 35, BULLET = 25, LASER = 25, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 10)
+ desc = "A protective and concealing hood."
+ armor = list(MELEE = 35, BULLET = 25, LASER = 25, ENERGY = 10, BOMB = 35, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 10)
resistance_flags = FIRE_PROOF
flags_inv = HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR
transparent_protection = HIDEMASK
/obj/item/clothing/suit/hooded/cloak/goliath/desert
name = "brown leather cape"
- desc = "An ash coated cloak."
+ desc = "An ash-coated cloak."
icon_state = "desertcloak"
armor = list()
resistance_flags = 0
@@ -361,7 +364,7 @@
/obj/item/clothing/head/hooded/cloakhood/goliath/desert
name = "goliath cloak hood"
icon_state = "desertcloak"
- desc = "An ash coated cloak hood."
+ desc = "The hood of an ashy cloak."
armor = list()
resistance_flags = 0
flags_inv = HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR
@@ -384,7 +387,7 @@
name = "drake helm"
icon_state = "dragon"
desc = "The skull of a dragon."
- armor = list(MELEE = 70, BULLET = 20, LASER = 50, ENERGY = 20, BOMB = 70, BIO = 60, RAD = 50, FIRE = 100, ACID = 100)
+ armor = list(MELEE = 70, BULLET = 20, LASER = 30, ENERGY = 20, BOMB = 70, BIO = 60, RAD = 50, FIRE = 100, ACID = 100)
heat_protection = HEAD
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF | ACID_PROOF
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index cf1944184103..ed4bcfb242e2 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -467,7 +467,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
image_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
else
image_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
- A = image(image_file,H,"crossbow", layer=ABOVE_MOB_LAYER)
+ A = image(image_file,H,"ecrossbow", layer=ABOVE_MOB_LAYER)
if("baton")
if(side == "right")
image_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 7e8e0c106270..60c805b9f48a 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -13,7 +13,7 @@ GLOBAL_LIST_EMPTY(aide_list)
desc = "It's watching you suspiciously."
/obj/structure/closet/crate/necropolis/tendril/PopulateContents()
- var/loot = rand(1,21)
+ var/loot = rand(1,23)
switch(loot)
if(1)
new /obj/item/shared_storage/red(src)
@@ -69,6 +69,11 @@ GLOBAL_LIST_EMPTY(aide_list)
new /obj/item/grenade/plastic/miningcharge/mega(src)
new /obj/item/grenade/plastic/miningcharge/mega(src)
new /obj/item/grenade/plastic/miningcharge/mega(src)
+ if(22)
+ new /obj/item/clothing/gloves/gauntlets(src)
+ if(23)
+ new /obj/item/gun/ballistic/bow/ashen(src)
+ new /obj/item/storage/belt/quiver/returning/bone(src)
//KA modkit design discs
/obj/item/disk/design_disk/modkit_disc
@@ -637,7 +642,7 @@ GLOBAL_LIST_EMPTY(aide_list)
if(51 to 60)//10% for bow and bronze tipped arrows, bronze are supposed to be the worst in runescape but they kinda slap in here, hopefully limited by the 5 arrows
new /obj/item/gun/ballistic/bow(spot)
for(var/i in 1 to 5)
- new /obj/item/ammo_casing/caseless/arrow/bronze(spot)
+ new /obj/item/ammo_casing/reusable/arrow/bronze(spot)
if(61 to 70)//10% chance at a seed drop, runescape drops seeds somewhat frequently for players to plant and harvest later
switch(rand(1,5))
if(1)
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 4766463d8bce..8fd1facd6db0 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -109,7 +109,7 @@
//Sad that this will cause some overhead, but the alias seems necessary
-//*I* may be happy with a million and one references to "indexes" but others won't be
+//*I* may be happy with a million and one references to "indexes" but others won't be // This should be converted into a helper define to reduce proc overhead
/mob/proc/is_holding(obj/item/I)
return get_held_index_of_item(I)
@@ -343,6 +343,7 @@
else
I.forceMove(newloc)
I.dropped(src, silent)
+ SEND_SIGNAL(I, COMSIG_ITEM_POST_UNEQUIP, force, newloc, no_move, invdrop, silent)
return TRUE
//Outdated but still in use apparently. This should at least be a human proc.
@@ -453,13 +454,13 @@
to_chat(M, span_warning("You are unable to equip that!"))
return FALSE
-
+
/mob/verb/quick_equip()
set name = "quick-equip"
set hidden = TRUE
var/obj/item/I = get_active_held_item()
- if (I)
+ if (!CHECK_BITFIELD(SEND_SIGNAL(src, COMSIG_MOB_QUICK_EQUIP, I), COMPONENT_BLOCK_QUICK_EQUIP) && I)
I.equip_to_best_slot(src)
//used in code for items usable by both carbon and drones, this gives the proper back slot for each mob.(defibrillator, backpack watertank, ...)
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 8148b0ac8072..f6f90c766b62 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -82,7 +82,7 @@
* Embeds an object into this carbon
*/
/mob/living/carbon/proc/embed_object(obj/item/embedding, part, deal_damage, silent, forced)
- if(!(forced || (can_embed(embedding) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE))))
+ if(!(forced || (can_embed(embedding) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE))) || get_embedded_part(embedding))
return FALSE
var/obj/item/bodypart/body_part = part
// In case its a zone
@@ -94,11 +94,10 @@
// Thats probably not good
if(!istype(body_part))
return FALSE
- if(!embedding.on_embed(src, body_part))
- return
- body_part.embedded_objects |= embedding
- var/obj/item/ammo_casing/AC = embedding
- if(!((istype(AC) && !AC.harmful) || embedding.taped))
+ if(CHECK_BITFIELD(SEND_SIGNAL(embedding, COMSIG_ITEM_EMBEDDED, src), COMSIG_ITEM_BLOCK_EMBED) || !embedding)
+ return FALSE
+ LAZYADD(body_part.embedded_objects, embedding)
+ if(embedding.embedding.embedded_bleed_rate)
embedding.add_mob_blood(src)//it embedded itself in you, of course it's bloody!
embedding.forceMove(src)
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
@@ -112,22 +111,33 @@
/**
* Removes the given embedded object from this carbon
*/
-/mob/living/carbon/proc/remove_embedded_object(obj/item/embedded, new_loc, silent, forced)
- var/obj/item/bodypart/body_part
- for(var/obj/item/bodypart/part in bodyparts)
- if(embedded in part.embedded_objects)
- body_part = part
+/mob/living/carbon/proc/remove_embedded_object(obj/item/embedded, new_loc, silent, forced, unsafe)
+ var/obj/item/bodypart/body_part = get_embedded_part(embedded)
if(!body_part)
- return
- body_part.embedded_objects -= embedded
- if(!silent)
- emote("scream")
+ return FALSE
+ var/sig_return = SEND_SIGNAL(embedded, COMSIG_ITEM_EMBED_REMOVAL, src)
+ if(CHECK_BITFIELD(sig_return, COMSIG_ITEM_BLOCK_EMBED_REMOVAL))
+ LAZYADD(body_part.embedded_objects, embedded)
+ return FALSE
+ LAZYREMOVE(body_part.embedded_objects, embedded)
+ if(unsafe)
+ var/damage_amount = embedded.embedding.embedded_unsafe_removal_pain_multiplier * embedded.w_class
+ if(embedded.embedding.embedded_bleed_rate)
+ body_part.receive_damage(damage_amount * 0.25, sharpness = SHARP_EDGED)//It hurts to rip it out, get surgery you dingus.
+ body_part.check_wounding(WOUND_SLASH, damage_amount, 20, 0)
+ else
+ body_part.receive_damage(stamina = damage_amount * 0.25, sharpness = SHARP_EDGED)//Non-harmful stuff causes stamina damage when removed
+
+ if(!silent && damage_amount)
+ emote("scream")
+
if(!has_embedded_objects())
clear_alert("embeddedobject")
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
- if(new_loc)
+ if(CHECK_BITFIELD(sig_return, COMSIG_ITEM_QDEL_EMBED_REMOVAL))
+ qdel(embedded)
+ else if(new_loc)
embedded.forceMove(new_loc)
- embedded.on_embed_removal(src)
return TRUE
/**
@@ -140,27 +150,47 @@
for(var/obj/item/embedded in part.embedded_objects)
choice_list[embedded] = image(embedded)
var/obj/item/choice = show_radial_menu(user, src, choice_list, tooltips = TRUE)
- for(var/obj/item/bodypart/part in bodyparts)
- if(choice in part.embedded_objects)
- body_part = part
+ body_part = get_embedded_part(choice)
if(!istype(choice) || !(choice in choice_list))
return
var/time_taken = choice.embedding.embedded_unsafe_removal_time * choice.w_class
user.visible_message(span_warning("[user] attempts to remove [choice] from [user.p_their()] [body_part.name]."),span_notice("You attempt to remove [choice] from your [body_part.name]... (It will take [DisplayTimeText(time_taken)].)"))
if(!do_after(user, time_taken, needhand = 1, target = src) && !(choice in body_part.embedded_objects))
return
- var/obj/item/ammo_casing/AC = choice
- var/damage_amount = choice.embedding.embedded_unsafe_removal_pain_multiplier * choice.w_class
- if((istype(AC) && !AC.harmful) || choice.taped)
- body_part.receive_damage(stamina = damage_amount * 0.25, sharpness = SHARP_EDGED)//Non-harmful stuff causes stamina damage when removed
- else
- body_part.receive_damage(damage_amount * 0.25, sharpness = SHARP_EDGED)//It hurts to rip it out, get surgery you dingus.
- body_part.check_wounding(WOUND_SLASH, damage_amount, 20, 0)
- if(remove_embedded_object(choice, get_turf(src), damage_amount))
+ if(remove_embedded_object(choice, get_turf(src), unsafe = TRUE) && !QDELETED(choice))
user.put_in_hands(choice)
user.visible_message("[user] successfully rips [choice] out of [user == src? p_their() : "[src]'s"] [body_part.name]!", span_notice("You successfully remove [choice] from your [body_part.name]."))
return TRUE
+/**
+ * Returns the bodypart that the item is embedded in or returns false if it is not currently embedded
+ */
+/mob/living/carbon/proc/get_embedded_part(obj/item/embedded)
+ if(!embedded)
+ return FALSE
+ var/obj/item/bodypart/body_part
+ for(var/obj/item/bodypart/part in bodyparts)
+ if(embedded in part.embedded_objects)
+ body_part = part
+ if(!body_part)
+ return FALSE
+
+ if(embedded.loc != src)
+ LAZYREMOVE(body_part.embedded_objects, embedded)
+ if(!has_embedded_objects())
+ clear_alert("embeddedobject")
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
+ return FALSE
+ return body_part
+
+/**
+ * Returns a list of all embedded objects
+ */
+/mob/living/carbon/proc/get_embedded_objects()
+ for(var/obj/item/bodypart/part in bodyparts)
+ if(part.embedded_objects)
+ LAZYADD(., part.embedded_objects)
+
/mob/living/carbon/proc/get_interaction_efficiency(zone)
var/obj/item/bodypart/limb = get_bodypart(zone)
if(!limb)
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 3af45acb7b0b..1ea29403b65c 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -10,7 +10,9 @@
var/list/obscured = check_obscured_slots()
if (handcuffed)
- . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] handcuffed!")
+ . += span_warning("[t_He] [t_is] handcuffed with [handcuffed.get_examine_string(user)]!")
+ if (legcuffed)
+ . += span_warning("[t_He] [t_is] legcuffed with [legcuffed.get_examine_string(user)]!")
if (head)
. += "[t_He] [t_is] wearing [head.get_examine_string(user)] on [t_his] head. "
if(wear_mask && !(SLOT_WEAR_MASK in obscured))
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 5772300410a9..dc392bccb4f5 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -70,14 +70,13 @@
if(hand_number)
. += span_warning("[t_He] [t_has] [hand_number > 1 ? "" : "a"] blood-stained hand[hand_number > 1 ? "s" : ""]!")
- //handcuffed?
-
//handcuffed?
if(handcuffed)
- if(istype(handcuffed, /obj/item/restraints/handcuffs/cable))
- . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] restrained with cable!")
- else
- . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] handcuffed!")
+ . += span_warning("[t_He] [t_is] handcuffed with [handcuffed.get_examine_string(user)]!")
+
+ //legcuffed?
+ if(legcuffed)
+ . += span_warning("[t_He] [t_is] legcuffed with [legcuffed.get_examine_string(user)]!")
//belt
if(belt)
@@ -559,10 +558,11 @@
//handcuffed?
if(handcuffed)
- if(istype(handcuffed, /obj/item/restraints/handcuffs/cable))
- . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] restrained with cable!")
- else
- . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] handcuffed!")
+ . += span_warning("[t_He] [t_is] handcuffed with [handcuffed.get_examine_string(user)]!")
+
+ //legcuffed?
+ if(legcuffed)
+ . += span_warning("[t_He] [t_is] legcuffed with [legcuffed.get_examine_string(user)]!")
//belt
if(belt)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 0c6dce37b89e..8b391d21038f 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1244,6 +1244,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(!disable_warning)
to_chat(H, "You somehow have a suit with no defined allowed items for suit storage, stop that.")
return FALSE
+ if(I.slot_flags & ITEM_SLOT_DENY_S_STORE)
+ return FALSE
if(I.w_class > WEIGHT_CLASS_BULKY)
if(!disable_warning)
to_chat(H, "The [I.name] is too big to attach.") //should be src?
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index d5ff466fc2b4..4e0ad8c466ca 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -659,7 +659,11 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
for(var/obj/item/I in BP.embedded_objects)
- I.embed_tick(src, BP)
+ // If its not embedded, don't bother proccessing it
+ if(!get_embedded_part(I))
+ continue
+ if(CHECK_BITFIELD(SEND_SIGNAL(I, COMSIG_ITEM_EMBED_TICK, src, BP), COMSIG_ITEM_BLOCK_EMBED_TICK))
+ continue
var/pain_chance_current = I.embedding.embedded_pain_chance
if(!(mobility_flags & MOBILITY_STAND))
pain_chance_current *= 0.2
@@ -675,9 +679,6 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier, wound_bonus = CANT_WOUND) // can wound
remove_embedded_object(I, drop_location(), FALSE)
visible_message(span_danger("[I] falls out of [name]'s [BP.name]!"), span_userdanger("[I] falls out of your [BP.name]!"))
- if(!has_embedded_objects())
- clear_alert("embeddedobject")
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
/////////////////////////////////////
//MONKEYS WITH TOO MUCH CHOLOESTROL//
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 097a86d58e98..eb0c0337cec3 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -184,9 +184,9 @@
clear_alert("legcuffed")
if(legcuffed)
var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER)
- legcuffs.color = handcuffed.color
+ legcuffs.color = legcuffed.color
- overlays_standing[HANDCUFF_LAYER] = legcuffs
+ overlays_standing[LEGCUFF_LAYER] = legcuffs
apply_overlay(LEGCUFF_LAYER)
throw_alert("legcuffed", /atom/movable/screen/alert/restrained/legcuffed, new_master = legcuffed)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index d55e42974dc8..87af8f17d3b4 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -65,6 +65,10 @@
guardian_pass = (src in safe)
if(guardian_pass)
return BULLET_ACT_FORCE_PIERCE
+
+ var/sig_return = SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
+ if(sig_return != NONE)
+ return sig_return
if(!P.nodamage)
last_damage = P.name
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index c536c55fb915..5c42f7d00c02 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -741,7 +741,7 @@ Difficulty: Very Hard
charge_max = 200
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 0
summon_type = list(
/obj/structure/flora/ausbushes,
@@ -881,7 +881,7 @@ Difficulty: Very Hard
desc = "Exits the body you are possessing."
charge_max = 60
clothes_req = 0
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
max_targets = 1
range = -1
include_user = TRUE
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index bcaec997b825..93936f1da928 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -860,11 +860,11 @@
for(var/obj/effect/proc_holder/spell/S in spells)
if(S.can_be_cast_by(src))
switch(S.charge_type)
- if("recharge")
+ if(SPELL_CHARGE_TYPE_RECHARGE)
L[++L.len] = list("[S.panel]", "[S.charge_counter/10.0]/[S.charge_max/10]", S.name, REF(S))
- if("charges")
+ if(SPELL_CHARGE_TYPE_CHARGES)
L[++L.len] = list("[S.panel]", "[S.charge_counter]/[S.charge_max]", S.name, REF(S))
- if("holdervar")
+ if(SPELL_CHARGE_TYPE_HOLDERVAR)
L[++L.len] = list("[S.panel]", "[S.holder_var_type] [S.holder_var_amount]", S.name, REF(S))
return L
diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm
index b7839a898651..cce2cfb012f3 100644
--- a/code/modules/projectiles/ammunition/_ammunition.dm
+++ b/code/modules/projectiles/ammunition/_ammunition.dm
@@ -8,6 +8,8 @@
throwforce = 0
w_class = WEIGHT_CLASS_TINY
materials = list(/datum/material/iron = 500)
+ /// Bitflags for casings, check code\__DEFINES\combat.dm for a list of available flags and what they do
+ var/casing_flags = NONE
var/fire_sound = null //What sound should play when this ammo is fired
var/caliber = null //Which kind of guns it can be loaded into
var/projectile_type = null //The bullet type to create when New() is called
@@ -18,7 +20,6 @@
var/delay = 0 //Delay for energy weapons
var/click_cooldown_override = 0 //Override this to make your gun have a faster fire rate, in tenths of a second. 4 is the default gun cooldown.
var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the ammo is fired.
- var/heavy_metal = TRUE
var/harmful = TRUE //pacifism check for boolet, set to FALSE if bullet is non-lethal
/obj/item/ammo_casing/spent
@@ -36,8 +37,8 @@
/obj/item/ammo_casing/update_icon()
..()
- icon_state = "[initial(icon_state)][BB ? "-live" : ""]"
- desc = "[initial(desc)][BB ? "" : " This one is spent."]"
+ icon_state = "[initial(icon_state)][BB && !CHECK_BITFIELD(casing_flags, CASINGFLAG_NO_LIVE_SPRITE) ? "-live" : ""]"
+ desc = "[initial(desc)][!BB && !CHECK_BITFIELD(casing_flags, CASINGFLAG_NO_LIVE_SPRITE) ? " This one is spent." : ""]"
//proc to magically refill a casing with a new projectile
/obj/item/ammo_casing/proc/newshot() //For energy weapons, syringe gun, shotgun shells and wands (!).
@@ -66,7 +67,7 @@
return ..()
/obj/item/ammo_casing/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- if(heavy_metal)
+ if(!CHECK_BITFIELD(casing_flags, CASINGFLAG_NOT_HEAVY_METAL))
bounce_away(FALSE, NONE)
. = ..()
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index a02cc911a1e6..846c96a80fb0 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -55,8 +55,9 @@
direct_target = target
if(!direct_target)
BB.preparePixelProjectile(target, user, params, spread)
- BB.fire(null, direct_target)
+ var/obj/item/projectile/old_BB = BB // Need to set BB to null first, otherwise casings that create new projectiles when they land witll break if fired point blank
BB = null
+ old_BB.fire(null, direct_target)
return TRUE
/obj/item/ammo_casing/proc/spread(turf/target, turf/current, distro)
diff --git a/code/modules/projectiles/ammunition/caseless/_caseless.dm b/code/modules/projectiles/ammunition/caseless/_caseless.dm
index db1aa6562c46..dd162f77c81b 100644
--- a/code/modules/projectiles/ammunition/caseless/_caseless.dm
+++ b/code/modules/projectiles/ammunition/caseless/_caseless.dm
@@ -1,7 +1,7 @@
/obj/item/ammo_casing/caseless
desc = "A caseless bullet casing."
firing_effect_type = null
- heavy_metal = FALSE
+ casing_flags = CASINGFLAG_NO_LIVE_SPRITE | CASINGFLAG_FORCE_CLEAR_CHAMBER | CASINGFLAG_NOT_HEAVY_METAL
/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
if (..()) //successfully firing
@@ -10,7 +10,3 @@
return TRUE
else
return FALSE
-
-/obj/item/ammo_casing/caseless/update_icon()
- ..()
- icon_state = "[initial(icon_state)]"
diff --git a/code/modules/projectiles/ammunition/caseless/arrow.dm b/code/modules/projectiles/ammunition/caseless/arrow.dm
deleted file mode 100644
index c64b1f75258b..000000000000
--- a/code/modules/projectiles/ammunition/caseless/arrow.dm
+++ /dev/null
@@ -1,198 +0,0 @@
-/obj/item/ammo_casing/caseless/arrow
- name = "arrow"
- desc = "An arrow, typically fired from a bow."
- projectile_type = /obj/item/projectile/bullet/reusable/arrow
- caliber = "arrow"
- icon_state = "arrow"
- force = 5
- throwforce = 5 //good luck hitting someone with the pointy end of the arrow
- throw_speed = 3
- sharpness = SHARP_POINTY
- embedding = list("embed_chance" = 25, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/wood
- name = "wooden arrow"
- desc = "An arrow made of wood, typically fired from a bow."
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/wood
-
-/obj/item/ammo_casing/caseless/arrow/ash
- name = "ashen arrow"
- desc = "An arrow made from wood, hardened by fire"
- icon_state = "ashenarrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/ash
- embedding = list("embed_chance" = 30, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/bone_tipped
- name = "bone tipped arrow"
- desc = "An arrow made of bone, wood, and sinew. The tip is sharp enough to pierce goliath hide."
- icon_state = "bonetippedarrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/bone_tipped
- embedding = list("embed_chance" = 10, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/bone
- name = "bone arrow"
- desc = "A flimsy arrow made of bone. Not strong or durable, but is easy to make."
- icon_state = "bonearrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/bone
- embedding = list("embed_chance" = 10, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/chitin
- name = "chitin tipped arrow"
- desc = "A sharp arrow made of the guts of lavaland monsters. The tip is sharp and jagged, making it easier to get stuck into your target."
- icon_state = "chitinarrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/chitin
- embedding = list("embed_chance" = 10, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/bamboo
- name = "bamboo arrow"
- desc = "A flimsy arrow made of bamboo. Not strong or durable, but is easy to make."
- icon_state = "bambooarrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/bamboo
- embedding = list("embed_chance" = 15, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/bronze
- name = "bronze arrow"
- desc = "An arrow made from wood. tipped with bronze."
- icon_state = "bronzearrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/bronze
- embedding = list("embed_chance" = 15, "embedded_fall_chance" = 0)
-
-/obj/item/ammo_casing/caseless/arrow/glass
- name = "glass arrow"
- desc = "An arrow made from a metal rod, wrapped in wires, and tipped with glass."
- icon_state = "glassarrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/glass
- variance = 10
-
-/obj/item/ammo_casing/caseless/arrow/glass/plasma
- name = "plasma glass arrow"
- desc = "An arrow made from a metal rod, wrapped in wires, and tipped with plasma glass."
- icon_state = "plasmaglassarrow"
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/glass/plasma
- variance = 10
-
-/obj/item/ammo_casing/caseless/arrow/bola
- name = "bola arrow"
- desc = "An arrow made from wood. a bola is wrapped around it."
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/bola
-
-/obj/item/ammo_casing/caseless/arrow/bola/Initialize()
- ..()
- var/obj/item/ammo_casing/caseless/arrow/A = locate(/obj/item/ammo_casing/caseless/arrow) in contents
- if(istype(A))
- icon = A.icon
- icon_state = A.icon_state
- var/obj/item/projectile/bullet/reusable/arrow/AA = A.BB
- var/obj/item/projectile/bullet/reusable/arrow/BBB = BB
- if(istype(AA) && istype(BBB))
- BBB.damage = AA.damage * 0.5
- BBB.armour_penetration = AA.armour_penetration * 0.5
- BBB.embed_chance = AA.embed_chance * 0.5
- BBB.ammo_type = AA.ammo_type
-
- var/obj/item/restraints/legcuffs/bola/bola = locate(/obj/item/restraints/legcuffs/bola) in contents
- var/obj/item/projectile/bullet/reusable/arrow/bola/bola_arrow = BB
- if(!istype(bola))
- bola = new(src)
- if(istype(bola) && istype(bola_arrow))
- bola_arrow.bola = bola
-
- add_overlay(mutable_appearance(icon, "arrow_bola"), TRUE)
-/*
-/obj/item/ammo_casing/caseless/arrow/explosive
- name = "explosive arrow"
- desc = "An arrow made from wood. an explosive is attached to it."
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/explosive
-
-/obj/item/ammo_casing/caseless/arrow/explosive/Initialize()
- ..()
- var/obj/item/ammo_casing/caseless/arrow/A = locate(/obj/item/ammo_casing/caseless/arrow) in contents
- if(istype(A))
- icon = A.icon
- icon_state = A.icon_state
- var/obj/item/projectile/bullet/reusable/AA = A.BB
- var/obj/item/projectile/bullet/reusable/BBB = BB
- if(istype(AA) && istype(BBB))
- BBB.damage = AA.damage * 0.5
- BBB.armour_penetration = AA.armour_penetration * 0.5
- BBB.ammo_type = AA.ammo_type
-
- var/obj/item/grenade/explosive = locate(/obj/item/grenade) in contents
- var/obj/item/projectile/bullet/reusable/arrow/explosive/explosive_arrow = BB
- if(!istype(explosive))
- explosive = new /obj/item/grenade/plastic/c4(src)
- if(istype(explosive) && istype(explosive_arrow))
- explosive_arrow.explosive = explosive
-
- add_overlay(mutable_appearance(icon, "arrow_explosive"), TRUE)
-*/
-/obj/item/ammo_casing/caseless/arrow/flaming
- name = "flaming arrow"
- desc = "An arrow made from wood. lit on fire."
- projectile_type = /obj/item/projectile/bullet/reusable/arrow/flaming
-
-/obj/item/ammo_casing/caseless/arrow/flaming/Initialize()
- ..()
- var/obj/item/ammo_casing/caseless/arrow/A = locate(/obj/item/ammo_casing/caseless/arrow) in contents
- if(istype(A))
- icon = A.icon
- icon_state = A.icon_state
- var/obj/item/projectile/bullet/reusable/arrow/AA = A.BB
- var/obj/item/projectile/bullet/reusable/arrow/BBB = BB
- if(istype(AA) && istype(BBB))
- BBB.damage = AA.damage * 0.5
- BBB.armour_penetration = AA.armour_penetration * 0.5
- BBB.embed_chance = AA.embed_chance * 0.5
- BBB.ammo_type = AA.ammo_type
-
- add_overlay(mutable_appearance(icon, "arrow_fire"), TRUE)
-
-// Energy Arrows //
-
-/obj/item/ammo_casing/caseless/arrow/energy
- name = "energy bolt"
- desc = "An arrow made from hardlight."
- icon_state = "arrow_energy"
- item_flags = DROPDEL
- embedding = list("embedded_pain_chance" = 0, "embedded_pain_multiplier" = 0, "embedded_unsafe_removal_pain_multiplier" = 0, "embedded_pain_chance" = 0, "embedded_fall_chance" = 0)
- projectile_type = /obj/item/projectile/energy/arrow
- var/overlay_state = "redlight"
-
- var/ticks = 0
- var/tick_max = 10
- var/tick_damage = 1
- var/tick_damage_type = FIRE
- var/tick_sound = 'sound/effects/sparks4.ogg'
-
-/obj/item/ammo_casing/caseless/arrow/energy/on_embed_removal(mob/living/carbon/human/embedde)
- qdel(src)
-
-/obj/item/ammo_casing/caseless/arrow/energy/embed_tick(mob/living/carbon/human/embedde, obj/item/bodypart/part)
- if(ticks >= tick_max)
- embedde.remove_embedded_object(src, , TRUE, TRUE)
- return
- ticks++
- playsound(embedde, tick_sound , 10, 0)
- embedde.apply_damage(tick_damage, BB.damage_type, part.body_zone)
-
-/obj/item/ammo_casing/caseless/arrow/energy/disabler
- name = "disabler bolt"
- desc = "An arrow made from hardlight. This one stuns the victim in a non-lethal way."
- icon_state = "arrow_disable"
- overlay_state = "disable"
- projectile_type = /obj/item/projectile/energy/arrow/disabler
- harmful = FALSE
- tick_damage_type = STAMINA
-
-/obj/item/ammo_casing/caseless/arrow/energy/xray
- name = "X-ray bolt"
- desc = "An arrow made from hardlight. This one can pass through obstructions."
- icon_state = "arrow_xray"
- overlay_state = "xray"
- projectile_type = /obj/item/projectile/energy/arrow/xray
- tick_damage_type = TOX
-
-/obj/item/ammo_casing/caseless/arrow/energy/clockbolt
- name = "redlight bolt"
- desc = "An arrow made from a strange energy."
- projectile_type = /obj/item/projectile/energy/arrow/clockbolt
diff --git a/code/modules/projectiles/ammunition/energy/_energy.dm b/code/modules/projectiles/ammunition/energy/_energy.dm
index 02fb510a2ebe..9076ebe93627 100644
--- a/code/modules/projectiles/ammunition/energy/_energy.dm
+++ b/code/modules/projectiles/ammunition/energy/_energy.dm
@@ -7,4 +7,4 @@
var/select_name = "energy"
fire_sound = 'sound/weapons/laser.ogg'
firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
- heavy_metal = FALSE
+ casing_flags = CASINGFLAG_NOT_HEAVY_METAL
diff --git a/code/modules/projectiles/ammunition/reusable/_reusable.dm b/code/modules/projectiles/ammunition/reusable/_reusable.dm
new file mode 100644
index 000000000000..64cd363bec47
--- /dev/null
+++ b/code/modules/projectiles/ammunition/reusable/_reusable.dm
@@ -0,0 +1,34 @@
+// For casing that are dropped when the projectile has hit, usually for casing that are the projectiles like foam darts or arrows.
+// They don't get deleted when fire and instead are moved to the projectile until it lands, where it is then dropped.
+// Intended to be used with '/obj/item/projectile/bullet/reusable'.
+/obj/item/ammo_casing/reusable
+ desc = "A reusable bullet casing."
+ firing_effect_type = null
+ casing_flags = CASINGFLAG_NO_LIVE_SPRITE | CASINGFLAG_FORCE_CLEAR_CHAMBER | CASINGFLAG_NOT_HEAVY_METAL
+
+ /// If the projectile is currently being shot as a projectile
+ var/in_air = FALSE
+ /// How much the projectiles rotation should be adjusted to make this properly line up when it lands, mainly for thing like arrows where the sprite isn't stright up and down.
+ var/base_rotation = 0
+
+/obj/item/ammo_casing/reusable/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/fired_from)
+ ..()
+ if(!BB)
+ newshot() // Just in case it wasn't replaced. Should be replaced when the projectile landed in case a subtype wants to change it, this is just a failsafe.
+ var/obj/item/projectile/bullet/reusable/reusable_projectile = BB
+ if(istype(reusable_projectile))
+ reusable_projectile.ammo_type = src
+ forceMove(BB)
+ in_air = TRUE
+
+/obj/item/ammo_casing/reusable/proc/on_land(var/obj/item/projectile/old_projectile)
+ if(istype(old_projectile))
+ pixel_x = old_projectile.pixel_x
+ pixel_y = old_projectile.pixel_y
+ var/matrix/M = matrix(transform)
+ M.Turn(old_projectile.Angle - base_rotation)
+ transform = M
+ if(!BB)
+ newshot()
+ in_air = FALSE
+ update_icon()
diff --git a/code/modules/projectiles/ammunition/reusable/arrow.dm b/code/modules/projectiles/ammunition/reusable/arrow.dm
new file mode 100644
index 000000000000..39f9492767b2
--- /dev/null
+++ b/code/modules/projectiles/ammunition/reusable/arrow.dm
@@ -0,0 +1,577 @@
+/obj/item/ammo_casing/reusable/arrow
+ name = "arrow"
+ desc = "An arrow, typically fired from a bow."
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow
+ caliber = "arrow"
+ icon_state = "arrow"
+ item_state = "arrow"
+ lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
+ base_rotation = 45
+ force = 5
+ throwforce = 5 //If, if you want to throw the arrow since you don't have a bow?
+ throw_speed = 3
+ sharpness = SHARP_POINTY
+ embedding = list("embed_chance" = 25, "embedded_fall_chance" = 0)
+
+ // Arrow
+ /// List of all attached parts to move to the projectile when fired
+ var/list/attached_parts
+ /// Attached explosive
+ var/obj/item/grenade/explosive
+ /// Attached bola
+ var/obj/item/restraints/legcuffs/bola/bola
+ /// Attached syringe
+ var/obj/item/reagent_containers/syringe/syringe
+ /// If the arrow is on fire
+ var/flaming = FALSE
+
+/obj/item/ammo_casing/reusable/arrow/Initialize()
+ RegisterSignal(src, COMSIG_ITEM_EMBEDDED, PROC_REF(on_embed))
+ RegisterSignal(src, COMSIG_ITEM_EMBED_TICK, PROC_REF(embed_tick))
+ var/list/new_parts
+ if(ispath(explosive))
+ LAZYADD(new_parts, new explosive())
+ if(ispath(bola))
+ LAZYADD(new_parts, new bola())
+ if(ispath(syringe))
+ LAZYADD(new_parts, new syringe())
+ ..()
+ if(LAZYLEN(new_parts))
+ CheckParts(new_parts)
+
+/obj/item/ammo_casing/reusable/arrow/update_icon(force_update)
+ ..()
+ cut_overlays()
+ if(istype(explosive))
+ add_overlay(mutable_appearance(icon, "arrow_explosive[explosive.active ? "_active" : ""]"), TRUE)
+ if(istype(bola))
+ add_overlay(mutable_appearance(icon, "arrow_bola"), TRUE)
+ if(istype(syringe))
+ add_overlay(mutable_appearance(icon, "arrow_syringe"), TRUE)
+ if(syringe.reagents && syringe.reagents.total_volume)
+ var/image/filling_overlay = mutable_appearance(icon, "arrow_syringe[clamp(round((syringe.reagents.total_volume / syringe.volume * 15),5), 1, 15)]")
+ filling_overlay.color = mix_color_from_reagents(syringe.reagents.reagent_list)
+ add_overlay(filling_overlay)
+ if(flaming)
+ add_overlay(mutable_appearance(icon, "arrow_fire"), TRUE)
+
+/obj/item/ammo_casing/reusable/arrow/examine(mob/user)
+ . = ..()
+ if(explosive)
+ . += "It has [explosive.active ? "an armed " : ""][explosive] attached."
+ if(bola)
+ . += "It has [bola] attached."
+ if(syringe)
+ . += "It has [syringe] attached."
+ if(LAZYLEN(attached_parts))
+ . += "The added parts can be removed with a wirecutter."
+ if(flaming)
+ . += "It is on fire."
+
+/obj/item/ammo_casing/reusable/arrow/attack_self(mob/user)
+ if(istype(explosive))
+ explosive.attack_self(user)
+ add_fingerprint(user)
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ C.throw_mode_off()
+ update_icon()
+ return ..()
+
+/obj/item/ammo_casing/reusable/arrow/wirecutter_act(mob/living/user, obj/item/I)
+ var/obj/item/projectile/bullet/reusable/arrow/arrow = BB
+ if(!istype(arrow))
+ return
+ if(!LAZYLEN(attached_parts))
+ to_chat(user, span_warning("There is nothing to remove!"))
+ return
+ if(explosive)
+ explosive = null
+ if(bola)
+ bola = null
+ for(var/obj/item/part in attached_parts)
+ if(!part.forceMove(part.drop_location()))
+ qdel(part)
+ attached_parts = null
+ to_chat(user, span_notice("You remove the attached parts."))
+
+
+/obj/item/ammo_casing/reusable/arrow/CheckParts(list/parts_list)
+ var/obj/item/ammo_casing/reusable/arrow/A = locate(/obj/item/ammo_casing/reusable/arrow) in parts_list
+ if(A)
+ LAZYREMOVE(parts_list, A)
+ if(flaming)
+ add_flame()
+ A.CheckParts(parts_list)
+ qdel(src)
+ for(var/obj/item/grenade/G in parts_list)
+ if(G)
+ if(istype(explosive))
+ G.forceMove(G.drop_location())
+ else
+ add_explosive(G)
+ for(var/obj/item/restraints/legcuffs/bola/B in parts_list)
+ if(B)
+ if(istype(bola))
+ B.forceMove(B.drop_location())
+ else
+ add_bola(B)
+ for(var/obj/item/reagent_containers/syringe/S in parts_list)
+ if(S)
+ if(istype(syringe))
+ S.forceMove(S.drop_location())
+ else
+ add_syringe(S)
+ for(var/obj/item/restraints/handcuffs/cable/C in parts_list)
+ LAZYADD(attached_parts, C)
+ ..()
+
+/obj/item/ammo_casing/reusable/arrow/proc/add_explosive(obj/item/grenade/new_explosive)
+ if(istype(new_explosive))
+ explosive = new_explosive
+ LAZYADD(attached_parts, new_explosive)
+ update_icon()
+
+/obj/item/ammo_casing/reusable/arrow/proc/add_bola(obj/item/restraints/legcuffs/bola/new_bola)
+ if(istype(new_bola))
+ bola = new_bola
+ LAZYADD(attached_parts, new_bola)
+ update_icon()
+
+/obj/item/ammo_casing/reusable/arrow/proc/add_syringe(obj/item/reagent_containers/syringe/new_syringe)
+ if(istype(new_syringe))
+ syringe = new_syringe
+ LAZYADD(attached_parts, new_syringe)
+ update_icon()
+
+/obj/item/ammo_casing/reusable/arrow/proc/add_flame()
+ flaming = TRUE
+ update_icon()
+
+/obj/item/ammo_casing/reusable/arrow/proc/on_embed(target, mob/living/carbon/embedde)
+ if(syringe)
+ syringe.embed_inject(target, embedde)
+
+/obj/item/ammo_casing/reusable/arrow/proc/embed_tick(target, mob/living/carbon/embedde)
+ if(syringe)
+ syringe.embed_inject(target, embedde)
+
+
+// Arrow Subtypes //
+
+/obj/item/ammo_casing/reusable/arrow/wood
+ name = "wooden arrow"
+ desc = "A wooden arrow, quickly made."
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/wood
+
+/obj/item/ammo_casing/reusable/arrow/ash
+ name = "ashen arrow"
+ desc = "A wooden arrow tempered by fire. It's tougher, but less likely to embed."
+ icon_state = "ashenarrow"
+ item_state = "ashenarrow"
+ force = 7
+ throwforce = 7
+ embedding = list("embed_chance" = 15, "embedded_fall_chance" = 0)
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/ash
+
+/obj/item/ammo_casing/reusable/arrow/bone_tipped
+ name = "bone-tipped arrow"
+ desc = "An arrow made from bone, wood, and sinew. Sturdy and sharp."
+ icon_state = "bonetippedarrow"
+ item_state = "bonetippedarrow"
+ force = 9
+ throwforce = 9
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/bone_tipped
+
+/obj/item/ammo_casing/reusable/arrow/bone
+ name = "bone arrow"
+ desc = "An arrow made from bone and sinew. Better at hunting fauna."
+ icon_state = "bonearrow"
+ item_state = "bonearrow"
+ force = 4
+ throwforce = 4
+ embedding = list("embed_chance" = 20, "embedded_fall_chance" = 0)
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/bone
+
+/obj/item/ammo_casing/reusable/arrow/chitin
+ name = "chitin-tipped arrow"
+ desc = "An arrow made from chitin, bone, and sinew. Incredibly potent at puncturing armor and hunting fauna."
+ icon_state = "chitinarrow"
+ item_state = "chitinarrow"
+ armour_penetration = 25 //Ah yes the 25 AP on a 5 force hit
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/chitin
+
+/obj/item/ammo_casing/reusable/arrow/bamboo
+ name = "bamboo arrow"
+ desc = "An arrow made from bamboo. Incredibly fragile and weak, but prone to shattering in unarmored targets."
+ icon_state = "bambooarrow"
+ item_state = "bambooarrow"
+ force = 3
+ throwforce = 3
+ armour_penetration = -10
+ embedding = list("embed_chance" = 35, "embedded_fall_chance" = 0)
+ variance = 10
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/bamboo
+
+/obj/item/ammo_casing/reusable/arrow/bronze
+ name = "bronze arrow"
+ desc = "An arrow tipped with bronze. Better against armor than iron."
+ icon_state = "bronzearrow"
+ item_state = "bronzearrow"
+ armour_penetration = 10
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/bronze
+
+/obj/item/ammo_casing/reusable/arrow/glass
+ name = "glass arrow"
+ desc = "A shoddy arrow with a broken glass shard as its tip. Can break upon impact."
+ icon_state = "glassarrow"
+ item_state = "glassarrow"
+ force = 4
+ throwforce = 4
+ embedding = list("embed_chance" = 15, "embedded_fall_chance" = 0)
+ variance = 5
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/glass
+
+/obj/item/ammo_casing/reusable/arrow/glass/plasma
+ name = "plasmaglass arrow"
+ desc = "An arrow with a plasmaglass shard affixed to its head. Incredibly capable of puncturing armor."
+ icon_state = "plasmaglassarrow"
+ item_state = "plasmaglassarrow"
+ armour_penetration = 40 //Ah yes the 40 AP on a 4 force hit
+ embedding = list("embed_chance" = 25, "embedded_fall_chance" = 0)
+ variance = 5
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/glass/plasma
+
+/obj/item/ammo_casing/reusable/arrow/magic
+ name = "magic arrow"
+ desc = "An arrow made of magic that can track targets, though it can't track those under the effects of anti-magic. Can make a good throwing weapon in a pinch!"
+ icon_state = "arrow_magic"
+ item_state = "arrow_magic"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/magic
+ force = 12
+ throwforce = 20
+ embedding = list("embed_chance" = 50, "embedded_fall_chance" = 0)
+ /// Causes the arrow to become weaker, as I was told to prevent it from being used against wizards
+ var/dulled = FALSE
+
+/obj/item/ammo_casing/reusable/arrow/magic/examine(mob/user)
+ . = ..()
+ if(dulled)
+ . += "It appears to be dulled, and the tracking magic has left it."
+
+/obj/item/ammo_casing/reusable/arrow/magic/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/fired_from)
+ . = ..()
+ if(!.)
+ return
+
+ if(dulled)
+ BB.damage = 20
+ BB.armour_penetration = -25
+ var/obj/item/projectile/bullet/reusable/arrow/arrow = BB
+ if(!istype(arrow))
+ arrow.embed_chance = 0
+ else
+ BB.set_homing_target(target)
+ var/mob/M = target
+ if(istype(M) && M.anti_magic_check(chargecost = 0))
+ BB.homing_away = TRUE // And there it goes!
+
+/obj/item/ammo_casing/reusable/arrow/magic/on_land(var/obj/item/projectile/old_projectile)
+ dulled = TRUE
+ force = 3
+ throwforce = 0
+ sharpness = SHARP_NONE // It IS dull after all
+ . = ..()
+
+
+// Toy //
+
+/obj/item/ammo_casing/reusable/arrow/toy
+ name = "toy arrow"
+ desc = "A plastic arrow with a blunt tip covered in velcro to allow it to stick to whoever it hits."
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy
+ force = 0
+ throwforce = 0
+ sharpness = SHARP_NONE
+ embedding = list(100, 0, 0, 0, 0, 0, 0, 0.5, TRUE)
+ taped = TRUE
+
+/obj/item/ammo_casing/reusable/arrow/toy/energy
+ name = "toy energy bolt"
+ desc = "A deceiving arrow that looks to be lethal, but is a velcro-tipped toy. For use with toy bows."
+ icon_state = "arrow_energy"
+ item_state = "arrow_toy_energy"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy/energy
+
+/obj/item/ammo_casing/reusable/arrow/toy/disabler
+ name = "toy disabler bolt"
+ desc = "A toy arrow that looks like a disabler bolt fabricated from a hardlight bow. Tipped with velcro to allow it to stick to targets."
+ icon_state = "arrow_disable"
+ item_state = "arrow_toy_disable"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy/disabler
+
+/obj/item/ammo_casing/reusable/arrow/toy/pulse
+ name = "toy pulse bolt"
+ desc = "A plastic, fake arrow that looks like a pulse bolt. A velcro head lets it stick to targets."
+ icon_state = "arrow_pulse"
+ item_state = "arrow_toy_pulse"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy/pulse
+
+/obj/item/ammo_casing/reusable/arrow/toy/xray
+ name = "toy X-ray bolt"
+ desc = "A plastic arrow with a blunt tip covered in velcro to allow it to stick to whoever it hits. This one is made to resemble a X-ray bolt from a hardlight bow."
+ icon_state = "arrow_xray"
+ item_state = "arrow_toy_xray"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy/xray
+
+/obj/item/ammo_casing/reusable/arrow/toy/shock
+ name = "toy shock bolt"
+ desc = "A plastic arrow with a blunt tip covered in velcro to allow it to stick to whoever it hits. This one is made to resemble a shock bolt from a hardlight bow."
+ icon_state = "arrow_shock"
+ item_state = "arrow_toy_shock"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy/shock
+
+/obj/item/ammo_casing/reusable/arrow/toy/magic
+ name = "toy magic arrow"
+ desc = "A plastic arrow with a blunt tip covered in velcro to allow it to stick to whoever it hits. This one is made to resemble a magic arrow used by wizards."
+ icon_state = "arrow_magic"
+ item_state = "arrow_magic"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/toy/magic
+
+
+// Utility //
+
+/obj/item/ammo_casing/reusable/arrow/bola
+ bola = /obj/item/restraints/legcuffs/bola
+
+/obj/item/ammo_casing/reusable/arrow/explosive
+ explosive = /obj/item/grenade/iedcasing
+
+/obj/item/ammo_casing/reusable/arrow/syringe
+ syringe = /obj/item/reagent_containers/syringe/lethal/choral
+
+/obj/item/ammo_casing/reusable/arrow/flaming/Initialize()
+ ..()
+ add_flame()
+
+
+// Joke? //
+
+/obj/item/ammo_casing/reusable/arrow/supermatter
+ name = "supermatter-tipped arrow"
+ desc = "An arrow made of a hypernoblium-tipped rod, a shard of supermatter, and poor decision making."
+ icon_state = "supermatterarrow"
+ item_state = "supermatterarrow"
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/supermatter
+
+/obj/item/ammo_casing/reusable/arrow/supermatter/proc/disintigrate(atom/dusting)
+ if(ismob(dusting))
+ var/mob/ded = dusting
+ if(ded.status_flags & GODMODE)
+ return FALSE
+ dusting.visible_message(span_danger("As [ded] is impacted by [src], [ded.p_their()] body starts to glow and bursts into flames before flashing into dust!"),\
+ span_userdanger("You are hit by [src], and start to glow. Uh oh."),\
+ span_italics("Everything suddenly goes silent."))
+ radiation_pulse(src, 500, 2)
+ ded.dust()
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
+ return TRUE
+
+ else if(isobj(dusting))
+ dusting.visible_message(span_danger("As [dusting] is impacted by [src], [dusting.p_they()] burned into your eyes before disapearing into nothing!"))
+ radiation_pulse(src, 100, 2)
+ qdel(dusting)
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
+ return TRUE
+
+ else if(isturf(dusting))
+ var/turf/T = dusting
+ var/oldtype = T.type
+ var/oldname = T.name
+ var/turf/newT = T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
+ if(newT.type == oldtype)
+ return FALSE
+ dusting.visible_message(span_danger("As [oldname] is impacted by [src], its image is burned into your eyes before disapearing into nothing!"))
+ radiation_pulse(src, 50, 2)
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
+ return TRUE
+
+/obj/item/ammo_casing/reusable/arrow/supermatter/sliver
+ name = "supermatter sliver arrow"
+ desc = "An arrow made of a hypernoblium tipped rod, a sliver of supermatter, and poor decision making. It looks like it will only survive one hit."
+
+/obj/item/ammo_casing/reusable/arrow/supermatter/sliver/disintigrate(atom/dusting)
+ . = ..()
+ if(.)
+ qdel(src)
+
+/obj/item/ammo_casing/reusable/arrow/singulo
+ name = "singularity shard arrow"
+ desc = "An arrow with a shard of a singularity at the end. It radiates a small amout of radiation and slightly pulls you towards it."
+ icon_state = "singuloarrow"
+ item_state = "singuloarrow"
+ force = 4
+ throwforce = 4
+ embedding = list("embed_chance" = 15, "embedded_fall_chance" = 0)
+ variance = 5
+ projectile_type = /obj/item/projectile/bullet/reusable/arrow/singulo
+ /// The shard currently in the arrow
+ var/obj/item/singularity_shard/shard
+
+/obj/item/ammo_casing/reusable/arrow/singulo/Initialize()
+ ..()
+ // If the shard is a path, make a new one of that type and put it in the shard slot
+ if(ispath(shard))
+ CheckParts(list(new shard()))
+
+/obj/item/ammo_casing/reusable/arrow/singulo/CheckParts(list/parts_list)
+ var/obj/item/singularity_shard/new_shard = locate(/obj/item/singularity_shard) in parts_list
+ if(!new_shard)
+ if(!shard) // No shard, no point in having the arrow
+ qdel(src)
+ return ..()
+
+ if(istype(shard))
+ // If we already have a shard, drop the new one and keep the old one
+ new_shard.forceMove(new_shard.drop_location())
+ else
+ // Otherwise, move it to the arrow and make it the new shard
+ new_shard.forceMove(src)
+ shard = new_shard
+ update_icon()
+ ..()
+
+/obj/item/ammo_casing/reusable/arrow/singulo/update_icon(force_update)
+ ..()
+ if(istype(shard))
+ add_overlay(mutable_appearance(icon, "[icon_state]_[shard.icon_state]"), TRUE)
+
+/// Handles the special effect of the singulo arrow, called by the projectile on hit
+/obj/item/ammo_casing/reusable/arrow/singulo/proc/shard_effect()
+ // If the is no shard, may as well just delete this as it shouldn't exist anyways
+ if(!shard)
+ qdel(src)
+ return
+
+ /// Chance for the arrow to break on impact, if the shard is all powerful (stage 6), it will create a singularity when it breaks
+ var/break_chance = 0
+ /// The ammount of rads released on impact
+ var/rads_released = 0
+ switch(shard.type)
+ if(/obj/item/singularity_shard/stage1)
+ break_chance = 0.1
+ rads_released = 100
+ if(/obj/item/singularity_shard/stage2)
+ break_chance = 0.5
+ rads_released = 200
+ empulse(src, 0, 1)
+ if(/obj/item/singularity_shard/stage3)
+ break_chance = 1.5
+ rads_released = 500
+ empulse(src, 0, 3)
+ if(/obj/item/singularity_shard/stage4)
+ break_chance = 5
+ rads_released = 1000
+ empulse(src, 1, 5)
+ if(/obj/item/singularity_shard/stage5)
+ break_chance = 10
+ rads_released = 2000
+ empulse(src, 2, 7)
+ if(/obj/item/singularity_shard/stage6)
+ break_chance = 100
+ rads_released = 3000
+ empulse(src, 5, 15) // Its going to break open into a singulo anyways, may as well add some fireworks
+
+ // Handles releasing rads
+ if(rads_released)
+ radiation_pulse(src, rads_released, RAD_DISTANCE_COEFFICIENT * 0.5)
+
+ // Handles the shard breaking
+ if(prob(break_chance))
+ playsound(src, "shatter", 70, 1)
+ if(shard.all_powerful) // If it is all powerful, create a new singulo
+ new /obj/singularity(get_turf(src), 100)
+ visible_message(span_danger("\The [shard] shatters on impact, releasing a singularity!"))
+ else
+ visible_message(span_danger("\The [shard] shatters on impact!"))
+
+// A version of the singulo arrow that comes with a tier 6 shard in it
+/obj/item/ammo_casing/reusable/arrow/singulo/shard6
+ shard = /obj/item/singularity_shard/stage6
+
+
+// Hardlight //
+
+/obj/item/ammo_casing/reusable/arrow/energy
+ name = "energy bolt"
+ desc = "An arrow made from hardlight. This one burns the victim."
+ icon_state = "arrow_energy"
+ item_flags = DROPDEL
+ embedding = list("embedded_pain_chance" = 0, "embedded_pain_multiplier" = 0, "embedded_unsafe_removal_pain_multiplier" = 0, "embedded_fall_chance" = 0, "embedded_bleed_rate" = 0)
+ projectile_type = /obj/item/projectile/energy/arrow
+
+ // Embed tick damage vars //
+ /// How many embed ticks have passed
+ var/ticks = 0
+ /// The max number of embed ticks can be done before the arrow is deleted
+ var/tick_max = 10
+ /// How much damage is done per embed tick
+ var/tick_damage = 1
+ /// The damage type of the embed tick damage
+ var/tick_damage_type = FIRE
+ /// The sound that plays per embed tick
+ var/tick_sound = 'sound/effects/sparks4.ogg'
+
+/obj/item/ammo_casing/reusable/arrow/energy/Initialize()
+ RegisterSignal(src, COMSIG_ITEM_EMBED_REMOVAL, PROC_REF(on_embed_removal))
+ ..()
+
+/obj/item/ammo_casing/reusable/arrow/energy/proc/on_embed_removal(mob/living/carbon/human/embedde)
+ return COMSIG_ITEM_QDEL_EMBED_REMOVAL
+
+/obj/item/ammo_casing/reusable/arrow/energy/on_embed(mob/living/carbon/embedde)
+ return
+
+/obj/item/ammo_casing/reusable/arrow/energy/embed_tick(target, mob/living/carbon/human/embedde, obj/item/bodypart/part)
+ if(ticks >= tick_max)
+ embedde.remove_embedded_object(src, null, TRUE, TRUE)
+ return
+ ticks++
+ playsound(embedde, tick_sound , 10, 0)
+ embedde.apply_damage(tick_damage, tick_damage_type, part.body_zone)
+
+/obj/item/ammo_casing/reusable/arrow/energy/disabler
+ name = "disabler bolt"
+ desc = "An arrow made from hardlight. This one stuns the victim in a non-lethal way."
+ icon_state = "arrow_disable"
+ projectile_type = /obj/item/projectile/energy/arrow/disabler
+ harmful = FALSE
+ tick_damage_type = STAMINA
+
+/obj/item/ammo_casing/reusable/arrow/energy/pulse
+ name = "pulse bolt"
+ desc = "An arrow made from hardlight. This one eliminates any obstructions it hits."
+ icon_state = "arrow_pulse"
+ projectile_type = /obj/item/projectile/energy/arrow/pulse
+ tick_damage = 5
+
+/obj/item/ammo_casing/reusable/arrow/energy/xray
+ name = "X-ray bolt"
+ desc = "An arrow made from hardlight. This one can pass through obstructions."
+ icon_state = "arrow_xray"
+ projectile_type = /obj/item/projectile/energy/arrow/xray
+ tick_damage_type = TOX
+
+/obj/item/ammo_casing/reusable/arrow/energy/shock
+ name = "shock bolt"
+ desc = "An arrow made from hardlight. This one shocks the victim with harmless energy capable of stunning them."
+ icon_state = "arrow_shock"
+ projectile_type = /obj/item/projectile/energy/arrow/shock
+ harmful = FALSE
+ tick_damage_type = STAMINA
+
+/obj/item/ammo_casing/reusable/arrow/energy/clockbolt
+ name = "redlight bolt"
+ desc = "An arrow made from a strange energy."
+ projectile_type = /obj/item/projectile/energy/arrow/clockbolt
diff --git a/code/modules/projectiles/ammunition/caseless/foam.dm b/code/modules/projectiles/ammunition/reusable/foam.dm
similarity index 55%
rename from code/modules/projectiles/ammunition/caseless/foam.dm
rename to code/modules/projectiles/ammunition/reusable/foam.dm
index e1f8c72bc16a..90ebfb71bdf5 100644
--- a/code/modules/projectiles/ammunition/caseless/foam.dm
+++ b/code/modules/projectiles/ammunition/reusable/foam.dm
@@ -1,4 +1,4 @@
-/obj/item/ammo_casing/caseless/foam_dart
+/obj/item/ammo_casing/reusable/foam_dart
name = "foam dart"
desc = "It's nerf or nothing! Ages 8 and up."
projectile_type = /obj/item/projectile/bullet/reusable/foam_dart
@@ -8,38 +8,32 @@
materials = list(/datum/material/iron = 11.25)
harmful = FALSE
var/modified = FALSE
+ var/obj/item/pen/pen
-/obj/item/ammo_casing/caseless/foam_dart/update_icon()
+/obj/item/ammo_casing/reusable/foam_dart/update_icon()
..()
if (modified)
icon_state = "foamdart_empty"
- desc = "It's nerf or nothing! ... Although, this one doesn't look too safe."
- if(BB)
- BB.icon_state = "foamdart_empty"
else
icon_state = initial(icon_state)
- desc = "It's nerf or nothing! Ages 8 and up."
- if(BB)
- BB.icon_state = initial(BB.icon_state)
+/obj/item/ammo_casing/reusable/foam_dart/examine(mob/user)
+ . = ..()
+ if(modified)
+ . += "This one doesn't look too safe."
-/obj/item/ammo_casing/caseless/foam_dart/attackby(obj/item/A, mob/user, params)
- var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB
+/obj/item/ammo_casing/reusable/foam_dart/attackby(obj/item/A, mob/user, params)
if (A.tool_behaviour == TOOL_SCREWDRIVER && !modified)
modified = TRUE
- FD.modified = TRUE
- FD.damage_type = BRUTE
to_chat(user, span_notice("You pop the safety cap off [src]."))
update_icon()
else if (istype(A, /obj/item/pen))
if(modified)
- if(!FD.pen)
- harmful = TRUE
- if(!user.transferItemToLoc(A, FD))
+ if(!pen)
+ if(!user.transferItemToLoc(A, src))
return
- FD.pen = A
- FD.damage = 5
- FD.nodamage = FALSE
+ harmful = TRUE
+ pen = A
to_chat(user, span_notice("You insert [A] into [src]."))
else
to_chat(user, span_warning("There's already something in [src]."))
@@ -48,16 +42,23 @@
else
return ..()
-/obj/item/ammo_casing/caseless/foam_dart/attack_self(mob/living/user)
- var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB
- if(FD.pen)
- FD.damage = initial(FD.damage)
- FD.nodamage = initial(FD.nodamage)
- user.put_in_hands(FD.pen)
- to_chat(user, span_notice("You remove [FD.pen] from [src]."))
- FD.pen = null
+/obj/item/ammo_casing/reusable/foam_dart/attack_self(mob/living/user)
+ if(pen)
+ user.put_in_hands(pen)
+ pen = null
+ harmful = FALSE
+ to_chat(user, span_notice("You remove [pen] from [src]."))
-/obj/item/ammo_casing/caseless/foam_dart/riot
+/obj/item/ammo_casing/reusable/foam_dart/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/fired_from)
+ if(modified)
+ BB.damage_type = BRUTE
+ if(pen)
+ BB.damage = 5
+ BB.nodamage = FALSE
+ BB.icon_state = "[icon_state]_proj"
+ ..()
+
+/obj/item/ammo_casing/reusable/foam_dart/riot
name = "riot foam dart"
desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up."
projectile_type = /obj/item/projectile/bullet/reusable/foam_dart/riot
diff --git a/code/modules/projectiles/ammunition/special/magic.dm b/code/modules/projectiles/ammunition/special/magic.dm
index 405035c58b6d..aa429d6be159 100644
--- a/code/modules/projectiles/ammunition/special/magic.dm
+++ b/code/modules/projectiles/ammunition/special/magic.dm
@@ -3,7 +3,7 @@
desc = "I didn't even know magic needed ammo..."
projectile_type = /obj/item/projectile/magic
firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/magic
- heavy_metal = FALSE
+ casing_flags = CASINGFLAG_NOT_HEAVY_METAL
/obj/item/ammo_casing/magic/change
projectile_type = /obj/item/projectile/magic/change
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index 500cff120293..00089d50cd41 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -164,12 +164,53 @@
name = "ammo box (Foam Darts)"
icon = 'icons/obj/guns/toy.dmi'
icon_state = "foambox"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
caliber = "foam_force"
max_ammo = 40
materials = list(/datum/material/iron = 500)
/obj/item/ammo_box/foambox/riot
icon_state = "foambox_riot"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart/riot
materials = list(/datum/material/iron = 50000)
+
+// Arrows //
+/obj/item/ammo_box/arrow
+ name = "ammo box (Arrow)"
+ icon_state = "arrowbox_green"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow
+ max_ammo = 10
+
+/obj/item/ammo_box/arrow/toy
+ name = "ammo box (Toy Arrow)"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy
+
+/obj/item/ammo_box/arrow/toy/energy
+ name = "ammo box (Toy Energy Arrow)"
+ icon_state = "arrowbox_red"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy/energy
+
+/obj/item/ammo_box/arrow/toy/disabler
+ name = "ammo box (Toy Disabler Arrow)"
+ icon_state = "arrowbox_teal"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy/disabler
+
+/obj/item/ammo_box/arrow/toy/pulse
+ name = "ammo box (Toy Pulse Arrow)"
+ icon_state = "arrowbox_blue"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy/pulse
+
+/obj/item/ammo_box/arrow/toy/xray
+ name = "ammo box (Toy X-ray Arrow)"
+ icon_state = "arrowbox_green"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy/xray
+
+/obj/item/ammo_box/arrow/toy/shock
+ name = "ammo box (Toy Shock Arrow)"
+ icon_state = "arrowbox_yellow"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy/shock
+
+/obj/item/ammo_box/arrow/toy/magic
+ name = "ammo box (Toy Magic Arrow)"
+ icon_state = "arrowbox_purple"
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/toy/magic
diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm
index fcb8cdc6e71d..82c97ea054ff 100644
--- a/code/modules/projectiles/boxes_magazines/external/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/external/toy.dm
@@ -1,12 +1,12 @@
/obj/item/ammo_box/magazine/toy
name = "foam force META magazine"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
caliber = "foam_force"
/obj/item/ammo_box/magazine/toy/smg
name = "foam force SMG magazine"
icon_state = "smg9mm-42"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
max_ammo = 20
/obj/item/ammo_box/magazine/toy/smg/update_icon()
@@ -17,7 +17,7 @@
icon_state = "smg9mm-0"
/obj/item/ammo_box/magazine/toy/smg/riot
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart/riot
/obj/item/ammo_box/magazine/toy/pistol
name = "foam force pistol magazine"
@@ -26,13 +26,13 @@
multiple_sprites = AMMO_BOX_FULL_EMPTY
/obj/item/ammo_box/magazine/toy/pistol/riot
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart/riot
/obj/item/ammo_box/magazine/toy/smgm45
name = "donksoft SMG magazine"
icon_state = "c20r45-toy"
caliber = "foam_force"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
max_ammo = 20
/obj/item/ammo_box/magazine/toy/smgm45/update_icon()
@@ -41,13 +41,13 @@
/obj/item/ammo_box/magazine/toy/smgm45/riot
icon_state = "c20r45-riot"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart/riot
/obj/item/ammo_box/magazine/toy/m762
name = "donksoft box magazine"
icon_state = "a762-toy"
caliber = "foam_force"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
max_ammo = 50
/obj/item/ammo_box/magazine/toy/m762/update_icon()
@@ -56,4 +56,4 @@
/obj/item/ammo_box/magazine/toy/m762/riot
icon_state = "a762-riot"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart/riot
diff --git a/code/modules/projectiles/boxes_magazines/internal/bow.dm b/code/modules/projectiles/boxes_magazines/internal/bow.dm
index a6e8b7811d2e..785dbfbe51b3 100644
--- a/code/modules/projectiles/boxes_magazines/internal/bow.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/bow.dm
@@ -1,28 +1,31 @@
/obj/item/ammo_box/magazine/internal/bow
name = "bow... magazine?" //shouldnt see this item
- ammo_type = /obj/item/ammo_casing/caseless/arrow
+ ammo_type = /obj/item/ammo_casing/reusable/arrow
caliber = "arrow"
max_ammo = 1
start_empty = TRUE
/obj/item/ammo_box/magazine/internal/bow/energy
- ammo_type = /obj/item/ammo_casing/caseless/arrow/energy
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/energy
start_empty = FALSE
- var/list/selectable_types = list(/obj/item/ammo_casing/caseless/arrow/energy, /obj/item/ammo_casing/caseless/arrow/energy/disabler)
+ var/list/selectable_types = list(/obj/item/ammo_casing/reusable/arrow/energy, /obj/item/ammo_casing/reusable/arrow/energy/disabler)
/obj/item/ammo_box/magazine/internal/bow/energy/advanced
- selectable_types = list(/obj/item/ammo_casing/caseless/arrow/energy, /obj/item/ammo_casing/caseless/arrow/energy/disabler, /obj/item/ammo_casing/caseless/arrow/energy/xray)
+ selectable_types = list(/obj/item/ammo_casing/reusable/arrow/energy, /obj/item/ammo_casing/reusable/arrow/energy/disabler, /obj/item/ammo_casing/reusable/arrow/energy/xray, /obj/item/ammo_casing/reusable/arrow/energy/pulse, /obj/item/ammo_casing/reusable/arrow/energy/shock)
+
+/obj/item/ammo_box/magazine/internal/bow/energy/ert
+ selectable_types = list(/obj/item/ammo_casing/reusable/arrow/energy, /obj/item/ammo_casing/reusable/arrow/energy/disabler, /obj/item/ammo_casing/reusable/arrow/energy/pulse, /obj/item/ammo_casing/reusable/arrow/energy/shock)
/obj/item/ammo_box/magazine/internal/bow/energy/syndicate
- selectable_types = list(/obj/item/ammo_casing/caseless/arrow/energy, /obj/item/ammo_casing/caseless/arrow/energy/xray)
+ selectable_types = list(/obj/item/ammo_casing/reusable/arrow/energy, /obj/item/ammo_casing/reusable/arrow/energy/xray)
/obj/item/ammo_box/magazine/internal/bow/energy/clockcult
- ammo_type = /obj/item/ammo_casing/caseless/arrow/energy/clockbolt
- selectable_types = list(/obj/item/ammo_casing/caseless/arrow/energy/clockbolt)
+ ammo_type = /obj/item/ammo_casing/reusable/arrow/energy/clockbolt
+ selectable_types = list(/obj/item/ammo_casing/reusable/arrow/energy/clockbolt)
/obj/item/ammo_box/magazine/arrow
name = "crossbow magazine"
- ammo_type = /obj/item/ammo_casing/caseless/arrow
+ ammo_type = /obj/item/ammo_casing/reusable/arrow
icon_state = ".50mag"
caliber = "arrow"
max_ammo = 5
diff --git a/code/modules/projectiles/boxes_magazines/internal/toy.dm b/code/modules/projectiles/boxes_magazines/internal/toy.dm
index 7a520c6a1f10..10653bab7062 100644
--- a/code/modules/projectiles/boxes_magazines/internal/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/toy.dm
@@ -1,5 +1,5 @@
/obj/item/ammo_box/magazine/internal/shot/toy
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
caliber = "foam_force"
max_ammo = 4
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 68df9478e3c5..8b0b522902cc 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -231,11 +231,15 @@
/obj/item/gun/ballistic/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
- if(!semi_auto && from_firing)
- return
var/obj/item/ammo_casing/AC = chambered //Find chambered round
+ if(!semi_auto && from_firing)
+ if(istype(AC) && CHECK_BITFIELD(AC.casing_flags, CASINGFLAG_FORCE_CLEAR_CHAMBER))
+ chambered = null
+ return
if(istype(AC)) //there's a chambered round
- if(casing_ejector || !from_firing)
+ if(CHECK_BITFIELD(AC.casing_flags, CASINGFLAG_FORCE_CLEAR_CHAMBER) && from_firing)
+ chambered = null
+ else if(casing_ejector || !from_firing)
AC.forceMove(drop_location()) //Eject casing onto ground.
AC.bounce_away(TRUE)
chambered = null
diff --git a/code/modules/projectiles/guns/ballistic/bow.dm b/code/modules/projectiles/guns/ballistic/bow.dm
index 4cb48d2ebd8d..e6867f9d96da 100644
--- a/code/modules/projectiles/guns/ballistic/bow.dm
+++ b/code/modules/projectiles/guns/ballistic/bow.dm
@@ -1,8 +1,9 @@
/obj/item/gun/ballistic/bow
name = "wooden bow"
- desc = "some sort of primitive projectile weapon. used to fire arrows."
+ desc = "A well-made weapon capable of firing arrows. Mostly outdated, but still dependable."
icon_state = "bow"
item_state = "bow"
+ icon = 'icons/obj/guns/bows.dmi'
w_class = WEIGHT_CLASS_BULKY
weapon_weight = WEAPON_HEAVY //need both hands to fire
force = 5
@@ -17,29 +18,45 @@
no_pin_required = TRUE
trigger_guard = TRIGGER_GUARD_ALLOW_ALL //so ashwalkers can use it
+ // No vertical grip on a bow
+ available_attachments = list(
+ /obj/item/attachment/scope/simple,
+ /obj/item/attachment/scope/holo,
+ /obj/item/attachment/scope/infrared,
+ /obj/item/attachment/laser_sight,
+ )
+
+ // Drawing vars //
var/drawing = FALSE
var/drop_release_draw = TRUE
var/move_drawing = TRUE
var/draw_time = 0.5 SECONDS
- var/draw_slowdown = 1.5
+ var/draw_slowdown = 0.75
var/draw_sound = 'sound/weapons/sound_weapons_bowdraw.ogg'
var/mutable_appearance/arrow_overlay
+ /// If the bow can be equipped when an arrow is loaded
+ var/equip_when_loaded = FALSE
+ /// If the last loaded arrow was a toy arrow or not, used to see if foam darts / arrows should do stamina damage
+ var/nerfed = FALSE
/obj/item/gun/ballistic/bow/shoot_with_empty_chamber()
return
/obj/item/gun/ballistic/bow/chamber_round()
chambered = magazine.get_round(1)
+ update_slowdown()
update_icon()
/obj/item/gun/ballistic/bow/dropped()
. = ..()
- if(drop_release_draw && !QDELING(src))
+ if(!QDELING(src))
addtimer(CALLBACK(src, .proc/release_draw_if_not_held))
/obj/item/gun/ballistic/bow/proc/release_draw_if_not_held()
if(!ismob(loc))
- release_draw()
+ if(drop_release_draw)
+ release_draw()
+ nerfed = initial(nerfed) // So you can't meta if the last arrow loaded by a dropped bow was a toy arrow or not
/obj/item/gun/ballistic/bow/proc/release_draw()
var/old_chambered = chambered
@@ -48,35 +65,73 @@
update_slowdown()
update_icon()
+/obj/item/gun/ballistic/bow/equipped(mob/user, slot)
+ ..()
+ nerfed = initial(nerfed)
+
/obj/item/gun/ballistic/bow/process_chamber()
chambered = null
- magazine.get_round(0)
+ magazine.get_round(FALSE)
update_slowdown()
update_icon()
/obj/item/gun/ballistic/bow/attack_self(mob/living/user)
+ if(drawing)
+ to_chat(user, span_notice("You are already drawing the bowstring!"))
+ return TRUE
if(chambered)
- var/obj/item/ammo_casing/AC = magazine.get_round(0)
- user.put_in_hands(AC)
- chambered = null
- to_chat(user, span_notice("You gently release the bowstring, removing the arrow."))
+ release_draw()
+ to_chat(user, span_notice("You gently release the bowstring."))
+ return TRUE
else if(get_ammo())
drawing = TRUE
update_slowdown()
if (!do_after(user, draw_time, src, TRUE, stayStill = !move_drawing))
drawing = FALSE
update_slowdown()
- return
+ return TRUE
drawing = FALSE
to_chat(user, span_notice("You draw back the bowstring."))
playsound(src, draw_sound, 75, 0, falloff = 3) //gets way too high pitched if the freq varies
chamber_round()
+ return TRUE
+
+/obj/item/gun/ballistic/bow/AltClick(mob/user)
+ if(chambered || get_ammo())
+ var/obj/item/ammo_casing/AC = chambered ? chambered : magazine.get_round(TRUE)
+ AC.attack_self(user)
+ return
+ ..()
+
+/obj/item/gun/ballistic/bow/attack_hand(mob/user)
+ if(internal_magazine && loc == user && user.is_holding(src) && (chambered || get_ammo()))
+ remove_arrow(user)
+ return
+ return ..()
+
+/obj/item/gun/ballistic/bow/proc/remove_arrow(mob/user)
+ if(!chambered && !get_ammo())
+ return
+ var/obj/item/ammo_casing/AC = magazine.get_round(FALSE)
+ chambered = null
+ if(CHECK_BITFIELD(AC.item_flags, DROPDEL))
+ // Shouldn't be put into someone's hand
+ qdel(AC)
+ if(user)
+ to_chat(user, span_notice("You disperse [AC]."))
+ else if(user)
+ user.put_in_hands(AC)
+ to_chat(user, span_notice("You remove [AC]."))
update_slowdown()
update_icon()
/obj/item/gun/ballistic/bow/attackby(obj/item/I, mob/user, params)
- if (magazine.attackby(I, user, params, 1))
- to_chat(user, span_notice("You notch the arrow."))
+ if(istype(I, /obj/item/ammo_casing))
+ if(!user.is_holding(src))
+ to_chat(user, span_notice("You need to hold [src] to load \the [I]."))
+ else if (magazine.attackby(I, user, params, 1))
+ to_chat(user, span_notice("You notch [I]."))
+ nerfed = istype(I, /obj/item/ammo_casing/reusable/arrow/toy)
update_slowdown()
update_icon()
@@ -84,8 +139,8 @@
cut_overlay(arrow_overlay, TRUE)
icon_state = "[initial(icon_state)][chambered ? "_firing" : ""]"
if(get_ammo())
- var/obj/item/ammo_casing/caseless/arrow/energy/E = magazine.get_round(TRUE)
- arrow_overlay = mutable_appearance(icon, "[initial(E.icon_state)][chambered ? "_firing" : ""]")
+ var/obj/item/ammo_casing/reusable/arrow/energy/E = magazine.get_round(TRUE)
+ arrow_overlay = mutable_appearance(icon, "[initial(E.item_state)][chambered ? "_firing" : ""]")
add_overlay(arrow_overlay, TRUE)
/obj/item/gun/ballistic/bow/proc/update_slowdown()
@@ -93,45 +148,62 @@
slowdown = draw_slowdown
else
slowdown = initial(slowdown)
+ if(equip_when_loaded)
+ return
+ if(get_ammo())
+ slot_flags = ITEM_SLOT_DENY_S_STORE // So you can't put a drawn bow in your suit storage slot
+ else
+ slot_flags = initial(slot_flags)
/obj/item/gun/ballistic/bow/can_shoot()
return chambered
/obj/item/gun/ballistic/bow/ashen
- name = "Bone Bow"
- desc = "Some sort of primitive projectile weapon made of bone and wrapped sinew."
+ name = "bone bow"
+ desc = "A primitive bow with a sinew bowstring. Typically used by tribal hunters and warriors."
icon_state = "ashenbow"
item_state = "ashenbow"
+ force = 10
spread = 3
- force = 8
/obj/item/gun/ballistic/bow/pipe
- name = "Pipe Bow"
- desc = "A crude projectile weapon made from silk string, pipe and lots of bending."
+ name = "pipe bow"
+ desc = "A variety of pipes and plastic bent together with a silk bowstring. Cumbersome and inaccurate."
icon_state = "pipebow"
item_state = "pipebow"
- force = 7
+ force = 12
spread = 5
draw_time = 1 SECONDS
/obj/item/gun/ballistic/bow/maint
- name = "Makeshift Bow"
- desc = "A crude projectile weapon made from cables, pipe, tape and lots of bending."
+ name = "makeshift bow"
+ desc = "A crude contraption of rods, tape, and cable; this bow is servicable, but of poor quality."
icon_state = "makeshift_bow"
item_state = "makeshift_bow"
- force = 7
- spread = 10
- draw_time = 2 SECONDS
+ force = 8
+ spread = 7
+ draw_time = 1 SECONDS
/obj/item/gun/ballistic/bow/crossbow
name = "wooden crossbow"
- desc = "A bow with a locking mechanism that more closely resembles a modern gun."
+ desc = "A handcrafted version of a typical medieval crossbow. The stock is heavy and loading it takes time, but it can be quickly fired once ready."
icon_state = "crossbow"
+ item_state = "crossbow"
+ force = 15 //Beating someone with a goddamned stock are we
spread = 0
+ weapon_weight = WEAPON_MEDIUM // You only need one hand to pull the trigger, though good luck reloading it with one hand
draw_time = 2 SECONDS
draw_slowdown = FALSE
drop_release_draw = FALSE
move_drawing = FALSE
+ equip_when_loaded = TRUE
+
+/obj/item/gun/ballistic/bow/crossbow/ashen
+ name = "bone crossbow"
+ desc = "An advanced, primitive bow that is designed to function similar to a crossbow. The stock is heavy and loading it takes time, but it can be quickly fired once ready."
+ icon_state = "ashencrossbow"
+ item_state = "ashencrossbow"
+ spread = 1
/obj/item/gun/ballistic/bow/crossbow/magfed
name = "wooden magfed crossbow"
@@ -139,7 +211,7 @@
mag_type = /obj/item/ammo_box/magazine/arrow
internal_magazine = FALSE
-/obj/item/gun/ballistic/bow/attackby(obj/item/I, mob/user, params)
+/obj/item/gun/ballistic/bow/crossbow/magfed/attackby(obj/item/I, mob/user, params)
if (!internal_magazine && istype(I, /obj/item/ammo_box/magazine))
var/obj/item/ammo_box/magazine/AM = I
if (!magazine)
@@ -152,8 +224,209 @@
return
..()
+
+// Toy //
+
+/obj/item/gun/ballistic/bow/toy
+ name = "toy bow"
+ desc = "A plastic bow that can fire arrows. Features real voice action!"
+ force = 0
+ spread = 10
+ draw_time = 2 SECONDS
+ nerfed = TRUE
+
+ var/obj/item/assembly/assembly = /obj/item/assembly/voice_box/bow
+
+/obj/item/gun/ballistic/bow/toy/Initialize()
+ . = ..()
+ if(ispath(assembly))
+ assembly = new assembly(src)
+
+/obj/item/gun/ballistic/bow/toy/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(!assembly)
+ to_chat(user, span_warning("[src] doesn't have a device inside!"))
+ return TRUE
+ I.play_tool_sound(src)
+ to_chat(user, span_notice("You remove [assembly] from [src]."))
+ user.put_in_hands(assembly)
+ assembly = null
+ return TRUE
+
+/obj/item/gun/ballistic/bow/toy/process_chamber()
+ ..()
+ if(assembly)
+ assembly.pulsed()
+
+/obj/item/gun/ballistic/bow/toy/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/assembly))
+ if(assembly)
+ to_chat(user, span_warning("[src] already has a device inside!"))
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ assembly = I
+ return
+ return ..()
+
+/obj/item/gun/ballistic/bow/toy/white
+ name = "white toy bow"
+ icon_state = "bow_toy_white"
+ item_state = "bow_hardlight_arrow_disable"
+
+/obj/item/gun/ballistic/bow/toy/blue
+ name = "blue toy bow"
+ desc = "A toy bow equipped with a screeching voice box, themed after Nanotrasen."
+ icon_state = "bow_toy_blue"
+ item_state = "bow_ert_arrow_pulse"
+ assembly = /obj/item/assembly/voice_box/bow/nanotrasen
+
+/obj/item/gun/ballistic/bow/toy/red
+ name = "red toy bow"
+ desc = "A red toy boy meant to replicate the hardlight bow used by Syndicate operatives. Comes equipped with a loud voice box."
+ icon_state = "bow_toy_red"
+ item_state = "bow_syndicate_arrow_energy"
+ assembly = /obj/item/assembly/voice_box/bow/syndie
+
+/obj/item/gun/ballistic/bow/toy/clockwork
+ name = "clockwork toy bow"
+ desc = "A plastic, Ratvarian-based toy bow. Sounds a gnarly, obnoxious voice box when fired."
+ icon_state = "bow_toy_clockwork"
+ item_state = "bow_clockwork_arrow_energy"
+ assembly = /obj/item/assembly/voice_box/bow/clockwork
+
+
+// Wizard //
+
+/obj/item/gun/ballistic/bow/break_bow
+ name = "break bow"
+ desc = "A finely-crafted bow consisting of two blades combined at the hilt and a magical, semi-transparent bowstring. Can be taken apart to use the blades individually."
+ icon_state = "breakbow"
+ item_state = "breakbow"
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ force = 40 // You can still hit them with both of the blades (better)
+ throwforce = 40 //Last ditch screaming
+ armour_penetration = 50 //Bro this shit's MAGIC
+ sharpness = SHARP_EDGED
+ attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut")
+ wound_bonus = 10
+ draw_time = 0.25 SECONDS
+ draw_slowdown = 0 //They're a wizard they need to zoom around
+ var/bladetype = /obj/item/break_blade
+
+/obj/item/gun/ballistic/bow/break_bow/Initialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 80 - force, 100, force - 10)
+
+/obj/item/gun/ballistic/bow/break_bow/attack_self(mob/living/user)
+ if(get_ammo())
+ return ..()
+ form_blades(user)
+
+/obj/item/gun/ballistic/bow/break_bow/proc/form_blades(mob/living/user)
+ moveToNullspace()
+ user.put_in_hands(new bladetype())
+ user.put_in_hands(new bladetype())
+ playsound(user, 'sound/weapons/batonextend.ogg', 50, 1)
+ to_chat(user, span_notice("You detach the two blades of [src]."))
+ qdel(src)
+
+/obj/item/break_blade
+ name = "break bow blade"
+ desc = "One of two blades used to form a break bow. Can attack with both blades at the same time or combine them into a bow."
+ icon_state = "brakebow_blade"
+ item_state = "brakebow_blade"
+ icon = 'icons/obj/weapons/swords.dmi'
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 10, "embedded_fall_chance" = 10, "embedded_ignore_throwspeed_threshold" = TRUE)
+ force = 27 //Total of 54 damage = death in two clicks (probably) PLUS it doesn't care about anti-magic
+ throwforce = 45 //Can't return if it hits anti-magic
+ armour_penetration = 50 //Enchanted blade of fuck you
+ sharpness = SHARP_EDGED
+ attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut")
+ wound_bonus = 10
+ var/bowtype = /obj/item/gun/ballistic/bow/break_bow
+ var/returning = FALSE
+
+/obj/item/break_blade/Initialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 80 - force, 100, force - 10)
+
+/obj/item/break_blade/attack_self(mob/living/user)
+ var/obj/item/break_blade/secondblade = user.get_inactive_held_item()
+ if(istype(secondblade))
+ form_bow(user, secondblade)
+ else
+ to_chat(user, span_warning("You need two of [src] to combine them!"))
+
+/obj/item/break_blade/proc/form_bow(mob/living/user, var/obj/item/break_blade/other_blade)
+ if(!istype(other_blade))
+ return
+ moveToNullspace()
+ other_blade.moveToNullspace()
+ user.put_in_hands(new bowtype())
+ playsound(user, 'sound/weapons/batonextend.ogg', 50, 1)
+ to_chat(user, span_notice("You combine the two [src]."))
+ qdel(other_blade)
+ qdel(src)
+
+/obj/item/break_blade/pre_attack(atom/A, mob/living/user, params)
+ if(istype(A, /obj/item/break_blade))
+ form_bow(user, A)
+ return FALSE
+ . = ..()
+
+/obj/item/break_blade/attack(mob/living/M, mob/living/user, secondattack = FALSE)
+ . = ..()
+ var/obj/item/break_blade/secondblade = user.get_inactive_held_item()
+ if(istype(secondblade) && !secondattack)
+ sleep(0.2 SECONDS)
+ secondblade.attack(M, user, TRUE)
+
+/obj/item/break_blade/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback, force, quickstart)
+ . = ..()
+ if(!thrower)
+ return
+ if(!returning)
+ addtimer(CALLBACK(src, .proc/return_to, thrower), 3 SECONDS)
+ returning = TRUE
+ var/obj/item/break_blade/secondblade = thrower.get_inactive_held_item()
+ if(istype(secondblade))
+ sleep(0.2 SECONDS)
+ thrower.dropItemToGround(secondblade, silent = TRUE)
+ secondblade.throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, quickstart)
+
+/obj/item/break_blade/proc/return_to(mob/living/user)
+ if(!istype(user))
+ return
+
+ var/mob/holder = loc
+ if(istype(holder) && holder.anti_magic_check(TRUE, FALSE, FALSE, 0))
+ to_chat(holder, span_notice("You feel [src] tugging on you."))
+ return
+
+ var/mob/living/carbon/carbon = loc
+ if(istype(carbon))
+ var/obj/item/bodypart/part = carbon.get_embedded_part(src)
+ if(part)
+ if(!carbon.remove_embedded_object(src, unsafe = TRUE))
+ to_chat(carbon, span_notice("You feel [src] tugging on you."))
+ return
+ to_chat(carbon, span_userdanger("[src] suddenly rips out of you!"))
+
+ if(!user.put_in_hands(src))
+ return
+ playsound(user, 'sound/magic/blink.ogg', 50, 1)
+ returning = FALSE
+ to_chat(user, span_notice("[src] suddenly returns to you!"))
+
+
+// Hardlight //
+
/obj/item/gun/ballistic/bow/energy
- name = "Hardlight Bow"
+ name = "hardlight bow"
desc = "A modern bow that can fabricate hardlight arrows using an internal energy."
icon_state = "bow_hardlight"
item_state = "bow_hardlight"
@@ -162,14 +435,52 @@
draw_slowdown = 0
var/recharge_time = 1 SECONDS
+ var/can_fold = FALSE
+ var/folded_w_class = WEIGHT_CLASS_NORMAL
+ var/folded = FALSE
+ //var/stored_ammo ///what was stored in the magazine before being folded?
+ var/fold_sound = 'sound/weapons/batonextend.ogg'
+
+/obj/item/gun/ballistic/bow/energy/Initialize()
+ if(folded)
+ toggle_folded(TRUE)
+ . = ..()
+
+/obj/item/gun/ballistic/bow/energy/examine(mob/user)
+ . = ..()
+ var/obj/item/ammo_box/magazine/internal/bow/energy/M = magazine
+ if(magazine.ammo_type)
+ var/obj/item/arrow_type = magazine.ammo_type
+ . += "It is current firing mode is \"[initial(arrow_type.name)]\"[M.selectable_types.len > 1 ? ", you can select firing modes by using ALT + CLICK" : ""]."
+ if(can_fold)
+ . += "[folded ? "It is currently folded, you can unfold it" : "It can be folded into a compact form"] by using CTRL + CLICK."
+ if(TIMER_COOLDOWN_CHECK(src, "arrow_recharge"))
+ . += span_warning("It is currently recharging!")
+
/obj/item/gun/ballistic/bow/energy/update_icon()
cut_overlay(arrow_overlay, TRUE)
- if(get_ammo())
- var/obj/item/ammo_casing/caseless/arrow/energy/E = magazine.get_round(TRUE)
- arrow_overlay = mutable_appearance(icon, "[initial(E.icon_state)][chambered ? "_firing" : ""]")
- add_overlay(arrow_overlay, TRUE)
+
+ if(folded)
+ icon_state = "[initial(icon_state)]_folded"
+ item_state = "[initial(item_state)]_folded"
+ else
+ icon_state = initial(icon_state)
+ item_state = initial(item_state)
+
+ if(get_ammo())
+ var/obj/item/ammo_casing/reusable/arrow/energy/E = magazine.get_round(TRUE)
+ arrow_overlay = mutable_appearance(icon, "[initial(E.icon_state)][chambered ? "_firing" : ""]")
+ add_overlay(arrow_overlay, TRUE)
+ item_state = "[item_state]_[E.icon_state]"
+
+ if(ismob(loc))
+ var/mob/M = loc
+ M.update_inv_hands()
/obj/item/gun/ballistic/bow/energy/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message)
+ if(folded)
+ to_chat(user, span_notice("You must unfold [src] before firing it!"))
+ return FALSE
. = ..()
if(recharge_time)
TIMER_COOLDOWN_START(src, "arrow_recharge", recharge_time)
@@ -179,32 +490,22 @@
playsound(src, 'sound/effects/sparks4.ogg', 25, 0)
/obj/item/gun/ballistic/bow/energy/attack_self(mob/living/user)
- if(chambered)
- chambered = null
- to_chat(user, span_notice("You disperse the arrow."))
- else if(get_ammo())
- drawing = TRUE
- update_slowdown()
- if (!do_after(user, draw_time, src, TRUE, stayStill = FALSE))
- drawing = FALSE
- update_slowdown()
- return
- drawing = FALSE
- to_chat(user, span_notice("You draw back the bowstring."))
- playsound(src, draw_sound, 75, 0, falloff = 3) //gets way too high pitched if the freq varies
- chamber_round()
- else if(!recharge_time || !TIMER_COOLDOWN_CHECK(src, "arrow_recharge"))
+ if(folded)
+ toggle_folded(FALSE, user)
+ if(..())
+ return TRUE
+ if(!chambered && !get_ammo() && (!recharge_time || !TIMER_COOLDOWN_CHECK(src, "arrow_recharge")))
to_chat(user, span_notice("You fabricate an arrow."))
- recharge_bolt()
+ recharge_arrow()
update_slowdown()
update_icon()
-/obj/item/gun/ballistic/bow/energy/proc/recharge_bolt()
- if(magazine.get_round(TRUE))
+/obj/item/gun/ballistic/bow/energy/proc/recharge_arrow()
+ if(folded || magazine.get_round(TRUE))
return
var/ammo_type = magazine.ammo_type
magazine.give_round(new ammo_type())
-
+ update_slowdown()
update_icon()
/obj/item/gun/ballistic/bow/energy/attackby(obj/item/I, mob/user, params)
@@ -216,7 +517,7 @@
if(current_round)
QDEL_NULL(current_round)
if(!TIMER_COOLDOWN_CHECK(src, "arrow_recharge"))
- recharge_bolt()
+ recharge_arrow()
update_icon()
/obj/item/gun/ballistic/bow/energy/proc/select_projectile(mob/living/user)
@@ -224,43 +525,87 @@
if(!istype(M) || !M.selectable_types)
return
var/list/selectable_types = M.selectable_types
+
+ switch(selectable_types.len)
+ if(1)
+ M.ammo_type = selectable_types[1]
+ to_chat(user, span_notice("\The [src] doesn't have any other firing modes."))
+ if(2)
+ selectable_types = selectable_types - M.ammo_type
+ var/obj/item/ammo_casing/reusable/arrow/energy/new_ammo_type = selectable_types[1]
+ M.ammo_type = new_ammo_type
+ to_chat(user, span_notice("You switch \the [src]'s firing mode to \"[initial(new_ammo_type.name)]\"."))
+ else
+ var/list/choice_list = list()
+ var/list/radial_list = list()
+ for(var/type in M.selectable_types)
+ var/obj/item/arrow_type = type
+ var/datum/radial_menu_choice/choice = new
+ choice.image = image(initial(arrow_type.icon), icon_state = initial(arrow_type.icon_state))
+ choice.info = initial(arrow_type.desc)
+ choice.active = M.ammo_type == type
+ choice_list[initial(arrow_type.name)] = arrow_type
+ radial_list[initial(arrow_type.name)] = choice
+ var/raw_choice = show_radial_menu(user, user, radial_list, tooltips = TRUE)
+ if(!raw_choice || !(raw_choice in radial_list))
+ return
+ var/obj/item/ammo_casing/reusable/arrow/energy/choice = choice_list[raw_choice]
+ if(!choice || !(choice in M.selectable_types))
+ return
+ M.ammo_type = choice
+ to_chat(user, span_notice("You switch \the [src]'s firing mode to \"[initial(choice.name)]\"."))
+ QDEL_NULL(choice_list)
+ QDEL_NULL(radial_list)
+ update_icon()
- if(selectable_types.len == 1)
- M.ammo_type = selectable_types[1]
- to_chat(user, span_notice("\The [src] doesn't have any other firing modes."))
- update_icon()
+/obj/item/gun/ballistic/bow/energy/CtrlClick(mob/living/user)
+ if(!can_fold || !user.is_holding(src))
+ return ..()
+ if(drawing)
+ to_chat(user, span_notice("You can't fold \the [src] while drawing the bowstring."))
+ toggle_folded(!folded, user)
+
+/obj/item/gun/ballistic/bow/energy/proc/toggle_folded(new_folded, mob/living/user)
+ if(!can_fold)
return
- if(selectable_types.len == 2)
- selectable_types = selectable_types - M.ammo_type
- M.ammo_type = selectable_types[1]
- to_chat(user, span_notice("You switch \the [src]'s firing mode."))
- update_icon()
- return
+ if(folded != new_folded)
+ playsound(src.loc, fold_sound, 50, 1)
- var/list/choice_list = list()
- for(var/arrow_type in M.selectable_types)
- var/obj/item/ammo_casing/caseless/arrow/energy/arrow = new arrow_type()
- choice_list[arrow] = image(arrow)
- var/obj/item/ammo_casing/caseless/arrow/energy/choice = show_radial_menu(user, user, choice_list, tooltips = TRUE)
- if(!choice || !(choice.type in M.selectable_types))
- return
- M.ammo_type = choice.type
- to_chat(user, span_notice("You switch \the [src]'s firing mode to \"[choice]\"."))
- for(var/arrow in choice_list)
- QDEL_NULL(choice_list[arrow])
- QDEL_NULL(arrow)
- QDEL_NULL(choice_list)
+ folded = new_folded
+
+ if(folded)
+ w_class = folded_w_class
+ chambered = null
+ //stored_ammo = magazine.ammo_list()
+ //magazine.stored_ammo = null
+ if(user)
+ to_chat(user, span_notice("You fold [src]."))
+ else
+ w_class = initial(w_class)
+ //magazine.stored_ammo = stored_ammo
+ if(user)
+ to_chat(user, span_notice("You extend [src], allowing it to be fired."))
update_icon()
/obj/item/gun/ballistic/bow/energy/advanced
- name = "Advanced Hardlight Bow"
+ name = "advanced hardlight bow"
mag_type = /obj/item/ammo_box/magazine/internal/bow/energy/advanced
recharge_time = 0
pin = /obj/item/firing_pin
+ can_fold = TRUE
+
+/obj/item/gun/ballistic/bow/energy/ert
+ name = "\improper HL-P1 Multipurpose Combat Bow"
+ desc = "An expensive hardlight bow designed by Nanotrasen and often sold to the SIC's espionage branch. Capable of firing disabler, energy, pulse, and taser bolts."
+ icon_state = "bow_ert"
+ item_state = "bow_ert"
+ mag_type = /obj/item/ammo_box/magazine/internal/bow/energy/ert
+ pin = /obj/item/firing_pin
+ can_fold = TRUE
/obj/item/gun/ballistic/bow/energy/syndicate
- name = "Syndicate Hardlight Bow"
+ name = "syndicate hardlight bow"
desc = "A modern bow that can fabricate hardlight arrows using an internal energy. This one is designed by the Syndicate for silent takedowns of targets."
icon_state = "bow_syndicate"
item_state = "bow_syndicate"
@@ -271,57 +616,13 @@
pin = /obj/item/firing_pin
fire_sound = null
draw_sound = null
- var/folded = FALSE
- var/stored_ammo ///what was stored in the magazine before being folded?
+ can_fold = TRUE
-/obj/item/gun/ballistic/bow/energy/syndicate/examine(mob/user)
- . = ..()
- . += "It can be folded into a compact form by using CTRL + CLICK."
-
-/obj/item/gun/ballistic/bow/energy/syndicate/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message)
- if(!folded)
- return ..()
- else
- to_chat(user, span_notice("You must unfold [src] before firing it!"))
- return FALSE
-
-/obj/item/gun/ballistic/bow/energy/syndicate/attack_self(mob/living/user)
- if(!folded)
- return ..()
- else
- to_chat(user, span_notice("You must unfold [src] to chamber a round!"))
- return FALSE
-
-/obj/item/gun/ballistic/bow/energy/syndicate/AltClick(mob/living/user)
- if(!folded)
- return ..()
- else
- to_chat(user, span_notice("You must unfold [src] to switch firing modes!"))
- return FALSE
-
-/obj/item/gun/ballistic/bow/energy/syndicate/CtrlClick(mob/living/user)
- if(!user.is_holding(src))
- to_chat(user, span_notice("You need be holding [src] to do that!"))
- return
- folded = !folded
- playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1)
- if(folded)
- to_chat(user, span_notice("You fold [src]."))
- w_class = WEIGHT_CLASS_NORMAL
- chambered = null
- icon_state = "bow_syndicate_folded"
- stored_ammo = magazine.ammo_list()
- magazine.stored_ammo = null
- update_icon()
- else
- w_class = WEIGHT_CLASS_BULKY
- to_chat(user, span_notice("You extend [src], allowing it to be fired."))
- icon_state = "bow_syndicate"
- magazine.stored_ammo = stored_ammo
- update_icon()
+/obj/item/gun/ballistic/bow/energy/syndicate/folded
+ folded = TRUE
/obj/item/gun/ballistic/bow/energy/clockwork
- name = "Brass Bow"
+ name = "brass bow"
desc = "A bow made from brass and other components that you can't quite understand. It glows with a deep energy and fabricates arrows by itself."
icon_state = "bow_clockwork"
item_state = "bow_clockwork"
diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm
index bbf909452b7c..0de7b9610b37 100644
--- a/code/modules/projectiles/guns/ballistic/toy.dm
+++ b/code/modules/projectiles/guns/ballistic/toy.dm
@@ -60,11 +60,6 @@
. = ..()
add_overlay("[icon_state]_toy")
-/obj/item/gun/ballistic/shotgun/toy/process_chamber(empty_chamber = 0)
- ..()
- if(chambered && !chambered.BB)
- qdel(chambered)
-
/obj/item/gun/ballistic/shotgun/toy/unrestricted
pin = /obj/item/firing_pin
@@ -73,7 +68,7 @@
desc = "A weapon favored by many overactive children. Ages 8 and up."
icon = 'icons/obj/toy.dmi'
icon_state = "foamcrossbow"
- item_state = "crossbow"
+ item_state = "ecrossbow"
mag_type = /obj/item/ammo_box/magazine/internal/shot/toy/crossbow
fire_sound = 'sound/items/syringeproj.ogg'
slot_flags = ITEM_SLOT_BELT
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index e9a11b716ffe..0aee14e52133 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -89,7 +89,7 @@
name = "mini energy crossbow"
desc = "A weapon favored by syndicate stealth specialists. Each bolt injects some poison into the victim."
icon_state = "crossbow"
- item_state = "crossbow"
+ item_state = "ecrossbow"
w_class = WEIGHT_CLASS_SMALL
materials = list(/datum/material/iron=2000)
suppressed = TRUE
@@ -106,7 +106,7 @@
name = "candy corn crossbow"
desc = "A weapon favored by Syndicate trick-or-treaters."
icon_state = "crossbow_halloween"
- item_state = "crossbow"
+ item_state = "ecrossbow"
ammo_type = list(/obj/item/ammo_casing/energy/bolt/halloween)
/obj/item/gun/energy/plasmacutter
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 7b28397a4f69..81b68011c720 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -76,6 +76,7 @@
//Homing
var/homing = FALSE
+ var/homing_away = FALSE // In case you want it to instead turn away from the target, useful for when the projectile is going haywire!
var/atom/homing_target
var/homing_turn_speed = 10 //Angle per tick.
var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target.
@@ -561,7 +562,7 @@
PT.x += clamp(homing_offset_x, 1, world.maxx)
PT.y += clamp(homing_offset_y, 1, world.maxy)
var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT))
- setAngle(Angle + clamp(angle, -homing_turn_speed, homing_turn_speed))
+ setAngle(Angle + clamp(homing_away ? -angle : angle, -homing_turn_speed, homing_turn_speed))
/obj/item/projectile/proc/set_homing_target(atom/A)
if(!A || (!isturf(A) && !isturf(A.loc)))
diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
index 6fc144f65b98..240198d81000 100644
--- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm
+++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
@@ -2,51 +2,35 @@
name = "dart"
icon_state = "cbbolt"
damage = 6
- var/obj/item/reagent_containers/container
var/piercing = FALSE
-/obj/item/projectile/bullet/reusable/dart/hidden
- name = "beanbag slug"
- stamina = 5 // gotta act like we did stamina
- sharpness = SHARP_NONE
-
-// don't want our "beanbag slugs" dropping reagent darts everywhere
-/obj/item/projectile/bullet/reusable/dart/hidden/handle_drop()
- if(!dropped)
- QDEL_NULL(container)
- dropped = TRUE
-
/obj/item/projectile/bullet/reusable/dart/Initialize()
. = ..()
-/obj/item/projectile/bullet/reusable/dart/on_hit(atom/target, blocked = FALSE)
- if(iscarbon(target) && (blocked < 100))
- var/mob/living/carbon/C = target
-
- if(C.can_inject(null, FALSE, def_zone, piercing) && C.embed_object(container, def_zone, FALSE))
- dropped = TRUE
- ..()
- return BULLET_ACT_HIT
- else
- target.visible_message(span_danger("\The [container] was deflected!"), \
- span_userdanger("You were protected against \the [container]!"))
- if(blocked >= 100)
- target.visible_message(span_danger("\The [container] was deflected!"), \
- span_userdanger("You were protected against \the [container]!"))
- ..(target, blocked)
- return BULLET_ACT_HIT
-
-/obj/item/projectile/bullet/reusable/dart/handle_drop()
- if(!dropped)
- container.forceMove(get_turf(src))
- dropped = TRUE
-
/obj/item/projectile/bullet/reusable/dart/proc/add_dart(obj/item/reagent_containers/new_dart, syrpierce)
piercing = syrpierce
- container = new_dart
+ ammo_type = new_dart
new_dart.forceMove(src)
name = new_dart.name
+/obj/item/projectile/bullet/reusable/dart/handle_drop(mob/living/carbon/target, blocked)
+ if(dropped || !isitem(ammo_type) || !iscarbon(target))
+ return ..()
+
+ if(blocked >= 100 || !target.can_inject(null, FALSE, def_zone, piercing) || !target.embed_object(ammo_type, def_zone, FALSE)) // The bulk of the code to actualy embbed the dart is here, its all stacked up so we don't have to copy the fail text multiple times
+ target.visible_message(span_danger("\The [ammo_type] was deflected!"), \
+ span_userdanger("You were protected against \the [ammo_type]!"))
+ else
+ dropped = TRUE // If we got here, the dart should already be embedded so we just need to mark it as dropped to prevent further handle_drop stuff from messing it up
+
+ return ..() // Run further handle_drop stuff, for if the syringe doesn't embbed in the target
+
/obj/item/projectile/bullet/reusable/dart/syringe
name = "syringe"
icon_state = "syringeproj"
+
+/obj/item/projectile/bullet/reusable/dart/hidden
+ name = "beanbag slug"
+ icon_state = "bullet" //So it doesn't look like a goddamned syringe
+ stamina = 5 // gotta act like we did stamina
+ sharpness = SHARP_NONE
diff --git a/code/modules/projectiles/projectile/reusable/_reusable.dm b/code/modules/projectiles/projectile/reusable/_reusable.dm
index e6a6e0137ae5..d69fca282dcd 100644
--- a/code/modules/projectiles/projectile/reusable/_reusable.dm
+++ b/code/modules/projectiles/projectile/reusable/_reusable.dm
@@ -1,20 +1,34 @@
/obj/item/projectile/bullet/reusable
name = "reusable bullet"
desc = "How do you even reuse a bullet?"
- var/ammo_type = /obj/item/ammo_casing/caseless
+ var/obj/item/ammo_casing/ammo_type
var/dropped = FALSE
impact_effect_type = null
/obj/item/projectile/bullet/reusable/on_hit(atom/target, blocked = FALSE)
. = ..()
- handle_drop(target)
+ handle_drop(target, blocked)
/obj/item/projectile/bullet/reusable/on_range()
handle_drop()
..()
-/obj/item/projectile/bullet/reusable/proc/handle_drop(atom/target)
- if(!dropped)
- var/turf/T = get_turf(src)
- new ammo_type(T)
+/obj/item/projectile/bullet/reusable/proc/handle_drop(atom/target, blocked)
+ if(dropped || !ammo_type)
+ return
+
+ var/turf/T = get_turf(src)
+ var/obj/item/thing_to_drop = ispath(ammo_type) ? new ammo_type(src) : ammo_type
+
+ if(CHECK_BITFIELD(thing_to_drop.item_flags, DROPDEL)) // Delete it if it has the dropdel flag
+ qdel(thing_to_drop)
dropped = TRUE
+ return
+
+ thing_to_drop.forceMove(T)
+
+ if(istype(ammo_type, /obj/item/ammo_casing/reusable))
+ var/obj/item/ammo_casing/reusable/reusable_to_drop = thing_to_drop
+ reusable_to_drop.on_land(src)
+
+ dropped = TRUE
diff --git a/code/modules/projectiles/projectile/reusable/arrow.dm b/code/modules/projectiles/projectile/reusable/arrow.dm
index d803c84635bd..5e6a290139d7 100644
--- a/code/modules/projectiles/projectile/reusable/arrow.dm
+++ b/code/modules/projectiles/projectile/reusable/arrow.dm
@@ -1,148 +1,217 @@
/obj/item/projectile/bullet/reusable/arrow //Base arrow. Good against fauna, not perfect, but well-rounded.
- name = "Arrow"
+ name = "arrow"
desc = "Woosh!"
- damage = 20
+ damage = 35
+ armour_penetration = -25 //Melee armor tends to be much higher, so this hurts
speed = 0.6
flag = MELEE
icon_state = "arrow"
- ammo_type = /obj/item/ammo_casing/caseless/arrow
var/embed_chance = 0.4
- var/break_chance = 10
- var/fauna_damage_bonus = 20
+ var/break_chance = 0
+ var/fauna_damage_bonus = 10
/obj/item/projectile/bullet/reusable/arrow/on_hit(atom/target, blocked = FALSE)
- . = ..()
- if(isliving(target))
- var/mob/living/L = target
- if(ismegafauna(L) || istype(L, /mob/living/simple_animal/hostile/asteroid))
- L.apply_damage(fauna_damage_bonus)
+ ..()
+ if(!isliving(target) || (blocked == 100))
+ return
+
+ var/mob/living/L = target
+ if(ismegafauna(L) || istype(L, /mob/living/simple_animal/hostile/asteroid))
+ L.apply_damage(fauna_damage_bonus)
+
+ if(!istype(ammo_type, /obj/item/ammo_casing/reusable/arrow))
+ return
+
+ var/obj/item/ammo_casing/reusable/arrow/arrow = ammo_type
+ if(istype(arrow.bola))
+ if(iscarbon(target))
+ arrow.bola.impactCarbon(target)
+ if(isanimal(target))
+ arrow.bola.impactAnimal(target)
+ if(!istype(arrow.bola) || arrow.bola.loc != src)
+ LAZYREMOVE(arrow.attached_parts, arrow.bola)
+ arrow.bola = null
+
+ if(arrow.flaming)
+ L.adjust_fire_stacks(1)
+ L.IgniteMob()
+ arrow.flaming = FALSE
+
+ arrow.update_icon()
/obj/item/projectile/bullet/reusable/arrow/handle_drop(atom/target)
+ if(dropped || !ammo_type)
+ return ..()
+
if(prob(break_chance))
- return
- var/obj/item/dropping = new ammo_type()
+ if(istype(ammo_type))
+ visible_message(span_danger("\The [ammo_type] breaks on impact!"))
+ qdel(ammo_type)
+ dropped = TRUE
+ return ..()
+
if(iscarbon(target))
+ ammo_type = ispath(ammo_type) ? new ammo_type(ammo_type) : ammo_type
var/mob/living/carbon/embede = target
var/obj/item/bodypart/part = embede.get_bodypart(def_zone)
- if(prob(embed_chance * clamp((100 - (embede.getarmor(part, flag) - armour_penetration)), 0, 100)) && embede.embed_object(dropping, part, TRUE))
+ var/obj/item/ammo_casing/reusable/arrow/arrow = ammo_type
+ if(!(istype(arrow) && arrow.explosive) && prob(embed_chance * clamp((100 - (embede.getarmor(part, flag) - armour_penetration)), 0, 100)) && embede.embed_object(ammo_type, part, TRUE))
+ arrow.on_land(src)
dropped = TRUE
-
- // Icky code, but i dont want to create a new obj, delete it, then make a new one
- if(!dropped)
- dropping.forceMove(get_turf(src))
- dropped = TRUE
+ return ..()
+
+
+// Arrow Subtypes //
/obj/item/projectile/bullet/reusable/arrow/wood
- name = "Wooden arrow"
- desc = "Wooden arrow."
- ammo_type = /obj/item/ammo_casing/caseless/arrow/wood
+ name = "wooden arrow"
+ desc = "A wooden arrow, quickly made."
-/obj/item/projectile/bullet/reusable/arrow/ash //Fire-tempered head makes it tougher; more damage, but less likely to shatter and embed
- name = "Ashen arrow"
- desc = "Fire Hardened arrow."
+/obj/item/projectile/bullet/reusable/arrow/ash //Fire-tempered head makes it tougher; more damage, but less likely to embed
+ name = "ashen arrow"
+ desc = "A wooden arrow tempered by fire. It's tougher, but less likely to embed."
+ damage = 40
+ embed_chance = 0.3
+
+/obj/item/projectile/bullet/reusable/arrow/bone_tipped //A fully upgraded normal arrow; it's got the stats to show. Still less damage than a slug, resolving against melee, fired less often, slower, and with negative AP
+ name = "bone-tipped arrow"
+ desc = "An arrow made from bone, wood, and sinew. Sturdy and sharp."
+ damage = 45
+ armour_penetration = -10
+
+/obj/item/projectile/bullet/reusable/arrow/bone //Cheap, easy to make in bulk but mostly used for hunting fauna
+ name = "bone arrow"
+ desc = "An arrow made from bone and sinew. Better at hunting fauna."
damage = 25
- embed_chance = 0.25
- break_chance = 0
- ammo_type = /obj/item/ammo_casing/caseless/arrow/ash
-
-/obj/item/projectile/bullet/reusable/arrow/bone_tipped //Highest damage; fully upgraded normal arrow, simply well-crafted
- name = "Bone tipped arrow"
- desc = "An arrow made from bone, wood, and sinew."
- damage = 30
- armour_penetration = 20
+ armour_penetration = -10 //So it's not as terrible against miners; still bad
+ fauna_damage_bonus = 35 //Significantly better for hunting fauna, but you don't get to instantly recharge your shots
embed_chance = 0.33
- break_chance = 0
- ammo_type = /obj/item/ammo_casing/caseless/arrow/bone_tipped
-/obj/item/projectile/bullet/reusable/arrow/bone //Cheap, easy to make in bulk but mostly capable of being used to hunt fauna
- name = "Bone arrow"
- desc = "An arrow made from bone and sinew."
- damage = 15
- fauna_damage_bonus = 35
- embed_chance = 0.33
- break_chance = 10
- ammo_type = /obj/item/ammo_casing/caseless/arrow/bone
-
-/obj/item/projectile/bullet/reusable/arrow/chitin //Most expensive arrow, but powerful for those who put in the time. Greater AP alternative to bone-tipped. Very robust against fauna.
- name = "Chitin tipped arrow"
- desc = "An arrow made from chitin, bone, and sinew."
- damage = 25
- fauna_damage_bonus = 40
- armour_penetration = 35
- embed_chance = 0.4
- break_chance = 0
- ammo_type = /obj/item/ammo_casing/caseless/arrow/chitin
+/obj/item/projectile/bullet/reusable/arrow/chitin //Most expensive arrow time and resource-wise, simply because of ash resin. Should be good
+ name = "chitin-tipped arrow"
+ desc = "An arrow made from chitin, bone, and sinew. Incredibly potent at puncturing armor and hunting fauna."
+ damage = 35
+ armour_penetration = 30 //Basically an AP arrow
+ fauna_damage_bonus = 40 //Even better, since they're that much harder to make
/obj/item/projectile/bullet/reusable/arrow/bamboo //Very brittle, very fragile, but very potent at splintering into targets assuming it isn't broken on impact
- name = "Bamboo arrow"
- desc = "An arrow made from bamboo."
- damage = 10
- embed_chance = 0.5
- break_chance = 50
- ammo_type = /obj/item/ammo_casing/caseless/arrow/bamboo
+ name = "bamboo arrow"
+ desc = "An arrow made from bamboo. Incredibly fragile and weak, but prone to shattering in unarmored targets."
+ damage = 20
+ armour_penetration = -40
+ embed_chance = 0.6 //Reminder that this resolves against melee armor
+ break_chance = 33 //Doesn't embed if it breaks
-/obj/item/projectile/bullet/reusable/arrow/bronze //Inferior metal. Slightly better than ashen
- name = "Bronze arrow"
- desc = "Bronze tipped arrow"
+/obj/item/projectile/bullet/reusable/arrow/bronze //Bronze > iron, that's why they called it the bronze age
+ name = "bronze arrow"
+ desc = "An arrow tipped with bronze. Better against armor than iron."
+ armour_penetration = -10
+
+/obj/item/projectile/bullet/reusable/arrow/glass //Basically just a downgrade for people who can't get their hands on wood/cloth
+ name = "glass arrow"
+ desc = "A shoddy arrow with a broken glass shard as its tip. Can break upon impact."
damage = 25
- armour_penetration = 10
embed_chance = 0.3
break_chance = 10
- ammo_type = /obj/item/ammo_casing/caseless/arrow/bronze
-/obj/item/projectile/bullet/reusable/arrow/glass //Fragile, but sharp and light. Not as effective as metal but cheaper.
- name = "Glass arrow"
- desc = "Glass tipped arrow"
- damage = 15
- embed_chance = 0.3
- break_chance = 25
- ammo_type = /obj/item/ammo_casing/caseless/arrow/glass
+/obj/item/projectile/bullet/reusable/arrow/glass/plasma //It's HARD to get plasmaglass shards without an axe, so this should be GOOD
+ name = "plasmaglass arrow"
+ desc = "An arrow with a plasmaglass shard affixed to its head. Incredibly capable of puncturing armor."
+ damage = 25
+ armour_penetration = 45 //18.75 damage against elite hardsuit assuming chest shot (and that's a long reload, draw, projectile speed, etc.)
-/obj/item/projectile/bullet/reusable/arrow/glass/plasma //Immensely capable of puncturing through materials; plasma is a robust material, more capable of slicing through protection
- name = "Plasma Glass arrow"
- desc = "Plasma Glass tipped arrow"
- damage = 18
- armour_penetration = 60
- embed_chance = 0.4
+/obj/item/projectile/bullet/reusable/arrow/magic
+ name = "magic arrow"
+ desc = "A magic arrow thats probably tracking you, how nice!"
+ icon_state = "arrow_magic"
+ damage = 40
+ embed_chance = 0.6
+ armour_penetration = 0
+
+// Toy //
+
+/obj/item/projectile/bullet/reusable/arrow/toy //Toy arrow with velcro tip that safely embeds into target
+ name = "toy arrow"
+ damage = 0
+ embed_chance = 0.9
break_chance = 0
- ammo_type = /obj/item/ammo_casing/caseless/arrow/glass/plasma
-/obj/item/projectile/bullet/reusable/arrow/bola //More of a blunt impact of the bola, still an arrow. Only able to be crafted with makeshift weaponry
- name = "Bola arrow"
- desc = "An arrow with a bola wrapped around it"
- var/obj/item/restraints/legcuffs/bola/bola
-
-/obj/item/projectile/bullet/reusable/arrow/bola/on_hit(atom/target, blocked = FALSE)
+/obj/item/projectile/bullet/reusable/arrow/toy/on_hit(atom/target, blocked)
. = ..()
- if(iscarbon(target))
- return bola.impactCarbon(target)
- if(isanimal(target))
- return bola.impactAnimal(target)
-/*
-/obj/item/projectile/bullet/reusable/arrow/explosive
- name = "Explosive arrow"
- desc = "An arrow with an explosive attached to it"
- damage = 20
- armour_penetration = 10
- var/obj/item/grenade/explosive = null
-/obj/item/projectile/bullet/reusable/arrow/explosive/prehit(atom/target)
+ if(!iscarbon(target))
+ return
+
+ var/nerfed = FALSE
+ var/mob/living/carbon/C = target
+ for(var/obj/item/gun/ballistic/T in C.held_items) // Is usually just ~2 items
+ if(ispath(T.mag_type, /obj/item/ammo_box/magazine/toy) || ispath(T.mag_type, /obj/item/ammo_box/magazine/internal/shot/toy)) // All automatic foam force guns || Foam force shotguns & crossbows
+ nerfed = TRUE
+ break
+ if(istype(T, /obj/item/gun/ballistic/bow)) // Bows have their own handling
+ var/obj/item/gun/ballistic/bow/bow = T
+ if(bow.nerfed)
+ nerfed = TRUE
+ break
+
+ if(!nerfed)
+ return
+
+ C.adjustStaminaLoss(25) // ARMOR IS CHEATING!!!
+
+/obj/item/projectile/bullet/reusable/arrow/toy/energy
+ name = "toy energy bolt"
+ icon_state = "arrow_energy"
+
+/obj/item/projectile/bullet/reusable/arrow/toy/disabler
+ name = "toy disabler bolt"
+ icon_state = "arrow_disable"
+
+/obj/item/projectile/bullet/reusable/arrow/toy/pulse
+ name = "toy pulse bolt"
+ icon_state = "arrow_pulse"
+
+/obj/item/projectile/bullet/reusable/arrow/toy/xray
+ name = "toy X-ray bolt"
+ icon_state = "arrow_xray"
+
+/obj/item/projectile/bullet/reusable/arrow/toy/shock
+ name = "toy shock bolt"
+ icon_state = "arrow_shock"
+
+/obj/item/projectile/bullet/reusable/arrow/toy/magic
+ name = "toy magic arrow"
+ icon_state = "arrow_magic"
+
+
+// Joke //
+
+/obj/item/projectile/bullet/reusable/arrow/supermatter
+ name = "supermatter arrow"
+
+/obj/item/projectile/bullet/reusable/arrow/supermatter/on_hit(atom/target, blocked = FALSE)
. = ..()
- explosive.forceMove(target)
- explosive.prime()
- visible_message("[explosive]")
-*/
-/obj/item/projectile/bullet/reusable/arrow/flaming //Normal arrow, but it also sets people on fire. Simple
- name = "Flaming arrow"
- desc = "A burning arrow"
+ var/obj/item/ammo_casing/reusable/arrow/supermatter/arrow = ammo_type
+ if(istype(arrow))
+ arrow.disintigrate(target)
-/obj/item/projectile/bullet/reusable/arrow/flaming/on_hit(atom/target, blocked = FALSE)
- if((blocked != 100) && iscarbon(target))
- var/mob/living/carbon/M = target
- M.apply_damage(8, BURN)
- M.adjust_fire_stacks(1)
- M.IgniteMob()
- return ..()
+/obj/item/projectile/bullet/reusable/arrow/supermatter/on_range()
+ . = ..()
+ var/obj/item/ammo_casing/reusable/arrow/supermatter/arrow = ammo_type
+ if(istype(arrow))
+ arrow.disintigrate(get_turf(src))
+
+/obj/item/projectile/bullet/reusable/arrow/singulo
+ name = "singularity shard arrow"
+
+/obj/item/projectile/bullet/reusable/arrow/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ var/obj/item/ammo_casing/reusable/arrow/singulo/arrow = ammo_type
+ if(istype(arrow))
+ arrow.shard_effect()
+
+
+// Hardlight //
/obj/item/projectile/energy/arrow //Hardlight projectile. Significantly more robust than a standard laser. Capable of hardening in target's flesh
name = "energy bolt"
@@ -151,7 +220,7 @@
wound_bonus = -60
speed = 0.6
var/embed_chance = 0.4
- var/obj/item/embed_type = /obj/item/ammo_casing/caseless/arrow/energy
+ var/obj/item/embed_type = /obj/item/ammo_casing/reusable/arrow/energy
/obj/item/projectile/energy/arrow/on_hit(atom/target, blocked = FALSE)
if((blocked != 100) && iscarbon(target))
@@ -167,7 +236,22 @@
light_color = LIGHT_COLOR_BLUE
damage = 50
damage_type = STAMINA
- embed_type = /obj/item/ammo_casing/caseless/arrow/energy/disabler
+ embed_type = /obj/item/ammo_casing/reusable/arrow/energy/disabler
+
+/obj/item/projectile/energy/arrow/pulse //Hardlight projectile. Woe to your enemies.
+ name = "pulse bolt"
+ icon_state = "arrow_pulse"
+ light_color = LIGHT_COLOR_BLUE
+ damage = 75
+ embed_type = /obj/item/ammo_casing/reusable/arrow/energy/pulse
+
+/obj/item/projectile/energy/arrow/pulse/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ if (!QDELETED(target) && (isturf(target) || istype(target, /obj/structure/)))
+ if(isobj(target))
+ SSexplosions.med_mov_atom += target
+ else
+ SSexplosions.medturf += target
/obj/item/projectile/energy/arrow/xray //Hardlight projectile. Weakened arrow capable of passing through material. Massive irradiation on hit.
name = "X-ray bolt"
@@ -178,10 +262,40 @@
irradiate = 500
range = 20
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINES | PASSSTRUCTURE | PASSDOOR
- embed_type = /obj/item/ammo_casing/caseless/arrow/energy/xray
+ embed_type = /obj/item/ammo_casing/reusable/arrow/energy/xray
+
+/obj/item/projectile/energy/arrow/shock //Hardlight projectile. Replicable tasers are fair and balanced.
+ name = "shock bolt"
+ icon_state = "arrow_shock"
+ light_color = LIGHT_COLOR_YELLOW
+ nodamage = TRUE
+ paralyze = 10 SECONDS
+ stutter = 5
+ jitter = 20
+ damage_type = STAMINA
+ embed_type = /obj/item/ammo_casing/reusable/arrow/energy/shock
+
+/obj/item/projectile/energy/arrow/shock/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks!
+ do_sparks(1, TRUE, src)
+ else if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "tased", /datum/mood_event/tased)
+ SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK)
+ if(C.dna && (C.dna.check_mutation(HULK) || C.dna.check_mutation(ACTIVE_HULK)))
+ C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk")
+ else if((C.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(C, TRAIT_STUNIMMUNE))
+ addtimer(CALLBACK(C, /mob/living/carbon.proc/do_jitter_animation, jitter), 5)
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ stomach.adjust_charge(10 * ETHEREAL_CHARGE_SCALING_MULTIPLIER)
+ to_chat(C,span_notice("You get charged by [src]."))
/obj/item/projectile/energy/arrow/clockbolt
name = "redlight bolt"
damage = 18
wound_bonus = 5
- embed_type = /obj/item/ammo_casing/caseless/arrow/energy/clockbolt
+ embed_type = /obj/item/ammo_casing/reusable/arrow/energy/clockbolt
diff --git a/code/modules/projectiles/projectile/reusable/foam_dart.dm b/code/modules/projectiles/projectile/reusable/foam_dart.dm
index 2c060996089a..99fe347be93a 100644
--- a/code/modules/projectiles/projectile/reusable/foam_dart.dm
+++ b/code/modules/projectiles/projectile/reusable/foam_dart.dm
@@ -6,10 +6,8 @@
nodamage = TRUE
icon = 'icons/obj/guns/toy.dmi'
icon_state = "foamdart_proj"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart
range = 10
- var/modified = FALSE
- var/obj/item/pen/pen = null
/// Apply stamina damage to other toy gun users
/obj/item/projectile/bullet/reusable/foam_dart/on_hit(atom/target, blocked)
@@ -24,44 +22,22 @@
var/nerfed = FALSE
var/mob/living/carbon/C = target
for(var/obj/item/gun/ballistic/T in C.held_items) // Is usually just ~2 items
- if(ispath(T.mag_type, /obj/item/ammo_box/magazine/toy)) // All automatic foam force guns
- nerfed = TRUE
- break
- if(ispath(T.mag_type, /obj/item/ammo_box/magazine/internal/shot/toy)) // Foam force shotguns & crossbows
+ if(ispath(T.mag_type, /obj/item/ammo_box/magazine/toy) || ispath(T.mag_type, /obj/item/ammo_box/magazine/internal/shot/toy)) // All automatic foam force guns || Foam force shotguns & crossbows
nerfed = TRUE
break
+ if(istype(T, /obj/item/gun/ballistic/bow)) // Bows have their own handling
+ var/obj/item/gun/ballistic/bow/bow = T
+ if(bow.nerfed)
+ nerfed = TRUE
+ break
if(!nerfed)
return
C.adjustStaminaLoss(25) // ARMOR IS CHEATING!!!
-/obj/item/projectile/bullet/reusable/foam_dart/handle_drop()
- if(dropped)
- return
- var/turf/T = get_turf(src)
- dropped = 1
- var/obj/item/ammo_casing/caseless/foam_dart/newcasing = new ammo_type(T)
- newcasing.modified = modified
- var/obj/item/projectile/bullet/reusable/foam_dart/newdart = newcasing.BB
- newdart.modified = modified
- if(modified)
- newdart.damage = 5
- newdart.nodamage = FALSE
- newdart.damage_type = damage_type
- if(pen)
- newdart.pen = pen
- pen.forceMove(newdart)
- pen = null
- newdart.update_icon()
-
-
-/obj/item/projectile/bullet/reusable/foam_dart/Destroy()
- pen = null
- return ..()
-
/obj/item/projectile/bullet/reusable/foam_dart/riot
name = "riot foam dart"
icon_state = "foamdart_riot_proj"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/reusable/foam_dart/riot
stamina = 25
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 5dc69b4e52a8..3db50cbcaa55 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -15,13 +15,14 @@
materials = list(/datum/material/iron=10, /datum/material/glass=20)
reagent_flags = TRANSPARENT
sharpness = SHARP_POINTY
- embedding = list("embedded_pain_chance" = 0, "embedded_pain_multiplier" = 0, "embedded_unsafe_removal_time" = 0.25 SECONDS, "embedded_unsafe_removal_pain_multiplier" = 0, "embed_chance" = 15, "embedded_fall_chance" = 5)
+ embedding = list("embedded_pain_chance" = 0, "embedded_pain_multiplier" = 0, "embedded_unsafe_removal_time" = 0.25 SECONDS, "embedded_unsafe_removal_pain_multiplier" = 0, "embed_chance" = 15, "embedded_fall_chance" = 5, "embedded_bleed_rate" = 0)
/obj/item/reagent_containers/syringe/Initialize()
. = ..()
if(list_reagents) //syringe starts in inject mode if its already got something inside
mode = SYRINGE_INJECT
update_icon()
+ RegisterSignals(src, list(COMSIG_ITEM_EMBEDDED, COMSIG_ITEM_EMBED_TICK), PROC_REF(embed_inject))
/obj/item/reagent_containers/syringe/on_reagent_change(changetype)
update_icon()
@@ -195,13 +196,9 @@
add_overlay(injoverlay)
M.update_inv_hands()
-/obj/item/reagent_containers/syringe/on_embed(mob/living/carbon/human/embedde, obj/item/bodypart/part)
- var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1)
- reagents.reaction(embedde, INJECT, fraction)
- reagents.trans_to(embedde, amount_per_transfer_from_this)
- return TRUE
-
-/obj/item/reagent_containers/syringe/embed_tick(embedde, part)
+/obj/item/reagent_containers/syringe/proc/embed_inject(target, mob/living/carbon/human/embedde, obj/item/bodypart/part)
+ if(!reagents.total_volume)
+ return
var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1)
reagents.reaction(embedde, INJECT, fraction)
reagents.trans_to(embedde, amount_per_transfer_from_this)
@@ -366,10 +363,14 @@
/obj/item/reagent_containers/syringe/dart
name = "reagent dart"
amount_per_transfer_from_this = 10
- embedding = list("embed_chance" = 15, "embedded_fall_chance" = 0)
+ embedding = list("embedded_pain_chance" = 0, "embedded_pain_multiplier" = 0, "embedded_unsafe_removal_time" = 0.25 SECONDS, "embedded_unsafe_removal_pain_multiplier" = 0, "embed_chance" = 15, "embedded_fall_chance" = 0, "embedded_bleed_rate" = 0)
/obj/item/reagent_containers/syringe/dart/temp
item_flags = DROPDEL
-/obj/item/reagent_containers/syringe/dart/temp/on_embed_removal(mob/living/carbon/human/embedde)
- qdel(src)
+/obj/item/reagent_containers/syringe/dart/temp/Initialize()
+ ..()
+ RegisterSignal(src, COMSIG_ITEM_EMBED_REMOVAL, PROC_REF(on_embed_removal))
+
+/obj/item/reagent_containers/syringe/dart/temp/proc/on_embed_removal(mob/living/carbon/human/embedde)
+ return COMSIG_ITEM_QDEL_EMBED_REMOVAL
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index 0f4138a5261c..db03bab3abae 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -885,7 +885,7 @@
id = "riot_dart"
build_type = AUTOLATHE
materials = list(/datum/material/iron = 1000) //Discount for making individually - no box = less metal!
- build_path = /obj/item/ammo_casing/caseless/foam_dart/riot
+ build_path = /obj/item/ammo_casing/reusable/foam_dart/riot
category = list("hacked", "Security")
/datum/design/riot_darts
diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm
index 0476015991e6..8be1bc7ca9ae 100644
--- a/code/modules/research/designs/misc_designs.dm
+++ b/code/modules/research/designs/misc_designs.dm
@@ -507,6 +507,16 @@
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
+/datum/design/anomaly_quiver
+ name = "Anomaly Quiver"
+ desc = "An empty, experimental quiver with not much space inside. A bluespace, pyroclastic, or gravitational anomaly can be inserted for varying effects."
+ id = "anomaly_quiver"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 7500, /datum/material/diamond = 3750, /datum/material/uranium = 6000, /datum/material/silver = 3500, /datum/material/gold = 3750)
+ build_path = /obj/item/storage/belt/quiver/anomaly
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
/datum/design/platingmki
name = "MK.I bluespace plating"
desc = "Plating fitted for a plated vest or helmet. Makes you faster, but gives less armor."
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index fc89522fa69a..04122018fe37 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -171,7 +171,7 @@
display_name = "Anomaly Research"
description = "Unlock the potential of the mysterious anomalies that appear on station."
prereq_ids = list("adv_engi", "practical_bluespace")
- design_ids = list("reactive_armor", "anomaly_neutralizer")
+ design_ids = list("reactive_armor", "anomaly_quiver", "anomaly_neutralizer")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
/datum/techweb_node/high_efficiency
diff --git a/code/modules/research/xenobiology/crossbreeding/_mobs.dm b/code/modules/research/xenobiology/crossbreeding/_mobs.dm
index 66551d9768e9..834f5c1f09f4 100644
--- a/code/modules/research/xenobiology/crossbreeding/_mobs.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_mobs.dm
@@ -11,7 +11,7 @@ Slimecrossing Mobs
action_icon_state = "transformslime"
cooldown_min = 0
charge_max = 0
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
shapeshift_type = /mob/living/simple_animal/slime/transformedslime
convert_damage = TRUE
convert_damage_type = CLONE
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index c88e2af1ee8b..b48a884a2600 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -112,14 +112,14 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
- var/charge_type = "recharge" //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that
+ var/charge_type = SPELL_CHARGE_TYPE_RECHARGE // See spell defines for a full list of options and what they do
- var/charge_max = 10 SECONDS //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges"
- var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges"
+ var/charge_max = 10 SECONDS //recharge time in deciseconds if charge_type = SPELL_CHARGE_TYPE_RECHARGE or starting charges if charge_type = SPELL_CHARGE_TYPE_CHARGES
+ var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = SPELL_CHARGE_TYPE_RECHARGE or -- each cast if charge_type = SPELL_CHARGE_TYPE_CHARGES
var/still_recharging_msg = span_notice("The spell is still recharging.")
var/recharging = TRUE
- var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var"
+ var/holder_var_type = "bruteloss" //only used if charge_type equals to SPELL_CHARGE_TYPE_HOLDERVAR
var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used
var/clothes_req = TRUE //see if it requires clothes
@@ -131,7 +131,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/antimagic_allowed = FALSE // If false, the spell cannot be cast while under the effect of antimagic
var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell
var/invocation_emote_self = null
- var/invocation_type = "none" //can be none, whisper, emote and shout
+ var/invocation_type = SPELL_INVOCATION_NONE // See spell defines for a full list of options and what they do
var/range = 7 //the range of the spell; outer radius for aoe spells
var/message = "" //whatever it says to the guy affected by it
var/selection_type = "view" //can be "range" or "view"
@@ -200,7 +200,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/mob/living/carbon/human/H = user
- if((invocation_type == "whisper" || invocation_type == "shout") && !H.can_speak_vocal())
+ if((invocation_type == SPELL_INVOCATION_SAY || invocation_type == SPELL_INVOCATION_WHISPER) && !H.can_speak_vocal())
to_chat(user, span_notice("You can't get the words out!"))
return FALSE
@@ -237,11 +237,11 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
if(!skipcharge)
switch(charge_type)
- if("recharge")
+ if(SPELL_CHARGE_TYPE_RECHARGE)
charge_counter = 0 //doesn't start recharging until the targets selecting ends
- if("charges")
+ if(SPELL_CHARGE_TYPE_CHARGES)
charge_counter-- //returns the charge if the targets selecting fails
- if("holdervar")
+ if(SPELL_CHARGE_TYPE_HOLDERVAR)
adjust_var(user, holder_var_type, holder_var_amount)
if(action)
action.UpdateButtonIcon()
@@ -249,12 +249,12 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/charge_check(mob/user, silent = FALSE)
switch(charge_type)
- if("recharge")
+ if(SPELL_CHARGE_TYPE_RECHARGE)
if(charge_counter < charge_max)
if(!silent)
to_chat(user, still_recharging_msg)
return FALSE
- if("charges")
+ if(SPELL_CHARGE_TYPE_CHARGES)
if(!charge_counter)
if(!silent)
to_chat(user, span_notice("[name] has no charges left."))
@@ -263,17 +263,19 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount
switch(invocation_type)
- if("shout")
+ if(SPELL_INVOCATION_SAY)
if(prob(50))//Auto-mute? Fuck that noise
user.say(invocation, forced = "spell")
else
user.say(replacetext(invocation," ","`"), forced = "spell")
- if("whisper")
+ if(SPELL_INVOCATION_WHISPER)
if(prob(50))
user.whisper(invocation)
else
user.whisper(replacetext(invocation," ","`"))
- if("emote")
+ if(SPELL_INVOCATION_EMOTE)
+ user.emote(invocation)
+ if(SPELL_INVOCATION_MESSAGE)
user.visible_message(invocation, invocation_emote_self) //same style as in mob/living/emote.dm
/obj/effect/proc_holder/spell/proc/playMagSound()
@@ -309,7 +311,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
recharging = TRUE
/obj/effect/proc_holder/spell/process(delta_time)
- if(recharging && charge_type == "recharge" && (charge_counter < charge_max))
+ if(recharging && charge_type == SPELL_CHARGE_TYPE_RECHARGE && (charge_counter < charge_max))
charge_counter += delta_time * 10
cooldown_overlay?.tick()
if(charge_counter >= charge_max)
@@ -369,11 +371,11 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge
switch(charge_type)
- if("recharge")
+ if(SPELL_CHARGE_TYPE_RECHARGE)
charge_counter = charge_max
- if("charges")
+ if(SPELL_CHARGE_TYPE_CHARGES)
charge_counter++
- if("holdervar")
+ if(SPELL_CHARGE_TYPE_HOLDERVAR)
adjust_var(user, holder_var_type, -holder_var_amount)
QDEL_NULL(cooldown_overlay)
if(action)
@@ -555,7 +557,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
charge_max = 100
cooldown_min = 50
invocation = "Victus sano!"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
school = "restoration"
sound = 'sound/magic/staff_healing.ogg'
diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm
index 7464f1078622..33c29cf48a03 100644
--- a/code/modules/spells/spell_types/aimed.dm
+++ b/code/modules/spells/spell_types/aimed.dm
@@ -22,7 +22,7 @@
return
if(active)
msg = span_notice("[deactive_msg]")
- if(charge_type == "recharge")
+ if(charge_type == SPELL_CHARGE_TYPE_RECHARGE)
var/refund_percent = current_amount/projectile_amount
charge_counter = charge_max * refund_percent
start_recharge()
@@ -95,7 +95,7 @@
charge_max = 100
clothes_req = FALSE
invocation = "UN'LTD P'WAH"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
cooldown_min = 30
active_icon_state = "lightning"
base_icon_state = "lightning"
@@ -113,7 +113,7 @@
charge_max = 60
clothes_req = FALSE
invocation = "ONI SOMA"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 20
cooldown_min = 20 //10 deciseconds reduction per rank
projectile_type = /obj/item/projectile/magic/aoe/fireball
@@ -132,7 +132,7 @@
charge_max = 50
clothes_req = FALSE
invocation = "Sigi'lu M'Fan 'Tasia"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 40
cooldown_min = 10
projectile_amount = 5
diff --git a/code/modules/spells/spell_types/area_teleport.dm b/code/modules/spells/spell_types/area_teleport.dm
index 9696e1a39ec1..4fe4624f7e51 100644
--- a/code/modules/spells/spell_types/area_teleport.dm
+++ b/code/modules/spells/spell_types/area_teleport.dm
@@ -16,7 +16,7 @@
revert_cast()
return
invocation(thearea,user)
- if(charge_type == "recharge" && recharge)
+ if(charge_type == SPELL_CHARGE_TYPE_RECHARGE && recharge)
INVOKE_ASYNC(src, .proc/start_recharge)
cast(targets,thearea,user)
after_cast(targets)
@@ -72,21 +72,8 @@
playsound(get_turf(user), sound2, 50,1)
/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/living/user = usr)
- if(!invocation_area || !chosenarea)
- ..()
+ if(say_destination && chosenarea)
+ invocation = "[initial(invocation)] [uppertext(chosenarea.name)]"
else
- var/words
- if(say_destination)
- words = "[invocation] [uppertext(chosenarea.name)]"
- else
- words = "[invocation]"
-
- switch(invocation_type)
- if("shout")
- user.say(words, forced = "spell")
- if(user.gender==MALE)
- playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
- else
- playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
- if("whisper")
- user.whisper(words, forced = "spell")
+ invocation = initial(invocation)
+ ..()
diff --git a/code/modules/spells/spell_types/arrow.dm b/code/modules/spells/spell_types/arrow.dm
new file mode 100644
index 000000000000..05901c5e2ea8
--- /dev/null
+++ b/code/modules/spells/spell_types/arrow.dm
@@ -0,0 +1,35 @@
+/obj/effect/proc_holder/spell/targeted/conjure_item/arrow
+ name = "Summon Arrow"
+ desc = "A spell that summons a sharp arrow in the user's hand, ready to be shot out of a bow. Can be quickly casted by pressing the 'quick-equip' key on an empty hand."
+ action_icon = 'icons/obj/ammo.dmi'
+ action_icon_state = "arrow"
+ invocation_type = SPELL_INVOCATION_EMOTE
+ invocation = "snap"
+ item_type = /obj/item/ammo_casing/reusable/arrow
+ charge_max = 15 SECONDS
+ cooldown_min = 1 SECONDS
+ delete_old = FALSE
+ drop_currently_held = FALSE
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/on_gain(mob/living/user)
+ . = ..()
+ RegisterSignal(user, COMSIG_MOB_QUICK_EQUIP, PROC_REF(on_quick_equip))
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/on_lose(mob/living/user)
+ . = ..()
+ UnregisterSignal(user, COMSIG_MOB_QUICK_EQUIP)
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/proc/on_quick_equip(obj/item/held_item)
+ if(isitem(held_item) || isitem(usr.get_active_held_item()))
+ return
+ Click()
+ if(usr.get_active_held_item())
+ return COMPONENT_BLOCK_QUICK_EQUIP
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/arrow/magic
+ name = "Summon Magic Arrow"
+ desc = "A spell that summons a homing arrow in the user's hand, ready to be shot out of a bow that quickly becomes dull after hitting something. Can be quickly casted by pressing the 'quick-equip' key on an empty hand."
+ action_icon_state = "arrow_magic"
+ item_type = /obj/item/ammo_casing/reusable/arrow/magic
+ charge_max = 15 SECONDS
+ cooldown_min = 0.25 SECONDS
diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/barnyard.dm
index 7ae66eeaccbf..1d690086642a 100644
--- a/code/modules/spells/spell_types/barnyard.dm
+++ b/code/modules/spells/spell_types/barnyard.dm
@@ -2,13 +2,13 @@
name = "Curse of the Barnyard"
desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal."
school = "transmutation"
- charge_type = "recharge"
+ charge_type = SPELL_CHARGE_TYPE_RECHARGE
charge_max = 150
charge_counter = 0
clothes_req = FALSE
stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 7
cooldown_min = 30
selection_type = "range"
diff --git a/code/modules/spells/spell_types/charge.dm b/code/modules/spells/spell_types/charge.dm
index 26d51644616c..a7497158f286 100644
--- a/code/modules/spells/spell_types/charge.dm
+++ b/code/modules/spells/spell_types/charge.dm
@@ -6,7 +6,7 @@
charge_max = 600
clothes_req = FALSE
invocation = "DIRI CEL"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = -1
cooldown_min = 400 //50 deciseconds reduction per rank
include_user = TRUE
diff --git a/code/modules/spells/spell_types/cone_spells.dm b/code/modules/spells/spell_types/cone_spells.dm
index 26dd15798614..519d83f5fc7d 100644
--- a/code/modules/spells/spell_types/cone_spells.dm
+++ b/code/modules/spells/spell_types/cone_spells.dm
@@ -5,7 +5,7 @@
charge_max = 100
clothes_req = FALSE
invocation = "FUKAN NOTHAN"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
sound = 'sound/magic/forcewall.ogg'
action_icon_state = "shield"
range = -1
diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm
index d466f1713713..0d06e7a24eaa 100644
--- a/code/modules/spells/spell_types/conjure.dm
+++ b/code/modules/spells/spell_types/conjure.dm
@@ -71,7 +71,7 @@
/obj/effect/proc_holder/spell/targeted/conjure_item
name = "Summon weapon"
desc = "A generic spell that should not exist. This summons an instance of a specific type of item, or if one already exists, un-summons it. Summons into hand if possible."
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
include_user = TRUE
range = -1
clothes_req = FALSE
@@ -80,15 +80,18 @@
school = "conjuration"
charge_max = 150
cooldown_min = 10
- var/delete_old = TRUE //TRUE to delete the last summoned object if it's still there, FALSE for infinite item stream weeeee
+ /// TRUE to delete the last summoned object if it's still there, FALSE for infinite item stream weeeee
+ var/delete_old = TRUE
+ /// If the currently held item should be dropped to make way for the new item
+ var/drop_currently_held = TRUE
/obj/effect/proc_holder/spell/targeted/conjure_item/cast(list/targets, mob/user = usr)
if (delete_old && item && !QDELETED(item))
QDEL_NULL(item)
- else
- for(var/mob/living/carbon/C in targets)
- if(C.dropItemToGround(C.get_active_held_item()))
- C.put_in_hands(make_item(), TRUE)
+ for(var/mob/living/carbon/C in targets)
+ if(drop_currently_held)
+ C.dropItemToGround(C.get_active_held_item())
+ C.put_in_hands(make_item(), TRUE)
/obj/effect/proc_holder/spell/targeted/conjure_item/Destroy()
if(item)
diff --git a/code/modules/spells/spell_types/construct_spells.dm b/code/modules/spells/spell_types/construct_spells.dm
index 8da77d12bab9..9350b8db5723 100644
--- a/code/modules/spells/spell_types/construct_spells.dm
+++ b/code/modules/spells/spell_types/construct_spells.dm
@@ -19,7 +19,7 @@
charge_max = 50
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 2
action_icon = 'icons/mob/actions/actions_cult.dmi'
action_icon_state = "areaconvert"
@@ -39,7 +39,7 @@
charge_max = 20
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 0
summon_type = list(/turf/open/floor/engine/cult)
action_icon = 'icons/mob/actions/actions_cult.dmi'
@@ -55,7 +55,7 @@
charge_max = 100
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 0
action_icon = 'icons/mob/actions/actions_cult.dmi'
action_icon_state = "lesserconstruct"
@@ -72,7 +72,7 @@
charge_max = 300
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 0
summon_type = list(/turf/closed/wall/r_wall)
@@ -85,7 +85,7 @@
charge_max = 2400
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 0
action_icon = 'icons/mob/actions/actions_cult.dmi'
action_icon_state = "summonsoulstone"
@@ -110,7 +110,7 @@
charge_max = 400
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
wall_type = /obj/effect/forcefield/cult
action_icon = 'icons/mob/actions/actions_cult.dmi'
action_icon_state = "cultforcewall"
@@ -126,7 +126,7 @@
charge_max = 250
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = -1
include_user = TRUE
jaunt_duration = 50 //in deciseconds
@@ -148,7 +148,7 @@
charge_max = 400
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
max_targets = 6
action_icon_state = "magicm"
action_background_icon_state = "bg_demon"
@@ -178,7 +178,7 @@
charge_max = 200
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = -1
include_user = TRUE
cooldown_min = 20 //25 deciseconds reduction per rank
@@ -202,7 +202,7 @@
school = "evocation"
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
action_icon = 'icons/mob/actions/actions_cult.dmi'
action_background_icon_state = "bg_demon"
action_icon_state = "abyssal_gaze"
@@ -249,7 +249,7 @@
school = "evocation"
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
action_icon = 'icons/mob/actions/actions_cult.dmi'
action_background_icon_state = "bg_demon"
action_icon_state = "dominate"
diff --git a/code/modules/spells/spell_types/devil.dm b/code/modules/spells/spell_types/devil.dm
index aebd488225ae..f66697b24c8c 100644
--- a/code/modules/spells/spell_types/devil.dm
+++ b/code/modules/spells/spell_types/devil.dm
@@ -1,7 +1,7 @@
/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork
name = "Summon Pitchfork"
desc = "A devil's weapon of choice. Use this to summon/unsummon your pitchfork."
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
include_user = TRUE
range = -1
clothes_req = FALSE
@@ -23,7 +23,7 @@
/obj/effect/proc_holder/spell/targeted/conjure_item/violin
item_type = /obj/item/instrument/violin/golden
desc = "A devil's instrument of choice. Use this to summon/unsummon your golden violin."
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
invocation = "I aint have this much fun since Georgia."
action_icon_state = "golden_violin"
name = "Summon golden violin"
@@ -33,7 +33,7 @@
/obj/effect/proc_holder/spell/targeted/summon_contract
name = "Summon infernal contract"
desc = "Skip making a contract by hand, just do it by magic."
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
invocation = "Just sign on the dotted line."
include_user = FALSE
range = 5
@@ -83,7 +83,7 @@
charge_max = 80
clothes_req = FALSE
invocation = "Your very soul will catch fire!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 2
projectile_type = /obj/item/projectile/magic/aoe/fireball/infernal
@@ -183,7 +183,7 @@
random_target_priority = TARGET_RANDOM
max_targets = 3
invocation = "TASTE SIN AND INDULGE!!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
/obj/effect/proc_holder/spell/targeted/sintouch/ascended
name = "Greater sin touch"
diff --git a/code/modules/spells/spell_types/devil_boons.dm b/code/modules/spells/spell_types/devil_boons.dm
index 0fa7f91dec81..3492446d80fd 100644
--- a/code/modules/spells/spell_types/devil_boons.dm
+++ b/code/modules/spells/spell_types/devil_boons.dm
@@ -1,7 +1,7 @@
/obj/effect/proc_holder/spell/targeted/summon_wealth
name = "Summon wealth"
desc = "The reward for selling your soul."
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
include_user = TRUE
range = -1
clothes_req = FALSE
@@ -29,7 +29,7 @@
/obj/effect/proc_holder/spell/targeted/view_range
name = "Distant vision"
desc = "The reward for selling your soul."
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
include_user = TRUE
range = -1
clothes_req = FALSE
@@ -48,7 +48,7 @@
/obj/effect/proc_holder/spell/targeted/summon_friend
name = "Summon Friend"
desc = "The reward for selling your soul."
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
include_user = TRUE
range = -1
clothes_req = FALSE
diff --git a/code/modules/spells/spell_types/disguise.dm b/code/modules/spells/spell_types/disguise.dm
index 4d646128bc96..9235fbc0a27a 100644
--- a/code/modules/spells/spell_types/disguise.dm
+++ b/code/modules/spells/spell_types/disguise.dm
@@ -2,7 +2,7 @@
name = "Mimicry"
desc = "Why fight your foes when you can simply outwit them? Disguises the caster as a random crewmember. The body-covering shell keeps your form as is, and protects you from body-altering effects."
invocation = "CONJR DIS GUISE"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
school = "transmutation"
charge_max = 60 SECONDS
cooldown_min = 50 SECONDS
diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm
index 4433c3d5fbf9..ea5c662c6e6f 100644
--- a/code/modules/spells/spell_types/ethereal_jaunt.dm
+++ b/code/modules/spells/spell_types/ethereal_jaunt.dm
@@ -6,7 +6,7 @@
charge_max = 300
clothes_req = TRUE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = -1
cooldown_min = 10 SECONDS //5 seconds reduction per rank
include_user = TRUE
diff --git a/code/modules/spells/spell_types/forcewall.dm b/code/modules/spells/spell_types/forcewall.dm
index 2329790525d8..b99218e4a793 100644
--- a/code/modules/spells/spell_types/forcewall.dm
+++ b/code/modules/spells/spell_types/forcewall.dm
@@ -5,7 +5,7 @@
charge_max = 100
clothes_req = FALSE
invocation = "TARCOL MINTI ZHERI"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
sound = 'sound/magic/forcewall.ogg'
action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
action_icon_state = "shield"
diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm
index 91a11e273278..adf0fccb4ac7 100644
--- a/code/modules/spells/spell_types/hivemind.dm
+++ b/code/modules/spells/spell_types/hivemind.dm
@@ -1,6 +1,6 @@
/obj/effect/proc_holder/spell/target_hive
panel = "Hivemind Abilities"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
selection_type = "range"
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
@@ -185,7 +185,7 @@
desc = "After a short charging time, we overload the mind of one of our vessels with psionic energy, temporarilly disrupting their sight, hearing, and speech."
charge_max = 600
panel = "Hivemind Abilities"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -222,7 +222,7 @@
desc = "We release a pulse to receive information on any enemies we have previously located via Network Invasion, as well as those currently tracking us."
panel = "Hivemind Abilities"
charge_max = 1800
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -296,7 +296,7 @@
panel = "Hivemind Abilities"
charge_max = 600
clothes_req = 0
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
action_icon_state = "drain"
@@ -524,7 +524,7 @@
panel = "Hivemind Abilities"
charge_max = 600
range = 7
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
max_targets = 0
antimagic_allowed = TRUE
@@ -579,7 +579,7 @@
panel = "Hivemind Abilities"
charge_max = 600
range = 7
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
max_targets = 0
antimagic_allowed = TRUE
@@ -656,7 +656,7 @@
name = "Telekinetic hand"
desc = "Makes a telekinetic hand to extend the reach of our unarmed combat. Drop to remove."
clothes_req = 0
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
antimagic_allowed = TRUE
charge_max = 200
panel = "Hivemind Abilities"
@@ -680,7 +680,7 @@
panel = "Hivemind Abilities"
charge_max = 600
range = 1
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
max_targets = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -751,7 +751,7 @@
charge_max = 600
range = 1
max_targets = 0
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -795,9 +795,9 @@
name = "Chaos Induction"
desc = "A one-use power, we awaken four random vessels within our hive and force them to do our bidding."
panel = "Hivemind Abilities"
- charge_type = "charges"
+ charge_type = SPELL_CHARGE_TYPE_CHARGES
charge_max = 1
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -840,7 +840,7 @@
desc = "Our ability to assimilate is boosted at the cost of, allowing us to crush the technology shielding the minds of savyy personnel and assimilate them. This power comes at a small price, and we will be immobilized for a few seconds after assimilation."
panel = "Hivemind Abilities"
charge_max = 600
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -870,7 +870,7 @@
charge_max = 600
clothes_req = 0
human_req = 1
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
action_icon_state = "forcewall"
@@ -916,9 +916,9 @@
name = "One Mind"
desc = "Our true power... finally within reach."
panel = "Hivemind Abilities"
- charge_type = "charges"
+ charge_type = SPELL_CHARGE_TYPE_CHARGES
charge_max = 1
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
@@ -974,7 +974,7 @@
desc = "Now that we are free we may finally share our thoughts with our many bretheren."
panel = "Hivemind Abilities"
charge_max = 100
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = 0
human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
diff --git a/code/modules/spells/spell_types/infinite_guns.dm b/code/modules/spells/spell_types/infinite_guns.dm
index ddf292366854..e07b075a48ba 100644
--- a/code/modules/spells/spell_types/infinite_guns.dm
+++ b/code/modules/spells/spell_types/infinite_guns.dm
@@ -1,7 +1,7 @@
/obj/effect/proc_holder/spell/targeted/infinite_guns
name = "Lesser Summon Guns"
desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage."
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
include_user = TRUE
range = -1
diff --git a/code/modules/spells/spell_types/knock.dm b/code/modules/spells/spell_types/knock.dm
index beab4da05fc6..f4abbf82e2de 100644
--- a/code/modules/spells/spell_types/knock.dm
+++ b/code/modules/spells/spell_types/knock.dm
@@ -6,7 +6,7 @@
charge_max = 100
clothes_req = FALSE
invocation = "AULIE OXIN FIERA"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = 3
cooldown_min = 20 //20 deciseconds reduction per rank
action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm
index 7c135ac2eb20..6988ba76c3e7 100644
--- a/code/modules/spells/spell_types/lichdom.dm
+++ b/code/modules/spells/spell_types/lichdom.dm
@@ -11,7 +11,7 @@
clothes_req = FALSE
centcom_cancast = FALSE
invocation = "NECREM IMORTIUM!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = -1
level_max = 0 //cannot be improved
cooldown_min = 10
diff --git a/code/modules/spells/spell_types/lightning.dm b/code/modules/spells/spell_types/lightning.dm
index f8f4ec4ec0de..4f7e30a034e5 100644
--- a/code/modules/spells/spell_types/lightning.dm
+++ b/code/modules/spells/spell_types/lightning.dm
@@ -1,11 +1,11 @@
/obj/effect/proc_holder/spell/targeted/tesla
name = "Tesla Blast"
desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down if they do not have shock protection."
- charge_type = "recharge"
+ charge_type = SPELL_CHARGE_TYPE_RECHARGE
charge_max = 200
clothes_req = TRUE
invocation = "UN'LTD P'WAH!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 7
cooldown_min = 30
selection_type = "view"
diff --git a/code/modules/spells/spell_types/mime.dm b/code/modules/spells/spell_types/mime.dm
index 668052b766b0..41a7d89c03e1 100644
--- a/code/modules/spells/spell_types/mime.dm
+++ b/code/modules/spells/spell_types/mime.dm
@@ -4,7 +4,7 @@
school = "mime"
panel = "Mime"
summon_type = list(/obj/effect/forcefield/mime)
- invocation_type = "emote"
+ invocation_type = SPELL_INVOCATION_MESSAGE
invocation_emote_self = span_notice("You form a wall in front of yourself.")
summon_lifespan = 300
charge_max = 300
@@ -29,7 +29,7 @@
return
invocation = "[usr.real_name] looks as if a wall is in front of [usr.p_them()]."
else
- invocation_type ="none"
+ invocation_type = SPELL_INVOCATION_NONE
..()
/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair
@@ -38,7 +38,7 @@
school = "mime"
panel = "Mime"
summon_type = list(/obj/structure/chair/mime)
- invocation_type = "emote"
+ invocation_type = SPELL_INVOCATION_MESSAGE
invocation_emote_self = span_notice("You conjure an invisible chair and sit down.")
summon_lifespan = 250
charge_max = 300
@@ -59,7 +59,7 @@
return
invocation = "[usr.real_name] pulls out an invisible chair and sits down."
else
- invocation_type ="none"
+ invocation_type = SPELL_INVOCATION_NONE
..()
/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/cast(list/targets,mob/user = usr)
@@ -76,7 +76,7 @@
school = "mime"
panel = "Mime"
summon_type = list(/obj/item/storage/box/mime)
- invocation_type = "emote"
+ invocation_type = SPELL_INVOCATION_MESSAGE
invocation_emote_self = span_notice("You conjure up an invisible box, large enough to store a few things.")
summon_lifespan = 250
charge_max = 300
@@ -106,7 +106,7 @@
return
invocation = "[usr.real_name] moves [usr.p_their()] hands in the shape of a cube, pressing a box out of the air."
else
- invocation_type ="none"
+ invocation_type = SPELL_INVOCATION_NONE
..()
@@ -156,7 +156,7 @@
school = "mime"
panel = "Mime"
wall_type = /obj/effect/forcefield/mime/advanced
- invocation_type = "emote"
+ invocation_type = SPELL_INVOCATION_MESSAGE
invocation_emote_self = span_notice("You form a blockade in front of yourself.")
charge_max = 600
sound = null
@@ -176,7 +176,7 @@
return
invocation = "[usr.real_name] looks as if a blockade is in front of [usr.p_them()]."
else
- invocation_type ="none"
+ invocation_type = SPELL_INVOCATION_NONE
..()
/obj/effect/proc_holder/spell/aimed/finger_guns
@@ -187,7 +187,7 @@
charge_max = 300
clothes_req = FALSE
antimagic_allowed = TRUE
- invocation_type = "emote"
+ invocation_type = SPELL_INVOCATION_MESSAGE
invocation_emote_self = span_dangers("You fire your finger gun!")
range = 20
projectile_type = /obj/item/projectile/bullet/mime
@@ -214,7 +214,7 @@
return
invocation = "[usr.real_name] fires [usr.p_their()] finger gun!"
else
- invocation_type ="none"
+ invocation_type = SPELL_INVOCATION_NONE
..()
diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm
index 7d605c8748ad..b9fec8bbcd5d 100644
--- a/code/modules/spells/spell_types/mind_transfer.dm
+++ b/code/modules/spells/spell_types/mind_transfer.dm
@@ -6,7 +6,7 @@
charge_max = 600
clothes_req = FALSE
invocation = "GIN'YU CAPAN"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = 1
cooldown_min = 200 //100 deciseconds reduction per rank
var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell
diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm
index 4968b47b3eb4..9aa3d193960c 100644
--- a/code/modules/spells/spell_types/pointed/blind.dm
+++ b/code/modules/spells/spell_types/pointed/blind.dm
@@ -5,7 +5,7 @@
charge_max = 300
clothes_req = FALSE
invocation = "STI KALY"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
message = span_notice("Your eyes cry out in pain!")
cooldown_min = 50 //12 deciseconds reduction per rank
ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi'
@@ -31,4 +31,4 @@
if(!silent)
to_chat(user, span_warning("You can only blind living beings!"))
return FALSE
- return TRUE
\ No newline at end of file
+ return TRUE
diff --git a/code/modules/spells/spell_types/rod_form.dm b/code/modules/spells/spell_types/rod_form.dm
index 9829deee73cc..091190cf677a 100644
--- a/code/modules/spells/spell_types/rod_form.dm
+++ b/code/modules/spells/spell_types/rod_form.dm
@@ -8,7 +8,7 @@
range = -1
include_user = TRUE
invocation = "CLANG!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
action_icon_state = "immrod"
/obj/effect/proc_holder/spell/targeted/rod_form/cast(list/targets,mob/user = usr)
diff --git a/code/modules/spells/spell_types/santa.dm b/code/modules/spells/spell_types/santa.dm
index e807f863ba92..ad6673228316 100644
--- a/code/modules/spells/spell_types/santa.dm
+++ b/code/modules/spells/spell_types/santa.dm
@@ -7,7 +7,7 @@
clothes_req = FALSE
antimagic_allowed = TRUE
invocation = "HO HO HO"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 3
cooldown_min = 50
diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm
index 2a4684409be8..e0c085316bd8 100644
--- a/code/modules/spells/spell_types/shapeshift.dm
+++ b/code/modules/spells/spell_types/shapeshift.dm
@@ -8,7 +8,7 @@
range = -1
include_user = TRUE
invocation = "RAC'WA NO!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
action_icon_state = "shapeshift"
var/revert_on_death = TRUE
diff --git a/code/modules/spells/spell_types/soultap.dm b/code/modules/spells/spell_types/soultap.dm
index be22aac90fb9..830436d74dff 100644
--- a/code/modules/spells/spell_types/soultap.dm
+++ b/code/modules/spells/spell_types/soultap.dm
@@ -10,7 +10,7 @@
school = "necromancy" //i could see why this wouldn't be necromancy but messing with souls or whatever. ectomancy?
charge_max = 10
invocation = "AT ANY COST!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
level_max = 0
cooldown_min = 10
@@ -30,4 +30,4 @@
for(var/obj/effect/proc_holder/spell/spell in user.mind.spell_list)
spell.charge_counter = spell.charge_max
spell.recharging = FALSE
- spell.update_icon()
\ No newline at end of file
+ spell.update_icon()
diff --git a/code/modules/spells/spell_types/summonitem.dm b/code/modules/spells/spell_types/summonitem.dm
index 0382ccc81738..a5e4644386e7 100644
--- a/code/modules/spells/spell_types/summonitem.dm
+++ b/code/modules/spells/spell_types/summonitem.dm
@@ -5,7 +5,7 @@
charge_max = 100
clothes_req = FALSE
invocation = "GAR YOK"
- invocation_type = "whisper"
+ invocation_type = SPELL_INVOCATION_WHISPER
range = -1
level_max = 0 //cannot be improved
cooldown_min = 100
diff --git a/code/modules/spells/spell_types/the_traps.dm b/code/modules/spells/spell_types/the_traps.dm
index cc270764fa5d..4bbdb786fd0a 100644
--- a/code/modules/spells/spell_types/the_traps.dm
+++ b/code/modules/spells/spell_types/the_traps.dm
@@ -7,7 +7,7 @@
clothes_req = TRUE
invocation = "CAVERE INSIDIAS"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 3
summon_type = list(
diff --git a/code/modules/spells/spell_types/touch_attacks.dm b/code/modules/spells/spell_types/touch_attacks.dm
index e0b84061920d..f9b9785f2a5f 100644
--- a/code/modules/spells/spell_types/touch_attacks.dm
+++ b/code/modules/spells/spell_types/touch_attacks.dm
@@ -3,7 +3,7 @@
var/obj/item/melee/touch_attack/attached_hand = null
var/drawmessage = "You channel the power of the spell to your hand."
var/dropmessage = "You draw the power out of your hand."
- invocation_type = "none" //you scream on connecting, not summoning
+ invocation_type = SPELL_INVOCATION_NONE //you scream on connecting, not summoning
include_user = TRUE
range = -1
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index 505ddbe6329f..cfc1797a4975 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -6,7 +6,7 @@
charge_max = 200
clothes_req = TRUE
invocation = "FORTI GY AMA"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 7
cooldown_min = 60 //35 deciseconds reduction per rank
max_targets = 0
@@ -46,7 +46,7 @@
charge_max = 400
clothes_req = TRUE
invocation = "BIRUZ BENNAR"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = -1
include_user = TRUE
@@ -67,7 +67,7 @@
charge_max = 120
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = -1
include_user = TRUE
cooldown_min = 20 //25 deciseconds reduction per rank
@@ -86,7 +86,7 @@
charge_max = 360
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = -1
include_user = TRUE
@@ -101,7 +101,7 @@
charge_max = 400
clothes_req = TRUE
invocation = "NEC CANTIO"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = -1
include_user = TRUE
cooldown_min = 200 //50 deciseconds reduction per rank
@@ -118,7 +118,7 @@
charge_max = 20
clothes_req = TRUE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = -1
include_user = TRUE
cooldown_min = 5 //4 deciseconds reduction per rank
@@ -150,7 +150,7 @@
charge_max = 600
clothes_req = TRUE
invocation = "SCYAR NILA"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = -1
include_user = TRUE
cooldown_min = 200 //100 deciseconds reduction per rank
@@ -173,7 +173,7 @@
charge_max = 500
clothes_req = TRUE
invocation = "TOKI WO TOMARE"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 0
cooldown_min = 100
summon_amt = 1
@@ -190,7 +190,7 @@
charge_max = 1200
clothes_req = TRUE
invocation = "NOUK FHUNMM SACP RISSKA"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 1
summon_type = list(/mob/living/simple_animal/hostile/carp)
@@ -205,7 +205,7 @@
charge_max = 600
clothes_req = FALSE
invocation = "none"
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
range = 0
summon_type = list(/obj/structure/constructshell)
@@ -222,7 +222,7 @@
charge_max = 1200
clothes_req = FALSE
invocation = "IA IA"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
summon_amt = 10
range = 3
@@ -242,7 +242,7 @@
charge_max = 400
clothes_req = TRUE
invocation = "GITTAH WEIGH"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 5
cooldown_min = 150
selection_type = "view"
@@ -298,7 +298,7 @@
antimagic_allowed = TRUE
range = 2
cooldown_min = 150
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
sparkle_path = /obj/effect/temp_visual/dir_setting/tailsweep
action_icon = 'icons/mob/actions/actions_xeno.dmi'
action_icon_state = "tailsweep"
@@ -338,7 +338,7 @@
charge_max = 60
clothes_req = FALSE
invocation = "FI'RAN DADISKO"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
max_targets = 0
range = 6
include_user = TRUE
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index fcbfaa5b64ec..59347c2eca3c 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -953,12 +953,11 @@
if(generic_bleedstacks > 0)
bleed_rate += generic_bleedstacks
- //We want an accurate reading of .len
- listclearnulls(embedded_objects)
- for(var/obj/item/embeddies in embedded_objects)
- var/obj/item/ammo_casing/AC = embeddies
- if(!(embeddies.taped || (istype(AC) && !AC.harmful)))
- bleed_rate += 0.5
+ if(embedded_objects)
+ //We want an accurate reading of .len
+ listclearnulls(embedded_objects)
+ for(var/obj/item/embeddies in embedded_objects)
+ bleed_rate += embeddies.embedding.embedded_bleed_rate
for(var/thing in wounds)
var/datum/wound/W = thing
diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm
index 594f36c97ca9..d92175d3a763 100644
--- a/code/modules/surgery/bodyparts/helpers.dm
+++ b/code/modules/surgery/bodyparts/helpers.dm
@@ -155,18 +155,10 @@
return disabled
//Remove all embedded objects from all limbs on the carbon mob
-/mob/living/carbon/proc/remove_all_embedded_objects()
+/mob/living/carbon/proc/remove_all_embedded_objects(silent = TRUE, forced = TRUE)
var/turf/T = get_turf(src)
-
- for(var/X in bodyparts)
- var/obj/item/bodypart/L = X
- for(var/obj/item/I in L.embedded_objects)
- remove_embedded_object(I, T, TRUE, TRUE)
- L.embedded_objects -= I
- I.forceMove(T)
-
- clear_alert("embeddedobject")
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
+ for(var/obj/item/I in get_embedded_objects())
+ remove_embedded_object(I, T, silent, forced)
/mob/living/carbon/proc/has_embedded_objects()
. = FALSE
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 9c2d8349f6a2..4f50cb19666f 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -1834,6 +1834,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 8
surplus = 90
+/datum/uplink_item/device_tools/dodge_cloak
+ name = "Shadow Cloak"
+ desc = "A highly advanced cloak that renders the user transparent over time and able to dodge attacks. Moving, \
+ dodging attacks, or suffering an EMP will reduce or remove the transperency temporarily."
+ item = /obj/item/clothing/neck/cloak/ranger/syndie
+ cost = 30
+ include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
+
/datum/uplink_item/device_tools/medkit
name = "Syndicate Combat Medic Kit"
desc = "Included is a combat stimulant injector \
@@ -2748,6 +2756,12 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/gun/energy/pulse/pistol
cost = 35
+/datum/uplink_item/nt/energy_weps/hardlightbow
+ name = "HL-P1 Multipurpose Combat Bow"
+ desc = "An expensive hardlight bow designed by Nanotrasen and often sold to the SIC's espionage branch. Capable of firing disabler, energy, pulse, and taser bolts."
+ item = /obj/item/gun/ballistic/bow/energy/ert
+ cost = 75 //Doesn't need to be recharged but also fires once every now and then instead of being spammable
+
/datum/uplink_item/nt/energy_weps/pulsedestroyer
name = "Pulse Destroyer"
desc = "LOG-ENTRY ERROR. DEATH. DEATH. DEATH. KILL. DESTROY. NONE LEFT ALIVE."
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index 959677559298..646065b7efad 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi
index af4704cf3466..2231212ba414 100644
Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ
diff --git a/icons/mob/clothing/hands/hands.dmi b/icons/mob/clothing/hands/hands.dmi
index b0c223c38e90..7df9a07bde3e 100644
Binary files a/icons/mob/clothing/hands/hands.dmi and b/icons/mob/clothing/hands/hands.dmi differ
diff --git a/icons/mob/clothing/neck/neck.dmi b/icons/mob/clothing/neck/neck.dmi
index 03cf02ce00b5..2a7f063c1363 100644
Binary files a/icons/mob/clothing/neck/neck.dmi and b/icons/mob/clothing/neck/neck.dmi differ
diff --git a/icons/mob/clothing/suit_storage.dmi b/icons/mob/clothing/suit_storage.dmi
index 3c72a4cef8bf..15f92b172537 100644
Binary files a/icons/mob/clothing/suit_storage.dmi and b/icons/mob/clothing/suit_storage.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index d2ccb674dfb3..4de0df4551eb 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 7f8dcfa9debd..0f199867cd15 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi
index 7a1af280b529..73b5bd898c65 100644
Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi
index cc3934830d82..715eb82f9f85 100644
Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi
index 7106cde283e3..170997bc872d 100644
Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi
index b1180468e480..5b0b2d210301 100644
Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/thrown_lefthand.dmi b/icons/mob/inhands/weapons/thrown_lefthand.dmi
index fafcf948988a..5cd9afee1240 100644
Binary files a/icons/mob/inhands/weapons/thrown_lefthand.dmi and b/icons/mob/inhands/weapons/thrown_lefthand.dmi differ
diff --git a/icons/mob/radial.dmi b/icons/mob/radial.dmi
index 6e5c79cc34dc..7339c2167b6b 100644
Binary files a/icons/mob/radial.dmi and b/icons/mob/radial.dmi differ
diff --git a/icons/mob/restraints.dmi b/icons/mob/restraints.dmi
index 6e22ac1d58d1..dfb205c576c3 100644
Binary files a/icons/mob/restraints.dmi and b/icons/mob/restraints.dmi differ
diff --git a/icons/obj/ammo.dmi b/icons/obj/ammo.dmi
index eb1e8d7f8e72..3a466159405e 100644
Binary files a/icons/obj/ammo.dmi and b/icons/obj/ammo.dmi differ
diff --git a/icons/obj/clothing/belt_overlays.dmi b/icons/obj/clothing/belt_overlays.dmi
index 92171e682cd0..9ecdbf6f3511 100644
Binary files a/icons/obj/clothing/belt_overlays.dmi and b/icons/obj/clothing/belt_overlays.dmi differ
diff --git a/icons/obj/clothing/belts.dmi b/icons/obj/clothing/belts.dmi
index e73cea3394bc..c72356a7f679 100644
Binary files a/icons/obj/clothing/belts.dmi and b/icons/obj/clothing/belts.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index 1c1d36c8b1e3..b62ce16c8dd7 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/icons/obj/clothing/neck.dmi b/icons/obj/clothing/neck.dmi
index 1f926e46b79a..66b8023bb322 100644
Binary files a/icons/obj/clothing/neck.dmi and b/icons/obj/clothing/neck.dmi differ
diff --git a/icons/obj/guns/bows.dmi b/icons/obj/guns/bows.dmi
new file mode 100644
index 000000000000..2a8cb564decf
Binary files /dev/null and b/icons/obj/guns/bows.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index 718f3f1d6f3a..2466e92b74e5 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/guns/toy.dmi b/icons/obj/guns/toy.dmi
index 01240245903f..502eae3e1430 100644
Binary files a/icons/obj/guns/toy.dmi and b/icons/obj/guns/toy.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index d34173ce84c1..2850636df4f2 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/icons/obj/stack_medical.dmi b/icons/obj/stack_medical.dmi
index 23fe044a0d4c..60c03bc71f22 100644
Binary files a/icons/obj/stack_medical.dmi and b/icons/obj/stack_medical.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 597d47e2568a..56a6c72b2fd8 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/icons/obj/weapons/swords.dmi b/icons/obj/weapons/swords.dmi
index 32f0134435a4..5462b43ff662 100644
Binary files a/icons/obj/weapons/swords.dmi and b/icons/obj/weapons/swords.dmi differ
diff --git a/yogstation.dme b/yogstation.dme
index 78184059c34c..1dd5e39d88ef 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -108,6 +108,7 @@
#include "code\__DEFINES\spaceman_dmm.dm"
#include "code\__DEFINES\span.dm"
#include "code\__DEFINES\speech_channels.dm"
+#include "code\__DEFINES\spells.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\station.dm"
@@ -1757,6 +1758,7 @@
#include "code\modules\assembly\signaler.dm"
#include "code\modules\assembly\timer.dm"
#include "code\modules\assembly\voice.dm"
+#include "code\modules\assembly\voicebox.dm"
#include "code\modules\asset_cache\asset_cache_client.dm"
#include "code\modules\asset_cache\asset_cache_item.dm"
#include "code\modules\asset_cache\asset_list.dm"
@@ -3013,8 +3015,6 @@
#include "code\modules\projectiles\ammunition\ballistic\smg.dm"
#include "code\modules\projectiles\ammunition\ballistic\sniper.dm"
#include "code\modules\projectiles\ammunition\caseless\_caseless.dm"
-#include "code\modules\projectiles\ammunition\caseless\arrow.dm"
-#include "code\modules\projectiles\ammunition\caseless\foam.dm"
#include "code\modules\projectiles\ammunition\caseless\misc.dm"
#include "code\modules\projectiles\ammunition\caseless\rocket.dm"
#include "code\modules\projectiles\ammunition\energy\_energy.dm"
@@ -3026,6 +3026,9 @@
#include "code\modules\projectiles\ammunition\energy\portal.dm"
#include "code\modules\projectiles\ammunition\energy\special.dm"
#include "code\modules\projectiles\ammunition\energy\stun.dm"
+#include "code\modules\projectiles\ammunition\reusable\_reusable.dm"
+#include "code\modules\projectiles\ammunition\reusable\arrow.dm"
+#include "code\modules\projectiles\ammunition\reusable\foam.dm"
#include "code\modules\projectiles\ammunition\special\magic.dm"
#include "code\modules\projectiles\ammunition\special\syringe.dm"
#include "code\modules\projectiles\attachments\_attachment.dm"
@@ -3317,6 +3320,7 @@
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\aimed.dm"
#include "code\modules\spells\spell_types\area_teleport.dm"
+#include "code\modules\spells\spell_types\arrow.dm"
#include "code\modules\spells\spell_types\barnyard.dm"
#include "code\modules\spells\spell_types\bloodcrawl.dm"
#include "code\modules\spells\spell_types\charge.dm"
diff --git a/yogstation/code/datums/martial/garden_warfare.dm b/yogstation/code/datums/martial/garden_warfare.dm
index 097eb8f9abdf..e27a32129d2d 100644
--- a/yogstation/code/datums/martial/garden_warfare.dm
+++ b/yogstation/code/datums/martial/garden_warfare.dm
@@ -214,11 +214,18 @@
embedding = list("embedded_pain_multiplier" = 3, "embed_chance" = 100, "embedded_fall_chance" = 0)
var/passive_damage = 0.5
-/obj/item/splinter/on_embed_removal(mob/living/carbon/human/embedde)
- qdel(src)
- . = ..()
+/obj/item/splinter/Initialize()
+ ..()
+ RegisterSignal(src, COMSIG_ITEM_EMBED_REMOVAL, PROC_REF(on_embed_removal))
+ RegisterSignal(src, COMSIG_ITEM_EMBED_TICK, PROC_REF(embed_tick))
-/obj/item/splinter/embed_tick(mob/living/carbon/human/embedde, obj/item/bodypart/part)
+/obj/item/splinter/proc/on_embed_removal(mob/living/carbon/human/embedde)
+ return COMSIG_ITEM_QDEL_EMBED_REMOVAL
+
+/obj/item/splinter/proc/embed_tick(mob/living/carbon/human/embedde)
+ var/obj/item/bodypart/part = embedde.get_embedded_part(src)
+ if(!part)
+ return
part.receive_damage(passive_damage, wound_bonus=-30, sharpness = TRUE)
/datum/martial_art/gardern_warfare/handle_counter(mob/living/carbon/human/user, mob/living/carbon/human/attacker)
diff --git a/yogstation/code/modules/guardian/abilities/_ability.dm b/yogstation/code/modules/guardian/abilities/_ability.dm
index 75a8d6f7aaa4..a4facb080e86 100644
--- a/yogstation/code/modules/guardian/abilities/_ability.dm
+++ b/yogstation/code/modules/guardian/abilities/_ability.dm
@@ -3,7 +3,7 @@
human_req = FALSE
clothes_req = FALSE
antimagic_allowed = TRUE
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
/obj/effect/proc_holder/spell/targeted/guardian/proc/Finished()
charge_counter = 0
diff --git a/yogstation/code/modules/guardian/abilities/special/time.dm b/yogstation/code/modules/guardian/abilities/special/time.dm
index 31a6966e815d..b1b6633017ec 100644
--- a/yogstation/code/modules/guardian/abilities/special/time.dm
+++ b/yogstation/code/modules/guardian/abilities/special/time.dm
@@ -10,7 +10,7 @@
guardian.AddSpell(spell)
/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop/guardian
- invocation_type = "none"
+ invocation_type = SPELL_INVOCATION_NONE
clothes_req = FALSE
summon_type = list(/obj/effect/timestop)
diff --git a/yogstation/code/modules/projectiles/ammunition/caseless/misc.dm b/yogstation/code/modules/projectiles/ammunition/caseless/misc.dm
index 43c9854d7464..faa3d1e18b5c 100644
--- a/yogstation/code/modules/projectiles/ammunition/caseless/misc.dm
+++ b/yogstation/code/modules/projectiles/ammunition/caseless/misc.dm
@@ -1,4 +1,4 @@
-/obj/item/ammo_casing/caseless/kineticspear
+/obj/item/ammo_casing/reusable/kineticspear
name = "kinetic spear"
desc = "A specialized spear rigged to deliver a weak kinetic blast on contact with fauna."
projectile_type = /obj/item/projectile/bullet/reusable/kineticspear
@@ -12,5 +12,5 @@
/obj/item/storage/pod/PopulateContents()
. = ..()
- new /obj/item/ammo_casing/caseless/kineticspear(src)
- new /obj/item/ammo_casing/caseless/kineticspear(src)
+ new /obj/item/ammo_casing/reusable/kineticspear(src)
+ new /obj/item/ammo_casing/reusable/kineticspear(src)
diff --git a/yogstation/code/modules/projectiles/boxes_magazines/internal/misc.dm b/yogstation/code/modules/projectiles/boxes_magazines/internal/misc.dm
index 8a1ae4886022..83cb6e133fc2 100644
--- a/yogstation/code/modules/projectiles/boxes_magazines/internal/misc.dm
+++ b/yogstation/code/modules/projectiles/boxes_magazines/internal/misc.dm
@@ -1,5 +1,5 @@
/obj/item/ammo_box/magazine/internal/speargun
name = "speargun internal magazine"
- ammo_type = /obj/item/ammo_casing/caseless/kineticspear
+ ammo_type = /obj/item/ammo_casing/reusable/kineticspear
caliber = "speargun"
max_ammo = 1
diff --git a/yogstation/code/modules/projectiles/reusable/kineticspear.dm b/yogstation/code/modules/projectiles/reusable/kineticspear.dm
index 8f29be719878..732aa3c2585a 100644
--- a/yogstation/code/modules/projectiles/reusable/kineticspear.dm
+++ b/yogstation/code/modules/projectiles/reusable/kineticspear.dm
@@ -6,7 +6,6 @@
var/fauna_damage_bonus = 20
icon = 'yogstation/icons/obj/ammo.dmi'
icon_state = "kineticspear"
- ammo_type = /obj/item/ammo_casing/caseless/kineticspear
hitsound = 'sound/weapons/bladeslice.ogg'
/obj/item/projectile/bullet/reusable/kineticspear/on_hit(atom/target, blocked = FALSE)
diff --git a/yogstation/code/modules/spells/cauterize.dm b/yogstation/code/modules/spells/cauterize.dm
index af0f87cd0707..70790c0628b4 100644
--- a/yogstation/code/modules/spells/cauterize.dm
+++ b/yogstation/code/modules/spells/cauterize.dm
@@ -9,7 +9,7 @@
var/cauterize_duration = 20 //in seconds
include_user = TRUE
invocation = "AMOS INO!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
action_icon = 'yogstation/icons/mob/actions.dmi'
action_icon_state = "cauterize"
diff --git a/yogstation/code/modules/spells/cluwnecurse.dm b/yogstation/code/modules/spells/cluwnecurse.dm
index f65bcdcfa7dd..338ce66b6bfb 100644
--- a/yogstation/code/modules/spells/cluwnecurse.dm
+++ b/yogstation/code/modules/spells/cluwnecurse.dm
@@ -2,13 +2,12 @@
name = "Curse of the Cluwne"
desc = "This spell dooms the fate of any unlucky soul to the live of a pitiful cluwne, a terrible creature that is hunted for fun."
school = "transmutation"
- charge_type = "recharge"
charge_max = 600
charge_counter = 0
clothes_req = 1
stat_allowed = 0
invocation = "CLU WO'NIS CA'TE'BEST'IS MAXIMUS!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 3
cooldown_min = 75
selection_type = "range"
@@ -40,4 +39,4 @@
/datum/action/spell_action/New(Target)
..()
var/obj/effect/proc_holder/spell/S = Target
- icon_icon = S.action_icon
\ No newline at end of file
+ icon_icon = S.action_icon
diff --git a/yogstation/code/modules/spells/slip.dm b/yogstation/code/modules/spells/slip.dm
index 23002a29139a..16409effc7cd 100644
--- a/yogstation/code/modules/spells/slip.dm
+++ b/yogstation/code/modules/spells/slip.dm
@@ -7,7 +7,7 @@
cooldown_min = 100 //50 deciseconds reduction per level
range = 3
invocation = "OO'BANAN'A!"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
action_icon = 'yogstation/icons/mob/actions.dmi'
action_icon_state = "slip"
diff --git a/yogstation/code/modules/spells/spell_types/aimed.dm b/yogstation/code/modules/spells/spell_types/aimed.dm
index aef2897a47bd..d2ed0db76691 100644
--- a/yogstation/code/modules/spells/spell_types/aimed.dm
+++ b/yogstation/code/modules/spells/spell_types/aimed.dm
@@ -4,7 +4,7 @@
charge_max = 60
clothes_req = FALSE
invocation = "ONA ANIMATUS"
- invocation_type = "shout"
+ invocation_type = SPELL_INVOCATION_SAY
range = 20
cooldown_min = 20 //10 deciseconds reduction per rank
projectile_type = /obj/item/projectile/magic/animate
@@ -13,4 +13,4 @@
sound = 'sound/magic/staff_animation.ogg'
active_msg = "You prepare to cast your animation spell!"
deactive_msg = "You stop casting your animation spell... for now."
- active = FALSE
\ No newline at end of file
+ active = FALSE