/* 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; 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.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