Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions data/json/items/gunmod/underbarrel.json
Original file line number Diff line number Diff line change
Expand Up @@ -616,5 +616,62 @@
"min_skills": [ [ "weapon", 2 ] ],
"flags": [ "BIPOD", "SLOW_WIELD" ],
"melee_damage": { "bash": 12 }
},
{
"id": "mounted_flashlight",
"type": "ITEM",
"subtypes": [ "TOOL", "GUNMOD" ],
"name": { "str": "mounted flashlight" },
"description": "Precisely manufactured flashlight that can be mounted on any modern gun. You still can use it without attaching it to anything if you happen to never find a handheld one.",
"weight": "68 g",
"volume": "150 ml",
"longest_side": "63 mm",
"price": "350 USD",
"price_postapoc": "4 USD",
"install_time": "4 m",
"material": [ "steel", "plastic" ],
"symbol": ":",
"color": "brown",
"location": "underbarrel",
"mod_targets": [ "pistol", "shotgun", "smg", "rifle", "crossbow", "launcher" ],
"min_skills": [ [ "weapon", 1 ] ],
"charges_per_use": 1,
"is_visible_when_installed": true,
"use_action": {
"type": "transform",
"msg": "You turn the mounted flashlight on.",
"target": "mounted_flashlight_on",
"active": true,
"need_charges": 1,
"need_charges_msg": "The mounted flashlight's batteries are dead."
},
"pocket_data": [
{
"pocket_type": "MAGAZINE_WELL",
"rigid": true,
"flag_restriction": [ "BATTERY_LIGHT" ],
"default_magazine": "light_battery_cell"
}
],
"tool_ammo": [ "battery" ]
},
{
"id": "mounted_flashlight_on",
"copy-from": "mounted_flashlight",
"type": "ITEM",
"subtypes": [ "TOOL", "GUNMOD" ],
"name": { "str": "mounted flashlight (on)", "str_pl": "mounted flashlights (on)" },
"//": "about 1.5 h of work off a single battery",
"power_draw": "2700 mW",
"revert_to": "mounted_flashlight",
"use_action": {
"menu_text": "Turn off",
"type": "transform",
"msg": "You turn the mounted flashlight off.",
"target": "mounted_flashlight",
"ammo_scale": 0
},
"light": 160,
"extend": { "flags": [ "CHARGEDIM", "TRADER_AVOID" ] }
}
]
5 changes: 3 additions & 2 deletions doc/JSON/ITEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ Guns can be defined like this:

Gun mods can be defined like this:

