Factorio-like gameplay wip

This commit is contained in:
Nikita Lisitsa 2024-08-18 22:33:25 +03:00
parent b0ab5c3d5b
commit 9bfbe69df9

View file

@ -16,8 +16,8 @@
#include <psemek/log/log.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/container/flat_map.hpp>
#include <variant>
#include <deque>
#include <format>
@ -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<resource_type> inputs;
resource_type output;
};
static util::hash_map<transformer_type, util::hash_map<resource_type, int>> 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<transformer_type, std::vector<recipe>> 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<int, 2> 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<resource_type, int> 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<timestamp> 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<color> existing_tasks;
util::hash_set<resource_type> existing_tasks;
int task_count = 0;
map.world.apply<task const>([&](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<index>();
result.world.index<path_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, source const>([&](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, build_site const>([&](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, transformer const>([&](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, task const>([&](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>([&](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<float, 2>::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<float, 2>::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<index>().find(*selected_))
if (selected_ && !belt_start_)
{
util::hash_set<color> types;
int task_count = 0;
map_.world.apply<task const>([&](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<path_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<path_vertex>();
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<path_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<path_vertex>();
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<index>().find(*selected_))
{
util::hash_set<color> types;
int task_count = 0;
map_.world.apply<task const>([&](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<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<index>().find(*selected_))
if (!map_.world.get(*entity).contains<task>())
{
auto acc = map_.world.get(*entity);
if (!acc.contains<source>() && !acc.contains<task>() && !acc.contains<zoomer>())
map_.world.destroy(*entity);
}
}
}
@ -721,12 +854,97 @@ namespace gmtk
);
}
map_.world.apply<task>([&](task & t)
map_.world.apply<build_site>([&](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<build_site>(entity);
map_.world.attach(entity, transformer{type});
}
});
map_.world.apply<vertex const, transformer const>([&](vertex const & v, transformer const & t)
{
boost::container::flat_map<resource_type, location> has_inputs;
auto c = v.location.down();
auto ce = map_.world.index<path_index>().get(c);
if (map_.world.get(ce).contains<occupied>())
return;
for (auto n : neighbours)
{
auto p = c.moved(n);
if (auto ne = map_.world.index<path_index>().find(p))
if (map_.world.get(*ne).get<path_vertex>().belts_to.contains(ce))
if (auto occ = map_.world.get(*ne).get_if<occupied const>())
if (auto i = map_.world.get(occ->entity).get_if<item const>())
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<path_index>().get(l);
auto re = map_.world.get(e).get<occupied>().entity;
map_.world.detach<occupied>(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<path_index>().get(p.second);
auto re = map_.world.get(e).get<occupied>().entity;
map_.world.detach<occupied>(e);
map_.world.destroy(re);
}
});
map_.world.apply<item>([&](ecs::handle entity, item & i)
@ -768,18 +986,31 @@ namespace gmtk
if (auto cell = map_.world.index<index>().find(i.start.up()))
{
if (auto t = map_.world.get(*cell).get_if<task>())
if (auto t = map_.world.get(*cell).get_if<build_site>())
{
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<transformer>())
if (auto t = map_.world.get(*cell).get_if<task>())
{
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<transformer>())
{
auto se = map_.world.index<path_index>().get(i.start);
auto ce = map_.world.index<path_index>().get(i.start.up().down());
if (map_.world.get(se).get<path_vertex>().belts_to.contains(ce))
{
map_.world.attach(map_.world.index<path_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<index>().find(p))
if (map_.world.get(*entity).contains<task>())
selected_ = p;
selected_ = p;
}
}