diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 2298976d955..d666a93f0d6 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -429,6 +429,10 @@
#define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out"
///from the base of /obj/projectile/process(): ()
#define COMSIG_PROJECTILE_BEFORE_MOVE "projectile_before_move"
+///sent to firer at the end of /mob/living/apply_projectile_effects(): (mob/living/target, hit_limb, blocked)
+#define COMSIG_PROJECTILE_POST_HIT_LIVING "projectile_post_hit_living"
+///sent to projectile at the end of /mob/living/apply_projectile_effects(): (mob/living/target, hit_limb, blocked)
+#define COMSIG_PROJECTILE_SELF_POST_HIT_LIVING "projectile_post_hit_living"
// FROM [/obj/item/proc/set_embed] sent when an item's embedding properties are changed : ()
#define COMSIG_ITEM_EMBEDDING_UPDATE "item_embedding_update"
@@ -634,3 +638,7 @@
/// Sent from /obj/item/mob_holder/purple_raptor/proc/toggle_wings() : (mob/living/carbon/human/user)
#define COMSIG_RAPTOR_WINGS_CLOSED "raptor_wings_closed"
+
+/// Sent from /obj/effect/rune/convert/try_sacrifice_item(obj/effect/rune/convert/rune)
+#define COMSIG_ITEM_CULT_SACRIFICE "item_cult_sacrifice"
+ #define COMPONENT_SACRIFICE_SUCCESSFUL (1<<0)
diff --git a/code/datums/components/cult_kill_tracker.dm b/code/datums/components/cult_kill_tracker.dm
new file mode 100644
index 00000000000..00023ab23e3
--- /dev/null
+++ b/code/datums/components/cult_kill_tracker.dm
@@ -0,0 +1,85 @@
+/// Component to handle the behavior of a nullrod keeping track of cultists it has crit or killed, and converting the item into a cult weapon when sacrificed
+/datum/component/cult_kill_tracker
+ /// Lazylist, tracks weakrefs()s to all cultists which have been crit or killed by this nullrod.
+ var/list/cultists_slain
+ /// Ref to the last mob hit with this rod.
+ var/last_ref
+ /// The stat of the target being hit with this rod, before actually performing damage calculations.
+ var/last_stat = DEAD
+
+/datum/component/cult_kill_tracker/Initialize(...)
+ if(!istype(parent, /obj/item))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/cult_kill_tracker/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(on_attack_zone))
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(post_hit))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_ITEM_CULT_SACRIFICE, PROC_REF(on_sacrificed))
+ if(isgun(parent))
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_projectile_hit))
+ RegisterSignal(parent, COMSIG_PROJECTILE_POST_HIT_LIVING, PROC_REF(post_hit))
+
+/datum/component/cult_kill_tracker/UnregisterFromParent()
+ . = ..()
+ UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_ZONE, COMSIG_ITEM_AFTERATTACK, COMSIG_ATOM_EXAMINE, COMSIG_ITEM_CULT_SACRIFICE, COMSIG_PROJECTILE_ON_HIT, COMSIG_PROJECTILE_POST_HIT_LIVING))
+
+/datum/component/cult_kill_tracker/proc/on_attack_zone(obj/item/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER
+ if(!user.mind?.holy_role)
+ return
+ if(!IS_CULTIST(target) || istype(target, /mob/living/carbon/human/cult_ghost))
+ return
+ last_ref = WEAKREF(target)
+ last_stat = target.stat
+
+/datum/component/cult_kill_tracker/proc/post_hit(source, mob/living/target)
+ SIGNAL_HANDLER
+ if(!last_ref)
+ return
+ var/mob/living/resolved_mob = locate(last_ref)
+ //If they got deleted during the processing of the attack, they're probably fucking dead.
+ if(!istype(resolved_mob) || (resolved_mob == target && resolved_mob.stat > last_stat))
+ LAZYOR(cultists_slain, last_ref)
+ last_ref = null
+ last_stat = DEAD
+
+/datum/component/cult_kill_tracker/proc/on_examine(obj/item/source, mob/viewer, list/examine_list)
+ SIGNAL_HANDLER
+ if(!IS_CULTIST(viewer) || !GET_ATOM_BLOOD_DNA_LENGTH(source))
+ return
+
+ var/num_slain = LAZYLEN(cultists_slain)
+ examine_list += span_cult_italic("It has the blood of [num_slain] fallen cultist[num_slain == 1 ? "" : "s"] on it. \
+ Offering it to Nar'sie will transform it into a [num_slain >= 3 ? "powerful" : "standard"] cult weapon.")
+
+/datum/component/cult_kill_tracker/proc/on_sacrificed(obj/item/source, obj/effect/rune/convert/rune)
+ SIGNAL_HANDLER
+ var/num_slain = LAZYLEN(cultists_slain)
+ var/displayed_message = "[source] glows an unholy red and begins to transform..."
+ if(num_slain && GET_ATOM_BLOOD_DNA_LENGTH(source))
+ displayed_message += " The blood of [num_slain] fallen cultist[num_slain == 1 ? "":"s"] is absorbed into [source]!"
+
+ source.visible_message(span_cult_italic(displayed_message))
+ switch(num_slain)
+ if(0)
+ rune.animate_convert_item(source, /obj/item/melee/cultblade/dagger)
+ if(1)
+ rune.animate_convert_item(source, /obj/item/melee/cultblade)
+ else
+ rune.animate_convert_item(source, /obj/item/melee/cultblade/halberd)
+ return COMPONENT_SACRIFICE_SUCCESSFUL
+
+/datum/component/cult_kill_tracker/proc/on_projectile_hit(obj/projectile/source, atom/movable/firer, atom/target)
+ SIGNAL_HANDLER
+ if(!isliving(firer) || !isliving(target))
+ return
+ var/mob/living/firer_mob = firer
+ var/mob/living/target_mob = target
+ if(!firer_mob.mind.holy_role)
+ return
+ if(!IS_CULTIST(target_mob) || istype(target_mob, /mob/living/carbon/human/cult_ghost))
+ return
+ last_ref = WEAKREF(target_mob)
+ last_stat = target_mob.stat
diff --git a/code/datums/elements/nullrod_core.dm b/code/datums/elements/nullrod_core.dm
index 020874777d5..fa48e421f8e 100644
--- a/code/datums/elements/nullrod_core.dm
+++ b/code/datums/elements/nullrod_core.dm
@@ -18,6 +18,7 @@
on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed), target), \
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \
)
+ target.AddComponent(/datum/component/cult_kill_tracker)
target.AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
ADD_TRAIT(target, TRAIT_NULLROD_ITEM, ELEMENT_TRAIT(type))
diff --git a/code/game/objects/items/skateboards.dm b/code/game/objects/items/skateboards.dm
index 0d860ef11f9..9f18d42ebe0 100644
--- a/code/game/objects/items/skateboards.dm
+++ b/code/game/objects/items/skateboards.dm
@@ -15,9 +15,9 @@
var/board_item_type = /obj/vehicle/ridden/scooter/skateboard
/obj/item/melee/skateboard/attack_self(mob/user)
- var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasn't my fault
- S.buckle_mob(user)
- qdel(src)
+ var/obj/vehicle/ridden/scooter/skateboard/board = new board_item_type(get_turf(user), src)//this probably has fucky interactions with telekinesis but for the record it wasn't my fault
+ board.buckle_mob(user)
+ forceMove(board)
/obj/item/melee/skateboard/improvised
name = "improvised skateboard"
@@ -55,6 +55,7 @@
force = 18
throwforce = 6
w_class = WEIGHT_CLASS_NORMAL
+ obj_flags = parent_type::obj_flags | UNIQUE_RENAME
attack_verb_continuous = list("bashes", "crashes", "grinds", "skates")
attack_verb_simple = list("bash", "crash", "grind", "skate")
board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard/holyboarded
diff --git a/code/game/objects/items/weaponry/melee/energy.dm b/code/game/objects/items/weaponry/melee/energy.dm
index 0c6ed38d674..ca7f75e93a9 100644
--- a/code/game/objects/items/weaponry/melee/energy.dm
+++ b/code/game/objects/items/weaponry/melee/energy.dm
@@ -556,6 +556,7 @@
armour_penetration = 0
wound_bonus = -10
demolition_mod = 1
+ obj_flags = parent_type::obj_flags | UNIQUE_RENAME
sword_color_icon = "blue"
light_color = LIGHT_COLOR_LIGHT_CYAN
active_force = 18
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index fbadeba3518..be3a5b92d2d 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -242,7 +242,7 @@ structure_check() searches for nearby cultist structures required for the invoca
if(!IS_CULTIST(non_cultist))
myriad_targets += non_cultist
- if(!length(myriad_targets) && !try_spawn_sword())
+ if(!length(myriad_targets) && !try_sacrifice_item())
fail_invoke()
return
@@ -400,57 +400,38 @@ structure_check() searches for nearby cultist structures required for the invoca
sacrificial.investigate_log("has been sacrificially gibbed by the cult.", INVESTIGATE_DEATHS)
sacrificial.gib(DROP_ALL_REMAINS)
- try_spawn_sword() // after sharding and gibbing, which potentially dropped a null rod
+ try_sacrifice_item() // after sharding and gibbing, which potentially dropped a sacrificable item
return TRUE
-/// Tries to convert a null rod over the rune to a cult sword
-/obj/effect/rune/convert/proc/try_spawn_sword()
- for(var/obj/item/potential_rod in loc)
- if(!HAS_TRAIT(potential_rod, TRAIT_NULLROD_ITEM))
+/// Tries to convert a valid item over the rune to something else
+/obj/effect/rune/convert/proc/try_sacrifice_item()
+ for(var/obj/item/checked_item in loc)
+ if(checked_item.anchored || (checked_item.resistance_flags & INDESTRUCTIBLE))
continue
- if(potential_rod.anchored || (potential_rod.resistance_flags & INDESTRUCTIBLE))
- continue
-
- var/num_slain = 0
- if (istype(potential_rod, /obj/item/nullrod))
- var/obj/item/nullrod/actual_rod = potential_rod
- num_slain = LAZYLEN(actual_rod.cultists_slain)
-
- var/displayed_message = "[potential_rod] glows an unholy red and begins to transform..."
- if(num_slain && GET_ATOM_BLOOD_DNA_LENGTH(potential_rod))
- displayed_message += " The blood of [num_slain] fallen cultist[num_slain == 1 ? "":"s"] is absorbed into [potential_rod]!"
-
- potential_rod.visible_message(span_cult_italic(displayed_message))
- switch(num_slain)
- if(0)
- animate_spawn_sword(potential_rod, /obj/item/melee/cultblade/dagger)
- if(1)
- animate_spawn_sword(potential_rod, /obj/item/melee/cultblade)
- else
- animate_spawn_sword(potential_rod, /obj/item/melee/cultblade/halberd)
- return TRUE
+ if(SEND_SIGNAL(checked_item, COMSIG_ITEM_CULT_SACRIFICE, src) & COMPONENT_SACRIFICE_SUCCESSFUL)
+ return TRUE
return FALSE
-/// Does an animation of a null rod transforming into a cult sword
-/obj/effect/rune/convert/proc/animate_spawn_sword(obj/item/former_rod, new_blade_typepath)
+/// Does an animation of a sacrificable item transforming into something else
+/obj/effect/rune/convert/proc/animate_convert_item(obj/item/old_item, new_movable_typepath)
playsound(src, 'sound/effects/magic.ogg', 33, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, frequency = 0.66)
- former_rod.anchored = TRUE
- former_rod.Shake()
- animate(former_rod, alpha = 0, transform = matrix(former_rod.transform).Scale(0.01), time = 2 SECONDS, easing = BOUNCE_EASING, flags = ANIMATION_PARALLEL)
- QDEL_IN(former_rod, 2 SECONDS)
+ old_item.anchored = TRUE
+ old_item.Shake()
+ animate(old_item, alpha = 0, transform = matrix(old_item.transform).Scale(0.01), time = 2 SECONDS, easing = BOUNCE_EASING, flags = ANIMATION_PARALLEL)
+ QDEL_IN(old_item, 2 SECONDS)
- var/obj/item/new_blade = new new_blade_typepath(loc)
- var/matrix/blade_matrix_on_spawn = matrix(new_blade.transform)
- new_blade.name = "converted [new_blade.name]"
- new_blade.anchored = TRUE
- new_blade.alpha = 0
- new_blade.transform = matrix(new_blade.transform).Scale(0.01)
- new_blade.Shake()
- animate(new_blade, alpha = 255, transform = blade_matrix_on_spawn, time = 2 SECONDS, easing = BOUNCE_EASING, flags = ANIMATION_PARALLEL)
- addtimer(VARSET_CALLBACK(new_blade, anchored, FALSE), 2 SECONDS)
+ var/atom/movable/new_movable = new new_movable_typepath(loc)
+ var/matrix/matrix_on_spawn = matrix(new_movable.transform)
+ new_movable.name = "converted [new_movable.name]"
+ new_movable.anchored = TRUE
+ new_movable.alpha = 0
+ new_movable.transform = matrix(new_movable.transform).Scale(0.01)
+ new_movable.Shake()
+ animate(new_movable, alpha = 255, transform = matrix_on_spawn, time = 2 SECONDS, easing = BOUNCE_EASING, flags = ANIMATION_PARALLEL)
+ addtimer(VARSET_CALLBACK(new_movable, anchored, FALSE), 2 SECONDS)
/obj/effect/rune/empower
cultist_name = "Empower"
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
index becd993f5e8..5e67c8de35b 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
@@ -47,8 +47,6 @@ GLOBAL_LIST_INIT(nullrod_variants, init_nullrod_variants())
var/chaplain_spawnable = TRUE
/// Short description of what this item is capable of, for radial menu uses.
var/menu_description = "A standard chaplain's weapon. Fits in pockets. Can be worn on the belt."
- /// Lazylist, tracks refs()s to all cultists which have been crit or killed by this nullrod.
- var/list/cultists_slain
/// Affects GLOB.holy_weapon_type. Disable to allow null rods to change at will and without affecting the station's type.
var/station_holy_item = TRUE
@@ -72,26 +70,6 @@ GLOBAL_LIST_INIT(nullrod_variants, init_nullrod_variants())
user.visible_message(span_suicide("[user] is killing [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to get closer to god!"))
return (BRUTELOSS|FIRELOSS)
-/obj/item/nullrod/attack(mob/living/target_mob, mob/living/user, list/modifiers, list/attack_modifiers)
- if(!user.mind?.holy_role)
- return ..()
- if(!IS_CULTIST(target_mob) || istype(target_mob, /mob/living/carbon/human/cult_ghost))
- return ..()
-
- var/old_stat = target_mob.stat
- . = ..()
- if(old_stat < target_mob.stat)
- LAZYOR(cultists_slain, REF(target_mob))
- return .
-
-/obj/item/nullrod/examine(mob/user)
- . = ..()
- if(!IS_CULTIST(user) || !GET_ATOM_BLOOD_DNA_LENGTH(src))
- return
-
- var/num_slain = LAZYLEN(cultists_slain)
- . += span_cult_italic("It has the blood of [num_slain] fallen cultist[num_slain == 1 ? "" : "s"] on it. \
- Offering it to Nar'sie will transform it into a [num_slain >= 3 ? "powerful" : "standard"] cult weapon.")
/obj/item/nullrod/non_station
station_holy_item = FALSE
@@ -613,6 +591,7 @@ GLOBAL_LIST_INIT(nullrod_variants, init_nullrod_variants())
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
force = 15
+ obj_flags = parent_type::obj_flags | UNIQUE_RENAME
offspring_type = /obj/item/toy/plush/carpplushie
divine = TRUE
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm
index f2c7ed53d40..91823a073c8 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm
@@ -45,6 +45,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and
armour_penetration = 50 //Very good armor penetration to make up for our abysmal force
reach = 2 //why yes, this does have reach
slot_flags = null
+ obj_flags = UNIQUE_RENAME
sharpness = SHARP_EDGED
attack_verb_continuous = list("chops", "slices", "cuts", "reaps")
attack_verb_simple = list("chop", "slice", "cut", "reap")
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 884fbe51d8e..68bcf1fee29 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -179,6 +179,10 @@
if (proj.damage && armor_check < 100)
create_projectile_hit_effects(proj, def_zone, armor_check)
+ if(proj.fired_from)
+ SEND_SIGNAL(proj.fired_from, COMSIG_PROJECTILE_POST_HIT_LIVING, src, def_zone, armor_check)
+ SEND_SIGNAL(proj, COMSIG_PROJECTILE_SELF_POST_HIT_LIVING, src, def_zone, armor_check)
+
/mob/living/proc/create_projectile_hit_effects(obj/projectile/proj, def_zone, blocked)
if (proj.damage_type != BRUTE)
return
diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm
index 1e4ac046762..5d1cc61f92c 100644
--- a/code/modules/religion/burdened/psyker.dm
+++ b/code/modules/religion/burdened/psyker.dm
@@ -181,28 +181,10 @@
/obj/item/gun/ballistic/revolver/chaplain/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/anti_magic, MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)
- AddComponent(/datum/component/effect_remover, \
- success_feedback = "You disrupt the magic of %THEEFFECT with %THEWEAPON.", \
- success_forcesay = "BEGONE FOUL MAGIKS!!", \
- tip_text = "Clear rune", \
- on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \
- effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \
- )
- AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25)
+ AddElement(/datum/element/nullrod_core, FALSE)
name = pick(possible_names)
desc = possible_names[name]
-/obj/item/gun/ballistic/revolver/chaplain/proc/on_cult_rune_removed(obj/effect/target, mob/living/user)
- SIGNAL_HANDLER
- if(!istype(target, /obj/effect/rune))
- return
-
- var/obj/effect/rune/target_rune = target
- if(target_rune.log_when_erased)
- user.log_message("erased [target_rune.cultist_name] rune using [src]", LOG_GAME)
- SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_NARNAR] = TRUE
-
/obj/item/gun/ballistic/revolver/chaplain/suicide_act(mob/living/user)
. = ..()
name = "Habemus Papam"
diff --git a/code/modules/vehicles/scooter.dm b/code/modules/vehicles/scooter.dm
index 3e1b6cf86cb..16d084cf518 100644
--- a/code/modules/vehicles/scooter.dm
+++ b/code/modules/vehicles/scooter.dm
@@ -53,11 +53,17 @@
var/instability = 10
///If true, riding the skateboard with walk intent on will prevent crashing.
var/can_slow_down = TRUE
+ ///The actual item for the skateboard
+ var/obj/item/melee/skateboard/board_item
-/obj/vehicle/ridden/scooter/skateboard/Initialize(mapload)
+/obj/vehicle/ridden/scooter/skateboard/Initialize(mapload, obj/item/melee/skateboard/board_item)
. = ..()
sparks = new(src, 1, FALSE)
sparks.attach(src)
+ if(!istype(board_item))
+ src.board_item = new board_item_type(src)
+ else
+ src.board_item = board_item
/obj/vehicle/ridden/scooter/skateboard/make_ridable()
AddElement(/datum/element/ridable, /datum/component/riding/vehicle/scooter/skateboard)
@@ -174,7 +180,7 @@
if(has_buckled_mobs())
to_chat(skater, span_warning("You can't lift this up when somebody's on it."))
return
- skater.put_in_hands(new board_item_type(get_turf(skater)))
+ skater.put_in_hands(board_item)
qdel(src)
/obj/vehicle/ridden/scooter/skateboard/pro
diff --git a/tgstation.dme b/tgstation.dme
index fa648bd841c..39baa1f4c1a 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1152,6 +1152,7 @@
#include "code\datums\components\crank_recharge.dm"
#include "code\datums\components\crate_carrier.dm"
#include "code\datums\components\cuff_n_stun.dm"
+#include "code\datums\components\cult_kill_tracker.dm"
#include "code\datums\components\cult_ritual_item.dm"
#include "code\datums\components\curse_of_hunger.dm"
#include "code\datums\components\curse_of_polymorph.dm"