Files
Bubberstation/modular_skyrat/modules/reagent_forging/code/crafting_bench.dm
Roxy 5020cec410 Fix define sanity linter failures (#3209)
Fix all of the define sanity failures, either by adding an `#undef`
where needed or moving them to a file in `code/__DEFINES`

![image](https://github.com/user-attachments/assets/abd76dcd-d610-4567-801e-ee7fc477d58c)
2025-03-06 03:24:59 -05:00

391 lines
15 KiB
Plaintext

/// How many planks of wood are required to complete a weapon?
#define WEAPON_COMPLETION_WOOD_AMOUNT 2
/// The number of hits you are set back when a bad hit is made
#define BAD_HIT_PENALTY 3
/obj/structure/reagent_crafting_bench
name = "forging workbench"
desc = "A crafting bench fitted with tools, securing mechanisms, and a steady surface for blacksmithing."
icon = 'modular_skyrat/modules/reagent_forging/icons/obj/forge_structures.dmi'
icon_state = "crafting_bench_empty"
anchored = TRUE
density = TRUE
/// What the currently picked recipe is
var/datum/crafting_bench_recipe/selected_recipe
/// How many successful hits towards completion of the item have we done
var/current_hits_to_completion = 0
/// Is this bench able to complete forging items? Exists to allow non-forging workbenches to exist
var/finishes_forging_weapons = TRUE
/// The cooldown from the last hit before we allow another 'good hit' to happen
COOLDOWN_DECLARE(hit_cooldown)
/// What recipes are we allowed to choose from?
var/list/allowed_choices = list(
/datum/crafting_bench_recipe/plate_helmet,
/datum/crafting_bench_recipe/plate_vest,
/datum/crafting_bench_recipe/plate_gloves,
/datum/crafting_bench_recipe/plate_boots,
/datum/crafting_bench_recipe/horse_shoes,
/datum/crafting_bench_recipe/ring,
/datum/crafting_bench_recipe/collar,
/datum/crafting_bench_recipe/handcuffs,
/datum/crafting_bench_recipe/borer_cage,
/datum/crafting_bench_recipe/pavise,
/datum/crafting_bench_recipe/buckler,
/datum/crafting_bench_recipe/coil,
/datum/crafting_bench_recipe/seed_mesh,
/datum/crafting_bench_recipe/centrifuge,
/datum/crafting_bench_recipe/bokken,
/datum/crafting_bench_recipe/bow,
/datum/crafting_bench_recipe/empty_circuit,
)
/// Radial options for recipes in the allowed_choices list, populated by populate_radial_choice_list
var/list/radial_choice_list = list()
/// An associative list of names --> recipe path that the radial recipe picker will choose from later
var/list/recipe_names_to_path = list()
/obj/structure/reagent_crafting_bench/Initialize(mapload)
. = ..()
populate_radial_choice_list()
/obj/structure/reagent_crafting_bench/proc/populate_radial_choice_list()
if(!length(allowed_choices))
return
if(length(radial_choice_list) && length(recipe_names_to_path)) // We already have both of these and don't need it, if this is called after these are generated for some reason
return
for(var/recipe in allowed_choices)
var/datum/crafting_bench_recipe/recipe_to_take_from = new recipe()
var/obj/recipe_resulting_item = recipe_to_take_from.resulting_item
radial_choice_list[recipe_to_take_from.recipe_name] = image(icon = initial(recipe_resulting_item.icon), icon_state = initial(recipe_resulting_item.icon_state))
recipe_names_to_path[recipe_to_take_from.recipe_name] = recipe
qdel(recipe_to_take_from)
/obj/structure/reagent_crafting_bench/examine(mob/user)
. = ..()
if(length(contents))
if(istype(contents[1], /obj/item/forging/complete))
var/obj/item/forging/complete/contained_forge_item = contents[1]
. += span_notice("[src] has a <b>[initial(contained_forge_item.name)]</b> sitting on it, awaiting completion. <br>")
var/obj/item/completion_item = contained_forge_item.spawning_item
. += span_notice("With <b>[WEAPON_COMPLETION_WOOD_AMOUNT]</b> sheets of <b>wood</b> nearby, and some <b>hammering</b>, it could be completed into a <b>[initial(completion_item.name)]</b>.")
return // We don't want to show any selected recipes if there's weapon head on the bench
if(!selected_recipe)
return
var/obj/resulting_item = selected_recipe.resulting_item
. += span_notice("The selected recipe's resulting item is: <b>[initial(resulting_item.name)]</b> <br>")
. += span_notice("Gather the required materials, listed below, <b>near the bench</b>, then start <b>hammering</b> to complete it! <br>")
if(!length(selected_recipe.recipe_requirements))
. += span_boldwarning("Somehow, this recipe has no requirements, report this as this shouldn't happen.")
return
for(var/obj/requirement_item as anything in selected_recipe.recipe_requirements)
if(!selected_recipe.recipe_requirements[requirement_item])
. += span_boldwarning("[requirement_item] does not have an amount required set, this should not happen, report it.")
continue
. += span_notice("<b>[selected_recipe.recipe_requirements[requirement_item]]</b> - [initial(requirement_item.name)]")
return .
/obj/structure/reagent_crafting_bench/update_appearance(updates)
. = ..()
cut_overlays()
if(!length(contents))
return
var/image/overlayed_item = image(icon = contents[1].icon, icon_state = contents[1].icon_state)
add_overlay(overlayed_item)
/obj/structure/reagent_crafting_bench/attack_hand(mob/living/user, list/modifiers)
. = ..()
select_recipe(user)
/obj/structure/reagent_crafting_bench/attack_robot(mob/living/user)
. = ..()
select_recipe(user)
/obj/structure/reagent_crafting_bench/proc/select_recipe(mob/living/user)
update_appearance()
if(length(contents))
var/obj/item/contained_item = contents[1]
user.put_in_hands(contained_item)
balloon_alert(user, "[contained_item] retrieved")
update_appearance()
return
if(selected_recipe)
clear_recipe()
balloon_alert_to_viewers("recipe cleared")
update_appearance()
return
var/chosen_recipe = show_radial_menu(user, src, radial_choice_list, radius = 38, require_near = TRUE, tooltips = TRUE)
if(!chosen_recipe)
balloon_alert(user, "no recipe choice")
return
var/datum/crafting_bench_recipe/recipe_to_use = recipe_names_to_path[chosen_recipe]
selected_recipe = new recipe_to_use
balloon_alert(user, "recipe chosen")
update_appearance()
/// Clears the current recipe and sets hits to completion to zero
/obj/structure/reagent_crafting_bench/proc/clear_recipe()
QDEL_NULL(selected_recipe)
current_hits_to_completion = 0
/obj/structure/reagent_crafting_bench/attackby(obj/item/attacking_item, mob/user, params)
if(istype(attacking_item, /obj/item/forging/complete))
attempt_place(attacking_item, user)
return TRUE
return ..()
/obj/structure/reagent_crafting_bench/mouse_drop_receive(atom/movable/attacking_item, mob/living/user, params)
. = ..()
if(!isliving(user))
return
if(!isobj(attacking_item))
return
if(istype(attacking_item, /obj/item/forging/complete))
attempt_place(attacking_item, user)
/obj/structure/reagent_crafting_bench/proc/attempt_place(obj/item/attacking_item, mob/user)
if(length(contents))
balloon_alert(user, "already full")
return
attacking_item.forceMove(src)
balloon_alert_to_viewers("placed [attacking_item]")
update_appearance()
return
/obj/structure/reagent_crafting_bench/wrench_act(mob/living/user, obj/item/tool)
tool.play_tool_sound(src)
deconstruct(disassembled = TRUE)
return ITEM_INTERACT_SUCCESS
/obj/structure/reagent_crafting_bench/hammer_act(mob/living/user, obj/item/tool)
playsound(src, 'modular_skyrat/modules/reagent_forging/sound/forge.ogg', 50, TRUE)
if(length(contents))
if(!istype(contents[1], /obj/item/forging/complete))
balloon_alert(user, "invalid item")
return ITEM_INTERACT_SUCCESS
var/obj/item/forging/complete/weapon_to_finish = contents[1]
if(!weapon_to_finish.spawning_item)
balloon_alert(user, "[weapon_to_finish] cannot be completed")
return ITEM_INTERACT_SUCCESS
var/list/wood_required_for_weapons = list(
/obj/item/stack/sheet/mineral/wood = WEAPON_COMPLETION_WOOD_AMOUNT,
)
if(!can_we_craft_this(wood_required_for_weapons))
balloon_alert(user, "not enough wood")
return ITEM_INTERACT_SUCCESS
var/list/things_to_use = can_we_craft_this(wood_required_for_weapons, TRUE)
var/obj/thing_just_made = create_thing_from_requirements(things_to_use, user = user, skill_to_grant = /datum/skill/smithing, skill_amount = 30, completing_a_weapon = TRUE)
if(!thing_just_made)
message_admins("[src] just tried to finish a weapon but somehow created nothing! This is not working as intended!")
return ITEM_INTERACT_SUCCESS
balloon_alert_to_viewers("[thing_just_made] created")
update_appearance()
return ITEM_INTERACT_SUCCESS
if(!selected_recipe)
balloon_alert(user, "no recipe selected")
return ITEM_INTERACT_SUCCESS
if(!can_we_craft_this(selected_recipe.recipe_requirements))
balloon_alert(user, "missing ingredients")
return ITEM_INTERACT_SUCCESS
var/skill_modifier = user.mind.get_skill_modifier(selected_recipe.relevant_skill, SKILL_SPEED_MODIFIER) * 1 SECONDS
if(!COOLDOWN_FINISHED(src, hit_cooldown)) // If you hit it before the cooldown is done, you get a bad hit, setting you back three good hits
current_hits_to_completion -= BAD_HIT_PENALTY
if(current_hits_to_completion <= -(selected_recipe.required_good_hits))
balloon_alert_to_viewers("recipe failed")
clear_recipe()
return ITEM_INTERACT_SUCCESS
balloon_alert(user, "bad hit")
return ITEM_INTERACT_SUCCESS
COOLDOWN_START(src, hit_cooldown, skill_modifier)
if((current_hits_to_completion >= selected_recipe.required_good_hits) && !length(contents))
var/list/things_to_use = can_we_craft_this(selected_recipe.recipe_requirements, TRUE)
create_thing_from_requirements(things_to_use, selected_recipe, user, selected_recipe.relevant_skill, selected_recipe.relevant_skill_reward)
return ITEM_INTERACT_SUCCESS
current_hits_to_completion++
balloon_alert(user, "good hit")
user.mind.adjust_experience(selected_recipe.relevant_skill, selected_recipe.relevant_skill_reward / 15) // Good hits towards the current item grants experience in that skill
return ITEM_INTERACT_SUCCESS
/// Takes the given list of item requirements and checks the surroundings for them, returns TRUE unless return_ingredients_list is set, in which case a list of all the items to use is returned
/obj/structure/reagent_crafting_bench/proc/can_we_craft_this(list/required_items, return_ingredients_list = FALSE)
if(!length(required_items))
message_admins("[src] just tried to check for ingredients nearby without having a list of items to check for!")
return FALSE
var/list/surrounding_items = list()
var/list/requirement_items = list()
for(var/obj/item/potential_requirement in get_environment())
surrounding_items += potential_requirement
for(var/obj/item/requirement_path as anything in required_items)
var/required_amount = required_items[requirement_path]
for(var/obj/item/nearby_item as anything in surrounding_items)
if(!istype(nearby_item, requirement_path))
continue
if(isstack(nearby_item)) // If the item is a stack, check if that stack has enough material in it to fill out the amount
var/obj/item/stack/nearby_stack = nearby_item
if(required_amount > 0)
requirement_items += nearby_item
required_amount -= nearby_stack.amount
else // Otherwise, we still exist and should subtract one from the required number of items
if(required_amount > 0)
requirement_items += nearby_item
required_amount -= 1
if(required_amount > 0)
return FALSE
if(return_ingredients_list)
return requirement_items
else
return TRUE
/// Passes the list of found ingredients + the recipe to use_or_delete_recipe_requirements, then spawns the given recipe's result
/obj/structure/reagent_crafting_bench/proc/create_thing_from_requirements(list/things_to_use, datum/crafting_bench_recipe/recipe_to_follow, mob/living/user, datum/skill/skill_to_grant, skill_amount, completing_a_weapon)
if(!recipe_to_follow && !completing_a_weapon)
message_admins("[src] just tried to complete a recipe without having a recipe, and without it being the completion of a forging weapon!")
return FALSE
if(completing_a_weapon && (!length(contents) || !istype(contents[1], /obj/item/forging/complete)))
message_admins("[src] just tried to complete a forge weapon without there being a weapon head inside it to complete!")
return FALSE
if(!length(things_to_use))
message_admins("[src] just tried to craft something from requirements, but was not given a list of requirements!")
return FALSE
if(completing_a_weapon)
recipe_to_follow = new /datum/crafting_bench_recipe/weapon_completion_recipe
var/materials_to_transfer = list()
var/list/temporary_materials_list = use_or_delete_recipe_requirements(things_to_use, recipe_to_follow)
for(var/material as anything in temporary_materials_list)
materials_to_transfer[material] += temporary_materials_list[material]
var/obj/newly_created_thing
if(completing_a_weapon)
var/obj/item/forging/complete/completed_forge_item = contents[1]
newly_created_thing = new completed_forge_item.spawning_item(src)
if(newly_created_thing.force > 0) //we don't want the staff to get added damage
newly_created_thing.force += min(round(completed_forge_item.current_perfects * INVERSE(10)), 3) //adds a maximum of 3 force, and 6 if dual-wielded
if(completed_forge_item.custom_materials) // We need to add the weapon head's materials to the completed item, too
for(var/custom_material in completed_forge_item.custom_materials)
materials_to_transfer[custom_material] += completed_forge_item.custom_materials[custom_material]
qdel(completed_forge_item) // And then we also need to 'use' the item
else
newly_created_thing = new recipe_to_follow.resulting_item(src)
if(!newly_created_thing)
message_admins("[src] just failed to create something while crafting!")
return FALSE
if(recipe_to_follow.transfers_materials)
newly_created_thing.set_custom_materials(materials_to_transfer, multiplier = 1)
user.mind.adjust_experience(skill_to_grant, skill_amount)
clear_recipe()
update_appearance()
return newly_created_thing
/// Takes the given list, things_to_use, compares it to recipe_to_follow's requirements, then either uses items from a stack, or deletes them otherwise. Returns custom material of forge items in the end.
/obj/structure/reagent_crafting_bench/proc/use_or_delete_recipe_requirements(list/things_to_use, datum/crafting_bench_recipe/recipe_to_follow)
var/list/materials_to_transfer = list()
for(var/obj/requirement_item as anything in things_to_use)
if(isstack(requirement_item))
var/stack_type
for(var/recipe_thing_to_reference as anything in recipe_to_follow.recipe_requirements)
if(!istype(requirement_item, recipe_thing_to_reference))
continue
stack_type = recipe_thing_to_reference
break
var/obj/item/stack/requirement_stack = requirement_item
if(requirement_stack.amount < recipe_to_follow.recipe_requirements[stack_type])
recipe_to_follow.recipe_requirements[stack_type] -= requirement_stack.amount
requirement_stack.use(requirement_stack.amount)
continue
requirement_stack.use(recipe_to_follow.recipe_requirements[stack_type])
else if(istype(requirement_item, /obj/item/forging/complete))
if(!requirement_item.custom_materials || !recipe_to_follow.transfers_materials)
qdel(requirement_item)
continue
for(var/custom_material as anything in requirement_item.custom_materials)
materials_to_transfer += custom_material
qdel(requirement_item)
else
qdel(requirement_item)
return materials_to_transfer
/// Gets movable atoms within one tile of range of the crafting bench
/obj/structure/reagent_crafting_bench/proc/get_environment()
. = list()
if(!get_turf(src))
return
for(var/atom/movable/found_movable_atom in range(1, src))
if((found_movable_atom.flags_1 & HOLOGRAM_1))
continue
. += found_movable_atom
return .
#undef WEAPON_COMPLETION_WOOD_AMOUNT
#undef BAD_HIT_PENALTY