Skip to content

Commit 8d68ad4

Browse files
feat: add proc builder payload 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 7594c58 commit 8d68ad4

22 files changed

+1631
-31
lines changed

data/json/main.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
local voltmeter = require("./voltmeter")
22
local nyctophobia = require("./nyctophobia")
33
local artifact_analyzer = require("./artifact_analyzer")
4+
local proc = require("./proc")
45

56
local mod = game.mod_runtime[game.current_mod]
67
local storage = game.mod_storage[game.current_mod]
78

89
mod.voltmeter = voltmeter
910
mod.artifact_analyzer = artifact_analyzer
1011
nyctophobia.register(mod)
12+
13+
_G.proc = proc
14+
mod.proc = proc

data/json/proc.lua

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
local proc = {}
2+
proc.food = {}
3+
4+
local function sum_parts(parts, key)
5+
local total = 0
6+
for _, part in ipairs(parts) do
7+
total = total + (part[key] or 0)
8+
end
9+
return total
10+
end
11+
12+
local function sum_vitamins(parts)
13+
local total = {}
14+
for _, part in ipairs(parts) do
15+
for id, value in pairs(part.vit or {}) do
16+
total[id] = (total[id] or 0) + value
17+
end
18+
end
19+
return total
20+
end
21+
22+
local function has_tag(parts, needle)
23+
for _, part in ipairs(parts) do
24+
for _, tag in ipairs(part.tag or {}) do
25+
if tag == needle then return true end
26+
end
27+
end
28+
return false
29+
end
30+
31+
function proc.food.full(params)
32+
local facts = params.facts or {}
33+
local mass_g = sum_parts(facts, "mass_g")
34+
local volume_ml = sum_parts(facts, "volume_ml")
35+
local kcal = sum_parts(facts, "kcal")
36+
local vit = sum_vitamins(facts)
37+
local name = "sandwich"
38+
if has_tag(facts, "meat") then
39+
name = "meat sandwich"
40+
elseif has_tag(facts, "cheese") then
41+
name = "cheese sandwich"
42+
elseif has_tag(facts, "veg") then
43+
name = "veggy sandwich"
44+
end
45+
return {
46+
mass_g = mass_g,
47+
volume_ml = volume_ml,
48+
kcal = kcal,
49+
vit = vit,
50+
name = name,
51+
}
52+
end
53+
54+
function proc.food.make(params)
55+
local blob = params.blob or {}
56+
return {
57+
name = blob.name,
58+
kcal = blob.kcal,
59+
mass_g = blob.mass_g,
60+
volume_ml = blob.volume_ml,
61+
mode = "none",
62+
}
63+
end
64+
65+
return proc

data/json/proc/sandwich.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[
2+
{
3+
"type": "PROC",
4+
"id": "sandwich",
5+
"cat": "food",
6+
"res": "sandwich_generic",
7+
"hist": {
8+
"def": "none",
9+
"ok": [ "none", "compact", "full" ]
10+
},
11+
"slot": [
12+
{ "id": "top", "role": "bread", "min": 1, "max": 1, "ok": [ "itype:bread", "itype:bread_garlic" ] },
13+
{ "id": "bot", "role": "bread", "min": 1, "max": 1, "ok": [ "itype:bread", "itype:bread_garlic" ] },
14+
{ "id": "veg", "role": "veg", "min": 0, "max": 4, "rep": true, "ok": [ "itype:lettuce", "itype:tomato" ] },
15+
{ "id": "cheese", "role": "cheese", "min": 0, "max": 2, "rep": true, "ok": [ "itype:cheese", "itype:cheese_fresh", "itype:cheese_hard" ] },
16+
{ "id": "cond", "role": "cond", "min": 0, "max": 2, "rep": true, "ok": [ "itype:mustard" ] },
17+
{ "id": "meat", "role": "meat", "min": 0, "max": 3, "rep": true, "ok": [ "itype:meat_cooked", "itype:meat_cooked_seasoned" ] }
18+
],
19+
"lua": {
20+
"full": "proc.food.full",
21+
"make": "proc.food.make"
22+
}
23+
}
24+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"type": "recipe",
4+
"result": "sandwich_generic",
5+
"category": "CC_FOOD",
6+
"subcategory": "CSC_FOOD_MEAT",
7+
"skill_used": "cooking",
8+
"difficulty": 1,
9+
"time": 100,
10+
"proc": true,
11+
"proc_id": "sandwich",
12+
"builder_name": { "str": "Sandwich" },
13+
"builder_desc": { "str": "Pick bread and fillings." },
14+
"qualities": [],
15+
"tools": [],
16+
"components": []
17+
}
18+
]

