diff --git a/data/json/itemgroups/misc.json b/data/json/itemgroups/misc.json index 8b82da7eab8a3..690a78a1a51e5 100644 --- a/data/json/itemgroups/misc.json +++ b/data/json/itemgroups/misc.json @@ -147,6 +147,6 @@ "id": "horse_gear", "type": "item_group", "//": "Horse vehicle items", - "items": [ [ "riding_saddle", 50 ], [ "yoke_harness", 50 ] ] + "items": [ [ "horse_tack", 50 ], [ "yoke_harness", 50 ] ] } ] diff --git a/data/json/itemgroups/tools.json b/data/json/itemgroups/tools.json index f8944c0505ea3..4f2e4ba7f2a1a 100644 --- a/data/json/itemgroups/tools.json +++ b/data/json/itemgroups/tools.json @@ -304,7 +304,7 @@ [ "chem_hexamine", 10 ], [ "esbit_stove", 15 ], [ "mess_tin", 5 ], - [ "riding_saddle", 3 ], + [ "horse_tack", 3 ], [ "saddlebag", 5 ], [ "lifestraw", 3 ], [ "acetylene_machine", 2 ] diff --git a/data/json/items/armor/pets_horse_armor.json b/data/json/items/armor/pets_horse_armor.json index 6e68706090474..af1d6a32e9c34 100644 --- a/data/json/items/armor/pets_horse_armor.json +++ b/data/json/items/armor/pets_horse_armor.json @@ -1,65 +1,85 @@ [ { + "abstract": "horse_armor", "type": "PET_ARMOR", - "id": "kevlar_armor_horse", + "name": "horse armor", "symbol": "[", - "looks_like": "hsurvivor_suit", + "min_pet_vol": "380 L", + "max_pet_vol": "1000 L", + "pet_bodytype": "horse", + "flags": [ "IS_PET_ARMOR" ] + }, + { + "type": "PET_ARMOR", + "id": "kevlar_armor_horse", + "copy-from": "horse_armor", "color": "yellow", - "name": "Kevlar-lined horse peto", + "name": { "str": "Kevlar-lined horse peto" }, "description": "A heavy mattress-like armor of cloth, leather and thick linings of Kevlar, originally used as protection in bullfighting. You could put this on a friendly horse.", "price": 50000, "price_postapoc": 5000, - "material": [ "kevlar" ], - "weight": "30000 g", + "material": [ "cotton", "leather", "kevlar" ], + "weight": "30 kg", "volume": "150 L", - "bashing": 10, - "to_hit": -3, - "flags": [ "IS_PET_ARMOR", "NO_SALVAGE" ], - "material_thickness": 2, - "max_pet_vol": "1000000 ml", - "min_pet_vol": "380000 ml", - "pet_bodytype": "horse" + "material_thickness": 10 }, { "type": "PET_ARMOR", "id": "acidchitin_armor_horse", - "copy-from": "kevlar_armor_horse", + "copy-from": "horse_armor", "color": "green", - "name": "biosilicified chitin horse body armor", + "name": { "str": "biosilicified chitin horse armor" }, "description": "A makeshift assembly of criniere, peytral and croupiere made from biosilicified chitin fitted to a thin mesh. You could put this on a friendly horse.", - "proportional": { "price": 1.67, "price_postapoc": 1.67, "weight": 1.15 }, - "relative": { "environmental_protection": 7 }, - "material": [ "acidchitin" ] + "price": 120000, + "price_postapoc": 12000, + "material": [ "acidchitin", "steel" ], + "weight": "35 kg", + "volume": "150 L", + "material_thickness": 6, + "environmental_protection": 7 }, { "type": "PET_ARMOR", "id": "chitin_armor_horse", - "copy-from": "acidchitin_armor_horse", - "name": "chitin horse body armor", + "copy-from": "horse_armor", + "color": "green", + "name": { "str": "chitin horse armor" }, "description": "A makeshift assembly of criniere, peytral and croupiere made from chitin fitted to a thin mesh. You could put this on a friendly horse.", - "relative": { "price": -15000, "price_postapoc": -1500, "environmental_protection": -3 }, - "material": [ "chitin" ] + "price": 100000, + "price_postapoc": 10000, + "material": [ "chitin", "steel" ], + "weight": "35 kg", + "volume": "150 L", + "material_thickness": 6, + "environmental_protection": 4 }, { "type": "PET_ARMOR", "id": "chainmail_armor_horse", - "copy-from": "kevlar_armor_horse", - "color": "light_red", - "name": "chainmail horse coat", + "copy-from": "horse_armor", + "color": "green", + "name": { "str": "chainmail horse armor" }, "description": "A heavy covering of chainmail, suitably made for horses as protection. You could put this on a friendly horse.", - "proportional": { "price": 0.83, "price_postapoc": 0.83, "weight": 1.3 }, - "relative": { "material_thickness": -2 }, - "material": [ "iron", "budget_steel" ] + "price": 40000, + "price_postapoc": 4000, + "material": [ "steel", "leather" ], + "weight": "40 kg", + "volume": "150 L", + "material_thickness": 6 }, { "type": "PET_ARMOR", "id": "leather_armor_horse", - "copy-from": "kevlar_armor_horse", - "color": "brown", - "name": "boiled leather horse barding with caparison", + "copy-from": "horse_armor", + "color": "green", + "name": { "str": "boiled leather horse barding with caprison" }, "description": "A full barding for horses consisting of boiled leather and cloth undercovering. This caparison is depicting a battle between a monstrous dragon and regal griffin. You could put this on a friendly horse.", - "proportional": { "price": 0.58, "price_postapoc": 0.58, "weight": 0.5 }, + "price": 30000, + "price_postapoc": 3000, "material": [ "cotton", "leather" ], + "weight": "15 kg", + "volume": "150 L", + "material_thickness": 6, "snippet_category": [ { "id": "battle", @@ -98,40 +118,37 @@ { "type": "PET_ARMOR", "id": "leatherbone_armor_horse", - "copy-from": "leather_armor_horse", + "copy-from": "horse_armor", + "color": "green", "name": { "str": "boiled leather horse barding with bones", "str_pl": "boiled leather horse bardings with bones" }, "description": "Decorative bones affixed to leather horse barding to invoke fear in bandits and raiders and traders all! You could put this on a friendly horse.", - "relative": { "price": 1500, "price_postapoc": 150, "weight": 500 }, - "material": [ "bone", "leather" ] + "price": 45000, + "price_postapoc": 4500, + "material": [ "bone", "leather" ], + "weight": "17 kg", + "volume": "150 L", + "material_thickness": 6 }, { "type": "PET_ARMOR", "id": "rubber_armor_horse", - "copy-from": "kevlar_armor_horse", - "color": "dark_gray", - "name": "horse rain sheet", + "copy-from": "horse_armor", + "color": "green", + "name": { "str": "horse rain sheet" }, "description": "A thin plastic covering adapted for horses to protect from acid rain and other caustic sources. You could put this on a friendly horse.", - "proportional": { "price": 0.34, "price_postapoc": 0.34, "min_pet_vol": 0.6, "weight": 0.45 }, - "relative": { "environmental_protection": 10, "material_thickness": -2 }, - "material": [ "neoprene", "plastic" ] - }, - { - "type": "PET_ARMOR", - "id": "superalloy_armor_horse", - "copy-from": "kevlar_armor_horse", - "color": "light_cyan", - "name": "superalloy crafted horse barding", - "description": "The latest fashion statement and protection for polo equestrians and ahistorical reenactor steeds alike, designed and manufactured by Land Dwarf Industries. You could put this on a friendly horse.", - "proportional": { "price": 100, "price_postapoc": 100, "min_pet_vol": 0.72, "weight": 0.72 }, - "relative": { "environmental_protection": 10, "storage": 32 }, - "material": [ "superalloy" ] + "price": 15000, + "price_postapoc": 1500, + "material": [ "neoprene", "plastic" ], + "weight": "14 kg", + "volume": "150 L", + "material_thickness": 3 }, { "id": "saddlebag", "type": "ARMOR", "name": { "str": "pair of saddle bags", "str_pl": "pairs of saddle bags" }, "description": "A pair of covered pouches laid across the back of a horse behind the saddle.", - "weight": "1000 g", + "weight": "1 kg", "volume": "7500 ml", "price": 15000, "material": [ "leather" ], diff --git a/data/json/items/migration.json b/data/json/items/migration.json index d930b0e8d342d..9e705e69b8946 100644 --- a/data/json/items/migration.json +++ b/data/json/items/migration.json @@ -959,6 +959,11 @@ "type": "MIGRATION", "replace": "40x46mm_m651" }, + { + "id": "riding_saddle", + "type": "MIGRATION", + "replace": "horse_tack" + }, { "id": "ballistic_vest", "type": "MIGRATION", diff --git a/data/json/items/tools.json b/data/json/items/tools.json index 772ffb8685549..747374b0f16b1 100644 --- a/data/json/items/tools.json +++ b/data/json/items/tools.json @@ -26,18 +26,19 @@ "flags": [ "RADIO_MODABLE", "RADIO_INVOKE_PROC", "BOMB", "GRENADE" ] }, { - "id": "riding_saddle", + "id": "horse_tack", "type": "TOOL", - "name": "riding saddle", - "description": "A saddle that can be placed on a tamed animal that is capable of being ridden.", - "weight": "800 g", - "volume": "2 L", + "name": "horse tack", + "description": "A saddle, bridle, and associated tack that can be placed on a tamed animal that is capable of being ridden.", + "weight": "2 kg", + "volume": "7 L", "price": 0, "to_hit": -1, "bashing": 5, - "material": [ "leather" ], + "material": [ "leather", "steel" ], "symbol": "M", - "color": "yellow" + "color": "yellow", + "flags": [ "TACK" ] }, { "id": "EMPbomb_act", diff --git a/data/json/mapgen/isherwood_farms/cabin_isherwood.json b/data/json/mapgen/isherwood_farms/cabin_isherwood.json index a9ce290580fd4..0efc2782ac4bb 100644 --- a/data/json/mapgen/isherwood_farms/cabin_isherwood.json +++ b/data/json/mapgen/isherwood_farms/cabin_isherwood.json @@ -159,7 +159,7 @@ "liquids": { "l": { "liquid": "water_clean", "amount": [ 0, 100 ] } }, "place_loot": [ { "item": "cattlefodder", "x": [ 17, 18 ], "y": [ 2, 4 ], "chance": 100 }, - { "item": "riding_saddle", "x": 15, "y": 2, "chance": 100 }, + { "item": "horse_tack", "x": 15, "y": 2, "chance": 100 }, { "item": "stepladder", "x": 15, "y": 4, "chance": 100 }, { "item": "straw_pile", "x": [ 17, 18 ], "y": [ 2, 4 ], "chance": 30, "repeat": [ 2, 4 ] } ], diff --git a/data/json/npcs/isherwood_farm/NPC_Jesse_Isherwood.json b/data/json/npcs/isherwood_farm/NPC_Jesse_Isherwood.json index 631da27f5e4cb..de326b28672d6 100644 --- a/data/json/npcs/isherwood_farm/NPC_Jesse_Isherwood.json +++ b/data/json/npcs/isherwood_farm/NPC_Jesse_Isherwood.json @@ -206,7 +206,7 @@ "success_lie": "Show me the bodies.", "failure": "It was a lost cause anyways…" }, - "end": { "opinion": { "trust": 1, "value": 1 }, "effect": [ { "u_buy_item": "riding_saddle", "count": 1 } ] } + "end": { "opinion": { "trust": 1, "value": 1 }, "effect": [ { "u_buy_item": "horse_tack", "count": 1 } ] } }, { "id": "MISSION_ISHERWOOD_JESSE_2", @@ -232,7 +232,7 @@ "success_lie": "Show me the bodies.", "failure": "It was a lost cause anyways…" }, - "end": { "opinion": { "trust": 1, "value": 1 }, "effect": [ { "u_buy_item": "riding_saddle", "count": 1 } ] } + "end": { "opinion": { "trust": 1, "value": 1 }, "effect": [ { "u_buy_item": "horse_tack", "count": 1 } ] } }, { "id": "MISSION_ISHERWOOD_JESSE_2", diff --git a/data/json/professions.json b/data/json/professions.json index b4eda0c9233a0..526e293de8856 100644 --- a/data/json/professions.json +++ b/data/json/professions.json @@ -3881,7 +3881,7 @@ "cowboy_hat", "boots_western", "wristwatch", - "riding_saddle", + "horse_tack", "pockknife", "cattlefodder", "cattlefodder", diff --git a/data/json/recipes/other/tool.json b/data/json/recipes/other/tool.json index db9729a4b4133..4e23bc24726a9 100644 --- a/data/json/recipes/other/tool.json +++ b/data/json/recipes/other/tool.json @@ -209,16 +209,15 @@ ] }, { - "result": "riding_saddle", + "result": "horse_tack", "type": "recipe", "category": "CC_OTHER", "subcategory": "CSC_OTHER_TOOLS", - "skill_used": "tailor", - "difficulty": 4, + "skills_required": [ [ "fabrication", 3 ], [ "tailor", 4 ] ], "time": 300000, "book_learn": [ [ "textbook_tailor", 4 ], [ "manual_tailor", 5 ], [ "tailor_portfolio", 4 ] ], "using": [ [ "sewing_standard", 100 ], [ "cordage", 2 ] ], - "components": [ [ [ "leather", 30 ], [ "tanned_hide", 5 ], [ "fur", 30 ], [ "tanned_pelt", 5 ] ] ] + "components": [ [ [ "leather", 30 ], [ "tanned_hide", 5 ], [ "fur", 30 ], [ "tanned_pelt", 5 ] ], [ [ "wire", 2 ] ] ] }, { "result": "jack", diff --git a/doc/JSON_FLAGS.md b/doc/JSON_FLAGS.md index a86e1ba5f0257..e92e3a1a3f7bb 100644 --- a/doc/JSON_FLAGS.md +++ b/doc/JSON_FLAGS.md @@ -682,6 +682,7 @@ List of known flags, used in both `terrain.json` and `furniture.json`. - ```REQUIRES_TINDER``` ... Requires tinder to be present on the tile this item tries to start a fire on. - ```SLEEP_AID``` ... This item helps in sleeping. - ```SLOW_WIELD``` ... Has an additional time penalty upon wielding. For melee weapons and guns this is offset by the relevant skill. Stacks with "NEEDS_UNFOLD". +- ```TACK``` ... Item can be used as tack for a mount. - ```TIE_UP``` ... Item can be used to tie up a creature. - ```TINDER``` ... This item can be used as tinder for lighting a fire with a REQUIRES_TINDER flagged firestarter. - ```TRADER_AVOID``` ... NPCs will not start with this item. Use this for active items (e.g. flashlight (on)), dangerous items (e.g. active bomb), fake item or unusual items (e.g. unique quest item). diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index d2c2aeda697ee..f2f4932e79d6e 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -219,25 +219,17 @@ static void pass_to_ownership_handling( item obj, player *p ) static void stash_on_pet( const std::list &items, monster &pet, player *p ) { - // Add volume of the bag itself since it is going to be subtracted later in the for-each loop. - units::volume remaining_volume = pet.inv.empty() ? 0_ml : - pet.inv.front().get_storage() + pet.inv.front().volume(); - units::mass remaining_weight = pet.weight_capacity(); + units::volume remaining_volume = pet.storage_item->get_storage() - pet.get_carried_volume(); + units::mass remaining_weight = pet.weight_capacity() - pet.get_carried_weight(); - for( const auto &it : pet.inv ) { - remaining_volume -= it.volume(); - remaining_weight -= it.weight(); - } - - for( auto &it : items ) { - pet.add_effect( effect_controlled, 5_turns ); + for( const item &it : items ) { if( it.volume() > remaining_volume ) { - add_msg( m_bad, _( "%1$s did not fit and fell to the %2$s." ), - it.display_name(), g->m.name( pet.pos() ) ); + add_msg( m_bad, _( "%1$s did not fit and fell to the %2$s." ), it.display_name(), + g->m.name( pet.pos() ) ); g->m.add_item_or_charges( pet.pos(), it ); } else if( it.weight() > remaining_weight ) { - add_msg( m_bad, _( "%1$s is too heavy and fell to the %2$s." ), - it.display_name(), g->m.name( pet.pos() ) ); + add_msg( m_bad, _( "%1$s is too heavy and fell to the %2$s." ), it.display_name(), + g->m.name( pet.pos() ) ); g->m.add_item_or_charges( pet.pos(), it ); } else { pet.add_item( it ); diff --git a/src/character.cpp b/src/character.cpp index b6cc04a73d170..82fc0d1d44574 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -611,7 +611,7 @@ void Character::mount_creature( monster &z ) z.remove_effect( effect_tied ); if( z.tied_item ) { i_add( *z.tied_item ); - z.tied_item = cata::nullopt; + z.tied_item.reset(); } } z.mounted_player_id = getID(); diff --git a/src/creature.cpp b/src/creature.cpp index c3f49a6c13aad..f268e9578653b 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -685,7 +685,8 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack if( z ) { if( !proj.get_drop().is_null() ) { z->add_effect( effect_tied, 1_turns, num_bp, true ); - z->tied_item = proj.get_drop(); + item drop_item = proj.get_drop(); + z->tied_item = cata::make_value( proj.get_drop() ); } else { add_msg( m_debug, "projectile with TANGLE effect, but no drop item specified" ); } diff --git a/src/item.cpp b/src/item.cpp index dbb326a8905d1..1589f1441c311 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -2201,6 +2201,61 @@ void item::armor_info( std::vector &info, const iteminfo_query *parts, } } +void item::animal_armor_info( std::vector &info, const iteminfo_query *parts, + int /* batch */, + bool /* debug */ ) const +{ + if( !is_pet_armor() ) { + return; + } + + const std::string space = " "; + + int converted_storage_scale = 0; + const double converted_storage = round_up( convert_volume( get_storage().value(), + &converted_storage_scale ), 2 ); + if( parts->test( iteminfo_parts::ARMOR_STORAGE ) && converted_storage > 0 ) { + const iteminfo::flags f = converted_storage_scale == 0 ? iteminfo::no_flags : iteminfo::is_decimal; + info.push_back( iteminfo( "ARMOR", space + _( "Storage: " ), + string_format( " %s", volume_units_abbr() ), + f, converted_storage ) ); + } + + // Whatever the last entry was, we want a newline at this point + info.back().bNewLine = true; + + if( parts->test( iteminfo_parts::ARMOR_PROTECTION ) ) { + info.push_back( iteminfo( "ARMOR", _( "Protection: Bash: " ), "", + iteminfo::no_newline, bash_resist() ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Cut: " ), cut_resist() ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Acid: " ), "", + iteminfo::no_newline, acid_resist() ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Fire: " ), "", + iteminfo::no_newline, fire_resist() ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Environmental: " ), + get_base_env_resist( *this ) ) ); + if( type->can_use( "GASMASK" ) || type->can_use( "DIVE_TANK" ) ) { + info.push_back( iteminfo( "ARMOR", + _( "Protection when active: " ) ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Acid: " ), "", + iteminfo::no_newline, + acid_resist( false, get_base_env_resist_w_filter() ) ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Fire: " ), "", + iteminfo::no_newline, + fire_resist( false, get_base_env_resist_w_filter() ) ) ); + info.push_back( iteminfo( "ARMOR", space + _( "Environmental: " ), + get_env_resist( get_base_env_resist_w_filter() ) ) ); + } + + if( damage() > 0 ) { + info.push_back( iteminfo( "ARMOR", + _( "Protection values are reduced by damage and " + "you may be able to improve them by repairing this " + "item." ) ) ); + } + } +} + void item::book_info( std::vector &info, const iteminfo_query *parts, int /* batch */, bool /* debug */ ) const { @@ -3300,6 +3355,7 @@ std::string item::info( std::vector &info, const iteminfo_query *parts gunmod_info( info, parts, batch, debug ); armor_info( info, parts, batch, debug ); + animal_armor_info( info, parts, batch, debug ); book_info( info, parts, batch, debug ); container_info( info, parts, batch, debug ); battery_info( info, parts, batch, debug ); diff --git a/src/item.h b/src/item.h index d54d521738b31..97a6b2f554dcd 100644 --- a/src/item.h +++ b/src/item.h @@ -399,6 +399,8 @@ class item : public visitable bool debug ) const; void armor_info( std::vector &info, const iteminfo_query *parts, int batch, bool debug ) const; + void animal_armor_info( std::vector &info, const iteminfo_query *parts, int batch, + bool debug ) const; void book_info( std::vector &info, const iteminfo_query *parts, int batch, bool debug ) const; void battery_info( std::vector &info, const iteminfo_query *parts, int batch, diff --git a/src/monexamine.cpp b/src/monexamine.cpp index 8cbe1cdf03e96..66758d9ccdaf9 100644 --- a/src/monexamine.cpp +++ b/src/monexamine.cpp @@ -26,13 +26,13 @@ #include "string_input_popup.h" #include "translations.h" #include "ui.h" +#include "units.h" #include "bodypart.h" #include "debug.h" #include "enums.h" #include "player_activity.h" #include "rng.h" #include "string_formatter.h" -#include "units.h" #include "type_id.h" #include "pimpl.h" #include "point.h" @@ -58,6 +58,7 @@ bool monexamine::pet_menu( monster &z ) push_zlave, rename, attach_bag, + remove_bag, drop_all, give_items, mon_armor_add, @@ -88,12 +89,14 @@ bool monexamine::pet_menu( monster &z ) amenu.addentry( swap_pos, true, 's', _( "Swap positions" ) ); amenu.addentry( push_zlave, true, 'p', _( "Push %s" ), pet_name ); amenu.addentry( rename, true, 'e', _( "Rename" ) ); - if( z.has_effect( effect_has_bag ) || z.has_effect( effect_monster_armor ) ) { + if( z.has_effect( effect_has_bag ) ) { amenu.addentry( give_items, true, 'g', _( "Place items into bag" ) ); - amenu.addentry( drop_all, true, 'd', _( "Drop all items except armor" ) ); - } - if( !z.has_effect( effect_has_bag ) && !z.has_flag( MF_RIDEABLE_MECH ) ) { - amenu.addentry( attach_bag, true, 'b', _( "Attach bag" ) ); + amenu.addentry( remove_bag, true, 'b', _( "Remove bag from %s" ), pet_name ); + if( !z.inv.empty() ) { + amenu.addentry( drop_all, true, 'd', _( "Remove all items from bag" ) ); + } + } else if( !z.has_flag( MF_RIDEABLE_MECH ) ) { + amenu.addentry( attach_bag, true, 'b', _( "Attach bag to %s" ), pet_name ); } if( z.has_effect( effect_harnessed ) ) { amenu.addentry( mon_harness_remove, true, 'H', _( "Remove vehicle harness from %s" ), pet_name ); @@ -128,12 +131,12 @@ bool monexamine::pet_menu( monster &z ) amenu.addentry( milk, true, 'm', _( "Milk %s" ), pet_name ); } if( z.has_flag( MF_PET_MOUNTABLE ) && !z.has_effect( effect_saddled ) && - g->u.has_amount( "riding_saddle", 1 ) && g->u.get_skill_level( skill_survival ) >= 1 ) { - amenu.addentry( attach_saddle, true, 'h', _( "Attach a saddle to %s" ), pet_name ); + g->u.has_item_with_flag( "TACK" ) && g->u.get_skill_level( skill_survival ) >= 1 ) { + amenu.addentry( attach_saddle, true, 'h', _( "Tack up %s" ), pet_name ); } else if( z.has_flag( MF_PET_MOUNTABLE ) && z.has_effect( effect_saddled ) ) { - amenu.addentry( remove_saddle, true, 'h', _( "Remove the saddle from %s" ), pet_name ); + amenu.addentry( remove_saddle, true, 'h', _( "Remove tack from %s" ), pet_name ); } else if( z.has_flag( MF_PET_MOUNTABLE ) && !z.has_effect( effect_saddled ) && - g->u.has_amount( "riding_saddle", 1 ) && g->u.get_skill_level( skill_survival ) < 1 ) { + g->u.has_item_with_flag( "TACK" ) && g->u.get_skill_level( skill_survival ) < 1 ) { amenu.addentry( remove_saddle, false, 'h', _( "You don't know how to saddle %s" ), pet_name ); } if( z.has_flag( MF_PAY_BOT ) ) { @@ -148,13 +151,10 @@ bool monexamine::pet_menu( monster &z ) amenu.addentry( mount, false, 'r', _( "%s is too small to carry your weight" ), pet_name ); } else if( g->u.get_skill_level( skill_survival ) < 1 ) { amenu.addentry( mount, false, 'r', _( "You have no knowledge of riding at all" ) ); - } else if( g->u.get_weight() >= z.get_weight() / 5 ) { + } else if( g->u.get_weight() >= z.get_weight() * z.get_mountable_weight_ratio() ) { amenu.addentry( mount, false, 'r', _( "You are too heavy to mount %s" ), pet_name ); } else if( !z.has_effect( effect_saddled ) && g->u.get_skill_level( skill_survival ) < 4 ) { amenu.addentry( mount, false, 'r', _( "You are not skilled enough to ride without a saddle" ) ); - } else if( z.has_effect( effect_saddled ) && g->u.get_skill_level( skill_survival ) < 1 ) { - amenu.addentry( mount, false, 'r', _( "Despite the saddle, you still don't know how to ride %s" ), - pet_name ); } } else { const itype &type = *item::find_type( z.type->mech_battery ); @@ -198,6 +198,9 @@ bool monexamine::pet_menu( monster &z ) case attach_bag: attach_bag_to( z ); break; + case remove_bag: + remove_bag_from( z ); + break; case drop_all: dump_items( z ); break; @@ -250,7 +253,7 @@ bool monexamine::pet_menu( monster &z ) return true; } -int monexamine::pet_armor_pos( monster &z ) +static item_location pet_armor_loc( monster &z ) { auto filter = [z]( const item & it ) { return z.type->bodytype == it.get_pet_armor_bodytype() && @@ -258,15 +261,22 @@ int monexamine::pet_armor_pos( monster &z ) z.get_volume() <= it.get_pet_armor_max_vol(); }; - item_location loc = game_menus::inv::titled_filter_menu( filter, g->u, _( "Pet armor" ) ); + return game_menus::inv::titled_filter_menu( filter, g->u, _( "Pet armor" ) ); +} - return g->u.get_item_position( loc.get_item() ); +static item_location tack_loc() +{ + auto filter = []( const item & it ) { + return it.has_flag( "TACK" ); + }; + + return game_menus::inv::titled_filter_menu( filter, g->u, _( "Tack" ) ); } void monexamine::remove_battery( monster &z ) { g->m.add_item_or_charges( g->u.pos(), *z.battery_item ); - z.battery_item = cata::nullopt; + z.battery_item.reset(); } void monexamine::insert_battery( monster &z ) @@ -296,10 +306,10 @@ void monexamine::insert_battery( monster &z ) index > static_cast( bat_inv.size() ) ) { return; } - auto bat_item = bat_inv[index - 1]; + item *bat_item = bat_inv[index - 1]; int item_pos = g->u.get_item_position( bat_item ); if( item_pos != INT_MIN ) { - z.battery_item = *bat_item; + z.battery_item = cata::make_value( *bat_item ); g->u.i_rem( item_pos ); } } @@ -380,11 +390,18 @@ void monexamine::attach_or_remove_saddle( monster &z ) { if( z.has_effect( effect_saddled ) ) { z.remove_effect( effect_saddled ); - item riding_saddle( "riding_saddle", 0 ); - g->u.i_add( riding_saddle ); + g->u.i_add( *z.tack_item ); + z.tack_item.reset(); } else { + item_location loc = tack_loc(); + + if( !loc ) { + add_msg( _( "Never mind." ) ); + return; + } z.add_effect( effect_saddled, 1_turns, num_bp, true ); - g->u.use_amount( "riding_saddle", 1 ); + z.tack_item = cata::make_value( *loc.get_item() ); + loc.remove_item(); } } @@ -476,41 +493,39 @@ void monexamine::attach_bag_to( monster &z ) } item &it = *loc; - // force it to the front of the monster's inventory in case they have armor on - z.inv.insert( z.inv.begin(), it ); - add_msg( _( "You mount the %1$s on your %2$s, ready to store gear." ), - it.display_name(), pet_name ); - g->u.i_rem( &*loc ); + z.storage_item = cata::make_value( it ); + add_msg( _( "You mount the %1$s on your %2$s." ), it.display_name(), pet_name ); + g->u.i_rem( &it ); z.add_effect( effect_has_bag, 1_turns, num_bp, true ); // Update encumbrance in case we were wearing it g->u.flag_encumbrance(); g->u.moves -= 200; } -void monexamine::dump_items( monster &z ) +void monexamine::remove_bag_from( monster &z ) { std::string pet_name = z.get_name(); - int armor_index = 0; - bool found_armor = false; - for( auto &it : z.inv ) { - if( z.has_effect( effect_monster_armor ) && it.is_pet_armor( true ) ) { - found_armor = true; - } else { - armor_index += 1; - g->m.add_item_or_charges( z.pos(), it ); + if( z.storage_item ) { + if( !z.inv.empty() ) { + dump_items( z ); } + g->m.add_item_or_charges( g->u.pos(), *z.storage_item ); + add_msg( _( "You remove the %1$s from %2$s." ), z.storage_item->display_name(), pet_name ); + z.storage_item.reset(); + g->u.moves -= 200; + } else { + add_msg( m_bad, _( "Your %1$s doesn't have a bag!" ), pet_name ); } - item armor; - if( found_armor ) { - armor = z.inv[ armor_index ]; - } - - z.inv.clear(); z.remove_effect( effect_has_bag ); - if( found_armor ) { - armor.set_var( "pet_armor", "true" ); - z.add_item( armor ); +} + +void monexamine::dump_items( monster &z ) +{ + std::string pet_name = z.get_name(); + for( auto &it : z.inv ) { + g->m.add_item_or_charges( g->u.pos(), it ); } + z.inv.clear(); add_msg( _( "You dump the contents of the %s's bag on the ground." ), pet_name ); g->u.moves -= 200; } @@ -518,86 +533,64 @@ void monexamine::dump_items( monster &z ) bool monexamine::give_items_to( monster &z ) { std::string pet_name = z.get_name(); - if( z.inv.empty() ) { + if( !z.storage_item ) { add_msg( _( "There is no container on your %s to put things in!" ), pet_name ); return true; } - int armor_index = INT_MIN; - if( z.has_effect( effect_monster_armor ) ) { - armor_index = 0; - for( auto &it : z.inv ) { - if( it.is_pet_armor( true ) ) { - break; - } else { - armor_index += 1; - } - } - } - - // might be a bag, might be armor - item &storage = z.inv[0]; - units::volume max_cap = storage.get_storage(); - units::mass max_weight = z.weight_capacity() - storage.weight(); - if( armor_index != 0 && armor_index != INT_MIN ) { - item &armor = z.inv[armor_index]; - max_cap += armor.get_storage() + armor.volume(); - max_weight -= armor.weight(); - } - - if( z.inv.size() > 1 ) { - for( auto &i : z.inv ) { - max_cap -= i.volume(); - max_weight -= i.weight(); + item &storage = *z.storage_item; + units::mass max_weight = z.weight_capacity() - z.get_carried_weight(); + units::volume max_volume = storage.get_storage() - z.get_carried_volume(); + + drop_locations items = game_menus::inv::multidrop( g->u ); + drop_locations to_move; + for( const drop_location &itq : items ) { + const item &it = *itq.first; + units::volume item_volume = it.volume() * itq.second; + units::mass item_weight = it.weight() * itq.second; + if( max_weight < item_weight ) { + add_msg( _( "The %1$s is too heavy for the %2$s to carry." ), it.tname(), pet_name ); + continue; + } else if( max_volume < item_volume ) { + add_msg( _( "The %1$s is too big to fit in the %2$s." ), it.tname(), storage.tname() ); + continue; + } else { + max_weight -= item_weight; + max_volume -= item_volume; + to_move.insert( to_move.end(), itq ); } } + z.add_effect( effect_controlled, 5_turns ); + g->u.drop( to_move, z.pos(), true ); - if( max_weight <= 0_gram ) { - add_msg( _( "%1$s is overburdened. You can't transfer your %2$s." ), - pet_name, storage.tname( 1 ) ); - return true; - } - if( max_cap <= 0_ml ) { - add_msg( _( "There's no room in your %1$s's %2$s for that, it's too bulky!" ), - pet_name, storage.tname( 1 ) ); - return true; - } - - const auto items_to_stash = game_menus::inv::multidrop( g->u ); - if( !items_to_stash.empty() ) { - g->u.drop( items_to_stash, z.pos(), true ); - z.add_effect( effect_controlled, 5_turns ); - return true; - } return false; } bool monexamine::add_armor( monster &z ) { std::string pet_name = z.get_name(); - int pos = pet_armor_pos( z ); + item_location loc = pet_armor_loc( z ); - if( pos == INT_MIN ) { + if( !loc ) { add_msg( _( "Never mind." ) ); return true; } - item &armor = g->u.i_at( pos ); - units::mass max_weight = z.weight_capacity(); - for( auto &i : z.inv ) { - max_weight -= i.weight(); - } - if( max_weight <= 0_gram ) { - add_msg( _( "Your %1$s is too heavy for your %2$s." ), armor.tname( 1 ), pet_name ); + item &armor = *loc; + units::mass max_weight = z.weight_capacity() - z.get_carried_weight(); + if( max_weight <= armor.weight() ) { + add_msg( pgettext( "pet armor", "Your %1$s is too heavy for your %2$s." ), armor.tname( 1 ), + pet_name ); return true; } armor.set_var( "pet_armor", "true" ); - z.add_item( armor ); - add_msg( _( "You put the %1$s on your %2$s, protecting it from future harm." ), - armor.display_name(), pet_name ); - g->u.i_rem( pos ); + z.armor_item = cata::make_value( armor ); + add_msg( pgettext( "pet armor", "You put the %1$s on your %2$s." ), armor.display_name(), + pet_name ); + loc.remove_item(); z.add_effect( effect_monster_armor, 1_turns, num_bp, true ); + // TODO: armoring a horse takes a lot longer than 2 seconds. This should be a long action. g->u.moves -= 200; return true; } @@ -611,24 +604,15 @@ void monexamine::remove_harness( monster &z ) void monexamine::remove_armor( monster &z ) { std::string pet_name = z.get_name(); - bool found_armor = false; - int pos = 0; - for( auto &it : z.inv ) { - if( it.is_pet_armor( true ) ) { - found_armor = true; - it.erase_var( "pet_armor" ); - g->m.add_item_or_charges( z.pos(), it ); - //~ %1$s: armor name, %2$s: pet name - add_msg( m_info, pgettext( "pet armor", "You remove the %1$s from %2$s." ), it.tname( 1 ), - pet_name ); - z.inv.erase( z.inv.begin() + pos ); - g->u.moves -= 200; - break; - } else { - pos += 1; - } - } - if( !found_armor ) { + if( z.armor_item ) { + z.armor_item->erase_var( "pet_armor" ); + g->m.add_item_or_charges( z.pos(), *z.armor_item ); + add_msg( pgettext( "pet armor", "You remove the %1$s from %2$s." ), z.armor_item->display_name(), + pet_name ); + z.armor_item.reset(); + // TODO: removing armor from a horse takes a lot longer than 2 seconds. This should be a long action. + g->u.moves -= 200; + } else { add_msg( m_bad, _( "Your %1$s isn't wearing armor!" ), pet_name ); } z.remove_effect( effect_monster_armor ); @@ -662,7 +646,7 @@ void monexamine::tie_or_untie( monster &z ) z.remove_effect( effect_tied ); if( z.tied_item ) { g->u.i_add( *z.tied_item ); - z.tied_item = cata::nullopt; + z.tied_item.reset(); } } else { std::vector rope_inv = g->u.items_with( []( const item & itm ) { @@ -685,10 +669,10 @@ void monexamine::tie_or_untie( monster &z ) index > static_cast( rope_inv.size() ) ) { return; } - auto rope_item = rope_inv[index - 1]; + item *rope_item = rope_inv[index - 1]; int item_pos = g->u.get_item_position( rope_item ); if( item_pos != INT_MIN ) { - z.tied_item = *rope_item; + z.tied_item = cata::make_value( *rope_item ); g->u.i_rem( item_pos ); z.add_effect( effect_tied, 1_turns, num_bp, true ); } diff --git a/src/monexamine.h b/src/monexamine.h index beae4f9d0017a..2aff0fa186741 100644 --- a/src/monexamine.h +++ b/src/monexamine.h @@ -15,9 +15,9 @@ void swap( monster &z ); void push( monster &z ); void rename_pet( monster &z ); void attach_bag_to( monster &z ); +void remove_bag_from( monster &z ); void dump_items( monster &z ); bool give_items_to( monster &z ); -int pet_armor_pos( monster &z ); bool add_armor( monster &z ); void remove_armor( monster &z ); void remove_harness( monster &z ); diff --git a/src/monster.cpp b/src/monster.cpp index 43ca8ab8ddd0e..434201bdb9812 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -234,7 +234,7 @@ monster::monster( const mtype_id &id ) : monster() int max_charge = type.magazine->capacity; item mech_bat_item = item( mech_bat, 0 ); mech_bat_item.ammo_consume( rng( 0, max_charge ), tripoint_zero ); - battery_item = mech_bat_item; + battery_item = cata::make_value( mech_bat_item ); } } @@ -1626,7 +1626,7 @@ bool monster::move_effects( bool ) add_msg( _( "The %s easily slips out of its bonds." ), name() ); } g->m.add_item_or_charges( pos(), *tied_item ); - tied_item = cata::nullopt; + tied_item.reset(); } } else { if( tied_item ) { @@ -1635,7 +1635,7 @@ bool monster::move_effects( bool ) if( !broken ) { g->m.add_item_or_charges( pos(), *tied_item ); } - tied_item = cata::nullopt; + tied_item.reset(); if( u_see_me ) { if( broken ) { add_msg( _( "The %s snaps the bindings holding it down." ), name() ); @@ -1765,14 +1765,11 @@ std::string monster::get_effect_status() const int monster::get_worn_armor_val( damage_type dt ) const { - if( !has_effect( effect_monster_armor ) || inv.empty() ) { + if( !has_effect( effect_monster_armor ) ) { return 0; } - for( const item &armor : inv ) { - if( !armor.is_pet_armor( true ) ) { - continue; - } - return armor.damage_resist( dt ); + if( armor_item ) { + return armor_item->damage_resist( dt ); } return 0; } @@ -2136,14 +2133,8 @@ void monster::die( Creature *nkiller ) return; } // We were carrying a creature, deposit the rider - if( has_effect( effect_ridden ) ) { - if( has_effect( effect_saddled ) ) { - item riding_saddle( "riding_saddle", 0 ); - g->m.add_item_or_charges( pos(), riding_saddle ); - } - if( mounted_player ) { - mounted_player->forced_dismount(); - } + if( has_effect( effect_ridden ) && mounted_player ) { + mounted_player->forced_dismount(); } g->set_critter_died(); dead = true; @@ -2172,13 +2163,12 @@ void monster::die( Creature *nkiller ) ch->rem_morale( MORALE_KILLER_NEED_TO_KILL ); } } - // We were tied up at the moment of death, add a short rope to inventory - if( has_effect( effect_tied ) ) { - if( tied_item ) { - add_item( *tied_item ); - tied_item = cata::nullopt; - } - } + // Drop items stored in optionals + move_special_item_to_inv( tack_item ); + move_special_item_to_inv( armor_item ); + move_special_item_to_inv( storage_item ); + move_special_item_to_inv( tied_item ); + if( has_effect( effect_lightsnare ) ) { add_item( item( "string_36", 0 ) ); add_item( item( "snare_trigger", 0 ) ); @@ -2645,6 +2635,41 @@ void monster::add_msg_player_or_npc( const game_message_type type, } } +units::mass monster::get_carried_weight() +{ + units::mass total_weight = 0_gram; + if( tack_item ) { + total_weight += tack_item->weight(); + } + if( storage_item ) { + total_weight += storage_item->weight(); + } + if( armor_item ) { + total_weight += armor_item->weight(); + } + for( const item &it : inv ) { + total_weight += it.weight(); + } + return total_weight; +} + +units::volume monster::get_carried_volume() +{ + units::volume total_volume = 0_ml; + for( const item &it : inv ) { + total_volume += it.volume(); + } + return total_volume; +} + +void monster::move_special_item_to_inv( cata::value_ptr &it ) +{ + if( it ) { + add_item( *it ); + it.reset(); + } +} + bool monster::is_dead() const { return dead || is_dead_state(); diff --git a/src/monster.h b/src/monster.h index 33ca4f22c1ee8..57ff1aa876456 100644 --- a/src/monster.h +++ b/src/monster.h @@ -27,6 +27,7 @@ #include "type_id.h" #include "units.h" #include "point.h" +#include "value_ptr.h" class JsonObject; class JsonIn; @@ -437,8 +438,15 @@ class monster : public Creature Character *mounted_player = nullptr; // player that is mounting this creature character_id mounted_player_id; // id of player that is mounting this creature ( for save/load ) character_id dragged_foe_id; // id of character being dragged by the monster - cata::optional tied_item; // item used to tie the monster - cata::optional battery_item; // item to power mechs + cata::value_ptr tied_item; // item used to tie the monster + cata::value_ptr tack_item; // item representing saddle and reins and such + cata::value_ptr armor_item; // item of armor the monster may be wearing + cata::value_ptr storage_item; // storage item for monster carrying items + cata::value_ptr battery_item; // item to power mechs + units::mass get_carried_weight(); + units::volume get_carried_volume(); + void move_special_item_to_inv( cata::value_ptr &it ); + // DEFINING VALUES int friendly; int anger = 0; diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 2003b37d29c76..291b56979faba 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -1819,9 +1819,37 @@ void monster::load( const JsonObject &data ) if( data.read( "wandz", wander_pos.z ) ) { wander_pos.z = position.z; } - data.read( "tied_item", tied_item ); + if( data.has_object( "tied_item" ) ) { + JsonIn *tied_item_json = data.get_raw( "tied_item" ); + item newitem; + newitem.deserialize( *tied_item_json ); + tied_item = cata::make_value( newitem ); + } + if( data.has_object( "tack_item" ) ) { + JsonIn *tack_item_json = data.get_raw( "tack_item" ); + item newitem; + newitem.deserialize( *tack_item_json ); + tack_item = cata::make_value( newitem ); + } + if( data.has_object( "armor_item" ) ) { + JsonIn *armor_item_json = data.get_raw( "armor_item" ); + item newitem; + newitem.deserialize( *armor_item_json ); + armor_item = cata::make_value( newitem ); + } + if( data.has_object( "storage_item" ) ) { + JsonIn *storage_item_json = data.get_raw( "storage_item" ); + item newitem; + newitem.deserialize( *storage_item_json ); + storage_item = cata::make_value( newitem ); + } + if( data.has_object( "battery_item" ) ) { + JsonIn *battery_item_json = data.get_raw( "battery_item" ); + item newitem; + newitem.deserialize( *battery_item_json ); + battery_item = cata::make_value( newitem ); + } data.read( "hp", hp ); - data.read( "battery_item", battery_item ); // sp_timeout indicates an old save, prior to the special_attacks refactor if( data.has_array( "sp_timeout" ) ) { @@ -1954,8 +1982,21 @@ void monster::store( JsonOut &json ) const json.member( "morale", morale ); json.member( "hallucination", hallucination ); json.member( "stairscount", staircount ); - json.member( "tied_item", tied_item ); - json.member( "battery_item", battery_item ); + if( tied_item ) { + json.member( "tied_item", *tied_item ); + } + if( tack_item ) { + json.member( "tack_item", *tack_item ); + } + if( armor_item ) { + json.member( "armor_item", *armor_item ); + } + if( storage_item ) { + json.member( "storage_item", *storage_item ); + } + if( battery_item ) { + json.member( "battery_item", *battery_item ); + } // Store the relative position of the goal so it loads correctly after a map shift. json.member( "destination", goal - pos() ); json.member( "ammo", ammo ); diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index 398b0de599e34..13a0276c9bf51 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -1739,7 +1739,7 @@ void vehicle::use_harness( int part, const tripoint &pos ) m.remove_effect( effect_tied ); if( m.tied_item ) { g->u.i_add( *m.tied_item ); - m.tied_item = cata::nullopt; + m.tied_item.reset(); } } }