Gives non-standard nullrods the rest of the features common to regular null rods (#94980)

## About The Pull Request

Non-standard null-rods (the skateboard, bow, revolver, carpsie plush,
and eswords) did not receive all the common characteristics of regular
null rods in #93394. In particular, they were not given the
`UNIQUE_RENAME` obj flag, and they did not receive the ability to track
how many cultists they've crit/killed and be turned into cult weapons
when sacrificed. This PR corrects that by extracting the cult kill
tracking behavior to a component, while doing the following refactors to
ensure complete compatibility:

- Skateboard items now move themselves into the skateboard vehicle they
spawn on use, instead of deleting themselves. This was necessary for the
holy board to keep its custom name/desc and cultist kill count.
- Guns (which the bow technically is) will track cultists killed/crit
with bullets fired from them by a mob with a holy role.
- A signal can now be used for an item to provide an arbitrary response
when an offer rune underneath it is activated.

## Why It's Good For The Game

I just wanted the holy eswords to be renameable for chaplains to provide
plausible deniability for the possession of actual eswords, but after I
discovered this consistency issue, and felt the need to fix it.

## Changelog

🆑
fix: The holy energy swords and the carp-sie plushie can once again be
renamed
fix: The holy energy swords and the carp-sie plushie can once again be
sacrificed by cultists to spawn different weapons based on how many
unique cultists the chaplain has crit or killed with them
qol: The holy skateboard can be renamed like other null rod variants can
fix: Unusual null rod variants like the holy skateboard and bow can now
be sacrificed by cultists to spawn different weapons based on how many
unique cultists the chaplain has crit or killed with them. The bow and
the burdened chaplain's revolver, in particular, also count cultists
crit or killed by arrows/bullets fired from them by the chaplain.
/🆑
This commit is contained in:
Y0SH1M4S73R
2026-02-03 22:23:20 -05:00
committed by GitHub
parent f58b8511f0
commit 6f37db300b
12 changed files with 138 additions and 88 deletions
@@ -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)
@@ -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. \
<b>Offering</b> 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
+1
View File
@@ -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))
+4 -3
View File
@@ -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
@@ -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
+23 -42
View File
@@ -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"
@@ -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. \
<b>Offering</b> 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
@@ -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")
@@ -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
+1 -19
View File
@@ -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"
+8 -2
View File
@@ -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
+1
View File
@@ -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"