```C++
```jsonc
"type": "ITEM",
"subtypes": [ "GUNMOD" ], // Allows the below GUNMOD fields to be read in addition to generic ITEM fields
"location": "stock", // Mandatory. Where is this gunmod is installed?
Expand Down Expand Up @@ -891,14 +891,15 @@ Gun mods can be defined like this:
"consume_divisor": 10, // Divide damage against mod by this amount (default 1)
"handling_modifier": 4, // Improve gun handling. For example a forward grip might have 6, a bipod 18
"mode_modifier": [ [ "AUTO", "auto", 4 ] ], // Modify firing modes of the gun, to give AUTO or REACH for example
"barrel_length": "45 mm" // Specify a direct barrel length for this gun mod. If used only the first mod with a barrel length will be counted
"barrel_length": "45 mm", // Specify a direct barrel length for this gun mod. If used only the first mod with a barrel length will be counted
"overheat_threshold_modifier": 100, // Add a flat amount to gun's "overheat_threshold"; if the threshold is 100, and the modifier is 10, the result is 110; if the modifier is -25, the result is 75
"overheat_threshold_multiplier": 1.5, // Multiply gun's "overheat_threshold" by this number; if the threshold is 100, and the multiplier is 1.5, the result is 150; if the multiplier is 0.8, the result is 80
"cooling_value_modifier": 2, // Add a flat amount to gun's "cooling_value"; works the same as overheat_threshold_modifier
"cooling_value_multiplier": 0.5, // Multiply gun's "cooling_value" by this number; works the same as overheat_threshold_multiplier
"heat_per_shot_modifier": -2, // Add a flat amount to gun's "heat_per_shot"; works the same as overheat_threshold_modifier
"heat_per_shot_multiplier": 2.0, // Multiply the gun's "heat_per_shot" by this number; works the same as overheat_threshold_multiplier
"is_bayonet": true, // Optional, if true, the melee damage of this item is added to the base damage of the gun. Defaults to false.
"is_visible_when_installed": false, // optional, if true, this gunmod is shown in your inventory akin to items in pockets, making it possible to interact with it in
"blacklist_slot": [ "rail", "underbarrel" ], // prevents installation of the gunmod if the specified slot(s) are present on the gun.
"blacklist_mod": [ "m203", "m320" ], // prevents installation of the gunmod if the specified mods(s) are present on the gun.
"to_hit_mod": -1 // increases or decreases the item to_hit value
Expand Down
3 changes: 2 additions & 1 deletion src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2240,7 +2240,8 @@ int game::inventory_item_menu( item_location locThisItem,
int cMenu = static_cast<int>( '+' );

item &oThisItem = *locThisItem;
if( u.has_item( oThisItem ) ) {
// u.has_item(oThisItem) do not include mod pockets, where mounted flashlights are
if( /* u.has_item(oThisItem) */ true ) {
#if defined(__ANDROID__)
if( get_option<bool>( "ANDROID_INVENTORY_AUTOADD" ) ) {
add_key_to_quick_shortcuts( oThisItem.invlet, "INVENTORY", false );
Expand Down
2 changes: 1 addition & 1 deletion src/game_inventory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ class pickup_inventory_preset : public inventory_selector_preset
bool skip_wield_check = false, bool ignore_liquidcont = false ) : you( you ),
skip_wield_check( skip_wield_check ), ignore_liquidcont( ignore_liquidcont ) {
save_state = &pickup_sel_default_state;
_pk_type = pocket_type::LAST;
_pk_type = { pocket_type::LAST };
}

std::string get_denial( const item_location &loc ) const override {
Expand Down
11 changes: 8 additions & 3 deletions src/inventory_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2049,9 +2049,14 @@ bool inventory_selector::add_contained_items( item_location &container, inventor
return false;
}

std::list<item *> const items = preset.get_pocket_type() == pocket_type::LAST
? container->all_items_top()
: container->all_items_top( preset.get_pocket_type() );
std::list<item *> items;
if( preset.get_pocket_type().size() == 1 && preset.has_pocket_type( pocket_type::LAST ) ) {
items = container->all_items_top();
} else {
for( const pocket_type pt : preset.get_pocket_type() ) {
items.splice( items.begin(), container->all_items_top( pt ) );
}
}

bool vis_top = false;
inventory_column temp( preset );
Expand Down
18 changes: 14 additions & 4 deletions src/inventory_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#ifndef CATA_SRC_INVENTORY_UI_H
#define CATA_SRC_INVENTORY_UI_H

#include <algorithm>
#include <array>
#include <climits>
#include <cstddef>
Expand All @@ -23,20 +24,22 @@
#include "cursesdef.h"
#include "debug.h"
#include "input_context.h"
#include "item.h"
#include "item_location.h"
#include "itype.h"
#include "memory_fast.h"
#include "pimpl.h"
#include "pocket_type.h"
#include "point.h"
#include "translations.h"
#include "units.h"
#include "value_ptr.h"

class Character;
class JsonObject;
class JsonOut;
class basecamp;
class inventory_selector_preset;
class item;
class item_category;
class item_stack;
class string_input_popup;
Expand Down Expand Up @@ -238,7 +241,10 @@ class inventory_selector_preset
virtual ~inventory_selector_preset() = default;

/** Does this entry satisfy the basic preset conditions? */
virtual bool is_shown( const item_location & ) const {
virtual bool is_shown( const item_location &loc ) const {
if( loc->is_gunmod() && !loc->type->gunmod->is_visible_when_installed ) {
return false;
}
return true;
}

Expand Down Expand Up @@ -269,10 +275,14 @@ class inventory_selector_preset
return check_components;
}

pocket_type get_pocket_type() const {
std::vector<pocket_type> get_pocket_type() const {
return _pk_type;
}

bool has_pocket_type( pocket_type pt ) const {
return std::find( _pk_type.begin(), _pk_type.end(), pt ) != _pk_type.end();
}

virtual std::function<bool( const inventory_entry & )> get_filter( const std::string &filter )
const;

Expand Down Expand Up @@ -307,7 +317,7 @@ class inventory_selector_preset
bool _indent_entries = true;
bool _collate_entries = false;

pocket_type _pk_type = pocket_type::CONTAINER;
std::vector<pocket_type> _pk_type = { pocket_type::CONTAINER, pocket_type::MOD };

private:
class cell_t
Expand Down
23 changes: 21 additions & 2 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10300,6 +10300,16 @@ std::vector<const item_pocket *> item::get_all_ablative_pockets() const
return contents.get_all_ablative_pockets();
}

std::vector<const item_pocket *> item::get_all_contained_and_mod_pockets() const
{
return contents.get_all_contained_and_mod_pockets();
}

std::vector<item_pocket *> item::get_all_contained_and_mod_pockets()
{
return contents.get_all_contained_and_mod_pockets();
}

item_pocket *item::contained_where( const item &contained )
{
return contents.contained_where( contained );
Expand Down Expand Up @@ -10536,7 +10546,7 @@ bool item::is_emissive() const
return true;
}

for( const item_pocket *pkt : get_all_contained_pockets() ) {
for( const item_pocket *pkt : get_all_contained_and_mod_pockets() ) {
if( pkt->transparent() ) {
for( const item *it : pkt->all_items_top() ) {
if( it->is_emissive() ) {
Expand Down Expand Up @@ -12715,7 +12725,16 @@ int item::getlight_emit() const
float lumint = type->light_emission;

if( lumint == 0 ) {
return 0;
// gunmods can create light, but cache_visit_items_with() do not check items inside mod pockets
// we will check it here
if( is_gun() && !gunmods().empty() ) {
for( const item *maybe_flashlight : gunmods() ) {
if( maybe_flashlight->type->light_emission != 0 ) {
return maybe_flashlight->getlight_emit();
}
}
return 0;
}
}

if( ammo_required() == 0 || ( has_flag( flag_USE_UPS ) && ammo_capacity( ammo_battery ) == 0 ) ||
Expand Down
2 changes: 2 additions & 0 deletions src/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,8 @@ class item : public visitable
std::vector<item_pocket *> get_all_standard_pockets();
std::vector<item_pocket *> get_all_ablative_pockets();
std::vector<const item_pocket *> get_all_ablative_pockets() const;
std::vector<const item_pocket *> get_all_contained_and_mod_pockets() const;
std::vector<item_pocket *> get_all_contained_and_mod_pockets();
/**
* Updates the pockets of this item to be correct based on the mods that are installed.
* Pockets which are modified that contain an item will be spilled
Expand Down
16 changes: 15 additions & 1 deletion src/item_contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,20 @@ std::vector<item_pocket *> item_contents::get_all_ablative_pockets()
} );
}

std::vector<const item_pocket *> item_contents::get_all_contained_and_mod_pockets() const
{
return get_pockets( []( item_pocket const & pocket ) {
return pocket.is_type( pocket_type::CONTAINER ) || pocket.is_type( pocket_type::MOD );
} );
}

std::vector<item_pocket *> item_contents::get_all_contained_and_mod_pockets()
{
return get_pockets( []( item_pocket const & pocket ) {
return pocket.is_type( pocket_type::CONTAINER ) || pocket.is_type( pocket_type::MOD );
} );
}

std::vector<const item *> item_contents::get_added_pockets() const
{
std::vector<const item *> items_added;
Expand Down Expand Up @@ -2585,7 +2599,7 @@ void item_contents::process( map &here, Character *carrier, const tripoint_bub_m
temperature_flag flag, float spoil_multiplier_parent, bool watertight_container )
{
for( item_pocket &pocket : contents ) {
if( pocket.is_type( pocket_type::CONTAINER ) ) {
if( pocket.is_type( pocket_type::CONTAINER ) || pocket.is_type( pocket_type::MOD ) ) {
pocket.process( here, carrier, pos, insulation, flag, spoil_multiplier_parent,
watertight_container );
}
Expand Down
2 changes: 2 additions & 0 deletions src/item_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ class item_contents
std::vector<item_pocket *> get_all_standard_pockets();
std::vector<const item_pocket *> get_all_ablative_pockets() const;
std::vector<item_pocket *> get_all_ablative_pockets();
std::vector<const item_pocket *> get_all_contained_and_mod_pockets() const;
std::vector<item_pocket *> get_all_contained_and_mod_pockets();
std::vector<const item_pocket *>
get_pockets( std::function<bool( item_pocket const & )> const &filter ) const;
std::vector<item_pocket *>
Expand Down
5 changes: 4 additions & 1 deletion src/item_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3485,6 +3485,7 @@ void islot_gunmod::deserialize( const JsonObject &jo )
optional( jo, was_loaded, "min_str_required_mod", min_str_required_mod );
optional( jo, was_loaded, "min_str_required_mod_if_prone", min_str_required_mod_if_prone );
optional( jo, was_loaded, "is_bayonet", is_bayonet );
optional( jo, was_loaded, "is_visible_when_installed", is_visible_when_installed );
optional( jo, was_loaded, "blacklist_mod", blacklist_mod, auto_flags_reader<itype_id> {} );
optional( jo, was_loaded, "blacklist_slot", blacklist_slot, auto_flags_reader<gunmod_location> {} );
optional( jo, was_loaded, "barrel_length", barrel_length );
Expand Down Expand Up @@ -3737,7 +3738,9 @@ void Item_factory::add_special_pockets( itype &def )
def.pockets.emplace_back( pocket_type::CORPSE );
}
if( ( def.tool || def.gun ) && !has_pocket_type( def.pockets, pocket_type::MOD ) ) {
def.pockets.emplace_back( pocket_type::MOD );
pocket_data mod_pocket( pocket_type::MOD );
mod_pocket.transparent = true;
def.pockets.emplace_back( mod_pocket );
}
if( !has_pocket_type( def.pockets, pocket_type::MIGRATION ) ) {
def.pockets.emplace_back( pocket_type::MIGRATION );
Expand Down
5 changes: 5 additions & 0 deletions src/itype.h
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,11 @@ struct islot_gunmod : common_ranged_data {
// wheter the item is supposed to work as a bayonet when attached
bool is_bayonet = false;

/** if the item is visible and selectable in the inventory menu
used by mounted flashlights and similar
*/
bool is_visible_when_installed = false;

/** Not compatible on weapons that have this mod slot */
std::set<gunmod_location> blacklist_slot;

Expand Down
8 changes: 7 additions & 1 deletion src/iuse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9218,8 +9218,14 @@ ret_val<void> use_function::can_call( const Character &p, const item &it,
} else if( it.is_broken() ) {
return ret_val<void>::make_failure( _( "Your %s is broken and won't activate." ),
it.tname() );
} else if( actor->type == "GUNMOD_ATTACH" &&
it.is_gunmod() && !p.has_item( it ) ) {
// this should just check if gunmod is in MOD pocket already
// but it requires item_location
// so check if character do not have item in CONTAINER pockets
return ret_val<void>::make_failure(
_( "Your %s is already installed and needs to be detached first." ), it.tname() );
}

return actor->can_use( p, it, here, pos );
}

Expand Down
Loading