1616#include " proc_fact.h"
1717#include " recipe.h"
1818#include " string_formatter.h"
19+ #include " type_id.h"
1920#include " translations.h"
2021#include " ui.h"
2122#include " ui_manager.h"
2223
2324namespace
2425{
2526
27+ static const trait_id trait_DEBUG_HS ( " DEBUG_HS" );
28+ static const itype_id itype_bread ( " bread" );
29+ static const itype_id itype_potato ( " potato" );
30+ static const itype_id itype_meat_cooked ( " meat_cooked" );
31+ static const itype_id itype_fish ( " fish" );
32+
2633struct source_entry {
2734 item *src = nullptr ;
2835 std::string where;
2936 proc::part_fact fact;
3037};
3138
39+ struct source_pool {
40+ std::vector<detached_ptr<item>> owned;
41+ std::vector<source_entry> entries;
42+ };
43+
44+ struct source_options {
45+ item *src = nullptr ;
46+ std::string where;
47+ proc::part_ix ix = proc::invalid_part_ix;
48+ int charges = 0 ;
49+ int uses = 1 ;
50+ };
51+
52+ auto make_source_entry ( const source_options &opts ) -> source_entry
53+ {
54+ return source_entry{
55+ .src = opts.src ,
56+ .where = opts.where ,
57+ .fact = proc::normalize_part_fact ( *opts.src , {
58+ .ix = opts.ix ,
59+ .charges = opts.charges ,
60+ .uses = opts.uses
61+ } )
62+ };
63+ }
64+
3265auto source_for_ix ( const std::vector<source_entry> &sources,
3366 const proc::part_ix ix ) -> const source_entry * // *NOPAD*
3467{
@@ -38,9 +71,9 @@ auto source_for_ix( const std::vector<source_entry> &sources,
3871 return &sources[static_cast <size_t >( ix )];
3972}
4073
41- auto gather_sources ( Character &who, const proc::schema &sch ) -> std::vector<source_entry>
74+ auto gather_inventory_sources ( Character &who, const proc::schema &sch ) -> source_pool
4275{
43- auto ret = std::vector<source_entry> {};
76+ auto ret = source_pool {};
4477 auto ix = proc::part_ix{ 0 };
4578 const auto &inv = who.crafting_inventory ();
4679 std::ranges::for_each ( inv.const_slice (), [&]( const std::vector<item *> *stack ) {
@@ -51,26 +84,84 @@ auto gather_sources( Character &who, const proc::schema &sch ) -> std::vector<so
5184 if ( it == nullptr || it->is_craft () ) {
5285 return ;
5386 }
54- auto entry = source_entry{};
55- entry.src = it;
56- entry.where = it->describe_location ( &who );
57- entry.fact = proc::normalize_part_fact ( *it, {
87+ const auto entry = make_source_entry ( {
88+ .src = it,
89+ .where = it->describe_location ( &who ),
5890 .ix = ix,
5991 .charges = it->count_by_charges () ? 1 : 0 ,
6092 .uses = it->count_by_charges () ? std::max ( it->charges , 1 ) : 1
6193 } );
6294 if ( std::ranges::none_of ( sch.slots , [&]( const proc::slot_data & slot ) {
63- return proc::matches_slot ( slot, entry.fact );
95+ return proc::matches_slot ( slot, entry.fact );
6496 } ) ) {
6597 return ;
6698 }
67- ret.push_back ( entry );
99+ ret.entries . push_back ( entry );
68100 ix++;
69101 } );
70102 } );
71103 return ret;
72104}
73105
106+ auto debug_item_for_atom ( const std::string &atom ) -> std::optional<itype_id>
107+ {
108+ if ( atom.starts_with ( " itype:" ) ) {
109+ return itype_id ( atom.substr ( 6 ) );
110+ }
111+ if ( atom == " mat:veggy" ) {
112+ return itype_potato;
113+ }
114+ if ( atom == " mat:flesh" ) {
115+ return itype_meat_cooked;
116+ }
117+ if ( atom == " mat:fish" ) {
118+ return itype_fish;
119+ }
120+ if ( atom == " mat:wheat" ) {
121+ return itype_bread;
122+ }
123+ return std::nullopt ;
124+ }
125+
126+ auto gather_debug_sources ( const proc::schema &sch ) -> source_pool
127+ {
128+ auto ret = source_pool {};
129+ auto ix = proc::part_ix{ 0 };
130+ std::ranges::for_each ( sch.slots , [&]( const proc::slot_data & slot ) {
131+ std::ranges::for_each ( slot.ok , [&]( const std::string & atom ) {
132+ const auto id = debug_item_for_atom ( atom );
133+ if ( !id || id->is_null () ) {
134+ return ;
135+ }
136+ ret.owned .push_back ( item::spawn ( *id, calendar::turn ) );
137+ auto *temp = &*ret.owned .back ();
138+ ret.entries .push_back ( make_source_entry ( {
139+ .src = temp,
140+ .where = _ ( " debug hammerspace" ),
141+ .ix = ix,
142+ .charges = temp->count_by_charges () ? std::max ( temp->charges , 1 ) : 0 ,
143+ .uses = std::max ( slot.max , 1 )
144+ } ) );
145+ ix++;
146+ } );
147+ } );
148+ return ret;
149+ }
150+
151+ auto missing_required_slots ( const proc::schema &sch,
152+ const proc::builder_state &state ) -> std::vector<std::string>
153+ {
154+ auto ret = std::vector<std::string> {};
155+ std::ranges::for_each ( sch.slots , [&]( const proc::slot_data & slot ) {
156+ const auto iter = state.cand .find ( slot.id );
157+ const auto missing = slot.min > 0 && ( iter == state.cand .end () || iter->second .empty () );
158+ if ( missing ) {
159+ ret.push_back ( slot.role );
160+ }
161+ } );
162+ return ret;
163+ }
164+
74165auto current_slot ( const proc::schema &sch, const int slot_cursor ) -> const proc::slot_data &
75166{
76167 return sch.slots [static_cast <size_t >( slot_cursor )];
@@ -164,13 +255,26 @@ auto proc::open_builder( Character &who, const recipe &rec ) -> std::optional<ui
164255 return std::nullopt ;
165256 }
166257
167- const auto sources = gather_sources ( who, sch );
168- const auto facts = sources
169- | std::views::transform ( []( const source_entry & entry ) {
258+ auto source_data = gather_inventory_sources ( who, sch );
259+ if ( who.has_trait ( trait_DEBUG_HS ) && source_data.entries .empty () ) {
260+ source_data = gather_debug_sources ( sch );
261+ }
262+
263+ const auto facts = source_data.entries
264+ | std::views::transform ( []( const source_entry & entry ) {
170265 return entry.fact ;
171266 } )
172- | std::ranges::to<std::vector>();
267+ | std::ranges::to<std::vector>();
173268 auto state = proc::build_state ( sch, facts );
269+ if ( source_data.entries .empty () ) {
270+ popup ( _ ( " No nearby items match this procedural recipe." ) );
271+ return std::nullopt ;
272+ }
273+ if ( const auto missing = missing_required_slots ( sch, state ); !missing.empty () ) {
274+ popup ( _ ( " Missing candidates for required slots: %s" ),
275+ enumerate_as_string ( missing, enumeration_conjunction::none ) );
276+ return std::nullopt ;
277+ }
174278 auto candidate_cursor = std::map<slot_id, int > {};
175279 std::ranges::for_each ( sch.slots , [&]( const slot_data & slot ) {
176280 candidate_cursor.emplace ( slot.id , 0 );
@@ -225,7 +329,7 @@ auto proc::open_builder( Character &who, const recipe &rec ) -> std::optional<ui
225329 const auto color = row == slot_cursor ? c_yellow : proc::slot_complete ( state, sch, slot.id ) ?
226330 c_light_green : c_white;
227331 trim_and_print ( w, point ( 2 , content_top + row - slot_start ), left_width - 2 , color,
228- slot_pick_text ( state, slot, sources ) );
332+ slot_pick_text ( state, slot, source_data. entries ) );
229333 } );
230334
231335 const auto &slot = current_slot ( sch, slot_cursor );
@@ -241,7 +345,7 @@ auto proc::open_builder( Character &who, const recipe &rec ) -> std::optional<ui
241345 const auto cand_end = std::min ( static_cast <int >( candidates.size () ),
242346 cand_start + content_height );
243347 std::ranges::for_each ( std::views::iota ( cand_start, cand_end ), [&]( const int row ) {
244- const auto *source = source_for_ix ( sources , candidates[static_cast <size_t >( row )] );
348+ const auto *source = source_for_ix ( source_data. entries , candidates[static_cast <size_t >( row )] );
245349 if ( source == nullptr ) {
246350 return ;
247351 }
@@ -250,7 +354,7 @@ auto proc::open_builder( Character &who, const recipe &rec ) -> std::optional<ui
250354 color, candidate_text ( *source, state ) );
251355 } );
252356
253- const auto preview = preview_lines ( sch, state, sources );
357+ const auto preview = preview_lines ( sch, state, source_data. entries );
254358 const auto preview_end = std::min ( static_cast <int >( preview.size () ), content_height );
255359 std::ranges::for_each ( std::views::iota ( 0 , preview_end ), [&]( const int row ) {
256360 trim_and_print ( w, point ( left_width + middle_width + 4 , content_top + row ), right_width - 1 ,
@@ -359,7 +463,7 @@ auto proc::open_builder( Character &who, const recipe &rec ) -> std::optional<ui
359463 result.preview = state.fast ;
360464 const auto picks = proc::selected_picks ( state, sch );
361465 std::ranges::for_each ( picks, [&]( const proc::craft_pick & pick ) {
362- if ( const auto *source = source_for_ix ( sources , pick.ix ) ) {
466+ if ( const auto *source = source_for_ix ( source_data. entries , pick.ix ) ) {
363467 result.picks .push_back ( ui_pick{ .slot = pick.slot , .src = source->src , .fact = source->fact } );
364468 }
365469 } );
0 commit comments