1 #include "item_action.h"
2
3 #include <algorithm>
4 #include <iterator>
5 #include <list>
6 #include <memory>
7 #include <new>
8 #include <set>
9 #include <tuple>
10 #include <unordered_set>
11 #include <utility>
12
13 #include "avatar.h"
14 #include "calendar.h"
15 #include "catacharset.h"
16 #include "clone_ptr.h"
17 #include "debug.h"
18 #include "game.h"
19 #include "input.h"
20 #include "inventory.h"
21 #include "item.h"
22 #include "item_contents.h"
23 #include "item_factory.h"
24 #include "item_pocket.h"
25 #include "itype.h"
26 #include "iuse.h"
27 #include "json.h"
28 #include "make_static.h"
29 #include "optional.h"
30 #include "output.h"
31 #include "pimpl.h"
32 #include "player.h"
33 #include "ret_val.h"
34 #include "string_formatter.h"
35 #include "translations.h"
36 #include "type_id.h"
37 #include "ui.h"
38
39 class Character;
40
41 static const std::string errstring( "ERROR" );
42
43 static const bionic_id bio_tools( "bio_tools" );
44 static const bionic_id bio_claws( "bio_claws" );
45 static const bionic_id bio_claws_weapon( "bio_claws_weapon" );
46
47 static const itype_id itype_UPS( "UPS" );
48
49 struct tripoint;
50
51 static item_action nullaction;
52
key_bound_to(const input_context & ctxt,const item_action_id & act)53 static cata::optional<input_event> key_bound_to( const input_context &ctxt,
54 const item_action_id &act )
55 {
56 const std::vector<input_event> keys = ctxt.keys_bound_to( act, /*maximum_modifier_count=*/1 );
57 if( keys.empty() ) {
58 return cata::nullopt;
59 } else {
60 return keys.front();
61 }
62 }
63
64 class actmenu_cb : public uilist_callback
65 {
66 private:
67 const action_map am;
68 public:
actmenu_cb(const action_map & acm)69 explicit actmenu_cb( const action_map &acm ) : am( acm ) { }
70 ~actmenu_cb() override = default;
71
key(const input_context & ctxt,const input_event & event,int,uilist *)72 bool key( const input_context &ctxt, const input_event &event, int /*idx*/,
73 uilist * /*menu*/ ) override {
74 const std::string &action = ctxt.input_to_action( event );
75 // Don't write a message if unknown command was sent
76 // Only when an inexistent tool was selected
77 const auto itemless_action = am.find( action );
78 if( itemless_action != am.end() ) {
79 popup( _( "You do not have an item that can perform this action." ) );
80 return true;
81 }
82 return false;
83 }
84 };
85
86 item_action_generator::item_action_generator() = default;
87
88 item_action_generator::~item_action_generator() = default;
89
90 // Get use methods of this item and its contents
item_has_uses_recursive() const91 bool item::item_has_uses_recursive() const
92 {
93 if( !type->use_methods.empty() ) {
94 return true;
95 }
96
97 return contents.item_has_uses_recursive();
98 }
99
item_has_uses_recursive() const100 bool item_contents::item_has_uses_recursive() const
101 {
102 for( const item_pocket &pocket : contents ) {
103 if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) &&
104 pocket.item_has_uses_recursive() ) {
105 return true;
106 }
107 }
108
109 return false;
110 }
111
item_has_uses_recursive() const112 bool item_pocket::item_has_uses_recursive() const
113 {
114 for( const item &it : contents ) {
115 if( it.item_has_uses_recursive() ) {
116 return true;
117 }
118 }
119
120 return false;
121 }
122
map_actions_to_items(player & p) const123 item_action_map item_action_generator::map_actions_to_items( player &p ) const
124 {
125 return map_actions_to_items( p, std::vector<item *>() );
126 }
127
map_actions_to_items(player & p,const std::vector<item * > & pseudos) const128 item_action_map item_action_generator::map_actions_to_items( player &p,
129 const std::vector<item *> &pseudos ) const
130 {
131 std::set< item_action_id > unmapped_actions;
132 for( const auto &ia_ptr : item_actions ) { // Get ids of wanted actions
133 unmapped_actions.insert( ia_ptr.first );
134 }
135
136 item_action_map candidates;
137 std::vector< item * > items = p.inv_dump();
138 items.reserve( items.size() + pseudos.size() );
139 items.insert( items.end(), pseudos.begin(), pseudos.end() );
140
141 std::unordered_set< item_action_id > to_remove;
142 for( item *i : items ) {
143 if( !i->item_has_uses_recursive() ) {
144 continue;
145 }
146
147 for( const item_action_id &use : unmapped_actions ) {
148 // Actually used item can be different from the "outside item"
149 // For example, sheathed knife
150 item *actual_item = i->get_usable_item( use );
151 if( actual_item == nullptr ) {
152 continue;
153 }
154
155 const use_function *func = actual_item->get_use( use );
156 if( !( func && func->get_actor_ptr() &&
157 func->get_actor_ptr()->can_use( p, *actual_item, false, p.pos() ).success() ) ) {
158 continue;
159 }
160 if( !actual_item->ammo_sufficient() &&
161 ( !actual_item->has_flag( STATIC( flag_id( "USE_UPS" ) ) ) ||
162 p.charges_of( itype_UPS ) < actual_item->ammo_required() ) ) {
163 continue;
164 }
165
166 // Don't try to remove 'irremovable' toolmods
167 if( actual_item->is_toolmod() && use == STATIC( item_action_id( "TOOLMOD_ATTACH" ) ) &&
168 actual_item->has_flag( STATIC( flag_id( "IRREMOVABLE" ) ) ) ) {
169 continue;
170 }
171
172 // Add to usable items if it needs less charges per use or has less charges
173 auto found = candidates.find( use );
174 bool better = false;
175 if( found == candidates.end() ) {
176 better = true;
177 } else {
178 if( actual_item->ammo_required() > found->second->ammo_required() ) {
179 continue; // Other item consumes less charges
180 }
181
182 if( found->second->ammo_remaining() > actual_item->ammo_remaining() ) {
183 better = true; // Items with less charges preferred
184 }
185 }
186
187 if( better ) {
188 candidates[use] = actual_item;
189 if( actual_item->ammo_required() == 0 ) {
190 to_remove.insert( use );
191 }
192 }
193 }
194
195 for( const item_action_id &r : to_remove ) {
196 unmapped_actions.erase( r );
197 }
198 }
199
200 return candidates;
201 }
202
get_action_name(const item_action_id & id) const203 std::string item_action_generator::get_action_name( const item_action_id &id ) const
204 {
205 const auto &act = get_action( id );
206 if( !act.name.empty() ) {
207 return act.name.translated();
208 }
209
210 return id;
211 }
212
action_exists(const item_action_id & id) const213 bool item_action_generator::action_exists( const item_action_id &id ) const
214 {
215 return item_actions.find( id ) != item_actions.end();
216 }
217
get_action(const item_action_id & id) const218 const item_action &item_action_generator::get_action( const item_action_id &id ) const
219 {
220 const auto &iter = item_actions.find( id );
221 if( iter != item_actions.end() ) {
222 return iter->second;
223 }
224
225 debugmsg( "Couldn't find item action named %s", id.c_str() );
226 return nullaction;
227 }
228
load_item_action(const JsonObject & jo)229 void item_action_generator::load_item_action( const JsonObject &jo )
230 {
231 item_action ia;
232
233 ia.id = jo.get_string( "id" );
234 if( !jo.read( "name", ia.name ) || ia.name.empty() ) {
235 ia.name = no_translation( ia.id );
236 }
237
238 item_actions[ia.id] = ia;
239 }
240
check_consistency() const241 void item_action_generator::check_consistency() const
242 {
243 for( const auto &elem : item_actions ) {
244 const auto &action = elem.second;
245 if( !item_controller->has_iuse( action.id ) ) {
246 debugmsg( "Item action \"%s\" isn't known to the game. Check item action definitions in JSON.",
247 action.id.c_str() );
248 }
249 }
250 }
251
item_action_menu()252 void game::item_action_menu()
253 {
254 const auto &gen = item_action_generator::generator();
255 const action_map &item_actions = gen.get_item_action_map();
256
257 // HACK: A bit of a hack for now. If more pseudos get implemented, this should be un-hacked
258 std::vector<item *> pseudos;
259 item toolset( "toolset", calendar::turn );
260 if( u.has_active_bionic( bio_tools ) ) {
261 pseudos.push_back( &toolset );
262 }
263 item bio_claws_item( static_cast<std::string>( bio_claws_weapon ), calendar::turn );
264 if( u.has_active_bionic( bio_claws ) ) {
265 pseudos.push_back( &bio_claws_item );
266 }
267
268 item_action_map iactions = gen.map_actions_to_items( u, pseudos );
269 if( iactions.empty() ) {
270 popup( _( "You don't have any items with registered uses" ) );
271 }
272
273 uilist kmenu;
274 kmenu.text = _( "Execute which action?" );
275 kmenu.input_category = "ITEM_ACTIONS";
276 input_context ctxt( "ITEM_ACTIONS", keyboard_mode::keycode );
277 for( const std::pair<const item_action_id, item_action> &id : item_actions ) {
278 ctxt.register_action( id.first, id.second.name );
279 kmenu.additional_actions.emplace_back( id.first, id.second.name );
280 }
281 actmenu_cb callback( item_actions );
282 kmenu.callback = &callback;
283 int num = 0;
284
285 const auto assigned_action = [&iactions]( const item_action_id & action ) {
286 return iactions.find( action ) != iactions.end();
287 };
288
289 std::vector<std::tuple<item_action_id, std::string, std::string>> menu_items;
290 // Sorts menu items by action.
291 using Iter = decltype( menu_items )::iterator;
292 const auto sort_menu = []( Iter from, Iter to ) {
293 std::sort( from, to, []( const std::tuple<item_action_id, std::string, std::string> &lhs,
294 const std::tuple<item_action_id, std::string, std::string> &rhs ) {
295 return std::get<1>( lhs ).compare( std::get<1>( rhs ) ) < 0;
296 } );
297 };
298 // Add mapped actions to the menu vector.
299 std::transform( iactions.begin(), iactions.end(), std::back_inserter( menu_items ),
300 []( const std::pair<item_action_id, item *> &elem ) {
301 std::string ss = elem.second->display_name();
302 if( elem.second->ammo_required() ) {
303 ss += string_format( "(-%d)", elem.second->ammo_required() );
304 }
305
306 const use_function *method = elem.second->get_use( elem.first );
307 if( method ) {
308 return std::make_tuple( method->get_type(), method->get_name(), ss );
309 } else {
310 return std::make_tuple( errstring, std::string( "NO USE FUNCTION" ), ss );
311 }
312 } );
313 // Sort mapped actions.
314 sort_menu( menu_items.begin(), menu_items.end() );
315 // Add unmapped but binded actions to the menu vector.
316 for( const auto &elem : item_actions ) {
317 if( key_bound_to( ctxt, elem.first ).has_value() && !assigned_action( elem.first ) ) {
318 menu_items.emplace_back( elem.first, gen.get_action_name( elem.first ), "-" );
319 }
320 }
321 // Sort unmapped actions.
322 auto iter = menu_items.begin();
323 std::advance( iter, iactions.size() );
324 sort_menu( iter, menu_items.end() );
325 // Determine max lengths, to print the menu nicely.
326 std::pair<int, int> max_len;
327 for( const auto &elem : menu_items ) {
328 max_len.first = std::max( max_len.first, utf8_width( std::get<1>( elem ), true ) );
329 max_len.second = std::max( max_len.second, utf8_width( std::get<2>( elem ), true ) );
330 }
331 // Fill the menu.
332 for( const auto &elem : menu_items ) {
333 std::string ss;
334 ss += std::get<1>( elem );
335 ss += std::string( max_len.first - utf8_width( std::get<1>( elem ), true ), ' ' );
336 ss += std::string( 4, ' ' );
337
338 ss += std::get<2>( elem );
339 ss += std::string( max_len.second - utf8_width( std::get<2>( elem ), true ), ' ' );
340
341 const cata::optional<input_event> bind = key_bound_to( ctxt, std::get<0>( elem ) );
342 const bool enabled = assigned_action( std::get<0>( elem ) );
343
344 kmenu.addentry( num, enabled, bind, ss );
345 num++;
346 }
347
348 kmenu.query();
349 if( kmenu.ret < 0 || kmenu.ret >= static_cast<int>( iactions.size() ) ) {
350 return;
351 }
352
353 const item_action_id action = std::get<0>( menu_items[kmenu.ret] );
354 item *it = iactions[action];
355
356 u.invoke_item( it, action );
357
358 u.inv->restack( u );
359 u.inv->unsort();
360 }
361
get_type() const362 std::string use_function::get_type() const
363 {
364 if( actor ) {
365 return actor->type;
366 } else {
367 return errstring;
368 }
369 }
370
can_use(const Character &,const item &,bool,const tripoint &) const371 ret_val<bool> iuse_actor::can_use( const Character &, const item &, bool, const tripoint & ) const
372 {
373 return ret_val<bool>::make_success();
374 }
375
is_valid() const376 bool iuse_actor::is_valid() const
377 {
378 return item_action_generator::generator().action_exists( type );
379 }
380
get_name() const381 std::string iuse_actor::get_name() const
382 {
383 return item_action_generator::generator().get_action_name( type );
384 }
385
get_name() const386 std::string use_function::get_name() const
387 {
388 if( actor ) {
389 return actor->get_name();
390 } else {
391 return errstring;
392 }
393 }
394
395