summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common.hpp33
-rw-r--r--src/game/game.cpp66
-rw-r--r--src/game/game.hpp12
-rw-r--r--src/game/interface.cpp105
-rw-r--r--src/game/text.cpp87
-rw-r--r--src/game/units.cpp68
-rw-r--r--src/math.hpp5
-rw-r--r--src/render.cpp33
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<interface::pie_item_t> &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<pie_item_t> 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<v2f_t> *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<interface::pie_item_t> &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<fx_move_marker_t>(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<fx_move_marker_t>(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<int, 2> 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<float, 2> rad(float t)
+ {
+ return vec_t<float, 2>(cos(t), sin(t));
+ }
+
// Compatibility with SFML
template <typename U>
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<v2f_t> *path)
{
sf::Vertex line[2];