src/consumption.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "mutation.h"
3838
#include "options.h"
3939
#include "pldata.h"
40+
#include "proc_item.h"
4041
#include "recipe.h"
4142
#include "recipe_dictionary.h"
4243
#include "requirements.h"
@@ -310,6 +311,15 @@ static std::map<vitamin_id, int> compute_default_effective_vitamins(
310311
static nutrients compute_default_effective_nutrients( const item &comest,
311312
const Character &you, const cata::flat_set<flag_id> &extra_flags = {} )
312313
{
314+
if( const auto kcal = proc::blob_kcal( comest ) ) {
315+
auto ret = nutrients{};
316+
ret.kcal = *kcal;
317+
if( const auto vit = proc::blob_vitamins( comest ) ) {
318+
ret.vitamins = *vit;
319+
}
320+
return ret;
321+
}
322+
313323
return { compute_default_effective_kcal( comest, you, extra_flags ),
314324
compute_default_effective_vitamins( comest, you ) };
315325
}

src/crafting.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
#include "pimpl.h"
5555
#include "player.h"
5656
#include "player_activity.h"
57+
#include "proc_item.h"
58+
#include "popup.h"
5759
#include "point.h"
5860
#include "recipe.h"
5961
#include "recipe_dictionary.h"
@@ -599,6 +601,12 @@ void Character::make_craft_with_command( const recipe_id &id_to_make, int batch_
599601
return;
600602
}
601603

604+
if( recipe_to_make.is_proc() ) {
605+
popup( _( "Procedural builder stub for %s" ), recipe_to_make.builder_name().translated().empty() ?
606+
recipe_to_make.result_name() : recipe_to_make.builder_name().translated() );
607+
return;
608+
}
609+
602610
*last_craft = craft_command( &recipe_to_make, batch_size, is_long, this, loc );
603611
last_craft->execute();
604612
}
@@ -2201,6 +2209,14 @@ void crafting::complete_disassemble( Character &who, const iuse_location &target
22012209
// Even if the best-fit recipe does not involve those items
22022210
location_vector<item> &components = dis_item.get_components();
22032211

2212+
if( const auto payload = proc::read_payload( dis_item ); payload &&
2213+
payload->mode == proc::hist::compact ) {
2214+
auto restored = proc::restore_parts( *payload );
2215+
std::ranges::for_each( restored, [&]( detached_ptr<item> &entry ) {
2216+
components.push_back( std::move( entry ) );
2217+
} );
2218+
}
2219+
22042220
// If the components are empty, item is the default kind and made of default components
22052221
if( components.empty() ) {
22062222
const bool uncraft_liquids_contained = dis.has_flag( flag_UNCRAFT_LIQUIDS_CONTAINED );

src/item.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
#include "overmapbuffer.h"
8686
#include "pimpl.h"
8787
#include "player.h"
88+
#include "proc_item.h"
8889
#include "player_activity.h"
8990
#include "pldata.h"
9091
#include "point.h"
@@ -4919,6 +4920,7 @@ void item::on_contents_changed()
49194920

49204921
void item::on_damage( int qty, damage_type )
49214922
{
4923+
proc::apply_on_damage( *this, qty );
49224924
if( is_corpse() && qty + damage_ >= max_damage() ) {
49234925
set_flag( flag_PULPED );
49244926
}
@@ -5430,6 +5432,14 @@ units::mass item::weight( bool include_contents, bool integral ) const
54305432
return ret;
54315433
}
54325434

5435+
if( const auto mass = proc::blob_mass( *this ) ) {
5436+
auto ret = units::from_gram( *mass );
5437+
if( include_contents ) {
5438+
ret += contents.item_weight_modifier();
5439+
}
5440+
return ret;
5441+
}
5442+
54335443
units::mass ret;
54345444
std::string local_str_mass = integral ? get_var( "integral_weight" ) : get_var( "weight" );
54355445
if( local_str_mass.empty() ) {
@@ -5557,6 +5567,10 @@ units::volume item::base_volume() const
55575567
}
55585568
}
55595569

5570+
if( const auto proc_volume = proc::blob_volume( *this ) ) {
5571+
return units::from_milliliter( *proc_volume );
5572+
}
5573+
55605574
return type->volume;
55615575
}
55625576

@@ -5578,6 +5592,10 @@ units::volume item::volume( bool integral ) const
55785592
return ret;
55795593
}
55805594

5595+
if( const auto proc_volume = proc::blob_volume( *this ) ) {
5596+
return units::from_milliliter( *proc_volume );
5597+
}
5598+
55815599
const int local_volume = get_var( "volume", -1 );
55825600
units::volume ret;
55835601
if( local_volume >= 0 ) {
@@ -9923,6 +9941,10 @@ std::string item::components_to_string() const
99239941

99249942
uint64_t item::make_component_hash() const
99259943
{
9944+
if( const auto hash = proc::component_hash( *this ) ) {
9945+
return *hash;
9946+
}
9947+
99269948
// First we need to sort the IDs so that identical ingredients give identical hashes.
99279949
std::multiset<std::string> id_set;
99289950
for( const item * const &it : components ) {
@@ -11230,6 +11252,21 @@ int item::get_min_str() const
1123011252
std::vector<item_comp> item::get_uncraft_components() const
1123111253
{
1123211254
std::vector<item_comp> ret;
11255+
if( const auto payload = proc::read_payload( *this ); payload &&
11256+
payload->mode == proc::hist::compact ) {
11257+
std::ranges::for_each( payload->parts, [&]( const proc::compact_part & part ) {
11258+
auto iter = std::ranges::find_if( ret, [&]( item_comp & obj ) {
11259+
return obj.type == part.id;
11260+
} );
11261+
if( iter != ret.end() ) {
11262+
iter->count += part.n;
11263+
} else {
11264+
ret.emplace_back( part.id, part.n );
11265+
}
11266+
} );
11267+
return ret;
11268+
}
11269+
1123311270
if( components.empty() ) {
1123411271
//If item wasn't crafted with specific components use default recipe
1123511272
std::vector<std::vector<item_comp>> recipe = recipe_dictionary::get_uncraft(

0 commit comments

Comments
 (0)