new crafting system

efd/v1
Dominik Madarász 2022-10-18 08:57:43 +02:00
parent 70bb456a43
commit 9e368527a3
14 changed files with 318 additions and 148 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

View File

@ -13,6 +13,7 @@ add_library(eco2d-foundation STATIC
src/models/items.c src/models/items.c
src/models/entity.c src/models/entity.c
src/models/device.c src/models/device.c
src/models/crafting.c
src/models/prefabs/player.c src/models/prefabs/player.c
src/models/prefabs/vehicle.c src/models/prefabs/vehicle.c

View File

@ -0,0 +1,26 @@
#include "models/crafting.h"
#define R(id1,qty1)\
{\
.id = id1,\
.qty = qty1\
}
#define RECIPE(id,prod,qty,...)\
{\
.product = id,\
.product_qty = qty,\
.producer = prod,\
.reagents = (reagent[]){\
__VA_ARGS__\
}\
}
static recipe recipes[] = {
RECIPE(ASSET_BELT, ASSET_FURNACE, 4, R(ASSET_FENCE, 8), R(ASSET_WOOD, 2), {0}),
};
#define MAX_RECIPES (sizeof(recipes)/sizeof(recipes[0]))
#undef R
#undef RECIPE

View File

@ -4,7 +4,7 @@
static item_desc items[] = { static item_desc items[] = {
{ .kind = 0, .max_quantity = 0, }, { .kind = 0, .max_quantity = 0, },
ITEM_INGREDIENT(ASSET_FENCE, 64, ASSET_FURNACE, ASSET_BELT, 0), ITEM_HOLD(ASSET_FENCE, 64),
ITEM_ENERGY(ASSET_COAL, ASSET_FURNACE, 64, 15.0f), ITEM_ENERGY(ASSET_COAL, ASSET_FURNACE, 64, 15.0f),
ITEM_SELF(ASSET_WOOD, 64), ITEM_SELF(ASSET_WOOD, 64),
ITEM_SELF(ASSET_TREE, 64), ITEM_SELF(ASSET_TREE, 64),

View File

@ -17,7 +17,6 @@ ECS_COMPONENT_DECLARE(Inventory);
ECS_COMPONENT_DECLARE(ItemContainer); ECS_COMPONENT_DECLARE(ItemContainer);
ECS_COMPONENT_DECLARE(Producer); ECS_COMPONENT_DECLARE(Producer);
ECS_COMPONENT_DECLARE(EnergySource); ECS_COMPONENT_DECLARE(EnergySource);
ECS_COMPONENT_DECLARE(Ingredient);
ECS_COMPONENT_DECLARE(ItemRouter); ECS_COMPONENT_DECLARE(ItemRouter);
ECS_COMPONENT_DECLARE(Device); ECS_COMPONENT_DECLARE(Device);
ECS_COMPONENT_DECLARE(Blueprint); ECS_COMPONENT_DECLARE(Blueprint);
@ -44,7 +43,6 @@ void ComponentsImport(ecs_world_t *ecs) {
ECS_COMPONENT_DEFINE(ecs, ItemContainer); ECS_COMPONENT_DEFINE(ecs, ItemContainer);
ECS_COMPONENT_DEFINE(ecs, Producer); ECS_COMPONENT_DEFINE(ecs, Producer);
ECS_COMPONENT_DEFINE(ecs, EnergySource); ECS_COMPONENT_DEFINE(ecs, EnergySource);
ECS_COMPONENT_DEFINE(ecs, Ingredient);
ECS_COMPONENT_DEFINE(ecs, ItemRouter); ECS_COMPONENT_DEFINE(ecs, ItemRouter);
ECS_COMPONENT_DEFINE(ecs, Device); ECS_COMPONENT_DEFINE(ecs, Device);
ECS_COMPONENT_DEFINE(ecs, Blueprint); ECS_COMPONENT_DEFINE(ecs, Blueprint);

View File

@ -131,12 +131,13 @@ typedef struct {
typedef struct { typedef struct {
asset_id processed_item; asset_id processed_item;
uint32_t processed_item_qty;
float process_time; float process_time;
float energy_level; float energy_level;
} Producer; } Producer;
typedef struct { typedef struct {
char _unused; uint32_t push_qty;
} ItemRouter; } ItemRouter;
typedef struct { typedef struct {
@ -144,12 +145,6 @@ typedef struct {
float energy_level; float energy_level;
} EnergySource; } EnergySource;
typedef struct {
asset_id producer;
asset_id additional_ingredient; // optional - can specify additional item we need in the container to craft this item
asset_id product;
} Ingredient;
typedef struct { typedef struct {
uint16_t asset; uint16_t asset;
@ -188,7 +183,6 @@ extern ECS_COMPONENT_DECLARE(Inventory);
extern ECS_COMPONENT_DECLARE(ItemContainer); extern ECS_COMPONENT_DECLARE(ItemContainer);
extern ECS_COMPONENT_DECLARE(Producer); extern ECS_COMPONENT_DECLARE(Producer);
extern ECS_COMPONENT_DECLARE(EnergySource); extern ECS_COMPONENT_DECLARE(EnergySource);
extern ECS_COMPONENT_DECLARE(Ingredient);
extern ECS_COMPONENT_DECLARE(ItemRouter); extern ECS_COMPONENT_DECLARE(ItemRouter);
extern ECS_COMPONENT_DECLARE(Device); extern ECS_COMPONENT_DECLARE(Device);
extern ECS_COMPONENT_DECLARE(Blueprint); extern ECS_COMPONENT_DECLARE(Blueprint);

View File

@ -0,0 +1,147 @@
#include "crafting.h"
#include "models/items.h"
typedef struct {
asset_id id;
uint32_t qty;
} reagent;
typedef struct {
asset_id product;
uint32_t product_qty;
asset_id producer;
reagent *reagents;
} recipe;
#include "lists/crafting_list.c"
uint32_t craft__find_num_recipes_by_reagent(asset_id producer, asset_id id) {
uint32_t num_recipes=0;
for (int i = 0; i < MAX_RECIPES; ++i) {
if (recipes[i].producer == producer) {
for (int j = 0; recipes[i].reagents[j].id; ++j) {
if (recipes[i].reagents[j].id == id) {
++num_recipes;
}
}
}
}
return num_recipes;
}
recipe *craft__find_recipe_by_reagent(asset_id producer, asset_id id, uint32_t slot_id) {
for (int i = 0; i < MAX_RECIPES; ++i) {
if (recipes[i].producer == producer) {
for (int j = 0; recipes[i].reagents[j].id; ++j) {
if (recipes[i].reagents[j].id == id) {
if (slot_id > 0) {
--slot_id;
continue;
}
return &recipes[i];
}
}
}
}
return NULL;
}
bool craft_is_reagent_used_in_producer(asset_id reagent, asset_id producer) {
return craft__find_num_recipes_by_reagent(producer, reagent) > 0;
}
asset_id craft_perform_recipe(ecs_entity_t *items, asset_id producer, uint32_t *quantity) {
ZPL_ASSERT_NOT_NULL(items);
for (int i = 0; i < ITEMS_CONTAINER_SIZE; i++) {
ecs_entity_t item_slot_ent = items[i];
if (item_slot_ent == 0) continue;
Item *item = item_get_data(item_slot_ent);
if (!item) continue;
uint32_t num_recipes = craft__find_num_recipes_by_reagent(producer, item->kind);
for (uint32_t rec_i = 0; rec_i < num_recipes; ++rec_i) {
// TODO(zaklaus): slow, find a better way to retrieve known recipes
recipe *rec = craft__find_recipe_by_reagent(producer, item->kind, rec_i);
if (!rec) {
// NOTE(zaklaus): this item is not used as a reagent, skip it.
// TODO(zaklaus): is this a bug? should we assert?
continue;
}
uint8_t skip_slot=0;
// NOTE(zaklaus): analyse if all the reagents are present
for (int j = 0; rec->reagents[j].id; ++j) {
reagent *rea = &rec->reagents[j];
uint32_t pending_qty = rea->qty;
for (int k = 0; k < ITEMS_CONTAINER_SIZE; k++) {
ecs_entity_t rea_item_slot_ent = items[k];
if (rea_item_slot_ent == 0) continue;
Item *rea_item = item_get_data(rea_item_slot_ent);
if (!rea_item) continue;
if (rea->id == rea_item->kind && rea_item->quantity > 0) {
pending_qty -= zpl_min(pending_qty, rea_item->quantity);
if (pending_qty == 0) {
break;
}
}
}
if (pending_qty > 0) {
// NOTE(zaklaus): reagent not found, bail
skip_slot=1;
break;
}
}
// NOTE(zaklaus): demand not met, bye!
if (skip_slot)
continue;
// NOTE(zaklaus): deplete used reagents
for (int j = 0; rec->reagents[j].id; ++j) {
reagent *rea = &rec->reagents[j];
uint32_t pending_qty = rea->qty;
for (int k = 0; k < ITEMS_CONTAINER_SIZE; k++) {
ecs_entity_t rea_item_slot_ent = items[k];
if (rea_item_slot_ent == 0) continue;
Item *rea_item = item_get_data(rea_item_slot_ent);
if (!rea_item) continue;
if (rea->id == rea_item->kind && rea_item->quantity > 0) {
rea_item->quantity -= zpl_min(pending_qty, rea_item->quantity);
pending_qty -= zpl_min(pending_qty, rea_item->quantity);
if (rea_item->quantity == 0) {
item_despawn(rea_item_slot_ent);
items[k] = 0;
}
if (pending_qty == 0) {
break;
}
}
}
}
// NOTE(zaklaus): all done, return the product and its qty
*quantity = rec->product_qty;
return rec->product;
}
}
return 0;
}
// TODO(zaklaus):
asset_id craft_has_byproducts(asset_id product) {
return 0xFF;
}
// TODO(zaklaus):
uint32_t craft_resolve_graph(asset_id product, uint16_t *hops, uint8_t direct_cost) {
return 0;
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "platform/system.h"
#include "models/assets.h"
#include "models/components.h"
// NOTE(zaklaus): resolves recipe dependencies and consumes reagents
// to enqueue a production of a new item.
// TODO(zaklaus): "items" is assumed to come from ItemContainer component.
asset_id craft_perform_recipe(ecs_entity_t *items, asset_id producer, uint32_t *quantity);
// NOTE(zaklaus): informs us on whether this product has any byproducts desired.
asset_id craft_has_byproducts(asset_id product);
// NOTE(zaklaus): mostly used by item router so we don't push reagents out
bool craft_is_reagent_used_in_producer(asset_id reagent, asset_id producer);
// NOTE(zaklaus): resolves the production chain and analyses the amount of items required
// and a number of hops (production layers) needed to produce the item.
// optionally, it allows to calculate "direct_cost" of the product.
uint32_t craft_resolve_graph(asset_id product, uint16_t *hops, uint8_t direct_cost);

View File

@ -57,12 +57,6 @@ uint64_t item_spawn(asset_id kind, uint32_t qty) {
.energy_level = it->energy_source.energy_level, .energy_level = it->energy_source.energy_level,
}; };
} break; } break;
case UDATA_INGREDIENT: {
Ingredient *i = ecs_get_mut(world_ecs(), e, Ingredient);
i->producer = it->ingredient.producer;
i->product = it->ingredient.product;
i->additional_ingredient = it->ingredient.additional_ingredient;
} break;
default: break; default: break;
} }

View File

@ -21,7 +21,6 @@ typedef enum {
typedef enum { typedef enum {
UDATA_NONE, UDATA_NONE,
UDATA_ENERGY_SOURCE, UDATA_ENERGY_SOURCE,
UDATA_INGREDIENT,
} item_attachment; } item_attachment;
typedef struct { typedef struct {
@ -52,12 +51,6 @@ typedef struct {
asset_id producer; asset_id producer;
float energy_level; float energy_level;
} energy_source; } energy_source;
struct {
asset_id producer;
asset_id product;
asset_id additional_ingredient;
} ingredient;
}; };
// NOTE: item data // NOTE: item data

View File

@ -15,7 +15,7 @@ uint64_t furnace_spawn(void) {
*producer = (Producer){0}; *producer = (Producer){0};
producer->energy_level = 69.0f; producer->energy_level = 69.0f;
ecs_add(world_ecs(), e, ItemRouter); ecs_set(world_ecs(), e, ItemRouter, {1});
return (uint64_t)e; return (uint64_t)e;
} }

View File

@ -1,3 +1,5 @@
#include "models/crafting.h"
static inline static inline
asset_id FetchAssetAtPos(float x, float y) { asset_id FetchAssetAtPos(float x, float y) {
world_block_lookup lookup = world_block_from_realpos(x, y); world_block_lookup lookup = world_block_from_realpos(x, y);
@ -63,6 +65,7 @@ void PushItemsOnNodes(ecs_iter_t *it) {
// We need a way to refer to specific blocks in the world so we can do easy block ID checks // We need a way to refer to specific blocks in the world so we can do easy block ID checks
// and re-build the cache when a change is detected. // and re-build the cache when a change is detected.
float push_dx[4], push_dy[4]; float push_dx[4], push_dy[4];
uint8_t nodes = CheckForNearbyBelts(&p[i], push_dx, push_dy); uint8_t nodes = CheckForNearbyBelts(&p[i], push_dx, push_dy);
uint8_t num_nodes = (uint8_t)zpl_count_set_bits(nodes); uint8_t num_nodes = (uint8_t)zpl_count_set_bits(nodes);
@ -77,14 +80,12 @@ void PushItemsOnNodes(ecs_iter_t *it) {
ecs_entity_t item_slot_ent = storage[i].items[j]; ecs_entity_t item_slot_ent = storage[i].items[j];
if (item_slot_ent == 0) continue; if (item_slot_ent == 0) continue;
Item *item = item_get_data(item_slot_ent); Item *item = item_get_data(item_slot_ent);
if (!item) continue;
const Ingredient *ing = 0; if (craft_is_reagent_used_in_producer(item->kind, d->asset)) {
// NOTE(zaklaus): Make sure we don't push out items from input node // NOTE(zaklaus): this is an input reagent, keep it
if ((ing = ecs_get_if(it->world, item_slot_ent, Ingredient))) {
if (ing->producer == d->asset) {
continue; continue;
} }
}
while (item->quantity > 0 && num_nodes > 0) { while (item->quantity > 0 && num_nodes > 0) {
// NOTE(zaklaus): Use a rolling counter to select an output node. // NOTE(zaklaus): Use a rolling counter to select an output node.
@ -94,14 +95,14 @@ void PushItemsOnNodes(ecs_iter_t *it) {
continue; continue;
} }
uint64_t e = item_spawn(item->kind, 1); uint64_t e = item_spawn(item->kind, zpl_min(r->push_qty, item->quantity));
entity_set_position(e, p[i].x + push_dx[counter], p[i].y + push_dy[counter]); entity_set_position(e, p[i].x + push_dx[counter], p[i].y + push_dy[counter]);
Velocity *e_vel = ecs_get_mut_ex(it->world, e, Velocity); Velocity *e_vel = ecs_get_mut_ex(it->world, e, Velocity);
e_vel->x = push_dx[counter]; e_vel->x = push_dx[counter];
e_vel->y = push_dy[counter]; e_vel->y = push_dy[counter];
--item->quantity; item->quantity -= zpl_min(r->push_qty, item->quantity);
--num_nodes; --num_nodes;
++counter; ++counter;
} }

View File

@ -1,3 +1,5 @@
#include "models/crafting.h"
void ProduceItems(ecs_iter_t *it) { void ProduceItems(ecs_iter_t *it) {
ItemContainer *storage = ecs_field(it, ItemContainer, 1); ItemContainer *storage = ecs_field(it, ItemContainer, 1);
Producer *producer = ecs_field(it, Producer, 2); Producer *producer = ecs_field(it, Producer, 2);
@ -7,6 +9,7 @@ void ProduceItems(ecs_iter_t *it) {
for (int i = 0; i < it->count; i++) { for (int i = 0; i < it->count; i++) {
for (int j = 0; j < ITEMS_CONTAINER_SIZE; j++) { for (int j = 0; j < ITEMS_CONTAINER_SIZE; j++) {
ecs_entity_t item_slot_ent = storage[i].items[j]; ecs_entity_t item_slot_ent = storage[i].items[j];
if (item_slot_ent == 0) continue;
Item *item = item_get_data(item_slot_ent); Item *item = item_get_data(item_slot_ent);
const EnergySource *energy_source = 0; const EnergySource *energy_source = 0;
@ -19,27 +22,19 @@ void ProduceItems(ecs_iter_t *it) {
continue; continue;
} }
// TODO(zaklaus): handle fuel
// if (producer[i].energy_level <= 0.0f) continue; // if (producer[i].energy_level <= 0.0f) continue;
// TODO(zaklaus): use ticks
if (producer[i].process_time < game_time()) { if (producer[i].process_time < game_time()) {
if (producer[i].processed_item > 0) { if (producer[i].processed_item > 0) {
uint64_t e = item_spawn(producer[i].processed_item, 1); uint64_t e = item_spawn(producer[i].processed_item, producer[i].processed_item_qty);
entity_set_position(e, p[i].x, p[i].y); entity_set_position(e, p[i].x, p[i].y);
producer[i].processed_item = 0; producer[i].processed_item = 0;
} else { } else {
const Ingredient *ing = 0; producer[i].processed_item = craft_perform_recipe(storage[i].items, d->asset, &producer[i].processed_item_qty);
if ((ing = ecs_get_if(it->world, item_slot_ent, Ingredient))) {
if (ing->producer == d->asset) {
if (item->quantity <= 0) {
item_despawn(item_slot_ent);
storage[i].items[j] = 0;
} else {
producer[i].processed_item = ing->product;
producer[i].process_time = game_time() + game_rules.furnace_cook_time; producer[i].process_time = game_time() + game_rules.furnace_cook_time;
} }
item->quantity--;
}
}
}
} }
} }