summaryrefslogtreecommitdiff
path: root/sim/src
diff options
context:
space:
mode:
authorAki <please@ignore.pl>2022-12-03 00:44:07 +0100
committerAki <please@ignore.pl>2022-12-03 00:44:07 +0100
commit18a763bcb19c5ece4b7b7d079dab07a1d915deb6 (patch)
tree137278522c98f5cb5cf4067886444b20b3eaf82d /sim/src
parent18eba7f30381c05ee1c03bec5f537ec7d4dc9815 (diff)
downloadkurator-18a763bcb19c5ece4b7b7d079dab07a1d915deb6.zip
kurator-18a763bcb19c5ece4b7b7d079dab07a1d915deb6.tar.gz
kurator-18a763bcb19c5ece4b7b7d079dab07a1d915deb6.tar.bz2
Moved battles module files to sim
Diffstat (limited to 'sim/src')
-rw-r--r--sim/src/BaseBattle.cpp162
-rw-r--r--sim/src/BaseBattle.h40
-rw-r--r--sim/src/Battle.cpp24
-rw-r--r--sim/src/Builder.cpp57
-rw-r--r--sim/src/Builder.h35
-rw-r--r--sim/src/Point.cpp72
-rw-r--r--sim/src/RandomSpawner.cpp42
-rw-r--r--sim/src/RandomSpawner.h30
-rw-r--r--sim/src/Scenario.cpp23
-rw-r--r--sim/src/Spawner.h21
-rw-r--r--sim/src/TeamManager.cpp62
-rw-r--r--sim/src/TeamManager.h31
-rw-r--r--sim/src/scenarios.cpp43
13 files changed, 642 insertions, 0 deletions
diff --git a/sim/src/BaseBattle.cpp b/sim/src/BaseBattle.cpp
new file mode 100644
index 0000000..33e0957
--- /dev/null
+++ b/sim/src/BaseBattle.cpp
@@ -0,0 +1,162 @@
+#include "BaseBattle.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include <entt/entity/registry.hpp>
+#include <entt/signal/dispatcher.hpp>
+
+#include <kurator/battles/components.h>
+#include <kurator/battles/events.h>
+#include <kurator/battles/Scenario.h>
+#include <kurator/universe.h>
+
+#include "Builder.h"
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+BaseBattle::BaseBattle(const Scenario& scenario) :
+ _registry {},
+ spawner {scenario.total_teams(), 12000, 0.1}
+{
+ const auto repo = universe::load_sample();
+ Builder build {_registry, spawner};
+ for (const auto& ship : scenario.ships) {
+ const auto entity = build(repo->ship_type(ship.type), ship.team);
+ for (const auto& turret_type : ship.turrets)
+ build(repo->turret_type(turret_type), entity);
+ manager.add(ship.team, entity); // registry supports on construction events
+ }
+}
+
+
+entt::registry&
+BaseBattle::registry()
+{
+ return _registry;
+}
+
+
+entt::dispatcher&
+BaseBattle::dispatcher()
+{
+ return _dispatcher;
+}
+
+
+void
+BaseBattle::update(const float dt)
+{
+ pick_random_targets();
+ keep_at_range();
+ floating_movement(dt);
+ turrets(dt);
+ kill_off_dead();
+ manager.clear(_registry); // registry supports on destructions events
+}
+
+
+void
+BaseBattle::pick_random_targets()
+{
+ auto view = _registry.view<Team, AIState>();
+ for (auto&& [entity, team, ai] : view.each()) {
+ if (!_registry.valid(ai.target))
+ ai.target = manager.random((team.id + 1) % 2); // FIXME
+ }
+}
+
+
+void
+BaseBattle::keep_at_range()
+{
+ auto view = _registry.view<Transform, AIState>();
+ for (auto&& [entity, self, ai] : view.each()) {
+ if (!_registry.valid(ai.target))
+ continue;
+ const auto target = _registry.get<Transform>(ai.target);
+ const Point offset = target.position - self.position;
+ ai.destination = target.position - offset.normalized().scale(6000.0);
+ }
+}
+
+
+void
+BaseBattle::floating_movement(const float dt)
+{
+ auto view = _registry.view<Transform, FloatingMovement, AIState>();
+ for (auto&& [entity, transform, movement, ai] : view.each()) {
+ const auto offset = ai.destination - transform.position;
+ const auto at_destination = offset.magnitude() > movement.destination_boundary;
+ const auto acceleration =
+ at_destination ?
+ offset.normalized().scale(movement.acceleration * dt) :
+ offset.normalized().scale(-1 * movement.deceleration * dt);
+ movement.speed.x += acceleration.x;
+ movement.speed.y += acceleration.y;
+ if (movement.speed.magnitude() > movement.max_speed)
+ movement.speed = movement.speed.normalized().scale(movement.max_speed);
+ const auto speed = movement.speed.scale(dt);
+ transform.position.x += speed.x;
+ transform.position.y += speed.y;
+ transform.angle = speed.angle();
+ }
+}
+
+
+double
+effective_damage(const universe::TurretType& def, const double distance)
+{
+ const auto overflow = distance - def.optimal_range;
+ const auto falloff = std::max(0.0, overflow / def.optimal_range / def.falloff_modifier);
+ return def.base_damage * std::round(std::pow(def.falloff_intensity, std::pow(falloff, 2)) * 1000) / 1000;
+}
+
+
+void
+BaseBattle::turrets(const float dt)
+{
+ auto view = _registry.view<TurretControl, universe::TurretType>();
+ for (auto&& [entity, control, def] : view.each()) {
+ if (!_registry.valid(control.owner)) {
+ _registry.destroy(entity);
+ continue;
+ }
+ const auto& [state, transform] = _registry.get<AIState, Transform>(control.owner); // no checks
+ if (!_registry.valid(state.target))
+ continue;
+ if (control.reload > 0.0)
+ control.reload -= dt;
+ if (control.reload <= 0.0) {
+ auto& target_points = _registry.get<HitPoints>(state.target);
+ const auto& target = _registry.get<Transform>(state.target);
+ const auto distance = transform.position - target.position;
+ const auto damage = effective_damage(def, distance.magnitude());
+ if (damage > 0.0) {
+ target_points.health -= damage;
+ _dispatcher.trigger(Hit{damage, entity, state.target});
+ }
+ control.reload = def.rate_of_fire;
+ }
+ }
+}
+
+
+void
+BaseBattle::kill_off_dead()
+{
+ auto view = _registry.view<HitPoints>();
+ for (auto&& [entity, points] : view.each()) {
+ if (points.health <= 0.0)
+ _registry.destroy(entity);
+ }
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/BaseBattle.h b/sim/src/BaseBattle.h
new file mode 100644
index 0000000..8240fd2
--- /dev/null
+++ b/sim/src/BaseBattle.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <entt/entity/registry.hpp>
+#include <entt/signal/dispatcher.hpp>
+
+#include <kurator/battles/Battle.h>
+#include <kurator/battles/Scenario.h>
+
+#include "RandomSpawner.h"
+#include "TeamManager.h"
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+class BaseBattle : public Battle
+{
+public:
+ BaseBattle(const Scenario& scenario);
+ entt::registry& registry() override;
+ entt::dispatcher& dispatcher() override;
+ void update(float dt) override;
+private:
+ entt::registry _registry;
+ entt::dispatcher _dispatcher;
+ RandomSpawner spawner;
+ TeamManager manager;
+ void pick_random_targets();
+ void keep_at_range();
+ void floating_movement(float dt);
+ void turrets(float dt);
+ void kill_off_dead();
+};
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/Battle.cpp b/sim/src/Battle.cpp
new file mode 100644
index 0000000..f44a88e
--- /dev/null
+++ b/sim/src/Battle.cpp
@@ -0,0 +1,24 @@
+#include <kurator/battles/Battle.h>
+
+#include <memory>
+
+#include <kurator/battles/Scenario.h>
+
+#include "BaseBattle.h"
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+std::unique_ptr<Battle>
+prepare(const Scenario& scenario)
+{
+ return std::make_unique<BaseBattle>(scenario);
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/Builder.cpp b/sim/src/Builder.cpp
new file mode 100644
index 0000000..d7cc893
--- /dev/null
+++ b/sim/src/Builder.cpp
@@ -0,0 +1,57 @@
+#include "Builder.h"
+
+#include <entt/entity/registry.hpp>
+
+#include <kurator/battles/components.h>
+#include <kurator/battles/Point.h>
+#include <kurator/universe/ShipType.h>
+#include <kurator/universe/TurretType.h>
+
+#include "Spawner.h"
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+Builder::Builder(entt::registry& _registry, Spawner& _spawner) :
+ registry {_registry},
+ spawner {_spawner}
+{
+}
+
+
+entt::entity
+Builder::operator()(const universe::ShipType& ship_type, const int team) const
+{
+ const auto entity = registry.create();
+ registry.emplace<universe::ShipType>(entity, ship_type);
+ registry.emplace<Team>(entity, team);
+ registry.emplace<Transform>(entity, spawner.get(team));
+ registry.emplace<FloatingMovement>(
+ entity,
+ ship_type.max_speed,
+ ship_type.max_speed * 2.0,
+ ship_type.max_speed * 3.0,
+ 100.0);
+ registry.emplace<AIState>(entity, Point{0.0, 0.0});
+ registry.emplace<HitPoints>(entity, ship_type.base_health_points);
+ return entity;
+}
+
+
+entt::entity
+Builder::operator()(const universe::TurretType& turret_type, const entt::entity& owner) const
+{
+ const auto entity = registry.create();
+ registry.emplace<universe::TurretType>(entity, turret_type);
+ registry.emplace<TurretControl>(entity, 0.0, owner);
+ registry.emplace<Transform>(entity, Point{0.0, 0.0}, 0.0, owner);
+ return entity;
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/Builder.h b/sim/src/Builder.h
new file mode 100644
index 0000000..df50c4e
--- /dev/null
+++ b/sim/src/Builder.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <entt/entity/registry.hpp>
+
+#include <kurator/universe/ShipType.h>
+#include <kurator/universe/TurretType.h>
+
+#include "Spawner.h"
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+class Builder
+{
+public:
+ Builder(entt::registry& _registry, Spawner& _spawner);
+ ~Builder() = default;
+ Builder(Builder&&) = delete;
+ Builder(const Builder&) = delete;
+ Builder& operator=(Builder&&) = delete;
+ Builder& operator=(const Builder&) = delete;
+ entt::entity operator()(const universe::ShipType& ship_type, int team) const;
+ entt::entity operator()(const universe::TurretType& turret_type, const entt::entity& owner) const;
+private:
+ entt::registry& registry;
+ Spawner& spawner;
+};
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/Point.cpp b/sim/src/Point.cpp
new file mode 100644
index 0000000..31aecae
--- /dev/null
+++ b/sim/src/Point.cpp
@@ -0,0 +1,72 @@
+#include <kurator/battles/Point.h>
+
+#include <cmath>
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+double
+Point::magnitude() const
+{
+ return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
+}
+
+
+double
+Point::distance(const Point& other) const
+{
+ return std::sqrt(std::pow(other.x - x, 2) + std::pow(other.y - y, 2));
+}
+
+
+double
+Point::angle() const
+{
+ return std::atan2(y, x); // (+x, _) is 0
+}
+
+
+Point
+Point::rotate(const double angle) const
+{
+ return {
+ x * std::cos(angle) - y * std::sin(angle),
+ x * std::sin(angle) + y * std::cos(angle),
+ };
+}
+
+
+Point
+Point::scale(const double _scale) const
+{
+ return {x * _scale, y * _scale};
+}
+
+
+Point
+Point::normalized() const
+{
+ return scale(1.0 / magnitude());
+}
+
+
+Point
+Point::operator-(const Point& other) const
+{
+ return {x - other.x, y - other.y};
+}
+
+
+Point
+Point::operator+(const Point& other) const
+{
+ return {x + other.x, y + other.y};
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/RandomSpawner.cpp b/sim/src/RandomSpawner.cpp
new file mode 100644
index 0000000..b85d140
--- /dev/null
+++ b/sim/src/RandomSpawner.cpp
@@ -0,0 +1,42 @@
+#include "RandomSpawner.h"
+
+#include <cmath>
+
+#include <kurator/battles/components.h>
+#include <kurator/battles/Point.h>
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+RandomSpawner::RandomSpawner(const int total_teams, const double distance, const double variation) :
+ angle_step {2.0 * M_PI / total_teams},
+ device {},
+ distribution_d {distance - distance * variation, distance + distance * variation},
+ distribution_a {-variation * M_PI, variation * M_PI}
+{
+}
+
+
+Transform
+RandomSpawner::get(const int team)
+{
+ const double distance = distribution_d(device);
+ const double clean_angle = angle_step * team;
+ const double angle = clean_angle + distribution_a(device);
+ double facing = clean_angle + M_PI;
+ if (facing > 2 * M_PI)
+ facing -= 2 * M_PI;
+ const Point position {
+ distance * std::cos(angle),
+ distance * std::sin(angle),
+ };
+ return {position, facing};
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/RandomSpawner.h b/sim/src/RandomSpawner.h
new file mode 100644
index 0000000..1ef2f20
--- /dev/null
+++ b/sim/src/RandomSpawner.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <random>
+
+#include <kurator/battles/components.h>
+
+#include "Spawner.h"
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+class RandomSpawner : public Spawner
+{
+public:
+ RandomSpawner(int total_teams, double distance, double variation);
+ Transform get(int team) override;
+private:
+ const double angle_step;
+ std::random_device device;
+ std::uniform_real_distribution<double> distribution_d;
+ std::uniform_real_distribution<double> distribution_a;
+};
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/Scenario.cpp b/sim/src/Scenario.cpp
new file mode 100644
index 0000000..49a9c7c
--- /dev/null
+++ b/sim/src/Scenario.cpp
@@ -0,0 +1,23 @@
+#include <kurator/battles/Scenario.h>
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+int
+Scenario::total_teams() const
+{
+ int last_team = 0;
+ for (const auto& ship : ships) {
+ if (ship.team > last_team)
+ last_team = ship.team;
+ }
+ return last_team + 1;
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/Spawner.h b/sim/src/Spawner.h
new file mode 100644
index 0000000..ebae699
--- /dev/null
+++ b/sim/src/Spawner.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <kurator/battles/components.h>
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+class Spawner
+{
+public:
+ virtual ~Spawner() = default;
+ virtual Transform get(int team) = 0;
+};
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/TeamManager.cpp b/sim/src/TeamManager.cpp
new file mode 100644
index 0000000..9dc90e3
--- /dev/null
+++ b/sim/src/TeamManager.cpp
@@ -0,0 +1,62 @@
+#include "TeamManager.h"
+
+#include <algorithm>
+#include <random>
+#include <utility>
+#include <vector>
+
+#include <entt/entity/registry.hpp>
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+TeamManager::TeamManager() :
+ teams {},
+ generator {std::random_device{}()}
+{
+}
+
+
+void
+TeamManager::add(int team, entt::entity entity)
+{
+ for (int i = teams.size(); i < team + 1; ++i)
+ teams.emplace_back();
+ teams.at(team).push_back(std::move(entity));
+}
+
+
+TeamManager::Team
+TeamManager::get(int team) const
+{
+ return teams.at(team);
+}
+
+
+entt::entity
+TeamManager::random(int team)
+{
+ auto& members = teams.at(team);
+ if (members.size() == 0)
+ return entt::null;
+ std::uniform_int_distribution<Team::size_type> uniform{0, members.size() - 1};
+ return members.at(uniform(generator));
+}
+
+
+void
+TeamManager::clear(entt::registry& registry)
+{
+ for (auto& members : teams) {
+ auto is_valid = [&registry](entt::entity entity){ return !registry.valid(entity); };
+ members.erase(std::remove_if(members.begin(), members.end(), is_valid), members.end());
+ }
+}
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/TeamManager.h b/sim/src/TeamManager.h
new file mode 100644
index 0000000..384d9ee
--- /dev/null
+++ b/sim/src/TeamManager.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <random>
+#include <vector>
+
+#include <entt/entity/registry.hpp>
+
+
+namespace kurator
+{
+namespace battles
+{
+
+
+class TeamManager
+{
+public:
+ using Team = std::vector<entt::entity>;
+ TeamManager();
+ void add(int team, entt::entity entity);
+ Team get(int team) const;
+ entt::entity random(int team);
+ void clear(entt::registry& registry);
+private:
+ std::vector<Team> teams;
+ std::mt19937 generator;
+};
+
+
+} // namespace battles
+} // namespace kurator
diff --git a/sim/src/scenarios.cpp b/sim/src/scenarios.cpp
new file mode 100644
index 0000000..34b4a24
--- /dev/null
+++ b/sim/src/scenarios.cpp
@@ -0,0 +1,43 @@
+#include <kurator/battles/scenarios.h>
+
+#include <kurator/battles/Scenario.h>
+
+
+namespace kurator
+{
+namespace battles
+{
+namespace scenarios
+{
+
+
+Scenario
+example()
+{
+ return {
+ "Example",
+ {
+ {0, "Anvil", {"ChargeLaser", "ChargeLaser"}},
+ {0, "Anvil", {"ChargeLaser", "ChargeLaser"}},
+ {0, "Anvil", {"ChargeLaser", "ChargeLaser"}},
+ {0, "Warbringer", {"ChargeLaser"}},
+ {0, "Warbringer", {"ChargeLaser"}},
+ {0, "Eclipse", {"ChargeLaser"}},
+ {0, "Eclipse", {"ChargeLaser"}},
+ {0, "Eclipse", {"ChargeLaser"}},
+ {1, "Anvil", {"ChargeLaser", "ChargeLaser"}},
+ {1, "Anvil", {"ChargeLaser", "ChargeLaser"}},
+ {1, "Anvil", {"ChargeLaser", "ChargeLaser"}},
+ {1, "Warbringer", {"ChargeLaser"}},
+ {1, "Warbringer", {"ChargeLaser"}},
+ {1, "Eclipse", {"ChargeLaser"}},
+ {1, "Eclipse", {"ChargeLaser"}},
+ {1, "Eclipse", {"ChargeLaser"}},
+ },
+ };
+}
+
+
+} // namespace scenarios
+} // namespace battles
+} // namespace kurator