/*
This file is part of Minitrem.
Minitrem 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 2 of the License, or
(at your option) any later version.
Minitrem 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 Minitrem. If not, see .
*/
#include "game.hpp"
#include
namespace game {
bool load_assets(void)
{
assets::load();
return true;
}
size_t selection_cookie = 1;
entity_t::entity_t(game::state_t *game_, int type_) : world::entity_t(type_)
{
game = game_;
}
entity_t::~entity_t(void)
{
sleep();
if (world)
unlink();
}
void entity_t::place(world::world_t *world_)
{
bool do_spawn = false;
if (!world)
do_spawn = true;
link(world_);
if (do_spawn)
on_spawn();
}
void entity_t::place(world::world_t *world, v2f_t x_)
{
x = x_;
cmodel.bounds = size + x;
render_bounds = render_size + x;
place(world);
}
void entity_t::wake(void)
{
awake = true;
wake_time = game->now;
game->awake_entities.insert(this);
if (!ignore_waking)
on_wake();
}
void entity_t::sleep(void)
{
awake = false;
game->awake_entities.erase(this);
}
void state_t::start(void)
{
std::random_device rd;
std::uniform_int_distribution dist(0, (uint32_t)1 << 31);
world.seed_procgen(dist(rd));
world.generator = worldgen;
world.generator_data = (void*)this;
resume();
}
void state_t::stop(void)
{
}
bool state_t::select_unit(unit_t *unit, int type)
{
switch (type) {
case SELECT_NEW:
case SELECT_OR:
if (unit->selected == selection_cookie)
return false;
else
goto do_select;
case SELECT_XOR:
if (unit->selected == selection_cookie)
goto do_deselect;
else
goto do_select;
}
do_select:
unit->selected = selection_cookie;
selected_units.insert(unit);
return true;
do_deselect:
unit->selected = selection_cookie - 1;
selected_units.erase(unit);
return false;
}
void state_t::select(rectf_t rect, int type)
{
bool select_one;
std::list ents;
rect = rect.norm();
if (type == SELECT_NEW) {
selection_cookie++;
selected_units.clear();
}
select_one = rect.area() < 0.2f;
ents = world.get_render_entities(rect);
if (select_one)
ents.sort(render::visibility_order);
for (world::entity_t *ent : ents) {
unit_t *unit;
if (ent->type != ET_UNIT)
continue;
unit = (unit_t*)ent;
if (!unit->controllable)
continue;
if (select_unit(unit, type)) {
switch (unit->type) {
case unit_t::UNIT_SOLDIER:
unit->say("Ich bin bereit.", false);
break;
case unit_t::UNIT_SCIENTIST:
unit->say("Я готов.", false);
break;
case unit_t::UNIT_BUILDER:
unit->say("Gotowy na rozkaz.", false);
break;
default:
break;
}
}
if (select_one)
break;
}
}
enum {
COMMAND_FIRST_TELEPORTER,
COMMAND_MOVE,
COMMAND_FIRE,
COMMAND_THROW_GRENADE,
COMMAND_STOP,
COMMAND_RESTOCK_SHELLS,
COMMAND_RESTOCK_GRENADES,
COMMAND_HIRE_SOLDIER,
COMMAND_HIRE_SCIENTIST,
COMMAND_HIRE_BUILDER,
COMMAND_GATHER,
COMMAND_REPAIR,
COMMAND_BUILD_TELEPORTER,
COMMAND_BUILD_REPLICATOR,
COMMAND_REPLICATE_SHELLS,
COMMAND_REPLICATE_GRENADES,
};
bool state_t::populate_pie_menu(std::vector &items)
{
bool soldiers = false, teleporters = false, grenades = false,
scientists = false, builders = false, replicators = false,
restock_shells = false, restock_grenades = false;
items.clear();
if (!first_teleporter_placed) {
items.push_back((interface::pie_item_t){&assets::ui.icon_teleporter, "Place the teleporter", COMMAND_FIRST_TELEPORTER});
return true;
}
if (selected_units.size() == 0)
return false;
for (unit_t *unit : selected_units) {
if (unit->dead || !unit->controllable || !unit->constructed)
continue;
switch (unit->type) {
case unit_t::UNIT_SOLDIER:
soldiers = true;
if (unit->storage.grenades)
grenades = true;
if (unit->storage.shells < unit->storage.max_shells)
restock_shells = true;
if (unit->storage.grenades < unit->storage.max_grenades)
restock_grenades = true;
break;
case unit_t::UNIT_SCIENTIST:
scientists = true;
break;
case unit_t::UNIT_BUILDER:
builders = true;
break;
case unit_t::UNIT_TELEPORTER:
teleporters = true;
break;
case unit_t::UNIT_REPLICATOR:
replicators = true;
break;
default:;
}
}
if (soldiers || scientists || builders) {
items.push_back((interface::pie_item_t){&assets::ui.icon_move, "Move", COMMAND_MOVE});
items.push_back((interface::pie_item_t){&assets::ui.icon_stop, "Stop", COMMAND_STOP});
}
if (soldiers) {
items.push_back((interface::pie_item_t){&assets::ui.icon_fire, "Fire", COMMAND_FIRE});
if (grenades)
items.push_back((interface::pie_item_t){&assets::ui.icon_grenade, "Throw a grenade", COMMAND_THROW_GRENADE});
if (restock_shells)
items.push_back((interface::pie_item_t){&assets::ui.icon_shells, "Restock shells", COMMAND_RESTOCK_SHELLS});
if (restock_grenades)
items.push_back((interface::pie_item_t){&assets::ui.icon_grenades, "Restock grenades", COMMAND_RESTOCK_GRENADES});
}
if (scientists)
items.push_back((interface::pie_item_t){&assets::ui.icon_crystals, "Gather", COMMAND_GATHER});
if (builders) {
items.push_back((interface::pie_item_t){&assets::ui.icon_repair, "Repair", COMMAND_REPAIR});
items.push_back((interface::pie_item_t){&assets::ui.icon_teleporter, "Build a teleporter", COMMAND_BUILD_TELEPORTER});
items.push_back((interface::pie_item_t){&assets::ui.icon_replicator, "Build a replicator", COMMAND_BUILD_REPLICATOR});
}
if (teleporters) {
items.push_back((interface::pie_item_t){&assets::ui.icon_soldier, "Hire a soldier", COMMAND_HIRE_SOLDIER});
items.push_back((interface::pie_item_t){&assets::ui.icon_scientist, "Hire a scientist", COMMAND_HIRE_SCIENTIST});
items.push_back((interface::pie_item_t){&assets::ui.icon_builder, "Hire a builder", COMMAND_HIRE_BUILDER});
}
if (replicators) {
items.push_back((interface::pie_item_t){&assets::ui.icon_shells, "Replicate shells", COMMAND_REPLICATE_SHELLS});
items.push_back((interface::pie_item_t){&assets::ui.icon_grenades, "Replicate grenades", COMMAND_REPLICATE_GRENADES});
}
return true;
}
void state_t::command_first_teleporter(v2f_t where)
{
unit_teleporter_t *teleporter;
world::cmodel_t test;
teleporter = new unit_teleporter_t(this);
teleporter->place(&world, where);
test = teleporter->cmodel;
test.cflags = CF_SOLID|CF_BODY|CF_BODY_SMALL|CF_DECOS|CF_WATER|CF_SURFACE2;
if (world.test_rect(&test, teleporter)) {
interface.print("There is no room for a teleporter here.");
delete teleporter;
return;
}
teleporter->constructed = true;
teleporter->health = teleporter->max_health;
first_teleporter_placed = true;
}
static void command_soldier(unit_soldier_t *soldier, v2f_t x, int number)
{
switch (number) {
case COMMAND_MOVE:
if (!soldier->start_moving(x))
soldier->say("Es gibt keinen Weg.", false);
else {
soldier->move_marker = std::make_unique(soldier->game, soldier->move.path.back());
soldier->say("Ich bin unterwegs.", false);
}
break;
case COMMAND_STOP:
soldier->stop_moving();
soldier->manual_firing = false;
soldier->say("Halt.", false);
soldier->aim_marker.reset();
break;
case COMMAND_FIRE:
soldier->manual_firing = true;
soldier->manual_firing_target = x;
soldier->say("Feuer!", false);
break;
case COMMAND_THROW_GRENADE:
soldier->command_throw_grenade(x);
break;
case COMMAND_RESTOCK_SHELLS:
soldier->command_restock(false);
break;
case COMMAND_RESTOCK_GRENADES:
soldier->command_restock(true);
break;
}
}
static void command_scientist(unit_scientist_t *scientist, v2f_t x, int number)
{
switch (number) {
case COMMAND_MOVE:
if (!scientist->start_moving(x))
scientist->say("Нет пути.", false);
else {
scientist->move_marker = std::make_unique(scientist->game, scientist->move.path.back());
scientist->say("Я иду.", false);
}
break;
case COMMAND_STOP:
scientist->stop_moving();
scientist->say("Стоп.", false);
scientist->gather_marker.reset();
break;
case COMMAND_GATHER:
scientist->gathering = true;
scientist->gathering_at = x;
scientist->gather_marker = std::make_unique(scientist->game, x);
scientist->say("Я собираю.", false);
break;
}
}
static void command_builder(unit_builder_t *builder, v2f_t x, int number)
{
switch (number) {
case COMMAND_MOVE:
if (!builder->start_moving(x))
builder->say("Nie ma tam drogi.", false);
else {
builder->move_marker = std::make_unique(builder->game, builder->move.path.back());
builder->say("W drodze.", false);
}
break;
case COMMAND_STOP:
builder->command_stop();
break;
case COMMAND_REPAIR:
builder->command_repair(x);
break;
case COMMAND_BUILD_TELEPORTER:
builder->command_build(x, unit_t::UNIT_TELEPORTER);
break;
case COMMAND_BUILD_REPLICATOR:
builder->command_build(x, unit_t::UNIT_REPLICATOR);
break;
}
}
static void command_teleporter(unit_teleporter_t *teleporter, v2f_t x, int number)
{
switch (number) {
case COMMAND_HIRE_SOLDIER:
teleporter->activate(unit_t::UNIT_SOLDIER);
break;
case COMMAND_HIRE_SCIENTIST:
teleporter->activate(unit_t::UNIT_SCIENTIST);
break;
case COMMAND_HIRE_BUILDER:
teleporter->activate(unit_t::UNIT_BUILDER);
break;
}
}
static void command_replicator(unit_replicator_t *replicator, v2f_t x, int number)
{
switch (number) {
case COMMAND_REPLICATE_SHELLS:
replicator->activate(false);
break;
case COMMAND_REPLICATE_GRENADES:
replicator->activate(true);
break;
}
}
void state_t::command(v2f_t x, int number)
{
bool unlink;
if (!first_teleporter_placed || number == COMMAND_FIRST_TELEPORTER) {
command_first_teleporter(x);
return;
}
if (!selected_units.size())
return;
unlink = (number == COMMAND_MOVE);
if (unlink)
for (unit_t *unit : selected_units)
unit->cmodel.ignore = true;
auto copy = selected_units;
for (unit_t *unit : copy) {
if (unit->dead || !unit->controllable)
continue;
switch (unit->type) {
case unit_t::UNIT_SOLDIER:
command_soldier(dynamic_cast(unit),
x, number);
break;
case unit_t::UNIT_SCIENTIST:
command_scientist(dynamic_cast(unit),
x, number);
break;
case unit_t::UNIT_BUILDER:
command_builder(dynamic_cast(unit),
x, number);
break;
case unit_t::UNIT_TELEPORTER:
command_teleporter(dynamic_cast(unit),
x, number);
break;
case unit_t::UNIT_REPLICATOR:
command_replicator(dynamic_cast(unit),
x, number);
default:;
}
}
if (unlink)
for (unit_t *unit : selected_units)
unit->cmodel.ignore = false;
}
void state_t::pause(void)
{
paused = true;
}
void state_t::resume(void)
{
t0 = nclock();
frames_since_t0 = 0;
paused = false;
}
void state_t::check_game_over(void)
{
if (game_over_displayed)
return;
if (!first_teleporter_placed)
return;
// Can collect crystals and got a teleporter.
if (num_scientists && num_teleporters)
return;
// Got a teleporter and enough crystals for a scientist.
if (num_teleporters && crystals >= 95)
return;
// Got a scientist, a builder and enough crystals for a teleporter.
if (num_scientists && num_builders && crystals >= 265)
return;
// Got a builder and enough crystals for a teleporter and a scientist.
if (num_builders && crystals >= 265 + 95)
return;
interface.print("FURTHER PROGRESS IS NOT POSSIBLE.");
interface.print("At this point you might want to restart the game.");
game_over_displayed = true;
}
#define TIME_DELTA ((ntime_t)1000000000 / 100)
#define TIME_LIMIT ((ntime_t)1000000000 / 20)
void state_t::tick(ntime_t time_)
{
size_t target;
if (paused)
return;
target = (time_ - t0) / TIME_DELTA;
while (frames_since_t0 < target) {
ntime_t tmp;
// FIXME: Is this non-deterministic enough?
prng.seed(prng.next() ^ time);
// setting up old variables (refactor them out eventually)
now = time * 1.0e-9;
dt = TIME_DELTA * 1.0e-9;
for (entity_t *ent : deletion_list)
delete ent;
deletion_list.clear();
// on_think can insert/erase elements of awake_entities so iterate
// over a copy of it.
auto copy = awake_entities;
for (entity_t *ent : copy)
if (awake_entities.find(ent) != awake_entities.end())
ent->on_think();
check_game_over();
frames++;
frames_since_t0++;
time += TIME_DELTA;
fc_game.tick();
tmp = nclock();
if (tmp - time_ > TIME_LIMIT) {
t0 = nclock();
frames_since_t0 = 0;
frames_behind++;
interface.print("(Lag: " + std::to_string((tmp - time_) / MSEC(1)) + " ms)");
break;
}
}
}
#define XRES 9
#define YRES 9
void state_t::compute_ambience(void)
{
const size_t samples = XRES * YRES;
rectf_t area = interface.world_view;
v2f_t origins[AMBIENT_COUNT];
size_t hits[AMBIENT_COUNT];
for (size_t i = 0; i < AMBIENT_COUNT; i++) {
origins[i] = v2f_t(0, 0);
hits[i] = 0;
}
// resolution chosen arbitrarily
for (size_t y = 0; y < YRES; y++)
for (size_t x = 0; x < XRES; x++) {
v2f_t point;
world::tile_t *tile;
int type;
point[0] = lerp(area[0][0], area[1][0], y / (YRES - 1.0f));
point[1] = lerp(area[0][1], area[1][1], x / (XRES - 1.0f));
tile = world.get_tile(world::tile_index_t(point), world::SECTOR_FULL);
switch (tile->type) {
case TILE_DIRT:
type = AMBIENT_WIND;
break;
case TILE_GRAVEL:
case TILE_STONE:
type = AMBIENT_CHASM;
break;
case TILE_WATER:
type = AMBIENT_WATER;
break;
case TILE_DIRT_RED:
case TILE_STONE_RED:
type = AMBIENT_NEXUS;
break;
default:
continue;
}
origins[type] += point;
hits[type]++;
}
for (size_t i = 0; i < AMBIENT_COUNT; i++) {
assets::ambients[i].origin = origins[i] / hits[i];
assets::ambients[i].weight = (float)hits[i] / samples;
}
}
void state_t::explosion(v2f_t x)
{
fx_explosion_t *explosion;
const float r = 5.0f;
rectf_t rect;
rect[0] = x - v2f_t(r, r);
rect[1] = x + v2f_t(r, r);
for (world::entity_t *ent : world.get_entities(rect, -1)) {
v2f_t center;
float dist, damage;
world::trace_t trace;
entity_t *g_ent;
center = ent->cmodel.bounds.center();
dist = (center - x).len();
if (dist > r)
continue;
trace = world.ray_v_all(x, center, CF_SOLID, ent);
if (trace.frac < 1.0f)
continue;
damage = clamp(40.0f / dist, 0.0f, 1000.0f);
g_ent = dynamic_cast(ent);
g_ent->damage((int)damage, nullptr);
}
explosion = new fx_explosion_t(this, x);
explosion->place(&world);
hivemind_alert(x, 20.0f, false, v2f_t(0, 0));
}
} //namespace game