/*
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 interface {
using namespace game; // FIXME
static sf::Vector2f compute_pan(sf::RenderWindow *window, sf::Vector2f pan_ref)
{
sf::Vector2i mouse = sf::Mouse::getPosition(*window);
sf::Vector2f vmouse = window->mapPixelToCoords(mouse);
return -(vmouse - pan_ref);
}
void state_t::start_following(void)
{
camera.following = true;
camera.zero_point_s = camera.center;
camera.center = v2f_t(0, 0);
print("Following: on.");
}
void state_t::stop_following(void)
{
camera.following = false;
camera.center += camera.zero_point_s;
camera.zero_point_s = v2f_t(0, 0);
print("Following: off.");
}
void pie_menu_t::open(v2f_t wmouse, v2f_t mouse)
{
size_t layer = 0;
float base_radius = 50.0f; // FIXME
for (size_t i = 0; i < items.size(); ) {
size_t left, layer_cap, on_layer;
left = items.size() - i;
layer_cap = (layer + 1) * (layer + 1);
on_layer = std::min(left, layer_cap);
for (size_t j = 0; j < on_layer; j++) {
pie_item_t *item = &items[i + j];
float dt = 2 * M_PI / on_layer;
item->r0 = layer * base_radius;
item->r1 = (layer + 1) * base_radius;
item->t0 = j * dt;
item->t1 = (j + 1) * dt;
}
i += on_layer;
layer++;
}
is_open = true;
x_world = wmouse;
x = mouse;
}
void pie_menu_t::close(game::state_t *game)
{
if (!is_open)
return;
if (selected)
game->command(x_world, selected->action);
is_open = false;
items.clear();
}
void pie_menu_t::update(v2f_t mouse)
{
v2f_t delta;
float r, t;
delta = mouse - x;
r = delta.len();
t = atan2(delta[1], delta[0]);
if (t < 0)
t += 2 * M_PI;
selected = nullptr;
for (pie_item_t &i : items) {
if (r >= i.r0 && r <= i.r1 && t >= i.t0 && t <= i.t1) {
selected = &i;
break;
}
}
}
static int get_selection_type(void)
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LControl))
return game::SELECT_OR;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LShift))
return game::SELECT_XOR;
return game::SELECT_NEW;
}
void state_t::tick(double dt)
{
vec_t window_size;
sf::Event event;
v2f_t view_size;
v2f_t follow_center(0, 0), view_center, pan_delta = v2f_t(0, 0);
float view_scale;
sf::Vector2i mouse;
v2f_t wmouse; // Mouse position in world space;
window_size = window->getSize();
expfade(&camera.zoom_s, camera.zoom, 15, dt);
view_scale = 4.5 * exp(camera.zoom_s * 0.12);
if (window_size[0] < window_size[1]) {
view_size[0] = view_scale * window_size[0] / window_size[1];
view_size[1] = view_scale;
} else {
view_size[0] = view_scale;
view_size[1] = view_scale * window_size[1] / window_size[0];
}
if (!game->selected_units.size() && camera.following)
stop_following();
if (camera.following) {
float limit;
for (entity_t *ent : game->selected_units)
follow_center += ent->render_bounds.center();
follow_center /= game->selected_units.size();
expfade(&camera.zero_point_s, follow_center, 15, dt);
limit = view_scale * 0.2f;
if (camera.center.len() > limit * 2.0f)
stop_following();
else if (camera.center.len() > limit)
expfade(&camera.center, camera.center.norm() * limit, 3, dt);
}
view_center = camera.zero_point_s + camera.center;
window->setView(sf::View(view_center, view_size));
if (camera.panning) {
pan_delta = compute_pan(window, camera.pan_ref);
view_center += compute_pan(window, camera.pan_ref);
window->setView(sf::View(view_center, view_size));
}
camera_3d[0] = view_center[0];
camera_3d[1] = view_center[1];
camera_3d[2] = view_scale;
mouse = sf::Mouse::getPosition(*window);
wmouse = window->mapPixelToCoords(sf::Mouse::getPosition(*window));
while (window->pollEvent(event)) {
// FIXME: refactor this nested switch clusterfuck
switch (event.type) {
case sf::Event::Closed:
window->close();
return;
case sf::Event::MouseButtonPressed:
switch (event.mouseButton.button) {
case sf::Mouse::Button::Left:
select.selecting = true;
select.rect[0] = wmouse;
select.rect[1] = wmouse;
break;
case sf::Mouse::Button::Right:
if (game->populate_pie_menu(pie_menu.items))
pie_menu.open(wmouse, mouse);
break;
case sf::Mouse::Button::Middle:
camera.panning = true;
camera.pan_ref = wmouse;
break;
default:;
}
break;
case sf::Event::MouseButtonReleased:
switch (event.mouseButton.button) {
case sf::Mouse::Button::Left:
if (select.selecting)
game->select(select.rect, select.type);
select.selecting = false;
break;
case sf::Mouse::Button::Right:
pie_menu.close(game);
break;
case sf::Mouse::Button::Middle:
if (camera.panning)
camera.center += pan_delta;
camera.panning = false;
break;
default:;
}
break;
case sf::Event::MouseWheelScrolled:
camera.zoom -= event.mouseWheelScroll.delta;
if (camera.zoom < 0)
camera.zoom = 0;
if (camera.zoom > 17)
camera.zoom = 17;
break;
case sf::Event::KeyPressed:
switch (event.key.code) {
case sf::Keyboard::Key::Space:
if (!game->paused) {
game->pause();
print("PAUSED");
} else {
game->resume();
print("UNPAUSED");
}
break;
case sf::Keyboard::Key::F:
if (camera.following)
stop_following();
else
start_following();
break;
case sf::Keyboard::Key::F1:
debug_draw_cmodels ^= 1;
print("debug_draw_cmodels = " + std::to_string(debug_draw_cmodels));
break;
case sf::Keyboard::Key::F2:
debug_draw_paths ^= 1;
print("debug_draw_paths = " + std::to_string(debug_draw_paths));
break;
case sf::Keyboard::Key::F3:
debug_draw_tile_coords ^= 1;
print("debug_draw_tile_coords = " + std::to_string(debug_draw_tile_coords));
break;
case sf::Keyboard::Key::F4:
debug_AI ^= 1;
print("debug_AI = " + std::to_string(debug_AI));
break;
case sf::Keyboard::Key::F12:
game->crystals = (size_t)-1;
print("INFINITE CRYSTALS\n");
break;
default:;
}
default:;
}
}
if (select.selecting) {
select.type = get_selection_type();
select.rect[1] = wmouse;
}
if (pie_menu.is_open)
pie_menu.update(mouse);
world_view = render::window_bounds(window);
}
void state_t::print(std::string str)
{
log.push_back((log_entry_t){game->now, str});
std::cout << str << std::endl;
}
void pie_menu_t::render_to(render::state_t *render)
{
float r_max = 0.0f;
if (!is_open)
return;
for (pie_item_t &i : items) {
sf::Color color;
v2f_t rad, center;
if (&i == selected)
color = sf::Color(255, 255, 255, 120);
else
color = sf::Color(255, 255, 255, 40);
render->render_ring_sect(x, i.r0, i.r1, i.t0, i.t1, color);
color.a = 255;
if (i.r0 == 0.0f) {
center = x;
} else {
center = x + (i.r1 + i.r0) / 2 *
v2f_t::rad((i.t1 + i.t0) / 2);
}
render->render(0.0, i.icon, rectf_t::around(center, 15.0f));
r_max = i.r1;
}
if (selected)
render->render_text(x + v2f_t(0, r_max + 20.0f), 15, selected->label, render::text_align_t::ALIGN_CENTER_BOTTOM, sf::Color::White);
}
static sf::Color health_to_color(float frac,int alpha)
{
int t;
if (frac >= 1.0f)
return sf::Color(0, 255, 0, alpha);
else if (frac > 0.5f) {
t = (int)remap(1.0f, 0.5f, 0.0f, 255.0f, frac);
return sf::Color(t, 255, 0, alpha);
} else if (frac > 0.0f) {
t = (int)remap(0.5f, 0.0f, 255.0f, 0.0f, frac);
return sf::Color(255, t, 0, alpha);
} else
return sf::Color(255, 0, 0, alpha);
}
static void render_bar(render::state_t *render, rectf_t rect, size_t value, size_t max,
render::animated_texture_t *icon_image)
{
rectf_t bar, icon;
std::string text;
v2f_t text_x;
float text_h;
render->render_rect(rect, sf::Color(0, 0, 0, 128));
if (max) {
float frac = (float)value / max;
bar = rect;
bar[1][0] = lerp(bar[0][0], bar[1][0], frac);
render->render_rect(bar, health_to_color(frac, 90));
text = std::to_string(value) + "/" + std::to_string(max);
} else
text = std::to_string(value);
icon[0] = bar[0];
icon[1] = bar[0] + v2f_t(1.0f, 1.0f) * bar.dim(1);
render->render(0.0, icon_image, icon);
text_h = bar.dim(1);
text_x = v2f_t(bar[0][0] + text_h, bar[1][1] - text_h * 0.35f);
render->render_text(text_x, text_h, text, render::ALIGN_LEFT_BOTTOM, sf::Color::White);
}
static void render_avatar(render::state_t *render, game::unit_t *unit, v2f_t at,
v2f_t avatar_size, float em, bool large)
{
render::animated_texture_t *image;
rectf_t rect, row;
v2f_t name_x;
switch (unit->type) {
case game::unit_t::UNIT_SOLDIER:
image = &assets::soldier.avatar;
break;
case game::unit_t::UNIT_SCIENTIST:
image = &assets::scientist.avatar;
break;
case game::unit_t::UNIT_BUILDER:
image = &assets::builder.avatar;
break;
case game::unit_t::UNIT_TELEPORTER:
image = &assets::teleporter.avatar;
break;
case game::unit_t::UNIT_REPLICATOR:
image = &assets::replicator.avatar;
break;
default:
image = &assets::deco.eyething;
break;
}
rect = rectf_t(at, at + avatar_size * em);
render->render(0, image, rect, sf::Color::White);
if (!large)
em *= 0.65f;
row = rect;
row[0][1] = row[1][1] - em;
render_bar(render, row, unit->health, unit->max_health,
&assets::ui.icon_health);
row[0][1] -= em;
row[1][1] -= em;
if (unit->storage.max_shells) {
render_bar(render, row, unit->storage.shells, unit->storage.max_shells,
&assets::ui.icon_shells);
row[0][1] -= em;
row[1][1] -= em;
}
if (unit->storage.max_grenades) {
render_bar(render, row, unit->storage.grenades, unit->storage.max_grenades,
&assets::ui.icon_grenades);
row[0][1] -= em;
row[1][1] -= em;
}
name_x = v2f_t(row[0][0], row[1][1] - em * 0.35f);
render->render_text(name_x, em, unit->name, render::ALIGN_LEFT_BOTTOM, sf::Color::White);
}
void state_t::render_to(render::state_t *render)
{
size_t w = window->getSize().x, h = window->getSize().y;
rectf_t rect;
v2f_t x;
std::stringstream ss;
v2f_t avatar_size;
bool large_avatars = true;
size_t row = 0;
if (select.selecting) {
sf::Color color;
switch (select.type) {
case SELECT_NEW:
color = sf::Color(60, 60, 150);
break;
case SELECT_OR:
color = sf::Color(60, 150, 60);
break;
case SELECT_XOR:
color = sf::Color(150, 150, 60);
break;
}
render->render_hlrect(select.rect, color);
}
window->setView(sf::View(sf::FloatRect(0, 0, w, h)));
em = std::max(w, h) * 0.013;
if (em < 15.0f)
em = 15.0f;
avatar_size = v2f_t(3.0f/5.0f, 1.0f) * 14.0f;
if (game->selected_units.size() > 8)
large_avatars = false;
if (game->selected_units.size() > 4)
avatar_size /= (game->selected_units.size() - 5.0f) * 0.1f + 1.0f;
x = v2f_t(w, h) - avatar_size * em;
for (unit_t *unit : game->selected_units) {
render_avatar(render, unit, x, avatar_size, em, large_avatars);
row++;
if (row >= 4) {
x[0] = w - avatar_size[0] * em;
x[1] -= avatar_size[1] * em;
row = 0;
} else
x[0] -= avatar_size[0] * em;
}
pie_menu.render_to(render);
for (auto i = log.begin(); i != log.end(); ) {
if (i->time + 3 < game->now)
i = log.erase(i);
else
i++;
}
rect = rectf_t(v2f_t(0.0f, 0.0f), v2f_t(w, 1.1f * em));
render->render_rect(rect, sf::Color(0, 0, 0, 140));
rect = rectf_t(v2f_t(0.0f, 0.0f), 1.1f * v2f_t(em, em));
render->render(0.0, &assets::ui.icon_crystals, rect, sf::Color::White);
x = v2f_t(1.1f * em, 0.0f);
render->render_text(x, em, std::to_string(game->crystals), render::ALIGN_LEFT_TOP, sf::Color::White);
x = v2f_t(w - 0.3f * em, 0.0f);
if (game->paused)
ss << "PAUSED ";
ss << game->time / SEC(60);
ss << ":";
ss << std::setw(2) << std::setfill('0') << game->time / SEC(1) % 60 << std::setw(0);
render->render_text(x, em, ss.str(), render::ALIGN_RIGHT_TOP, sf::Color::White);
x = v2f_t(0.0f, rect[1][1]);
for (log_entry_t &entry : log) {
render->render_text(x, em, entry.text, render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] += em;
}
x = v2f_t(0.0f, h - em * 1.5);
ss.str(std::string());
ss << std::fixed << std::setprecision(3);
ss << "Game t/F/B: " << game->time * 1.0e-9 << "/" << game->frames << "/" << game->frames_behind;
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] -= em;
ss.str(std::string());
ss << std::fixed << std::setprecision(1);
ss << "T/e/t: ";
ss << fc_traces.read(SEC(1)) << "/";
ss << fc_trace_ents.read(SEC(1)) << "/";
ss << fc_trace_tiles.read(SEC(1));
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] -= em;
ss.str(std::string());
ss << std::fixed << std::setprecision(1);
ss << "GE/e: ";
ss << fc_get_entities.read(SEC(1)) << "/";
ss << fc_get_entities_ents.read(SEC(1));
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] -= em;
ss.str(std::string());
ss << "FPS: " << fc_render.read(SEC(1)) << ", " << fc_game.read(SEC(1)) << " Hz";
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] -= em;
ss.str(std::string());
ss << "View S/T/E: ";
ss << render->stats.sectors << "/";
ss << render->stats.tiles << "/";
ss << render->stats.entities;
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] -= em;
ss.str(std::string());
ss << "Awake: " << game->awake_entities.size() << "/" << game->world.stats.entities;
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
x[1] -= em;
ss.str(std::string());
ss << "World S/T:";
ss << game->world.stats.sectors << "/";
ss << game->world.stats.tiles;
#ifdef CONFIG_SHOW_RSS
ss << ", " << sys_get_rss() / 1000000.0f;
ss << "MB RSS";
#endif
render->render_text(x, em, ss.str(), render::ALIGN_LEFT_TOP, sf::Color::White);
}
} // namespace interface