diff --git a/docs/changelog.txt b/docs/changelog.txt index 88c4d1cbbb..10ac5f7e37 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -54,6 +54,7 @@ Template for new versions: ## New Tools ## New Features +- `tweak`: ``realistic-melting``: change melting return for inorganic armor parts, shields, weapons, trap components and tools to stop smelters from creating metal, bring melt return for adamantine in line with other metals to ~95% of forging cost. wear reduces melt return by 10% per level ## Fixes diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index 996b0ec103..7c72f946c7 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -53,6 +53,19 @@ Commands Names filled waterskins, flasks, and vials according to their contents, the same way other containers such as barrels, bins, and cages are named. (:bug:`4914`) +``realistic-melting`` + Makes amortized metal bar returns for melting uniform across all item types. + Affects weapons, shields, armor parts, tools, and trap components. The target + amount of metal produced by melting is 95% of the metal used for production + of the item. Each level of wear decreases melt return by a further 10%. The game + has a fixed granularity of 0.3 for metal bar returns, so individual items will + randomly return an amount that may be above or below the target. For example + a metal cap with item size 1 will produce 0.9 of a bar with a 16.6% chance of + producing an additional 0.3 of a bar. Over time, the average return for melting + these types of caps will be ~0.95 of a bar. Calculations for melting return are + done for items with base game production cost. Melting return might not be + calculated correctly for modded items or items created in custom reactions + that don't respect vanilla production costs. (:bug:`6027`) ``named-codices`` Displays titles for books instead of the default material description. ``partial-items`` diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index e10a9535a3..e621a14d5e 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -17,6 +17,7 @@ using namespace DFHack; #include "tweaks/eggs-fertile.h" #include "tweaks/fast-heat.h" #include "tweaks/flask-contents.h" +#include "tweaks/material-size-for-melting.h" #include "tweaks/named-codices.h" #include "tweaks/partial-items.h" #include "tweaks/reaction-gloves.h" @@ -74,6 +75,15 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector + +#include "modules/Materials.h" +#include "modules/Random.h" + +#include "df/inorganic_raw.h" +#include "df/item_armorst.h" +#include "df/item_constructed.h" +#include "df/item_glovesst.h" +#include "df/item_helmst.h" +#include "df/item_pantsst.h" +#include "df/item_shieldst.h" +#include "df/item_shoesst.h" +#include "df/item_toolst.h" +#include "df/item_trapcompst.h" +#include "df/item_weaponst.h" + +struct Mrng { + Random::MersenneRNG rng; + Mrng() { rng.init(); } +}; + +static float get_random() { + static Mrng mrng; + return static_cast (mrng.rng.drandom1()); +} + +static int32_t get_material_size_for_melting(df::item_constructed *item, int32_t base_material_size, float production_stack_size) { + const float melt_return_per_material_size = 0.3f, base_melt_recovery = 0.95f, loss_per_wear_level = 0.1f; + + if (item->mat_type != 0) // bail if not INORGANIC + return base_material_size; + + float forging_cost_per_item; + if (auto inorganic = df::inorganic_raw::find(item->mat_index); + inorganic && inorganic->flags.is_set(df::inorganic_flags::DEEP_SPECIAL)) + { + // adamantine items + forging_cost_per_item = static_cast(base_material_size) / production_stack_size; + } else { + // non adamantine items + forging_cost_per_item = std::max(std::floor(static_cast(base_material_size) / 3.0f), 1.0f); + forging_cost_per_item /= production_stack_size; + } + + float calculated_size = forging_cost_per_item / melt_return_per_material_size; + float melt_recovery = base_melt_recovery - static_cast(item->wear) * loss_per_wear_level; + calculated_size *= melt_recovery; + int32_t random_part = ((modff(calculated_size, &calculated_size) > get_random()) ? 1 : 0); + return static_cast(calculated_size) + random_part; +} + +#define DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(TYPE, PRODUCTION_STACK_SIZE) \ +struct material_size_for_melting_##TYPE##_hook : df::item_##TYPE##st {\ + typedef df::item_##TYPE##st interpose_base;\ + DEFINE_VMETHOD_INTERPOSE(int32_t, getMaterialSizeForMelting, ()) {\ + return get_material_size_for_melting(this, INTERPOSE_NEXT(getMaterialSizeForMelting)(), PRODUCTION_STACK_SIZE);\ + }\ +};\ +IMPLEMENT_VMETHOD_INTERPOSE(material_size_for_melting_##TYPE##_hook, getMaterialSizeForMelting); + +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(armor, 1.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(gloves, 2.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(shoes, 2.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(helm, 1.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(pants, 1.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(weapon, 1.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(trapcomp, 1.0f) +DEFINE_MATERIAL_SIZE_FOR_MELTING_TWEAK(tool, 1.0f)