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