diff options
Diffstat (limited to 'sim/src')
-rw-r--r-- | sim/src/BaseBattle.cpp | 162 | ||||
-rw-r--r-- | sim/src/BaseBattle.h | 40 | ||||
-rw-r--r-- | sim/src/Battle.cpp | 24 | ||||
-rw-r--r-- | sim/src/Builder.cpp | 57 | ||||
-rw-r--r-- | sim/src/Builder.h | 35 | ||||
-rw-r--r-- | sim/src/Point.cpp | 72 | ||||
-rw-r--r-- | sim/src/RandomSpawner.cpp | 42 | ||||
-rw-r--r-- | sim/src/RandomSpawner.h | 30 | ||||
-rw-r--r-- | sim/src/Scenario.cpp | 23 | ||||
-rw-r--r-- | sim/src/Spawner.h | 21 | ||||
-rw-r--r-- | sim/src/TeamManager.cpp | 62 | ||||
-rw-r--r-- | sim/src/TeamManager.h | 31 | ||||
-rw-r--r-- | sim/src/scenarios.cpp | 43 |
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 = [®istry](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 |