Skip to content

Commit ab84b00

Browse files
fix: handle procgen builder debug flow
Assisted-by: openai/gpt-5.4 on opencode Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 5664df1 commit ab84b00

File tree

2 files changed

+172
-22
lines changed

2 files changed

+172
-22
lines changed

src/crafting.cpp

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -855,9 +855,60 @@ auto restore_consumed_proc_items( Character &who, std::vector<detached_ptr<item>
855855
} );
856856
}
857857

858+
auto selected_proc_slots( const proc::ui_result &result ) -> std::vector<proc::slot_id>
859+
{
860+
auto slots = std::vector<proc::slot_id> {};
861+
slots.reserve( result.picks.size() );
862+
std::ranges::for_each( result.picks, [&]( const proc::ui_pick & pick ) {
863+
slots.push_back( pick.slot );
864+
} );
865+
return slots;
866+
}
867+
868+
auto selected_proc_facts( const proc::ui_result &result ) -> std::vector<proc::part_fact>
869+
{
870+
auto facts = std::vector<proc::part_fact> {};
871+
facts.reserve( result.picks.size() );
872+
std::ranges::for_each( result.picks, [&]( const proc::ui_pick & pick ) {
873+
facts.push_back( pick.fact );
874+
} );
875+
return facts;
876+
}
877+
878+
auto finish_debug_proc_craft( Character &who, const recipe &rec,
879+
const proc::ui_result &result ) -> item * // *NOPAD*
880+
{
881+
if( !proc::has( rec.proc_id() ) ) {
882+
return nullptr;
883+
}
884+
885+
const auto &sch = proc::get( rec.proc_id() );
886+
auto newit = proc::make_item( sch, selected_proc_facts( result ), {
887+
.mode = sch.hist.def,
888+
.rec = &rec,
889+
.used = {},
890+
.slots = selected_proc_slots( result )
891+
} );
892+
who.add_msg_player_or_npc(
893+
_( "You craft %s using debug hammerspace." ),
894+
_( "<npcname> crafts %s using debug hammerspace." ),
895+
newit->tname() );
896+
if( newit->made_of( LIQUID ) ) {
897+
liquid_handler::handle_all_liquid( std::move( newit ), PICKUP_RANGE );
898+
} else {
899+
set_item_inventory( who, std::move( newit ) );
900+
}
901+
who.inv_restack();
902+
return nullptr;
903+
}
904+
858905
auto start_proc_craft( Character &who, const recipe &rec, const bool is_long,
859906
const proc::ui_result &result ) -> item * // *NOPAD*
860907
{
908+
if( who.has_trait( trait_DEBUG_HS ) ) {
909+
return finish_debug_proc_craft( who, rec, result );
910+
}
911+
861912
auto tool_selections = std::vector<comp_selection<tool_comp>> {};
862913
if( !prepare_proc_craft_tools( who, rec, tool_selections ) ) {
863914
return nullptr;
@@ -875,14 +926,9 @@ auto start_proc_craft( Character &who, const recipe &rec, const bool is_long,
875926
craft->set_tools_to_continue( true );
876927
who.craft_consume_tools( *craft, 1, true );
877928
craft->set_next_failure_point( who );
878-
auto slots = std::vector<proc::slot_id> {};
879-
slots.reserve( result.picks.size() );
880-
std::ranges::for_each( result.picks, [&]( const proc::ui_pick & pick ) {
881-
slots.push_back( pick.slot );
882-
} );
883929
proc::write_craft_plan( *craft, {
884930
.mode = proc::get( rec.proc_id() ).hist.def,
885-
.slots = std::move( slots )
931+
.slots = selected_proc_slots( result )
886932
} );
887933
return start_craft_item( who, std::move( craft ), rec.skill_used, is_long );
888934
}

src/proc_ui.cpp

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,52 @@
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

2324
namespace
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+
2633
struct 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+
3265
auto 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+
74165
auto 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

Comments
 (0)