From 9bfbe69df9273fbf7c7060c53f0b5f026e08f161 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 18 Aug 2024 22:33:25 +0300 Subject: [PATCH] Factorio-like gameplay wip --- source/application.cpp | 470 ++++++++++++++++++++++++++++++----------- 1 file changed, 350 insertions(+), 120 deletions(-) diff --git a/source/application.cpp b/source/application.cpp index d0ad292..bad9948 100644 --- a/source/application.cpp +++ b/source/application.cpp @@ -16,8 +16,8 @@ #include #include +#include -#include #include #include @@ -26,23 +26,88 @@ namespace gmtk using namespace psemek; - psemek_declare_enum(color, std::uint32_t, - (red) - (green) - (blue) + psemek_declare_enum(resource_type, std::uint32_t, + (stone) + (coal) + (iron_ore) + (copper_ore) + (stone_brick) + (iron_plate) + (copper_plate) + (red_science_pack) ) - gfx::color_rgba to_color(color c) + psemek_declare_enum(transformer_type, std::uint32_t, + (furnace) + (factory) + ) + + gfx::color_rgba color_of(resource_type c) { switch (c) { - case color::red: return {255, 127, 127, 255}; - case color::green: return {127, 255, 127, 255}; - case color::blue: return {127, 127, 255, 255}; + case resource_type::stone: return {144, 128, 96, 255}; + case resource_type::coal: return {32, 32, 32, 255}; + case resource_type::iron_ore: return {96, 128, 144, 255}; + case resource_type::copper_ore: return {224, 128, 64, 255}; + case resource_type::stone_brick: return color_of(resource_type::stone); + case resource_type::iron_plate: return color_of(resource_type::iron_ore); + case resource_type::copper_plate: return color_of(resource_type::copper_ore); + case resource_type::red_science_pack: return {192, 64, 64, 255}; } + throw util::unknown_enum_value_exception{c}; } + struct recipe + { + util::hash_set inputs; + resource_type output; + }; + + static util::hash_map> const construction_recipe + { + { + transformer_type::furnace, + { + {resource_type::stone, 30}, + }, + }, + { + transformer_type::factory, + { + {resource_type::stone_brick, 30}, + {resource_type::iron_plate, 30}, + }, + }, + }; + + static util::hash_map> const recipies + { + { + transformer_type::furnace, + { + {{resource_type::coal, resource_type::stone}, resource_type::stone_brick}, + {{resource_type::coal, resource_type::iron_ore}, resource_type::iron_plate}, + {{resource_type::coal, resource_type::copper_ore}, resource_type::copper_plate}, + }, + }, + { + transformer_type::factory, + { + {{resource_type::iron_plate, resource_type::copper_plate}, resource_type::red_science_pack}, + }, + }, + }; + + geom::vector const neighbours[4] + { + {1, 0}, + {0, 1}, + {-1, 0}, + {0, -1}, + }; + struct location { int level; @@ -118,15 +183,22 @@ namespace gmtk { psemek_ecs_declare_uuid("source") - color type; + resource_type type; + }; + + struct build_site + { + psemek_ecs_declare_uuid("build_site") + + transformer_type type; + util::hash_map resources = {}; }; struct transformer { psemek_ecs_declare_uuid("transformer") - color input; - color output; + transformer_type type; }; struct zoomer @@ -167,7 +239,7 @@ namespace gmtk { psemek_ecs_declare_uuid("item") - color type; + resource_type type; location start; ecs::handle target = ecs::handle::null(); float state = 0.f; @@ -200,12 +272,9 @@ namespace gmtk { psemek_ecs_declare_uuid("task") - color type; + resource_type type; - // In last 15 seconds - std::deque received = {}; - - static constexpr int freq = 3; + int count = 0; }; struct map @@ -335,9 +404,9 @@ namespace gmtk void generate_next_task(random::generator & rng, map & map) { - color type; + resource_type type; - util::hash_set existing_tasks; + util::hash_set existing_tasks; int task_count = 0; map.world.apply([&](task const & task) { @@ -347,7 +416,7 @@ namespace gmtk while (true) { - type = random::uniform_from(rng, color_values()); + type = random::uniform_from(rng, resource_type_values()); if (task_count >= 3) break; @@ -381,7 +450,32 @@ namespace gmtk result.world.index(); result.world.index(); - generate_next_task(rng, result); + result.world.create( + vertex{{0, {-1, 1}}}, + source{resource_type::stone} + ); + + result.world.create( + vertex{{0, {0, 3}}}, + source{resource_type::iron_ore} + ); + + result.world.create( + vertex{{0, {1, 3}}}, + source{resource_type::coal} + ); + + result.world.create( + vertex{{0, {2, 3}}}, + source{resource_type::copper_ore} + ); + + result.world.create( + vertex{{0, {1, -1}}}, + task{resource_type::red_science_pack} + ); + + (void)rng; return result; } @@ -447,23 +541,63 @@ namespace gmtk map.world.apply([&](vertex const & v, source const & s) { + // float vs = std::pow(3.f, - v.location.level) * 0.01f; + painter.rect(v.location.bbox(-0.2f), color_of(s.type)); + // painter.text(v.location.center(), "180/m", {.scale = {vs, -vs}, .c = {0, 0, 0, 255}}); + }); + + auto draw_transformer = [&](location const & l, transformer_type type, bool in_construction) + { + auto box = l.bbox(-0.2f); + + auto p0 = box.corner(0, 0); + auto p1 = box.corner(1, 0); + auto p2 = box.corner(0.8f, 1); + auto p3 = box.corner(0.2f, 1); + + gfx::color_rgba color = {255, 0, 255, 255}; + switch (type) + { + case transformer_type::furnace: color = color_of(resource_type::stone); break; + case transformer_type::factory: color = color_of(resource_type::iron_ore); break; + } + + if (in_construction) + color[3] = 127; + + painter.triangle(p0, p1, p2, color); + painter.triangle(p0, p2, p3, color); + }; + + map.world.apply([&](vertex const & v, build_site const & t) + { + draw_transformer(v.location, t.type, true); + float vs = std::pow(3.f, - v.location.level) * 0.01f; - painter.rect(v.location.bbox(-0.2f), to_color(s.type)); - painter.text(v.location.center(), "180/m", {.scale = {vs, -vs}, .c = {0, 0, 0, 255}}); + auto text_bbox = v.location.bbox(-0.4f); + auto pen = text_bbox.corner(0.5f, 1.f); + + for (auto const & p : construction_recipe.at(t.type)) + { + int have = t.resources.contains(p.first) ? t.resources.at(p.first) : 0; + int need = p.second; + + painter.text(pen, std::format("{}/{}", have, need), {.scale = {vs, -vs}, .c = color_of(p.first)}); + + pen[1] -= 12.f * vs; + } }); map.world.apply([&](vertex const & v, transformer const & t) { - auto box = v.location.bbox(-0.2f); - painter.triangle(box.corner(0, 0), box.corner(1, 1), box.corner(0, 1), to_color(t.input)); - painter.triangle(box.corner(0, 0), box.corner(1, 0), box.corner(1, 1), to_color(t.output)); + draw_transformer(v.location, t.type, false); }); map.world.apply([&](vertex const & v, task const & t) { float vs = std::pow(3.f, - v.location.level) * 0.01f; - painter.rect(v.location.bbox(-0.2f), to_color(t.type)); - painter.text(v.location.center(), std::format("{}/m", t.received.size() * task::freq), {.scale = {vs, -vs}, .c = {0, 0, 0, 255}}); + painter.rect(v.location.bbox(-0.2f), color_of(t.type)); + painter.text(v.location.center(), std::format("{}", t.count), {.scale = {vs, -vs}, .c = {0, 0, 0, 255}}); }); map.world.apply([&](item const & i) @@ -483,8 +617,36 @@ namespace gmtk } scale *= 3.f; - painter.circle(pos, 0.075f * scale, {0, 0, 0, 255}); - painter.circle(pos, 0.05f * scale, to_color(i.type)); + auto color = color_of(i.type); + + switch (i.type) + { + case resource_type::coal: + case resource_type::stone: + case resource_type::iron_ore: + case resource_type::copper_ore: + painter.circle(pos, 0.075f * scale, {0, 0, 0, 255}); + painter.circle(pos, 0.05f * scale, color); + break; + case resource_type::stone_brick: + case resource_type::iron_plate: + case resource_type::copper_plate: + { + auto box = geom::expand(geom::box::singleton(pos), 0.075f * scale); + painter.rect(box, {0, 0, 0, 255}); + box = geom::shrink(box, 0.025f * scale); + painter.rect(box, color); + } + break; + case resource_type::red_science_pack: + { + auto box = geom::expand(geom::box::singleton(pos), 0.075f * scale); + painter.triangle(box.corner(0, 0), box.corner(1, 0), box.corner(0.5f, 1), {0, 0, 0, 255}); + box = geom::shrink(box, 0.025f * scale); + painter.triangle(box.corner(0, 0), box.corner(1, 0), box.corner(0.5f, 1), color); + } + break; + } }); } @@ -546,74 +708,50 @@ namespace gmtk void on_event(app::key_event const & event) override { - if (event.down && event.key == app::keycode::S) + if (event.down && event.key == app::keycode::B) { - if (selected_ && !map_.world.index().find(*selected_)) + if (selected_ && !belt_start_) { - util::hash_set types; - int task_count = 0; - - map_.world.apply([&](task const & t) - { - types.insert(t.type); - task_count += 1; - }); - - if (task_count > 1) - for (auto t : color_values()) - types.insert(t); - - map_.world.create( - vertex{*selected_}, - source{random::uniform_from(rng_, types)} - ); + belt_start_ = *selected_; } } - if (event.down && event.key == app::keycode::B) + if (!event.down && event.key == app::keycode::B) { - if (selected_) + if (selected_ && belt_start_ && selected_->level == belt_start_->level) { - if (belt_start_) + auto d = selected_->coords - belt_start_->coords; + if (std::abs(d[0]) + std::abs(d[1]) == 1) { - if (selected_->level == belt_start_->level) + auto s = belt_start_->down(); + + location belt[4]; + for (int i = 0; i <= 3; ++i) + belt[i] = s.moved(d * i); + sink(map_.world, belt[0], belt[1]); + sink(map_.world, belt[3], belt[2]); + + auto & index = map_.world.index(); + for (int i = 0; i < 3; ++i) { - auto d = selected_->coords - belt_start_->coords; - if (std::abs(d[0]) + std::abs(d[1]) == 1) + auto p = belt[i]; + auto q = belt[i + 1]; + + auto s = index.get(p); + auto t = index.get(q); + + auto & sv = map_.world.get(s).get(); + + if (sv.belts_to.contains(t)) + remove_belt(map_.world, s, t); + else { - auto s = belt_start_->down(); - - location belt[4]; - for (int i = 0; i <= 3; ++i) - belt[i] = s.moved(d * i); - sink(map_.world, belt[0], belt[1]); - sink(map_.world, belt[3], belt[2]); - - auto & index = map_.world.index(); - for (int i = 0; i < 3; ++i) - { - auto p = belt[i]; - auto q = belt[i + 1]; - - auto s = index.get(p); - auto t = index.get(q); - - auto & sv = map_.world.get(s).get(); - - if (sv.belts_to.contains(t)) - remove_belt(map_.world, s, t); - else - { - remove_belt(map_.world, t, s); - add_belt(map_.world, s, t); - } - } + remove_belt(map_.world, t, s); + add_belt(map_.world, s, t); } } - belt_start_ = std::nullopt; } - else if (within_grid(*selected_)) - belt_start_ = *selected_; + belt_start_ = std::nullopt; } } @@ -621,28 +759,20 @@ namespace gmtk { if (selected_ && !map_.world.index().find(*selected_)) { - util::hash_set types; - int task_count = 0; - - map_.world.apply([&](task const & t) - { - types.insert(t.type); - task_count += 1; - }); - - if (task_count == 1) - { - for (auto c : color_values()) - types.insert(c); - } - - color input = random::uniform_from(rng_, types); - types.erase(input); - color output = random::uniform_from(rng_, types); - map_.world.create( vertex{*selected_}, - transformer{input, output} + build_site{transformer_type::furnace} + ); + } + } + + if (event.down && event.key == app::keycode::G) + { + if (selected_ && !map_.world.index().find(*selected_)) + { + map_.world.create( + vertex{*selected_}, + build_site{transformer_type::factory} ); } } @@ -670,8 +800,11 @@ namespace gmtk if (selected_) { if (auto entity = map_.world.index().find(*selected_)) - if (!map_.world.get(*entity).contains()) + { + auto acc = map_.world.get(*entity); + if (!acc.contains() && !acc.contains() && !acc.contains()) map_.world.destroy(*entity); + } } } @@ -721,12 +854,97 @@ namespace gmtk ); } - map_.world.apply([&](task & t) + map_.world.apply([&](ecs::handle entity, build_site & b) { - auto threshold = map_.time; - threshold.trunc -= 60 / task::freq; - while (!t.received.empty() && t.received.front() < threshold) - t.received.pop_front(); + bool built = true; + for (auto const & p : construction_recipe.at(b.type)) + { + if (!b.resources.contains(p.first)) + { + built = false; + break; + } + + if (b.resources.at(p.first) < p.second) + { + built = false; + break; + } + } + + if (built) + { + auto type = b.type; + map_.world.detach(entity); + map_.world.attach(entity, transformer{type}); + } + }); + + map_.world.apply([&](vertex const & v, transformer const & t) + { + boost::container::flat_map has_inputs; + + auto c = v.location.down(); + auto ce = map_.world.index().get(c); + + if (map_.world.get(ce).contains()) + return; + + for (auto n : neighbours) + { + auto p = c.moved(n); + if (auto ne = map_.world.index().find(p)) + if (map_.world.get(*ne).get().belts_to.contains(ce)) + if (auto occ = map_.world.get(*ne).get_if()) + if (auto i = map_.world.get(occ->entity).get_if()) + if (i->start == p) + has_inputs[i->type] = p; + } + + bool crafted = false; + + for (auto const & recipe : recipies.at(t.type)) + { + bool has_all = true; + for (auto type : recipe.inputs) + has_all &= has_inputs.contains(type); + + if (!has_all) + continue; + + for (auto type : recipe.inputs) + { + auto l = has_inputs.at(type); + auto e = map_.world.index().get(l); + auto re = map_.world.get(e).get().entity; + + map_.world.detach(e); + map_.world.destroy(re); + } + + auto r = map_.world.create( + item{recipe.output, c} + ); + + map_.world.attach(ce, occupied{r}); + + crafted = true; + + break; + } + + if (!crafted && !has_inputs.empty()) + { + return; + + auto p = random::uniform_from(rng_, has_inputs); + + auto e = map_.world.index().get(p.second); + auto re = map_.world.get(e).get().entity; + + map_.world.detach(e); + map_.world.destroy(re); + } }); map_.world.apply([&](ecs::handle entity, item & i) @@ -768,18 +986,31 @@ namespace gmtk if (auto cell = map_.world.index().find(i.start.up())) { - if (auto t = map_.world.get(*cell).get_if()) + if (auto t = map_.world.get(*cell).get_if()) { - if (t->type == i.type) - t->received.push_back(map_.time); + t->resources[i.type] += 1; map_.world.destroy(entity); return; } - if (auto t = map_.world.get(*cell).get_if()) + if (auto t = map_.world.get(*cell).get_if()) { - if (t->input == i.type) - i.type = t->output; + if (t->type == i.type) + t->count += 1; + map_.world.destroy(entity); + return; + } + + if (map_.world.get(*cell).get_if()) + { + auto se = map_.world.index().get(i.start); + auto ce = map_.world.index().get(i.start.up().down()); + + if (map_.world.get(se).get().belts_to.contains(ce)) + { + map_.world.attach(map_.world.index().get(i.start), occupied{entity}); + return; + } } } @@ -844,8 +1075,7 @@ namespace gmtk if (p.coords[0] >= 3 * l.coords[0] && p.coords[0] < 3 * l.coords[0] + 3 && p.coords[1] >= 3 * l.coords[1] && p.coords[1] < 3 * l.coords[1] + 3) selected_ = p; else if (auto entity = map_.world.index().find(p)) - if (map_.world.get(*entity).contains()) - selected_ = p; + selected_ = p; } }