Fixes transforming items getting their force incorrectly modified when metalgenned multiple times (#95739)

## About The Pull Request

Transforming weapons could change their sharpness which would break
material force modification as materials modify the force differently
based on item sharpness. Also the force was hard reset every time you
switched it on or off, which would also break the math.
I've rewritten the transforming component to preserve force and
sharpness, and account for different sharpness of the item when switched
on/off. Additionally, I've updated the two-handed component to use the
new system to make it easier to work with/less prone to breaking.

## Why It's Good For The Game

<img width="557" height="218" alt="image"
src="https://github.com/user-attachments/assets/59cfb21a-e97e-4843-9c11-c87ed32384a9"
/>

## Changelog
🆑
fix: Transforming items like eswords should no longer get absurd stats
when metalgenned multiple times in a row
/🆑
This commit is contained in:
SmArtKar
2026-04-19 22:58:37 +01:00
committed by GitHub
parent 99ec96a775
commit d68efbbf45
7 changed files with 166 additions and 63 deletions
+73 -16
View File
@@ -24,6 +24,16 @@
var/sharpness_on
/// Hitsound played when active
var/hitsound_on
/// Force when the weapon is inactive
var/force_off
/// Throwforce when the weapon is inactive
var/throwforce_off
/// Throw speed of the weapon when inactive
var/throw_speed_off
/// Weight class of the weapon when inactive
var/w_class_off
/// The sharpness of the weapon when inactive
var/sharpness_off
/// List of the original continuous attack verbs the item has.
var/list/attack_verb_continuous_off
/// List of the original simple attack verbs the item has.
@@ -75,6 +85,12 @@
src.clumsy_damage = clumsy_damage
src.inhand_icon_change = inhand_icon_change
src.force_off = item_parent.force
src.throwforce_off = item_parent.throwforce
src.throw_speed_off = item_parent.throw_speed
src.sharpness_off = item_parent.sharpness // Raw value, not via the getter
src.w_class_off = item_parent.w_class
if(attack_verb_continuous_on)
src.attack_verb_continuous_on = attack_verb_continuous_on
attack_verb_continuous_off = item_parent.attack_verb_continuous
@@ -95,6 +111,10 @@
RegisterSignal(parent, COMSIG_DETECTIVE_SCANNED, PROC_REF(on_scan))
RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses))
RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated))
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated))
RegisterSignal(parent, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_APPLY, PROC_REF(on_material_apply))
RegisterSignal(parent, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_REMOVE, PROC_REF(on_material_remove))
/datum/component/transforming/proc/apply_fantasy_bonuses(obj/item/source, bonus)
SIGNAL_HANDLER
@@ -110,9 +130,38 @@
force_on = source.reset_fantasy_variable("force_on", force_on)
throwforce_on = source.reset_fantasy_variable("throwforce_on", throwforce_on)
/datum/component/transforming/proc/on_material_apply(obj/item/source, datum/material/material, amount, multiplier)
SIGNAL_HANDLER
// Opposite state's force needs to be calculated for each material's effect
if (active)
force_off *= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, initial(source.sharpness)), multiplier)
throwforce_off *= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, initial(source.sharpness)), multiplier)
else
force_on *= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, sharpness_on), multiplier)
throwforce_on *= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, sharpness_on), multiplier)
/datum/component/transforming/proc/on_material_remove(obj/item/source, datum/material/material, amount, multiplier)
SIGNAL_HANDLER
// Same as appliation but inversed
if (active)
force_off /= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, initial(source.sharpness)), multiplier)
throwforce_off /= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, initial(source.sharpness)), multiplier)
else
force_on /= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, sharpness_on), multiplier)
throwforce_on /= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, sharpness_on), multiplier)
/datum/component/transforming/proc/on_materials_updated(obj/item/source, list/materials, datum/material/main_material)
SIGNAL_HANDLER
// Current force can be set directly
if (active)
force_on = source.force
throwforce_on = source.throwforce
else
force_off = source.force
throwforce_off = source.throwforce
/datum/component/transforming/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ITEM_SHARPEN_ACT, COMSIG_DETECTIVE_SCANNED))
UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ITEM_SHARPEN_ACT, COMSIG_DETECTIVE_SCANNED, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS))
/datum/component/transforming/proc/on_scan(datum/source, mob/user, datum/detective_scanner_log/entry)
SIGNAL_HANDLER
@@ -198,13 +247,13 @@
*/
/datum/component/transforming/proc/set_active(obj/item/source)
ADD_TRAIT(source, TRAIT_TRANSFORM_ACTIVE, REF(src))
if(sharpness_on)
if(!isnull(sharpness_on))
source.sharpness = sharpness_on
if(force_on)
source.force = force_on + (source.sharpness ? sharpened_bonus : 0)
if(throwforce_on)
source.throwforce = throwforce_on + (source.sharpness ? sharpened_bonus : 0)
if(throw_speed_on)
if(!isnull(force_on))
source.force = force_on
if(!isnull(throwforce_on))
source.throwforce = throwforce_on
if(!isnull(throw_speed_on))
source.throw_speed = throw_speed_on
if(LAZYLEN(attack_verb_continuous_on))
@@ -228,14 +277,14 @@
*/
/datum/component/transforming/proc/set_inactive(obj/item/source)
REMOVE_TRAIT(source, TRAIT_TRANSFORM_ACTIVE, REF(src))
if(sharpness_on)
source.sharpness = initial(source.sharpness)
if(force_on)
source.force = initial(source.force) + (source.sharpness ? sharpened_bonus : 0)
if(throwforce_on)
source.throwforce = initial(source.throwforce) + (source.sharpness ? sharpened_bonus : 0)
if(throw_speed_on)
source.throw_speed = initial(source.throw_speed)
if(!isnull(sharpness_on))
source.sharpness = sharpness_off
if(!isnull(force_on))
source.force = force_off
if(!isnull(throwforce_on))
source.throwforce = throwforce_off
if(!isnull(throw_speed_on))
source.throw_speed = throwforce_off
if(LAZYLEN(attack_verb_continuous_on))
source.attack_verb_continuous = attack_verb_continuous_off
@@ -243,7 +292,7 @@
source.attack_verb_simple = attack_verb_simple_off
source.hitsound = initial(source.hitsound)
source.update_weight_class(initial(source.w_class))
source.update_weight_class(w_class_off)
source.icon_state = initial(source.icon_state)
source.inhand_icon_state = initial(source.inhand_icon_state)
source.update_appearance()
@@ -307,3 +356,11 @@
if(force_on + increment > max)
return COMPONENT_BLOCK_SHARPEN_MAXED
sharpened_bonus = increment
force_on += sharpened_bonus
throwforce_on += sharpened_bonus
force_off += sharpened_bonus
throwforce_off += sharpened_bonus
// Mimics base whetstone effect for the on state
sharpness_on = SHARP_EDGED
if (!active)
return COMPONENT_BLOCK_SHARPEN_SHARPNESS
+18 -5
View File
@@ -149,6 +149,8 @@
RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated))
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated))
RegisterSignal(parent, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_APPLY, PROC_REF(on_material_apply))
RegisterSignal(parent, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_REMOVE, PROC_REF(on_material_remove))
// Remove all siginals registered to the parent item
/datum/component/two_handed/UnregisterFromParent()
@@ -427,15 +429,26 @@
SIGNAL_HANDLER
// With materials assigned we need to update our forces.
if (wielded)
// Materials modify force multiplicatively! Most of the time, for snowflakes they gotta handle it themselves
if (!isnull(force_wielded))
force_unwielded *= source.force / force_wielded
force_wielded = source.force
else
if (!isnull(force_unwielded))
force_wielded *= source.force / force_unwielded
force_unwielded = source.force
/datum/component/two_handed/proc/on_material_apply(obj/item/source, datum/material/material, amount, multiplier)
SIGNAL_HANDLER
// Opposite state's force needs to be calculated for each material's effect
if (wielded)
force_unwielded *= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, source.sharpness), multiplier)
else
force_wielded *= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, source.sharpness), multiplier)
/datum/component/two_handed/proc/on_material_remove(obj/item/source, datum/material/material, amount, multiplier)
SIGNAL_HANDLER
// Same as appliation but inversed
if (wielded)
force_unwielded /= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, source.sharpness), multiplier)
else
force_wielded /= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, source.sharpness), multiplier)
/**
* The offhand dummy item for two handed items
*/