Files
Ghom 0b0c5ea91e Unit test material checks are now performed on all crafting recipes by default. All stack recipes now transfer mats to the results (#92620)
## About The Pull Request
Extends the part of the crafting unit test that ensures consistency
between the total mats of the components of a recipe (or rather, the
result of said recipe) and a generic instance of the same type as its
result, previously only implemented on food recipes.

## Why It's Good For The Game
This ensures a degree of consistency with the material composition of
various objects in the game. I couldn't do it in the original PR as that
one was too big already and it took months to get it merged, and have
the relative bugs fixed.

Currently a WIP as I slowly deal with the unit test reports.

## Changelog

🆑
refactor: Follow-up to the crafting/material refactor from months ago.
All objects crafted with stacks now inherit their mat composition (not
necessarily the effects and color) by default, while previously only a
few things like chair, sinks and toilets did. Report any object looking
or behaving weirdly as a result.
fix: The material composition of ammo boxes is no longer a 1/10 of what
it's supposed to be. It was a shitty hack to make it harder to recycle
empty ammo boxes. Instead, they lose materials as they're emptied now.
/🆑
2025-12-02 18:29:01 -05:00

198 lines
8.0 KiB
Plaintext

#define ENGINE_COOLDOWN 5 SECONDS
#define SLASH_COOLDOWN 1.2 SECONDS
#define SLASH_WINDUP 1 SECONDS
#define CHARGE_DAMAGE_MOD 10
#define HOUSE_EDGE_ICONS_MAX 3
#define HOUSE_EDGE_ICONS_MIN 0
/**
* File for the House Edge sword and the V8 Engine, obtained from the black market.
*/
/obj/item/v8_engine
name = "ancient engine"
desc = "An extremely well-preserved, massive V8 engine from the early 2000s. It seems to be missing the rest of the vehicle. There's a tiny label on the side."
icon = 'icons/obj/weapons/sword.dmi'
icon_state = "v8_engine"
w_class = WEIGHT_CLASS_HUGE
force = 5
throwforce = 15
throw_range = 1
throw_speed = 1
COOLDOWN_DECLARE(engine_sound_cooldown)
/obj/item/v8_engine/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=5, force_wielded=5)
/obj/item/v8_engine/attack_self(mob/user, modifiers)
. = ..()
if (!COOLDOWN_FINISHED(src, engine_sound_cooldown))
return
playsound(src, 'sound/items/car_engine_start.ogg', vol = 75, vary = FALSE, extrarange = 3)
Shake(duration = ENGINE_COOLDOWN)
to_chat(user, span_notice("Darn thing... it's too old to keep on without retrofitting it! Without modifications, it works like it's junk."))
COOLDOWN_START(src, engine_sound_cooldown, ENGINE_COOLDOWN)
/obj/item/v8_engine/examine_more(mob/user)
. = ..()
INVOKE_ASYNC(src, PROC_REF(start_learning_recipe), user)
/obj/item/v8_engine/proc/start_learning_recipe(mob/user)
if(!user.mind)
return
if(user.mind.has_crafting_recipe(user = user, potential_recipe = /datum/crafting_recipe/house_edge))
return
to_chat(user, span_notice("You peer at the label on the side, reading about some unique modifications that could be made to the engine..."))
if(do_after(user, 15 SECONDS, src))
user.mind.teach_crafting_recipe(/datum/crafting_recipe/house_edge)
to_chat(user, span_notice("You learned how to make the House Edge."))
/obj/item/house_edge
name = "House Edge"
desc = "Dangerous. Loud. Sleek. It has a built in roulette wheel. This thing could easily rip your arm off if you're not careful."
icon = 'icons/obj/weapons/sword.dmi'
icon_state = "house_edge"
base_icon_state = "house_edge"
inhand_icon_state = "house_edge"
icon_angle = -45
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
w_class = WEIGHT_CLASS_HUGE
sharpness = SHARP_EDGED
force = 12
throwforce = 10
throw_range = 5
throw_speed = 1
armour_penetration = 15
hitsound = 'sound/items/car_engine_start.ogg'
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 14, /datum/material/cardboard = SHEET_MATERIAL_AMOUNT, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.8)
/// The number of charges the house edge has accrued through 2-handed hits, to charge a more powerful charge attack.
var/fire_charges = 0
///Sound played when wielded.
var/active_hitsound = 'sound/items/house_edge_hit.ogg'
///Datum that tracks weapon dashing for the fire_charge system
var/datum/action/innate/dash/charge
COOLDOWN_DECLARE(fire_charge_cooldown)
/obj/item/house_edge/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, force_unwielded = 12, force_wielded = 22, attacksound = active_hitsound)
RegisterSignals(src, list(COMSIG_ITEM_DROPPED, COMSIG_MOVABLE_PRE_THROW, COMSIG_ITEM_ATTACK_SELF), PROC_REF(reset_charges))
/obj/item/house_edge/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers)
if(!ismob(target))
return
var/mob/mob_target = target
if(HAS_TRAIT(src, TRAIT_WIELDED) && mob_target.stat != DEAD)
//Add a fire charge to a max of 3, updates icon_state.
fire_charges = clamp((fire_charges + 1), HOUSE_EDGE_ICONS_MIN, HOUSE_EDGE_ICONS_MAX)
COOLDOWN_RESET(src, fire_charge_cooldown)
update_appearance(UPDATE_ICON_STATE)
/obj/item/house_edge/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!COOLDOWN_FINISHED(src, fire_charge_cooldown))
return ITEM_INTERACT_BLOCKING
if(fire_charges <= 0)
balloon_alert(user, "no fire charges!")
return ITEM_INTERACT_BLOCKING
to_chat(user, span_boldnotice("You take aim at [interacting_with]..."))
user.add_shared_particles(/particles/bonfire)
if(!do_after(user, SLASH_WINDUP, target = src))
// Special attack fizzles, no slash and charges lost.
reset_charges()
drop_particles(user)
return ITEM_INTERACT_BLOCKING
user.add_shared_particles(/particles/bonfire)
new /obj/effect/temp_visual/focus_ring(get_turf(src))
playsound(src, 'sound/items/car_engine_start.ogg', vol = 35, vary = FALSE, extrarange = 3)
if(!do_after(user, SLASH_WINDUP * 2, target = src) || fire_charges < 3)
flaming_slash(interacting_with, user, upgraded = FALSE)
return ITEM_INTERACT_SUCCESS
flaming_slash(interacting_with, user, upgraded = TRUE) //Upgraded slash.
return ITEM_INTERACT_SUCCESS
/obj/item/house_edge/update_icon_state()
inhand_icon_state = HAS_TRAIT(src, TRAIT_WIELDED) ? "house_edge1" : "house_edge"
icon_state = "[base_icon_state][fire_charges ? fire_charges : ""]"
return ..()
/**
* Proc that handles the house edge's fire_charge mechanic when resetting charges back to zero.
* Updates icon, sets count, and updates icon.
*/
/obj/item/house_edge/proc/reset_charges(on_slash = FALSE)
if(!COOLDOWN_FINISHED(src, fire_charge_cooldown) && !on_slash)
return
if(fire_charges)
balloon_alert_to_viewers("charges lost!")
fire_charges = 0
update_icon(UPDATE_OVERLAYS|UPDATE_ICON_STATE)
/// Kills any of the relevant particles off the wielder, as added during special attacks.
/obj/item/house_edge/proc/drop_particles(mob/living/user)
user.remove_shared_particles(/particles/bonfire)
/**
* Proc that creates and fires the flaming_slash projectile from the house edge special attack.
* Extends off the ranged_interact_with_atom_secondary() behavior.
* * upgraded: when juiced up from the second do_after, applies a light explosion radius to the slash.
*/
/obj/item/house_edge/proc/flaming_slash(atom/interacting_with, mob/living/user, upgraded = FALSE)
// Do the cool slash
var/obj/projectile/flaming_slash/projectile = new /obj/projectile/flaming_slash(get_turf(src))
projectile.aim_projectile(interacting_with, src)
projectile.firer = user
projectile.damage += (10 * fire_charges)
if(upgraded)
projectile.explosion_power = 1
projectile.damage /= 2 // The damage is pretty solid normally, but with the explosion and all the RNG that comes with, it's nearly a 1-shot. This evens a playing field a bit.
projectile.fire(null, interacting_with)
user.visible_message(span_danger("[user] makes a[upgraded ? " devastating" : "" ] blazing slash at [interacting_with]!"),\
span_notice("You take a blazing swipe at [interacting_with]!"))
playsound(src, 'sound/items/modsuit/flamethrower.ogg', vol = 75, vary = FALSE, extrarange = 3)
playsound(src, 'sound/items/weapons/slash.ogg', vol = 50, vary = FALSE, extrarange = 3)
drop_particles(user)
COOLDOWN_START(src, fire_charge_cooldown, SLASH_COOLDOWN)
reset_charges(on_slash = TRUE)
/// Flaming slash for the special attack at max charges.
/obj/projectile/flaming_slash
name = "flaming slash"
desc = "Someone is about to cash out."
icon_state = "flaming_slash"
damage_type = BURN
armor_flag = MELEE //We're operating off of anime remote slash logic here. As such, we can treat this as a hybrid burn/brute this way.
damage = 10 // Damage amps based on the number of flame_charges it was created off of.
speed = 2
light_range = 1
light_power = 1
light_color = LIGHT_COLOR_FIRE
sharpness = SHARP_EDGED
///Applied in on_hit as the light_devastation_range. Defaults to zero unless giving the slash an extra juiced charge.
var/explosion_power = 0
/obj/projectile/flaming_slash/on_hit(atom/target, blocked, pierce_hit)
. = ..()
explosion(loc,
light_impact_range = explosion_power,
flame_range = 3,
explosion_cause = "[firer] using \the [fired_from]",
explosion_arc = 120
)
#undef ENGINE_COOLDOWN
#undef SLASH_COOLDOWN
#undef SLASH_WINDUP
#undef CHARGE_DAMAGE_MOD
#undef HOUSE_EDGE_ICONS_MAX
#undef HOUSE_EDGE_ICONS_MIN