/*
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"
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();
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)
{
unit_soldier_t *soldier;
unit_repl_t *repl;
world.generator = worldgen;
world.generator_data = (void*)this;
soldier = new unit_soldier_t(this);
soldier->place(&world, v2f_t(0.5, 0.5));
repl = new unit_repl_t(this);
repl->place(&world, v2f_t(1.5, 0.5));
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;
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_MOVE,
COMMAND_FIRE,
COMMAND_FIRE_ROCKET,
COMMAND_STOP,
COMMAND_REPL_SOLDIER,
COMMAND_REPL_SCIENTIST,
COMMAND_REPL_BUILDER,
COMMAND_GATHER
};
bool state_t::populate_pie_menu(std::vector &items)
{
bool soldiers = false, repls = false, rockets = false,
scientists = false, builders = false;
items.clear();
if (selected_units.size() == 0)
return false;
for (unit_t *unit : selected_units) {
if (unit->dead || !unit->controllable)
continue;
switch (unit->type) {
case unit_t::UNIT_SOLDIER:
soldiers = true;
if (!dynamic_cast(unit)->rocket_fired)
rockets = true;
break;
case unit_t::UNIT_SCIENTIST:
scientists = true;
break;
case unit_t::UNIT_BUILDER:
builders = true;
break;
case unit_t::UNIT_REPL:
repls = true;
break;
default:;
}
}
if (soldiers || scientists || builders) {
items.push_back((interface::pie_item_t){"Move", COMMAND_MOVE});
items.push_back((interface::pie_item_t){"Stop", COMMAND_STOP});
}
if (soldiers) {
items.push_back((interface::pie_item_t){"Fire", COMMAND_FIRE});
if (rockets)
items.push_back((interface::pie_item_t){"Fire a rocket", COMMAND_FIRE_ROCKET});
}
if (scientists)
items.push_back((interface::pie_item_t){"Gather", COMMAND_GATHER});
if (builders)
items.push_back((interface::pie_item_t){"TODO", -1});
if (repls) {
items.push_back((interface::pie_item_t){"Spawn a soldier", COMMAND_REPL_SOLDIER});
items.push_back((interface::pie_item_t){"Spawn a scientist", COMMAND_REPL_SCIENTIST});
items.push_back((interface::pie_item_t){"Spawn a builder", COMMAND_REPL_BUILDER});
}
return 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_FIRE_ROCKET:
{
v2f_t from, v;
rocket_t *rocket;
if (soldier->rocket_fired) {
soldier->say("Keine Raketen!");
break;
}
soldier->rocket_fired = true;
from = soldier->x + v2f_t(0, -0.5);
rocket = new rocket_t(soldier->game, from, x, soldier);
rocket->place(&soldier->game->world, from);
}
}
}
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->stop_moving();
builder->say("Stop.", false);
break;
}
}
static void command_repl(unit_repl_t *repl, v2f_t x, int number)
{
switch (number) {
case COMMAND_REPL_SOLDIER:
repl->activate(unit_t::UNIT_SOLDIER);
break;
case COMMAND_REPL_SCIENTIST:
repl->activate(unit_t::UNIT_SCIENTIST);
break;
case COMMAND_REPL_BUILDER:
repl->activate(unit_t::UNIT_BUILDER);
break;
}
}
void state_t::command(v2f_t x, int number)
{
bool unlink;
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_REPL:
command_repl(dynamic_cast(unit),
x, number);
break;
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;
}
#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) {
// 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();
frames++;
frames_since_t0++;
time += TIME_DELTA;
fc_game.tick();
if (nclock() - time_ > TIME_LIMIT) {
size_t left;
left = target - frames_since_t0;
t0 = nclock();
frames_since_t0 = 0;
frames_behind++;
interface.print("(Lag: " + std::to_string(left) + ")");
break;
}
}
}
#define XRES 9
#define YRES 9
void state_t::compute_ambience(render::state_t *render)
{
const size_t samples = XRES * YRES;
rectf_t area;
v2f_t origins[AMBIENT_COUNT];
size_t hits[AMBIENT_COUNT];
area = render->window_in_world_space();
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