diff --git a/code/datums/elements/plant_backfire.dm b/code/datums/elements/plant_backfire.dm
index f753947e9ea..b9a2000ffe4 100644
--- a/code/datums/elements/plant_backfire.dm
+++ b/code/datums/elements/plant_backfire.dm
@@ -3,7 +3,7 @@
/// If a user is protected with something like leather gloves, they can handle them normally.
/// If they're not protected properly, we invoke a callback on the user, harming or inconveniencing them.
/datum/element/plant_backfire
- element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
+ element_flags = ELEMENT_BESPOKE
id_arg_index = 2
/// Whether we stop the current action if backfire is triggered (EX: returning CANCEL_ATTACK_CHAIN)
var/cancel_action = FALSE
@@ -29,52 +29,67 @@
. = ..()
UnregisterSignal(target, list(COMSIG_ITEM_PRE_ATTACK, COMSIG_ITEM_PICKUP, COMSIG_MOVABLE_PRE_THROW))
-/*
+/**
* Checks before we attack if we're okay to continue.
*
* source - our plant
* user - the mob wielding our [source]
*/
-/datum/element/plant_backfire/proc/attack_safety_check(datum/source, atom/target, mob/user)
+/datum/element/plant_backfire/proc/attack_safety_check(obj/item/source, atom/target, mob/user)
SIGNAL_HANDLER
- if(plant_safety_check(source, user))
+ // Covers stuff like tk, since we aren't actually touching the plant.
+ if(!user.is_holding(source))
+ return
+ if(!backfire(source, user))
return
-
- SEND_SIGNAL(source, COMSIG_PLANT_ON_BACKFIRE, user)
- if(cancel_action)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-/*
+ return cancel_action ? COMPONENT_CANCEL_ATTACK_CHAIN : NONE
+
+/**
* Checks before we pick up the plant if we're okay to continue.
*
* source - our plant
* user - the mob picking our [source]
*/
-/datum/element/plant_backfire/proc/pickup_safety_check(datum/source, mob/user)
+/datum/element/plant_backfire/proc/pickup_safety_check(obj/item/source, mob/user)
SIGNAL_HANDLER
- if(plant_safety_check(source, user))
- return
- SEND_SIGNAL(source, COMSIG_PLANT_ON_BACKFIRE, user)
+ backfire(source, user)
-/*
+/**
* Checks before we throw the plant if we're okay to continue.
*
* source - our plant
* thrower - the mob throwing our [source]
*/
-/datum/element/plant_backfire/proc/throw_safety_check(datum/source, list/arguments)
+/datum/element/plant_backfire/proc/throw_safety_check(obj/item/source, list/arguments)
SIGNAL_HANDLER
- var/mob/living/thrower = arguments[4] // 4th arg = mob/thrower
- if(plant_safety_check(source, thrower))
+ var/mob/living/thrower = arguments[4] // the 4th arg = the mob throwing our item
+ if(!thrower.is_holding(source))
+ return
+ if(!backfire(source, thrower))
return
- SEND_SIGNAL(source, COMSIG_PLANT_ON_BACKFIRE, thrower)
- if(cancel_action)
- return COMPONENT_CANCEL_THROW
-/*
+ return cancel_action ? COMPONENT_CANCEL_ATTACK_CHAIN : NONE
+
+/**
+ * The actual backfire occurs here.
+ * Checks if the user is able to safely handle the plant.
+ * If not, sends the backfire signal (meaning backfire will occur and be handled by one or multiple genes).
+ *
+ * Returns FALSE if the user was safe and no backfire occured.
+ * Returns TRUE if the user was not safe and a backfire actually happened.
+ */
+/datum/element/plant_backfire/proc/backfire(obj/item/plant, mob/user)
+ if(plant_safety_check(plant, user))
+ return FALSE
+
+ SEND_SIGNAL(plant, COMSIG_PLANT_ON_BACKFIRE, user)
+ return TRUE
+
+/**
* Actually checks if our user is safely handling our plant.
*
* Checks for TRAIT_PLANT_SAFE, and returns TRUE if we have it.
@@ -86,13 +101,10 @@
*
* returns FALSE if none of the checks are successful.
*/
-/datum/element/plant_backfire/proc/plant_safety_check(datum/source, mob/living/carbon/user)
+/datum/element/plant_backfire/proc/plant_safety_check(obj/item/plant, mob/living/carbon/user)
if(!istype(user))
return TRUE
- if(istype(source, /obj/item/tk_grab)) // since we aren't actually touching the plant
- return TRUE
-
if(HAS_TRAIT(user, TRAIT_PLANT_SAFE))
return TRUE
@@ -100,8 +112,7 @@
if(HAS_TRAIT(user, checked_trait))
return TRUE
- var/obj/item/parent_item = source
- var/obj/item/seeds/our_seed = parent_item.get_plant_seed()
+ var/obj/item/seeds/our_seed = plant.get_plant_seed()
if(our_seed)
for(var/checked_gene in extra_genes)
if(!our_seed.get_gene(checked_gene))
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index 598f38d3adf..bc601ef167a 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -220,6 +220,7 @@
SEND_SIGNAL(owner, COMSIG_LIVING_IGNITED, owner)
cache_stacks()
update_overlay()
+ return TRUE
/**
* Handles mob extinguishing, should be the only way to set on_fire to FALSE
diff --git a/code/modules/hydroponics/unique_plant_genes.dm b/code/modules/hydroponics/unique_plant_genes.dm
index 5ea7f604a15..7370e86f50a 100644
--- a/code/modules/hydroponics/unique_plant_genes.dm
+++ b/code/modules/hydroponics/unique_plant_genes.dm
@@ -38,6 +38,10 @@
name = "On Attack Trait"
/// The multiplier we apply to the potency to calculate force. Set to 0 to not affect the force.
var/force_multiplier = 0
+ /// If TRUE, our plant will degrade in force every hit until diappearing.
+ var/degrades_after_hit = FALSE
+ /// When we fully degrade, what degraded off of us?
+ var/degradation_noun = "leaves"
/datum/plant_gene/trait/attack/on_new_plant(obj/item/our_plant, newloc)
. = ..()
@@ -50,97 +54,101 @@
RegisterSignal(our_plant, COMSIG_ITEM_ATTACK, .proc/on_plant_attack)
RegisterSignal(our_plant, COMSIG_ITEM_AFTERATTACK, .proc/after_plant_attack)
+/// Signal proc for [COMSIG_ITEM_ATTACK] that allows for effects on attack
+/datum/plant_gene/trait/attack/proc/on_plant_attack(obj/item/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, .proc/attack_effect, source, target, user)
+
/*
- * Plant effects ON attack.
+ * Effects done when we hit people with our plant, ON attack.
+ * Override on a per-plant basis.
*
* our_plant - our plant, that we're attacking with
* user - the person who is attacking with the plant
* target - the person who is attacked by the plant
*/
-/datum/plant_gene/trait/attack/proc/on_plant_attack(obj/item/our_plant, mob/living/target, mob/living/user)
+/datum/plant_gene/trait/attack/proc/attack_effect(obj/item/our_plant, mob/living/target, mob/living/user)
+ return
+
+/// Signal proc for [COMSIG_ITEM_AFTERATTACK] that allows for effects after an attack is done
+/datum/plant_gene/trait/attack/proc/after_plant_attack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
SIGNAL_HANDLER
+ if(!proximity_flag)
+ return
+
+ if(!ismovable(target))
+ return
+
+ if(isobj(target))
+ var/obj/object_target = target
+ if(!(object_target.obj_flags & CAN_BE_HIT))
+ return
+
+ INVOKE_ASYNC(src, .proc/after_attack_effect, source, target, user)
+
/*
- * Plant effects AFTER attack.
+ * Effects done when we hit people with our plant, AFTER the attack is done.
+ * Extend on a per-plant basis.
*
* our_plant - our plant, that we're attacking with
* user - the person who is attacking with the plant
* target - the atom which is attacked by the plant
- *
- * return TRUE if plant attack is acceptable, otherwise FALSE to early return subtypes.
*/
-/datum/plant_gene/trait/attack/proc/after_plant_attack(obj/item/our_plant, atom/target, mob/user, proximity_flag, click_parameters)
- SIGNAL_HANDLER
+/datum/plant_gene/trait/attack/proc/after_attack_effect(obj/item/our_plant, atom/target, mob/living/user)
+ SHOULD_CALL_PARENT(TRUE)
- if(!proximity_flag)
- return FALSE
- return TRUE
+ if(!degrades_after_hit)
+ return
+
+ // We probably hit something or someone. Reduce our force
+ if(our_plant.force > 0)
+ our_plant.force -= rand(1, (our_plant.force / 3) + 1)
+ return
+
+ // When our force degrades to zero or below, we're all done
+ to_chat(user, span_warning("All the [degradation_noun] have fallen off [our_plant] from violent whacking!"))
+ qdel(our_plant)
/// Novaflower's attack effects (sets people on fire) + degradation on attack
/datum/plant_gene/trait/attack/novaflower_attack
name = "Heated Petals"
force_multiplier = 0.2
+ degrades_after_hit = TRUE
+ degradation_noun = "petals"
-/datum/plant_gene/trait/attack/novaflower_attack/on_plant_attack(obj/item/our_plant, mob/living/target, mob/living/user)
- . = ..()
- if(!.)
+/datum/plant_gene/trait/attack/novaflower_attack/attack_effect(obj/item/our_plant, mob/living/target, mob/living/user)
+ if(!istype(target))
return
var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
- to_chat(target, "You are lit on fire from the intense heat of [our_plant]!")
- target.adjust_fire_stacks(our_seed.potency / 20)
+ to_chat(target, span_danger("You are lit on fire from the intense heat of [our_plant]!"))
+ target.adjust_fire_stacks(round(our_seed.potency / 20))
if(target.ignite_mob())
message_admins("[ADMIN_LOOKUPFLW(user)] set [ADMIN_LOOKUPFLW(target)] on fire with [our_plant] at [AREACOORD(user)]")
log_game("[key_name(user)] set [key_name(target)] on fire with [our_plant] at [AREACOORD(user)]")
our_plant.investigate_log("was used by [key_name(user)] to burn [key_name(target)] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
-/datum/plant_gene/trait/attack/novaflower_attack/after_plant_attack(obj/item/our_plant, atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(!.)
- return
-
- if(!ismovable(target))
- return
- if(our_plant.force > 0)
- our_plant.force -= rand(1, (our_plant.force / 3) + 1)
- else
- to_chat(user, "All the petals have fallen off [our_plant] from violent whacking!")
- qdel(our_plant)
-
/// Sunflower's attack effect (shows cute text)
/datum/plant_gene/trait/attack/sunflower_attack
name = "Bright Petals"
-/datum/plant_gene/trait/attack/sunflower_attack/after_plant_attack(obj/item/our_plant, atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(!.)
- return
+/datum/plant_gene/trait/attack/sunflower_attack/after_attack_effect(obj/item/our_plant, atom/target, mob/user, proximity_flag, click_parameters)
+ if(ismob(target))
+ var/mob/target_mob = target
+ user.visible_message("[user] smacks [target_mob] with [user.p_their()] [our_plant.name]! FLOWER POWER!", ignored_mobs = list(target_mob, user))
+ if(target_mob != user)
+ to_chat(target_mob, "[user] smacks you with [our_plant]!FLOWER POWER!")
+ to_chat(user, "Your [our_plant.name]'s FLOWER POWER strikes [target_mob]!")
- if(!ismob(target))
- return
- var/mob/target_mob = target
- user.visible_message("[user] smacks [target_mob] with [user.p_their()] [our_plant.name]! FLOWER POWER!", ignored_mobs = list(target_mob, user))
- if(target_mob != user)
- to_chat(target_mob, "[user] smacks you with [our_plant]!FLOWER POWER!")
- to_chat(user, "Your [our_plant.name]'s FLOWER POWER strikes [target_mob]!")
+ return ..()
/// Normal nettle's force + degradation on attack
/datum/plant_gene/trait/attack/nettle_attack
name = "Sharpened Leaves"
force_multiplier = 0.2
-
-/datum/plant_gene/trait/attack/nettle_attack/after_plant_attack(obj/item/our_plant, atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(!.)
- return
-
- if(!ismovable(target))
- return
- if(our_plant.force > 0)
- our_plant.force -= rand(1, (our_plant.force / 3) + 1)
- else
- to_chat(user, "All the leaves have fallen off [our_plant] from violent whacking!")
- qdel(our_plant)
+ degrades_after_hit = TRUE
/// Deathnettle force + degradation on attack
/datum/plant_gene/trait/attack/nettle_attack/death
@@ -153,9 +161,9 @@
/// Whether our actions are cancelled when the backfire triggers.
var/cancel_action_on_backfire = FALSE
/// A list of extra traits to check to be considered safe.
- var/traits_to_check
+ var/list/traits_to_check
/// A list of extra genes to check to be considered safe.
- var/genes_to_check
+ var/list/genes_to_check
/datum/plant_gene/trait/backfire/on_new_plant(obj/item/our_plant, newloc)
. = ..()
@@ -163,16 +171,20 @@
return
our_plant.AddElement(/datum/element/plant_backfire, cancel_action_on_backfire, traits_to_check, genes_to_check)
- RegisterSignal(our_plant, COMSIG_PLANT_ON_BACKFIRE, .proc/backfire_effect)
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_BACKFIRE, .proc/on_backfire)
-/*
- * The backfire effect. Override with plant-specific effects.
- *
- * user - the person who is carrying the plant
- * our_plant - our plant
+/// Signal proc for [COMSIG_PLANT_ON_BACKFIRE] that causes the backfire effect.
+/datum/plant_gene/trait/backfire/proc/on_backfire(obj/item/source, mob/living/carbon/user)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, .proc/backfire_effect, source, user)
+
+/**
+ * The actual backfire effect on the user.
+ * Override with plant-specific effects.
*/
/datum/plant_gene/trait/backfire/proc/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
- SIGNAL_HANDLER
+ return
/// Rose's prick on backfire
/datum/plant_gene/trait/backfire/rose_thorns
@@ -180,18 +192,15 @@
traits_to_check = list(TRAIT_PIERCEIMMUNE)
/datum/plant_gene/trait/backfire/rose_thorns/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
- . = ..()
-
var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
if(!our_seed.get_gene(/datum/plant_gene/trait/sticky) && prob(66))
- to_chat(user, "[our_plant]'s thorns nearly prick your hand. Best be careful.")
+ to_chat(user, span_danger("[our_plant]'s thorns nearly prick your hand. Best be careful."))
return
- to_chat(user, "[our_plant]'s thorns prick your hand. Ouch.")
+ to_chat(user, span_danger("[our_plant]'s thorns prick your hand. Ouch."))
our_plant.investigate_log("rose-pricked [key_name(user)] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
var/obj/item/bodypart/affecting = user.get_active_hand()
- if(affecting?.receive_damage(2))
- user.update_damage_overlays()
+ affecting?.receive_damage(2)
/// Novaflower's hand burn on backfire
/datum/plant_gene/trait/backfire/novaflower_heat
@@ -199,26 +208,20 @@
cancel_action_on_backfire = TRUE
/datum/plant_gene/trait/backfire/novaflower_heat/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
- . = ..()
-
- to_chat(user, "[our_plant] singes your bare hand!")
+ to_chat(user, span_danger("[our_plant] singes your bare hand!"))
our_plant.investigate_log("self-burned [key_name(user)] for [our_plant.force] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
var/obj/item/bodypart/affecting = user.get_active_hand()
- if(affecting?.receive_damage(0, our_plant.force, wound_bonus = CANT_WOUND))
- user.update_damage_overlays()
+ return affecting?.receive_damage(0, our_plant.force, wound_bonus = CANT_WOUND)
/// Normal Nettle hannd burn on backfire
/datum/plant_gene/trait/backfire/nettle_burn
name = "Stinging Stem"
/datum/plant_gene/trait/backfire/nettle_burn/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
- . = ..()
-
- to_chat(user, "[our_plant] burns your bare hand!")
+ to_chat(user, span_danger("[our_plant] burns your bare hand!"))
our_plant.investigate_log("self-burned [key_name(user)] for [our_plant.force] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
var/obj/item/bodypart/affecting = user.get_active_hand()
- if(affecting?.receive_damage(0, our_plant.force, wound_bonus = CANT_WOUND))
- user.update_damage_overlays()
+ return affecting?.receive_damage(0, our_plant.force, wound_bonus = CANT_WOUND)
/// Deathnettle hand burn + stun on backfire
/datum/plant_gene/trait/backfire/nettle_burn/death
@@ -227,10 +230,11 @@
/datum/plant_gene/trait/backfire/nettle_burn/death/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
. = ..()
+ if(!. || prob(50))
+ return
- if(prob(50))
- user.Paralyze(100)
- to_chat(user, "You are stunned by the powerful acids of [our_plant]!")
+ user.Paralyze(10 SECONDS)
+ to_chat(user, span_userdanger("You are stunned by the powerful acids of [our_plant]!"))
/// Ghost-Chili heating up on backfire
/datum/plant_gene/trait/backfire/chili_heat
@@ -256,8 +260,6 @@
* user - the mob holding our plant
*/
/datum/plant_gene/trait/backfire/chili_heat/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
- . = ..()
-
held_mob = WEAKREF(user)
START_PROCESSING(SSobj, src)
@@ -296,11 +298,14 @@
genes_to_check = list(/datum/plant_gene/trait/squash)
/datum/plant_gene/trait/backfire/bluespace/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
- . = ..()
-
if(prob(50))
- to_chat(user, "[our_plant] slips out of your hand!")
- INVOKE_ASYNC(our_plant, /obj/item/.proc/attack_self, user)
+ return
+
+ to_chat(user, span_danger("[our_plant] slips out of your hand!"))
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/datum/plant_gene/trait/squash/squash_gene = our_seed.get_gene(/datum/plant_gene/trait/squash)
+ squash_gene.squash_plant(our_plant, user)
/// Traits for plants that can be activated to turn into a mob.
/datum/plant_gene/trait/mob_transformation
@@ -347,8 +352,8 @@
return
if(target != user)
- to_chat(user, "[our_plant] is twitching and shaking, preventing you from feeding it to [target].")
- to_chat(target, "[our_plant] is twitching and shaking, preventing you from eating it.")
+ to_chat(user, span_warning("[our_plant] is twitching and shaking, preventing you from feeding it to [target]."))
+ to_chat(target, span_warning("[our_plant] is twitching and shaking, preventing you from eating it."))
return COMPONENT_CANCEL_ATTACK_CHAIN
/*
@@ -365,10 +370,10 @@
return
if(dangerous && HAS_TRAIT(user, TRAIT_PACIFISM))
- to_chat(user, "You decide not to awaken [our_plant]. It may be very dangerous!")
+ to_chat(user, span_notice("You decide not to awaken [our_plant]. It may be very dangerous!"))
return
- to_chat(user, "You begin to awaken [our_plant]...")
+ to_chat(user, span_notice("You begin to awaken [our_plant]..."))
begin_awaken(our_plant, 3 SECONDS)
our_plant.investigate_log("was awakened by [key_name(user)] at [AREACOORD(user)].", INVESTIGATE_BOTANY)
@@ -382,7 +387,7 @@
SIGNAL_HANDLER
if(!awakening && !isspaceturf(user.loc) && prob(25))
- to_chat(user, "[our_plant] begins to growl and shake!")
+ our_plant.visible_message(span_danger("[our_plant] begins to growl and shake!"))
begin_awaken(our_plant, 1 SECONDS)
our_plant.investigate_log("was awakened (via plant backfire) by [key_name(user)] at [AREACOORD(user)].", INVESTIGATE_BOTANY)
@@ -416,7 +421,7 @@
spawned_simplemob.melee_damage_upper += round(our_seed.potency * mob_melee_multiplier)
spawned_simplemob.move_to_delay -= round(our_seed.production * mob_speed_multiplier)
our_plant.forceMove(our_plant.drop_location())
- spawned_mob.visible_message("[our_plant] growls as it suddenly awakens!")
+ spawned_mob.visible_message(span_notice("[our_plant] growls as it suddenly awakens!"))
qdel(our_plant)
/// Killer Tomato's transformation gene.
@@ -505,7 +510,10 @@
our_plant.color = COLOR_RED
playsound(our_plant, 'sound/effects/fuse.ogg', our_seed.potency, FALSE)
- user.visible_message("[user] plucks the stem from [our_plant]!", "You pluck the stem from [our_plant], which begins to hiss loudly!")
+ user.visible_message(
+ span_warning("[user] plucks the stem from [our_plant]!"),
+ span_userdanger("You pluck the stem from [our_plant], which begins to hiss loudly!"),
+ )
log_bomber(user, "primed a", our_plant, "for detonation")
detonate(our_plant)
@@ -551,7 +559,10 @@
name = "Explosive Nature"
/datum/plant_gene/trait/bomb_plant/potency_based/trigger_detonation(obj/item/our_plant, mob/living/user)
- user.visible_message("[user] primes [our_plant]!", "You prime [our_plant]!")
+ user.visible_message(
+ span_warning("[user] primes [our_plant]!"),
+ span_userdanger("You prime [our_plant]!"),
+ )
log_bomber(user, "primed a", our_plant, "for detonation")
var/obj/item/food/grown/grown_plant = our_plant
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 60a6ee4fa61..0465295c295 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -111,6 +111,7 @@
#include "mob_spawn.dm"
#include "modsuit.dm"
#include "modular_map_loader.dm"
+#include "novaflower_burn.dm"
#include "ntnetwork_tests.dm"
#include "nuke_cinematic.dm"
#include "objectives.dm"
diff --git a/code/modules/unit_tests/novaflower_burn.dm b/code/modules/unit_tests/novaflower_burn.dm
new file mode 100644
index 00000000000..c54cb9d6d15
--- /dev/null
+++ b/code/modules/unit_tests/novaflower_burn.dm
@@ -0,0 +1,37 @@
+/// Unit tests that the novaflower's unique genes function.
+/datum/unit_test/novaflower_burn
+
+/datum/unit_test/novaflower_burn/Run()
+ var/mob/living/carbon/human/botanist = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+ var/obj/item/grown/novaflower/weapon = allocate(/obj/item/grown/novaflower)
+
+ TEST_ASSERT(weapon.force > 0, "[weapon] spawned with zero force.")
+
+ // Keep this around for comparison later.
+ var/initial_force = weapon.force
+ // Start by having the novaflower equipped to an attacker's hands
+ // They are not wearing botany gloves (have plant protection), so they should take damage = the flower's force.
+ weapon.attack_hand(botanist)
+ TEST_ASSERT_EQUAL(botanist.get_active_held_item(), weapon, "The botanist failed to pick up [weapon].")
+ TEST_ASSERT_EQUAL(botanist.getFireLoss(), weapon.force, "The botanist picked up [weapon] with their bare hands, and took an incorrect amount of fire damage.")
+
+ // Heal our attacker for easy comparison later
+ botanist.adjustFireLoss(-100)
+ // And give them the plant safe trait so we don't have to worry about attacks being cancelled
+ ADD_TRAIT(botanist, TRAIT_PLANT_SAFE, "unit_test")
+
+ // Now, let's get a smack with the novaflower and see what happens.
+ weapon.melee_attack_chain(botanist, victim)
+
+ TEST_ASSERT(botanist.getFireLoss() <= 0, "The botanist took fire damage from [weapon], even though they were plant safe.")
+ TEST_ASSERT_EQUAL(victim.getFireLoss(), initial_force, "The target took an incorrect amount of fire damage after being hit with [weapon].")
+ TEST_ASSERT(weapon.force < initial_force, "[weapon] didn't lose any force after an attack.")
+ TEST_ASSERT(victim.fire_stacks > 0, "[weapon] didn't apply any firestacks to the target after an attack.")
+ TEST_ASSERT(victim.on_fire, "[weapon] didn't set the target on fire after an attack.")
+
+ // Lastly we should check that degredation to zero works.
+ weapon.force = 0
+ weapon.melee_attack_chain(botanist, victim)
+
+ TEST_ASSERT(QDELETED(weapon), "[weapon] wasn't deleted after hitting someone with zero force.")