new crafting system
parent
70bb456a43
commit
9e368527a3
Binary file not shown.
After Width: | Height: | Size: 424 B |
|
@ -13,6 +13,7 @@ add_library(eco2d-foundation STATIC
|
|||
src/models/items.c
|
||||
src/models/entity.c
|
||||
src/models/device.c
|
||||
src/models/crafting.c
|
||||
|
||||
src/models/prefabs/player.c
|
||||
src/models/prefabs/vehicle.c
|
||||
|
|
|
@ -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
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
static item_desc items[] = {
|
||||
{ .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_SELF(ASSET_WOOD, 64),
|
||||
ITEM_SELF(ASSET_TREE, 64),
|
||||
|
|
|
@ -17,7 +17,6 @@ ECS_COMPONENT_DECLARE(Inventory);
|
|||
ECS_COMPONENT_DECLARE(ItemContainer);
|
||||
ECS_COMPONENT_DECLARE(Producer);
|
||||
ECS_COMPONENT_DECLARE(EnergySource);
|
||||
ECS_COMPONENT_DECLARE(Ingredient);
|
||||
ECS_COMPONENT_DECLARE(ItemRouter);
|
||||
ECS_COMPONENT_DECLARE(Device);
|
||||
ECS_COMPONENT_DECLARE(Blueprint);
|
||||
|
@ -44,7 +43,6 @@ void ComponentsImport(ecs_world_t *ecs) {
|
|||
ECS_COMPONENT_DEFINE(ecs, ItemContainer);
|
||||
ECS_COMPONENT_DEFINE(ecs, Producer);
|
||||
ECS_COMPONENT_DEFINE(ecs, EnergySource);
|
||||
ECS_COMPONENT_DEFINE(ecs, Ingredient);
|
||||
ECS_COMPONENT_DEFINE(ecs, ItemRouter);
|
||||
ECS_COMPONENT_DEFINE(ecs, Device);
|
||||
ECS_COMPONENT_DEFINE(ecs, Blueprint);
|
||||
|
|
|
@ -131,12 +131,13 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
asset_id processed_item;
|
||||
uint32_t processed_item_qty;
|
||||
float process_time;
|
||||
float energy_level;
|
||||
} Producer;
|
||||
|
||||
typedef struct {
|
||||
char _unused;
|
||||
uint32_t push_qty;
|
||||
} ItemRouter;
|
||||
|
||||
typedef struct {
|
||||
|
@ -144,12 +145,6 @@ typedef struct {
|
|||
float energy_level;
|
||||
} 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 {
|
||||
uint16_t asset;
|
||||
|
||||
|
@ -188,7 +183,6 @@ extern ECS_COMPONENT_DECLARE(Inventory);
|
|||
extern ECS_COMPONENT_DECLARE(ItemContainer);
|
||||
extern ECS_COMPONENT_DECLARE(Producer);
|
||||
extern ECS_COMPONENT_DECLARE(EnergySource);
|
||||
extern ECS_COMPONENT_DECLARE(Ingredient);
|
||||
extern ECS_COMPONENT_DECLARE(ItemRouter);
|
||||
extern ECS_COMPONENT_DECLARE(Device);
|
||||
extern ECS_COMPONENT_DECLARE(Blueprint);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -57,12 +57,6 @@ uint64_t item_spawn(asset_id kind, uint32_t qty) {
|
|||
.energy_level = it->energy_source.energy_level,
|
||||
};
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ typedef enum {
|
|||
typedef enum {
|
||||
UDATA_NONE,
|
||||
UDATA_ENERGY_SOURCE,
|
||||
UDATA_INGREDIENT,
|
||||
} item_attachment;
|
||||
|
||||
typedef struct {
|
||||
|
@ -52,12 +51,6 @@ typedef struct {
|
|||
asset_id producer;
|
||||
float energy_level;
|
||||
} energy_source;
|
||||
|
||||
struct {
|
||||
asset_id producer;
|
||||
asset_id product;
|
||||
asset_id additional_ingredient;
|
||||
} ingredient;
|
||||
};
|
||||
|
||||
// NOTE: item data
|
||||
|
|
|
@ -15,7 +15,7 @@ uint64_t furnace_spawn(void) {
|
|||
*producer = (Producer){0};
|
||||
producer->energy_level = 69.0f;
|
||||
|
||||
ecs_add(world_ecs(), e, ItemRouter);
|
||||
ecs_set(world_ecs(), e, ItemRouter, {1});
|
||||
return (uint64_t)e;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include "models/crafting.h"
|
||||
|
||||
static inline
|
||||
asset_id FetchAssetAtPos(float x, float 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
|
||||
// and re-build the cache when a change is detected.
|
||||
|
||||
|
||||
float push_dx[4], push_dy[4];
|
||||
uint8_t nodes = CheckForNearbyBelts(&p[i], push_dx, push_dy);
|
||||
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];
|
||||
if (item_slot_ent == 0) continue;
|
||||
Item *item = item_get_data(item_slot_ent);
|
||||
if (!item) continue;
|
||||
|
||||
const Ingredient *ing = 0;
|
||||
// NOTE(zaklaus): Make sure we don't push out items from input node
|
||||
if ((ing = ecs_get_if(it->world, item_slot_ent, Ingredient))) {
|
||||
if (ing->producer == d->asset) {
|
||||
if (craft_is_reagent_used_in_producer(item->kind, d->asset)) {
|
||||
// NOTE(zaklaus): this is an input reagent, keep it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (item->quantity > 0 && num_nodes > 0) {
|
||||
// NOTE(zaklaus): Use a rolling counter to select an output node.
|
||||
|
@ -94,14 +95,14 @@ void PushItemsOnNodes(ecs_iter_t *it) {
|
|||
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]);
|
||||
|
||||
Velocity *e_vel = ecs_get_mut_ex(it->world, e, Velocity);
|
||||
e_vel->x = push_dx[counter];
|
||||
e_vel->y = push_dy[counter];
|
||||
|
||||
--item->quantity;
|
||||
item->quantity -= zpl_min(r->push_qty, item->quantity);
|
||||
--num_nodes;
|
||||
++counter;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include "models/crafting.h"
|
||||
|
||||
void ProduceItems(ecs_iter_t *it) {
|
||||
ItemContainer *storage = ecs_field(it, ItemContainer, 1);
|
||||
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 j = 0; j < ITEMS_CONTAINER_SIZE; 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);
|
||||
|
||||
const EnergySource *energy_source = 0;
|
||||
|
@ -19,27 +22,19 @@ void ProduceItems(ecs_iter_t *it) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// TODO(zaklaus): handle fuel
|
||||
// if (producer[i].energy_level <= 0.0f) continue;
|
||||
|
||||
// TODO(zaklaus): use ticks
|
||||
if (producer[i].process_time < game_time()) {
|
||||
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);
|
||||
producer[i].processed_item = 0;
|
||||
} else {
|
||||
const Ingredient *ing = 0;
|
||||
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].processed_item = craft_perform_recipe(storage[i].items, d->asset, &producer[i].processed_item_qty);
|
||||
producer[i].process_time = game_time() + game_rules.furnace_cook_time;
|
||||
}
|
||||
item->quantity--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue