From 6966908022e36df9fb4c5e2233603a6fef18e97d Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Wed, 28 Mar 2018 14:17:47 +0200 Subject: Pie menu; start redoing shooting mechanics. --- src/common.hpp | 33 +++++++++++++++- src/game/game.cpp | 66 +++++++++++++++++++++++++++---- src/game/game.hpp | 12 +++++- src/game/interface.cpp | 105 ++++++++++++++++++++++++++++++++++++++++++++++++- src/game/text.cpp | 87 +++++----------------------------------- src/game/units.cpp | 68 +++++++++++++++++++++++--------- src/math.hpp | 5 +++ src/render.cpp | 33 ++++++++++++++++ 8 files changed, 301 insertions(+), 108 deletions(-) diff --git a/src/common.hpp b/src/common.hpp index c45836a..8c2f21b 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -221,6 +221,14 @@ namespace world { namespace interface { class state_t; + + typedef struct { + std::string label; + int action; + + float r0, r1; + float t0, t1; + } pie_item_t; } namespace game { @@ -267,7 +275,8 @@ namespace game { // These are called by the interface. void select(rectf_t rect); - void command(v2f_t x); + bool populate_pie_menu(std::vector &items); + void command(v2f_t x, int number); void spawn_soldier(v2f_t x); size_t roll(die_t die); @@ -275,6 +284,24 @@ namespace game { } namespace interface { + class pie_menu_t { + v2f_t x, x_world; + float radius; + pie_item_t *selected = nullptr; + + protected: + friend state_t; + std::vector items; + + public: + bool is_open = false; + + void open(v2f_t wmouse, v2f_t mouse); + void close(game::state_t *game); + void update(v2f_t mouse); + void render_to(render::state_t *render); + }; + class state_t { sf::RenderWindow *window; game::state_t *game; @@ -299,6 +326,8 @@ namespace interface { rectf_t rect; } select; + pie_menu_t pie_menu; + typedef struct { double time; std::string text; @@ -389,6 +418,8 @@ namespace render { void render_rect(rectf_t rect, sf::Color color); void render_hlrect(rectf_t rect, sf::Color color); void render_line(v2f_t x0, v2f_t x1, sf::Color color); + void render_circle(v2f_t x, float r, sf::Color color); + void render_ring_sect(v2f_t x, float r0, float r1, float t0, float t1, sf::Color color); void debug_path(v2f_t x, std::list *path); diff --git a/src/game/game.cpp b/src/game/game.cpp index ccd133d..79685cb 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -131,15 +131,48 @@ void state_t::select(rectf_t x) group_say(text::get(text::SAY_READY_GROUP)); } -void state_t::command(v2f_t x) +enum { + COMMAND_MOVE, + COMMAND_FIRE, + COMMAND_STOP +}; + +bool state_t::populate_pie_menu(std::vector &items) +{ + items.clear(); + + if (selected_units.size() == 0) + return false; + + 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}); + return true; +} + +void state_t::command(v2f_t x, int number) { v2f_t snap; + bool group; + + if (!selected_units.size()) + return; snap[0] = std::round(x[0] - 0.5f) + 0.5f; snap[1] = std::round(x[1] - 0.5f) + 0.5f; - if (selected_units.size() > 1) + group = selected_units.size() > 1; + if (group) switch (number) { + case COMMAND_MOVE: group_say(text::get(text::SAY_MOVING_GROUP)); + break; + case COMMAND_STOP: + group_say(text::get(text::SAY_STOPPING_GROUP)); + break; + case COMMAND_FIRE: + group_say(text::get(text::SAY_FIRING_GROUP)); + break; + } for (unit_t *unit : selected_units) { unit_soldier_t *soldier; @@ -155,13 +188,30 @@ void state_t::command(v2f_t x) if (!soldier->controllable) continue; - if (!soldier->start_moving(snap)) - soldier->say(text::get(text::SAY_NO_PATH)); - else { - soldier->move_marker = std::make_unique(this, soldier->move.path.back()); + switch (number) { + case COMMAND_MOVE: + if (!soldier->start_moving(snap)) + soldier->say(text::get(text::SAY_NO_PATH)); + else { + soldier->move_marker = std::make_unique(this, soldier->move.path.back()); + if (!group) + soldier->say(text::get(text::SAY_MOVING)); + } + break; - if (selected_units.size() == 1) - soldier->say(text::get(text::SAY_MOVING)); + case COMMAND_STOP: + soldier->stop_moving(); + soldier->manual_firing = false; + if (!group) + soldier->say(text::get(text::SAY_STOPPING)); + break; + + case COMMAND_FIRE: + soldier->manual_firing = true; + soldier->manual_firing_target = x; + if (!group) + soldier->say(text::get(text::SAY_FIRING)); + break; } } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 025aa12..848ba92 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -96,8 +96,7 @@ namespace game { namespace text { typedef enum { - LANG_ENGLISH, - LANG_POLISH + LANG_ENGLISH } language_t; extern language_t language; @@ -113,6 +112,10 @@ namespace game { SAY_READY_GROUP, SAY_MOVING, SAY_MOVING_GROUP, + SAY_STOPPING, + SAY_STOPPING_GROUP, + SAY_FIRING, + SAY_FIRING_GROUP, SAY_PANIC, UNIT_NAME_SPIDER, UNIT_NAME_SOLDIER, @@ -195,6 +198,7 @@ namespace game { bool keep_moving(double speed); bool start_moving(v2f_t dst); + void stop_moving(void); bool dead = false; double death_time = -INFINITY; @@ -230,7 +234,11 @@ namespace game { double panic_end; double panic_turn; + bool manual_firing = false; + v2f_t manual_firing_target; + void check_area(void); + void shoot(v2f_t aim); void target_and_attack(void); public: diff --git a/src/game/interface.cpp b/src/game/interface.cpp index 262c3ae..d3ef661 100644 --- a/src/game/interface.cpp +++ b/src/game/interface.cpp @@ -51,6 +51,69 @@ void state_t::stop_following(void) print(text::get(text::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; + } + } +} + void state_t::tick(double dt) { vec_t window_size; @@ -59,6 +122,7 @@ void state_t::tick(double dt) v2f_t view_size; v2f_t follow_center(0, 0), view_center, pan_delta; float view_scale; + sf::Vector2i mouse; v2f_t wmouse; // Mouse position in world space; window_size = window->getSize(); @@ -101,6 +165,7 @@ void state_t::tick(double dt) window->setView(sf::View(view_center, view_size)); } + mouse = sf::Mouse::getPosition(*window); wmouse = window->mapPixelToCoords(sf::Mouse::getPosition(*window)); while (window->pollEvent(event)) { @@ -119,7 +184,8 @@ void state_t::tick(double dt) break; case sf::Mouse::Button::Right: - game->command(wmouse); + if (game->populate_pie_menu(pie_menu.items)) + pie_menu.open(wmouse, mouse); break; case sf::Mouse::Button::Middle: @@ -139,6 +205,10 @@ void state_t::tick(double dt) 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; @@ -207,6 +277,8 @@ void state_t::tick(double dt) if (select.selecting) select.rect[1] = wmouse; + + pie_menu.update(mouse); } void state_t::print(std::string str) @@ -215,6 +287,35 @@ void state_t::print(std::string str) std::cout << str << std::endl; } +void pie_menu_t::render_to(render::state_t *render) +{ + 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_text(center, 15, i.label, render::text_align_t::ALIGN_CENTER_BOTTOM, color); + } +} + void state_t::render_to(render::state_t *render) { size_t w = window->getSize().x, h = window->getSize().y; @@ -228,6 +329,8 @@ void state_t::render_to(render::state_t *render) window->setView(sf::View(sf::FloatRect(0, 0, w, h))); em = std::max(w, h) * 0.017; + pie_menu.render_to(render); + for (auto i = log.begin(); i != log.end(); ) { if (i->time + 3 < game->now) i = log.erase(i); diff --git a/src/game/text.cpp b/src/game/text.cpp index 7e90edf..75fb032 100644 --- a/src/game/text.cpp +++ b/src/game/text.cpp @@ -56,8 +56,16 @@ static std::string get_english(index_t index) case SAY_MOVING_GROUP: return "On our way."; + case SAY_STOPPING: + case SAY_STOPPING_GROUP: + return "Stopping."; + + case SAY_FIRING: + case SAY_FIRING_GROUP: + return "Firing!"; + case SAY_PANIC: - return "I'm not getting paid enough for this."; + return "I'm not getting paid enough for this!"; case UNIT_NAME_SPIDER: return "Spider"; @@ -100,89 +108,12 @@ static std::string get_english(index_t index) } } -static std::string get_polish(index_t index) -{ - switch (index) { - case PAUSED: - return "WSTRZYMANO"; - - case UNPAUSED: - return "WZNOWIONO"; - - case FOLLOWING_ON: - return "Podążanie: włączone."; - - case FOLLOWING_OFF: - return "Podążanie: wyłączone."; - - case SAY_GROUP: - return "Oddział"; - - case SAY_NO_PATH: - return "Nie mogę się tam dostać."; - - case SAY_READY: - case SAY_READY_GROUP: - return "Gotowy na rozkaz."; - - case SAY_MOVING: - return "Jestem w drodze."; - - case SAY_MOVING_GROUP: - return "W drodze."; - - case SAY_PANIC: - return "Za mało mi za to płacą."; - - case UNIT_NAME_SPIDER: - return "Pająk"; - - case UNIT_NAME_SOLDIER: - return soldier_names[rand() % COUNT(soldier_names)]; - - case UNIT_NAME_NEST: - return "Gniazdo"; - - case UNIT_DEATH: - return "nie żyje"; - - case UNIT_ATTACK: - return "atakuje"; - - case UNIT_MISS: - return "chybienie"; - - case UNIT_CRITICAL_MISS: - return "chybienie krytyczne"; - - case UNIT_CRITICAL_HIT: - return "trafienie krytyczne"; - - case UNIT_DAMAGE: - return "zadaje obrażenia"; - - case UNIT_SAVING_THROW_WILLPOWER: - return "wykonuje rzut obronny na siłę woli"; - - case UNIT_SAVING_THROW_SUCCESS: - return "sukces"; - - case UNIT_SAVING_THROW_FAILURE: - return "porażka"; - - default: - abort(); - } -} std::string get(index_t index) { switch (language) { case LANG_ENGLISH: return get_english(index); - case LANG_POLISH: - return get_polish(index); - default: abort(); } diff --git a/src/game/units.cpp b/src/game/units.cpp index a0dcee3..f3d53d7 100644 --- a/src/game/units.cpp +++ b/src/game/units.cpp @@ -188,6 +188,12 @@ bool unit_t::start_moving(v2f_t dst) return true; } +void unit_t::stop_moving(void) +{ + move.path.clear(); + move.moving = false; +} + void unit_t::damage(int points, unit_t *attacker) { fx_blood_t *blood; @@ -316,14 +322,52 @@ void unit_soldier_t::check_area(void) } } -void unit_soldier_t::target_and_attack(void) +static v2f_t spread_aim(v2f_t aim, float cof, procgen::prng_t *prng) { - unit_t *target; + float t; + + t = prng->next_float(-cof / 2, cof / 2); + + return v2f_t(cos(t) * aim[0] - sin(t) * aim[1], + sin(t) * aim[0] + cos(t) * aim[1]); +} + +void unit_soldier_t::shoot(v2f_t aim) +{ + v2f_t end; world::trace_t trace; v2f_t muzzle_point; fx_tracer_t *tracer; fx_flash_t *flash; + end = x + (aim - x).norm() * 40; + + trace = world->trace(x, end, CF_SOLID); + + muzzle_point = x + v2f_t(0, -1.0f); + + tracer = new fx_tracer_t(game, muzzle_point, trace.end); + tracer->place(&game->world); + + flash = new fx_flash_t(game, muzzle_point, 5.0f); + flash->place(&game->world); + + last_attack = game->now; + assets::soldier.fire.play(); + //target->damage(3, this); FIXME +} + +void unit_soldier_t::target_and_attack(void) +{ + unit_t *target; + v2f_t aim; + + if (manual_firing) { + aim = manual_firing_target; + last_target_x = aim; + goto skip_targetting; + } + if (game->now < next_targetting) return; @@ -332,28 +376,16 @@ void unit_soldier_t::target_and_attack(void) target = find_target(world, x, 5.0f, false); if (!target) return; + aim = target->x; last_target_time = game->now; last_target_x = target->x; +skip_targetting: if (last_attack + game->dice_prng.next_float(1.4f, 1.6f) > game->now) return; - trace = world->trace(x, target->x, CF_SOLID); - if (trace.hit) - return; - - muzzle_point = x + v2f_t(0, -1.0f); - - tracer = new fx_tracer_t(game, muzzle_point, target->x); - tracer->place(&game->world); - - flash = new fx_flash_t(game, muzzle_point, 5.0f); - flash->place(&game->world); - - last_attack = game->now; - assets::soldier.fire.play(); - target->damage(3, this); + shoot(spread_aim(aim, 0.2, &game->dice_prng)); } void unit_soldier_t::on_think(void) @@ -465,7 +497,7 @@ void unit_soldier_t::render_to(render::state_t *render) else legs = &assets::soldier.legs_idle; - if (!panic && last_target_time + 3 > game->now) { + if (!panic && (manual_firing || last_target_time + 3 > game->now)) { if (last_attack + 0.1 > game->now) body = &assets::soldier.body_firing; else diff --git a/src/math.hpp b/src/math.hpp index e0b8f1b..dc077e1 100644 --- a/src/math.hpp +++ b/src/math.hpp @@ -254,6 +254,11 @@ public: return *this / len(); } + static vec_t rad(float t) + { + return vec_t(cos(t), sin(t)); + } + // Compatibility with SFML template diff --git a/src/render.cpp b/src/render.cpp index 8d4949b..c3266ef 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -402,6 +402,39 @@ void state_t::render_line(v2f_t x0, v2f_t x1, sf::Color color) window->draw(line, 2, sf::Lines); } +void state_t::render_circle(v2f_t x, float r, sf::Color color) +{ + sf::CircleShape circle; + circle.setRadius(r); + circle.setPosition(x - v2f_t(r, r)); + circle.setFillColor(color); + window->draw(circle); +} + +void state_t::render_ring_sect(v2f_t x, float r0, float r1, float t0, float t1, + sf::Color color) +{ + sf::VertexArray array(sf::TriangleStrip); + float t; + bool done = false; + + for (t = t0; !done; t += 0.1f) { + v2f_t rad; + + if (t > t1) { + t = t1; + done = true; + } + + rad = v2f_t::rad(t); + array.append(sf::Vertex(x + rad * r0, color)); + array.append(sf::Vertex(x + rad * r1, color)); + } + + + window->draw(array); +} + void state_t::debug_path(v2f_t x, std::list *path) { sf::Vertex line[2]; -- cgit