/*
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)) {
if (unit->type == unit_t::UNIT_SOLDIER)
unit->say(text::get(text::SAY_READY));
}
if (select_one)
break;
}
}
enum {
COMMAND_MOVE,
COMMAND_FIRE,
COMMAND_STOP,
COMMAND_REPL,
};
bool state_t::populate_pie_menu(std::vector &items)
{
bool soldiers = false, repls = 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;
break;
case unit_t::UNIT_REPL:
repls = true;
break;
default:;
}
}
if (soldiers) {
items.push_back((interface::pie_item_t){"Move", COMMAND_MOVE});
items.push_back((interface::pie_item_t){"Fire", COMMAND_FIRE});
items.push_back((interface::pie_item_t){"Stop", COMMAND_STOP});
}
if (repls) {
items.push_back((interface::pie_item_t){"Replicate", COMMAND_REPL});
}
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(text::get(text::SAY_NO_PATH));
else {
soldier->move_marker = std::make_unique(soldier->game, soldier->move.path.back());
soldier->say(text::get(text::SAY_MOVING));
}
break;
case COMMAND_STOP:
soldier->stop_moving();
soldier->manual_firing = false;
soldier->say(text::get(text::SAY_STOPPING));
soldier->aim_marker.reset();
break;
case COMMAND_FIRE:
soldier->manual_firing = true;
soldier->manual_firing_target = x;
soldier->say(text::get(text::SAY_FIRING));
break;
}
}
static void command_repl(unit_repl_t *repl, v2f_t x, int number)
{
switch (number) {
case COMMAND_REPL:
repl->activate();
break;
}
}
void state_t::command(v2f_t x, int number)
{
if (!selected_units.size())
return;
for (unit_t *unit : selected_units) {
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_REPL:
command_repl(dynamic_cast(unit),
x, number);
break;
default:;
}
}
}
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 / 10)
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) {
frames_behind += target - frames_since_t0;
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;
explosion = new fx_explosion_t(this, x);
explosion->place(&world);
for (size_t i = 0; i < 30; i++) {
v2f_t end;
world::trace_t trace;
entity_t *ent;
int damage;
end = x + prng.unit_vec2() * 6.0f;
trace = world.ray_v_all(x, end, CF_SOLID|CF_BODY|CF_BODY_SMALL, NULL);
if (!trace.hit)
continue;
if (!trace.ent)
continue;
if (trace.ent->type != ET_UNIT)
continue;
ent = dynamic_cast(trace.ent);
damage = pow(1 - trace.frac, 2.0f) * 40.0f;
ent->damage(damage, nullptr);
}
}
} //namespace game