From c2e647b79b7befc81dd56f9e011a647967413a22 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 3 Jul 2023 18:05:30 -0700 Subject: [PATCH 1/6] fix item prices and algorithm adjust prices according to race WAR affinity --- docs/changelog.txt | 2 + docs/dev/Lua API.rst | 12 +++- library/include/modules/Items.h | 5 +- library/modules/Items.cpp | 115 ++++++++++++++++++++++++++++---- 4 files changed, 117 insertions(+), 17 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8971dd0ac..4a8a100df 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,10 +47,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``Units::getUnitByNobleRole``, ``Units::getUnitsByNobleRole``: unit lookup API by role ## Internals +- Price calculations fixed for many item types ## Lua - ``dfhack.items.markForTrade``: new API for marking items for trade - ``dfhack.units.getUnitByNobleRole``, ``dfhack.units.getUnitsByNobleRole``: unit lookup API by role +- ``dfhack.items.getValue``: gained optional ``caravan`` and ``caravan_buying`` parameters for prices that take trader races and agreements into account ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 785e477dd..e81a34de3 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1768,9 +1768,15 @@ Items module Calculates the base value for an item of the specified type and material. -* ``dfhack.items.getValue(item)`` - - Calculates the Basic Value of an item, as seen in the View Item screen. +* ``dfhack.items.getValue(item[, caravan_state, caravan_buying])`` + + Calculates the value of an item. If a ``df.caravan_state`` object is given + (from ``df.global.plotinfo.caravans`` or + ``df.global.main_interface.trade.mer``), then the value is modified by civ + properties and any trade agreements that might be in effect. In this case, + specify ``caravan_buying`` as ``true`` to get the price the caravan will pay + for the item and ``false`` to get the price that the caravan will sell the + item for. * ``dfhack.items.createItem(item_type, item_subtype, mat_type, mat_index, unit)`` diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 08737fb2b..6cce5d2f1 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -34,6 +34,7 @@ distribution. #include "DataDefs.h" #include "df/building_tradedepotst.h" +#include "df/caravan_state.h" #include "df/item.h" #include "df/item_type.h" #include "df/general_ref.h" @@ -189,8 +190,8 @@ DFHACK_EXPORT df::proj_itemst *makeProjectile(MapExtras::MapCache &mc, df::item /// Gets value of base-quality item with specified type and material DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_subtype); -/// Gets the value of a specific item, ignoring civ values and trade agreements -DFHACK_EXPORT int getValue(df::item *item); +/// Gets the value of a specific item, taking into account civ values and trade agreements if a caravan is given +DFHACK_EXPORT int getValue(df::item *item, df::caravan_state *caravan = NULL, bool caravan_buying = false); DFHACK_EXPORT int32_t createItem(df::item_type type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, df::unit* creator); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index f98c7c46b..a2107aa1d 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -50,8 +50,10 @@ using namespace std; #include "df/building.h" #include "df/building_actual.h" #include "df/building_tradedepotst.h" +#include "df/caravan_state.h" #include "df/caste_raw.h" #include "df/creature_raw.h" +#include "df/entity_raw.h" #include "df/general_ref.h" #include "df/general_ref_building_holderst.h" #include "df/general_ref_contained_in_itemst.h" @@ -828,15 +830,15 @@ std::string Items::getDescription(df::item *item, int type, bool decorate) item->getItemDescription(&tmp, type); if (decorate) { - if (item->flags.bits.foreign) - tmp = "(" + tmp + ")"; - addQuality(tmp, item->getQuality()); if (item->isImproved()) { tmp = '\xAE' + tmp + '\xAF'; // («) + tmp + (») addQuality(tmp, item->getImprovementQuality()); } + + if (item->flags.bits.foreign) + tmp = "(" + tmp + ")"; } return tmp; @@ -1197,18 +1199,24 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat switch (item_type) { case item_type::BAR: - case item_type::SMALLGEM: case item_type::BLOCKS: case item_type::SKIN_TANNED: value = 5; break; - case item_type::ROUGH: + case item_type::SMALLGEM: + value = 20; + break; + case item_type::BOULDER: case item_type::WOOD: value = 3; break; + case item_type::ROUGH: + value = 6; + break; + case item_type::DOOR: case item_type::FLOODGATE: case item_type::BED: @@ -1224,6 +1232,7 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::TABLE: case item_type::COFFIN: case item_type::BOX: + case item_type::BAG: case item_type::BIN: case item_type::ARMORSTAND: case item_type::WEAPONRACK: @@ -1367,6 +1376,7 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::COIN: case item_type::GLOB: case item_type::ORTHOPEDIC_CAST: + case item_type::BRANCH: value = 1; break; @@ -1435,7 +1445,61 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat return value; } -int Items::getValue(df::item *item) +static int32_t get_war_multiplier(df::item *item, df::caravan_state *caravan) { + static const int32_t DEFAULT_MULTIPLIER = 256; + + if (!caravan) + return DEFAULT_MULTIPLIER; + auto caravan_he = df::historical_entity::find(caravan->entity); + if (!caravan_he) + return DEFAULT_MULTIPLIER; + int32_t war_alignment = caravan_he->entity_raw->sphere_alignment[df::sphere_type::WAR]; + if (war_alignment == DEFAULT_MULTIPLIER) + return DEFAULT_MULTIPLIER; + switch (item->getType()) { + case df::item_type::WEAPON: + { + auto weap_def = df::itemdef_weaponst::find(item->getSubtype()); + auto caravan_cre_raw = df::creature_raw::find(caravan_he->race); + if (!weap_def || !caravan_cre_raw || caravan_cre_raw->adultsize < weap_def->minimum_size) + return DEFAULT_MULTIPLIER; + break; + } + case df::item_type::ARMOR: + case df::item_type::SHOES: + case df::item_type::HELM: + case df::item_type::GLOVES: + case df::item_type::PANTS: + { + if (item->getEffectiveArmorLevel() <= 0) + return DEFAULT_MULTIPLIER; + auto caravan_cre_raw = df::creature_raw::find(caravan_he->race); + auto maker_cre_raw = df::creature_raw::find(item->getMakerRace()); + if (!caravan_cre_raw || !maker_cre_raw) + return DEFAULT_MULTIPLIER; + if (caravan_cre_raw->adultsize < ((maker_cre_raw->adultsize * 6) / 7)) + return DEFAULT_MULTIPLIER; + if (caravan_cre_raw->adultsize > ((maker_cre_raw->adultsize * 8) / 7)) + return DEFAULT_MULTIPLIER; + break; + } + case df::item_type::SHIELD: + case df::item_type::AMMO: + case df::item_type::BACKPACK: + case df::item_type::QUIVER: + break; + default: + return DEFAULT_MULTIPLIER; + } + return war_alignment; +} + +// returns 0 if the multiplier would be equal to 1.0 +static float get_trade_agreement_multiplier(df::item *item, df::caravan_state *caravan, bool caravan_buying) { + return 0; +} + +int Items::getValue(df::item *item, df::caravan_state *caravan, bool caravan_buying) { CHECK_NULL_POINTER(item); @@ -1447,16 +1511,38 @@ int Items::getValue(df::item *item) // Get base value for item type, subtype, and material int value = getItemBaseValue(item_type, item_subtype, mat_type, mat_subtype); - // Ignore entity value modifications + // entity value modifications + value *= get_war_multiplier(item, caravan); + value >>= 8; // Improve value based on quality - int quality = item->getQuality(); - value *= (quality + 1); - if (quality == 5) + switch (item->getQuality()) { + case 1: + value *= 1.1; + value += 3; + break; + case 2: + value *= 1.2; + value += 6; + break; + case 3: + value *= 1.333; + value += 10; + break; + case 4: + value *= 1.5; + value += 15; + break; + case 5: value *= 2; + value += 30; + break; + default: + break; + } // Add improvement values - int impValue = item->getThreadDyeValue(NULL) + item->getImprovementsValue(NULL); + int impValue = item->getThreadDyeValue(caravan) + item->getImprovementsValue(caravan); if (item_type == item_type::AMMO) // Ammo improvements are worth less impValue /= 30; value += impValue; @@ -1481,12 +1567,17 @@ int Items::getValue(df::item *item) if (item->flags.bits.artifact_mood) value *= 10; + // modify buy/sell prices if a caravan is given + float trade_agreement_multiplier = get_trade_agreement_multiplier(item, caravan, caravan_buying); + if (trade_agreement_multiplier > 0) + value *= trade_agreement_multiplier; + // Boost value from stack size value *= item->getStackSize(); // ...but not for coins if (item_type == item_type::COIN) { - value /= 500; + value /= 50; if (!value) value = 1; } From 2aeb86ba8f553c209a646842e2b9615f00685605 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 3 Jul 2023 18:45:33 -0700 Subject: [PATCH 2/6] implement trade agreement buy prices --- library/modules/Items.cpp | 66 ++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index a2107aa1d..63c2a1992 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -53,6 +53,9 @@ using namespace std; #include "df/caravan_state.h" #include "df/caste_raw.h" #include "df/creature_raw.h" +#include "df/entity_buy_prices.h" +#include "df/entity_buy_requests.h" +#include "df/entity_sell_prices.h" #include "df/entity_raw.h" #include "df/general_ref.h" #include "df/general_ref_building_holderst.h" @@ -1446,23 +1449,23 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat } static int32_t get_war_multiplier(df::item *item, df::caravan_state *caravan) { - static const int32_t DEFAULT_MULTIPLIER = 256; + static const int32_t DEFAULT_WAR_MULTIPLIER = 256; if (!caravan) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; auto caravan_he = df::historical_entity::find(caravan->entity); if (!caravan_he) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; int32_t war_alignment = caravan_he->entity_raw->sphere_alignment[df::sphere_type::WAR]; - if (war_alignment == DEFAULT_MULTIPLIER) - return DEFAULT_MULTIPLIER; + if (war_alignment == DEFAULT_WAR_MULTIPLIER) + return DEFAULT_WAR_MULTIPLIER; switch (item->getType()) { case df::item_type::WEAPON: { auto weap_def = df::itemdef_weaponst::find(item->getSubtype()); auto caravan_cre_raw = df::creature_raw::find(caravan_he->race); if (!weap_def || !caravan_cre_raw || caravan_cre_raw->adultsize < weap_def->minimum_size) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; break; } case df::item_type::ARMOR: @@ -1472,15 +1475,15 @@ static int32_t get_war_multiplier(df::item *item, df::caravan_state *caravan) { case df::item_type::PANTS: { if (item->getEffectiveArmorLevel() <= 0) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; auto caravan_cre_raw = df::creature_raw::find(caravan_he->race); auto maker_cre_raw = df::creature_raw::find(item->getMakerRace()); if (!caravan_cre_raw || !maker_cre_raw) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; if (caravan_cre_raw->adultsize < ((maker_cre_raw->adultsize * 6) / 7)) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; if (caravan_cre_raw->adultsize > ((maker_cre_raw->adultsize * 8) / 7)) - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; break; } case df::item_type::SHIELD: @@ -1489,14 +1492,42 @@ static int32_t get_war_multiplier(df::item *item, df::caravan_state *caravan) { case df::item_type::QUIVER: break; default: - return DEFAULT_MULTIPLIER; + return DEFAULT_WAR_MULTIPLIER; } return war_alignment; } -// returns 0 if the multiplier would be equal to 1.0 -static float get_trade_agreement_multiplier(df::item *item, df::caravan_state *caravan, bool caravan_buying) { - return 0; +static const int32_t DEFAULT_AGREEMENT_MULTIPLIER = 128; + +static int32_t get_buy_request_multiplier(df::item *item, const df::entity_buy_prices *buy_prices) { + int16_t item_type = item->getType(); + int16_t item_subtype = item->getSubtype(); + int16_t mat_type = item->getMaterial(); + int32_t mat_subtype = item->getMaterialIndex(); + + for (size_t idx = 0; idx < buy_prices->price.size(); ++idx) { + if (buy_prices->items->item_type[idx] != item_type) + continue; + if (buy_prices->items->item_subtype[idx] != -1 && buy_prices->items->item_subtype[idx] != item_subtype) + continue; + if (buy_prices->items->mat_types[idx] != -1 && buy_prices->items->mat_types[idx] != mat_type) + continue; + if (buy_prices->items->mat_indices[idx] != -1 && buy_prices->items->mat_indices[idx] != mat_subtype) + continue; + return buy_prices->price[idx]; + } + return DEFAULT_AGREEMENT_MULTIPLIER; +} + +static int32_t get_sell_request_multiplier(df::item *item, const df::entity_sell_prices *sell_prices) { + return DEFAULT_AGREEMENT_MULTIPLIER; +} + +static int32_t get_trade_agreement_multiplier(df::item *item, df::caravan_state *caravan, bool caravan_buying) { + if (!caravan) + return DEFAULT_AGREEMENT_MULTIPLIER; + return caravan_buying ? get_buy_request_multiplier(item, caravan->buy_prices) + : get_sell_request_multiplier(item, caravan->sell_prices); } int Items::getValue(df::item *item, df::caravan_state *caravan, bool caravan_buying) @@ -1567,10 +1598,9 @@ int Items::getValue(df::item *item, df::caravan_state *caravan, bool caravan_buy if (item->flags.bits.artifact_mood) value *= 10; - // modify buy/sell prices if a caravan is given - float trade_agreement_multiplier = get_trade_agreement_multiplier(item, caravan, caravan_buying); - if (trade_agreement_multiplier > 0) - value *= trade_agreement_multiplier; + // modify buy/sell prices + value *= get_trade_agreement_multiplier(item, caravan, caravan_buying); + value >>= 7; // Boost value from stack size value *= item->getStackSize(); From c45dcdd7b0aa1d458d06e5ee2cc0dfd2a0866c0f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 4 Jul 2023 01:53:45 -0700 Subject: [PATCH 3/6] implement sell_prices adjustments --- library/modules/Items.cpp | 393 +++++++++++++++++++++++++++++++++++++- 1 file changed, 390 insertions(+), 3 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 63c2a1992..537be7c3a 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -50,11 +50,14 @@ using namespace std; #include "df/building.h" #include "df/building_actual.h" #include "df/building_tradedepotst.h" +#include "df/builtin_mats.h" #include "df/caravan_state.h" #include "df/caste_raw.h" #include "df/creature_raw.h" +#include "df/dfhack_material_category.h" #include "df/entity_buy_prices.h" #include "df/entity_buy_requests.h" +#include "df/entity_sell_category.h" #include "df/entity_sell_prices.h" #include "df/entity_raw.h" #include "df/general_ref.h" @@ -1500,6 +1503,9 @@ static int32_t get_war_multiplier(df::item *item, df::caravan_state *caravan) { static const int32_t DEFAULT_AGREEMENT_MULTIPLIER = 128; static int32_t get_buy_request_multiplier(df::item *item, const df::entity_buy_prices *buy_prices) { + if (!buy_prices) + return DEFAULT_AGREEMENT_MULTIPLIER; + int16_t item_type = item->getType(); int16_t item_subtype = item->getSubtype(); int16_t mat_type = item->getMaterial(); @@ -1519,15 +1525,396 @@ static int32_t get_buy_request_multiplier(df::item *item, const df::entity_buy_p return DEFAULT_AGREEMENT_MULTIPLIER; } -static int32_t get_sell_request_multiplier(df::item *item, const df::entity_sell_prices *sell_prices) { +template +static int get_price(const std::vector &res, int32_t val, const std::vector &pri) { + for (size_t idx = 0; idx < res.size(); ++idx) { + if (res[idx] == val && pri.size() > idx) + return pri[idx]; + } + return -1; +} + +template +static int get_price(const std::vector &mat_res, int32_t mat, const std::vector &gloss_res, int32_t gloss, const std::vector &pri) { + for (size_t idx = 0; idx < mat_res.size(); ++idx) { + if (mat_res[idx] == mat && (gloss_res[idx] == -1 || gloss_res[idx] == gloss) && pri.size() > idx) + return pri[idx]; + } + return -1; +} + +static const uint16_t PLANT_BASE = 419; +static const uint16_t NUM_PLANT_TYPES = 200; + +static int32_t get_sell_request_multiplier(df::item *item, const df::historical_entity::T_resources &resources, const std::vector *prices) { + static const df::dfhack_material_category silk_cat(df::dfhack_material_category::mask_silk); + static const df::dfhack_material_category yarn_cat(df::dfhack_material_category::mask_yarn); + static const df::dfhack_material_category leather_cat(df::dfhack_material_category::mask_leather); + + int16_t item_type = item->getType(); + int16_t item_subtype = item->getSubtype(); + int16_t mat_type = item->getMaterial(); + int32_t mat_subtype = item->getMaterialIndex(); + + bool inorganic = mat_type == df::builtin_mats::INORGANIC; + bool is_plant = (uint16_t)(mat_type - PLANT_BASE) < NUM_PLANT_TYPES; + + switch (item_type) { + case df::item_type::BAR: + if (inorganic) { + if (int32_t price = get_price(resources.metals, mat_subtype, prices[df::entity_sell_category::MetalBars]); price != -1) + return price; + } + break; + case df::item_type::SMALLGEM: + if (inorganic) { + if (int32_t price = get_price(resources.gems, mat_subtype, prices[df::entity_sell_category::SmallCutGems]); price != -1) + return price; + } + break; + case df::item_type::BLOCKS: + if (inorganic) { + if (int32_t price = get_price(resources.stones, mat_subtype, prices[df::entity_sell_category::StoneBlocks]); price != -1) + return price; + } + break; + case df::item_type::ROUGH: + if (int32_t price = get_price(resources.misc_mat.glass.mat_type, mat_type, resources.misc_mat.glass.mat_index, mat_subtype, + prices[df::entity_sell_category::Glass]); price != -1) + return price; + break; + case df::item_type::BOULDER: + if (int32_t price = get_price(resources.stones, mat_subtype, prices[df::entity_sell_category::Stone]); price != -1) + return price; + if (int32_t price = get_price(resources.misc_mat.clay.mat_type, mat_type, resources.misc_mat.clay.mat_index, mat_subtype, + prices[df::entity_sell_category::Clay]); price != -1) + return price; + break; + case df::item_type::WOOD: + if (int32_t price = get_price(resources.organic.wood.mat_type, mat_type, resources.organic.wood.mat_index, mat_subtype, + prices[df::entity_sell_category::Wood]); price != -1) + return price; + break; + case df::item_type::CHAIN: + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::RopesPlant]); price != -1) + return price; + } + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid()) { + if (mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::RopesSilk]); price != -1) + return price; + } + if (mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::RopesYarn]); price != -1) + return price; + } + } + } + break; + case df::item_type::FLASK: + if (int32_t price = get_price(resources.misc_mat.flasks.mat_type, mat_type, resources.misc_mat.flasks.mat_index, mat_subtype, + prices[df::entity_sell_category::FlasksWaterskins]); price != -1) + return price; + break; + case df::item_type::GOBLET: + if (int32_t price = get_price(resources.misc_mat.crafts.mat_type, mat_type, resources.misc_mat.crafts.mat_index, mat_subtype, + prices[df::entity_sell_category::CupsMugsGoblets]); price != -1) + return price; + break; + case df::item_type::INSTRUMENT: + if (int32_t price = get_price(resources.instrument_type, mat_subtype, prices[df::entity_sell_category::Instruments]); price != -1) + return price; + break; + case df::item_type::TOY: + if (int32_t price = get_price(resources.toy_type, mat_subtype, prices[df::entity_sell_category::Toys]); price != -1) + return price; + break; + case df::item_type::CAGE: + if (int32_t price = get_price(resources.misc_mat.cages.mat_type, mat_type, resources.misc_mat.cages.mat_index, mat_subtype, + prices[df::entity_sell_category::Cages]); price != -1) + return price; + break; + case df::item_type::BARREL: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Barrels]); price != -1) + return price; + break; + case df::item_type::BUCKET: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Buckets]); price != -1) + return price; + break; + case df::item_type::WEAPON: + if (int32_t price = get_price(resources.weapon_type, mat_subtype, prices[df::entity_sell_category::Weapons]); price != -1) + return price; + if (int32_t price = get_price(resources.digger_type, mat_subtype, prices[df::entity_sell_category::DiggingImplements]); price != -1) + return price; + if (int32_t price = get_price(resources.training_weapon_type, mat_subtype, prices[df::entity_sell_category::TrainingWeapons]); price != -1) + return price; + break; + case df::item_type::ARMOR: + if (int32_t price = get_price(resources.armor_type, mat_subtype, prices[df::entity_sell_category::Bodywear]); price != -1) + return price; + break; + case df::item_type::SHOES: + if (int32_t price = get_price(resources.shoes_type, mat_subtype, prices[df::entity_sell_category::Footwear]); price != -1) + return price; + break; + case df::item_type::SHIELD: + if (int32_t price = get_price(resources.shield_type, mat_subtype, prices[df::entity_sell_category::Shields]); price != -1) + return price; + break; + case df::item_type::HELM: + if (int32_t price = get_price(resources.helm_type, mat_subtype, prices[df::entity_sell_category::Headwear]); price != -1) + return price; + break; + case df::item_type::GLOVES: + if (int32_t price = get_price(resources.gloves_type, mat_subtype, prices[df::entity_sell_category::Handwear]); price != -1) + return price; + break; + case df::item_type::BAG: + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid() && mi.matches(leather_cat)) { + if (int32_t price = get_price(resources.organic.leather.mat_type, mat_type, resources.organic.leather.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsLeather]); price != -1) + return price; + } + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsPlant]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsSilk]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsYarn]); price != -1) + return price; + } + } + break; + case df::item_type::FIGURINE: + case df::item_type::AMULET: + case df::item_type::SCEPTER: + case df::item_type::CROWN: + case df::item_type::RING: + case df::item_type::EARRING: + case df::item_type::BRACELET: + case df::item_type::TOTEM: + case df::item_type::BOOK: + if (int32_t price = get_price(resources.misc_mat.crafts.mat_type, mat_type, resources.misc_mat.crafts.mat_index, mat_subtype, + prices[df::entity_sell_category::Crafts]); price != -1) + return price; + break; + case df::item_type::AMMO: + if (int32_t price = get_price(resources.ammo_type, mat_subtype, prices[df::entity_sell_category::Ammo]); price != -1) + return price; + break; + case df::item_type::GEM: + if (inorganic) { + if (int32_t price = get_price(resources.gems, mat_subtype, prices[df::entity_sell_category::LargeCutGems]); price != -1) + return price; + } + break; + case df::item_type::ANVIL: + if (int32_t price = get_price(resources.metal.anvil.mat_type, mat_type, resources.metal.anvil.mat_index, mat_subtype, + prices[df::entity_sell_category::Anvils]); price != -1) + return price; + break; + case df::item_type::MEAT: + if (int32_t price = get_price(resources.misc_mat.meat.mat_type, mat_type, resources.misc_mat.meat.mat_index, mat_subtype, + prices[df::entity_sell_category::Meat]); price != -1) + return price; + break; + case df::item_type::FISH: + case df::item_type::FISH_RAW: + if (int32_t price = get_price(resources.fish_races, mat_type, resources.fish_castes, mat_subtype, + prices[df::entity_sell_category::Fish]); price != -1) + return price; + break; + case df::item_type::VERMIN: + case df::item_type::PET: + if (int32_t price = get_price(resources.animals.pet_races, mat_type, resources.animals.pet_castes, mat_subtype, + prices[df::entity_sell_category::Pets]); price != -1) + return price; + break; + case df::item_type::SEEDS: + if (int32_t price = get_price(resources.seeds.mat_type, mat_type, resources.seeds.mat_index, mat_subtype, + prices[df::entity_sell_category::Seeds]); price != -1) + return price; + break; + case df::item_type::PLANT: + if (int32_t price = get_price(resources.plants.mat_type, mat_type, resources.plants.mat_index, mat_subtype, + prices[df::entity_sell_category::Plants]); price != -1) + return price; + break; + case df::item_type::SKIN_TANNED: + if (int32_t price = get_price(resources.organic.leather.mat_type, mat_type, resources.organic.leather.mat_index, mat_subtype, + prices[df::entity_sell_category::Leather]); price != -1) + return price; + break; + case df::item_type::PLANT_GROWTH: + if (is_plant) { + if (int32_t price = get_price(resources.tree_fruit_plants, mat_type, resources.tree_fruit_growths, mat_subtype, + prices[df::entity_sell_category::FruitsNuts]); price != -1) + return price; + if (int32_t price = get_price(resources.shrub_fruit_plants, mat_type, resources.shrub_fruit_growths, mat_subtype, + prices[df::entity_sell_category::GardenVegetables]); price != -1) + return price; + } + break; + case df::item_type::THREAD: + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::ThreadPlant]); price != -1) + return price; + } + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid() && mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::ThreadSilk]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::ThreadYarn]); price != -1) + return price; + } + } + break; + case df::item_type::CLOTH: + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::ClothPlant]); price != -1) + return price; + } + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid() && mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::ClothSilk]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::ClothYarn]); price != -1) + return price; + } + } + break; + case df::item_type::PANTS: + if (int32_t price = get_price(resources.pants_type, mat_subtype, prices[df::entity_sell_category::Legwear]); price != -1) + return price; + break; + case df::item_type::BACKPACK: + if (int32_t price = get_price(resources.misc_mat.backpacks.mat_type, mat_type, resources.misc_mat.backpacks.mat_index, mat_subtype, + prices[df::entity_sell_category::Backpacks]); price != -1) + return price; + break; + case df::item_type::QUIVER: + if (int32_t price = get_price(resources.misc_mat.quivers.mat_type, mat_type, resources.misc_mat.quivers.mat_index, mat_subtype, + prices[df::entity_sell_category::Quivers]); price != -1) + return price; + break; + case df::item_type::TRAPCOMP: + if (int32_t price = get_price(resources.trapcomp_type, mat_subtype, prices[df::entity_sell_category::TrapComponents]); price != -1) + return price; + break; + case df::item_type::DRINK: + if (int32_t price = get_price(resources.misc_mat.booze.mat_type, mat_type, resources.misc_mat.booze.mat_index, mat_subtype, + prices[df::entity_sell_category::Drinks]); price != -1) + return price; + break; + case df::item_type::POWDER_MISC: + if (int32_t price = get_price(resources.misc_mat.powders.mat_type, mat_type, resources.misc_mat.powders.mat_index, mat_subtype, + prices[df::entity_sell_category::Powders]); price != -1) + return price; + if (int32_t price = get_price(resources.misc_mat.sand.mat_type, mat_type, resources.misc_mat.sand.mat_index, mat_subtype, + prices[df::entity_sell_category::Sand]); price != -1) + return price; + break; + case df::item_type::CHEESE: + if (int32_t price = get_price(resources.misc_mat.cheese.mat_type, mat_type, resources.misc_mat.cheese.mat_index, mat_subtype, + prices[df::entity_sell_category::Cheese]); price != -1) + return price; + break; + case df::item_type::LIQUID_MISC: + if (int32_t price = get_price(resources.misc_mat.extracts.mat_type, mat_type, resources.misc_mat.extracts.mat_index, mat_subtype, + prices[df::entity_sell_category::Extracts]); price != -1) + return price; + break; + case df::item_type::SPLINT: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Splints]); price != -1) + return price; + break; + case df::item_type::CRUTCH: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Crutches]); price != -1) + return price; + break; + case df::item_type::TOOL: + if (int32_t price = get_price(resources.tool_type, mat_subtype, prices[df::entity_sell_category::Tools]); price != -1) + return price; + break; + case df::item_type::EGG: + if (int32_t price = get_price(resources.egg_races, mat_type, resources.egg_castes, mat_subtype, + prices[df::entity_sell_category::Eggs]); price != -1) + return price; + break; + case df::item_type::SHEET: + if (int32_t price = get_price(resources.organic.parchment.mat_type, mat_type, resources.organic.parchment.mat_index, mat_subtype, + prices[df::entity_sell_category::Parchment]); price != -1) + return price; + break; + default: + break; + } + + for (size_t idx = 0; idx < resources.wood_products.item_type.size(); ++idx) { + if (resources.wood_products.item_type[idx] == item_type && + (resources.wood_products.item_subtype[idx] == -1 || resources.wood_products.item_subtype[idx] == item_subtype) && + resources.wood_products.material.mat_type[idx] == mat_type && + (resources.wood_products.material.mat_index[idx] == -1 || resources.wood_products.material.mat_index[idx] == mat_subtype) && + prices[df::entity_sell_category::Miscellaneous].size() > idx) + return prices[df::entity_sell_category::Miscellaneous][idx]; + } + return DEFAULT_AGREEMENT_MULTIPLIER; } -static int32_t get_trade_agreement_multiplier(df::item *item, df::caravan_state *caravan, bool caravan_buying) { +static int32_t get_sell_request_multiplier(df::item *item, const df::caravan_state *caravan) { + const df::entity_sell_prices *sell_prices = caravan->sell_prices; + if (!sell_prices) + return DEFAULT_AGREEMENT_MULTIPLIER; + + auto caravan_he = df::historical_entity::find(caravan->entity); + if (!caravan_he) + return DEFAULT_AGREEMENT_MULTIPLIER; + + return get_sell_request_multiplier(item, caravan_he->resources, &sell_prices->price[0]); +} + +static int32_t get_trade_agreement_multiplier(df::item *item, const df::caravan_state *caravan, bool caravan_buying) { if (!caravan) return DEFAULT_AGREEMENT_MULTIPLIER; return caravan_buying ? get_buy_request_multiplier(item, caravan->buy_prices) - : get_sell_request_multiplier(item, caravan->sell_prices); + : get_sell_request_multiplier(item, caravan); } int Items::getValue(df::item *item, df::caravan_state *caravan, bool caravan_buying) From b938891e113238f2b9f7e365d62b998115271d55 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 4 Jul 2023 03:58:45 -0700 Subject: [PATCH 4/6] add dfhack.items.isRequestedTradeGood --- docs/changelog.txt | 3 ++- docs/dev/Lua API.rst | 4 ++++ library/LuaApi.cpp | 1 + library/include/modules/Items.h | 2 ++ library/modules/Items.cpp | 13 +++++++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 4a8a100df..e6a9dd36d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -50,8 +50,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Price calculations fixed for many item types ## Lua -- ``dfhack.items.markForTrade``: new API for marking items for trade - ``dfhack.units.getUnitByNobleRole``, ``dfhack.units.getUnitsByNobleRole``: unit lookup API by role +- ``dfhack.items.markForTrade``: mark items for trade +- ``dfhack.items.isRequestedTradeGood``: discover whether an item is named in a trade agreement with an active caravan - ``dfhack.items.getValue``: gained optional ``caravan`` and ``caravan_buying`` parameters for prices that take trader races and agreements into account ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index e81a34de3..8553d4e1a 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1778,6 +1778,10 @@ Items module for the item and ``false`` to get the price that the caravan will sell the item for. +* ``dfhack.items.isRequestedTradeGood(item)`` + + Returns whether any active caravan will pay extra for the given item. + * ``dfhack.items.createItem(item_type, item_subtype, mat_type, mat_index, unit)`` Creates an item, similar to the `createitem` plugin. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 7fb14baeb..7afb3b9ea 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2020,6 +2020,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getSubtypeDef), WRAPM(Items, getItemBaseValue), WRAPM(Items, getValue), + WRAPM(Items, isRequestedTradeGood), WRAPM(Items, createItem), WRAPM(Items, checkMandates), WRAPM(Items, canTrade), diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 6cce5d2f1..c473654e3 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -203,6 +203,8 @@ DFHACK_EXPORT bool canTrade(df::item *item); DFHACK_EXPORT bool canTradeWithContents(df::item *item); /// marks the given item for trade at the given depot DFHACK_EXPORT bool markForTrade(df::item *item, df::building_tradedepotst *depot); +/// Returns true if an active caravan will pay extra for the given item +DFHACK_EXPORT bool isRequestedTradeGood(df::item *item); /// Checks whether the item is an assigned hauling vehicle DFHACK_EXPORT bool isRouteVehicle(df::item *item); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 537be7c3a..06741894d 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1917,6 +1917,19 @@ static int32_t get_trade_agreement_multiplier(df::item *item, const df::caravan_ : get_sell_request_multiplier(item, caravan); } +bool Items::isRequestedTradeGood(df::item *item) { + for (auto caravan : df::global::plotinfo->caravans) { + auto trade_state = caravan->trade_state; + if (caravan->time_remaining <= 0 || + (trade_state != df::caravan_state::T_trade_state::Approaching && + trade_state != df::caravan_state::T_trade_state::AtDepot)) + continue; + if (get_buy_request_multiplier(item, caravan->buy_prices) > DEFAULT_AGREEMENT_MULTIPLIER) + return true; + } + return false; +} + int Items::getValue(df::item *item, df::caravan_state *caravan, bool caravan_buying) { CHECK_NULL_POINTER(item); From 7d3c8bd0406fd6d89fc01300510b5b479528982e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 5 Jul 2023 12:16:36 -0700 Subject: [PATCH 5/6] add notes to the API section of the changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index e6a9dd36d..bbd682c2d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -45,6 +45,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Units::getUnitByNobleRole``, ``Units::getUnitsByNobleRole``: unit lookup API by role +- ``Items::markForTrade()``, ``Items::isRequestedTradeGood()``, ``Items::getValue``: see Lua notes below ## Internals - Price calculations fixed for many item types From 6a8522ab5e762ae5df6f5e7e26e3ef2babd32a94 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 6 Jul 2023 03:21:19 -0700 Subject: [PATCH 6/6] generalize dfhack.items.isRequestedTradeGood --- docs/dev/Lua API.rst | 5 +++-- library/include/modules/Items.h | 2 +- library/modules/Items.cpp | 14 +++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 8553d4e1a..11bac3f61 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1778,9 +1778,10 @@ Items module for the item and ``false`` to get the price that the caravan will sell the item for. -* ``dfhack.items.isRequestedTradeGood(item)`` +* ``dfhack.items.isRequestedTradeGood(item[, caravan_state])`` - Returns whether any active caravan will pay extra for the given item. + Returns whether a caravan will pay extra for the given item. If caravan_state + is not given, checks all active caravans. * ``dfhack.items.createItem(item_type, item_subtype, mat_type, mat_index, unit)`` diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index c473654e3..7541660f4 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -204,7 +204,7 @@ DFHACK_EXPORT bool canTradeWithContents(df::item *item); /// marks the given item for trade at the given depot DFHACK_EXPORT bool markForTrade(df::item *item, df::building_tradedepotst *depot); /// Returns true if an active caravan will pay extra for the given item -DFHACK_EXPORT bool isRequestedTradeGood(df::item *item); +DFHACK_EXPORT bool isRequestedTradeGood(df::item *item, df::caravan_state *caravan = NULL); /// Checks whether the item is an assigned hauling vehicle DFHACK_EXPORT bool isRouteVehicle(df::item *item); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 06741894d..3fc08813c 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1917,7 +1917,19 @@ static int32_t get_trade_agreement_multiplier(df::item *item, const df::caravan_ : get_sell_request_multiplier(item, caravan); } -bool Items::isRequestedTradeGood(df::item *item) { +static bool is_requested_trade_good(df::item *item, df::caravan_state *caravan) { + auto trade_state = caravan->trade_state; + if (caravan->time_remaining <= 0 || + (trade_state != df::caravan_state::T_trade_state::Approaching && + trade_state != df::caravan_state::T_trade_state::AtDepot)) + return false; + return get_buy_request_multiplier(item, caravan->buy_prices) > DEFAULT_AGREEMENT_MULTIPLIER; +} + +bool Items::isRequestedTradeGood(df::item *item, df::caravan_state *caravan) { + if (caravan) + return is_requested_trade_good(item, caravan); + for (auto caravan : df::global::plotinfo->caravans) { auto trade_state = caravan->trade_state; if (caravan->time_remaining <= 0 ||