From 688ef326552333d9418329635ef34161a996f5db Mon Sep 17 00:00:00 2001 From: MistakeNot4892 Date: Thu, 9 Jan 2025 21:00:48 +1100 Subject: [PATCH] Working commit for blacksmithy. --- code/_helpers/visual_filters.dm | 5 +- code/game/objects/__objs.dm | 27 ++ code/game/objects/effects/effect_system.dm | 9 +- code/game/objects/items/__item.dm | 3 + .../objects/items/{ => artifice}/lockpicks.dm | 0 .../game/objects/items/crafting/barrel_rim.dm | 8 + .../items/crafting/shield_fasteners.dm | 8 + .../objects/items/weapons/material/knives.dm | 2 +- .../structures/_structure_materials.dm | 9 +- code/game/objects/structures/fires.dm | 3 + code/modules/atmospherics/he_pipes.dm | 19 +- code/modules/crafting/forging/forge_anvil.dm | 82 +++++ code/modules/crafting/forging/forge_bars.dm | 62 ++++ code/modules/crafting/forging/forge_billet.dm | 196 +++++++++++ code/modules/crafting/forging/forge_fire.dm | 83 +++++ code/modules/crafting/forging/forge_tools.dm | 37 +++ .../crafting/forging/forging_action.dm | 20 ++ code/modules/crafting/forging/forging_step.dm | 306 ++++++++++++++++++ .../tool_crafting/_tool_crafting.dm | 4 + .../crafting/stack_recipes/recipes_soft.dm | 6 + .../crafting/stack_recipes/recipes_steel.dm | 4 + code/modules/materials/_materials.dm | 3 + .../solids/materials_solid_metal.dm | 1 + .../materials/material_sheets_mapping.dm | 4 + code/modules/materials/material_stack_nail.dm | 16 + code/modules/tools/components/head.dm | 6 + code/modules/xenoarcheaology/boulder.dm | 24 ++ icons/obj/items/barrel_rim.dmi | Bin 0 -> 328 bytes icons/obj/items/billet.dmi | Bin 0 -> 891 bytes icons/obj/items/shield_fasteners.dmi | Bin 0 -> 474 bytes icons/obj/items/stacks/nails.dmi | Bin 0 -> 533 bytes icons/obj/items/tongs.dmi | Bin 0 -> 715 bytes icons/obj/items/tool/hammers/forge.dmi | Bin 0 -> 784 bytes icons/obj/structures/anvil.dmi | Bin 0 -> 558 bytes icons/obj/structures/anvil_crude.dmi | Bin 0 -> 640 bytes icons/obj/structures/anvil_crude_alt.dmi | Bin 0 -> 647 bytes icons/obj/structures/anvil_improvised.dmi | Bin 0 -> 674 bytes icons/obj/structures/forging/forge.dmi | Bin 0 -> 8551 bytes nebula.dme | 18 +- 39 files changed, 941 insertions(+), 24 deletions(-) rename code/game/objects/items/{ => artifice}/lockpicks.dm (100%) create mode 100644 code/game/objects/items/crafting/barrel_rim.dm create mode 100644 code/game/objects/items/crafting/shield_fasteners.dm create mode 100644 code/modules/crafting/forging/forge_anvil.dm create mode 100644 code/modules/crafting/forging/forge_bars.dm create mode 100644 code/modules/crafting/forging/forge_billet.dm create mode 100644 code/modules/crafting/forging/forge_fire.dm create mode 100644 code/modules/crafting/forging/forge_tools.dm create mode 100644 code/modules/crafting/forging/forging_action.dm create mode 100644 code/modules/crafting/forging/forging_step.dm create mode 100644 code/modules/materials/material_stack_nail.dm create mode 100644 icons/obj/items/barrel_rim.dmi create mode 100644 icons/obj/items/billet.dmi create mode 100644 icons/obj/items/shield_fasteners.dmi create mode 100644 icons/obj/items/stacks/nails.dmi create mode 100644 icons/obj/items/tongs.dmi create mode 100644 icons/obj/items/tool/hammers/forge.dmi create mode 100644 icons/obj/structures/anvil.dmi create mode 100644 icons/obj/structures/anvil_crude.dmi create mode 100644 icons/obj/structures/anvil_crude_alt.dmi create mode 100644 icons/obj/structures/anvil_improvised.dmi create mode 100644 icons/obj/structures/forging/forge.dmi diff --git a/code/_helpers/visual_filters.dm b/code/_helpers/visual_filters.dm index 10e485e1854..a02a1210928 100644 --- a/code/_helpers/visual_filters.dm +++ b/code/_helpers/visual_filters.dm @@ -3,13 +3,16 @@ // All of this ported from TG. // And then ported to Nebula from Polaris. /atom/movable - var/list/filter_data // For handling persistent filters + VAR_PRIVATE/list/filter_data // For handling persistent filters // Defining this for future proofing and ease of searching for erroneous usage. /image/proc/add_filter(filter_name, priority, list/params) filters += filter(arglist(params)) return TRUE +/atom/movable/proc/has_filter(filter_name) + return (name in filter_data) + /atom/movable/proc/add_filter(filter_name, priority, list/params, force_update = FALSE) // Check if we already have a filter and hence don't need to rebuild filters. diff --git a/code/game/objects/__objs.dm b/code/game/objects/__objs.dm index 8ab41e00a57..03064aae179 100644 --- a/code/game/objects/__objs.dm +++ b/code/game/objects/__objs.dm @@ -416,3 +416,30 @@ if(dumped_reagents && last_loc && !QDELETED(last_loc) && last_loc.reagents?.total_volume) last_loc.reagents.handle_update() HANDLE_REACTIONS(last_loc.reagents) + +// Used by HE pipes and forging bars/billets. Defaults are for HE pipes. +/obj/proc/animate_heat_glow(icon_temperature, scale_sub = 500, scale_div = 1500, scale_max = 2000, skip_filter = FALSE) + + var/scale = max((icon_temperature - scale_sub) / scale_div, 0) + var/h_r = heat2color_r(icon_temperature) + var/h_g = heat2color_g(icon_temperature) + var/h_b = heat2color_b(icon_temperature) + + var/base_color = get_color() + var/b_r = HEX_RED(base_color) + var/b_g = HEX_GREEN(base_color) + var/b_b = HEX_BLUE(base_color) + + if(icon_temperature < scale_max) + h_r = b_r + (h_r - b_r)*scale + h_g = b_g + (h_g - b_g)*scale + h_b = b_b + (h_b - b_b)*scale + + var/scale_color = rgb(h_r, h_g, h_b) + var/list/animate_targets = get_above_oo() + src + for (var/thing in animate_targets) + var/atom/movable/AM = thing + animate(AM, color = scale_color, time = 2 SECONDS, easing = SINE_EASING) + if(!skip_filter) + animate_filter("glow", list(color = scale_color, time = 2 SECONDS, easing = LINEAR_EASING)) + set_light(min(3, scale*2.5), min(3, scale*2.5), scale_color) diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index ef312635529..d5bcac25da9 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -107,12 +107,16 @@ steam.start() -- spawns the effect /obj/effect/sparks/struck spark_sound = "light_bic" +/obj/effect/sparks/silent + spark_sound = null + /obj/effect/sparks/Initialize() . = ..() // this is 2 seconds so that it doesn't appear to freeze after its last move, which ends up making it look like timers are broken // if you change the number of or delay between moves in spread(), this may need to be changed QDEL_IN(src, 2 SECONDS) - playsound(loc, spark_sound, 100, 1) + if(spark_sound) + playsound(loc, spark_sound, 100, 1) set_light(lit_light_range, lit_light_power, lit_light_color) if(isturf(loc)) var/turf/T = loc @@ -140,6 +144,9 @@ steam.start() -- spawns the effect /datum/effect/effect/system/spark_spread var/spark_type = /obj/effect/sparks +/datum/effect/effect/system/spark_spread/silent + spark_type = /obj/effect/sparks/silent + /datum/effect/effect/system/spark_spread/non_electrical spark_type = /obj/effect/sparks/struck diff --git a/code/game/objects/items/__item.dm b/code/game/objects/items/__item.dm index 20c732782fa..63c0203384d 100644 --- a/code/game/objects/items/__item.dm +++ b/code/game/objects/items/__item.dm @@ -377,6 +377,9 @@ if(drying_wetness > 0 && drying_wetness != initial(drying_wetness)) desc_comp += "\The [src] is [get_dryness_text()]." + if(check_rights(R_DEBUG, 0, user)) + to_chat(user, "\The [src] has a temperature of [temperature]K.") + return ..(user, distance, "", jointext(desc_comp, "
")) /obj/item/check_mousedrop_adjacency(var/atom/over, var/mob/user) diff --git a/code/game/objects/items/lockpicks.dm b/code/game/objects/items/artifice/lockpicks.dm similarity index 100% rename from code/game/objects/items/lockpicks.dm rename to code/game/objects/items/artifice/lockpicks.dm diff --git a/code/game/objects/items/crafting/barrel_rim.dm b/code/game/objects/items/crafting/barrel_rim.dm new file mode 100644 index 00000000000..f217957e557 --- /dev/null +++ b/code/game/objects/items/crafting/barrel_rim.dm @@ -0,0 +1,8 @@ +// Stub for forging. TODO barrel crafting. +/obj/item/barrel_rim + name = "barrel rim" + desc = "A circular brace used to hold a barrel together." + icon_state = ICON_STATE_WORLD + icon = 'icons/obj/items/barrel_rim.dmi' + material = /decl/material/solid/metal/iron + material_alteration = MAT_FLAG_ALTERATION_ALL diff --git a/code/game/objects/items/crafting/shield_fasteners.dm b/code/game/objects/items/crafting/shield_fasteners.dm new file mode 100644 index 00000000000..b61b95c7995 --- /dev/null +++ b/code/game/objects/items/crafting/shield_fasteners.dm @@ -0,0 +1,8 @@ +// Stub for forging. TODO shield crafting. +/obj/item/shield_fasteners + name = "shield fasteners" + desc = "A handful of shaped fasteners used to hold a buckler or shield together." + icon_state = ICON_STATE_WORLD + icon = 'icons/obj/items/shield_fasteners.dmi' + material = /decl/material/solid/metal/iron + material_alteration = MAT_FLAG_ALTERATION_ALL diff --git a/code/game/objects/items/weapons/material/knives.dm b/code/game/objects/items/weapons/material/knives.dm index 9df9b46dc09..55513f693bd 100644 --- a/code/game/objects/items/weapons/material/knives.dm +++ b/code/game/objects/items/weapons/material/knives.dm @@ -101,7 +101,7 @@ //random stuff /obj/item/knife/hook - name = "meat hook" + name = "hook" desc = "A sharp, metal hook what sticks into things." icon = 'icons/obj/items/weapon/knives/hook.dmi' sharp = FALSE diff --git a/code/game/objects/structures/_structure_materials.dm b/code/game/objects/structures/_structure_materials.dm index f3c619b1f61..01f0e56f069 100644 --- a/code/game/objects/structures/_structure_materials.dm +++ b/code/game/objects/structures/_structure_materials.dm @@ -3,6 +3,7 @@ var/decl/material/reinf_material var/material_alteration var/dismantled + var/name_prefix /obj/structure/get_material() RETURN_TYPE(/decl/material) @@ -37,10 +38,14 @@ /obj/structure/proc/update_material_name(var/override_name) var/base_name = override_name || initial(name) + var/new_name if(istype(material)) - SetName("[material.adjective_name] [base_name]") + new_name = "[material.adjective_name] [base_name]" else - SetName(base_name) + new_name = base_name + if(name_prefix) + new_name = "[name_prefix] [new_name]" + SetName(new_name) /obj/structure/proc/update_material_desc(var/override_desc) var/base_desc = override_desc || initial(desc) diff --git a/code/game/objects/structures/fires.dm b/code/game/objects/structures/fires.dm index e0fe3055aa7..371058113b0 100644 --- a/code/game/objects/structures/fires.dm +++ b/code/game/objects/structures/fires.dm @@ -200,6 +200,9 @@ else to_chat(user, "\The [src] is empty.") + if(check_rights(R_DEBUG, 0, user)) + to_chat(user, "\The [src] has a temperature of [temperature]K, an effective burn temperature of [get_effective_burn_temperature()]K and a fuel value of [fuel].") + /obj/structure/fire_source/attack_hand(var/mob/user) var/list/removable_atoms = get_removable_atoms() diff --git a/code/modules/atmospherics/he_pipes.dm b/code/modules/atmospherics/he_pipes.dm index 55afc98fd37..54dd9a6c975 100644 --- a/code/modules/atmospherics/he_pipes.dm +++ b/code/modules/atmospherics/he_pipes.dm @@ -93,24 +93,7 @@ //fancy radiation glowing if(pipe_air.temperature && (icon_temperature > 500 || pipe_air.temperature > 500)) //start glowing at 500K if(abs(pipe_air.temperature - icon_temperature) > 10) - icon_temperature = pipe_air.temperature - var/scale = max((icon_temperature - 500) / 1500, 0) - - var/h_r = heat2color_r(icon_temperature) - var/h_g = heat2color_g(icon_temperature) - var/h_b = heat2color_b(icon_temperature) - - if(icon_temperature < 2000) //scale up overlay until 2000K - h_r = 64 + (h_r - 64)*scale - h_g = 64 + (h_g - 64)*scale - h_b = 64 + (h_b - 64)*scale - var/scale_color = rgb(h_r, h_g, h_b) - var/list/animate_targets = get_above_oo() + src - for (var/thing in animate_targets) - var/atom/movable/AM = thing - animate(AM, color = scale_color, time = 2 SECONDS, easing = SINE_EASING) - animate_filter("glow", list(color = scale_color, time = 2 SECONDS, easing = LINEAR_EASING)) - set_light(min(3, scale*2.5), min(3, scale*2.5), scale_color) + animate_heat_glow(pipe_air.temperature) else set_light(0, 0) diff --git a/code/modules/crafting/forging/forge_anvil.dm b/code/modules/crafting/forging/forge_anvil.dm new file mode 100644 index 00000000000..df9da5e0ba1 --- /dev/null +++ b/code/modules/crafting/forging/forge_anvil.dm @@ -0,0 +1,82 @@ +/obj/structure/anvil + name = "anvil" + desc = "A heavy block of material used as support for hammering things into shape." + icon = 'icons/obj/structures/anvil.dmi' + icon_state = ICON_STATE_WORLD + anchored = TRUE + density = TRUE + opacity = FALSE + atom_flags = ATOM_FLAG_CLIMBABLE + w_class = ITEM_SIZE_STRUCTURE //_LARGE + material = /decl/material/solid/metal/iron + max_health = 1000 + structure_flags = STRUCTURE_FLAG_SURFACE + material_alteration = MAT_FLAG_ALTERATION_ALL + +/obj/structure/anvil/on_update_icon() + . = ..() + icon_state = initial(icon_state) + switch(get_health_percent()) + if(0 to 0.35) + icon_state = "[icon_state]-damage-heavy" + if(0.35 to 0.65) + icon_state = "[icon_state]-damage-light" + +/obj/structure/anvil/attackby(obj/item/used_item, mob/user, click_params) + + // Put the hammer on the forge. + if(!user.check_intent(I_FLAG_HARM) && istype(used_item, /obj/item/tool/hammer/forge)) + if(user.try_unequip(used_item, get_turf(src))) + auto_align(used_item, click_params) + return TRUE + + // Put the bar onto the anvil (need to do this to avoid repairs in ..()) + if(istype(used_item, /obj/item/stack/material/bar)) + var/obj/item/stack/material/bar/bar = used_item + if(used_item.material != material || current_health >= get_max_health()) + if(bar.get_amount() > 1) + bar = bar.split(1) + if(bar.loc == user) + user.try_unequip(bar, get_turf(src)) + else + bar.dropInto(get_turf(src)) + auto_align(bar, click_params) + return TRUE + + // Place a partially worked billet onto the anvil. + if(istype(used_item, /obj/item/billet)) + if(used_item.loc == user) + user.try_unequip(used_item, get_turf(src)) + else + used_item.dropInto(get_turf(src)) + auto_align(used_item, click_params) + return TRUE + + // Put the bar from tongs onto the anvil. + if(istype(used_item, /obj/item/tongs)) + var/obj/item/tongs/tongs = used_item + if(tongs.holding_bar) + return attackby(tongs.holding_bar, user) + + . = ..() + +// Chipped out of a boulder with a pick. +/obj/structure/anvil/boulder + name_prefix = "crude" + icon = 'icons/obj/structures/anvil_crude.dmi' + desc = "A crude anvil chipped out of a chunk of stone. It probably won't last very long." + material = /decl/material/solid/stone/granite + max_health = 500 + +/obj/structure/anvil/boulder/Initialize(ml, _mat, _reinf_mat) + . = ..() + if(prob(50)) + set_icon('icons/obj/structures/anvil_crude_alt.dmi') + +// Improvised with spaceman materials. +/obj/structure/anvil/improvised + name_prefix = "improvised" + icon = 'icons/obj/structures/anvil_improvised.dmi' + desc = "A anvil roughly improvised out of scrap metal. It probably won't last very long." + material = /decl/material/solid/metal/steel + max_health = 500 diff --git a/code/modules/crafting/forging/forge_bars.dm b/code/modules/crafting/forging/forge_bars.dm new file mode 100644 index 00000000000..a3ffa1590b1 --- /dev/null +++ b/code/modules/crafting/forging/forge_bars.dm @@ -0,0 +1,62 @@ +/obj/item/stack/material/bar/get_thermal_mass_coefficient() + return hot_enough_to_forge() ? 0.1 : ..() + +// Also copied from billets. +/obj/item/stack/material/bar/ProcessAtomTemperature() + . = ..() + if(get_amount() == 1 && istype(material) && material.forgable) + // We have no real way to find temperature bounds without a material that has a melting point. + if(!istype(material) || isnull(material.melting_point) || QDELETED(src)) + return + var/temperature_percentage + if(temperature >= material.melting_point) // We should have melted... + temperature_percentage = 1 + else if(temperature <= T20C) // Arbitrary point for the sake of not trying to find a proportional temperature delta with ice + temperature_percentage = 0 + else + temperature_percentage = (material.melting_point - T20C) / (temperature - T20C) + if(temperature_percentage < 0.25) + set_light(0, 0) + else + animate_heat_glow(temperature, scale_sub = round((material.melting_point - T20C) * 0.25) + T20C, scale_div = round(material.melting_point * 0.75), scale_max = material.melting_point, skip_filter = TRUE) + else + set_light(0, 0) + if(istype(loc, /obj/item/tongs)) + loc.update_icon() + +// Copied from billets. +/obj/item/stack/material/bar/proc/hot_enough_to_forge() + if(!istype(material) || isnull(material.melting_point) || !material.forgable) + return FALSE + // Needs to be halfway to melting to count as forgable. + return temperature >= ((material.melting_point - T20C) * 0.5) + T20C + +// Some bar overrides to allow for the raw form, before they become billets. +/obj/item/stack/material/bar/attackby(obj/item/used_item, mob/user) + // Turn this into a billet for step 1 of forging. + if(isturf(loc) && get_amount() == 1) + + // Picking up in tongs. + if(istype(used_item, /obj/item/tongs)) + var/obj/item/tongs/tongs = used_item + if(!tongs.holding_bar) + forceMove(tongs) + tongs.holding_bar = src + tongs.update_icon() + return TRUE + + // Hammering into a billet. + if(istype(used_item, /obj/item/tool/hammer/forge) && (locate(/obj/structure/anvil) in loc) && material?.forgable) + var/obj/item/billet/billet = new(get_turf(src), material?.type) + billet.matter = matter?.Copy() // Avoid creating or losing matter via reshaping. + billet.pixel_x = pixel_x + billet.pixel_y = pixel_y + billet.pixel_w = pixel_w + billet.pixel_z = pixel_z + billet.temperature = temperature + if(paint_color) + billet.paint_color = paint_color + billet.update_icon() + qdel(src) + return billet.attackby(used_item, user) + . = ..() diff --git a/code/modules/crafting/forging/forge_billet.dm b/code/modules/crafting/forging/forge_billet.dm new file mode 100644 index 00000000000..b754ae8f6ef --- /dev/null +++ b/code/modules/crafting/forging/forge_billet.dm @@ -0,0 +1,196 @@ +/obj/item/billet + name = "billet" + desc = "An unworked or partially-worked length of metal used to forge items and tools." + icon = 'icons/obj/items/billet.dmi' + icon_state = ICON_STATE_WORLD + material = /decl/material/solid/metal/iron + material_alteration = MAT_FLAG_ALTERATION_ALL + var/decl/forging_step/current_forging_step = /decl/forging_step/billet + +/obj/item/billet/Initialize(ml, material_key) + if(ispath(current_forging_step)) + set_forging_step(current_forging_step, force = TRUE) + . = ..() + +/obj/item/billet/proc/set_forging_step(decl/forging_step/new_step, force) + if(ispath(new_step)) + new_step = GET_DECL(new_step) + if(!istype(new_step) || (!force && current_forging_step == new_step)) + return FALSE + current_forging_step = new_step + . = current_forging_step.apply_to(src) + if(!QDELETED(src)) + update_icon() + update_name() + +/obj/item/billet/proc/hot_enough_to_forge() + if(!istype(material) || isnull(material.melting_point) || !material.forgable) + return FALSE + // Needs to be halfway to melting to count as forgable. + return temperature >= ((material.melting_point - T20C) * 0.5) + T20C + +/obj/item/billet/attackby(obj/item/used_item, mob/user) + + // Picking up in tongs. + if(istype(used_item, /obj/item/tongs)) + var/obj/item/tongs/tongs = used_item + if(tongs.holding_bar) + return ..() + var/mob/holder = loc + if(istype(holder)) + if(!holder.try_unequip(src, tongs)) + return TRUE + else if(loc?.storage) + if(!loc.storage.remove_from_storage(user, src, tongs)) + return TRUE + else + forceMove(tongs) + if(loc == tongs) + tongs.holding_bar = src + tongs.update_icon() + return TRUE + + if(!istype(used_item, /obj/item/tool/hammer/forge) || user.check_intent(I_FLAG_HARM)) + return ..() + + // Check for surface. + var/obj/structure/anvil/anvil = locate() in loc + if(!istype(anvil)) + to_chat(user, SPAN_WARNING("\The [src] can only be worked on an anvil.")) + return TRUE + + // Check for heat. + if(!hot_enough_to_forge()) + to_chat(user, SPAN_WARNING("\The [src] is too cold to be worked on the anvil.")) + return TRUE + + // Sanity check. + if(!length(current_forging_step?.steps)) // how tho + to_chat(user, SPAN_WARNING("You cannot see any further way to refine \the [src].")) + return TRUE + + // Handle the actual forging process. + var/last_step = current_forging_step + var/decl/forging_action/next_action = show_radial_menu(user, src, current_forging_step.get_radial_choices(), radius = 42, use_labels = RADIAL_LABELS_CENTERED, require_near = TRUE, check_locs = list(src)) + + if(!standard_forging_checks(user, used_item, last_step, next_action, anvil)) + return TRUE + + if(user.get_stamina() < 10) + to_chat(user, SPAN_WARNING("You are too exhausted to swing \the [used_item].")) + return TRUE + user.adjust_stamina(-10) + + playsound(src, 'sound/effects/clang.ogg', 100, 1) + user.do_attack_animation(anvil, used_item) + spark_at(get_turf(loc), amount = 1, spark_type = /datum/effect/effect/system/spark_spread/silent) + // TODO: shake the billet/anvil? + + + // Skill checks! + var/decl/forging_step/next_step = current_forging_step.steps[next_action] + if(!user.do_skilled(3 SECONDS, next_step.work_skill, anvil)) + return TRUE + if(!istype(next_step) || (next_step.skill_fail_prob && user.skill_fail_prob(next_step.work_skill, next_step.skill_fail_prob, next_step.skill_level, next_step.skill_factor))) + to_chat(user, SPAN_WARNING("You fumble the work and fail to reshape \the [src].")) + return TRUE + + // Since we have a sleep() above, we recheck our basic conditions. + if(!standard_forging_checks(user, used_item, last_step, next_action, anvil)) + return TRUE + + if(user.get_stamina() < 10) + to_chat(user, SPAN_WARNING("You are too exhausted to keep swinging \the [used_item].")) + return TRUE + user.adjust_stamina(-10) + + playsound(src, 'sound/effects/clang.ogg', 100, 1) + user.do_attack_animation(anvil, used_item) + spark_at(get_turf(loc), amount = 1, spark_type = /datum/effect/effect/system/spark_spread/silent) + // TODO: shake the billet/anvil? + + // Update the billet (which may produce an item!) + var/obj/item/forged_thing = set_forging_step(next_step) + if(istype(forged_thing)) + user.visible_message(SPAN_NOTICE("\The [user] has [next_action.work_verb] the billet into \a [forged_thing].")) + // Forging gradually degrades anvils. + if(!QDELETED(anvil)) + anvil.take_damage(rand(10, 20), BRUTE) + return TRUE + +/obj/item/billet/examine(mob/user, distance, infix, suffix) + . = ..() + for(var/decl/forging_action/action in current_forging_step?.steps) + var/decl/forging_step/next_step = current_forging_step.steps[action] + if(user.skill_check(next_step.work_skill, next_step.skill_level)) + to_chat(user, SPAN_INFO("It can be [action.work_verb] into \a [next_step.get_product_name(material)] on an anvil.")) + +/obj/item/billet/proc/standard_forging_checks(mob/user, obj/item/used_item, decl/forging_step/last_step, decl/forging_action/next_action, obj/structure/anvil/anvil) + // We cancelled or changed state, abort. + if(!next_action || current_forging_step != last_step || !(next_action in current_forging_step.steps)) + return FALSE + // Something has been destroyed since we started forging. + if(QDELETED(src) || QDELETED(used_item) || QDELETED(anvil) || QDELETED(user)) + return FALSE + // Something else has changed, very unfortunate. + if(loc != anvil.loc || !CanPhysicallyInteract(user) || user.get_active_held_item() != used_item) + return FALSE + return hot_enough_to_forge() + +/obj/item/billet/on_update_icon() + . = ..() + if(!istype(current_forging_step)) + return + if(current_forging_step.billet_icon) + set_icon(current_forging_step.billet_icon) + else + set_icon(initial(icon)) + icon_state = get_world_inventory_state() + if(current_forging_step.billet_icon_state) + icon_state = "[icon_state]-[current_forging_step.billet_icon_state]" + +/obj/item/billet/get_world_inventory_state() + if(!current_forging_step?.billet_icon_state) + return ..() + if(!check_state_in_icon("[ICON_STATE_INV]-[current_forging_step.billet_icon_state]", icon)) + return ICON_STATE_WORLD + return ..() + +/obj/item/billet/update_name() + if(!istype(current_forging_step)) + base_name = initial(base_name) + name_prefix = initial(name_prefix) + desc = initial(desc) + return ..() + base_name = current_forging_step.billet_name + name_prefix = current_forging_step.billet_name_prefix + . = ..() + desc = current_forging_step.billet_desc + if(istype(material)) + desc = "[desc] This one is made of [material.solid_name]." + +/obj/item/billet/ProcessAtomTemperature() + . = ..() + // We have no real way to find temperature bounds without a material that has a melting point. + if(!istype(material) || isnull(material.melting_point) || QDELETED(src)) + set_light(0, 0) + if(istype(loc, /obj/item/tongs)) + loc.update_icon() + return + var/temperature_percentage + if(temperature >= material.melting_point) // We should have melted... + temperature_percentage = 1 + else if(temperature <= T20C) // Arbitrary point for the sake of not trying to find a proportional temperature delta with ice + temperature_percentage = 0 + else + temperature_percentage = (material.melting_point - T20C) / (temperature - T20C) + if(temperature_percentage < 0.25) + set_light(0, 0) + else + animate_heat_glow(temperature, scale_sub = round((material.melting_point - T20C) * 0.25) + T20C, scale_div = round(material.melting_point * 0.75), scale_max = material.melting_point, skip_filter = TRUE) + if(istype(loc, /obj/item/tongs)) + loc.update_icon() + +// Arbitrary value to give people enough time to forge the bloody thing. +/obj/item/billet/get_thermal_mass_coefficient() + return hot_enough_to_forge() ? 0.1 : ..() diff --git a/code/modules/crafting/forging/forge_fire.dm b/code/modules/crafting/forging/forge_fire.dm new file mode 100644 index 00000000000..993049defe7 --- /dev/null +++ b/code/modules/crafting/forging/forge_fire.dm @@ -0,0 +1,83 @@ +/datum/storage/forge + can_hold = list( + /obj/item/stack/material/bar, + /obj/item/billet + ) + max_storage_space = ITEM_SIZE_NORMAL * 10 // Fairly spacious + max_w_class = ITEM_SIZE_LARGE + +/datum/storage/forge/consolidate_stacks() + return // We want to keep them as single bars. + +/obj/structure/fire_source/forge + name = "forge fire" + desc = "A sturdy hearth used to heat metal bars for forging on an anvil." + density = TRUE + icon = 'icons/obj/structures/forging/forge.dmi' + icon_state = "forge" + storage = /datum/storage/forge + +/obj/structure/fire_source/forge/proc/get_forgable_contents() + . = list() + for(var/obj/item/thing in get_stored_inventory()) + if(thing.material?.forgable && (istype(thing, /obj/item/billet) || istype(thing, /obj/item/stack/material/bar))) + . += thing + +/obj/structure/fire_source/forge/attackby(obj/item/used_item, mob/user) + + // Raw materials. + if(istype(used_item, /obj/item/stack/material/bar)) + var/obj/item/stack/material/bar/bar = used_item + if(bar.get_amount() > 1) + bar = bar.split(1) + if(!bar) + return TRUE + if(bar.loc == user) + user.try_unequip(bar, loc) + else + bar.dropInto(loc) + if(storage.can_be_inserted(bar, user)) + storage.handle_item_insertion(user, bar) + update_icon() + return TRUE + + // Partially worked billets. + if(istype(used_item, /obj/item/billet)) + if(used_item.loc == user) + user.try_unequip(used_item, loc) + else + used_item.dropInto(loc) + if(storage.can_be_inserted(used_item, user)) + storage.handle_item_insertion(user, used_item) + update_icon() + return TRUE + + // Tongs holding bars or partially worked billets. + if(istype(used_item, /obj/item/tongs) && !user.check_intent(I_FLAG_HARM)) + + // Put whatever's in the tongs into storage. + var/obj/item/tongs/tongs = used_item + if(tongs.holding_bar) + return attackby(tongs.holding_bar, user) + + // Check if we have any bars. + var/list/bars = get_forgable_contents() + if(!length(bars)) + to_chat(user, SPAN_WARNING("There are no bars in \the [src] to retrieve.")) + return TRUE + + // Get the hottest bar. + var/obj/item/hottest_bar + for(var/obj/item/bar in bars) + if(!hottest_bar || bar.temperature > hottest_bar) + hottest_bar = bar + + // Extract a single bar from the forge with the tongs. + if(storage.remove_from_storage(user, hottest_bar, tongs)) + tongs.holding_bar = hottest_bar + if(tongs.holding_bar) + user.visible_message(SPAN_NOTICE("\The [user] pulls \the [tongs.holding_bar] from \the [src] with \the [tongs].")) + tongs.update_icon() + return TRUE + + return ..() diff --git a/code/modules/crafting/forging/forge_tools.dm b/code/modules/crafting/forging/forge_tools.dm new file mode 100644 index 00000000000..2dea8ef2628 --- /dev/null +++ b/code/modules/crafting/forging/forge_tools.dm @@ -0,0 +1,37 @@ +/obj/item/tool/hammer/forge + name = "forging hammer" + desc = "A heavy hammer, used to forge hot metal at an anvil." + icon = 'icons/obj/items/tool/hammers/forge.dmi' + w_class = ITEM_SIZE_NORMAL + +/obj/item/tongs + name = "tongs" + desc = "Long-handled grippers well suited to fishing white-hot iron out of a forge fire." + icon = 'icons/obj/items/tongs.dmi' + material = /decl/material/solid/metal/iron + obj_flags = OBJ_FLAG_INSULATED_HANDLE + material_alteration = MAT_FLAG_ALTERATION_ALL + var/obj/item/holding_bar + +/obj/item/tongs/on_update_icon() + . = ..() + if(holding_bar) + // Note, not get_color(); heat color is temporarily applied over the top of base color. + add_overlay(overlay_image(icon, "[icon_state]-bar", holding_bar.color, RESET_COLOR)) + +/obj/item/tongs/adjust_mob_overlay(mob/living/user_mob, bodytype, image/overlay, slot, bodypart, use_fallback_if_icon_missing) + if(overlay && holding_bar) + var/check_state = "[overlay.icon_state]-bar" + if(check_state_in_icon(check_state, overlay.icon)) + overlay.overlays += overlay_image(overlay.icon, check_state, holding_bar.get_color(), RESET_COLOR) + . = ..() + +/obj/item/tongs/Exited(atom/movable/AM, atom/new_loc) + . = ..() + if(AM == holding_bar) + holding_bar = null + update_icon() + +/obj/item/tongs/Destroy() + QDEL_NULL(holding_bar) + . = ..() diff --git a/code/modules/crafting/forging/forging_action.dm b/code/modules/crafting/forging/forging_action.dm new file mode 100644 index 00000000000..1878f83dce3 --- /dev/null +++ b/code/modules/crafting/forging/forging_action.dm @@ -0,0 +1,20 @@ +/decl/forging_action + abstract_type = /decl/forging_action + var/name + var/work_verb + +/decl/forging_action/drawing + name = "draw" + work_verb = "drawn" + +/decl/forging_action/bending + name = "bend" + work_verb = "bent" + +/decl/forging_action/punching + name = "punch" + work_verb = "punched" + +/decl/forging_action/upsetting + name = "upset" + work_verb = "upset" diff --git a/code/modules/crafting/forging/forging_step.dm b/code/modules/crafting/forging/forging_step.dm new file mode 100644 index 00000000000..564619af02a --- /dev/null +++ b/code/modules/crafting/forging/forging_step.dm @@ -0,0 +1,306 @@ +/decl/forging_step + abstract_type = /decl/forging_step + /// Base name to use for the billet at this stage. + var/billet_name = "billet" + /// Name prefix to use for the billet at this stage. + var/billet_name_prefix + /// Description to use for the billet at this stage. + var/billet_desc + /// Icon state modifier to use for the billet at this stage. + var/billet_icon_state + /// Icon to use for the billet (for modpacks/downstreams) + var/billet_icon = 'icons/obj/items/billet.dmi' + /// Assoc list of /decl/forging_action to next /decl/forging_step. + var/list/steps + /// Probability of failing this step if we're below skill_level. + var/skill_fail_prob = 30 + /// Impact of skill against probability of failure. + var/skill_factor = 1 + /// Skill level where failing this step becomes impossible. + var/skill_level = SKILL_ADEPT + /// What skill this step requires. + var/work_skill = SKILL_CONSTRUCTION + +/decl/forging_step/Initialize() + + // Resolve our types now to get it out of the way. + for(var/action in steps) + var/decl/forging_action/action_decl = GET_DECL(action) + var/decl/forging_step/step_decl = steps[action] + step_decl = GET_DECL(step_decl) + steps -= action + if(istype(action_decl) && istype(step_decl)) + steps[action_decl] = step_decl + + . = ..() + +/decl/forging_step/validate() + . = ..() + if(!istext(billet_name)) + . += "null or invalid billet_name" + if(!istext(billet_desc)) + . += "null or invalid billet_desc" + if(!length(steps)) + . += "null or empty steps list" + if(billet_icon_state) + if(billet_icon) + if(!check_state_in_icon("[ICON_STATE_WORLD]-[billet_icon_state]", billet_icon)) + . += "missing billet icon state '[ICON_STATE_WORLD]-[billet_icon_state]' from icon '[billet_icon]'" + else + . += "missing billet_icon" + +/decl/forging_step/proc/get_product_name(decl/material/billet_material) + . = billet_name + if(billet_material) + . = "[billet_material.adjective_name] [.]" + if(billet_name_prefix) + . = "[billet_name_prefix] [.]" + +/decl/forging_step/proc/get_radial_choices() + for(var/decl/forging_action/action in steps) + var/image/radial_button = new + radial_button.name = capitalize(action.name) + LAZYSET(., action, radial_button) + +/decl/forging_step/proc/apply_to(obj/item/billet/billet) + return billet + +/decl/forging_step/billet + billet_desc = "An unworked length of metal used to forge items and tools." + steps = list( + /decl/forging_action/drawing = /decl/forging_step/thin_billet, + /decl/forging_action/bending = /decl/forging_step/curved_billet, + /decl/forging_action/punching = /decl/forging_step/flat_bar, + /decl/forging_action/upsetting = /decl/forging_step/punched_billet + ) + +/decl/forging_step/thin_billet + billet_name_prefix = "thin" + billet_icon_state = "thin" + billet_desc = "A thin, elongated length of metal used to forge items and tools." + steps = list( + /decl/forging_action/drawing = /decl/forging_step/blade_blank, + /decl/forging_action/bending = /decl/forging_step/ornate_blank, + /decl/forging_action/punching = /decl/forging_step/product/nails + ) + +/decl/forging_step/curved_billet + billet_name_prefix = "curved" + billet_icon_state = "curved" + billet_desc = "A curved length of metal used to forge items and tools." + steps = list( + /decl/forging_action/drawing = /decl/forging_step/product/hook, + /decl/forging_action/bending = /decl/forging_step/product/chain, + /decl/forging_action/punching = /decl/forging_step/product/rim, + /decl/forging_action/upsetting = /decl/forging_step/product/horseshoe + ) + +/decl/forging_step/flat_bar + billet_name = "bar" + billet_name_prefix = "flat" + billet_icon_state = "flat" + billet_desc = "A flattened bar of metal used to forge items and tools." + steps = list( + /decl/forging_action/drawing = /decl/forging_step/armour_plates, + /decl/forging_action/bending = /decl/forging_step/armour_segments, + /decl/forging_action/punching = /decl/forging_step/product/lock_and_key, + /decl/forging_action/upsetting = /decl/forging_step/product/shield_fasteners + ) + +/decl/forging_step/punched_billet + billet_name_prefix = "punched" + billet_icon_state = "punched" + steps = list( + /decl/forging_action/drawing = /decl/forging_step/tool_head_blank, + /decl/forging_action/bending = /decl/forging_step/product/tongs, + /decl/forging_action/punching = /decl/forging_step/product/chisel + ) + +/decl/forging_step/blade_blank + billet_name = "blade blank" + billet_icon_state = "blade" + billet_desc = "A roughly shaped, dull blade. It will need further refinement before it can be finished." + steps = list( + /decl/forging_action/drawing = /decl/forging_step/long_blade_blank, + /decl/forging_action/bending = /decl/forging_step/axe_blade_blank, + /decl/forging_action/punching = /decl/forging_step/short_sword_blank + ) + +/decl/forging_step/long_blade_blank + // shortsword + // longsword + // broadsword + // rapier + +/decl/forging_step/axe_blade_blank + // war axe blade + // halberd blade + // pickaxe blade + +/decl/forging_step/short_sword_blank + // poingard + // knife blade + // arrow head + // spearhead + +/decl/forging_step/ornate_blank + billet_name = "blank" + billet_name_prefix = "ornate" + billet_icon_state = "ornate" + billet_desc = "An ornate piece of worked metal. It still needs some last touches to be made into something useful." + // candelabra + +/decl/forging_step/armour_plates + billet_desc = "A set of worked metal plates, a few steps and fittings away from forming some kind of armour." + billet_name = "armour plates" + billet_icon_state = "armour" + steps = list( + /decl/forging_action/drawing = /decl/forging_step/product/breastplate, + /decl/forging_action/bending = /decl/forging_step/product/cuirass, + /decl/forging_action/punching = /decl/forging_step/product/banded + ) + +/decl/forging_step/armour_segments + billet_desc = "A set of small worked metal plates, a few steps and fittings away from forming a helmet, or arm or leg armour." + billet_name = "armour segments" + billet_icon_state = "helmet" + steps = list( + /decl/forging_action/drawing = /decl/forging_step/product/helmet, + /decl/forging_action/bending = /decl/forging_step/product/sabatons, + /decl/forging_action/punching = /decl/forging_step/product/vambraces + ) + +/decl/forging_step/tool_head_blank + billet_desc = "A heavy piece of shaped metal, almost suitable for use as the head of a tool. It still needs some last touches to be made into something useful." + billet_name = "tool head blank" + billet_icon_state = "tool_head" + steps = list( + /decl/forging_action/drawing = /decl/forging_step/product/hoe_head, + /decl/forging_action/bending = /decl/forging_step/product/shovel_head, + /decl/forging_action/punching = /decl/forging_step/hammer_head_blank, + /decl/forging_action/upsetting = /decl/forging_step/product/chisel_head + ) + +/decl/forging_step/hammer_head_blank + steps = list( + /decl/forging_action/drawing = /decl/forging_step/product/pickaxe_head, + /decl/forging_action/bending = /decl/forging_step/product/sledge_head, + /decl/forging_action/punching = /decl/forging_step/product/hammer_head, + /decl/forging_action/upsetting = /decl/forging_step/product/forging_hammer_head + ) + + +// hammer head blank + // hammer head + // sledge head + // forging hammer head + +// There are effectively finished products. +/decl/forging_step/product + // Dummy strings to avoid validate() fails; shouldn't be used anywhere. + billet_name = "finished product" + billet_desc = "A finished product." + abstract_type = /decl/forging_step/product + var/product_type = /obj/item/stick + +/decl/forging_step/product/get_product_name(decl/material/billet_material) + return atom_info_repository.get_name_for(product_type, billet_material?.type) + +/decl/forging_step/product/apply_to(obj/item/billet/billet) + var/obj/item/thing = new product_type(null, billet.material?.type) + thing.dropInto(billet.loc) + thing.pixel_x = billet.pixel_x + thing.pixel_y = billet.pixel_y + thing.pixel_w = billet.pixel_w + thing.pixel_z = billet.pixel_z + thing.temperature = billet.temperature + if(billet.paint_color) + thing.paint_color = billet.paint_color + thing.update_icon() + qdel(billet) + thing.base_name = billet_name + thing.update_name() + return thing + +/decl/forging_step/product/nails + billet_name = "nails" + product_type = /obj/item/stack/material/nail/twelve + +/decl/forging_step/product/hook + billet_name = "hook" + product_type = /obj/item/hook + +/decl/forging_step/product/chain + billet_name = "chain" + product_type = /obj/item/chain + +/decl/forging_step/product/rim + billet_name = "barrel rim" + product_type = /obj/item/barrel_rim + +/decl/forging_step/product/horseshoe + billet_name = "horseshoe" + product_type = /obj/item/horseshoe + +/decl/forging_step/product/shield_fasteners + billet_name = "shield fasteners" + product_type = /obj/item/shield_fasteners + +/decl/forging_step/product/tongs + billet_name = "tongs" + product_type = /obj/item/tongs + +/decl/forging_step/product/chisel + billet_name = "chisel" + product_type = /obj/item/tool/chisel/forged + +/decl/forging_step/product/lock_and_key + billet_name = "lock and key" + +/decl/forging_step/product/breastplate + product_type = /obj/item/clothing/suit/armor/forged/breastplate + +/decl/forging_step/product/cuirass + product_type = /obj/item/clothing/suit/armor/forged/cuirass + +/decl/forging_step/product/banded + product_type = /obj/item/clothing/suit/armor/forged/banded + +/decl/forging_step/product/helmet + product_type = /obj/item/clothing/head/helmet/plumed + +/decl/forging_step/product/sabatons + product_type = /obj/item/clothing/shoes/sabatons + +/decl/forging_step/product/vambraces + product_type = /obj/item/clothing/gloves/vambrace + +/decl/forging_step/product/hoe_head + product_type = /obj/item/clothing/gloves/vambrace + +/decl/forging_step/product/shovel_head + product_type = /obj/item/clothing/gloves/vambrace + +/decl/forging_step/product/chisel_head + product_type = /obj/item/clothing/gloves/vambrace + +/decl/forging_step/product/hammer_head + product_type = /obj/item/tool_component/head/hammer + +/decl/forging_step/product/shovel_head + product_type = /obj/item/tool_component/head/shovel + +/decl/forging_step/product/hoe_head + product_type = /obj/item/tool_component/head/hoe + +/decl/forging_step/product/handaxe_head + product_type = /obj/item/tool_component/head/handaxe + +/decl/forging_step/product/pickaxe_head + product_type = /obj/item/tool_component/head/pickaxe + +/decl/forging_step/product/sledge_head + product_type = /obj/item/tool_component/head/sledgehammer + +/decl/forging_step/product/forging_hammer_head + product_type = /obj/item/tool_component/head/forging_hammer diff --git a/code/modules/crafting/slapcrafting/crafting_recipes/tool_crafting/_tool_crafting.dm b/code/modules/crafting/slapcrafting/crafting_recipes/tool_crafting/_tool_crafting.dm index ad2cff20aa6..5472d992243 100644 --- a/code/modules/crafting/slapcrafting/crafting_recipes/tool_crafting/_tool_crafting.dm +++ b/code/modules/crafting/slapcrafting/crafting_recipes/tool_crafting/_tool_crafting.dm @@ -39,6 +39,10 @@ var/global/list/_tool_crafting_components = list( /obj/item/tool/axe = list( /obj/item/tool_component/head/handaxe, /obj/item/tool_component/handle/short + ), + /obj/item/tool/hammer/forge = list( + /obj/item/tool_component/head/forging_hammer, + /obj/item/tool_component/handle/short ) ) diff --git a/code/modules/crafting/stack_recipes/recipes_soft.dm b/code/modules/crafting/stack_recipes/recipes_soft.dm index 9672f23017e..eb38479fe9e 100644 --- a/code/modules/crafting/stack_recipes/recipes_soft.dm +++ b/code/modules/crafting/stack_recipes/recipes_soft.dm @@ -44,6 +44,12 @@ result_type = /obj/item/stack/material/brick test_result_type = /obj/item/stack/material/brick/clay +/decl/stack_recipe/soft/bar + name = "bar" + name_plural = "bars" + result_type = /obj/item/stack/material/bar + test_result_type = /obj/item/stack/material/bar/wax + /decl/stack_recipe/soft/stack/spawn_result(mob/user, location, amount, decl/material/mat, decl/material/reinf_mat, paint_color, spent_type, spent_amount = 1) var/obj/item/stack/S = ..() if(istype(S)) diff --git a/code/modules/crafting/stack_recipes/recipes_steel.dm b/code/modules/crafting/stack_recipes/recipes_steel.dm index 9befbe3d3f1..534e362bfc8 100644 --- a/code/modules/crafting/stack_recipes/recipes_steel.dm +++ b/code/modules/crafting/stack_recipes/recipes_steel.dm @@ -116,3 +116,7 @@ /decl/stack_recipe/steel/furniture/target_stake result_type = /obj/structure/target_stake difficulty = MAT_VALUE_NORMAL_DIY + +/decl/stack_recipe/steel/furniture + result_type = /obj/structure/anvil/improvised + difficulty = MAT_VALUE_HARD_DIY diff --git a/code/modules/materials/_materials.dm b/code/modules/materials/_materials.dm index b17f238fcb4..17586c02038 100644 --- a/code/modules/materials/_materials.dm +++ b/code/modules/materials/_materials.dm @@ -371,6 +371,8 @@ INITIALIZE_IMMEDIATE(/obj/effect/gas_overlay) /// Assoc weighted list of gemstone material types to weighting. var/list/gemstone_types + var/forgable = FALSE // Can this material be forged in bar/billet form? + // Placeholders for light tiles and rglass. /decl/material/proc/reinforce(var/mob/user, var/obj/item/stack/material/used_stack, var/obj/item/stack/material/target_stack, var/use_sheets = 1) if(!used_stack.can_use(use_sheets)) @@ -444,6 +446,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/gas_overlay) burn_product = null vapor_products = null compost_value = 0 + forgable = FALSE else if(isnull(temperature_damage_threshold)) var/new_temperature_damage_threshold = max(melting_point, boiling_point, heating_point) // Don't let the threshold be lower than the ignition point. diff --git a/code/modules/materials/definitions/solids/materials_solid_metal.dm b/code/modules/materials/definitions/solids/materials_solid_metal.dm index ee7d435e09d..5e7aa766a45 100644 --- a/code/modules/materials/definitions/solids/materials_solid_metal.dm +++ b/code/modules/materials/definitions/solids/materials_solid_metal.dm @@ -20,6 +20,7 @@ icon_reinf = 'icons/turf/walls/reinforced_metal.dmi' exoplanet_rarity_gas = MAT_RARITY_NOWHERE tensile_strength = 0.8 // metal wire is probably better than plastic? + forgable = TRUE /decl/material/solid/metal/uranium name = "uranium" diff --git a/code/modules/materials/material_sheets_mapping.dm b/code/modules/materials/material_sheets_mapping.dm index 1e17f9f3851..49be21fa79f 100644 --- a/code/modules/materials/material_sheets_mapping.dm +++ b/code/modules/materials/material_sheets_mapping.dm @@ -122,6 +122,9 @@ STACK_SUBTYPES(titanium, "titanium", solid/metal/tita STACK_SUBTYPES(cotton, "cotton", solid/organic/cloth, thread, null) STACK_SUBTYPES(dried_gut, "dried gut", solid/organic/leather/gut, thread, null) +STACK_SUBTYPES(iron, "iron", solid/metal/iron, bar, null) +STACK_SUBTYPES(copper, "copper", solid/metal/copper, bar, null) + STACK_SUBTYPES(chipboard_oak, "oak chipboard", solid/organic/wood/chipboard, sheet, null) STACK_SUBTYPES(chipboard_maple, "maple chipboard", solid/organic/wood/chipboard/maple, sheet, null) STACK_SUBTYPES(chipboard_mahogany, "mahogany chipboard", solid/organic/wood/chipboard/mahogany, sheet, null) @@ -129,4 +132,5 @@ STACK_SUBTYPES(chipboard_ebony, "ebony chipboard", solid/organic/wood/chip STACK_SUBTYPES(chipboard_walnut, "walnut chipboard", solid/organic/wood/chipboard/walnut, sheet, null) STACK_SUBTYPES(chipboard_yew, "yew chipboard", solid/organic/wood/chipboard/yew, sheet, null) + #undef STACK_SUBTYPES \ No newline at end of file diff --git a/code/modules/materials/material_stack_nail.dm b/code/modules/materials/material_stack_nail.dm new file mode 100644 index 00000000000..fb0efb77882 --- /dev/null +++ b/code/modules/materials/material_stack_nail.dm @@ -0,0 +1,16 @@ +/obj/item/stack/material/nail + name = "nails" + singular_name = "nail" + plural_name = "nails" + icon_state = "nail" + plural_icon_state = "nail-mult" + max_icon_state = "nail-max" + stack_merge_type = /obj/item/stack/material/nail + crafting_stack_type = /obj/item/stack/material/nail + icon = 'icons/obj/items/stacks/nails.dmi' + is_spawnable_type = TRUE + matter_multiplier = 0.05 // 20 per standard sheet + material = /decl/material/solid/metal/iron + +/obj/item/stack/material/nail/twelve + amount = 12 diff --git a/code/modules/tools/components/head.dm b/code/modules/tools/components/head.dm index 8f594c1f2e7..83fcc5ebb29 100644 --- a/code/modules/tools/components/head.dm +++ b/code/modules/tools/components/head.dm @@ -62,3 +62,9 @@ var/global/list/_tool_properties_cache = list() desc = "The head of a sledgehammer." icon_state = "sledgehammer" w_class = ITEM_SIZE_NORMAL + +/obj/item/tool_component/head/forging_hammer + name = "forging hammer head" + desc = "The head of a forging hammer." + icon_state = "sledgehammer" + w_class = ITEM_SIZE_NORMAL diff --git a/code/modules/xenoarcheaology/boulder.dm b/code/modules/xenoarcheaology/boulder.dm index ba7a38c2f3d..fe2ab49f360 100644 --- a/code/modules/xenoarcheaology/boulder.dm +++ b/code/modules/xenoarcheaology/boulder.dm @@ -98,3 +98,27 @@ var/mob/living/silicon/robot/R = AM if(IS_PICK(R.module_active)) attackby(R.module_active,R) + +/obj/structure/boulder/get_alt_interactions(mob/user) + . = ..() + LAZYADD(., /decl/interaction_handler/chip_anvil) + +/decl/interaction_handler/chip_anvil + name = "Chip Into Anvil" + expected_target_type = /obj/structure/boulder + var/work_skill = SKILL_CONSTRUCTION + +/decl/interaction_handler/chip_anvil/is_possible(atom/target, mob/user, obj/item/prop) + . = ..() && istype(prop) && IS_PICK(prop) && prop.material?.hardness >= target.get_material()?.hardness && user.skill_check(work_skill, SKILL_BASIC) + +/decl/interaction_handler/chip_anvil/invoked(atom/target, mob/user, obj/item/prop) + user.visible_message(SPAN_NOTICE("\The [user] begins chipping \the [target] into a rough anvil using \the [prop].")) + if(!user.do_skilled(10 SECONDS, work_skill, target)) + return FALSE + if(QDELETED(user) || QDELETED(target) || QDELETED(prop) || user.get_active_held_item() != prop || !CanPhysicallyInteractWith(user, target)) + return FALSE + if(!is_possible(target, user, prop)) + return FALSE + user.visible_message(SPAN_NOTICE("\The [user] chips \the [target] into a rough anvil using \the [prop].")) + new /obj/structure/anvil/boulder(get_turf(target), target.get_material()?.type) + return TRUE diff --git a/icons/obj/items/barrel_rim.dmi b/icons/obj/items/barrel_rim.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a5567e33c7c57c7276ebe585e14d8e63c61f693d GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{*8>L*96EUN|NsAFfg z7<$()=}&DsnKU zZE!G{sOEtFk_F<8}6F_c+_@mYXsXo@Tk&$)#M`FK_+W&B}eXPAA6 V>qywtdS{^344$rjF6*2UngD^Eb1nb? literal 0 HcmV?d00001 diff --git a/icons/obj/items/billet.dmi b/icons/obj/items/billet.dmi new file mode 100644 index 0000000000000000000000000000000000000000..1b841d667a04372bc063cd33ae958800ae2d87c1 GIT binary patch literal 891 zcmV->1BCpEP)p8x;fFDZ*Bkp zc$}4zy$ZuH41{O4n|mNVe!Vo)b_+-#*-oFSXcVK0RR_pr2@ku*V)*%Hy4ObX+0OnxUthq{MVe^Uin z7no=};SAi|&TC?#!xUP}2zFtHKoOq>#prJqG$-v?Gt16 zHhup8uM~~xnLBB;uU(dVPCQ zbfQQHj62|UbUZ;R?;76RMJEIja-XOLfttQSe@R%*lIXyJoCQx21S$`49VrR{ISYidU_8XNqo-Mr?*M1PbilJ=VY#yY z1P)R{fm=d>Yykueg@N;q!X7&K0GoLXZOcGYIJZC=yUmpDlJM z1)BZ>8B(wzF9hCy@CUhEz>MZ{Q5d&qLGQkBUcXb~7IgZaxM1BjxhJ}4+CDEKRw!6-H4>+)xHx;19Z zzxk^*-`}s}3js67^YuaR>C2NjtR@jPybu`XxLG>Lx5GVsaWFm{ycGD21M}p-tU8zn z{BU5FL5#ly#>F5L@LQkp4d^pJGRc3@rc)OGr(8|@H9w`v+e{*M1hK`Z5ga2ML*D43 zV}~GHd>TSi6BD2iBRhuK;uH2AcVK;T>>y~1&+nxV=-0C=2J zR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0*I5Sc+(=$pSoZ^zil2jm5 zDKoDuHLoPUs8WfGGbOXA7$|1Q#hF%=n41b=!&R2&7v-ejQl+fm>gNJ>008PhCchu; zBTE1P0QX5mK~y-6jgxB>-c zx^|NdHcSNkdAAAzTWsU!C~uTL_k+jFc$^1=DKN?EK|;9B!N4q&_-s0vb@Q%DqeNK} zuG5uGl@_x0%~n0xOE!o+mlu77SV+~nAFKeR9jyghSL{g1rY6#d2op$=7Mqcj$|0#o z?;7?oDai+-gT*36x>_nh#M1IRx=9S6hbLX zsG&l4_tVsjIV|YblBC`eXZ7>T)qA#NtLv_s(5%O4VIoW(-t^r~U+ztP04k{(Md+lZ Qe*gdg07*qoM6N<$f&&)Iq5uE@ literal 0 HcmV?d00001 diff --git a/icons/obj/items/stacks/nails.dmi b/icons/obj/items/stacks/nails.dmi new file mode 100644 index 0000000000000000000000000000000000000000..10ba6ebcc9891aa17743b06b2a3a0c8298f685bd GIT binary patch literal 533 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