From beee78a0844b1053210c5be7691080253cd9cd6d Mon Sep 17 00:00:00 2001 From: xtreme8000 Date: Fri, 1 Dec 2023 17:58:15 +0100 Subject: [PATCH] Local server inventory logic (4x4 crafting) not the prettiest solution, but it works for now --- CMakeLists.txt | 2 + README.md | 8 +- source/block/blocks_data.h | 1 + source/entity/entity_item.c | 19 +-- source/item/inventory.c | 109 +++++---------- source/item/inventory.h | 28 +++- source/item/items.h | 10 +- source/item/recipe.c | 147 ++++++++++++++++++++ source/item/recipe.h | 49 +++++++ source/item/window_container.c | 6 +- source/main.c | 2 + source/network/inventory_logic.h | 22 +++ source/network/inventory_player.c | 220 ++++++++++++++++++++++++++++++ source/network/server_local.c | 114 +++++++--------- source/network/server_local.h | 5 + 15 files changed, 571 insertions(+), 171 deletions(-) create mode 100644 source/item/recipe.c create mode 100644 source/item/recipe.h create mode 100644 source/network/inventory_logic.h create mode 100644 source/network/inventory_player.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f244c6b..55f14a5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ add_executable(cavex source/item/inventory.c source/item/items.c source/item/tool.c + source/item/recipe.c source/item/items/item_sugarcane.c source/network/client_interface.c @@ -105,6 +106,7 @@ add_executable(cavex source/network/server_interface.c source/network/server_local.c source/network/server_world.c + source/network/inventory_player.c source/graphics/gfx_util.c source/graphics/gui_util.c diff --git a/README.md b/README.md index 7176dca..a684936 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ * main menu * generation of new chunks * biome colors -* player physics -* inventory management +* ~~player physics~~ +* ~~inventory management~~ * ~~block placement~~ and destruction logic -* (random) block updates -* item actions +* ~~(random)~~ block updates +* ~~item actions~~ * real texture pack support * Beta 1.7.3 multiplayer support diff --git a/source/block/blocks_data.h b/source/block/blocks_data.h index 723306f..a335cca 100644 --- a/source/block/blocks_data.h +++ b/source/block/blocks_data.h @@ -63,6 +63,7 @@ enum block_type { BLOCK_SLAB = 44, BLOCK_MOSSY_COBBLE = 48, BLOCK_OBSIDIAN = 49, + BLOCK_WORKBENCH = 58, BLOCK_LADDER = 65, BLOCK_REDSTONE_TORCH = 75, BLOCK_SNOW = 78, diff --git a/source/entity/entity_item.c b/source/entity/entity_item.c index 05f75df..591a7e8 100644 --- a/source/entity/entity_item.c +++ b/source/entity/entity_item.c @@ -94,22 +94,11 @@ static bool entity_server_tick(struct entity* e, struct server_local* s) { e->pos, (vec3) {s->player.x, s->player.y - 0.6F, s->player.z}) < glm_pow2(2.0F)) { // allow pickup after 2s - bool slots_changed[INVENTORY_SIZE]; - memset(slots_changed, false, sizeof(slots_changed)); - // TODO: case where item cannot be picked up completely - inventory_collect_inventory(&s->player.inventory, &e->data.item.item, - slots_changed); - - for(size_t k = 0; k < INVENTORY_SIZE; k++) { - if(slots_changed[k]) - clin_rpc_send(&(struct client_rpc) { - .type = CRPC_INVENTORY_SLOT, - .payload.inventory_slot.window = WINDOWC_INVENTORY, - .payload.inventory_slot.slot = k, - .payload.inventory_slot.item = s->player.inventory.items[k], - }); - } + if(s->player.active_inventory && s->player.active_inventory->logic + && s->player.active_inventory->logic->on_collect) + s->player.active_inventory->logic->on_collect( + s->player.active_inventory, &e->data.item.item); clin_rpc_send(&(struct client_rpc) { .type = CRPC_PICKUP_ITEM, diff --git a/source/item/inventory.c b/source/item/inventory.c index f7921fb..0a75af8 100644 --- a/source/item/inventory.c +++ b/source/item/inventory.c @@ -21,9 +21,12 @@ #include "inventory.h" -bool inventory_create(struct inventory* inv, size_t capacity) { +bool inventory_create(struct inventory* inv, struct inventory_logic* logic, + void* user, size_t capacity) { assert(inv && capacity > 0); + inv->user = user; + inv->logic = logic; inv->capacity = capacity; inv->items = malloc(sizeof(struct item_data) * capacity); @@ -34,6 +37,9 @@ bool inventory_create(struct inventory* inv, size_t capacity) { ilist_inventory_init_field(inv); + if(inv->logic && inv->logic->on_create) + inv->logic->on_create(inv); + return true; } @@ -50,6 +56,10 @@ void inventory_copy(struct inventory* inv, struct inventory* from) { void inventory_destroy(struct inventory* inv) { assert(inv && inv->items); + + if(inv->logic && inv->logic->on_destroy) + inv->logic->on_destroy(inv); + free(inv->items); } @@ -82,72 +92,6 @@ void inventory_consume(struct inventory* inv, size_t slot) { } } -#define min(a, b) ((a) < (b) ? (a) : (b)) - -bool inventory_collect(struct inventory* inv, struct item_data* item, - uint8_t* slot_priority, size_t slot_length, bool* mask) { - assert(inv && item && item->id != 0 && mask); - - struct item* it = item_get(item); - - if(!it) - return false; - - while(item->count > 0) { - bool has_canidate_equal = false; - size_t candidate_equal = 0; - bool has_canidate_empty = false; - size_t candidate_empty = 0; - - for(size_t k = 0; k < slot_length; k++) { - uint8_t slot = slot_priority[k]; - - if(inv->items[slot].id == item->id - && inv->items[slot].durability == item->durability - && inv->items[slot].count < it->max_stack) { - has_canidate_equal = true; - candidate_equal = slot; - break; - } - - if(!has_canidate_empty && inv->items[slot].id == 0) { - has_canidate_empty = true; - candidate_empty = slot; - } - } - - if(has_canidate_equal || has_canidate_empty) { - size_t candidate - = has_canidate_equal ? candidate_equal : candidate_empty; - size_t additional - = min(it->max_stack - inv->items[candidate].count, item->count); - inv->items[candidate].id = item->id; - inv->items[candidate].durability = item->durability; - inv->items[candidate].count += additional; - item->count -= additional; - mask[candidate] = true; - } else { - return false; - } - } - - return true; -} - -bool inventory_collect_inventory(struct inventory* inv, struct item_data* item, - bool* mask) { - uint8_t priorities[INVENTORY_SIZE_HOTBAR + INVENTORY_SIZE_MAIN]; - - for(size_t k = 0; k < INVENTORY_SIZE_HOTBAR; k++) - priorities[k] = k + INVENTORY_SLOT_HOTBAR; - - for(size_t k = 0; k < INVENTORY_SIZE_MAIN; k++) - priorities[k + INVENTORY_SIZE_HOTBAR] = k + INVENTORY_SLOT_MAIN; - - return inventory_collect(inv, item, priorities, - sizeof(priorities) / sizeof(*priorities), mask); -} - size_t inventory_get_hotbar(struct inventory* inv) { assert(inv); return inv->hotbar_slot; @@ -286,15 +230,30 @@ static bool inventory_place_one_item(struct inventory* inv, size_t slot) { return true; } -bool inventory_action(struct inventory* inv, size_t slot, bool right) { +bool inventory_action(struct inventory* inv, size_t slot, bool right, + set_inv_slot_t changes) { assert(inv && slot < inv->capacity); - if(right) { - return (inv->picked_item.id == 0) ? - inventory_pick_item_split(inv, slot) : - inventory_place_one_item(inv, slot); - } else { - return (inv->picked_item.id == 0) ? inventory_pick_item(inv, slot) : - inventory_place_item(inv, slot); + bool result = true; + + if(inv->logic && inv->logic->pre_action + && !inv->logic->pre_action(inv, slot, right, changes)) + result = false; + + if(result) { + if(right) { + result = (inv->picked_item.id == 0) ? + inventory_pick_item_split(inv, slot) : + inventory_place_one_item(inv, slot); + } else { + result = (inv->picked_item.id == 0) ? + inventory_pick_item(inv, slot) : + inventory_place_item(inv, slot); + } } + + if(inv->logic && inv->logic->post_action) + inv->logic->post_action(inv, slot, right, result, changes); + + return result; } \ No newline at end of file diff --git a/source/item/inventory.h b/source/item/inventory.h index c4f1987..6c0def1 100644 --- a/source/item/inventory.h +++ b/source/item/inventory.h @@ -20,6 +20,7 @@ #ifndef INVENTORY_H #define INVENTORY_H +#include #include #include #include @@ -40,6 +41,10 @@ #define SPECIAL_SLOT_PICKED_ITEM 255 +DICT_SET_DEF(set_inv_slot, size_t) + +struct inventory_logic; + struct inventory { struct item_data picked_item; struct item_data* items; @@ -50,20 +55,30 @@ struct inventory { bool action_type; size_t action_slot; } revision; // for window container + struct inventory_logic* logic; + void* user; ILIST_INTERFACE(ilist_inventory, struct inventory); }; +struct inventory_logic { + void (*on_create)(struct inventory* inv); + void (*on_destroy)(struct inventory* inv); + bool (*pre_action)(struct inventory* inv, size_t slot, bool right, + set_inv_slot_t changes); + void (*post_action)(struct inventory* inv, size_t slot, bool right, + bool accepted, set_inv_slot_t changes); + bool (*on_collect)(struct inventory* inv, struct item_data* item); + void (*on_close)(struct inventory* inv); +}; + ILIST_DEF(ilist_inventory, struct inventory, M_POD_OPLIST) -bool inventory_create(struct inventory* inv, size_t capacity); +bool inventory_create(struct inventory* inv, struct inventory_logic* logic, + void* user, size_t capacity); void inventory_copy(struct inventory* inv, struct inventory* from); void inventory_destroy(struct inventory* inv); void inventory_clear(struct inventory* inv); void inventory_consume(struct inventory* inv, size_t slot); -bool inventory_collect(struct inventory* inv, struct item_data* item, - uint8_t* slot_priority, size_t slot_length, bool* mask); -bool inventory_collect_inventory(struct inventory* inv, struct item_data* item, - bool* mask); size_t inventory_get_hotbar(struct inventory* inv); void inventory_set_hotbar(struct inventory* inv, size_t slot); bool inventory_get_hotbar_item(struct inventory* inv, struct item_data* item); @@ -75,6 +90,7 @@ bool inventory_get_slot(struct inventory* inv, size_t slot, void inventory_clear_picked_item(struct inventory* inv); void inventory_set_picked_item(struct inventory* inv, struct item_data item); bool inventory_get_picked_item(struct inventory* inv, struct item_data* item); -bool inventory_action(struct inventory* inv, size_t slot, bool right); +bool inventory_action(struct inventory* inv, size_t slot, bool right, + set_inv_slot_t changes); #endif diff --git a/source/item/items.h b/source/item/items.h index 504e8e1..fde323d 100644 --- a/source/item/items.h +++ b/source/item/items.h @@ -34,6 +34,8 @@ struct item_data { enum item_type { ITEM_COAL = 263, ITEM_DIAMOND = 264, + ITEM_DIAMOND_PICKAXE = 278, + ITEM_STICK = 280, ITEM_SEED = 295, ITEM_WHEAT = 296, ITEM_BREAD = 297, @@ -52,10 +54,10 @@ enum item_type { struct server_local; enum armor_type { - ARMOR_TYPE_HELMET, - ARMOR_TYPE_CHESTPLATE, - ARMOR_TYPE_LEGGINGS, - ARMOR_TYPE_BOOTS, + ARMOR_TYPE_HELMET = 0, + ARMOR_TYPE_CHESTPLATE = 1, + ARMOR_TYPE_LEGGINGS = 2, + ARMOR_TYPE_BOOTS = 3, }; enum armor_tier { diff --git a/source/item/recipe.c b/source/item/recipe.c new file mode 100644 index 0000000..34f56ea --- /dev/null +++ b/source/item/recipe.c @@ -0,0 +1,147 @@ +/* + Copyright (c) 2023 ByteBit/xtreme8000 + + This file is part of CavEX. + + CavEX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CavEX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CavEX. If not, see . +*/ + +#include + +#include "recipe.h" + +array_recipe_t recipes_crafting; + +void recipe_init() { + array_recipe_init(recipes_crafting); + + recipe_add( + recipes_crafting, + (struct item_data) {.id = BLOCK_PLANKS, .durability = 0, .count = 4}, 1, + 1, (uint8_t[]) {1}, (struct item_data) {.id = BLOCK_LOG}, false); + recipe_add( + recipes_crafting, + (struct item_data) {.id = ITEM_STICK, .durability = 0, .count = 4}, 1, + 2, (uint8_t[]) {1, 1}, (struct item_data) {.id = BLOCK_PLANKS}, false); + recipe_add( + recipes_crafting, + (struct item_data) {.id = BLOCK_WORKBENCH, .durability = 0, .count = 1}, + 2, 2, (uint8_t[]) {1, 1, 1, 1}, (struct item_data) {.id = BLOCK_PLANKS}, + false); + recipe_add(recipes_crafting, + (struct item_data) { + .id = ITEM_DIAMOND_PICKAXE, .durability = 0, .count = 1}, + 3, 3, (uint8_t[]) {1, 1, 1, 0, 2, 0, 0, 2, 0}, + (struct item_data) {.id = ITEM_DIAMOND}, false, + (struct item_data) {.id = ITEM_STICK}, false); +} + +void recipe_add(array_recipe_t recipes, struct item_data result, size_t width, + size_t height, uint8_t* shape, ...) { + assert(recipes && width > 0 && height > 0 && width * height <= 9 && shape); + + int count = 0; + for(size_t k = 0; k < width * height; k++) { + if(shape[k] > count) + count = shape[k]; + } + + assert(count > 0 && count <= 9); + + struct recipe_ingredients ingredients[9]; + + va_list inputs; + va_start(inputs, shape); + + for(size_t k = 0; k < count; k++) { + ingredients[k].item = va_arg(inputs, struct item_data); + ingredients[k].match_durability = (bool)va_arg(inputs, int); + } + + va_end(inputs); + + struct recipe r = (struct recipe) { + .result = result, + .width = width, + .height = height, + }; + + for(size_t k = 0; k < width * height; k++) { + if(shape[k] > 0) { + r.shape[k] = ingredients[shape[k] - 1]; + } else { + r.shape[k].item.id = 0; + } + } + + array_recipe_push_back(recipes, r); +} + +bool recipe_match(array_recipe_t recipes, struct item_data slots[9], + bool slot_empty[9], struct item_data* result) { + assert(recipes && slots && slot_empty && result); + + array_recipe_it_t it; + array_recipe_it(it, recipes); + + while(!array_recipe_end_p(it)) { + struct recipe* current = array_recipe_ref(it); + + for(size_t y = 0; y <= 3 - current->height; y++) { + for(size_t x = 0; x <= 3 - current->width; x++) { + bool match = true; + + // check that outside of pattern is empty + for(size_t py = 0; py < 3 && match; py++) { + for(size_t px = 0; px < 3 && match; px++) { + if((px < x || px >= x + current->width || py < y + || py >= y + current->height) + && !slot_empty[px + py * 3]) + match = false; + } + } + + // check pattern itself + for(size_t py = 0; py < current->height && match; py++) { + for(size_t px = 0; px < current->width && match; px++) { + size_t slots_idx = (px + x) + (py + y) * 3; + size_t shape_idx = px + py * current->width; + + if(current->shape[shape_idx].item.id == 0) { + if(!slot_empty[slots_idx]) + match = false; + } else { + if(slot_empty[slots_idx] + || current->shape[shape_idx].item.id + != slots[slots_idx].id + || (current->shape->match_durability + && current->shape[shape_idx].item.durability + != slots[slots_idx].durability)) + match = false; + } + } + } + + if(match) { + *result = current->result; + return true; + } + } + } + + array_recipe_next(it); + } + + return false; +} \ No newline at end of file diff --git a/source/item/recipe.h b/source/item/recipe.h new file mode 100644 index 0000000..99f189d --- /dev/null +++ b/source/item/recipe.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2023 ByteBit/xtreme8000 + + This file is part of CavEX. + + CavEX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CavEX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CavEX. If not, see . +*/ + +#ifndef RECIPE_H +#define RECIPE_H + +#include +#include +#include +#include + +#include "items.h" + +struct recipe { + struct item_data result; + size_t width, height; + struct recipe_ingredients { + struct item_data item; + bool match_durability; + } shape[9]; +}; + +ARRAY_DEF(array_recipe, struct recipe, M_POD_OPLIST) + +extern array_recipe_t recipes_crafting; + +void recipe_init(void); +void recipe_add(array_recipe_t recipes, struct item_data result, size_t width, + size_t height, uint8_t* shape, ...); +bool recipe_match(array_recipe_t recipes, struct item_data slots[9], + bool slot_empty[9], struct item_data* result); + +#endif \ No newline at end of file diff --git a/source/item/window_container.c b/source/item/window_container.c index f43ee37..3da1fe5 100644 --- a/source/item/window_container.c +++ b/source/item/window_container.c @@ -68,7 +68,7 @@ bool windowc_new_action(struct window_container* wc, uint16_t* action_id, if(!next) return false; - if(!inventory_create(next, INVENTORY_SIZE)) // TODO + if(!inventory_create(next, NULL, NULL, INVENTORY_SIZE)) // TODO return false; if(!ilist_inventory_empty_p(wc->invs)) @@ -78,7 +78,7 @@ bool windowc_new_action(struct window_container* wc, uint16_t* action_id, next->revision.action_type = action_type; next->revision.action_slot = action_slot; - inventory_action(next, action_slot, action_type); + inventory_action(next, action_slot, action_type, NULL); *action_id = wc->next_action_id; wc->next_action_id++; @@ -150,7 +150,7 @@ void windowc_slot_change(struct window_container* wc, size_t slot, struct inventory* current = ilist_inventory_ref(it); inventory_copy(current, prev); inventory_action(current, current->revision.action_slot, - current->revision.action_type); + current->revision.action_type, NULL); prev = current; ilist_inventory_next(it); diff --git a/source/main.c b/source/main.c index 66b3c49..2ff16ef 100644 --- a/source/main.c +++ b/source/main.c @@ -34,6 +34,7 @@ #include "game/gui/screen.h" #include "graphics/gfx_util.h" #include "graphics/gui_util.h" +#include "item/recipe.h" #include "network/client_interface.h" #include "network/server_interface.h" #include "network/server_local.h" @@ -70,6 +71,7 @@ int main(void) { input_init(); blocks_init(); items_init(); + recipe_init(); gfx_setup(); screen_set(&screen_select_world); diff --git a/source/network/inventory_logic.h b/source/network/inventory_logic.h new file mode 100644 index 0000000..00b31ac --- /dev/null +++ b/source/network/inventory_logic.h @@ -0,0 +1,22 @@ +/* + Copyright (c) 2023 ByteBit/xtreme8000 + + This file is part of CavEX. + + CavEX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CavEX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CavEX. If not, see . +*/ + +#include "../item/inventory.h" + +extern struct inventory_logic inventory_logic_player; \ No newline at end of file diff --git a/source/network/inventory_player.c b/source/network/inventory_player.c new file mode 100644 index 0000000..8d6adc5 --- /dev/null +++ b/source/network/inventory_player.c @@ -0,0 +1,220 @@ +/* + Copyright (c) 2023 ByteBit/xtreme8000 + + This file is part of CavEX. + + CavEX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CavEX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CavEX. If not, see . +*/ + +#include "../item/recipe.h" +#include "../item/window_container.h" +#include "server_local.h" + +static bool inv_match_crafting(struct inventory* inv, + struct item_data* result) { + struct item_data slots[9]; + bool slot_empty[9]; + memset(slot_empty, true, sizeof(slot_empty)); + + for(size_t k = 0; k < INVENTORY_SIZE_CRAFTING; k++) + slot_empty[k + k / 2] = !inventory_get_slot( + inv, INVENTORY_SLOT_CRAFTING + k, slots + k + k / 2); + + return recipe_match(recipes_crafting, slots, slot_empty, result); +} + +static bool inv_pre_action(struct inventory* inv, size_t slot, bool right, + set_inv_slot_t changes) { + if(slot >= INVENTORY_SLOT_ARMOR + && slot < INVENTORY_SLOT_ARMOR + INVENTORY_SIZE_ARMOR) { + struct item_data it; + if(inventory_get_picked_item(inv, &it) && item_get(&it) + && (!item_get(&it)->armor.is_armor + || item_get(&it)->armor.type + != ARMOR_TYPE_HELMET + slot - INVENTORY_SLOT_ARMOR)) + return false; + } + + if(slot == INVENTORY_SLOT_OUTPUT) { + struct item_data output; + if(!right && inventory_get_slot(inv, INVENTORY_SLOT_OUTPUT, &output)) { + for(size_t k = INVENTORY_SLOT_CRAFTING; + k < INVENTORY_SLOT_CRAFTING + INVENTORY_SIZE_CRAFTING; k++) { + struct item_data it; + + if(inventory_get_slot(inv, k, &it) && it.count > 1) { + it.count--; + inventory_set_slot(inv, k, it); + } else { + inventory_clear_slot(inv, k); + } + + set_inv_slot_push(changes, k); + } + + struct item_data picked; + if(inventory_get_picked_item(inv, &picked)) { + struct item* it_type = item_get(&picked); + + if(it_type && picked.id == output.id + && picked.durability == output.durability + && picked.count + output.count <= it_type->max_stack) { + picked.count += output.count; + inventory_set_picked_item(inv, picked); + set_inv_slot_push(changes, SPECIAL_SLOT_PICKED_ITEM); + return false; + } + } else { + return true; + } + } + + return false; + } + + return true; +} + +static void inv_post_action(struct inventory* inv, size_t slot, bool right, + bool accepted, set_inv_slot_t changes) { + if((slot >= INVENTORY_SLOT_CRAFTING + && slot < INVENTORY_SLOT_CRAFTING + INVENTORY_SIZE_CRAFTING) + || slot == INVENTORY_SLOT_OUTPUT) { + struct item_data result; + if(inv_match_crafting(inv, &result)) { + inventory_set_slot(inv, INVENTORY_SLOT_OUTPUT, result); + } else { + inventory_clear_slot(inv, INVENTORY_SLOT_OUTPUT); + } + + set_inv_slot_push(changes, INVENTORY_SLOT_OUTPUT); + } +} + +static void inv_on_close(struct inventory* inv) { + struct server_local* s = inv->user; + + set_inv_slot_t changes; + set_inv_slot_init(changes); + + inventory_clear_slot(inv, INVENTORY_SLOT_OUTPUT); + set_inv_slot_push(changes, INVENTORY_SLOT_OUTPUT); + + for(size_t k = INVENTORY_SLOT_CRAFTING; + k < INVENTORY_SLOT_CRAFTING + INVENTORY_SIZE_CRAFTING; k++) { + struct item_data item; + inventory_get_slot(inv, k, &item); + + if(item.id != 0) { + inventory_clear_slot(inv, k); + set_inv_slot_push(changes, k); + server_local_spawn_item( + (vec3) {s->player.x, s->player.y, s->player.z}, &item, true, s); + } + } + + struct item_data picked_item; + if(inventory_get_picked_item(inv, &picked_item)) { + inventory_clear_picked_item(inv); + set_inv_slot_push(changes, SPECIAL_SLOT_PICKED_ITEM); + server_local_spawn_item((vec3) {s->player.x, s->player.y, s->player.z}, + &picked_item, true, s); + } + + server_local_send_inv_changes(changes, inv, WINDOWC_INVENTORY); + set_inv_slot_clear(changes); +} + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +static bool inventory_collect(struct inventory* inv, struct item_data* item, + uint8_t* slot_priority, size_t slot_length, + set_inv_slot_t changes) { + assert(inv && item && item->id != 0 && changes); + + struct item* it = item_get(item); + + if(!it) + return false; + + while(item->count > 0) { + bool has_canidate_equal = false; + size_t candidate_equal = 0; + bool has_canidate_empty = false; + size_t candidate_empty = 0; + + for(size_t k = 0; k < slot_length; k++) { + uint8_t slot = slot_priority[k]; + + if(inv->items[slot].id == item->id + && inv->items[slot].durability == item->durability + && inv->items[slot].count < it->max_stack) { + has_canidate_equal = true; + candidate_equal = slot; + break; + } + + if(!has_canidate_empty && inv->items[slot].id == 0) { + has_canidate_empty = true; + candidate_empty = slot; + } + } + + if(has_canidate_equal || has_canidate_empty) { + size_t candidate + = has_canidate_equal ? candidate_equal : candidate_empty; + size_t additional + = min(it->max_stack - inv->items[candidate].count, item->count); + inv->items[candidate].id = item->id; + inv->items[candidate].durability = item->durability; + inv->items[candidate].count += additional; + item->count -= additional; + set_inv_slot_push(changes, candidate); + } else { + return false; + } + } + + return true; +} + +static bool inv_on_collect(struct inventory* inv, struct item_data* item) { + uint8_t priorities[INVENTORY_SIZE_HOTBAR + INVENTORY_SIZE_MAIN]; + + for(size_t k = 0; k < INVENTORY_SIZE_HOTBAR; k++) + priorities[k] = k + INVENTORY_SLOT_HOTBAR; + + for(size_t k = 0; k < INVENTORY_SIZE_MAIN; k++) + priorities[k + INVENTORY_SIZE_HOTBAR] = k + INVENTORY_SLOT_MAIN; + + set_inv_slot_t changes; + set_inv_slot_init(changes); + + bool success + = inventory_collect(inv, item, priorities, + sizeof(priorities) / sizeof(*priorities), changes); + server_local_send_inv_changes(changes, inv, WINDOWC_INVENTORY); + set_inv_slot_clear(changes); + + return success; +} + +struct inventory_logic inventory_logic_player = { + .pre_action = inv_pre_action, + .post_action = inv_post_action, + .on_collect = inv_on_collect, + .on_create = NULL, + .on_destroy = NULL, + .on_close = inv_on_close, +}; \ No newline at end of file diff --git a/source/network/server_local.c b/source/network/server_local.c index a6f5f52..785388f 100644 --- a/source/network/server_local.c +++ b/source/network/server_local.c @@ -26,14 +26,15 @@ #include "../item/window_container.h" #include "../platform/thread.h" #include "client_interface.h" +#include "inventory_logic.h" #include "server_interface.h" #include "server_local.h" #define CHUNK_DIST2(x1, x2, z1, z2) \ (((x1) - (x2)) * ((x1) - (x2)) + ((z1) - (z2)) * ((z1) - (z2))) -static struct entity* spawn_item(vec3 pos, struct item_data* it, bool throw, - struct server_local* s) { +struct entity* server_local_spawn_item(vec3 pos, struct item_data* it, + bool throw, struct server_local* s) { uint32_t entity_id = entity_gen_id(s->entities); struct entity* e = dict_entity_safe_get(s->entities, entity_id); entity_item(entity_id, e, true, &s->world, *it); @@ -82,12 +83,36 @@ void server_local_spawn_block_drops(struct server_local* s, &s->rand_src); for(size_t k = 0; k < count; k++) - spawn_item((vec3) {blk_info->x + 0.5F, blk_info->y + 0.5F, + server_local_spawn_item((vec3) {blk_info->x + 0.5F, + blk_info->y + 0.5F, blk_info->z + 0.5F}, items + k, false, s); } } +void server_local_send_inv_changes(set_inv_slot_t changes, + struct inventory* inv, uint8_t window) { + assert(changes && inv); + + set_inv_slot_it_t it; + set_inv_slot_it(it, changes); + + while(!set_inv_slot_end_p(it)) { + size_t slot = *set_inv_slot_ref(it); + + clin_rpc_send(&(struct client_rpc) { + .type = CRPC_INVENTORY_SLOT, + .payload.inventory_slot.window = window, + .payload.inventory_slot.slot = slot, + .payload.inventory_slot.item = (slot == SPECIAL_SLOT_PICKED_ITEM) ? + inv->picked_item : + inv->items[slot], + }); + + set_inv_slot_next(it); + } +} + static void server_local_process(struct server_rpc* call, void* user) { assert(call && user); @@ -111,15 +136,12 @@ static void server_local_process(struct server_rpc* call, void* user) { call->payload.hotbar_slot.slot); break; case SRPC_WINDOW_CLICK: { - bool accept = false; - if(call->payload.window_click.window == WINDOWC_INVENTORY) { - if(!(inventory_get_picked_item(&s->player.inventory, NULL) - && call->payload.window_click.slot - == INVENTORY_SLOT_OUTPUT)) - accept = inventory_action( - &s->player.inventory, call->payload.window_click.slot, - call->payload.window_click.right_click); - } + set_inv_slot_t changes; + set_inv_slot_init(changes); + + bool accept = inventory_action( + s->player.active_inventory, call->payload.window_click.slot, + call->payload.window_click.right_click, changes); clin_rpc_send(&(struct client_rpc) { .type = CRPC_WINDOW_TRANSACTION, @@ -129,60 +151,20 @@ static void server_local_process(struct server_rpc* call, void* user) { .payload.window_transaction.window = call->payload.window_click.window, }); + + server_local_send_inv_changes(changes, s->player.active_inventory, + call->payload.window_click.window); + set_inv_slot_clear(changes); break; } - case SRPC_WINDOW_CLOSE: - if(call->payload.window_click.window == WINDOWC_INVENTORY) { - bool slots_changed[INVENTORY_SIZE]; - memset(slots_changed, false, sizeof(slots_changed)); - - inventory_clear_slot(&s->player.inventory, - INVENTORY_SLOT_OUTPUT); - slots_changed[INVENTORY_SLOT_OUTPUT] = true; - - for(size_t k = INVENTORY_SLOT_CRAFTING; - k < INVENTORY_SLOT_CRAFTING + INVENTORY_SIZE_CRAFTING; - k++) { - struct item_data item; - inventory_get_slot(&s->player.inventory, k, &item); - - if(item.id != 0) { - inventory_clear_slot(&s->player.inventory, k); - slots_changed[k] = true; - spawn_item( - (vec3) {s->player.x, s->player.y, s->player.z}, - &item, true, s); - } - } - - for(size_t k = 0; k < INVENTORY_SIZE; k++) { - if(slots_changed[k]) - clin_rpc_send(&(struct client_rpc) { - .type = CRPC_INVENTORY_SLOT, - .payload.inventory_slot.window = WINDOWC_INVENTORY, - .payload.inventory_slot.slot = k, - .payload.inventory_slot.item - = s->player.inventory.items[k], - }); - } - - struct item_data picked_item; - if(inventory_get_picked_item(&s->player.inventory, - &picked_item)) { - inventory_clear_picked_item(&s->player.inventory); - spawn_item((vec3) {s->player.x, s->player.y, s->player.z}, - &picked_item, true, s); - - clin_rpc_send(&(struct client_rpc) { - .type = CRPC_INVENTORY_SLOT, - .payload.inventory_slot.window = WINDOWC_INVENTORY, - .payload.inventory_slot.slot = SPECIAL_SLOT_PICKED_ITEM, - .payload.inventory_slot.item - = s->player.inventory.picked_item, - }); - } - } + case SRPC_WINDOW_CLOSE: { + if(s->player.active_inventory && s->player.active_inventory->logic + && s->player.active_inventory->logic->on_close) + s->player.active_inventory->logic->on_close( + s->player.active_inventory); + s->player.active_inventory = &s->player.inventory; break; + } case SRPC_BLOCK_DIG: if(s->player.has_pos && call->payload.block_dig.y >= 0 && call->payload.block_dig.y < WORLD_HEIGHT @@ -328,6 +310,7 @@ static void server_local_process(struct server_rpc* call, void* user) { level_archive_read(&s->level, LEVEL_TIME, &s->world_time, 0); dict_entity_reset(s->entities); + s->player.active_inventory = &s->player.inventory; clin_rpc_send(&(struct client_rpc) { .type = CRPC_WORLD_RESET, @@ -493,7 +476,10 @@ void server_local_create(struct server_local* s) { s->player.has_pos = false; s->player.finished_loading = false; string_init(s->level_name); - inventory_create(&s->player.inventory, INVENTORY_SIZE); + + inventory_create(&s->player.inventory, &inventory_logic_player, s, + INVENTORY_SIZE); + s->player.active_inventory = &s->player.inventory; dict_entity_init(s->entities); struct thread t; diff --git a/source/network/server_local.h b/source/network/server_local.h index 4821b94..115da8c 100644 --- a/source/network/server_local.h +++ b/source/network/server_local.h @@ -42,6 +42,7 @@ struct server_local { bool has_pos; bool finished_loading; struct inventory inventory; + struct inventory* active_inventory; } player; struct server_world world; dict_entity_t entities; @@ -51,7 +52,11 @@ struct server_local { }; void server_local_create(struct server_local* s); +struct entity* server_local_spawn_item(vec3 pos, struct item_data* it, + bool throw, struct server_local* s); void server_local_spawn_block_drops(struct server_local* s, struct block_info* blk_info); +void server_local_send_inv_changes(set_inv_slot_t changes, + struct inventory* inv, uint8_t window); #endif