diff --git a/data/json/items/armor/bespoke_armor/custom_storage.json b/data/json/items/armor/bespoke_armor/custom_storage.json index 134fd7863e485..8341781c0f8e5 100644 --- a/data/json/items/armor/bespoke_armor/custom_storage.json +++ b/data/json/items/armor/bespoke_armor/custom_storage.json @@ -20,6 +20,42 @@ "max_item_length": "60 cm", "magazine_well": "7500 ml", "moves": 300 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "items slipped under the top of a duffelbag not well secured", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 80, + "extra_encumbrance": 5, + "ripoff": 1 } ], "warmth": 10, @@ -48,6 +84,55 @@ "max_item_length": "50 cm", "magazine_well": "5 L", "moves": 300 + }, + { + "//": "waterbottle pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "510 ml", + "max_contains_weight": "1 kg", + "max_item_length": "12 cm", + "min_item_length": "7 cm", + "moves": 80, + "ripoff": 2 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 60 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 60 } } ], "warmth": 6, @@ -76,6 +161,69 @@ "max_item_length": "55 cm", "magazine_well": "6 L", "moves": 300 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 60 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 60 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 60 } + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 } ], "warmth": 8, diff --git a/data/json/items/armor/storage.json b/data/json/items/armor/storage.json index 5036d0fa7709f..b5603884428c2 100644 --- a/data/json/items/armor/storage.json +++ b/data/json/items/armor/storage.json @@ -19,6 +19,42 @@ "max_contains_weight": "30 kg", "max_item_length": "40 cm", "moves": 300 + }, + { + "//": "waterbottle pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "510 ml", + "max_contains_weight": "1 kg", + "max_item_length": "12 cm", + "min_item_length": "7 cm", + "moves": 80, + "ripoff": 2 + }, + { + "//": "tucked against back", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "4 kg", + "max_item_length": "150 cm", + "min_item_length": "60 cm", + "moves": 200, + "extra_encumbrance": 10, + "ripoff": 3 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } } ], "warmth": 6, @@ -89,6 +125,56 @@ "max_item_length": "70 cm", "moves": 3, "flag_restriction": [ "SHEATH_KNIFE", "SHEATH_SWORD" ] + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 } ], "use_action": { "type": "holster", "holster_prompt": "Sheath blade", "holster_msg": "You sheath your %s" }, @@ -206,6 +292,45 @@ "max_item_length": "30 cm", "magazine_well": "800 ml", "moves": 120 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } } ], "warmth": 10, @@ -292,6 +417,30 @@ "max_item_length": "30 cm", "magazine_well": "800 ml", "moves": 120 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 } ], "warmth": 10, @@ -346,6 +495,31 @@ "max_contains_weight": "60 kg", "max_item_length": "50 cm", "moves": 300 + }, + { + "//": "tucked against back", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "4 kg", + "max_item_length": "150 cm", + "min_item_length": "60 cm", + "moves": 200, + "extra_encumbrance": 10, + "ripoff": 3 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } } ], "warmth": 5, @@ -441,6 +615,42 @@ "max_item_length": "60 cm", "magazine_well": "2 L", "moves": 300 + }, + { + "//": "tucked against back", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "4 kg", + "max_item_length": "150 cm", + "min_item_length": "60 cm", + "moves": 200, + "extra_encumbrance": 10, + "ripoff": 3 + }, + { + "//": "waterbottle pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "510 ml", + "max_contains_weight": "1 kg", + "max_item_length": "12 cm", + "min_item_length": "7 cm", + "moves": 80, + "ripoff": 2 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } } ], "warmth": 5, @@ -497,6 +707,18 @@ "max_item_length": "55 cm", "magazine_well": "5 L", "moves": 300 + }, + { + "//": "items slipped under the top of a duffelbag not well secured", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 80, + "extra_encumbrance": 5, + "ripoff": 1 } ], "warmth": 10, @@ -845,6 +1067,18 @@ "max_contains_weight": "10 kg", "max_item_length": "70 cm", "moves": 300 + }, + { + "//": "tucked against back", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "4 kg", + "max_item_length": "150 cm", + "min_item_length": "60 cm", + "moves": 200, + "extra_encumbrance": 10, + "ripoff": 3 } ], "warmth": 5, @@ -958,6 +1192,44 @@ "max_item_length": "25 cm", "magazine_well": "500 ml", "moves": 120 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "tucked against back", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "4 kg", + "max_item_length": "150 cm", + "min_item_length": "60 cm", + "moves": 200, + "extra_encumbrance": 10, + "ripoff": 3 } ], "warmth": 10, @@ -1144,6 +1416,57 @@ "max_item_length": "30 cm", "magazine_well": "800 ml", "moves": 120 + }, + { + "//": "tucked against back", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "4 kg", + "max_item_length": "150 cm", + "min_item_length": "60 cm", + "moves": 200, + "extra_encumbrance": 10, + "ripoff": 3 + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } + }, + { + "//": "carabiner pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "5 L", + "max_contains_weight": "6 kg", + "max_item_length": "120 cm", + "min_item_length": "40 cm", + "moves": 150, + "extra_encumbrance": 3, + "ripoff": 2, + "activity_noise": { "volume": 8, "chance": 10 } } ], "warmth": 8, @@ -1379,6 +1702,41 @@ "max_item_length": "20 cm", "magazine_well": "200 ml", "moves": 120 + }, + { + "//": "waterbottle pocket", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "510 ml", + "max_contains_weight": "1 kg", + "max_item_length": "12 cm", + "min_item_length": "7 cm", + "moves": 80, + "ripoff": 2 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 + }, + { + "//": "dedicated tool loops. either a daisy chain, hooking to compression straps, or ice axe loop", + "pocket_type": "CONTAINER", + "holster": true, + "max_contains_volume": "4 L", + "max_contains_weight": "6 kg", + "max_item_length": "100 cm", + "min_item_length": "40 cm", + "moves": 300, + "extra_encumbrance": 1, + "ripoff": 5 } ], "warmth": 8, diff --git a/src/avatar.h b/src/avatar.h index cbeffbbc05636..3bd100bd6e1d1 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -324,6 +324,9 @@ class avatar : public Character int movecounter = 0; + // ammount of turns since last check for pocket noise + time_point last_pocket_noise = time_point( 0 ); + vproto_id starting_vehicle; std::vector starting_pets; std::set follower_ids; diff --git a/src/character.cpp b/src/character.cpp index 0549603e49aa5..01b0ddc8892e3 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1760,6 +1760,27 @@ int Character::footstep_sound() const return std::round( volume ); } +int Character::clatter_sound() const +{ + int max_volume = 0; + for( const item &i : worn ) { + // if the item has noise making pockets we should check if they have clatered + if( i.has_noisy_pockets() ) { + for( const item_pocket *pocket : i.get_all_contained_pockets().value() ) { + int noise_chance = pocket->get_pocket_data()->activity_noise.chance; + int volume = pocket->get_pocket_data()->activity_noise.volume; + if( noise_chance > 0 && !pocket->empty() ) { + // if this pocket causes more volume and it triggers noise + if( volume > max_volume && rng( 1, 100 ) < noise_chance ) { + max_volume = volume; + } + } + } + } + } + return std::round( max_volume ); +} + void Character::make_footstep_noise() const { const int volume = footstep_sound(); @@ -1777,6 +1798,17 @@ void Character::make_footstep_noise() const sfx::do_footstep(); } +void Character::make_clatter_sound() const +{ + + const int volume = clatter_sound(); + if( volume <= 0 ) { + return; + } + sounds::sound( pos(), volume, sounds::sound_t::movement, _( "clattering equipment" ), true, + "none", "none" ); // Sound of footsteps may awaken nearby monsters +} + steed_type Character::get_steed_type() const { steed_type steed; @@ -7046,7 +7078,8 @@ void Character::on_hit( Creature *source, bodypart_id bp_hit, Where damage to character is actually applied to hit body parts Might be where to put bleed stuff rather than in player::deal_damage() */ -void Character::apply_damage( Creature *source, bodypart_id hurt, int dam, const bool bypass_med ) +void Character::apply_damage( Creature *source, bodypart_id hurt, int dam, + const bool bypass_med ) { if( is_dead_state() || has_trait( trait_DEBUG_NODMG ) || has_effect( effect_incorporeal ) ) { // don't do any more damage if we're already dead @@ -8554,7 +8587,8 @@ void Character::on_item_acquire( const item &it ) } } -void Character::on_effect_int_change( const efftype_id &eid, int intensity, const bodypart_id &bp ) +void Character::on_effect_int_change( const efftype_id &eid, int intensity, + const bodypart_id &bp ) { // Adrenaline can reduce perceived pain (or increase it when you enter comedown). // See @ref get_perceived_pain() diff --git a/src/character.h b/src/character.h index 2fd4f1f6a28fc..f2fdd9201be89 100644 --- a/src/character.h +++ b/src/character.h @@ -827,7 +827,11 @@ class Character : public Creature, public visitable bool is_prone() const; int footstep_sound() const; + // the sound clattering items dangling off you can make + int clatter_sound() const; void make_footstep_noise() const; + void make_clatter_sound() const; + bool can_switch_to( const move_mode_id &mode ) const; steed_type get_steed_type() const; diff --git a/src/character_escape.cpp b/src/character_escape.cpp index 60bd2037eb014..c869537769736 100644 --- a/src/character_escape.cpp +++ b/src/character_escape.cpp @@ -196,9 +196,37 @@ bool Character::try_remove_grab() _( " tries to break out of the grab, but fails!" ) ); return false; } else { + // when you break out of a grab you have a chance to lose some things from your pockets + // that are hanging off your character + std::vector pd; + for( item &i : worn ) { + // if the item has ripoff pockets we should itterate on them also grabs only effect the torso + if( i.has_ripoff_pockets() ) { + for( item_pocket *pocket : i.get_all_contained_pockets().value() ) { + if( pocket->get_pocket_data()->ripoff > 0 && !pocket->empty() ) { + pd.push_back( pocket ); + } + } + } + } + // if we have items that can be pulled off + if( !pd.empty() ) { + // choose an item to be ripped off + int index = rng( 0, pd.size() - 1 ); + int chance = rng( 0, get_effect_int( effect_grabbed, body_part_torso ) ); + // the item is ripped off your character + if( chance > pd[index]->get_pocket_data()->ripoff ) { + pd[index]->spill_contents( adjacent_tile() ); + add_msg_player_or_npc( m_bad, + _( "As you escape the grab something comes loose and falls to the ground!" ), + _( " escapes the grab something comes loose and falls to the ground!" ) ); + } + } + add_msg_player_or_npc( m_good, _( "You break out of the grab!" ), _( " breaks out of the grab!" ) ); remove_effect( effect_grabbed ); + for( auto&& dest : here.points_in_radius( pos(), 1, 0 ) ) { // *NOPAD* monster *mon = creatures.creature_at( dest ); if( mon && mon->has_effect( effect_grabbing ) ) { diff --git a/src/game.cpp b/src/game.cpp index 0a8ad60eeb37b..ed75adde551d3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -9239,6 +9239,13 @@ bool game::walk_move( const tripoint &dest_loc, const bool via_ramp, const bool u.make_footstep_noise(); + //only clatter items every so often based on activity level + if( to_turns( calendar::turn - u.last_pocket_noise ) > std::max( static_cast + ( 10 - u.activity_level() ), 1 ) ) { + u.make_clatter_sound(); + u.last_pocket_noise = calendar::turn; + } + if( m.has_flag_ter_or_furn( ter_furn_flag::TFLAG_HIDE_PLACE, dest_loc ) ) { add_msg( m_good, _( "You are hiding in the %s." ), m.name( dest_loc ) ); } diff --git a/src/item.cpp b/src/item.cpp index 88572e7bad431..49cdaab665917 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1006,6 +1006,24 @@ bool item::is_ablative() const return t ? t->ablative : false; } +bool item::has_additional_encumbrance() const +{ + const islot_armor *t = find_armor_data(); + return t ? t->additional_pocket_enc : false; +} + +bool item::has_ripoff_pockets() const +{ + const islot_armor *t = find_armor_data(); + return t ? t->ripoff_chance : false; +} + +bool item::has_noisy_pockets() const +{ + const islot_armor *t = find_armor_data(); + return t ? t->noisy : false; +} + bool item::is_worn_only_with( const item &it ) const { return is_power_armor() && it.is_power_armor() && it.covers( bodypart_id( "torso" ) ); @@ -6776,8 +6794,8 @@ int item::get_encumber( const Character &p, const bodypart_id &bodypart, encumber += std::ceil( relative_encumbrance * ( portion_data->max_encumber - portion_data->encumber ) ); - // add the encumbrance values of any ablative plates - if( is_ablative() ) { + // add the encumbrance values of any ablative plates and additional encumbrance pockets + if( is_ablative() || has_additional_encumbrance() ) { for( const item_pocket *pocket : contents.get_all_contained_pockets().value() ) { if( pocket->get_pocket_data()->ablative && !pocket->empty() ) { // get the contained plate @@ -6788,6 +6806,9 @@ int item::get_encumber( const Character &p, const bodypart_id &bodypart, encumber += ablative_portion_data->encumber; } } + if( pocket->get_pocket_data()->extra_encumbrance > 0 && !pocket->empty() ) { + encumber += pocket->get_pocket_data()->extra_encumbrance; + } } } } @@ -8394,6 +8415,11 @@ units::length item::max_containable_length() const return contents.max_containable_length(); } +units::length item::min_containable_length() const +{ + return contents.min_containable_length(); +} + units::volume item::max_containable_volume() const { return contents.max_containable_volume(); diff --git a/src/item.h b/src/item.h index 07b7e21dc1396..aa872034e2f3a 100644 --- a/src/item.h +++ b/src/item.h @@ -1363,6 +1363,7 @@ class item : public visitable const item *avoid = nullptr, bool allow_sealed = false, bool ignore_settings = false ); units::length max_containable_length() const; + units::length min_containable_length() const; units::volume max_containable_volume() const; /** @@ -1735,6 +1736,18 @@ class item : public visitable * Returns if the armor has ablative pockets */ bool is_ablative() const; + /** + * Returns if the armor has pockets with additional encumbrance + */ + bool has_additional_encumbrance() const; + /** + * Returns if the armor has pockets with a chance to be ripped off + */ + bool has_ripoff_pockets() const; + /** + * Returns if the armor has pockets with a chance to make noise when moving + */ + bool has_noisy_pockets() const; /** * Returns the warmth value that this item has when worn. See player class for temperature * related code, or @ref player::warmth. Returned values should be positive. A value diff --git a/src/item_contents.cpp b/src/item_contents.cpp index d5975bc9a9746..12f7dfcd4a428 100644 --- a/src/item_contents.cpp +++ b/src/item_contents.cpp @@ -593,6 +593,22 @@ units::length item_contents::max_containable_length() const return ret; } +units::length item_contents::min_containable_length() const +{ + units::length ret = 0_mm; + for( const item_pocket &pocket : contents ) { + if( !pocket.is_type( item_pocket::pocket_type::CONTAINER ) || pocket.is_ablative() || + pocket.holster_full() ) { + continue; + } + units::length candidate = pocket.min_containable_length(); + if( candidate > ret ) { + ret = candidate; + } + } + return ret; +} + std::set item_contents::magazine_flag_restrictions() const { std::set ret; diff --git a/src/item_contents.h b/src/item_contents.h index c96818819d54d..8676e4bf735bd 100644 --- a/src/item_contents.h +++ b/src/item_contents.h @@ -42,6 +42,7 @@ class item_contents const item *avoid = nullptr, bool allow_sealed = false, bool ignore_settings = false ); units::length max_containable_length() const; + units::length min_containable_length() const; units::volume max_containable_volume() const; std::set magazine_flag_restrictions() const; diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 05b034c27a76c..88d16d49d8f5c 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -799,11 +799,21 @@ void Item_factory::finalize_post( itype &obj ) } } + // go through the pockets and apply some characteristics for( const pocket_data &pocket : obj.pockets ) { if( pocket.ablative ) { obj.armor->ablative = true; break; } + if( pocket.extra_encumbrance > 0 ) { + obj.armor->additional_pocket_enc = true; + } + if( pocket.ripoff > 0 ) { + obj.armor->ripoff_chance = true; + } + if( pocket.activity_noise.chance > 0 ) { + obj.armor->noisy = true; + } } } diff --git a/src/item_pocket.cpp b/src/item_pocket.cpp index faaa89e80d730..b8b1a84d1b788 100644 --- a/src/item_pocket.cpp +++ b/src/item_pocket.cpp @@ -152,6 +152,10 @@ void pocket_data::load( const JsonObject &jo ) max_weight_for_container ); optional( jo, was_loaded, "max_item_length", max_item_length, units::default_length_from_volume( volume_capacity ) * M_SQRT2 ); + optional( jo, was_loaded, "min_item_length", min_item_length ); + optional( jo, was_loaded, "extra_encumbrance", extra_encumbrance, 0 ); + optional( jo, was_loaded, "ripoff", ripoff, 0 ); + optional( jo, was_loaded, "activity_noise", activity_noise ); } optional( jo, was_loaded, "spoil_multiplier", spoil_multiplier, 1.0f ); optional( jo, was_loaded, "weight_multiplier", weight_multiplier, 1.0f ); @@ -177,6 +181,12 @@ void sealable_data::load( const JsonObject &jo ) optional( jo, was_loaded, "spoil_multiplier", spoil_multiplier, 1.0f ); } +void pocket_noise::load( const JsonObject &jo ) +{ + optional( jo, was_loaded, "volume", volume, 0 ); + optional( jo, was_loaded, "chance", chance, 0 ); +} + bool item_pocket::operator==( const item_pocket &rhs ) const { return *data == *rhs.data; @@ -307,6 +317,16 @@ bool item_pocket::better_pocket( const item_pocket &rhs, const item &it, bool ne return !rhs.data->get_flag_restrictions().empty(); } + if( data->extra_encumbrance != rhs.data->extra_encumbrance ) { + // pockets without extra encumbrance should be prioritized + return !rhs.data->extra_encumbrance; + } + + if( data->ripoff > rhs.data->ripoff ) { + // pockets without ripoff chance should be prioritized + return true; + } + if( it.is_comestible() && it.get_comestible()->spoils != 0_seconds ) { // a lower spoil multiplier is better return rhs.spoil_multiplier() < spoil_multiplier(); @@ -907,6 +927,14 @@ void item_pocket::general_info( std::vector &info, int pocket_number, convert_length( data->max_item_length ), data->max_item_length.value() ); } + if( data->min_item_length > 0_mm && !is_ablative() ) { + info.back().bNewLine = true; + info.emplace_back( base_type_str, _( "Minimum item length: " ), + string_format( " %s", length_units( data->min_item_length ) ), + iteminfo::no_flags, + convert_length( data->min_item_length ), data->min_item_length.value() ); + } + if( data->min_item_volume > 0_ml ) { std::string fmt = string_format( " %s", volume_units_abbr() ); info.emplace_back( base_type_str, _( "Minimum item volume: " ), fmt, @@ -927,6 +955,22 @@ void item_pocket::general_info( std::vector &info, int pocket_number, info.emplace_back( "DESCRIPTION", _( "This pocket is rigid." ) ); } + if( data->extra_encumbrance > 0 ) { + info.emplace_back( "DESCRIPTION", + string_format( _( "Causes %d additional encumbrance while in use." ), + data->extra_encumbrance ) ) ; + } + + if( data->ripoff > 0 ) { + info.emplace_back( "DESCRIPTION", + _( "Items have a chance of falling out when you are grabbed." ) ); + } + + if( data->activity_noise.chance > 0 ) { + info.emplace_back( "DESCRIPTION", + _( "Items will make noise when you move." ) ); + } + if( data->watertight ) { info.emplace_back( "DESCRIPTION", _( "This pocket can contain a liquid." ) ); @@ -1177,6 +1221,11 @@ ret_val item_pocket::is_compatible( const item &it ) contain_code::ERR_TOO_BIG, _( "item is too long" ) ); } + if( it.length() < data->min_item_length ) { + return ret_val::make_failure( + contain_code::ERR_TOO_BIG, _( "item is too short" ) ); + } + if( it.volume() < data->min_item_volume ) { return ret_val::make_failure( contain_code::ERR_TOO_SMALL, _( "item is too small" ) ); @@ -1618,6 +1667,14 @@ units::length item_pocket::max_containable_length() const return 0_mm; } +units::length item_pocket::min_containable_length() const +{ + if( data ) { + return data->min_item_length; + } + return 0_mm; +} + units::volume item_pocket::contains_volume() const { units::volume vol = 0_ml; diff --git a/src/item_pocket.h b/src/item_pocket.h index 158b267efe5db..095c4cc7446be 100644 --- a/src/item_pocket.h +++ b/src/item_pocket.h @@ -175,6 +175,7 @@ class item_pocket bool contains_phase( phase_id phase ) const; units::length max_containable_length() const; + units::length min_containable_length() const; // combined volume of contained items units::volume contains_volume() const; @@ -361,6 +362,18 @@ struct sealable_data { void deserialize( const JsonObject &data ); }; +// the chance and volume this pocket makes when moving +struct pocket_noise { + // required for generic_factory + bool was_loaded = false; + /** multiplier for spoilage rate of contained items when sealed */ + int volume = 0; + int chance = 0; + + void load( const JsonObject &jo ); + void deserialize( const JsonObject &data ); +}; + class pocket_data { public: @@ -385,6 +398,8 @@ class pocket_data cata::optional max_item_volume = cata::nullopt; // min volume of item that can be contained, otherwise it spills units::volume min_item_volume = 0_ml; + // min length of item that can be contained used for exterior pockets + units::length min_item_length = 0_mm; // max weight of stuff the pocket can hold units::mass max_contains_weight = max_weight_for_container; // longest item that can fit into the pocket @@ -394,6 +409,12 @@ class pocket_data bool holster = false; // if true, this pocket holds ablative armor bool ablative = false; + // additional encumbrance when this pocket is in use + int extra_encumbrance = 0; + // chance this pockets contents get ripped off when escaping a grab + int ripoff = 0; + // volume this pocket makes when moving + pocket_noise activity_noise; // multiplier for spoilage rate of contained items float spoil_multiplier = 1.0f; // items' weight in this pocket are modified by this number diff --git a/src/itype.h b/src/itype.h index 6c3bede09d662..2ec3ccc0bb5da 100644 --- a/src/itype.h +++ b/src/itype.h @@ -319,6 +319,18 @@ struct islot_armor { * Whether this item has ablative pockets */ bool ablative = false; + /** + * Whether this item has pockets that generate additional encumbrance + */ + bool additional_pocket_enc = false; + /** + * Whether this item has pockets that can be ripped off + */ + bool ripoff_chance = false; + /** + * Whether this item has pockets that are noisy + */ + bool noisy = false; /** * Whitelisted clothing mods. * Restricted clothing mods must be listed here by id to be compatible. diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index e37c169d06f3f..0990eb77753aa 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -307,6 +307,12 @@ void sealable_data::deserialize( const JsonObject &data ) load( data ); } +void pocket_noise::deserialize( const JsonObject &data ) +{ + data.allow_omitted_members(); + load( data ); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// ///// player_activity.h