Fixes cursed duffelbag's permanent curse (again), unit tests it. (#71969)

## About The Pull Request

Curse of hunger did some funky stuff by checking for
`slot_equipment_priority` (which ONLY BUCKETS use) and registering
certain signals based on that

The signals they were using instead didn't pass the unequipper, so the
curse never got removed on unequip.

Replaced them with just equip and drop, as equipped and dropped work
just fine for it.

Unit tests this.

## Why It's Good For The Game

Infinite curse of clumsy and pacifism is kinda bad

## Changelog

🆑 Melbert
fix: Dufflebag Curse no longer lasts forever after the bag is destroyed.
fix: Dufflebag Cursing someone already afflicted properly doesn't try to
add the curse again
/🆑
This commit is contained in:
MrMelbert
2022-12-14 01:57:58 -06:00
committed by GitHub
parent 23bb78ceb8
commit 7b10eb45bc
7 changed files with 43 additions and 40 deletions
-3
View File
@@ -337,8 +337,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_CAN_SIGN_ON_COMMS "can_sign_on_comms"
/// nobody can use martial arts on this mob
#define TRAIT_MARTIAL_ARTS_IMMUNE "martial_arts_immune"
/// You've been cursed with a living duffelbag, and can't have more added
#define TRAIT_DUFFEL_CURSE_PROOF "duffel_cursed"
/// Immune to being afflicted by time stop (spell)
#define TRAIT_TIME_STOP_IMMUNE "time_stop_immune"
/// Revenants draining you only get a very small benefit.
@@ -1011,4 +1009,3 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define SPEAKING_FROM_TONGUE "tongue"
///trait source that sign language should use
#define SPEAKING_FROM_HANDS "hands"
+22 -31
View File
@@ -32,16 +32,15 @@
. = ..()
var/obj/item/cursed_item = parent
RegisterSignal(cursed_item, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
//checking slot_equipment_priority is the better way to decide if it should be an equip-curse (alternative being if it has slot_flags)
//because it needs to know where to equip to (and stuff like buckets and cones can be on_pickup curses despite having slots to equip to)
if(cursed_item.slot_equipment_priority)
RegisterSignal(cursed_item, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
else
RegisterSignal(cursed_item, COMSIG_ITEM_PICKUP, PROC_REF(on_pickup))
RegisterSignal(cursed_item, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
/datum/component/curse_of_hunger/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, list(COMSIG_PARENT_EXAMINE, COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_POST_UNEQUIP, COMSIG_ITEM_PICKUP, COMSIG_ITEM_DROPPED))
UnregisterSignal(parent, list(
COMSIG_PARENT_EXAMINE,
COMSIG_ITEM_EQUIPPED,
COMSIG_ITEM_DROPPED,
))
///signal called on parent being examined
/datum/component/curse_of_hunger/proc/on_examine(datum/source, mob/user, list/examine_list)
@@ -57,23 +56,15 @@
/datum/component/curse_of_hunger/proc/on_equip(datum/source, mob/equipper, slot)
SIGNAL_HANDLER
var/obj/item/at_least_item = parent
if(!(at_least_item.slot_flags & slot))
// Items with no slot flags curse on pickup (because hand slot)
if(at_least_item.slot_flags && !(at_least_item.slot_flags & slot))
return
the_curse_begins(equipper)
///signal called from a successful unequip of parent
/datum/component/curse_of_hunger/proc/on_unequip(mob/living/unequipper, force, atom/newloc, no_move, invdrop, silent)
SIGNAL_HANDLER
the_curse_ends(unequipper)
///signal called from picking up parent
/datum/component/curse_of_hunger/proc/on_pickup(datum/source, mob/grabber)
SIGNAL_HANDLER
the_curse_begins(grabber)
///signal called from dropping parent
/datum/component/curse_of_hunger/proc/on_drop(datum/source, mob/dropper)
SIGNAL_HANDLER
the_curse_ends(dropper)
/datum/component/curse_of_hunger/proc/the_curse_begins(mob/cursed)
@@ -85,27 +76,24 @@
ADD_TRAIT(cursed, TRAIT_PACIFISM, CURSED_ITEM_TRAIT(cursed_item.type))
if(add_dropdel)
cursed_item.item_flags |= DROPDEL
return
if(cursed_item.slot_equipment_priority)
RegisterSignal(cursed_item, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_unequip))
else
RegisterSignal(cursed_item, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
RegisterSignal(cursed_item, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
/datum/component/curse_of_hunger/proc/the_curse_ends(mob/uncursed)
var/obj/item/at_least_item = parent
var/obj/item/cursed_item = parent
STOP_PROCESSING(SSobj, src)
REMOVE_TRAIT(parent, TRAIT_NODROP, CURSED_ITEM_TRAIT(parent.type))
REMOVE_TRAIT(uncursed, TRAIT_CLUMSY, CURSED_ITEM_TRAIT(at_least_item.type))
REMOVE_TRAIT(uncursed, TRAIT_PACIFISM, CURSED_ITEM_TRAIT(at_least_item.type))
REMOVE_TRAIT(cursed_item, TRAIT_NODROP, CURSED_ITEM_TRAIT(cursed_item.type))
REMOVE_TRAIT(uncursed, TRAIT_CLUMSY, CURSED_ITEM_TRAIT(cursed_item.type))
REMOVE_TRAIT(uncursed, TRAIT_PACIFISM, CURSED_ITEM_TRAIT(cursed_item.type))
//remove either one of the signals that could have called this proc
UnregisterSignal(parent, list(COMSIG_ITEM_POST_UNEQUIP, COMSIG_ITEM_DROPPED))
UnregisterSignal(cursed_item, COMSIG_ITEM_DROPPED)
var/turf/vomit_turf = get_turf(at_least_item)
var/turf/vomit_turf = get_turf(cursed_item)
playsound(vomit_turf, 'sound/effects/splat.ogg', 50, TRUE)
new /obj/effect/decal/cleanable/vomit(vomit_turf)
uncursed.dropItemToGround(at_least_item, force = TRUE)
if(!QDELETED(at_least_item)) //gives a head start for the person to get away from the cursed item before it begins hunting again!
uncursed.dropItemToGround(cursed_item, force = TRUE)
if(!QDELING(cursed_item)) //gives a head start for the person to get away from the cursed item before it begins hunting again!
addtimer(CALLBACK(src, PROC_REF(seek_new_target)), 10 SECONDS)
///proc called after a timer to awaken the AI in the cursed item if it doesn't have a target already.
@@ -166,3 +154,6 @@
cursed.visible_message(span_danger("[cursed_item] bites [cursed]!"), span_userdanger("[cursed_item] bites you to sate [cursed_item.p_their()] hunger!"))
cursed.apply_damage(60, BRUTE, BODY_ZONE_CHEST, wound_bonus = -20, bare_wound_bonus = 20)
current_health = min(current_health + 1, max_health)
#undef HUNGER_THRESHOLD_WARNING
#undef HUNGER_THRESHOLD_TRY_EATING
+1 -2
View File
@@ -203,8 +203,7 @@
var/filter_color = "#8a0c0ca1" //clarified args
var/new_name = pick(", eternally hungry", " of the glutton", " cursed with hunger", ", consumer of all", " of the feast")
master.AddElement(/datum/element/curse_announcement, "[master] is cursed with the curse of hunger!", filter_color, new_name, comp)
var/add_dropdel = FALSE //clarified boolean
comp.appliedComponents += master.AddComponent(/datum/component/curse_of_hunger, add_dropdel)
comp.appliedComponents += master.AddComponent(/datum/component/curse_of_hunger)
return newName //no spoilers!
/datum/fantasy_affix/curse_of_hunger/remove(datum/component/fantasy/comp)
@@ -361,7 +361,6 @@
icon_state = "duffel-curse"
inhand_icon_state = "duffel-curse"
slowdown = 2
item_flags = DROPDEL
max_integrity = 100
/obj/item/storage/backpack/duffelbag/cursed/Initialize(mapload)
@@ -41,7 +41,7 @@
victim.Knockdown(5 SECONDS)
// If someone's already cursed, don't try to give them another
if(HAS_TRAIT(victim, TRAIT_DUFFEL_CURSE_PROOF))
if(istype(victim.back, /obj/item/storage/backpack/duffelbag/cursed))
to_chat(caster, span_warning("The burden of [victim]'s duffel bag becomes too much, shoving them to the floor!"))
to_chat(victim, span_warning("The weight of this bag becomes overburdening!"))
return TRUE
@@ -53,8 +53,6 @@
span_danger("You feel something attaching itself to you, and a strong desire to discuss your [pick(elaborate_backstory)] at length!"),
)
// This duffelbag is now cuuuurrrsseed! Equip it on them
ADD_TRAIT(conjured_duffel, TRAIT_DUFFEL_CURSE_PROOF, CURSED_ITEM_TRAIT(conjured_duffel.name))
conjured_duffel.pickup(victim)
conjured_duffel.forceMove(victim)
+1
View File
@@ -116,6 +116,7 @@
#include "heretic_rituals.dm"
#include "holidays.dm"
#include "human_through_recycler.dm"
#include "hunger_curse.dm"
#include "hydroponics_extractor_storage.dm"
#include "hydroponics_harvest.dm"
#include "hydroponics_self_mutations.dm"
+18
View File
@@ -0,0 +1,18 @@
/// Tests that the curse of hunger, from [/datum/component/curse_of_hunger],
/// is properly added when equipped to the correct slot and removed when dropped / deleted
/datum/unit_test/hunger_curse
/datum/unit_test/hunger_curse/Run()
var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent)
var/obj/item/storage/backpack/duffelbag/cursed/cursed_bag = allocate(/obj/item/storage/backpack/duffelbag/cursed)
dummy.equip_to_appropriate_slot(cursed_bag)
TEST_ASSERT(HAS_TRAIT(cursed_bag, TRAIT_NODROP), "The cursed bag wasn't NODROP after being cursed!")
TEST_ASSERT(HAS_TRAIT(dummy, TRAIT_CLUMSY), "Dummy wasn't clumsy after being cursed!")
TEST_ASSERT(HAS_TRAIT(dummy, TRAIT_PACIFISM), "Dummy wasn't a pacifist after being cursed!")
QDEL_NULL(cursed_bag)
TEST_ASSERT(!HAS_TRAIT(dummy, TRAIT_CLUMSY), "Dummy was still clumsy after being cured of its curse!")
TEST_ASSERT(!HAS_TRAIT(dummy, TRAIT_PACIFISM), "Dummy was still a pacifist after being cured of its curse!")