From 84a648723674934ef46e1799404d778474a74aeb Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Fri, 13 Apr 2018 11:30:01 +0200 Subject: Explosions, better AI and misc refactoring. --- src/common.hpp | 5 ++++ src/game/assets.cpp | 4 ++++ src/game/effects.cpp | 38 ++++++++++++++++++++---------- src/game/game.cpp | 60 +++++++++++++++++++++++++++++++++++++----------- src/game/game.hpp | 36 ++++++++++++++++++++++------- src/game/interface.cpp | 4 ++-- src/game/unit_nest.cpp | 1 + src/game/unit_repl.cpp | 2 ++ src/game/unit_spider.cpp | 27 +++++++++++++++++----- src/game/units.cpp | 36 ++++++++++++++++++++++++++++- src/render.cpp | 3 +++ 11 files changed, 173 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/common.hpp b/src/common.hpp index e6879ca..f4ce9ff 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -44,6 +44,7 @@ extern bool debug_AI; typedef uint64_t ntime_t; ntime_t nclock(void); +#define SEC(x) ((ntime_t)x * 1000000000) #define MSEC(x) ((ntime_t)x * 1000000) class freq_counter_t { @@ -306,6 +307,8 @@ namespace game { procgen::prng_t prng; std::unordered_set awake_entities; std::unordered_set selected_units; + // Some deletes have to be deferred to the next frame. + std::unordered_set deletion_list; ntime_t time = 0; // game time double now, dt; // FIXME: refactor the code, use ntime_t everywhere @@ -322,6 +325,8 @@ namespace game { void pause(void); void resume(void); + void explosion(v2f_t x); + // These are called by the interface. void select(rectf_t rect, int type); bool populate_pie_menu(std::vector &items); diff --git a/src/game/assets.cpp b/src/game/assets.cpp index 553cafd..2009672 100644 --- a/src/game/assets.cpp +++ b/src/game/assets.cpp @@ -67,6 +67,10 @@ void load(void) fx.blood.load("assets/units/blood_", 4); fx.flash.load("assets/units/flash_", 1); + fx.explosion.load("assets/fx/explosion_", 7); + fx.explosion_sound.load("assets/fx/explosion.ogg"); + fx.explosion_sound.volume = 3.0f; + deco.stone.load("assets/deco/stone_", 1); deco.eyething.load("assets/deco/eyething_", 2); deco.spike.load("assets/deco/spike_", 1); diff --git a/src/game/effects.cpp b/src/game/effects.cpp index a398527..6f92f17 100644 --- a/src/game/effects.cpp +++ b/src/game/effects.cpp @@ -26,7 +26,7 @@ effect_t::effect_t(state_t *game_) : game::entity_t(game_, ET_EFFECT) void effect_t::on_think(void) { if (game->now >= ttl) - destroy(); + delete this; } fx_tracer_t::fx_tracer_t(state_t *game_, v2f_t x0_, v2f_t x1_) : effect_t(game_) @@ -39,6 +39,7 @@ fx_tracer_t::fx_tracer_t(state_t *game_, v2f_t x0_, v2f_t x1_) : effect_t(game_) render_bounds[0] = x0; render_bounds[1] = x1; render_bounds = render_bounds.norm(); + render_layer = render::LAYER_TOP; cmodel.bounds = render_bounds; cmodel.cflags = 0; @@ -72,6 +73,7 @@ fx_flash_t::fx_flash_t(state_t *game_, v2f_t x_, float radius_) : effect_t(game_ render_bounds[0] = x - v2f_t(radius, radius); render_bounds[1] = x + v2f_t(radius, radius); render_bounds = render_bounds.norm(); + render_layer = render::LAYER_TOP; cmodel.bounds = render_bounds; cmodel.cflags = 0; @@ -127,12 +129,6 @@ fx_move_marker_t::fx_move_marker_t(state_t *game_, v2f_t x_) : effect_t(game_) link(&game->world); } -fx_move_marker_t::~fx_move_marker_t(void) -{ - unlink(); - sleep(); -} - void fx_move_marker_t::render_to(render::state_t *render) { render->render(game->now * 2, &assets::move_marker, render_bounds, sf::Color::White); @@ -151,15 +147,33 @@ fx_aim_marker_t::fx_aim_marker_t(state_t *game_, v2f_t x_) : effect_t(game_) link(&game->world); } -fx_aim_marker_t::~fx_aim_marker_t(void) +void fx_aim_marker_t::render_to(render::state_t *render) { - unlink(); - sleep(); + render->render(game->now * 2, &assets::aim_marker, render_bounds, sf::Color::White); } -void fx_aim_marker_t::render_to(render::state_t *render) +fx_explosion_t::fx_explosion_t(state_t *game_, v2f_t x_) : effect_t(game_) { - render->render(game->now * 2, &assets::aim_marker, render_bounds, sf::Color::White); + ttl = game->now + 1.3; + + x = x_; + + render_bounds[0] = x + v2f_t(-1.0, -2.5); + render_bounds[1] = x + v2f_t(1.0, 0.5); + render_bounds = render_bounds.norm(); + cmodel.bounds = render_bounds; + cmodel.cflags = 0; + + ignore_waking = true; + wake(); + + assets::fx.explosion_sound.play_3d(x); +} + +void fx_explosion_t::render_to(render::state_t *render) +{ + double phase = (game->now - ttl) / 1.3f; + render->render(phase, &assets::fx.explosion, render_bounds, sf::Color::White); } } // namespace game diff --git a/src/game/game.cpp b/src/game/game.cpp index 66bf237..6e8ea78 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -19,6 +19,12 @@ along with Minitrem. If not, see . 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_) @@ -28,13 +34,8 @@ entity_t::entity_t(game::state_t *game_, int type_) : world::entity_t(type_) entity_t::~entity_t(void) { -} - -void entity_t::destroy(void) -{ + sleep(); unlink(); - game->awake_entities.erase(this); - delete this; } void entity_t::place(world::world_t *world_) @@ -48,9 +49,6 @@ void entity_t::place(world::world_t *world_) if (do_spawn) on_spawn(); - - if (!ignore_waking) - wake(); } void entity_t::place(world::world_t *world, v2f_t x_) @@ -168,7 +166,7 @@ enum { COMMAND_FIRE, COMMAND_STOP, - COMMAND_REPL + COMMAND_REPL, }; bool state_t::populate_pie_menu(std::vector &items) @@ -303,11 +301,16 @@ void state_t::tick(ntime_t time_) 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) - ent->on_think(); + if (awake_entities.find(ent) != awake_entities.end()) + ent->on_think(); frames++; frames_since_t0++; @@ -384,10 +387,39 @@ void state_t::compute_ambience(render::state_t *render) } } -bool load_assets(void) +void state_t::explosion(v2f_t x) { - assets::load(); - return true; + 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; + unit_t *unit; + 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; + + unit = dynamic_cast(trace.ent); + + if (unit->dead) + continue; + + damage = pow(1 - trace.frac, 2.0f) * 40.0f; + printf("frac=%f, damage=%i\n", trace.frac, damage); + unit->damage(damage, nullptr); + } } } //namespace game diff --git a/src/game/game.hpp b/src/game/game.hpp index 4773f9c..6c71736 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -80,7 +80,8 @@ namespace game { } repl_assets_t; typedef struct { - render::animated_texture_t blood, flash; + render::animated_texture_t blood, flash, explosion; + audio::sound_t explosion_sound; } fx_assets_t; typedef struct { @@ -138,7 +139,6 @@ namespace game { entity_t(game::state_t *game, int type_); virtual ~entity_t(void) = 0; - void destroy(void); void place(world::world_t *world); void place(world::world_t *world, v2f_t x_); @@ -158,6 +158,7 @@ namespace game { class fx_aim_marker_t; unit_t *find_target(world::world_t *world, v2f_t x, float r, bool friendly); + void hivemind_attack(unit_t *source, v2f_t target); class unit_t : public entity_t { protected: @@ -212,7 +213,11 @@ namespace game { void damage(int points, unit_t *attacker); void try_attack(unit_t *target); void die(unit_t *killer); - virtual void on_death() = 0; + + virtual void on_damage(unit_t *attacker) = 0; + virtual void on_death(void) = 0; + + bool have_target = false; std::string say_text; double say_time = -INFINITY; @@ -247,6 +252,7 @@ namespace game { void on_think(void); void on_spawn(void) {}; void on_wake(void) {}; + void on_damage(unit_t *attacker) {}; void on_death(void); }; @@ -261,7 +267,8 @@ namespace game { void on_think(void); void on_spawn(void) {}; - void on_wake(void); + void on_wake(void) {}; + void on_damage(unit_t *attacker); void on_death(void); }; @@ -276,8 +283,9 @@ namespace game { void on_think(void); void on_spawn(void); - void on_death(void); void on_wake(void) {}; + void on_damage(unit_t *attacker) {}; + void on_death(void); }; class unit_repl_t : public unit_t { @@ -289,8 +297,10 @@ namespace game { void on_think(void); void on_spawn(void); - void on_death(void); void on_wake(void) {}; + void on_damage(unit_t *attacker) {}; + void on_death(void); + void activate(void); }; @@ -345,7 +355,7 @@ namespace game { public: fx_move_marker_t(game::state_t *game_, v2f_t x_); - ~fx_move_marker_t(void); + ~fx_move_marker_t(void) {}; void render_to(render::state_t *render); }; @@ -355,7 +365,17 @@ namespace game { public: fx_aim_marker_t(game::state_t *game_, v2f_t x_); - ~fx_aim_marker_t(void); + ~fx_aim_marker_t(void) {}; + + void render_to(render::state_t *render); + }; + + class fx_explosion_t : public effect_t { + v2f_t x; + + public: + fx_explosion_t(game::state_t *game_, v2f_t x_); + ~fx_explosion_t(void) {}; void render_to(render::state_t *render); }; diff --git a/src/game/interface.cpp b/src/game/interface.cpp index d249f38..c366fa8 100644 --- a/src/game/interface.cpp +++ b/src/game/interface.cpp @@ -129,7 +129,7 @@ void state_t::tick(double dt) sf::Event event; v2f_t view_size; - v2f_t follow_center(0, 0), view_center, pan_delta; + 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; @@ -139,8 +139,8 @@ void state_t::tick(double dt) 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[1] = view_scale; 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]; diff --git a/src/game/unit_nest.cpp b/src/game/unit_nest.cpp index 163a585..cae98f5 100644 --- a/src/game/unit_nest.cpp +++ b/src/game/unit_nest.cpp @@ -26,6 +26,7 @@ unit_nest_t::unit_nest_t(game::state_t *game_) : unit_t(game_, UNIT_NEST) size[1] = {+0.6f, +0.2f}; render_size[0] = {-0.6f, -0.6f}; render_size[1] = {+0.6f, +0.6f}; + render_layer = render::LAYER_NORMAL; cmodel.cflags = CF_BODY; name = text::get(text::UNIT_NAME_NEST); diff --git a/src/game/unit_repl.cpp b/src/game/unit_repl.cpp index 8bfd835..e11d204 100644 --- a/src/game/unit_repl.cpp +++ b/src/game/unit_repl.cpp @@ -45,6 +45,8 @@ void unit_repl_t::on_spawn(void) void unit_repl_t::on_death(void) { + game->explosion(x); + delete this; } void unit_repl_t::render_to(render::state_t *render) diff --git a/src/game/unit_spider.cpp b/src/game/unit_spider.cpp index 9ff8fc1..df9073c 100644 --- a/src/game/unit_spider.cpp +++ b/src/game/unit_spider.cpp @@ -33,6 +33,8 @@ unit_spider_t::unit_spider_t(game::state_t *game) : unit_t(game, UNIT_SPIDER) ignore_waking = false; health = max_health = 4; + + sleep(); } void unit_spider_t::target_and_attack(void) @@ -45,12 +47,12 @@ void unit_spider_t::target_and_attack(void) target = find_target(world, x, 10.0f, true); if (!target) { - if (health < max_health) - random_walk(); - + have_target = false; return; } + have_target = true; + wake_time = game->time; start_moving(target->x); next_targetting = game->now + game->prng.next_float(0.2f, 0.4f); @@ -72,18 +74,31 @@ void unit_spider_t::on_think(void) { target_and_attack(); - keep_moving(4.0); + if (have_target || game->now - wake_time < 10.0) { + if (!have_target && !move.moving) + random_walk(); - if (!move.moving && wake_time + 5 < game->now) + keep_moving(4.0); + } else sleep(); } -void unit_spider_t::on_wake(void) +void unit_spider_t::on_damage(unit_t *attacker) { + if (!attacker) + return; + + hivemind_attack(this, attacker->x); } void unit_spider_t::on_death(void) { + if (health < -5) { + assets::soldier.gib_sound.play_3d(x); + game->deletion_list.insert(this); + return; + } + render_layer = render::LAYER_FLAT; cmodel.cflags = CF_BACKGROUND; } diff --git a/src/game/units.cpp b/src/game/units.cpp index fb0ce3c..6bce474 100644 --- a/src/game/units.cpp +++ b/src/game/units.cpp @@ -209,8 +209,10 @@ void unit_t::damage(int points, unit_t *attacker) health -= points; if (health <= 0) die(attacker); - else + else { wake(); + on_damage(attacker); + } } void unit_t::die(unit_t *killer) @@ -273,4 +275,36 @@ void unit_t::random_walk(void) move.random_walk_time = game->time; } +void hivemind_attack(unit_t *source, v2f_t target) +{ + rectf_t search; + std::list ents; + + search[0] = source->x - v2f_t(5, 5); + search[1] = source->x + v2f_t(5, 5); + + ents = source->game->world.get_entities(search, CF_BODY_SMALL); + + for (world::entity_t *ent : ents) { + unit_t *unit; + + if (ent->type != ET_UNIT) + continue; + + unit = dynamic_cast(ent); + + if (unit->type != unit_t::UNIT_SPIDER) + continue; + + if ((unit->x - target).len() > 25.0f) + continue; + + if (unit->have_target) + continue; + + unit->wake(); + unit->start_moving(target); + } +} + } // namespace game diff --git a/src/render.cpp b/src/render.cpp index 98c12a2..185e5e6 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -390,6 +390,9 @@ void state_t::render_text(v2f_t x, float height, std::string str, offset[0] = -rect.width; offset[1] = -rect.height; break; + + default: + abort(); } offset *= scale; -- cgit