Compare commits
7 Commits
3e19e44f24
...
eeaa309c08
| Author | SHA1 | Date | |
|---|---|---|---|
| eeaa309c08 | |||
| 7669245229 | |||
| 4e3e3ac715 | |||
| 9677133c54 | |||
| abc261c03a | |||
| 17e9913c98 | |||
| 900b5fdec1 |
@@ -10,7 +10,7 @@ glyph = "L"
|
|||||||
|
|
||||||
[module.weapon]
|
[module.weapon]
|
||||||
damage_formula = "2"
|
damage_formula = "2"
|
||||||
attack_range_formula = "5"
|
attack_range_formula = "50"
|
||||||
attack_rate_formula = "2.0"
|
attack_rate_formula = "2.0"
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
@@ -24,7 +24,7 @@ fill_color = "#AACC44"
|
|||||||
glyph = "Sv"
|
glyph = "Sv"
|
||||||
|
|
||||||
[module.salvage]
|
[module.salvage]
|
||||||
collection_range_formula = "50"
|
collection_range_formula = "500"
|
||||||
cargo_capacity_formula = "10"
|
cargo_capacity_formula = "10"
|
||||||
collection_rate_formula = "0.5"
|
collection_rate_formula = "0.5"
|
||||||
|
|
||||||
@@ -40,4 +40,4 @@ glyph = "Rp"
|
|||||||
|
|
||||||
[module.repair]
|
[module.repair]
|
||||||
repair_rate_formula = "5 + x"
|
repair_rate_formula = "5 + x"
|
||||||
repair_range_formula = "80"
|
repair_range_formula = "800"
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ cost_formula = "10"
|
|||||||
hp_formula = "3"
|
hp_formula = "3"
|
||||||
|
|
||||||
[ship.movement]
|
[ship.movement]
|
||||||
speed_formula = "4"
|
speed_formula = "40"
|
||||||
main_acceleration_formula = "8"
|
main_acceleration_formula = "80"
|
||||||
maneuvering_acceleration_formula = "4"
|
maneuvering_acceleration_formula = "40"
|
||||||
angular_acceleration_formula = "12.56"
|
angular_acceleration_formula = "12.56"
|
||||||
max_rotation_speed_formula = "6.28"
|
max_rotation_speed_formula = "6.28"
|
||||||
|
|
||||||
[ship.sensor]
|
[ship.sensor]
|
||||||
sensor_range_formula = "15"
|
sensor_range_formula = "150"
|
||||||
|
|
||||||
[ship.loot]
|
[ship.loot]
|
||||||
scrap_drop = 2
|
scrap_drop = 2
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ surface_mask = [
|
|||||||
level = 1
|
level = 1
|
||||||
hp_formula = "300"
|
hp_formula = "300"
|
||||||
damage_formula = "5"
|
damage_formula = "5"
|
||||||
range_formula = "20"
|
range_formula = "200"
|
||||||
fire_rate_formula = "1"
|
fire_rate_formula = "1"
|
||||||
scrap_drop_formula = "10"
|
scrap_drop_formula = "10"
|
||||||
|
|
||||||
@@ -25,6 +25,6 @@ surface_mask = [
|
|||||||
]
|
]
|
||||||
hp_formula = "300 + 150*x"
|
hp_formula = "300 + 150*x"
|
||||||
damage_formula = "2 + 1*x"
|
damage_formula = "2 + 1*x"
|
||||||
range_formula = "20"
|
range_formula = "200"
|
||||||
fire_rate_formula = "1.0 + 0.2*x"
|
fire_rate_formula = "1.0 + 0.2*x"
|
||||||
scrap_drop_formula = "10 + 5*x"
|
scrap_drop_formula = "10 + 5*x"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ height_tiles = 30
|
|||||||
refund_percentage = 75
|
refund_percentage = 75
|
||||||
starting_building_blocks = 1000
|
starting_building_blocks = 1000
|
||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
tile_size_m = 10
|
||||||
|
belt_speed_mps = 20
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
departure_interval_seconds = 20
|
departure_interval_seconds = 20
|
||||||
|
|
||||||
|
|||||||
@@ -8,91 +8,21 @@ enemy_buffer_width = 10
|
|||||||
[[arena.team]]
|
[[arena.team]]
|
||||||
name = "Alpha"
|
name = "Alpha"
|
||||||
[[arena.team.ship]]
|
[[arena.team.ship]]
|
||||||
schematic = "fighter"
|
schematic = "drone"
|
||||||
level = 1
|
level = 1
|
||||||
count = 5
|
count = 5
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "weapon_upgrade", x = 0, y = 1, rotation = "east"},
|
|
||||||
{type = "sensor_booster", x = 2, y = 1, rotation = "east"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[arena.team]]
|
[[arena.team]]
|
||||||
name = "Beta"
|
name = "Beta"
|
||||||
[[arena.team.ship]]
|
[[arena.team.ship]]
|
||||||
schematic = "sniper"
|
schematic = "drone"
|
||||||
level = 1
|
level = 1
|
||||||
count = 1
|
count = 1
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "armor_plate", x = 1, y = 0, rotation = "east"},
|
|
||||||
{type = "weapon_upgrade", x = 1, y = 2, rotation = "east"},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[[arena]]
|
|
||||||
name = "Sniper vs Gunship"
|
|
||||||
height_tiles = 20
|
|
||||||
player_buffer_width = 10
|
|
||||||
contest_zone_width = 60
|
|
||||||
enemy_buffer_width = 10
|
|
||||||
|
|
||||||
[[arena.team]]
|
|
||||||
name = "Alpha"
|
|
||||||
[[arena.team.ship]]
|
|
||||||
schematic = "sniper"
|
|
||||||
level = 1
|
|
||||||
count = 1
|
|
||||||
modules = [
|
|
||||||
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
|
||||||
{type = "armor_plate", x = 1, y = 0, rotation = "east"},
|
|
||||||
{type = "sensor_booster", x = 0, y = 1, rotation = "east"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[arena.team]]
|
|
||||||
name = "Beta"
|
|
||||||
[[arena.team.ship]]
|
|
||||||
schematic = "gunship"
|
|
||||||
level = 1
|
|
||||||
count = 1
|
|
||||||
modules = [
|
|
||||||
{type = "laser_cannon", x = 2, y = 1, rotation = "east"},
|
|
||||||
{type = "armor_plate", x = 1, y = 0, rotation = "east"},
|
|
||||||
{type = "weapon_upgrade", x = 3, y = 1, rotation = "east"},
|
|
||||||
{type = "engine_booster", x = 0, y = 1, rotation = "east"},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[[arena]]
|
|
||||||
name = "Gunship vs Fighters"
|
|
||||||
height_tiles = 20
|
|
||||||
player_buffer_width = 10
|
|
||||||
contest_zone_width = 60
|
|
||||||
enemy_buffer_width = 10
|
|
||||||
|
|
||||||
[[arena.team]]
|
|
||||||
name = "Alpha"
|
|
||||||
[[arena.team.ship]]
|
|
||||||
schematic = "gunship"
|
|
||||||
level = 1
|
|
||||||
count = 1
|
|
||||||
modules = [
|
|
||||||
{type = "laser_cannon", x = 2, y = 2, rotation = "east"},
|
|
||||||
{type = "armor_plate", x = 1, y = 0, rotation = "east"},
|
|
||||||
{type = "weapon_upgrade", x = 3, y = 2, rotation = "east"},
|
|
||||||
{type = "engine_booster", x = 0, y = 1, rotation = "east"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[arena.team]]
|
|
||||||
name = "Beta"
|
|
||||||
[[arena.team.ship]]
|
|
||||||
schematic = "fighter"
|
|
||||||
level = 1
|
|
||||||
count = 5
|
|
||||||
modules = [
|
|
||||||
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
|
||||||
{type = "engine_booster", x = 1, y = 0, rotation = "east"},
|
|
||||||
{type = "sensor_booster", x = 2, y = 1, rotation = "east"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -106,13 +36,11 @@ enemy_buffer_width = 15
|
|||||||
[[arena.team]]
|
[[arena.team]]
|
||||||
name = "Fortified"
|
name = "Fortified"
|
||||||
[[arena.team.ship]]
|
[[arena.team.ship]]
|
||||||
schematic = "fighter"
|
schematic = "drone"
|
||||||
level = 1
|
level = 1
|
||||||
count = 3
|
count = 3
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "weapon_upgrade", x = 2, y = 1, rotation = "east"},
|
|
||||||
{type = "sensor_booster", x = 1, y = 0, rotation = "east"},
|
|
||||||
]
|
]
|
||||||
[[arena.team.station]]
|
[[arena.team.station]]
|
||||||
type = "player_station"
|
type = "player_station"
|
||||||
@@ -128,10 +56,9 @@ enemy_buffer_width = 15
|
|||||||
[[arena.team]]
|
[[arena.team]]
|
||||||
name = "Swarm"
|
name = "Swarm"
|
||||||
[[arena.team.ship]]
|
[[arena.team.ship]]
|
||||||
schematic = "fighter"
|
schematic = "drone"
|
||||||
level = 1
|
level = 1
|
||||||
count = 8
|
count = 8
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "engine_booster", x = 1, y = 0, rotation = "east"},
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fill_color = "#40A0FF"
|
|||||||
glyph = "S"
|
glyph = "S"
|
||||||
|
|
||||||
[module.sensor]
|
[module.sensor]
|
||||||
added_sensor_range_formula = "10"
|
added_sensor_range_formula = "100"
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_upgrade"
|
id = "weapon_upgrade"
|
||||||
@@ -49,7 +49,7 @@ glyph = "L"
|
|||||||
|
|
||||||
[module.weapon]
|
[module.weapon]
|
||||||
damage_formula = "2"
|
damage_formula = "2"
|
||||||
attack_range_formula = "5"
|
attack_range_formula = "50"
|
||||||
attack_rate_formula = "2.0"
|
attack_rate_formula = "2.0"
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
@@ -63,7 +63,7 @@ fill_color = "#AACC44"
|
|||||||
glyph = "Sv"
|
glyph = "Sv"
|
||||||
|
|
||||||
[module.salvage]
|
[module.salvage]
|
||||||
collection_range_formula = "50"
|
collection_range_formula = "500"
|
||||||
cargo_capacity_formula = "10"
|
cargo_capacity_formula = "10"
|
||||||
collection_rate_formula = "0.5"
|
collection_rate_formula = "0.5"
|
||||||
|
|
||||||
@@ -79,4 +79,4 @@ glyph = "Rp"
|
|||||||
|
|
||||||
[module.repair]
|
[module.repair]
|
||||||
repair_rate_formula = "5 + x"
|
repair_rate_formula = "5 + x"
|
||||||
repair_range_formula = "80"
|
repair_range_formula = "800"
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ cost_formula = "5 + 1*x"
|
|||||||
hp_formula = "40 + 5*x"
|
hp_formula = "40 + 5*x"
|
||||||
|
|
||||||
[ship.movement]
|
[ship.movement]
|
||||||
speed_formula = "200 + 5*x"
|
speed_formula = "2000 + 50*x"
|
||||||
main_acceleration_formula = "100000"
|
main_acceleration_formula = "1000000"
|
||||||
maneuvering_acceleration_formula = "100000"
|
maneuvering_acceleration_formula = "1000000"
|
||||||
angular_acceleration_formula = "100000"
|
angular_acceleration_formula = "100000"
|
||||||
max_rotation_speed_formula = "100000"
|
max_rotation_speed_formula = "100000"
|
||||||
|
|
||||||
[ship.sensor]
|
[ship.sensor]
|
||||||
sensor_range_formula = "200"
|
sensor_range_formula = "2000"
|
||||||
|
|
||||||
[ship.loot]
|
[ship.loot]
|
||||||
scrap_drop = 2
|
scrap_drop = 2
|
||||||
@@ -47,14 +47,14 @@ cost_formula = "10 + 2*x"
|
|||||||
hp_formula = "120 + 15*x"
|
hp_formula = "120 + 15*x"
|
||||||
|
|
||||||
[ship.movement]
|
[ship.movement]
|
||||||
speed_formula = "120"
|
speed_formula = "1200"
|
||||||
main_acceleration_formula = "100000"
|
main_acceleration_formula = "1000000"
|
||||||
maneuvering_acceleration_formula = "100000"
|
maneuvering_acceleration_formula = "1000000"
|
||||||
angular_acceleration_formula = "100000"
|
angular_acceleration_formula = "100000"
|
||||||
max_rotation_speed_formula = "100000"
|
max_rotation_speed_formula = "100000"
|
||||||
|
|
||||||
[ship.sensor]
|
[ship.sensor]
|
||||||
sensor_range_formula = "300"
|
sensor_range_formula = "3000"
|
||||||
|
|
||||||
[ship.loot]
|
[ship.loot]
|
||||||
scrap_drop = 4
|
scrap_drop = 4
|
||||||
@@ -77,14 +77,14 @@ cost_formula = "0"
|
|||||||
hp_formula = "40 + 4*x"
|
hp_formula = "40 + 4*x"
|
||||||
|
|
||||||
[ship.movement]
|
[ship.movement]
|
||||||
speed_formula = "110"
|
speed_formula = "1100"
|
||||||
main_acceleration_formula = "100000"
|
main_acceleration_formula = "1000000"
|
||||||
maneuvering_acceleration_formula = "100000"
|
maneuvering_acceleration_formula = "1000000"
|
||||||
angular_acceleration_formula = "100000"
|
angular_acceleration_formula = "100000"
|
||||||
max_rotation_speed_formula = "100000"
|
max_rotation_speed_formula = "100000"
|
||||||
|
|
||||||
[ship.sensor]
|
[ship.sensor]
|
||||||
sensor_range_formula = "250"
|
sensor_range_formula = "2500"
|
||||||
|
|
||||||
[ship.loot]
|
[ship.loot]
|
||||||
scrap_drop = 2
|
scrap_drop = 2
|
||||||
@@ -107,14 +107,14 @@ cost_formula = "0"
|
|||||||
hp_formula = "60 + 5*x"
|
hp_formula = "60 + 5*x"
|
||||||
|
|
||||||
[ship.movement]
|
[ship.movement]
|
||||||
speed_formula = "130"
|
speed_formula = "1300"
|
||||||
main_acceleration_formula = "100000"
|
main_acceleration_formula = "1000000"
|
||||||
maneuvering_acceleration_formula = "100000"
|
maneuvering_acceleration_formula = "1000000"
|
||||||
angular_acceleration_formula = "100000"
|
angular_acceleration_formula = "100000"
|
||||||
max_rotation_speed_formula = "100000"
|
max_rotation_speed_formula = "100000"
|
||||||
|
|
||||||
[ship.sensor]
|
[ship.sensor]
|
||||||
sensor_range_formula = "250"
|
sensor_range_formula = "2500"
|
||||||
|
|
||||||
[ship.loot]
|
[ship.loot]
|
||||||
scrap_drop = 2
|
scrap_drop = 2
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ surface_mask = [
|
|||||||
level = 5
|
level = 5
|
||||||
hp_formula = "300 + 40*x"
|
hp_formula = "300 + 40*x"
|
||||||
damage_formula = "5 + 4*x"
|
damage_formula = "5 + 4*x"
|
||||||
range_formula = "300 + 20*x"
|
range_formula = "3000 + 200*x"
|
||||||
fire_rate_formula = "0.5 + 0.2*x"
|
fire_rate_formula = "0.5 + 0.2*x"
|
||||||
scrap_drop_formula = "x"
|
scrap_drop_formula = "x"
|
||||||
|
|
||||||
@@ -25,6 +25,6 @@ surface_mask = [
|
|||||||
]
|
]
|
||||||
hp_formula = "300 + 150*x"
|
hp_formula = "300 + 150*x"
|
||||||
damage_formula = "20 + 10*x"
|
damage_formula = "20 + 10*x"
|
||||||
range_formula = "350 + 20*x"
|
range_formula = "3500 + 200*x"
|
||||||
fire_rate_formula = "1.0 + 0.2*x"
|
fire_rate_formula = "1.0 + 0.2*x"
|
||||||
scrap_drop_formula = "10 + 5*x"
|
scrap_drop_formula = "10 + 5*x"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ height_tiles = 60
|
|||||||
refund_percentage = 75
|
refund_percentage = 75
|
||||||
starting_building_blocks = 100
|
starting_building_blocks = 100
|
||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
tile_size_m = 10
|
||||||
|
belt_speed_mps = 20
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
departure_interval_seconds = 20
|
departure_interval_seconds = 20
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
# Modular Ships: Remove Ship Roles, Unify Capabilities as Modules
|
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
Ships currently have a fixed role (combat, salvage, repair) baked into their definition. This limits ship customization — a ship is either a fighter or a salvage ship, never both. By moving weapon, salvage cargo, and repair tool capabilities into the module system, players can freely compose ship loadouts. A single hull can carry two weapons and a repair tool, or a weapon and a salvage bay, etc.
|
|
||||||
|
|
||||||
## What Changes
|
|
||||||
|
|
||||||
### Ship definitions lose role-specific sections
|
|
||||||
|
|
||||||
`ShipDef` drops `std::optional<ShipCombat>`, `std::optional<ShipSalvage>`, `std::optional<ShipRepair>`. Ships define only hull stats (HP, movement, sensor) and a layout grid. A new `default_modules` list is added per schematic for enemy wave ships (see below).
|
|
||||||
|
|
||||||
### Capability modules replace roles
|
|
||||||
|
|
||||||
New module types in `modules.toml` provide capabilities. A module with base stat formulas (e.g. `damage_formula`) under a capability section (`[module.weapon]`, `[module.salvage]`, `[module.repair]`) is a **capability module** that creates a child entity. A module with only `added_*`/`multiplied_*` formulas is a **passive module** that modifies stats.
|
|
||||||
|
|
||||||
Example capability module:
|
|
||||||
```toml
|
|
||||||
[[module]]
|
|
||||||
id = "laser_turret"
|
|
||||||
[module.weapon]
|
|
||||||
damage_formula = "5 + 2*x" # x = module's player_production_level
|
|
||||||
attack_range_formula = "8 + x"
|
|
||||||
attack_rate_formula = "1.5 + 0.1*x"
|
|
||||||
```
|
|
||||||
|
|
||||||
Example passive module boosting weapons:
|
|
||||||
```toml
|
|
||||||
[[module]]
|
|
||||||
id = "weapon_upgrade"
|
|
||||||
[module.weapon]
|
|
||||||
multiplied_damage_formula = "1.0 + 0.15 * x"
|
|
||||||
```
|
|
||||||
|
|
||||||
Example passive module boosting ship stats:
|
|
||||||
```toml
|
|
||||||
[[module]]
|
|
||||||
id = "armor_plate"
|
|
||||||
[module.health]
|
|
||||||
multiplied_hp_formula = "1.0 + 0.2 * x"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Capability modules become child entities
|
|
||||||
|
|
||||||
Each placed capability module instance becomes its own entt entity with a `ModuleOwnerComponent { entt::entity ship }` linking it to the parent ship. This allows multiple instances of the same type (e.g. three weapons, each with independent stats, cooldown, and target).
|
|
||||||
|
|
||||||
A new `ModuleOwnerComponent` is introduced:
|
|
||||||
```cpp
|
|
||||||
struct ModuleOwnerComponent
|
|
||||||
{
|
|
||||||
entt::entity ship;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Passive modifiers apply to both ship and module entities
|
|
||||||
|
|
||||||
During spawn, passive module modifiers are collected and routed by category:
|
|
||||||
- `[module.health]`, `[module.movement]`, `[module.sensor]` modifiers apply to the ship entity's hull stats.
|
|
||||||
- `[module.weapon]` modifiers apply to every weapon child entity on the ship.
|
|
||||||
- `[module.repair]` modifiers apply to every repair child entity on the ship.
|
|
||||||
- `[module.salvage]` modifiers apply to every salvage child entity on the ship.
|
|
||||||
|
|
||||||
Capability module child entities must be created first, then passive modifiers are applied. The formula variable `x` is always the module's `player_production_level`.
|
|
||||||
|
|
||||||
### Behavior components stay on the ship entity
|
|
||||||
|
|
||||||
`ThreatResponseBehaviorComponent`, `SalvageBehaviorComponent`, `RepairBehaviorComponent` remain on the ship entity (they drive movement). They are attached if the ship has at least one module of the corresponding type.
|
|
||||||
|
|
||||||
### Hybrid ships are allowed
|
|
||||||
|
|
||||||
A ship may have modules of different capability types. Movement arbitration currently uses last-writer-wins (the last behavior system ticked sets the intent). This is acceptable for now; dynamic priority-based arbitration will be added later.
|
|
||||||
|
|
||||||
### Systems query module entities
|
|
||||||
|
|
||||||
Weapon, repair, and salvage tick systems query for their component + `ModuleOwnerComponent` and resolve position from the owner ship. Each module instance ticks independently.
|
|
||||||
|
|
||||||
### Despawn cleans up child entities
|
|
||||||
|
|
||||||
`ShipSystem::despawn` destroys the ship entity and all module entities whose `ModuleOwnerComponent::ship` matches it.
|
|
||||||
|
|
||||||
### Enemy wave ships use default modules
|
|
||||||
|
|
||||||
Since weapons are now modules, enemy ships need modules to fight. Each ship schematic in `ships.toml` defines a `default_modules` list (same format as layout blueprints). Wave-spawned enemy ships are instantiated with this module layout. If `default_modules` is absent or empty, the ship spawns with no modules (and therefore no combat/salvage/repair capability).
|
|
||||||
|
|
||||||
### Visuals use per-schematic colors instead of per-role
|
|
||||||
|
|
||||||
`visuals.toml` defines fill/outline colors and glyphs per ship schematic (e.g. fighter, sniper, gunship) rather than per role (combat, salvage, repair). Debug draw sensor circles use the schematic's outline color.
|
|
||||||
@@ -123,6 +123,7 @@ void ArenaSimulation::placeStructures()
|
|||||||
weapon.cooldownTicks = 0.0f;
|
weapon.cooldownTicks = 0.0f;
|
||||||
weapon.currentTarget = std::nullopt;
|
weapon.currentTarget = std::nullopt;
|
||||||
const double lv = static_cast<double>(entry.level);
|
const double lv = static_cast<double>(entry.level);
|
||||||
|
const float tileSize = static_cast<float>(m_gameConfig.world.tileSize_m);
|
||||||
|
|
||||||
const std::vector<std::string>& mask = isEnemy
|
const std::vector<std::string>& mask = isEnemy
|
||||||
? m_gameConfig.stations.enemyStation.surfaceMask
|
? m_gameConfig.stations.enemyStation.surfaceMask
|
||||||
@@ -134,8 +135,8 @@ void ArenaSimulation::placeStructures()
|
|||||||
m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
|
m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
|
||||||
weapon.damage = static_cast<float>(
|
weapon.damage = static_cast<float>(
|
||||||
m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
|
m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
|
||||||
weapon.range = static_cast<float>(
|
weapon.range_tiles = static_cast<float>(
|
||||||
m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv));
|
m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv)) / tileSize;
|
||||||
weapon.fireRateHz = static_cast<float>(
|
weapon.fireRateHz = static_cast<float>(
|
||||||
m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
|
m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
|
||||||
}
|
}
|
||||||
@@ -145,8 +146,8 @@ void ArenaSimulation::placeStructures()
|
|||||||
m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
|
m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
|
||||||
weapon.damage = static_cast<float>(
|
weapon.damage = static_cast<float>(
|
||||||
m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
|
m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
|
||||||
weapon.range = static_cast<float>(
|
weapon.range_tiles = static_cast<float>(
|
||||||
m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv));
|
m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv)) / tileSize;
|
||||||
weapon.fireRateHz = static_cast<float>(
|
weapon.fireRateHz = static_cast<float>(
|
||||||
m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
|
m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ void ArenaWidget::buildLayout(const std::string& arenaName)
|
|||||||
|
|
||||||
titleRow->addStretch();
|
titleRow->addStretch();
|
||||||
|
|
||||||
m_inspectButton = new QPushButton("Inspect", this);
|
m_inspectButton = new QPushButton(tr("Inspect"), this);
|
||||||
connect(m_inspectButton, &QPushButton::clicked, this, &ArenaWidget::inspectRequested);
|
connect(m_inspectButton, &QPushButton::clicked, this, &ArenaWidget::inspectRequested);
|
||||||
titleRow->addWidget(m_inspectButton);
|
titleRow->addWidget(m_inspectButton);
|
||||||
|
|
||||||
m_startButton = new QPushButton("Start", this);
|
m_startButton = new QPushButton(tr("Start"), this);
|
||||||
connect(m_startButton, &QPushButton::clicked, this, &ArenaWidget::startRequested);
|
connect(m_startButton, &QPushButton::clicked, this, &ArenaWidget::startRequested);
|
||||||
titleRow->addWidget(m_startButton);
|
titleRow->addWidget(m_startButton);
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ void ArenaWidget::updateStatus(const ArenaStatus& status)
|
|||||||
|
|
||||||
if (status.finished && status.winnerTeam == ti)
|
if (status.finished && status.winnerTeam == ti)
|
||||||
{
|
{
|
||||||
header->setText("[WON] " + QString::fromStdString(team.name));
|
header->setText(tr("[WON] %1").arg(QString::fromStdString(team.name)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ BalancingWindow::BalancingWindow(const BalancingConfig& balancingConfig,
|
|||||||
, m_inspectedArenaIndex(-1)
|
, m_inspectedArenaIndex(-1)
|
||||||
{
|
{
|
||||||
m_visuals = VisualsLoader::load(m_configDir + "/visuals.toml");
|
m_visuals = VisualsLoader::load(m_configDir + "/visuals.toml");
|
||||||
setWindowTitle("DotaFactory — Balancing Tool");
|
setWindowTitle(tr("DotaFactory — Balancing Tool"));
|
||||||
resize(800, 600);
|
resize(800, 600);
|
||||||
|
|
||||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
QHBoxLayout* buttonRow = new QHBoxLayout();
|
QHBoxLayout* buttonRow = new QHBoxLayout();
|
||||||
m_reloadButton = new QPushButton("Reload Config", this);
|
m_reloadButton = new QPushButton(tr("Reload Config"), this);
|
||||||
m_startAllButton = new QPushButton("Start All", this);
|
m_startAllButton = new QPushButton(tr("Start All"), this);
|
||||||
buttonRow->addWidget(m_reloadButton);
|
buttonRow->addWidget(m_reloadButton);
|
||||||
buttonRow->addWidget(m_startAllButton);
|
buttonRow->addWidget(m_startAllButton);
|
||||||
buttonRow->addStretch();
|
buttonRow->addStretch();
|
||||||
@@ -146,7 +146,7 @@ void BalancingWindow::reloadConfig()
|
|||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Reload Failed", QString::fromStdString(e.what()));
|
QMessageBox::critical(this, tr("Reload Failed"), QString::fromStdString(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ InspectWindow::InspectWindow(ArenaSimulation* sim, const VisualsConfig* visuals,
|
|||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_sim(sim)
|
, m_sim(sim)
|
||||||
{
|
{
|
||||||
setWindowTitle(QString("Inspect \u2014 %1").arg(QString::fromStdString(arenaName)));
|
setWindowTitle(tr("Inspect \u2014 %1").arg(QString::fromStdString(arenaName)));
|
||||||
resize(900, 700);
|
resize(900, 700);
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ void InspectWindow::updateInfoPanel(const ArenaStatus& status)
|
|||||||
|
|
||||||
if (status.finished && status.winnerTeam == ti)
|
if (status.finished && status.winnerTeam == ti)
|
||||||
{
|
{
|
||||||
header->setText("[WON] " + QString::fromStdString(team.name));
|
header->setText(tr("[WON] %1").arg(QString::fromStdString(team.name)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
SET(HDRS)
|
SET(HDRS)
|
||||||
SET(SRCS)
|
SET(SRCS)
|
||||||
|
|
||||||
add_subdirectory(core)
|
|
||||||
add_subdirectory(config)
|
add_subdirectory(config)
|
||||||
|
add_subdirectory(core)
|
||||||
|
add_subdirectory(ecs)
|
||||||
|
add_subdirectory(eventsystem)
|
||||||
add_subdirectory(utility)
|
add_subdirectory(utility)
|
||||||
add_subdirectory(sim)
|
add_subdirectory(sim)
|
||||||
add_subdirectory(ecs)
|
|
||||||
|
|
||||||
SET(HDRS
|
SET(HDRS
|
||||||
${HDRS}
|
${HDRS}
|
||||||
|
|||||||
@@ -264,7 +264,8 @@ WorldConfig ConfigLoader::loadWorld(const std::string& path)
|
|||||||
cfg.refundPercentage = static_cast<int>(requireInt(tbl["world"]["refund_percentage"], file, "world.refund_percentage"));
|
cfg.refundPercentage = static_cast<int>(requireInt(tbl["world"]["refund_percentage"], file, "world.refund_percentage"));
|
||||||
cfg.startingBuildingBlocks = static_cast<int>(requireInt(tbl["world"]["starting_building_blocks"], file, "world.starting_building_blocks"));
|
cfg.startingBuildingBlocks = static_cast<int>(requireInt(tbl["world"]["starting_building_blocks"], file, "world.starting_building_blocks"));
|
||||||
cfg.scrapDespawnSeconds = requireDouble(tbl["world"]["scrap_despawn_seconds"], file, "world.scrap_despawn_seconds");
|
cfg.scrapDespawnSeconds = requireDouble(tbl["world"]["scrap_despawn_seconds"], file, "world.scrap_despawn_seconds");
|
||||||
cfg.beltSpeedTilesPerSecond = requireDouble(tbl["world"]["belt_speed_tiles_per_second"], file, "world.belt_speed_tiles_per_second");
|
cfg.tileSize_m = requireDouble(tbl["world"]["tile_size_m"], file, "world.tile_size_m");
|
||||||
|
cfg.beltSpeed_tps = requireDouble(tbl["world"]["belt_speed_mps"], file, "world.belt_speed_mps") / cfg.tileSize_m;
|
||||||
cfg.tunnelMaxDistance = static_cast<int>(requireInt(tbl["world"]["tunnel_max_distance"], file, "world.tunnel_max_distance"));
|
cfg.tunnelMaxDistance = static_cast<int>(requireInt(tbl["world"]["tunnel_max_distance"], file, "world.tunnel_max_distance"));
|
||||||
cfg.departureIntervalSeconds = requireDouble(tbl["world"]["departure_interval_seconds"], file, "world.departure_interval_seconds");
|
cfg.departureIntervalSeconds = requireDouble(tbl["world"]["departure_interval_seconds"], file, "world.departure_interval_seconds");
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ struct WorldConfig
|
|||||||
int refundPercentage; // REQ-BLD-DEMOLISH
|
int refundPercentage; // REQ-BLD-DEMOLISH
|
||||||
int startingBuildingBlocks; // REQ-HQ-STARTING-BLOCKS
|
int startingBuildingBlocks; // REQ-HQ-STARTING-BLOCKS
|
||||||
double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP
|
double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP
|
||||||
double beltSpeedTilesPerSecond; // REQ-GW-BELT-SPEED
|
double tileSize_m; // metres per tile (REQ-GW-TILE-SIZE)
|
||||||
|
double beltSpeed_tps; // REQ-GW-BELT-SPEED (tiles/s, converted from m/s in config)
|
||||||
int tunnelMaxDistance; // REQ-BLD-TUNNEL-PAIR
|
int tunnelMaxDistance; // REQ-BLD-TUNNEL-PAIR
|
||||||
double departureIntervalSeconds; // REQ-SHP-RALLY
|
double departureIntervalSeconds; // REQ-SHP-RALLY
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ void EntityAdmin::clear()
|
|||||||
}
|
}
|
||||||
|
|
||||||
entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
||||||
float maxSpeedPerTick, float mainAccelPerTick,
|
float maxSpeed_tpt, float mainAcceleration_tptt,
|
||||||
float maneuveringAccelPerTick, float angularAccelPerTick,
|
float maneuveringAcceleration_tptt, float maxAngularAcceleration_rptt,
|
||||||
float maxRotationSpeedPerTick, float sensorRange,
|
float maxRotationSpeed_rpt, float sensorRange_tiles,
|
||||||
int level, const std::string& schematicId, bool isEnemy)
|
int level, const std::string& schematicId, bool isEnemy)
|
||||||
{
|
{
|
||||||
entt::entity entity = createEntity();
|
entt::entity entity = createEntity();
|
||||||
@@ -50,17 +50,17 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
|||||||
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
||||||
add<FacingComponent>(entity, FacingComponent{0.0f});
|
add<FacingComponent>(entity, FacingComponent{0.0f});
|
||||||
add<DynamicBodyComponent>(entity, DynamicBodyComponent{
|
add<DynamicBodyComponent>(entity, DynamicBodyComponent{
|
||||||
maxSpeedPerTick,
|
maxSpeed_tpt,
|
||||||
mainAccelPerTick,
|
mainAcceleration_tptt,
|
||||||
maneuveringAccelPerTick,
|
maneuveringAcceleration_tptt,
|
||||||
angularAccelPerTick,
|
maxAngularAcceleration_rptt,
|
||||||
maxRotationSpeedPerTick,
|
maxRotationSpeed_rpt,
|
||||||
QVector2D(0.0f, 0.0f), // velocity
|
QVector2D(0.0f, 0.0f), // velocity_tpt
|
||||||
0.0f, // angularVelocity
|
0.0f, // angularVelocity_rpt
|
||||||
QVector2D(0.0f, 0.0f), // linearAcceleration
|
QVector2D(0.0f, 0.0f), // linearAcceleration_tptt
|
||||||
0.0f // angularAcceleration
|
0.0f // angularAcceleration_rptt
|
||||||
});
|
});
|
||||||
add<SensorRangeComponent>(entity, SensorRangeComponent{sensorRange});
|
add<SensorRangeComponent>(entity, SensorRangeComponent{sensorRange_tiles});
|
||||||
add<ShipIdentityComponent>(entity, ShipIdentityComponent{level, schematicId});
|
add<ShipIdentityComponent>(entity, ShipIdentityComponent{level, schematicId});
|
||||||
add<MovementIntentComponent>(entity, MovementIntentComponent{0, QVector2D(0.0f, 0.0f)});
|
add<MovementIntentComponent>(entity, MovementIntentComponent{0, QVector2D(0.0f, 0.0f)});
|
||||||
return entity;
|
return entity;
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ public:
|
|||||||
// -- Factory methods ----------------------------------------------------
|
// -- Factory methods ----------------------------------------------------
|
||||||
|
|
||||||
entt::entity spawnShip(QVector2D position, float hp, float maxHp,
|
entt::entity spawnShip(QVector2D position, float hp, float maxHp,
|
||||||
float maxSpeedPerTick, float mainAccelPerTick,
|
float maxSpeed_tpt, float mainAcceleration_tptt,
|
||||||
float maneuveringAccelPerTick, float angularAccelPerTick,
|
float maneuveringAcceleration_tptt, float maxAngularAcceleration_rptt,
|
||||||
float maxRotationSpeedPerTick, float sensorRange,
|
float maxRotationSpeed_rpt, float sensorRange_tiles,
|
||||||
int level, const std::string& schematicId, bool isEnemy);
|
int level, const std::string& schematicId, bool isEnemy);
|
||||||
|
|
||||||
entt::entity spawnStation(QPoint anchor, QSize footprint,
|
entt::entity spawnStation(QPoint anchor, QSize footprint,
|
||||||
|
|||||||
@@ -4,18 +4,18 @@
|
|||||||
|
|
||||||
struct DynamicBodyComponent
|
struct DynamicBodyComponent
|
||||||
{
|
{
|
||||||
// --- dynamics parameters (formerly ShipDynamics) ---
|
// --- dynamics parameters ---
|
||||||
float maxSpeedPerTick;
|
float maxSpeed_tpt; // tiles/tick
|
||||||
float mainAccelerationPerTick;
|
float mainAcceleration_tptt; // tiles/tick²
|
||||||
float maneuveringAccelerationPerTick;
|
float maneuveringAcceleration_tptt; // tiles/tick²
|
||||||
float angularAccelerationPerTick;
|
float maxAngularAcceleration_rptt; // rad/tick²
|
||||||
float maxRotationSpeedPerTick;
|
float maxRotationSpeed_rpt; // rad/tick
|
||||||
|
|
||||||
// --- integrated state ---
|
// --- integrated state ---
|
||||||
QVector2D velocity;
|
QVector2D velocity_tpt; // tiles/tick
|
||||||
float angularVelocity;
|
float angularVelocity_rpt; // rad/tick
|
||||||
|
|
||||||
// --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem ---
|
// --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem ---
|
||||||
QVector2D linearAcceleration;
|
QVector2D linearAcceleration_tptt; // tiles/tick²
|
||||||
float angularAcceleration;
|
float angularAcceleration_rptt; // rad/tick²
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
struct RepairBehaviorComponent
|
struct RepairBehaviorComponent
|
||||||
{
|
{
|
||||||
std::optional<entt::entity> currentTarget;
|
std::optional<entt::entity> currentTarget;
|
||||||
float maxRepairRange = 0.0f;
|
float maxRepairRange_tiles = 0.0f;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,6 @@
|
|||||||
struct RepairToolComponent
|
struct RepairToolComponent
|
||||||
{
|
{
|
||||||
float ratePerTick;
|
float ratePerTick;
|
||||||
float range;
|
float range_tiles;
|
||||||
std::optional<entt::entity> currentTarget;
|
std::optional<entt::entity> currentTarget;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ struct SalvageBehaviorComponent
|
|||||||
{
|
{
|
||||||
std::optional<QVector2D> scrapTarget;
|
std::optional<QVector2D> scrapTarget;
|
||||||
BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay
|
BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay
|
||||||
float maxCollectionRange = 0.0f;
|
float maxCollectionRange_tiles = 0.0f;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ struct SalvageCargoComponent
|
|||||||
{
|
{
|
||||||
int capacity;
|
int capacity;
|
||||||
int current;
|
int current;
|
||||||
float collectionRange;
|
float collectionRange_tiles;
|
||||||
int collectionIntervalTicks;
|
int collectionIntervalTicks;
|
||||||
int cooldownTicksRemaining;
|
int cooldownTicksRemaining;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
struct SensorRangeComponent
|
struct SensorRangeComponent
|
||||||
{
|
{
|
||||||
float value;
|
float value_tiles;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
struct WeaponComponent
|
struct WeaponComponent
|
||||||
{
|
{
|
||||||
float damage;
|
float damage;
|
||||||
float range;
|
float range_tiles;
|
||||||
float fireRateHz;
|
float fireRateHz;
|
||||||
float cooldownTicks;
|
float cooldownTicks;
|
||||||
std::optional<entt::entity> currentTarget;
|
std::optional<entt::entity> currentTarget;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "ShipIdentityComponent.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "StationBodyComponent.h"
|
#include "StationBodyComponent.h"
|
||||||
#include "ThreatResponseBehaviorComponent.h"
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Shared helpers for repair targeting
|
// Shared helpers for repair targeting
|
||||||
@@ -72,6 +73,7 @@ static std::vector<RepairableInfo> buildRepairables(EntityAdmin& admin)
|
|||||||
|
|
||||||
void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
admin.forEach<HomeReturnBehaviorComponent, HealthComponent, MovementIntentComponent>(
|
admin.forEach<HomeReturnBehaviorComponent, HealthComponent, MovementIntentComponent>(
|
||||||
[](entt::entity /*e*/, const HomeReturnBehaviorComponent& homeReturnBehavior,
|
[](entt::entity /*e*/, const HomeReturnBehaviorComponent& homeReturnBehavior,
|
||||||
const HealthComponent& h, MovementIntentComponent& intent)
|
const HealthComponent& h, MovementIntentComponent& intent)
|
||||||
@@ -92,6 +94,7 @@ void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
|||||||
|
|
||||||
void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSystem& buildings)
|
void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSystem& buildings)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
// Snapshot all combatant entities for target acquisition.
|
// Snapshot all combatant entities for target acquisition.
|
||||||
struct CombatantInfo
|
struct CombatantInfo
|
||||||
{
|
{
|
||||||
@@ -129,7 +132,7 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
|||||||
PositionComponent& pos, FactionComponent& faction,
|
PositionComponent& pos, FactionComponent& faction,
|
||||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
const float range = sensor.value;
|
const float range = sensor.value_tiles;
|
||||||
|
|
||||||
// Validate current target.
|
// Validate current target.
|
||||||
bool targetValid = false;
|
bool targetValid = false;
|
||||||
@@ -219,6 +222,7 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
|||||||
|
|
||||||
void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
std::vector<RepairableInfo> repairables = buildRepairables(admin);
|
std::vector<RepairableInfo> repairables = buildRepairables(admin);
|
||||||
|
|
||||||
// Snapshot enemy ships for threat detection.
|
// Snapshot enemy ships for threat detection.
|
||||||
@@ -247,7 +251,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
bool enemyNearby = false;
|
bool enemyNearby = false;
|
||||||
for (const EnemyInfo& enemy : enemies)
|
for (const EnemyInfo& enemy : enemies)
|
||||||
{
|
{
|
||||||
if ((enemy.position - pos.value).length() <= sensor.value)
|
if ((enemy.position - pos.value).length() <= sensor.value_tiles)
|
||||||
{
|
{
|
||||||
enemyNearby = true;
|
enemyNearby = true;
|
||||||
break;
|
break;
|
||||||
@@ -281,7 +285,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
if (!targetValid)
|
if (!targetValid)
|
||||||
{
|
{
|
||||||
rb.currentTarget = std::nullopt;
|
rb.currentTarget = std::nullopt;
|
||||||
float bestDist = sensor.value;
|
float bestDist = sensor.value_tiles;
|
||||||
|
|
||||||
for (const RepairableInfo& r : repairables)
|
for (const RepairableInfo& r : repairables)
|
||||||
{
|
{
|
||||||
@@ -327,6 +331,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
|
|
||||||
void AiSystem::tickRepairTools(EntityAdmin& admin)
|
void AiSystem::tickRepairTools(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
const std::vector<RepairableInfo> repairables = buildRepairables(admin);
|
const std::vector<RepairableInfo> repairables = buildRepairables(admin);
|
||||||
|
|
||||||
admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
|
admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
|
||||||
@@ -350,7 +355,7 @@ void AiSystem::tickRepairTools(EntityAdmin& admin)
|
|||||||
const float dist =
|
const float dist =
|
||||||
(admin.get<PositionComponent>(preferred).value
|
(admin.get<PositionComponent>(preferred).value
|
||||||
- ownerPos.value).length();
|
- ownerPos.value).length();
|
||||||
if (th.hp > 0.0f && th.hp < th.maxHp && dist <= rt.range)
|
if (th.hp > 0.0f && th.hp < th.maxHp && dist <= rt.range_tiles)
|
||||||
{
|
{
|
||||||
rt.currentTarget = rb.currentTarget;
|
rt.currentTarget = rb.currentTarget;
|
||||||
th.hp = std::min(th.hp + rt.ratePerTick, th.maxHp);
|
th.hp = std::min(th.hp + rt.ratePerTick, th.maxHp);
|
||||||
@@ -361,7 +366,7 @@ void AiSystem::tickRepairTools(EntityAdmin& admin)
|
|||||||
|
|
||||||
// Preferred target unavailable; scan for nearest damaged friendly in range.
|
// Preferred target unavailable; scan for nearest damaged friendly in range.
|
||||||
rt.currentTarget = std::nullopt;
|
rt.currentTarget = std::nullopt;
|
||||||
float bestDist = rt.range;
|
float bestDist = rt.range_tiles;
|
||||||
for (const RepairableInfo& r : repairables)
|
for (const RepairableInfo& r : repairables)
|
||||||
{
|
{
|
||||||
if (r.isEnemy) { continue; }
|
if (r.isEnemy) { continue; }
|
||||||
@@ -389,6 +394,7 @@ void AiSystem::tickRepairTools(EntityAdmin& admin)
|
|||||||
void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||||
BuildingSystem& buildings)
|
BuildingSystem& buildings)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
// Snapshot enemy ships for threat detection.
|
// Snapshot enemy ships for threat detection.
|
||||||
struct EnemyShipPos
|
struct EnemyShipPos
|
||||||
{
|
{
|
||||||
@@ -435,7 +441,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
PositionComponent& pos,
|
PositionComponent& pos,
|
||||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
const float collectRange = salvageBehavior.maxCollectionRange;
|
const float collectRange = salvageBehavior.maxCollectionRange_tiles;
|
||||||
const AggregatedCargo& cargoState = cargoByShip[e];
|
const AggregatedCargo& cargoState = cargoByShip[e];
|
||||||
|
|
||||||
// Assign nearest SalvageBay if needed.
|
// Assign nearest SalvageBay if needed.
|
||||||
@@ -524,7 +530,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
}
|
}
|
||||||
for (const ScrapInfo& si : allScrap)
|
for (const ScrapInfo& si : allScrap)
|
||||||
{
|
{
|
||||||
if ((si.position - pos.value).length() > c.collectionRange) { continue; }
|
if ((si.position - pos.value).length() > c.collectionRange_tiles) { continue; }
|
||||||
if (scraps.consume(si.entity))
|
if (scraps.consume(si.entity))
|
||||||
{
|
{
|
||||||
++c.current;
|
++c.current;
|
||||||
@@ -549,7 +555,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float bestDist = sensor.value;
|
float bestDist = sensor.value_tiles;
|
||||||
std::optional<QVector2D> bestPos;
|
std::optional<QVector2D> bestPos;
|
||||||
for (const ScrapInfo& si : allScrap)
|
for (const ScrapInfo& si : allScrap)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "SensorRangeComponent.h"
|
#include "SensorRangeComponent.h"
|
||||||
#include "ShipIdentityComponent.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ThreatResponseBehaviorComponent.h"
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
#include "tracing.h"
|
||||||
#include "WeaponComponent.h"
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
static constexpr Tick kWeaponImpactDelayTicks = 5;
|
static constexpr Tick kWeaponImpactDelayTicks = 5;
|
||||||
@@ -22,6 +23,7 @@ void CombatSystem::tick(Tick currentTick,
|
|||||||
BuildingSystem& /*buildings*/,
|
BuildingSystem& /*buildings*/,
|
||||||
std::vector<FireEvent>& outFireEvents)
|
std::vector<FireEvent>& outFireEvents)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
// All weapons (ships and stations) are child entities linked via ModuleOwnerComponent.
|
// All weapons (ships and stations) are child entities linked via ModuleOwnerComponent.
|
||||||
admin.forEach<WeaponComponent, ModuleOwnerComponent>(
|
admin.forEach<WeaponComponent, ModuleOwnerComponent>(
|
||||||
[&](entt::entity /*e*/, WeaponComponent& weapon, const ModuleOwnerComponent& owner)
|
[&](entt::entity /*e*/, WeaponComponent& weapon, const ModuleOwnerComponent& owner)
|
||||||
@@ -67,7 +69,7 @@ void CombatSystem::resolveWeapon(
|
|||||||
{
|
{
|
||||||
const float distanceSquared =
|
const float distanceSquared =
|
||||||
(ownPos.value - admin.get<PositionComponent>(t).value).lengthSquared();
|
(ownPos.value - admin.get<PositionComponent>(t).value).lengthSquared();
|
||||||
if (distanceSquared > weapon.range * weapon.range)
|
if (distanceSquared > weapon.range_tiles * weapon.range_tiles)
|
||||||
{
|
{
|
||||||
weapon.currentTarget = std::nullopt;
|
weapon.currentTarget = std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -79,8 +81,8 @@ void CombatSystem::resolveWeapon(
|
|||||||
if (!weapon.currentTarget)
|
if (!weapon.currentTarget)
|
||||||
{
|
{
|
||||||
const float acquisitionRange = admin.hasAll<SensorRangeComponent>(shipEntity)
|
const float acquisitionRange = admin.hasAll<SensorRangeComponent>(shipEntity)
|
||||||
? admin.get<SensorRangeComponent>(shipEntity).value
|
? admin.get<SensorRangeComponent>(shipEntity).value_tiles
|
||||||
: weapon.range;
|
: weapon.range_tiles;
|
||||||
float bestDistanceSquared = acquisitionRange * acquisitionRange;
|
float bestDistanceSquared = acquisitionRange * acquisitionRange;
|
||||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||||
[&](entt::entity candidate, const ShipIdentityComponent& /*si*/,
|
[&](entt::entity candidate, const ShipIdentityComponent& /*si*/,
|
||||||
@@ -124,6 +126,7 @@ void CombatSystem::resolveWeapon(
|
|||||||
|
|
||||||
void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
|
void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
|
std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
|
||||||
while (it != m_pendingDamage.end())
|
while (it != m_pendingDamage.end())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "FacingComponent.h"
|
#include "FacingComponent.h"
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
static float wrapAngle(float a)
|
static float wrapAngle(float a)
|
||||||
{
|
{
|
||||||
@@ -21,31 +22,33 @@ static float wrapAngle(float a)
|
|||||||
|
|
||||||
void DynamicBodySystem::tick(EntityAdmin& admin)
|
void DynamicBodySystem::tick(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent>(
|
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent>(
|
||||||
[](entt::entity /*e*/, PositionComponent& pos, FacingComponent& facing,
|
[](entt::entity /*e*/, PositionComponent& pos, FacingComponent& facing,
|
||||||
DynamicBodyComponent& body)
|
DynamicBodyComponent& body)
|
||||||
{
|
{
|
||||||
// Integrate angular velocity, clamp to max rotation speed, then advance facing.
|
// Integrate angular velocity, clamp to max rotation speed, then advance facing.
|
||||||
body.angularVelocity += body.angularAcceleration;
|
body.angularVelocity_rpt += body.angularAcceleration_rptt;
|
||||||
body.angularVelocity = std::max(-body.maxRotationSpeedPerTick,
|
body.angularVelocity_rpt = std::max(-body.maxRotationSpeed_rpt,
|
||||||
std::min(body.angularVelocity,
|
std::min(body.angularVelocity_rpt,
|
||||||
body.maxRotationSpeedPerTick));
|
body.maxRotationSpeed_rpt));
|
||||||
facing.radians = wrapAngle(facing.radians + body.angularVelocity);
|
facing.radians = wrapAngle(facing.radians + body.angularVelocity_rpt);
|
||||||
|
|
||||||
// Integrate linear velocity and cap to max speed.
|
// Integrate linear velocity and cap to max speed.
|
||||||
body.velocity += body.linearAcceleration;
|
body.velocity_tpt += body.linearAcceleration_tptt;
|
||||||
const float speed = body.velocity.length();
|
const float speed = body.velocity_tpt.length();
|
||||||
if (speed > body.maxSpeedPerTick)
|
if (speed > body.maxSpeed_tpt)
|
||||||
{
|
{
|
||||||
body.velocity = body.velocity.normalized() * body.maxSpeedPerTick;
|
body.velocity_tpt = body.velocity_tpt.normalized() * body.maxSpeed_tpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance position.
|
// Advance position.
|
||||||
pos.value += body.velocity;
|
pos.value += body.velocity_tpt;
|
||||||
|
|
||||||
// Reset per-tick fields so stale values don't linger if the intent
|
// Reset per-tick fields so stale values don't linger if the intent
|
||||||
// system is skipped for this entity in a future tick.
|
// system is skipped for this entity in a future tick.
|
||||||
body.linearAcceleration = QVector2D(0.0f, 0.0f);
|
body.linearAcceleration_tptt = QVector2D(0.0f, 0.0f);
|
||||||
body.angularAcceleration = 0.0f;
|
body.angularAcceleration_rptt = 0.0f;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "FacingComponent.h"
|
#include "FacingComponent.h"
|
||||||
#include "MovementIntentComponent.h"
|
#include "MovementIntentComponent.h"
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
static float wrapAngle(float a)
|
static float wrapAngle(float a)
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,7 @@ static float wrapAngle(float a)
|
|||||||
|
|
||||||
void MovementIntentSystem::tick(EntityAdmin& admin)
|
void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent,
|
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent,
|
||||||
MovementIntentComponent>(
|
MovementIntentComponent>(
|
||||||
[](entt::entity /*e*/, const PositionComponent& pos, const FacingComponent& facing,
|
[](entt::entity /*e*/, const PositionComponent& pos, const FacingComponent& facing,
|
||||||
@@ -30,16 +32,16 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
if (intent.priority == 0)
|
if (intent.priority == 0)
|
||||||
{
|
{
|
||||||
// No movement intent: brake using available thrust.
|
// No movement intent: brake using available thrust.
|
||||||
const float linearBraking = std::min(body.velocity.length(),
|
const float linearBraking = std::min(body.velocity_tpt.length(),
|
||||||
body.maneuveringAccelerationPerTick);
|
body.maneuveringAcceleration_tptt);
|
||||||
body.linearAcceleration = (body.velocity.length() > 0.0001f)
|
body.linearAcceleration_tptt = (body.velocity_tpt.length() > 0.0001f)
|
||||||
? -body.velocity.normalized() * linearBraking
|
? -body.velocity_tpt.normalized() * linearBraking
|
||||||
: QVector2D(0.0f, 0.0f);
|
: QVector2D(0.0f, 0.0f);
|
||||||
|
|
||||||
const float angBraking = std::min(std::abs(body.angularVelocity),
|
const float angBraking = std::min(std::abs(body.angularVelocity_rpt),
|
||||||
body.angularAccelerationPerTick);
|
body.maxAngularAcceleration_rptt);
|
||||||
body.angularAcceleration =
|
body.angularAcceleration_rptt =
|
||||||
(body.angularVelocity >= 0.0f) ? -angBraking : angBraking;
|
(body.angularVelocity_rpt >= 0.0f) ? -angBraking : angBraking;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +52,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
{
|
{
|
||||||
// Already at target: no new thrust. The ship drifts; it will
|
// Already at target: no new thrust. The ship drifts; it will
|
||||||
// re-approach next tick once it has moved away.
|
// re-approach next tick once it has moved away.
|
||||||
body.linearAcceleration = QVector2D(0.0f, 0.0f);
|
body.linearAcceleration_tptt = QVector2D(0.0f, 0.0f);
|
||||||
body.angularAcceleration = 0.0f;
|
body.angularAcceleration_rptt = 0.0f;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +62,11 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
const float desiredAngle = std::atan2(delta.y(), delta.x());
|
const float desiredAngle = std::atan2(delta.y(), delta.x());
|
||||||
const float angleDiff = wrapAngle(desiredAngle - facing.radians);
|
const float angleDiff = wrapAngle(desiredAngle - facing.radians);
|
||||||
|
|
||||||
const float rotDelta = std::max(-body.angularAccelerationPerTick,
|
const float rotDelta = std::max(-body.maxAngularAcceleration_rptt,
|
||||||
std::min(angleDiff,
|
std::min(angleDiff,
|
||||||
body.angularAccelerationPerTick));
|
body.maxAngularAcceleration_rptt));
|
||||||
|
|
||||||
float newAngVel = body.angularVelocity + rotDelta;
|
float newAngVel = body.angularVelocity_rpt + rotDelta;
|
||||||
|
|
||||||
// Overshoot prevention: if the accumulated angular velocity already
|
// Overshoot prevention: if the accumulated angular velocity already
|
||||||
// exceeds the remaining angle, snap it to exactly that angle so the
|
// exceeds the remaining angle, snap it to exactly that angle so the
|
||||||
@@ -75,8 +77,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
newAngVel = angleDiff;
|
newAngVel = angleDiff;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.angularAcceleration = newAngVel - body.angularVelocity;
|
body.angularAcceleration_rptt = newAngVel - body.angularVelocity_rpt;
|
||||||
// DynamicBodySystem applies the clamp to maxRotationSpeedPerTick after
|
// DynamicBodySystem applies the clamp to maxRotationSpeed_rpt after
|
||||||
// integrating, so we do not clamp here.
|
// integrating, so we do not clamp here.
|
||||||
|
|
||||||
// --- Linear acceleration ---
|
// --- Linear acceleration ---
|
||||||
@@ -88,22 +90,22 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
const QVector2D facingVec(std::cos(projectedRadians),
|
const QVector2D facingVec(std::cos(projectedRadians),
|
||||||
std::sin(projectedRadians));
|
std::sin(projectedRadians));
|
||||||
|
|
||||||
const float manAccel = body.maneuveringAccelerationPerTick;
|
const float manAccel = body.maneuveringAcceleration_tptt;
|
||||||
const float stoppingDist = (body.maxSpeedPerTick * body.maxSpeedPerTick)
|
const float stoppingDist = (body.maxSpeed_tpt * body.maxSpeed_tpt)
|
||||||
/ (2.0f * manAccel);
|
/ (2.0f * manAccel);
|
||||||
// Cap to dist so the ship never overshoots the target in a single tick.
|
// Cap to dist so the ship never overshoots the target in a single tick.
|
||||||
const float baseDesiredSpeed = (dist <= stoppingDist)
|
const float baseDesiredSpeed = (dist <= stoppingDist)
|
||||||
? std::sqrt(2.0f * manAccel * dist)
|
? std::sqrt(2.0f * manAccel * dist)
|
||||||
: body.maxSpeedPerTick;
|
: body.maxSpeed_tpt;
|
||||||
const float desiredSpeed = std::min(dist, baseDesiredSpeed);
|
const float desiredSpeed = std::min(dist, baseDesiredSpeed);
|
||||||
|
|
||||||
const QVector2D desiredVel = delta.normalized() * desiredSpeed;
|
const QVector2D desiredVel = delta.normalized() * desiredSpeed;
|
||||||
const QVector2D velError = desiredVel - body.velocity;
|
const QVector2D velError = desiredVel - body.velocity_tpt;
|
||||||
|
|
||||||
const float mainAligned = std::max(0.0f,
|
const float mainAligned = std::max(0.0f,
|
||||||
QVector2D::dotProduct(velError, facingVec));
|
QVector2D::dotProduct(velError, facingVec));
|
||||||
const float mainApplied = std::min(mainAligned,
|
const float mainApplied = std::min(mainAligned,
|
||||||
body.mainAccelerationPerTick);
|
body.mainAcceleration_tptt);
|
||||||
const QVector2D mainDelta = facingVec * mainApplied;
|
const QVector2D mainDelta = facingVec * mainApplied;
|
||||||
|
|
||||||
const QVector2D remaining = velError - mainDelta;
|
const QVector2D remaining = velError - mainDelta;
|
||||||
@@ -112,6 +114,7 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
|||||||
? remaining.normalized() * manAccel
|
? remaining.normalized() * manAccel
|
||||||
: remaining;
|
: remaining;
|
||||||
|
|
||||||
body.linearAcceleration = mainDelta + maneuverDelta;
|
body.linearAcceleration_tptt = mainDelta + maneuverDelta;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
#include "ScrapDataComponent.h"
|
#include "ScrapDataComponent.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
ScrapSystem::ScrapSystem(EntityAdmin& admin)
|
ScrapSystem::ScrapSystem(EntityAdmin& admin)
|
||||||
: m_admin(admin)
|
: m_admin(admin)
|
||||||
@@ -17,6 +18,7 @@ entt::entity ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt)
|
|||||||
|
|
||||||
void ScrapSystem::tickDespawn(Tick currentTick)
|
void ScrapSystem::tickDespawn(Tick currentTick)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
std::vector<entt::entity> expired;
|
std::vector<entt::entity> expired;
|
||||||
m_admin.forEach<DespawnAtComponent>(
|
m_admin.forEach<DespawnAtComponent>(
|
||||||
[&expired, currentTick](entt::entity e, DespawnAtComponent& d)
|
[&expired, currentTick](entt::entity e, DespawnAtComponent& d)
|
||||||
@@ -54,3 +56,4 @@ std::vector<ScrapInfo> ScrapSystem::allScrapInfo() const
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "SensorRangeComponent.h"
|
#include "SensorRangeComponent.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
#include "ThreatResponseBehaviorComponent.h"
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
#include "tracing.h"
|
||||||
#include "WeaponComponent.h"
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin)
|
ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin)
|
||||||
@@ -61,30 +62,32 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
|
|
||||||
const double x = static_cast<double>(level);
|
const double x = static_cast<double>(level);
|
||||||
const float tickRate = static_cast<float>(kTickRateHz);
|
const float tickRate = static_cast<float>(kTickRateHz);
|
||||||
|
const float tileSize = static_cast<float>(m_config.world.tileSize_m);
|
||||||
|
|
||||||
float hp = static_cast<float>(def->health.hpFormula.evaluate(x));
|
float hp = static_cast<float>(def->health.hpFormula.evaluate(x));
|
||||||
float maxHp = hp;
|
float maxHp = hp;
|
||||||
float maxSpeedPerTick = static_cast<float>(def->movement.speedFormula.evaluate(x))
|
float maxSpeed_tpt = static_cast<float>(def->movement.speedFormula.evaluate(x))
|
||||||
/ tickRate;
|
/ tileSize / tickRate;
|
||||||
float mainAccelPerTick = static_cast<float>(
|
float mainAcceleration_tptt = static_cast<float>(
|
||||||
def->movement.mainAccelerationFormula.evaluate(x))
|
def->movement.mainAccelerationFormula.evaluate(x))
|
||||||
/ tickRate;
|
/ tileSize / tickRate;
|
||||||
float maneuveringAccelPerTick = static_cast<float>(
|
float maneuveringAcceleration_tptt = static_cast<float>(
|
||||||
def->movement.maneuveringAccelerationFormula.evaluate(x))
|
def->movement.maneuveringAccelerationFormula.evaluate(x))
|
||||||
/ tickRate;
|
/ tileSize / tickRate;
|
||||||
float angularAccelPerTick = static_cast<float>(
|
float maxAngularAcceleration_rptt = static_cast<float>(
|
||||||
def->movement.angularAccelerationFormula.evaluate(x))
|
def->movement.angularAccelerationFormula.evaluate(x))
|
||||||
/ tickRate;
|
/ tickRate;
|
||||||
float maxRotationSpeedPerTick = static_cast<float>(
|
float maxRotationSpeed_rpt = static_cast<float>(
|
||||||
def->movement.maxRotationSpeedFormula.evaluate(x))
|
def->movement.maxRotationSpeedFormula.evaluate(x))
|
||||||
/ tickRate;
|
/ tickRate;
|
||||||
float sensorRange = static_cast<float>(
|
float sensorRange_tiles = static_cast<float>(
|
||||||
def->sensor.sensorRangeFormula.evaluate(x));
|
def->sensor.sensorRangeFormula.evaluate(x))
|
||||||
|
/ tileSize;
|
||||||
|
|
||||||
entt::entity entity = m_admin.spawnShip(
|
entt::entity entity = m_admin.spawnShip(
|
||||||
position, hp, maxHp,
|
position, hp, maxHp,
|
||||||
maxSpeedPerTick, mainAccelPerTick, maneuveringAccelPerTick,
|
maxSpeed_tpt, mainAcceleration_tptt, maneuveringAcceleration_tptt,
|
||||||
angularAccelPerTick, maxRotationSpeedPerTick, sensorRange,
|
maxAngularAcceleration_rptt, maxRotationSpeed_rpt, sensorRange_tiles,
|
||||||
level, schematicId, isEnemy);
|
level, schematicId, isEnemy);
|
||||||
|
|
||||||
// Determine module list: configured layout takes precedence over default.
|
// Determine module list: configured layout takes precedence over default.
|
||||||
@@ -108,8 +111,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
WeaponComponent w;
|
WeaponComponent w;
|
||||||
w.damage = static_cast<float>(
|
w.damage = static_cast<float>(
|
||||||
modDef->weaponCapability->damageFormula.evaluate(mx));
|
modDef->weaponCapability->damageFormula.evaluate(mx));
|
||||||
w.range = static_cast<float>(
|
w.range_tiles = static_cast<float>(
|
||||||
modDef->weaponCapability->attackRangeFormula.evaluate(mx));
|
modDef->weaponCapability->attackRangeFormula.evaluate(mx)) / tileSize;
|
||||||
w.fireRateHz = static_cast<float>(
|
w.fireRateHz = static_cast<float>(
|
||||||
modDef->weaponCapability->attackRateFormula.evaluate(mx));
|
modDef->weaponCapability->attackRateFormula.evaluate(mx));
|
||||||
w.cooldownTicks = 0.0f;
|
w.cooldownTicks = 0.0f;
|
||||||
@@ -127,8 +130,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
cargo.capacity = static_cast<int>(
|
cargo.capacity = static_cast<int>(
|
||||||
modDef->salvageCapability->cargoCapacityFormula.evaluate(mx));
|
modDef->salvageCapability->cargoCapacityFormula.evaluate(mx));
|
||||||
cargo.current = 0;
|
cargo.current = 0;
|
||||||
cargo.collectionRange = static_cast<float>(
|
cargo.collectionRange_tiles = static_cast<float>(
|
||||||
modDef->salvageCapability->collectionRangeFormula.evaluate(mx));
|
modDef->salvageCapability->collectionRangeFormula.evaluate(mx)) / tileSize;
|
||||||
const double rate = modDef->salvageCapability->collectionRateFormula.evaluate(mx);
|
const double rate = modDef->salvageCapability->collectionRateFormula.evaluate(mx);
|
||||||
cargo.collectionIntervalTicks = (rate > 0.0)
|
cargo.collectionIntervalTicks = (rate > 0.0)
|
||||||
? static_cast<int>(kTickRateHz / rate + 0.5)
|
? static_cast<int>(kTickRateHz / rate + 0.5)
|
||||||
@@ -147,8 +150,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
rt.ratePerTick = static_cast<float>(
|
rt.ratePerTick = static_cast<float>(
|
||||||
modDef->repairCapability->repairRateFormula.evaluate(mx))
|
modDef->repairCapability->repairRateFormula.evaluate(mx))
|
||||||
/ static_cast<float>(kTickRateHz);
|
/ static_cast<float>(kTickRateHz);
|
||||||
rt.range = static_cast<float>(
|
rt.range_tiles = static_cast<float>(
|
||||||
modDef->repairCapability->repairRangeFormula.evaluate(mx));
|
modDef->repairCapability->repairRangeFormula.evaluate(mx)) / tileSize;
|
||||||
rt.currentTarget = std::nullopt;
|
rt.currentTarget = std::nullopt;
|
||||||
|
|
||||||
entt::entity child = m_admin.createModuleEntity();
|
entt::entity child = m_admin.createModuleEntity();
|
||||||
@@ -206,6 +209,26 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Range stat additive modifiers are expressed in metres in config; convert to tiles.
|
||||||
|
const double tileSizeD = static_cast<double>(m_config.world.tileSize_m);
|
||||||
|
const char* const kRangeStats[] = {
|
||||||
|
"sensor_range", "attack_range", "collection_range", "repair_range"
|
||||||
|
};
|
||||||
|
std::map<std::string, std::pair<double, double>>* allModMaps[] = {
|
||||||
|
&hullMods, &weaponMods, &salvageMods, &repairMods
|
||||||
|
};
|
||||||
|
for (const char* stat : kRangeStats)
|
||||||
|
{
|
||||||
|
for (std::map<std::string, std::pair<double, double>>* mods : allModMaps)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::pair<double, double>>::iterator it = mods->find(stat);
|
||||||
|
if (it != mods->end())
|
||||||
|
{
|
||||||
|
it->second.second /= tileSizeD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: apply a modifier map to a float stat.
|
// Helper: apply a modifier map to a float stat.
|
||||||
auto applyMod = [](float& stat, const std::string& name,
|
auto applyMod = [](float& stat, const std::string& name,
|
||||||
const std::map<std::string, std::pair<double, double>>& mods)
|
const std::map<std::string, std::pair<double, double>>& mods)
|
||||||
@@ -225,30 +248,30 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
|
DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
|
||||||
SensorRangeComponent& sensor = m_admin.get<SensorRangeComponent>(entity);
|
SensorRangeComponent& sensor = m_admin.get<SensorRangeComponent>(entity);
|
||||||
|
|
||||||
applyMod(health.maxHp, "hp", hullMods);
|
applyMod(health.maxHp, "hp", hullMods);
|
||||||
health.hp = health.maxHp;
|
health.hp = health.maxHp;
|
||||||
applyMod(dynamics.maxSpeedPerTick, "speed", hullMods);
|
applyMod(dynamics.maxSpeed_tpt, "speed", hullMods);
|
||||||
applyMod(dynamics.mainAccelerationPerTick, "main_acceleration", hullMods);
|
applyMod(dynamics.mainAcceleration_tptt, "main_acceleration", hullMods);
|
||||||
applyMod(dynamics.maneuveringAccelerationPerTick, "maneuvering_acceleration", hullMods);
|
applyMod(dynamics.maneuveringAcceleration_tptt, "maneuvering_acceleration", hullMods);
|
||||||
applyMod(dynamics.angularAccelerationPerTick, "angular_acceleration", hullMods);
|
applyMod(dynamics.maxAngularAcceleration_rptt, "angular_acceleration", hullMods);
|
||||||
applyMod(dynamics.maxRotationSpeedPerTick, "max_rotation_speed", hullMods);
|
applyMod(dynamics.maxRotationSpeed_rpt, "max_rotation_speed", hullMods);
|
||||||
applyMod(sensor.value, "sensor_range", hullMods);
|
applyMod(sensor.value_tiles, "sensor_range", hullMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply weapon modifiers to each weapon child.
|
// Apply weapon modifiers to each weapon child.
|
||||||
for (entt::entity child : weaponChildren)
|
for (entt::entity child : weaponChildren)
|
||||||
{
|
{
|
||||||
WeaponComponent& w = m_admin.get<WeaponComponent>(child);
|
WeaponComponent& w = m_admin.get<WeaponComponent>(child);
|
||||||
applyMod(w.damage, "damage", weaponMods);
|
applyMod(w.damage, "damage", weaponMods);
|
||||||
applyMod(w.range, "attack_range", weaponMods);
|
applyMod(w.range_tiles, "attack_range", weaponMods);
|
||||||
applyMod(w.fireRateHz, "attack_rate", weaponMods);
|
applyMod(w.fireRateHz, "attack_rate", weaponMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply salvage modifiers to each salvage child.
|
// Apply salvage modifiers to each salvage child.
|
||||||
for (entt::entity child : salvageChildren)
|
for (entt::entity child : salvageChildren)
|
||||||
{
|
{
|
||||||
SalvageCargoComponent& c = m_admin.get<SalvageCargoComponent>(child);
|
SalvageCargoComponent& c = m_admin.get<SalvageCargoComponent>(child);
|
||||||
float fRange = c.collectionRange;
|
float fRange = c.collectionRange_tiles;
|
||||||
float fCapacity = static_cast<float>(c.capacity);
|
float fCapacity = static_cast<float>(c.capacity);
|
||||||
// Apply rate modifier: compute rate from interval, apply multiplier, convert back.
|
// Apply rate modifier: compute rate from interval, apply multiplier, convert back.
|
||||||
float fRate = (c.collectionIntervalTicks > 0)
|
float fRate = (c.collectionIntervalTicks > 0)
|
||||||
@@ -257,7 +280,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
applyMod(fRange, "collection_range", salvageMods);
|
applyMod(fRange, "collection_range", salvageMods);
|
||||||
applyMod(fCapacity, "cargo_capacity", salvageMods);
|
applyMod(fCapacity, "cargo_capacity", salvageMods);
|
||||||
applyMod(fRate, "collection_rate", salvageMods);
|
applyMod(fRate, "collection_rate", salvageMods);
|
||||||
c.collectionRange = fRange;
|
c.collectionRange_tiles = fRange;
|
||||||
c.capacity = static_cast<int>(fCapacity + 0.5f);
|
c.capacity = static_cast<int>(fCapacity + 0.5f);
|
||||||
c.collectionIntervalTicks = (fRate > 0.0f)
|
c.collectionIntervalTicks = (fRate > 0.0f)
|
||||||
? static_cast<int>(static_cast<float>(kTickRateHz) / fRate + 0.5f)
|
? static_cast<int>(static_cast<float>(kTickRateHz) / fRate + 0.5f)
|
||||||
@@ -268,8 +291,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
for (entt::entity child : repairChildren)
|
for (entt::entity child : repairChildren)
|
||||||
{
|
{
|
||||||
RepairToolComponent& rt = m_admin.get<RepairToolComponent>(child);
|
RepairToolComponent& rt = m_admin.get<RepairToolComponent>(child);
|
||||||
applyMod(rt.ratePerTick, "repair_rate", repairMods);
|
applyMod(rt.ratePerTick, "repair_rate", repairMods);
|
||||||
applyMod(rt.range, "repair_range", repairMods);
|
applyMod(rt.range_tiles, "repair_range", repairMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Pass 3: attach behavior components based on capability presence -----
|
// --- Pass 3: attach behavior components based on capability presence -----
|
||||||
@@ -291,14 +314,14 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
float maxCollRange = 0.0f;
|
float maxCollRange = 0.0f;
|
||||||
for (entt::entity child : salvageChildren)
|
for (entt::entity child : salvageChildren)
|
||||||
{
|
{
|
||||||
const float r = m_admin.get<SalvageCargoComponent>(child).collectionRange;
|
const float r = m_admin.get<SalvageCargoComponent>(child).collectionRange_tiles;
|
||||||
if (r > maxCollRange) { maxCollRange = r; }
|
if (r > maxCollRange) { maxCollRange = r; }
|
||||||
}
|
}
|
||||||
|
|
||||||
SalvageBehaviorComponent sb;
|
SalvageBehaviorComponent sb;
|
||||||
sb.scrapTarget = std::nullopt;
|
sb.scrapTarget = std::nullopt;
|
||||||
sb.deliveryBay = kInvalidBuildingId;
|
sb.deliveryBay = kInvalidBuildingId;
|
||||||
sb.maxCollectionRange = maxCollRange;
|
sb.maxCollectionRange_tiles = maxCollRange;
|
||||||
m_admin.addComponent<SalvageBehaviorComponent>(entity, sb);
|
m_admin.addComponent<SalvageBehaviorComponent>(entity, sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,13 +330,13 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
float maxRepairRange = 0.0f;
|
float maxRepairRange = 0.0f;
|
||||||
for (entt::entity child : repairChildren)
|
for (entt::entity child : repairChildren)
|
||||||
{
|
{
|
||||||
const float r = m_admin.get<RepairToolComponent>(child).range;
|
const float r = m_admin.get<RepairToolComponent>(child).range_tiles;
|
||||||
if (r > maxRepairRange) { maxRepairRange = r; }
|
if (r > maxRepairRange) { maxRepairRange = r; }
|
||||||
}
|
}
|
||||||
|
|
||||||
RepairBehaviorComponent rb;
|
RepairBehaviorComponent rb;
|
||||||
rb.currentTarget = std::nullopt;
|
rb.currentTarget = std::nullopt;
|
||||||
rb.maxRepairRange = maxRepairRange;
|
rb.maxRepairRange_tiles = maxRepairRange;
|
||||||
m_admin.addComponent<RepairBehaviorComponent>(entity, rb);
|
m_admin.addComponent<RepairBehaviorComponent>(entity, rb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +357,7 @@ void ShipSystem::despawn(entt::entity entity)
|
|||||||
|
|
||||||
void ShipSystem::clearMovementIntents()
|
void ShipSystem::clearMovementIntents()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
m_admin.forEach<MovementIntentComponent>(
|
m_admin.forEach<MovementIntentComponent>(
|
||||||
[](entt::entity /*e*/, MovementIntentComponent& i)
|
[](entt::entity /*e*/, MovementIntentComponent& i)
|
||||||
{
|
{
|
||||||
@@ -348,6 +372,7 @@ void ShipSystem::setRallyPoint(QVector2D point)
|
|||||||
|
|
||||||
void ShipSystem::triggerRallyDeparture()
|
void ShipSystem::triggerRallyDeparture()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
std::vector<entt::entity> toRemove;
|
std::vector<entt::entity> toRemove;
|
||||||
m_admin.forEach<RallyBehaviorComponent, FactionComponent>(
|
m_admin.forEach<RallyBehaviorComponent, FactionComponent>(
|
||||||
[&toRemove](entt::entity e, const RallyBehaviorComponent& /*rb*/,
|
[&toRemove](entt::entity e, const RallyBehaviorComponent& /*rb*/,
|
||||||
|
|||||||
23
src/lib/eventsystem/CMakeLists.txt
Normal file
23
src/lib/eventsystem/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
add_subdirectory(event)
|
||||||
|
|
||||||
|
SET(HDRS
|
||||||
|
${HDRS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/Event.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/EventHandler.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/EventHandlerBase.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/EventManager.h
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(SRCS
|
||||||
|
${SRCS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/EventHandlerBase.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/EventManager.cpp
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(LIB_INCLUDE_PATH
|
||||||
|
${LIB_INCLUDE_PATH}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
10
src/lib/eventsystem/Event.h
Normal file
10
src/lib/eventsystem/Event.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef EVENT_H
|
||||||
|
#define EVENT_H
|
||||||
|
|
||||||
|
class Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Event() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EVENT_H
|
||||||
80
src/lib/eventsystem/EventHandler.h
Normal file
80
src/lib/eventsystem/EventHandler.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#ifndef EVENT_HANDLER_H
|
||||||
|
#define EVENT_HANDLER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "EventHandlerBase.h"
|
||||||
|
#include "EventManager.h"
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class EventHandler: public EventHandlerBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void registerForEvent()
|
||||||
|
{
|
||||||
|
EventManager::getInstance()->registerEventHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregisterForEvent()
|
||||||
|
{
|
||||||
|
EventManager::getInstance()->unregisterEventHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleBaseEvent(std::shared_ptr<const Event> event) override
|
||||||
|
{
|
||||||
|
std::shared_ptr<const T> specificEvent = std::dynamic_pointer_cast<const T>(event);
|
||||||
|
if (specificEvent)
|
||||||
|
{
|
||||||
|
handleEvent(specificEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void handleEvent(std::shared_ptr<const T> event) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename... Ts> class CombinedEventHandlerHelper
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void registerForEventsHelper() {}
|
||||||
|
void unregisterForEventsHelper() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
class CombinedEventHandlerHelper<T, Ts...>
|
||||||
|
: public EventHandler<T>
|
||||||
|
, public CombinedEventHandlerHelper<Ts...>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void registerForEventsHelper()
|
||||||
|
{
|
||||||
|
EventHandler<T>::registerForEvent();
|
||||||
|
CombinedEventHandlerHelper<Ts...>::registerForEventsHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregisterForEventsHelper()
|
||||||
|
{
|
||||||
|
EventHandler<T>::unregisterForEvent();
|
||||||
|
CombinedEventHandlerHelper<Ts...>::unregisterForEventsHelper();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
class CombinedEventHandler: public CombinedEventHandlerHelper<Ts...>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void registerForEvents()
|
||||||
|
{
|
||||||
|
CombinedEventHandlerHelper<Ts...>::registerForEventsHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregisterForEvents()
|
||||||
|
{
|
||||||
|
CombinedEventHandlerHelper<Ts...>::unregisterForEventsHelper();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EVENT_HANDLER_H
|
||||||
7
src/lib/eventsystem/EventHandlerBase.cpp
Normal file
7
src/lib/eventsystem/EventHandlerBase.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "EventHandlerBase.h"
|
||||||
|
|
||||||
|
unsigned int EventHandlerBase::s_nextId = 0;
|
||||||
|
|
||||||
|
EventHandlerBase::EventHandlerBase(): m_id(s_nextId++)
|
||||||
|
{
|
||||||
|
}
|
||||||
25
src/lib/eventsystem/EventHandlerBase.h
Normal file
25
src/lib/eventsystem/EventHandlerBase.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef EVENT_HANDLER_BASE_H
|
||||||
|
#define EVENT_HANDLER_BASE_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class Event;
|
||||||
|
class EventManager;
|
||||||
|
|
||||||
|
class EventHandlerBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static unsigned int s_nextId;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventHandlerBase();
|
||||||
|
virtual ~EventHandlerBase() = default;
|
||||||
|
virtual void handleBaseEvent(std::shared_ptr<const Event> event) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const unsigned int m_id;
|
||||||
|
|
||||||
|
friend EventManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EVENT_HANDLER_BASE_H
|
||||||
129
src/lib/eventsystem/EventManager.cpp
Normal file
129
src/lib/eventsystem/EventManager.cpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include "EventManager.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "EventHandlerBase.h"
|
||||||
|
|
||||||
|
std::shared_ptr<EventManager> EventManager::getInstance()
|
||||||
|
{
|
||||||
|
if (!s_instance)
|
||||||
|
{
|
||||||
|
s_instance = std::shared_ptr<EventManager>(new EventManager());
|
||||||
|
}
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventManager::destroyInstance()
|
||||||
|
{
|
||||||
|
if (s_instance)
|
||||||
|
{
|
||||||
|
s_instance.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<EventManager> EventManager::s_instance = std::shared_ptr<EventManager>();
|
||||||
|
|
||||||
|
void EventManager::registerEventHandler(EventHandlerBase *eventHandler)
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventHandlersMutex);
|
||||||
|
m_eventHandlers[eventHandler->m_id] = eventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventManager::unregisterEventHandler(EventHandlerBase *eventHandler)
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventHandlersMutex);
|
||||||
|
m_eventHandlers.erase(eventHandler->m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventManager::sendEventImmediately(std::shared_ptr<Event> event)
|
||||||
|
{
|
||||||
|
if (event)
|
||||||
|
{
|
||||||
|
std::set<unsigned int> eventHandlerIds;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventHandlersMutex);
|
||||||
|
for (auto it : m_eventHandlers)
|
||||||
|
{
|
||||||
|
eventHandlerIds.insert(it.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int id : eventHandlerIds)
|
||||||
|
{
|
||||||
|
// this is necessary to allow HandleBaseEvent() to remove event handlers without causing a crash
|
||||||
|
EventHandlerBase *eventHandler = nullptr;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventHandlersMutex);
|
||||||
|
auto it = m_eventHandlers.find(id);
|
||||||
|
if (it != m_eventHandlers.end())
|
||||||
|
{
|
||||||
|
eventHandler = it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventHandler)
|
||||||
|
{
|
||||||
|
eventHandler->handleBaseEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventManager::addEvent(std::shared_ptr<Event> event)
|
||||||
|
{
|
||||||
|
if (event)
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventsMutex);
|
||||||
|
m_events.push_back(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventManager::processEvents()
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<Event>> events;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventsMutex);
|
||||||
|
events = m_events;
|
||||||
|
m_events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<unsigned int> eventHandlerIds;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventHandlersMutex);
|
||||||
|
for (auto it : m_eventHandlers)
|
||||||
|
{
|
||||||
|
eventHandlerIds.insert(it.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::shared_ptr<Event> event : events)
|
||||||
|
{
|
||||||
|
for (unsigned int id : eventHandlerIds)
|
||||||
|
{
|
||||||
|
// this is necessary to allow HandleBaseEvent() to remove event handlers without causing a crash
|
||||||
|
EventHandlerBase *eventHandler = nullptr;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventHandlersMutex);
|
||||||
|
auto it = m_eventHandlers.find(id);
|
||||||
|
if (it != m_eventHandlers.end())
|
||||||
|
{
|
||||||
|
eventHandler = it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventHandler)
|
||||||
|
{
|
||||||
|
eventHandler->handleBaseEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventManager::clearEvents()
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(m_eventsMutex);
|
||||||
|
m_events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventManager::hasEvents() const
|
||||||
|
{
|
||||||
|
return !m_events.empty();
|
||||||
|
}
|
||||||
44
src/lib/eventsystem/EventManager.h
Normal file
44
src/lib/eventsystem/EventManager.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef EVENT_MANAGER_H
|
||||||
|
#define EVENT_MANAGER_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
class EventHandlerBase;
|
||||||
|
|
||||||
|
class EventManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::shared_ptr<EventManager> getInstance();
|
||||||
|
static void destroyInstance();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::shared_ptr<EventManager> s_instance;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventManager(EventManager const &) = delete;
|
||||||
|
void operator=(EventManager const &) = delete;
|
||||||
|
|
||||||
|
void registerEventHandler(EventHandlerBase *eventHandler);
|
||||||
|
void unregisterEventHandler(EventHandlerBase *eventHandler);
|
||||||
|
void sendEventImmediately(std::shared_ptr<Event> event);
|
||||||
|
void addEvent(std::shared_ptr<Event> event);
|
||||||
|
void processEvents();
|
||||||
|
void clearEvents();
|
||||||
|
|
||||||
|
bool hasEvents() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EventManager() = default;
|
||||||
|
|
||||||
|
std::map<unsigned int, EventHandlerBase *> m_eventHandlers;
|
||||||
|
std::vector<std::shared_ptr<Event>> m_events;
|
||||||
|
std::mutex m_eventHandlersMutex;
|
||||||
|
std::mutex m_eventsMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EVENT_MANAGER_H
|
||||||
17
src/lib/eventsystem/event/BossWaveUpdatedEvent.h
Normal file
17
src/lib/eventsystem/event/BossWaveUpdatedEvent.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef BOSS_WAVE_UPDATED_EVENT_H
|
||||||
|
#define BOSS_WAVE_UPDATED_EVENT_H
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
#include "Tick.h"
|
||||||
|
|
||||||
|
class BossWaveUpdatedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BossWaveUpdatedEvent(int counter, Tick countdownTicks)
|
||||||
|
: counter(counter), countdownTicks(countdownTicks) {}
|
||||||
|
const int counter;
|
||||||
|
const Tick countdownTicks;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BOSS_WAVE_UPDATED_EVENT_H
|
||||||
|
|
||||||
14
src/lib/eventsystem/event/BuildingBlocksChangedEvent.h
Normal file
14
src/lib/eventsystem/event/BuildingBlocksChangedEvent.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef BUILDING_BLOCKS_CHANGED_EVENT_H
|
||||||
|
#define BUILDING_BLOCKS_CHANGED_EVENT_H
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
class BuildingBlocksChangedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BuildingBlocksChangedEvent(int blocks) : blocks(blocks) {}
|
||||||
|
const int blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BUILDING_BLOCKS_CHANGED_EVENT_H
|
||||||
|
|
||||||
21
src/lib/eventsystem/event/CMakeLists.txt
Normal file
21
src/lib/eventsystem/event/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
SET(HDRS
|
||||||
|
${HDRS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TracePrintRequestedEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TickAdvancedEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingBlocksChangedEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/GameSpeedChangedEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/BossWaveUpdatedEvent.h
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(SRCS
|
||||||
|
${SRCS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TracePrintRequestedEvent.cpp
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIB_INCLUDE_PATH
|
||||||
|
${LIB_INCLUDE_PATH}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
14
src/lib/eventsystem/event/GameSpeedChangedEvent.h
Normal file
14
src/lib/eventsystem/event/GameSpeedChangedEvent.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef GAME_SPEED_CHANGED_EVENT_H
|
||||||
|
#define GAME_SPEED_CHANGED_EVENT_H
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
class GameSpeedChangedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit GameSpeedChangedEvent(double speed) : speed(speed) {}
|
||||||
|
const double speed;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GAME_SPEED_CHANGED_EVENT_H
|
||||||
|
|
||||||
15
src/lib/eventsystem/event/TickAdvancedEvent.h
Normal file
15
src/lib/eventsystem/event/TickAdvancedEvent.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef TICK_ADVANCED_EVENT_H
|
||||||
|
#define TICK_ADVANCED_EVENT_H
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
#include "Tick.h"
|
||||||
|
|
||||||
|
class TickAdvancedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TickAdvancedEvent(Tick tick) : tick(tick) {}
|
||||||
|
const Tick tick;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TICK_ADVANCED_EVENT_H
|
||||||
|
|
||||||
1
src/lib/eventsystem/event/TracePrintRequestedEvent.cpp
Normal file
1
src/lib/eventsystem/event/TracePrintRequestedEvent.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "TracePrintRequestedEvent.h"
|
||||||
10
src/lib/eventsystem/event/TracePrintRequestedEvent.h
Normal file
10
src/lib/eventsystem/event/TracePrintRequestedEvent.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef TRACE_PRINT_REQUESTED_EVENT_H
|
||||||
|
#define TRACE_PRINT_REQUESTED_EVENT_H
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
class TracePrintRequestedEvent : public Event
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TRACE_PRINT_REQUESTED_EVENT_H
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -46,8 +47,8 @@ QPointF BeltSystem::slotWorldPos(QPoint tile, Rotation dir, double progress)
|
|||||||
// Construction / placement
|
// Construction / placement
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
BeltSystem::BeltSystem(double beltSpeedTilesPerSecond)
|
BeltSystem::BeltSystem(double beltSpeed_tps)
|
||||||
: m_progressPerTick(beltSpeedTilesPerSecond * kTickDurationSeconds)
|
: m_progressPerTick_tpt(beltSpeed_tps * kTickDurationSeconds)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,6 +397,7 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
|||||||
|
|
||||||
void BeltSystem::tick()
|
void BeltSystem::tick()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
advanceProgress();
|
advanceProgress();
|
||||||
advanceTunnelProgress();
|
advanceTunnelProgress();
|
||||||
moveItemsToNextTile();
|
moveItemsToNextTile();
|
||||||
@@ -412,7 +414,7 @@ void BeltSystem::advanceProgress()
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < bt.itemSlots.size(); ++i)
|
for (std::size_t i = 0; i < bt.itemSlots.size(); ++i)
|
||||||
{
|
{
|
||||||
bt.itemSlots[i].progress += m_progressPerTick;
|
bt.itemSlots[i].progress += m_progressPerTick_tpt;
|
||||||
|
|
||||||
// Absolute cap: slot i cannot exceed 1.0 - i * 0.25.
|
// Absolute cap: slot i cannot exceed 1.0 - i * 0.25.
|
||||||
const double absoluteCap = 1.0 - i * 0.25;
|
const double absoluteCap = 1.0 - i * 0.25;
|
||||||
@@ -440,7 +442,7 @@ void BeltSystem::advanceProgress()
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < st.back.size(); ++i)
|
for (std::size_t i = 0; i < st.back.size(); ++i)
|
||||||
{
|
{
|
||||||
st.back[i].progress += m_progressPerTick;
|
st.back[i].progress += m_progressPerTick_tpt;
|
||||||
const double absoluteCap = 0.5 - i * 0.25;
|
const double absoluteCap = 0.5 - i * 0.25;
|
||||||
if (st.back[i].progress > absoluteCap)
|
if (st.back[i].progress > absoluteCap)
|
||||||
{
|
{
|
||||||
@@ -463,7 +465,7 @@ void BeltSystem::advanceProgress()
|
|||||||
|
|
||||||
if (st.frontA)
|
if (st.frontA)
|
||||||
{
|
{
|
||||||
st.frontA->progress += m_progressPerTick;
|
st.frontA->progress += m_progressPerTick_tpt;
|
||||||
if (st.frontA->progress > 1.0)
|
if (st.frontA->progress > 1.0)
|
||||||
{
|
{
|
||||||
st.frontA->progress = 1.0;
|
st.frontA->progress = 1.0;
|
||||||
@@ -472,7 +474,7 @@ void BeltSystem::advanceProgress()
|
|||||||
|
|
||||||
if (st.frontB)
|
if (st.frontB)
|
||||||
{
|
{
|
||||||
st.frontB->progress += m_progressPerTick;
|
st.frontB->progress += m_progressPerTick_tpt;
|
||||||
if (st.frontB->progress > 1.0)
|
if (st.frontB->progress > 1.0)
|
||||||
{
|
{
|
||||||
st.frontB->progress = 1.0;
|
st.frontB->progress = 1.0;
|
||||||
@@ -490,7 +492,7 @@ void BeltSystem::advanceTunnelProgress()
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < te.itemSlots.size(); ++i)
|
for (std::size_t i = 0; i < te.itemSlots.size(); ++i)
|
||||||
{
|
{
|
||||||
te.itemSlots[i].progress += m_progressPerTick;
|
te.itemSlots[i].progress += m_progressPerTick_tpt;
|
||||||
|
|
||||||
const double absoluteCap = 1.0 - i * 0.25;
|
const double absoluteCap = 1.0 - i * 0.25;
|
||||||
if (te.itemSlots[i].progress > absoluteCap)
|
if (te.itemSlots[i].progress > absoluteCap)
|
||||||
@@ -516,7 +518,7 @@ void BeltSystem::advanceTunnelProgress()
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < tx.itemSlots.size(); ++i)
|
for (std::size_t i = 0; i < tx.itemSlots.size(); ++i)
|
||||||
{
|
{
|
||||||
tx.itemSlots[i].progress += m_progressPerTick;
|
tx.itemSlots[i].progress += m_progressPerTick_tpt;
|
||||||
|
|
||||||
const double absoluteCap = 1.0 - i * 0.25;
|
const double absoluteCap = 1.0 - i * 0.25;
|
||||||
if (tx.itemSlots[i].progress > absoluteCap)
|
if (tx.itemSlots[i].progress > absoluteCap)
|
||||||
@@ -540,7 +542,7 @@ void BeltSystem::advanceTunnelProgress()
|
|||||||
for (std::size_t i = 0; i < link.items.size(); ++i)
|
for (std::size_t i = 0; i < link.items.size(); ++i)
|
||||||
{
|
{
|
||||||
TunnelTransitItem& ti = link.items[i];
|
TunnelTransitItem& ti = link.items[i];
|
||||||
ti.progress += m_progressPerTick;
|
ti.progress += m_progressPerTick_tpt;
|
||||||
if (ti.progress > link.length)
|
if (ti.progress > link.length)
|
||||||
{
|
{
|
||||||
ti.progress = link.length;
|
ti.progress = link.length;
|
||||||
@@ -960,3 +962,4 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ struct VisualItem
|
|||||||
class BeltSystem
|
class BeltSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit BeltSystem(double beltSpeedTilesPerSecond);
|
explicit BeltSystem(double beltSpeed_tps);
|
||||||
|
|
||||||
// -- Placement -----------------------------------------------------------
|
// -- Placement -----------------------------------------------------------
|
||||||
// Register a new belt tile. Any items already on this tile are cleared.
|
// Register a new belt tile. Any items already on this tile are cleared.
|
||||||
@@ -170,7 +170,7 @@ private:
|
|||||||
std::vector<TunnelTransitItem> items; // front (highest progress) to back
|
std::vector<TunnelTransitItem> items; // front (highest progress) to back
|
||||||
};
|
};
|
||||||
|
|
||||||
double m_progressPerTick; // beltSpeedTilesPerSecond / kTickRateHz
|
double m_progressPerTick_tpt; // beltSpeed_tps / kTickRateHz
|
||||||
|
|
||||||
std::map<std::pair<int, int>, BeltTile> m_belts;
|
std::map<std::pair<int, int>, BeltTile> m_belts;
|
||||||
std::map<std::pair<int, int>, SplitterTile> m_splitters;
|
std::map<std::pair<int, int>, SplitterTile> m_splitters;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
BuildingSystem::BuildingSystem(const GameConfig& config,
|
BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||||
BeltSystem& belts,
|
BeltSystem& belts,
|
||||||
@@ -410,6 +411,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
|
|||||||
|
|
||||||
void BuildingSystem::tickConstruction(Tick currentTick)
|
void BuildingSystem::tickConstruction(Tick currentTick)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
if (m_constructionQueue.empty())
|
if (m_constructionQueue.empty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -516,6 +518,7 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
|||||||
|
|
||||||
void BuildingSystem::tickBeltPull()
|
void BuildingSystem::tickBeltPull()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
for (Building& building : m_buildings)
|
for (Building& building : m_buildings)
|
||||||
{
|
{
|
||||||
// HQ: pull building_block items and add to global stock.
|
// HQ: pull building_block items and add to global stock.
|
||||||
@@ -591,6 +594,7 @@ void BuildingSystem::tickBeltPull()
|
|||||||
|
|
||||||
void BuildingSystem::tickProduction(Tick currentTick)
|
void BuildingSystem::tickProduction(Tick currentTick)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
for (Building& building : m_buildings)
|
for (Building& building : m_buildings)
|
||||||
{
|
{
|
||||||
// Skip types without a recipe-based production loop.
|
// Skip types without a recipe-based production loop.
|
||||||
@@ -694,6 +698,7 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
|||||||
|
|
||||||
void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
for (Building& building : m_buildings)
|
for (Building& building : m_buildings)
|
||||||
{
|
{
|
||||||
if (building.type != BuildingType::Shipyard)
|
if (building.type != BuildingType::Shipyard)
|
||||||
@@ -795,6 +800,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
|||||||
|
|
||||||
void BuildingSystem::tickBeltPush()
|
void BuildingSystem::tickBeltPush()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
for (Building& building : m_buildings)
|
for (Building& building : m_buildings)
|
||||||
{
|
{
|
||||||
if (building.outputBuffer.items.empty())
|
if (building.outputBuffer.items.empty())
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "CombatSystem.h"
|
#include "CombatSystem.h"
|
||||||
#include "DynamicBodySystem.h"
|
#include "DynamicBodySystem.h"
|
||||||
#include "FactionComponent.h"
|
#include "FactionComponent.h"
|
||||||
|
#include "EventManager.h"
|
||||||
#include "HealthComponent.h"
|
#include "HealthComponent.h"
|
||||||
#include "ModuleOwnerComponent.h"
|
#include "ModuleOwnerComponent.h"
|
||||||
#include "MovementIntentSystem.h"
|
#include "MovementIntentSystem.h"
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "StationBodyComponent.h"
|
#include "StationBodyComponent.h"
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
|
#include "tracing.h"
|
||||||
#include "WaveSystem.h"
|
#include "WaveSystem.h"
|
||||||
#include "WeaponComponent.h"
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
|||||||
, m_hqProxyEntity(entt::null)
|
, m_hqProxyEntity(entt::null)
|
||||||
, m_playerStation1Entity(entt::null)
|
, m_playerStation1Entity(entt::null)
|
||||||
, m_playerStation2Entity(entt::null)
|
, m_playerStation2Entity(entt::null)
|
||||||
, m_beltSystem(m_config.world.beltSpeedTilesPerSecond)
|
, m_beltSystem(m_config.world.beltSpeed_tps)
|
||||||
{
|
{
|
||||||
m_currentEnemyStationEntities[0] = entt::null;
|
m_currentEnemyStationEntities[0] = entt::null;
|
||||||
m_currentEnemyStationEntities[1] = entt::null;
|
m_currentEnemyStationEntities[1] = entt::null;
|
||||||
@@ -70,9 +72,13 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
|||||||
}
|
}
|
||||||
|
|
||||||
placeInitialStructures();
|
placeInitialStructures();
|
||||||
|
registerForEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
Simulation::~Simulation() = default;
|
Simulation::~Simulation()
|
||||||
|
{
|
||||||
|
unregisterForEvents();
|
||||||
|
}
|
||||||
|
|
||||||
const GameConfig& Simulation::config() const
|
const GameConfig& Simulation::config() const
|
||||||
{
|
{
|
||||||
@@ -87,6 +93,7 @@ void Simulation::reset(GameConfig newConfig, unsigned int seed)
|
|||||||
|
|
||||||
void Simulation::reset(unsigned int seed)
|
void Simulation::reset(unsigned int seed)
|
||||||
{
|
{
|
||||||
|
EventManager::getInstance()->clearEvents();
|
||||||
m_rng.seed(seed);
|
m_rng.seed(seed);
|
||||||
m_currentTick = 0;
|
m_currentTick = 0;
|
||||||
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||||
@@ -103,7 +110,7 @@ void Simulation::reset(unsigned int seed)
|
|||||||
m_schematicDropEvents.clear();
|
m_schematicDropEvents.clear();
|
||||||
|
|
||||||
m_admin.clear();
|
m_admin.clear();
|
||||||
m_beltSystem = BeltSystem(m_config.world.beltSpeedTilesPerSecond);
|
m_beltSystem = BeltSystem(m_config.world.beltSpeed_tps);
|
||||||
m_buildingSystem = std::make_unique<BuildingSystem>(
|
m_buildingSystem = std::make_unique<BuildingSystem>(
|
||||||
m_config,
|
m_config,
|
||||||
m_beltSystem,
|
m_beltSystem,
|
||||||
@@ -146,6 +153,8 @@ void Simulation::reset(unsigned int seed)
|
|||||||
|
|
||||||
void Simulation::tick()
|
void Simulation::tick()
|
||||||
{
|
{
|
||||||
|
EventManager::getInstance()->processEvents();
|
||||||
|
|
||||||
// Step 1: wave scheduler
|
// Step 1: wave scheduler
|
||||||
m_waveSystem->tickWaveScheduler(m_currentTick, *m_shipSystem,
|
m_waveSystem->tickWaveScheduler(m_currentTick, *m_shipSystem,
|
||||||
m_config.world.heightTiles);
|
m_config.world.heightTiles);
|
||||||
@@ -235,11 +244,13 @@ void Simulation::placeInitialStructures()
|
|||||||
const float psHp = static_cast<float>(
|
const float psHp = static_cast<float>(
|
||||||
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
||||||
|
|
||||||
|
const float tileSize = static_cast<float>(m_config.world.tileSize_m);
|
||||||
|
|
||||||
WeaponComponent psWeapon;
|
WeaponComponent psWeapon;
|
||||||
psWeapon.damage = static_cast<float>(
|
psWeapon.damage = static_cast<float>(
|
||||||
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
||||||
psWeapon.range = static_cast<float>(
|
psWeapon.range_tiles = static_cast<float>(
|
||||||
m_config.stations.playerStation.rangeFormula.evaluate(psLevel));
|
m_config.stations.playerStation.rangeFormula.evaluate(psLevel)) / tileSize;
|
||||||
psWeapon.fireRateHz = static_cast<float>(
|
psWeapon.fireRateHz = static_cast<float>(
|
||||||
m_config.stations.playerStation.fireRateFormula.evaluate(psLevel));
|
m_config.stations.playerStation.fireRateFormula.evaluate(psLevel));
|
||||||
psWeapon.cooldownTicks = 0.0f;
|
psWeapon.cooldownTicks = 0.0f;
|
||||||
@@ -294,6 +305,7 @@ void Simulation::placeInitialStructures()
|
|||||||
|
|
||||||
void Simulation::placeEnemyStationSet(int generation)
|
void Simulation::placeEnemyStationSet(int generation)
|
||||||
{
|
{
|
||||||
|
const float tileSize = static_cast<float>(m_config.world.tileSize_m);
|
||||||
const ParsedSurfaceMask esParsed =
|
const ParsedSurfaceMask esParsed =
|
||||||
parseSurfaceMask(m_config.stations.enemyStation.surfaceMask, Rotation::East);
|
parseSurfaceMask(m_config.stations.enemyStation.surfaceMask, Rotation::East);
|
||||||
|
|
||||||
@@ -309,8 +321,8 @@ void Simulation::placeEnemyStationSet(int generation)
|
|||||||
WeaponComponent esWeapon;
|
WeaponComponent esWeapon;
|
||||||
esWeapon.damage = static_cast<float>(
|
esWeapon.damage = static_cast<float>(
|
||||||
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
||||||
esWeapon.range = static_cast<float>(
|
esWeapon.range_tiles = static_cast<float>(
|
||||||
m_config.stations.enemyStation.rangeFormula.evaluate(genD));
|
m_config.stations.enemyStation.rangeFormula.evaluate(genD)) / tileSize;
|
||||||
esWeapon.fireRateHz = static_cast<float>(
|
esWeapon.fireRateHz = static_cast<float>(
|
||||||
m_config.stations.enemyStation.fireRateFormula.evaluate(genD));
|
m_config.stations.enemyStation.fireRateFormula.evaluate(genD));
|
||||||
esWeapon.cooldownTicks = 0.0f;
|
esWeapon.cooldownTicks = 0.0f;
|
||||||
@@ -361,6 +373,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
|||||||
|
|
||||||
void Simulation::tickDeathsAndLoot()
|
void Simulation::tickDeathsAndLoot()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
// --- Dead ships ---
|
// --- Dead ships ---
|
||||||
std::vector<entt::entity> deadShips;
|
std::vector<entt::entity> deadShips;
|
||||||
m_admin.forEach<ShipIdentityComponent, HealthComponent>(
|
m_admin.forEach<ShipIdentityComponent, HealthComponent>(
|
||||||
@@ -638,6 +651,11 @@ const EntityAdmin& Simulation::admin() const
|
|||||||
return m_admin;
|
return m_admin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Simulation::handleEvent(std::shared_ptr<const TracePrintRequestedEvent> event)
|
||||||
|
{
|
||||||
|
PRINT_TRACES();
|
||||||
|
}
|
||||||
|
|
||||||
BuildingId Simulation::allocateBuildingId()
|
BuildingId Simulation::allocateBuildingId()
|
||||||
{
|
{
|
||||||
return m_nextBuildingId++;
|
return m_nextBuildingId++;
|
||||||
|
|||||||
@@ -10,15 +10,16 @@
|
|||||||
|
|
||||||
#include "BeltSystem.h"
|
#include "BeltSystem.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
|
||||||
#include "entt/entity/entity.hpp"
|
#include "entt/entity/entity.hpp"
|
||||||
#include "SchematicDropEvent.h"
|
#include "SchematicDropEvent.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
|
#include "EventHandler.h"
|
||||||
#include "FireEvent.h"
|
#include "FireEvent.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "Rotation.h"
|
#include "Rotation.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "TracePrintRequestedEvent.h"
|
||||||
|
|
||||||
class AiSystem;
|
class AiSystem;
|
||||||
class BuildingSystem;
|
class BuildingSystem;
|
||||||
@@ -29,7 +30,7 @@ class ShipSystem;
|
|||||||
class ScrapSystem;
|
class ScrapSystem;
|
||||||
class WaveSystem;
|
class WaveSystem;
|
||||||
|
|
||||||
class Simulation
|
class Simulation: public CombinedEventHandler<TracePrintRequestedEvent>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit Simulation(GameConfig config, unsigned int seed = 0);
|
explicit Simulation(GameConfig config, unsigned int seed = 0);
|
||||||
@@ -83,6 +84,8 @@ public:
|
|||||||
const EntityAdmin& admin() const;
|
const EntityAdmin& admin() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void handleEvent(std::shared_ptr<const TracePrintRequestedEvent> event) override;
|
||||||
|
|
||||||
BuildingId allocateBuildingId(); // Strictly increasing; never returns kInvalidBuildingId.
|
BuildingId allocateBuildingId(); // Strictly increasing; never returns kInvalidBuildingId.
|
||||||
|
|
||||||
// Populate HQ, player defence stations, and the first enemy station set.
|
// Populate HQ, player defence stations, and the first enemy station set.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
WaveSystem::WaveSystem(const GameConfig& config, std::mt19937& rng)
|
WaveSystem::WaveSystem(const GameConfig& config, std::mt19937& rng)
|
||||||
: m_config(config)
|
: m_config(config)
|
||||||
@@ -15,6 +16,7 @@ WaveSystem::WaveSystem(const GameConfig& config, std::mt19937& rng)
|
|||||||
void WaveSystem::tickWaveScheduler(Tick currentTick, ShipSystem& ships,
|
void WaveSystem::tickWaveScheduler(Tick currentTick, ShipSystem& ships,
|
||||||
int worldHeightTiles)
|
int worldHeightTiles)
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
// 1. Advance boss countdown.
|
// 1. Advance boss countdown.
|
||||||
--m_bossCountdownTicks;
|
--m_bossCountdownTicks;
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ void WaveSystem::tickWaveScheduler(Tick currentTick, ShipSystem& ships,
|
|||||||
|
|
||||||
void WaveSystem::tickThreatAccumulation()
|
void WaveSystem::tickThreatAccumulation()
|
||||||
{
|
{
|
||||||
|
TRACE();
|
||||||
const double x = static_cast<double>(m_bossWaveCounter);
|
const double x = static_cast<double>(m_bossWaveCounter);
|
||||||
const double rate = m_config.world.waves.threatRateFormula.evaluate(x);
|
const double rate = m_config.world.waves.threatRateFormula.evaluate(x);
|
||||||
if (rate > 0.0)
|
if (rate > 0.0)
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ add_subdirectory(random)
|
|||||||
|
|
||||||
SET(HDRS
|
SET(HDRS
|
||||||
${HDRS}
|
${HDRS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/tracing.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/utilityRandom.h
|
${CMAKE_CURRENT_SOURCE_DIR}/utilityRandom.h
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
||||||
SET(SRCS
|
SET(SRCS
|
||||||
${SRCS}
|
${SRCS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/tracing.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/utilityRandom.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/utilityRandom.cpp
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|||||||
279
src/lib/utility/tracing.cpp
Normal file
279
src/lib/utility/tracing.cpp
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
#include "tracing.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
std::shared_ptr<Tracer> Tracer::s_instance;
|
||||||
|
long long int Tracer::s_nextTraceId = 0;
|
||||||
|
|
||||||
|
Tracer* Tracer::getInstance()
|
||||||
|
{
|
||||||
|
if (!s_instance)
|
||||||
|
{
|
||||||
|
s_instance = std::shared_ptr<Tracer>(new Tracer());
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_instance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TraceEvent> Tracer::startEvent(const std::string& eventName)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
const std::thread::id id = std::this_thread::get_id();
|
||||||
|
|
||||||
|
std::shared_ptr<TraceEvent> event =
|
||||||
|
std::make_shared<TraceEvent>(eventName, s_nextTraceId++, m_startedEvents[id].size());
|
||||||
|
|
||||||
|
m_events[id].push_back(event);
|
||||||
|
m_startedEvents[id].push(event.get());
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tracer::finishEvent(std::shared_ptr<TraceEvent> event)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
const std::thread::id id = std::this_thread::get_id();
|
||||||
|
|
||||||
|
m_startedEvents[id].pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tracer::printTraces()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
size_t unfinishEvents = 0;
|
||||||
|
for (auto& p : m_startedEvents)
|
||||||
|
{
|
||||||
|
unfinishEvents += p.second.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unfinishEvents > 0)
|
||||||
|
{
|
||||||
|
std::cout << "TRACING: Trace events are still running." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (m_events.empty())
|
||||||
|
{
|
||||||
|
std::cout << "TRACING: No trace events collected." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::cout << "TRACING\n--------------------------\n" << std::endl;
|
||||||
|
|
||||||
|
std::cout << "HISTORY:\n\n";
|
||||||
|
std::cout << " time name function";
|
||||||
|
std::cout << " location\n";
|
||||||
|
std::cout << "-----------------------------------------------------------------";
|
||||||
|
std::cout << "------------------------------------------------------------\n";
|
||||||
|
|
||||||
|
for (auto& p : m_events)
|
||||||
|
{
|
||||||
|
std::cout << "thread: " << p.first << std::endl;
|
||||||
|
|
||||||
|
for (const std::shared_ptr<TraceEvent>& event : p.second)
|
||||||
|
{
|
||||||
|
std::cout.width(8 + 2 * event->depth);
|
||||||
|
std::cout << std::right /*<< std::setprecision(3)*/ << std::fixed << event->time;
|
||||||
|
|
||||||
|
std::cout.width(17 - 2 * event->depth);
|
||||||
|
std::cout << " ";
|
||||||
|
|
||||||
|
std::cout.width(25);
|
||||||
|
std::cout << std::left << event->eventName;
|
||||||
|
|
||||||
|
std::cout.width(50);
|
||||||
|
std::cout << (event->functionName + "()") << event->locationName << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nREPORT:\n\n";
|
||||||
|
std::cout << " time count name function";
|
||||||
|
std::cout << " location\n";
|
||||||
|
std::cout << "-----------------------------------------------------------------";
|
||||||
|
std::cout << "------------------------------------------------------------\n";
|
||||||
|
|
||||||
|
struct AccumulatedTraceEvent
|
||||||
|
{
|
||||||
|
TraceEvent* event;
|
||||||
|
size_t count;
|
||||||
|
float time;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::string, AccumulatedTraceEvent> accumulatedEvents;
|
||||||
|
|
||||||
|
for (auto& p : m_events)
|
||||||
|
{
|
||||||
|
for (const std::shared_ptr<TraceEvent>& event : p.second)
|
||||||
|
{
|
||||||
|
const std::string name = event->eventName + event->functionName + event->locationName;
|
||||||
|
|
||||||
|
std::pair<std::map<std::string, AccumulatedTraceEvent>::iterator, bool> p =
|
||||||
|
accumulatedEvents.emplace(name, AccumulatedTraceEvent());
|
||||||
|
|
||||||
|
AccumulatedTraceEvent* acc = &p.first->second;
|
||||||
|
if (p.second)
|
||||||
|
{
|
||||||
|
acc->event = event.get();
|
||||||
|
acc->time = event->time;
|
||||||
|
acc->count = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
acc->time += event->time;
|
||||||
|
acc->count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::multiset<AccumulatedTraceEvent,
|
||||||
|
std::function<bool(const AccumulatedTraceEvent&, const AccumulatedTraceEvent&)>> sortedEvents(
|
||||||
|
[](const AccumulatedTraceEvent& a, const AccumulatedTraceEvent& b)
|
||||||
|
{
|
||||||
|
return a.time > b.time;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const std::pair<std::string, AccumulatedTraceEvent>& p : accumulatedEvents)
|
||||||
|
{
|
||||||
|
sortedEvents.insert(p.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const AccumulatedTraceEvent& acc : sortedEvents)
|
||||||
|
{
|
||||||
|
std::cout.width(8);
|
||||||
|
std::cout << std::right/* << std::setprecision(3)*/ << std::fixed << acc.time;
|
||||||
|
|
||||||
|
std::cout.width(10);
|
||||||
|
std::cout << acc.count << " ";
|
||||||
|
|
||||||
|
std::cout.width(25);
|
||||||
|
std::cout << std::left << acc.event->eventName;
|
||||||
|
|
||||||
|
std::cout.width(50);
|
||||||
|
std::cout << (acc.event->functionName + "()") << acc.event->locationName << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
m_events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Tracer::Tracer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<AccumulatingTracer> AccumulatingTracer::s_instance;
|
||||||
|
long long int AccumulatingTracer::s_nextTraceId = 0;
|
||||||
|
|
||||||
|
AccumulatingTracer* AccumulatingTracer::getInstance()
|
||||||
|
{
|
||||||
|
if (!s_instance)
|
||||||
|
{
|
||||||
|
s_instance = std::shared_ptr<AccumulatingTracer>(new AccumulatingTracer());
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_instance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TraceEvent> AccumulatingTracer::startEvent(const std::string& eventName)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
const std::thread::id id = std::this_thread::get_id();
|
||||||
|
|
||||||
|
std::shared_ptr<TraceEvent> event =
|
||||||
|
std::make_shared<TraceEvent>(eventName, s_nextTraceId++, m_startedEvents[id].size());
|
||||||
|
|
||||||
|
m_startedEvents[id].push(event.get());
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccumulatingTracer::finishEvent(std::shared_ptr<TraceEvent> event)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
const std::thread::id id = std::this_thread::get_id();
|
||||||
|
|
||||||
|
m_startedEvents[id].pop();
|
||||||
|
|
||||||
|
const std::string name = event->eventName + event->functionName + event->locationName;
|
||||||
|
|
||||||
|
std::pair<std::map<std::string, AccumulatedTraceEvent>::iterator, bool> p =
|
||||||
|
m_accumulatedEvents.emplace(name, AccumulatedTraceEvent());
|
||||||
|
|
||||||
|
AccumulatedTraceEvent* acc = &p.first->second;
|
||||||
|
if (p.second)
|
||||||
|
{
|
||||||
|
acc->event = TraceEvent(*(event.get()));
|
||||||
|
acc->time = event->time;
|
||||||
|
acc->count = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
acc->time += event->time;
|
||||||
|
acc->count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccumulatingTracer::printTraces()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
for (auto& p : m_startedEvents)
|
||||||
|
{
|
||||||
|
if (!p.second.empty())
|
||||||
|
{
|
||||||
|
std::cout << "TRACING: Trace events are still running." << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nREPORT:\n\n";
|
||||||
|
std::cout << " time count name function";
|
||||||
|
std::cout << " location\n";
|
||||||
|
std::cout << "-----------------------------------------------------------------";
|
||||||
|
std::cout << "------------------------------------------------------------\n";
|
||||||
|
|
||||||
|
std::multiset<AccumulatedTraceEvent,
|
||||||
|
std::function<bool (const AccumulatedTraceEvent&, const AccumulatedTraceEvent&)>> sortedEvents(
|
||||||
|
[](const AccumulatedTraceEvent& a, const AccumulatedTraceEvent& b)
|
||||||
|
{
|
||||||
|
return a.time > b.time;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const std::pair<std::string, AccumulatedTraceEvent>& p : m_accumulatedEvents)
|
||||||
|
{
|
||||||
|
sortedEvents.insert(p.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const AccumulatedTraceEvent& acc : sortedEvents)
|
||||||
|
{
|
||||||
|
std::cout.width(8);
|
||||||
|
std::cout << std::right/* << std::setprecision(3)*/ << std::fixed << acc.time;
|
||||||
|
|
||||||
|
std::cout.width(10);
|
||||||
|
std::cout << acc.count << " ";
|
||||||
|
|
||||||
|
std::cout.width(25);
|
||||||
|
std::cout << std::left << acc.event.eventName;
|
||||||
|
|
||||||
|
std::cout.width(50);
|
||||||
|
std::cout << (acc.event.functionName + "()") << acc.event.locationName << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
m_accumulatedEvents.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
AccumulatingTracer::AccumulatingTracer()
|
||||||
|
{
|
||||||
|
}
|
||||||
156
src/lib/utility/tracing.h
Normal file
156
src/lib/utility/tracing.h
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#ifndef TRACING_H
|
||||||
|
#define TRACING_H
|
||||||
|
|
||||||
|
|
||||||
|
//#define TRACING_ENABLED
|
||||||
|
//#define USE_ACCUMULATED_TRACING
|
||||||
|
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <stack>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
struct TraceEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TraceEvent()
|
||||||
|
: eventName("")
|
||||||
|
, id(0)
|
||||||
|
, depth(0)
|
||||||
|
, time(0.0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceEvent(const std::string& eventName, long long int id, size_t depth)
|
||||||
|
: eventName(eventName)
|
||||||
|
, id(id)
|
||||||
|
, depth(depth)
|
||||||
|
, time(0.0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string eventName;
|
||||||
|
long long int id;
|
||||||
|
size_t depth;
|
||||||
|
|
||||||
|
std::string functionName;
|
||||||
|
std::string locationName;
|
||||||
|
|
||||||
|
double time;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Tracer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Tracer* getInstance();
|
||||||
|
|
||||||
|
std::shared_ptr<TraceEvent> startEvent(const std::string& eventName);
|
||||||
|
void finishEvent(std::shared_ptr<TraceEvent> event);
|
||||||
|
|
||||||
|
void printTraces();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::shared_ptr<Tracer> s_instance;
|
||||||
|
static long long int s_nextTraceId;
|
||||||
|
|
||||||
|
Tracer();
|
||||||
|
Tracer(const Tracer&) = delete;
|
||||||
|
void operator=(const Tracer&) = delete;
|
||||||
|
|
||||||
|
std::map<std::thread::id, std::vector<std::shared_ptr<TraceEvent>>> m_events;
|
||||||
|
std::map<std::thread::id, std::stack<TraceEvent*>> m_startedEvents;
|
||||||
|
|
||||||
|
std::mutex m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AccumulatingTracer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static AccumulatingTracer* getInstance();
|
||||||
|
|
||||||
|
std::shared_ptr<TraceEvent> startEvent(const std::string& eventName);
|
||||||
|
void finishEvent(std::shared_ptr<TraceEvent> event);
|
||||||
|
|
||||||
|
void printTraces();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AccumulatedTraceEvent
|
||||||
|
{
|
||||||
|
TraceEvent event;
|
||||||
|
size_t count;
|
||||||
|
double time;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::shared_ptr<AccumulatingTracer> s_instance;
|
||||||
|
static long long int s_nextTraceId;
|
||||||
|
|
||||||
|
AccumulatingTracer();
|
||||||
|
AccumulatingTracer(const AccumulatingTracer&) = delete;
|
||||||
|
void operator=(const AccumulatingTracer&) = delete;
|
||||||
|
|
||||||
|
std::map<std::string, AccumulatedTraceEvent> m_accumulatedEvents;
|
||||||
|
std::map<std::thread::id, std::stack<TraceEvent*>> m_startedEvents;
|
||||||
|
|
||||||
|
std::mutex m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename TracerType>
|
||||||
|
class ScopedTrace
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedTrace(const std::string& eventName, const std::string& fileName, int lineNumber, const std::string& functionName);
|
||||||
|
~ScopedTrace();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<TraceEvent> m_event;
|
||||||
|
QDateTime m_timeStamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TracerType>
|
||||||
|
ScopedTrace<TracerType>::ScopedTrace(
|
||||||
|
const std::string& eventName, const std::string& fileName, int lineNumber, const std::string& functionName)
|
||||||
|
{
|
||||||
|
m_event = TracerType::getInstance()->startEvent(eventName);
|
||||||
|
m_event->functionName = functionName;
|
||||||
|
m_event->locationName = QFileInfo(QString::fromStdString(fileName)).fileName().toStdString() + ":" + std::to_string(lineNumber);
|
||||||
|
|
||||||
|
m_timeStamp = QDateTime::currentDateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TracerType>
|
||||||
|
ScopedTrace<TracerType>::~ScopedTrace()
|
||||||
|
{
|
||||||
|
m_event->time = m_timeStamp.msecsTo(QDateTime::currentDateTime());
|
||||||
|
TracerType::getInstance()->finishEvent(m_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef TRACING_ENABLED
|
||||||
|
#ifdef USE_ACCUMULATED_TRACING
|
||||||
|
#define TRACE(__name__) \
|
||||||
|
ScopedTrace<AccumulatingTracer> __trace__(std::string(__name__), __FILE__, __LINE__, __FUNCTION__)
|
||||||
|
|
||||||
|
#define PRINT_TRACES() \
|
||||||
|
AccumulatingTracer::getInstance()->printTraces()
|
||||||
|
#else
|
||||||
|
#define TRACE(__name__) \
|
||||||
|
ScopedTrace<Tracer> __trace__(std::string(__name__), __FILE__, __LINE__, __FUNCTION__)
|
||||||
|
|
||||||
|
#define PRINT_TRACES() \
|
||||||
|
Tracer::getInstance()->printTraces()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define TRACE(__name__)
|
||||||
|
#define PRINT_TRACES()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TRACING_H
|
||||||
@@ -62,7 +62,7 @@ struct Fixture
|
|||||||
|
|
||||||
explicit Fixture()
|
explicit Fixture()
|
||||||
: cfg(loadConfig())
|
: cfg(loadConfig())
|
||||||
, belts(cfg.world.beltSpeedTilesPerSecond)
|
, belts(cfg.world.beltSpeed_tps)
|
||||||
, nextBuildingId(1)
|
, nextBuildingId(1)
|
||||||
, stock(0)
|
, stock(0)
|
||||||
, rng(42)
|
, rng(42)
|
||||||
@@ -188,13 +188,13 @@ TEST_CASE("BehaviorSystem: clearMovementIntents resets all ships to priority 0",
|
|||||||
// tickMovement
|
// tickMovement
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: tickMovement advances ship by maxSpeedPerTick toward target",
|
TEST_CASE("BehaviorSystem: tickMovement advances ship by maxSpeed_tpt toward target",
|
||||||
"[behavior]")
|
"[behavior]")
|
||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
const float speed = f.admin.get<DynamicBodyComponent>(e).maxSpeedPerTick;
|
const float speed = f.admin.get<DynamicBodyComponent>(e).maxSpeed_tpt;
|
||||||
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{1, QVector2D(100.0f, 0.0f)};
|
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{1, QVector2D(100.0f, 0.0f)};
|
||||||
f.movementIntent.tick(f.admin);
|
f.movementIntent.tick(f.admin);
|
||||||
f.dynamicBody.tick(f.admin);
|
f.dynamicBody.tick(f.admin);
|
||||||
@@ -209,7 +209,7 @@ TEST_CASE("BehaviorSystem: tickMovement stops exactly at target without overshoo
|
|||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
const float speed = f.admin.get<DynamicBodyComponent>(e).maxSpeedPerTick;
|
const float speed = f.admin.get<DynamicBodyComponent>(e).maxSpeed_tpt;
|
||||||
const QVector2D target(speed * 0.5f, 0.0f);
|
const QVector2D target(speed * 0.5f, 0.0f);
|
||||||
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{1, target};
|
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{1, target};
|
||||||
f.movementIntent.tick(f.admin);
|
f.movementIntent.tick(f.admin);
|
||||||
@@ -610,7 +610,7 @@ TEST_CASE("BehaviorSystem: tickRepairTools does not crash when owner lacks Repai
|
|||||||
const entt::entity moduleEntity = f.admin.createModuleEntity();
|
const entt::entity moduleEntity = f.admin.createModuleEntity();
|
||||||
RepairToolComponent rt;
|
RepairToolComponent rt;
|
||||||
rt.ratePerTick = 1.0f;
|
rt.ratePerTick = 1.0f;
|
||||||
rt.range = 10.0f;
|
rt.range_tiles = 10.0f;
|
||||||
rt.currentTarget = std::nullopt;
|
rt.currentTarget = std::nullopt;
|
||||||
f.admin.addComponent<RepairToolComponent>(moduleEntity, rt);
|
f.admin.addComponent<RepairToolComponent>(moduleEntity, rt);
|
||||||
f.admin.addComponent<ModuleOwnerComponent>(moduleEntity, ModuleOwnerComponent{ownerShip});
|
f.admin.addComponent<ModuleOwnerComponent>(moduleEntity, ModuleOwnerComponent{ownerShip});
|
||||||
@@ -861,7 +861,7 @@ TEST_CASE("SensorRange: sensorRange is populated from config formula at spawn",
|
|||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
REQUIRE(f.admin.get<SensorRangeComponent>(e).value == Approx(200.0f));
|
REQUIRE(f.admin.get<SensorRangeComponent>(e).value_tiles == Approx(200.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ static void runTicks(BuildingSystem& bs, BeltSystem& belts, int n, Tick& tick)
|
|||||||
TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building]")
|
TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -96,7 +96,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
|||||||
"[building]")
|
"[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -124,7 +124,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
|||||||
TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]")
|
TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -144,7 +144,7 @@ TEST_CASE("BuildingSystem: placed building enters construction queue", "[buildin
|
|||||||
TEST_CASE("BuildingSystem: demolish frees tiles and returns refund", "[building]")
|
TEST_CASE("BuildingSystem: demolish frees tiles and returns refund", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -177,7 +177,7 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately
|
|||||||
"[building]")
|
"[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -194,7 +194,7 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately
|
|||||||
TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[building]")
|
TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -215,7 +215,7 @@ TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[b
|
|||||||
TEST_CASE("BuildingSystem: construction completes after configured duration", "[building]")
|
TEST_CASE("BuildingSystem: construction completes after configured duration", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -239,7 +239,7 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
|
|||||||
TEST_CASE("BuildingSystem: second building starts after first completes", "[building]")
|
TEST_CASE("BuildingSystem: second building starts after first completes", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -268,7 +268,7 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
|
|||||||
TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[building]")
|
TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -297,7 +297,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
|
|||||||
TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -456,7 +456,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
|
|||||||
"[building]")
|
"[building]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -544,7 +544,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when tile is
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -562,7 +562,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a que
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -584,7 +584,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -610,7 +610,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when building
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -631,7 +631,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when footprin
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -654,7 +654,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-t
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -682,7 +682,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -704,7 +704,7 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -727,7 +727,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
@@ -757,7 +757,7 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
|
|||||||
"[building][rotate-in-place]")
|
"[building][rotate-in-place]")
|
||||||
{
|
{
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||||
int stock = 0;
|
int stock = 0;
|
||||||
std::mt19937 rng(0);
|
std::mt19937 rng(0);
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ struct CombatFixture
|
|||||||
: cfg(loadConfig())
|
: cfg(loadConfig())
|
||||||
, rng(42)
|
, rng(42)
|
||||||
, nextBuildingId(1)
|
, nextBuildingId(1)
|
||||||
, belts(cfg.world.beltSpeedTilesPerSecond)
|
, belts(cfg.world.beltSpeed_tps)
|
||||||
, ships(cfg, admin)
|
, ships(cfg, admin)
|
||||||
, buildings(cfg, belts,
|
, buildings(cfg, belts,
|
||||||
[this]() { return nextBuildingId++; },
|
[this]() { return nextBuildingId++; },
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ TEST_CASE("ConfigLoader loads the committed bin/config/ configs end-to-end", "[c
|
|||||||
// world.toml
|
// world.toml
|
||||||
REQUIRE(cfg.world.heightTiles == 60);
|
REQUIRE(cfg.world.heightTiles == 60);
|
||||||
REQUIRE(cfg.world.refundPercentage == 75);
|
REQUIRE(cfg.world.refundPercentage == 75);
|
||||||
REQUIRE(cfg.world.beltSpeedTilesPerSecond == Approx(2.0));
|
REQUIRE(cfg.world.beltSpeed_tps == Approx(2.0));
|
||||||
REQUIRE(cfg.world.regions.asteroidWidth == 40);
|
REQUIRE(cfg.world.regions.asteroidWidth == 40);
|
||||||
REQUIRE(cfg.world.regions.playerBufferWidth == 10);
|
REQUIRE(cfg.world.regions.playerBufferWidth == 10);
|
||||||
REQUIRE(cfg.world.regions.enemyBufferWidth == 15);
|
REQUIRE(cfg.world.regions.enemyBufferWidth == 15);
|
||||||
@@ -158,7 +158,8 @@ TEST_CASE("Missing field in world.toml is rejected with the field path", "[confi
|
|||||||
height_tiles = 60
|
height_tiles = 60
|
||||||
refund_percentage = 75
|
refund_percentage = 75
|
||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
tile_size_m = 10
|
||||||
|
belt_speed_mps = 20
|
||||||
starting_building_blocks = 100
|
starting_building_blocks = 100
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
departure_interval_seconds = 20
|
departure_interval_seconds = 20
|
||||||
@@ -205,7 +206,8 @@ TEST_CASE("Malformed formula in world.toml is rejected with field identification
|
|||||||
height_tiles = 60
|
height_tiles = 60
|
||||||
refund_percentage = 75
|
refund_percentage = 75
|
||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
tile_size_m = 10
|
||||||
|
belt_speed_mps = 20
|
||||||
starting_building_blocks = 100
|
starting_building_blocks = 100
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
departure_interval_seconds = 20
|
departure_interval_seconds = 20
|
||||||
@@ -253,7 +255,8 @@ TEST_CASE("Inverted wave gap range is rejected", "[config]")
|
|||||||
height_tiles = 60
|
height_tiles = 60
|
||||||
refund_percentage = 75
|
refund_percentage = 75
|
||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
tile_size_m = 10
|
||||||
|
belt_speed_mps = 20
|
||||||
|
|
||||||
[regions]
|
[regions]
|
||||||
asteroid_width = 40
|
asteroid_width = 40
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ TEST_CASE("ConfigLoader: loadModules parses additive modifiers", "[config][modul
|
|||||||
REQUIRE(sensor.statModifiers.size() == 1);
|
REQUIRE(sensor.statModifiers.size() == 1);
|
||||||
CHECK(sensor.statModifiers[0].stat == "sensor_range");
|
CHECK(sensor.statModifiers[0].stat == "sensor_range");
|
||||||
CHECK(sensor.statModifiers[0].modifierType == "additive");
|
CHECK(sensor.statModifiers[0].modifierType == "additive");
|
||||||
CHECK(sensor.statModifiers[0].formula.evaluate(1.0) == Approx(10.0));
|
CHECK(sensor.statModifiers[0].formula.evaluate(1.0) == Approx(100.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ConfigLoader: loadShips parses layout field", "[config][ships]")
|
TEST_CASE("ConfigLoader: loadShips parses layout field", "[config][ships]")
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
|||||||
REQUIRE(def != nullptr);
|
REQUIRE(def != nullptr);
|
||||||
|
|
||||||
const double x = static_cast<double>(def->schematic.playerProductionLevel);
|
const double x = static_cast<double>(def->schematic.playerProductionLevel);
|
||||||
const float baseRange = static_cast<float>(def->sensor.sensorRangeFormula.evaluate(x));
|
const float tileSize = static_cast<float>(sim.config().world.tileSize_m);
|
||||||
|
const float baseRange_tiles = static_cast<float>(def->sensor.sensorRangeFormula.evaluate(x)) / tileSize;
|
||||||
|
|
||||||
ShipLayoutConfig layout;
|
ShipLayoutConfig layout;
|
||||||
PlacedModule pm;
|
PlacedModule pm;
|
||||||
@@ -153,9 +154,9 @@ TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
|||||||
QVector2D(5.0f, 5.0f), false, layout);
|
QVector2D(5.0f, 5.0f), false, layout);
|
||||||
|
|
||||||
REQUIRE(sim.admin().isValid(e));
|
REQUIRE(sim.admin().isValid(e));
|
||||||
// sensor_booster has added_sensor_range_formula = "10"
|
// sensor_booster has added_sensor_range_formula = "100" m → 100/10 = 10 tiles
|
||||||
// final = base * 1.0 + 10 = base + 10
|
// final = baseRange_tiles * 1.0 + 10 = baseRange_tiles + 10
|
||||||
CHECK(sim.admin().get<SensorRangeComponent>(e).value == Approx(baseRange + 10.0f));
|
CHECK(sim.admin().get<SensorRangeComponent>(e).value_tiles == Approx(baseRange_tiles + 10.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
||||||
|
|||||||
@@ -110,14 +110,14 @@ TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]
|
|||||||
// hp_formula = "40 + 5*x" at x=1 → 45
|
// hp_formula = "40 + 5*x" at x=1 → 45
|
||||||
REQUIRE(admin.get<HealthComponent>(e).maxHp == Approx(45.0f));
|
REQUIRE(admin.get<HealthComponent>(e).maxHp == Approx(45.0f));
|
||||||
REQUIRE(admin.get<HealthComponent>(e).hp == Approx(45.0f));
|
REQUIRE(admin.get<HealthComponent>(e).hp == Approx(45.0f));
|
||||||
// sensor_range_formula = "200"
|
// sensor_range_formula = "2000" m → 2000/10 = 200 tiles
|
||||||
REQUIRE(admin.get<SensorRangeComponent>(e).value == Approx(200.0f));
|
REQUIRE(admin.get<SensorRangeComponent>(e).value_tiles == Approx(200.0f));
|
||||||
|
|
||||||
// laser_cannon: damage_formula = "2", attack_range_formula = "5"
|
// laser_cannon: damage_formula = "2", attack_range_formula = "50" m → 50/10 = 5 tiles
|
||||||
const entt::entity wc = firstWeaponChild(admin, e);
|
const entt::entity wc = firstWeaponChild(admin, e);
|
||||||
REQUIRE(admin.isValid(wc));
|
REQUIRE(admin.isValid(wc));
|
||||||
REQUIRE(admin.get<WeaponComponent>(wc).damage == Approx(2.0f));
|
REQUIRE(admin.get<WeaponComponent>(wc).damage == Approx(2.0f));
|
||||||
REQUIRE(admin.get<WeaponComponent>(wc).range == Approx(5.0f));
|
REQUIRE(admin.get<WeaponComponent>(wc).range_tiles == Approx(5.0f));
|
||||||
REQUIRE(admin.get<WeaponComponent>(wc).cooldownTicks == Approx(0.0f));
|
REQUIRE(admin.get<WeaponComponent>(wc).cooldownTicks == Approx(0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]")
|
|||||||
REQUIRE(admin.get<HealthComponent>(e).maxHp == Approx(65.0f));
|
REQUIRE(admin.get<HealthComponent>(e).maxHp == Approx(65.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTickRateHz", "[ship]")
|
TEST_CASE("ShipSystem: interceptor level 0 maxSpeed_tpt matches formula / tileSize / kTickRateHz", "[ship]")
|
||||||
{
|
{
|
||||||
EntityAdmin admin;
|
EntityAdmin admin;
|
||||||
const GameConfig cfg = loadConfig();
|
const GameConfig cfg = loadConfig();
|
||||||
@@ -141,9 +141,9 @@ TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTi
|
|||||||
|
|
||||||
const entt::entity e = ss.spawn("interceptor", 0, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("interceptor", 0, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
// speed_formula = "200 + 5*x" at x=0 → 200; maxSpeedPerTick = 200/30
|
// speed_formula = "2000 + 50*x" m/s at x=0 → 2000 m/s; maxSpeed_tpt = 2000/(10*30)
|
||||||
const float expected = 200.0f / static_cast<float>(kTickRateHz);
|
const float expected = 2000.0f / 10.0f / static_cast<float>(kTickRateHz);
|
||||||
REQUIRE(admin.get<DynamicBodyComponent>(e).maxSpeedPerTick == Approx(expected));
|
REQUIRE(admin.get<DynamicBodyComponent>(e).maxSpeed_tpt == Approx(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -175,14 +175,14 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
|||||||
const ShipLayoutConfig layout = makeSingleModuleLayout("salvager");
|
const ShipLayoutConfig layout = makeSingleModuleLayout("salvager");
|
||||||
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f), false, layout);
|
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f), false, layout);
|
||||||
|
|
||||||
// salvager: cargo_capacity_formula = "10", collection_range_formula = "50"
|
// salvager: cargo_capacity_formula = "10", collection_range_formula = "500" m → 500/10 = 50 tiles
|
||||||
const entt::entity sc = firstSalvageChild(admin, e);
|
const entt::entity sc = firstSalvageChild(admin, e);
|
||||||
REQUIRE(admin.isValid(sc));
|
REQUIRE(admin.isValid(sc));
|
||||||
REQUIRE(admin.get<SalvageCargoComponent>(sc).capacity == 10);
|
REQUIRE(admin.get<SalvageCargoComponent>(sc).capacity == 10);
|
||||||
REQUIRE(admin.get<SalvageCargoComponent>(sc).current == 0);
|
REQUIRE(admin.get<SalvageCargoComponent>(sc).current == 0);
|
||||||
REQUIRE(admin.get<SalvageBehaviorComponent>(e).deliveryBay == kInvalidBuildingId);
|
REQUIRE(admin.get<SalvageBehaviorComponent>(e).deliveryBay == kInvalidBuildingId);
|
||||||
REQUIRE_FALSE(admin.get<SalvageBehaviorComponent>(e).scrapTarget.has_value());
|
REQUIRE_FALSE(admin.get<SalvageBehaviorComponent>(e).scrapTarget.has_value());
|
||||||
REQUIRE(admin.get<SalvageBehaviorComponent>(e).maxCollectionRange == Approx(50.0f));
|
REQUIRE(admin.get<SalvageBehaviorComponent>(e).maxCollectionRange_tiles == Approx(50.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -219,9 +219,9 @@ TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas",
|
|||||||
const entt::entity rc = firstRepairChild(admin, e);
|
const entt::entity rc = firstRepairChild(admin, e);
|
||||||
REQUIRE(admin.isValid(rc));
|
REQUIRE(admin.isValid(rc));
|
||||||
REQUIRE(admin.get<RepairToolComponent>(rc).ratePerTick == Approx(expectedRate));
|
REQUIRE(admin.get<RepairToolComponent>(rc).ratePerTick == Approx(expectedRate));
|
||||||
// repair_range_formula = "80"
|
// repair_range_formula = "800" m → 800/10 = 80 tiles
|
||||||
REQUIRE(admin.get<RepairToolComponent>(rc).range == Approx(80.0f));
|
REQUIRE(admin.get<RepairToolComponent>(rc).range_tiles == Approx(80.0f));
|
||||||
REQUIRE(admin.get<RepairBehaviorComponent>(e).maxRepairRange == Approx(80.0f));
|
REQUIRE(admin.get<RepairBehaviorComponent>(e).maxRepairRange_tiles == Approx(80.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ TEST_CASE("WaveSystem: player stations have weapon set", "[wave]")
|
|||||||
{
|
{
|
||||||
++armedPlayerStations;
|
++armedPlayerStations;
|
||||||
REQUIRE(w.damage > 0.0f);
|
REQUIRE(w.damage > 0.0f);
|
||||||
REQUIRE(w.range > 0.0f);
|
REQUIRE(w.range_tiles > 0.0f);
|
||||||
REQUIRE(w.fireRateHz > 0.0f);
|
REQUIRE(w.fireRateHz > 0.0f);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -160,7 +160,7 @@ TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]")
|
|||||||
{
|
{
|
||||||
++armedEnemyStations;
|
++armedEnemyStations;
|
||||||
REQUIRE(w.damage > 0.0f);
|
REQUIRE(w.damage > 0.0f);
|
||||||
REQUIRE(w.range > 0.0f);
|
REQUIRE(w.range_tiles > 0.0f);
|
||||||
REQUIRE(w.fireRateHz > 0.0f);
|
REQUIRE(w.fireRateHz > 0.0f);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "BlueprintSerializer.h"
|
#include "BlueprintSerializer.h"
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
|
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
@@ -29,7 +30,7 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge
|
|||||||
layout->setContentsMargins(4, 4, 4, 4);
|
layout->setContentsMargins(4, 4, 4, 4);
|
||||||
layout->setSpacing(4);
|
layout->setSpacing(4);
|
||||||
|
|
||||||
m_createBtn = new QPushButton("Create Blueprint", this);
|
m_createBtn = new QPushButton(tr("Create Blueprint"), this);
|
||||||
m_createBtn->setFixedHeight(48);
|
m_createBtn->setFixedHeight(48);
|
||||||
m_createBtn->setEnabled(false);
|
m_createBtn->setEnabled(false);
|
||||||
layout->addWidget(m_createBtn);
|
layout->addWidget(m_createBtn);
|
||||||
@@ -50,8 +51,8 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge
|
|||||||
connect(m_createBtn, &QPushButton::clicked, this, &BlueprintPanel::onCreateClicked);
|
connect(m_createBtn, &QPushButton::clicked, this, &BlueprintPanel::onCreateClicked);
|
||||||
|
|
||||||
QHBoxLayout* ioLayout = new QHBoxLayout();
|
QHBoxLayout* ioLayout = new QHBoxLayout();
|
||||||
m_saveBtn = new QPushButton("Save", this);
|
m_saveBtn = new QPushButton(tr("Save"), this);
|
||||||
m_loadBtn = new QPushButton("Load", this);
|
m_loadBtn = new QPushButton(tr("Load"), this);
|
||||||
m_saveBtn->setFixedHeight(36);
|
m_saveBtn->setFixedHeight(36);
|
||||||
m_loadBtn->setFixedHeight(36);
|
m_loadBtn->setFixedHeight(36);
|
||||||
ioLayout->addWidget(m_saveBtn);
|
ioLayout->addWidget(m_saveBtn);
|
||||||
@@ -60,6 +61,13 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge
|
|||||||
|
|
||||||
connect(m_saveBtn, &QPushButton::clicked, this, &BlueprintPanel::onSaveClicked);
|
connect(m_saveBtn, &QPushButton::clicked, this, &BlueprintPanel::onSaveClicked);
|
||||||
connect(m_loadBtn, &QPushButton::clicked, this, &BlueprintPanel::onLoadClicked);
|
connect(m_loadBtn, &QPushButton::clicked, this, &BlueprintPanel::onLoadClicked);
|
||||||
|
|
||||||
|
registerForEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
BlueprintPanel::~BlueprintPanel()
|
||||||
|
{
|
||||||
|
unregisterForEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlueprintPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
|
void BlueprintPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
|
||||||
@@ -68,9 +76,9 @@ void BlueprintPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
|
|||||||
refreshButtonStates();
|
refreshButtonStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlueprintPanel::onStateUpdated(Tick /*tick*/, int blocks, double /*speed*/)
|
void BlueprintPanel::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
|
||||||
{
|
{
|
||||||
m_currentBlocks = blocks;
|
m_currentBlocks = event->blocks;
|
||||||
refreshButtonStates();
|
refreshButtonStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +101,7 @@ void BlueprintPanel::onCreateClicked()
|
|||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const QString name = QInputDialog::getText(
|
const QString name = QInputDialog::getText(
|
||||||
this, "Create Blueprint", "Blueprint name:", QLineEdit::Normal, QString(), &ok);
|
this, tr("Create Blueprint"), tr("Blueprint name:"), QLineEdit::Normal, QString(), &ok);
|
||||||
if (!ok || name.trimmed().isEmpty()) { return; }
|
if (!ok || name.trimmed().isEmpty()) { return; }
|
||||||
|
|
||||||
bp.name = name.trimmed();
|
bp.name = name.trimmed();
|
||||||
@@ -223,7 +231,7 @@ void BlueprintPanel::rebuildButtons()
|
|||||||
{
|
{
|
||||||
const Blueprint& bp = m_blueprints[static_cast<std::size_t>(i)];
|
const Blueprint& bp = m_blueprints[static_cast<std::size_t>(i)];
|
||||||
const int cost = computeBlueprintCost(bp);
|
const int cost = computeBlueprintCost(bp);
|
||||||
const QString label = bp.name + "\n" + QString::number(cost) + " Blocks";
|
const QString label = bp.name + "\n" + tr("%1 Blocks").arg(cost);
|
||||||
|
|
||||||
QWidget* row = new QWidget(m_buttonsContainer);
|
QWidget* row = new QWidget(m_buttonsContainer);
|
||||||
QHBoxLayout* rowLayout = new QHBoxLayout(row);
|
QHBoxLayout* rowLayout = new QHBoxLayout(row);
|
||||||
@@ -266,26 +274,26 @@ void BlueprintPanel::onSaveClicked()
|
|||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Save Failed",
|
QMessageBox::critical(this, tr("Save Failed"),
|
||||||
QString("Could not open file for writing:\n%1").arg(path));
|
tr("Could not open file for writing:\n%1").arg(path));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file.write(QByteArray::fromStdString(content));
|
file.write(QByteArray::fromStdString(content));
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Save Failed",
|
QMessageBox::critical(this, tr("Save Failed"),
|
||||||
QString("Failed to save blueprints:\n%1").arg(e.what()));
|
tr("Failed to save blueprints:\n%1").arg(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlueprintPanel::onLoadClicked()
|
void BlueprintPanel::onLoadClicked()
|
||||||
{
|
{
|
||||||
QMessageBox box(this);
|
QMessageBox box(this);
|
||||||
box.setWindowTitle("Load Blueprints");
|
box.setWindowTitle(tr("Load Blueprints"));
|
||||||
box.setText("Load blueprints? This will replace all current blueprints.");
|
box.setText(tr("Load blueprints? This will replace all current blueprints."));
|
||||||
QPushButton* confirmBtn = box.addButton("Confirm", QMessageBox::AcceptRole);
|
QPushButton* confirmBtn = box.addButton(tr("Confirm"), QMessageBox::AcceptRole);
|
||||||
box.addButton("Cancel", QMessageBox::RejectRole);
|
box.addButton(tr("Cancel"), QMessageBox::RejectRole);
|
||||||
box.exec();
|
box.exec();
|
||||||
if (box.clickedButton() != confirmBtn) { return; }
|
if (box.clickedButton() != confirmBtn) { return; }
|
||||||
|
|
||||||
@@ -295,8 +303,8 @@ void BlueprintPanel::onLoadClicked()
|
|||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Load Failed",
|
QMessageBox::critical(this, tr("Load Failed"),
|
||||||
QString("Could not open file:\n%1").arg(path));
|
tr("Could not open file:\n%1").arg(path));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::string content = file.readAll().toStdString();
|
const std::string content = file.readAll().toStdString();
|
||||||
@@ -313,8 +321,8 @@ void BlueprintPanel::onLoadClicked()
|
|||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Load Failed",
|
QMessageBox::critical(this, tr("Load Failed"),
|
||||||
QString("Failed to load blueprints:\n%1").arg(e.what()));
|
tr("Failed to load blueprints:\n%1").arg(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "Blueprint.h"
|
#include "Blueprint.h"
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
|
#include "EventHandler.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
|
||||||
@@ -14,22 +16,26 @@ class QPushButton;
|
|||||||
class QScrollArea;
|
class QScrollArea;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
|
|
||||||
class BlueprintPanel : public QWidget
|
class BlueprintPanel : public QWidget,
|
||||||
|
public EventHandler<BuildingBlocksChangedEvent>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BlueprintPanel(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr);
|
BlueprintPanel(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr);
|
||||||
|
~BlueprintPanel() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onSelectionChanged(const std::vector<BuildingId>& ids);
|
void onSelectionChanged(const std::vector<BuildingId>& ids);
|
||||||
void onStateUpdated(Tick tick, int blocks, double speed);
|
|
||||||
void clearActiveBlueprintButton();
|
void clearActiveBlueprintButton();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void blueprintPlacementRequested(Blueprint blueprint);
|
void blueprintPlacementRequested(Blueprint blueprint);
|
||||||
void exitBlueprintModeRequested();
|
void exitBlueprintModeRequested();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onCreateClicked();
|
void onCreateClicked();
|
||||||
void onDeleteBlueprintClicked(int index);
|
void onDeleteBlueprintClicked(int index);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent)
|
|||||||
m_costs[def.type] = def.cost;
|
m_costs[def.type] = def.cost;
|
||||||
|
|
||||||
const QString label = displayName(def.id)
|
const QString label = displayName(def.id)
|
||||||
+ "\n" + QString::number(def.cost) + " Blocks";
|
+ "\n" + tr("%1 Blocks").arg(def.cost);
|
||||||
QPushButton* btn = new QPushButton(label, this);
|
QPushButton* btn = new QPushButton(label, this);
|
||||||
btn->setCheckable(true);
|
btn->setCheckable(true);
|
||||||
btn->setFixedHeight(48);
|
btn->setFixedHeight(48);
|
||||||
@@ -83,7 +83,7 @@ BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent)
|
|||||||
}
|
}
|
||||||
connect(mapper, qOverload<int>(&QSignalMapper::mapped), this, &BuildButtonGrid::onBuildButton);
|
connect(mapper, qOverload<int>(&QSignalMapper::mapped), this, &BuildButtonGrid::onBuildButton);
|
||||||
|
|
||||||
m_demolishButton = new QPushButton("Demolish", this);
|
m_demolishButton = new QPushButton(tr("Demolish"), this);
|
||||||
m_demolishButton->setCheckable(true);
|
m_demolishButton->setCheckable(true);
|
||||||
m_demolishButton->setFixedHeight(48);
|
m_demolishButton->setFixedHeight(48);
|
||||||
layout->addWidget(m_demolishButton, row, col);
|
layout->addWidget(m_demolishButton, row, col);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "BeltSystem.h"
|
#include "BeltSystem.h"
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
|
#include "EventManager.h"
|
||||||
#include "FacingComponent.h"
|
#include "FacingComponent.h"
|
||||||
#include "FactionComponent.h"
|
#include "FactionComponent.h"
|
||||||
#include "HealthComponent.h"
|
#include "HealthComponent.h"
|
||||||
@@ -32,6 +33,11 @@
|
|||||||
#include "StationBodyComponent.h"
|
#include "StationBodyComponent.h"
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "TracePrintRequestedEvent.h"
|
||||||
|
#include "BossWaveUpdatedEvent.h"
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
|
#include "GameSpeedChangedEvent.h"
|
||||||
|
#include "TickAdvancedEvent.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -237,12 +243,33 @@ void GameWorldView::onFrame()
|
|||||||
clampScroll();
|
clampScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit state update for header bar / build grid
|
// Fire events for any state that changed since the last frame
|
||||||
emit stateUpdated(m_sim->currentTick(),
|
{
|
||||||
m_sim->buildingBlocksStock(),
|
const Tick newTick = m_sim->currentTick();
|
||||||
m_gameSpeedMultiplier,
|
const int newBlocks = m_sim->buildingBlocksStock();
|
||||||
m_sim->bossWaveCounter(),
|
const int newBoss = m_sim->bossWaveCounter();
|
||||||
m_sim->bossCountdownTicks());
|
const Tick newCountdown = m_sim->bossCountdownTicks();
|
||||||
|
|
||||||
|
if (newTick != m_lastTick)
|
||||||
|
{
|
||||||
|
m_lastTick = newTick;
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<TickAdvancedEvent>(newTick));
|
||||||
|
}
|
||||||
|
if (newBlocks != m_lastBlocks)
|
||||||
|
{
|
||||||
|
m_lastBlocks = newBlocks;
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<BuildingBlocksChangedEvent>(newBlocks));
|
||||||
|
}
|
||||||
|
if (newBoss != m_lastBossCounter || newCountdown != m_lastBossCountdown)
|
||||||
|
{
|
||||||
|
m_lastBossCounter = newBoss;
|
||||||
|
m_lastBossCountdown = newCountdown;
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<BossWaveUpdatedEvent>(newBoss, newCountdown));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Game over check
|
// Game over check
|
||||||
if (m_sim->isGameOver() && !m_gameOverShown)
|
if (m_sim->isGameOver() && !m_gameOverShown)
|
||||||
@@ -903,7 +930,7 @@ void GameWorldView::drawDebugSensorRanges(QPainter& painter)
|
|||||||
if (it == m_visuals->ships.end()) { return; }
|
if (it == m_visuals->ships.end()) { return; }
|
||||||
|
|
||||||
const QPointF center = worldToWidget(pos.value);
|
const QPointF center = worldToWidget(pos.value);
|
||||||
const qreal radiusPx = static_cast<qreal>(sensor.value)
|
const qreal radiusPx = static_cast<qreal>(sensor.value_tiles)
|
||||||
* static_cast<qreal>(tilePx());
|
* static_cast<qreal>(tilePx());
|
||||||
painter.setPen(QPen(it->second.outline, 1));
|
painter.setPen(QPen(it->second.outline, 1));
|
||||||
painter.drawEllipse(center, radiusPx, radiusPx);
|
painter.drawEllipse(center, radiusPx, radiusPx);
|
||||||
@@ -1132,9 +1159,12 @@ void GameWorldView::keyPressEvent(QKeyEvent* event)
|
|||||||
case Qt::Key_Backspace:
|
case Qt::Key_Backspace:
|
||||||
toggleDemolishMode();
|
toggleDemolishMode();
|
||||||
break;
|
break;
|
||||||
case Qt::Key_M:
|
case Qt::Key_M:
|
||||||
m_debugDraw = !m_debugDraw;
|
m_debugDraw = !m_debugDraw;
|
||||||
break;
|
break;
|
||||||
|
case Qt::Key_L:
|
||||||
|
EventManager::getInstance()->addEvent(std::make_shared<TracePrintRequestedEvent>());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
QOpenGLWidget::keyPressEvent(event);
|
QOpenGLWidget::keyPressEvent(event);
|
||||||
break;
|
break;
|
||||||
@@ -1398,11 +1428,8 @@ double GameWorldView::gameSpeed() const
|
|||||||
void GameWorldView::setGameSpeed(double multiplier)
|
void GameWorldView::setGameSpeed(double multiplier)
|
||||||
{
|
{
|
||||||
m_gameSpeedMultiplier = multiplier;
|
m_gameSpeedMultiplier = multiplier;
|
||||||
emit stateUpdated(m_sim->currentTick(),
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
m_sim->buildingBlocksStock(),
|
std::make_shared<GameSpeedChangedEvent>(m_gameSpeedMultiplier));
|
||||||
m_gameSpeedMultiplier,
|
|
||||||
m_sim->bossWaveCounter(),
|
|
||||||
m_sim->bossCountdownTicks());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameWorldView::resetForNewGame()
|
void GameWorldView::resetForNewGame()
|
||||||
@@ -1422,8 +1449,12 @@ void GameWorldView::resetForNewGame()
|
|||||||
m_scrollLeft = false;
|
m_scrollLeft = false;
|
||||||
m_scrollRight = false;
|
m_scrollRight = false;
|
||||||
m_gameOverShown = false;
|
m_gameOverShown = false;
|
||||||
m_gameSpeedMultiplier = 1.0;
|
|
||||||
m_prevNonZeroSpeed = 1.0;
|
m_prevNonZeroSpeed = 1.0;
|
||||||
|
m_lastTick = Tick(-1);
|
||||||
|
m_lastBlocks = -1;
|
||||||
|
m_lastBossCounter = -1;
|
||||||
|
m_lastBossCountdown = Tick(-1);
|
||||||
emit selectionChanged({});
|
emit selectionChanged({});
|
||||||
|
setGameSpeed(1.0);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void selectionChanged(const std::vector<BuildingId>& ids);
|
void selectionChanged(const std::vector<BuildingId>& ids);
|
||||||
void stateUpdated(Tick tick, int blocks, double speed, int bossCounter, Tick bossCountdownTicks);
|
|
||||||
void gameOver();
|
void gameOver();
|
||||||
void builderModeExited();
|
void builderModeExited();
|
||||||
void blueprintModeExited();
|
void blueprintModeExited();
|
||||||
@@ -173,4 +172,9 @@ private:
|
|||||||
bool m_scrollLeft;
|
bool m_scrollLeft;
|
||||||
bool m_scrollRight;
|
bool m_scrollRight;
|
||||||
bool m_gameOverShown;
|
bool m_gameOverShown;
|
||||||
|
|
||||||
|
Tick m_lastTick = Tick(-1);
|
||||||
|
int m_lastBlocks = -1;
|
||||||
|
int m_lastBossCounter = -1;
|
||||||
|
Tick m_lastBossCountdown = Tick(-1);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ HeaderBar::HeaderBar(QWidget* parent)
|
|||||||
layout->setSpacing(8);
|
layout->setSpacing(8);
|
||||||
|
|
||||||
m_timeLabel = new QLabel("00:00", this);
|
m_timeLabel = new QLabel("00:00", this);
|
||||||
m_blocksLabel = new QLabel("Blocks: 0", this);
|
m_blocksLabel = new QLabel(tr("Blocks: 0"), this);
|
||||||
m_bossLabel = new QLabel("Boss Wave #1 Next boss: 5:00", this);
|
m_bossLabel = new QLabel(tr("Boss Wave #1 Next boss: 5:00"), this);
|
||||||
layout->addWidget(m_timeLabel);
|
layout->addWidget(m_timeLabel);
|
||||||
layout->addWidget(m_blocksLabel);
|
layout->addWidget(m_blocksLabel);
|
||||||
layout->addStretch();
|
layout->addStretch();
|
||||||
@@ -43,39 +43,49 @@ HeaderBar::HeaderBar(QWidget* parent)
|
|||||||
connect(mapper, qOverload<int>(&QSignalMapper::mapped), this, &HeaderBar::onSpeedButton);
|
connect(mapper, qOverload<int>(&QSignalMapper::mapped), this, &HeaderBar::onSpeedButton);
|
||||||
|
|
||||||
setFixedHeight(sizeHint().height());
|
setFixedHeight(sizeHint().height());
|
||||||
|
|
||||||
|
registerForEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeaderBar::onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed,
|
HeaderBar::~HeaderBar()
|
||||||
int bossCounter, Tick bossCountdownTicks)
|
|
||||||
{
|
{
|
||||||
const int totalSeconds = static_cast<int>(ticksToSeconds(tick));
|
unregisterForEvents();
|
||||||
const int minutes = totalSeconds / 60;
|
}
|
||||||
const int seconds = totalSeconds % 60;
|
|
||||||
|
|
||||||
|
void HeaderBar::handleEvent(std::shared_ptr<const TickAdvancedEvent> event)
|
||||||
|
{
|
||||||
|
const int totalSeconds = static_cast<int>(ticksToSeconds(event->tick));
|
||||||
m_timeLabel->setText(
|
m_timeLabel->setText(
|
||||||
QString("%1:%2")
|
QString("%1:%2")
|
||||||
.arg(minutes, 2, 10, QChar('0'))
|
.arg(totalSeconds / 60, 2, 10, QChar('0'))
|
||||||
.arg(seconds, 2, 10, QChar('0')));
|
.arg(totalSeconds % 60, 2, 10, QChar('0')));
|
||||||
|
}
|
||||||
|
|
||||||
m_blocksLabel->setText(QString("Blocks: %1").arg(buildingBlocks));
|
void HeaderBar::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
|
||||||
|
{
|
||||||
const int bossSeconds = static_cast<int>(ticksToSeconds(
|
m_blocksLabel->setText(tr("Blocks: %1").arg(event->blocks));
|
||||||
bossCountdownTicks > 0 ? bossCountdownTicks : 0));
|
}
|
||||||
const int bossMin = bossSeconds / 60;
|
|
||||||
const int bossSec = bossSeconds % 60;
|
|
||||||
m_bossLabel->setText(
|
|
||||||
QString("Boss Wave #%1 Next boss: %2:%3")
|
|
||||||
.arg(bossCounter)
|
|
||||||
.arg(bossMin)
|
|
||||||
.arg(bossSec, 2, 10, QChar('0')));
|
|
||||||
|
|
||||||
|
void HeaderBar::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
|
||||||
|
{
|
||||||
for (int i = 0; i < kSpeedCount; ++i)
|
for (int i = 0; i < kSpeedCount; ++i)
|
||||||
{
|
{
|
||||||
const bool active = (std::abs(kSpeeds[i] - gameSpeed) < 0.001);
|
m_speedButtons[static_cast<std::size_t>(i)]->setChecked(
|
||||||
m_speedButtons[static_cast<std::size_t>(i)]->setChecked(active);
|
std::abs(kSpeeds[i] - event->speed) < 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HeaderBar::handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event)
|
||||||
|
{
|
||||||
|
const int bossSeconds = static_cast<int>(
|
||||||
|
ticksToSeconds(event->countdownTicks > 0 ? event->countdownTicks : 0));
|
||||||
|
m_bossLabel->setText(
|
||||||
|
tr("Boss Wave #%1 Next boss: %2:%3")
|
||||||
|
.arg(event->counter)
|
||||||
|
.arg(bossSeconds / 60)
|
||||||
|
.arg(bossSeconds % 60, 2, 10, QChar('0')));
|
||||||
|
}
|
||||||
|
|
||||||
void HeaderBar::onSpeedButton(int index)
|
void HeaderBar::onSpeedButton(int index)
|
||||||
{
|
{
|
||||||
if (index >= 0 && index < kSpeedCount)
|
if (index >= 0 && index < kSpeedCount)
|
||||||
@@ -83,4 +93,3 @@ void HeaderBar::onSpeedButton(int index)
|
|||||||
emit speedChanged(kSpeeds[index]);
|
emit speedChanged(kSpeeds[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,27 @@
|
|||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "BossWaveUpdatedEvent.h"
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
|
#include "EventHandler.h"
|
||||||
|
#include "GameSpeedChangedEvent.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "TickAdvancedEvent.h"
|
||||||
|
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
|
|
||||||
class HeaderBar : public QWidget
|
class HeaderBar : public QWidget,
|
||||||
|
public CombinedEventHandler<TickAdvancedEvent,
|
||||||
|
BuildingBlocksChangedEvent,
|
||||||
|
GameSpeedChangedEvent,
|
||||||
|
BossWaveUpdatedEvent>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HeaderBar(QWidget* parent = nullptr);
|
explicit HeaderBar(QWidget* parent = nullptr);
|
||||||
|
~HeaderBar() override;
|
||||||
public slots:
|
|
||||||
void onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed,
|
|
||||||
int bossCounter, Tick bossCountdownTicks);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void speedChanged(double multiplier);
|
void speedChanged(double multiplier);
|
||||||
@@ -27,6 +33,11 @@ private slots:
|
|||||||
void onSpeedButton(int index);
|
void onSpeedButton(int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event) override;
|
||||||
|
|
||||||
QLabel* m_timeLabel;
|
QLabel* m_timeLabel;
|
||||||
QLabel* m_blocksLabel;
|
QLabel* m_blocksLabel;
|
||||||
QLabel* m_bossLabel;
|
QLabel* m_bossLabel;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "BlueprintPanel.h"
|
#include "BlueprintPanel.h"
|
||||||
#include "BuildButtonGrid.h"
|
#include "BuildButtonGrid.h"
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "GameWorldView.h"
|
#include "GameWorldView.h"
|
||||||
@@ -28,7 +29,7 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
|||||||
, m_visuals(VisualsLoader::load(configDir + "/visuals.toml"))
|
, m_visuals(VisualsLoader::load(configDir + "/visuals.toml"))
|
||||||
, m_sim(sim)
|
, m_sim(sim)
|
||||||
{
|
{
|
||||||
setWindowTitle("Dota Factory");
|
setWindowTitle(tr("Dota Factory"));
|
||||||
resize(1280, 768);
|
resize(1280, 768);
|
||||||
|
|
||||||
m_headerBar = new HeaderBar(this);
|
m_headerBar = new HeaderBar(this);
|
||||||
@@ -52,15 +53,6 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
|||||||
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
||||||
m_selectedBuildingPanel, &SelectedBuildingPanel::onSelectionChanged);
|
m_selectedBuildingPanel, &SelectedBuildingPanel::onSelectionChanged);
|
||||||
|
|
||||||
connect(m_gameWorldView, &GameWorldView::stateUpdated,
|
|
||||||
m_headerBar, &HeaderBar::onStateUpdated);
|
|
||||||
|
|
||||||
connect(m_gameWorldView, &GameWorldView::stateUpdated,
|
|
||||||
this, &MainWindow::onStateUpdated); // for affordability
|
|
||||||
|
|
||||||
connect(m_gameWorldView, &GameWorldView::stateUpdated,
|
|
||||||
m_selectedBuildingPanel, &SelectedBuildingPanel::onStateUpdated);
|
|
||||||
|
|
||||||
connect(m_gameWorldView, &GameWorldView::gameOver,
|
connect(m_gameWorldView, &GameWorldView::gameOver,
|
||||||
this, &MainWindow::onGameOver);
|
this, &MainWindow::onGameOver);
|
||||||
|
|
||||||
@@ -90,9 +82,6 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
|||||||
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
connect(m_gameWorldView, &GameWorldView::selectionChanged,
|
||||||
m_blueprintPanel, &BlueprintPanel::onSelectionChanged);
|
m_blueprintPanel, &BlueprintPanel::onSelectionChanged);
|
||||||
|
|
||||||
connect(m_gameWorldView, &GameWorldView::stateUpdated,
|
|
||||||
m_blueprintPanel, &BlueprintPanel::onStateUpdated);
|
|
||||||
|
|
||||||
connect(m_blueprintPanel, &BlueprintPanel::blueprintPlacementRequested,
|
connect(m_blueprintPanel, &BlueprintPanel::blueprintPlacementRequested,
|
||||||
m_gameWorldView, &GameWorldView::enterBlueprintMode);
|
m_gameWorldView, &GameWorldView::enterBlueprintMode);
|
||||||
|
|
||||||
@@ -127,11 +116,18 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p
|
|||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Load Error",
|
QMessageBox::critical(this, tr("Load Error"),
|
||||||
QString("Failed to load ship_layouts.toml:\n%1").arg(e.what()));
|
tr("Failed to load ship_layouts.toml:\n%1").arg(e.what()));
|
||||||
m_layoutBlueprints.clear();
|
m_layoutBlueprints.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerForEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow()
|
||||||
|
{
|
||||||
|
unregisterForEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::resizeEvent(QResizeEvent* event)
|
void MainWindow::resizeEvent(QResizeEvent* event)
|
||||||
@@ -172,9 +168,9 @@ void MainWindow::layoutPanels()
|
|||||||
m_bottomPanel->setGeometry(0, headerH + gameH, totalW, panelH);
|
m_bottomPanel->setGeometry(0, headerH + gameH, totalW, panelH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onStateUpdated(Tick /*tick*/, int blocks, double /*speed*/)
|
void MainWindow::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
|
||||||
{
|
{
|
||||||
m_buildButtonGrid->updateAffordability(blocks);
|
m_buildButtonGrid->updateAffordability(event->blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onEscapeMenuRequested()
|
void MainWindow::onEscapeMenuRequested()
|
||||||
@@ -183,10 +179,10 @@ void MainWindow::onEscapeMenuRequested()
|
|||||||
m_gameWorldView->setGameSpeed(0.0);
|
m_gameWorldView->setGameSpeed(0.0);
|
||||||
|
|
||||||
QMessageBox box(this);
|
QMessageBox box(this);
|
||||||
box.setWindowTitle("Paused");
|
box.setWindowTitle(tr("Paused"));
|
||||||
QPushButton* continueBtn = box.addButton("Continue", QMessageBox::AcceptRole);
|
QPushButton* continueBtn = box.addButton(tr("Continue"), QMessageBox::AcceptRole);
|
||||||
QPushButton* restartBtn = box.addButton("Restart", QMessageBox::ResetRole);
|
QPushButton* restartBtn = box.addButton(tr("Restart"), QMessageBox::ResetRole);
|
||||||
QPushButton* quitBtn = box.addButton("Quit", QMessageBox::DestructiveRole);
|
QPushButton* quitBtn = box.addButton(tr("Quit"), QMessageBox::DestructiveRole);
|
||||||
box.setEscapeButton(continueBtn);
|
box.setEscapeButton(continueBtn);
|
||||||
box.exec();
|
box.exec();
|
||||||
|
|
||||||
@@ -202,8 +198,8 @@ void MainWindow::onEscapeMenuRequested()
|
|||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Config Error",
|
QMessageBox::critical(this, tr("Config Error"),
|
||||||
QString("Failed to reload config:\n%1").arg(e.what()));
|
tr("Failed to reload config:\n%1").arg(e.what()));
|
||||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -255,12 +251,12 @@ void MainWindow::onGameOver()
|
|||||||
const int seconds = totalSeconds % 60;
|
const int seconds = totalSeconds % 60;
|
||||||
|
|
||||||
QMessageBox box(this);
|
QMessageBox box(this);
|
||||||
box.setWindowTitle("Game Over");
|
box.setWindowTitle(tr("Game Over"));
|
||||||
box.setText(QString("HQ destroyed!\nSurvival time: %1:%2")
|
box.setText(tr("HQ destroyed!\nSurvival time: %1:%2")
|
||||||
.arg(minutes, 2, 10, QChar('0'))
|
.arg(minutes, 2, 10, QChar('0'))
|
||||||
.arg(seconds, 2, 10, QChar('0')));
|
.arg(seconds, 2, 10, QChar('0')));
|
||||||
QPushButton* restartBtn = box.addButton("Restart", QMessageBox::AcceptRole);
|
QPushButton* restartBtn = box.addButton(tr("Restart"), QMessageBox::AcceptRole);
|
||||||
box.addButton("Quit", QMessageBox::RejectRole);
|
box.addButton(tr("Quit"), QMessageBox::RejectRole);
|
||||||
box.exec();
|
box.exec();
|
||||||
|
|
||||||
if (box.clickedButton() == restartBtn)
|
if (box.clickedButton() == restartBtn)
|
||||||
@@ -274,8 +270,8 @@ void MainWindow::onGameOver()
|
|||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, "Config Error",
|
QMessageBox::critical(this, tr("Config Error"),
|
||||||
QString("Failed to reload config:\n%1").arg(e.what()));
|
tr("Failed to reload config:\n%1").arg(e.what()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_gameWorldView->resetForNewGame();
|
m_gameWorldView->resetForNewGame();
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
|
#include "EventHandler.h"
|
||||||
#include "ShipLayoutBlueprint.h"
|
#include "ShipLayoutBlueprint.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
#include "VisualsConfig.h"
|
#include "VisualsConfig.h"
|
||||||
@@ -19,26 +21,29 @@ class BlueprintPanel;
|
|||||||
class QCloseEvent;
|
class QCloseEvent;
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
class MainWindow : public QWidget
|
class MainWindow : public QWidget,
|
||||||
|
public EventHandler<BuildingBlocksChangedEvent>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MainWindow(Simulation* sim, const std::string& configDir, QWidget* parent = nullptr);
|
MainWindow(Simulation* sim, const std::string& configDir, QWidget* parent = nullptr);
|
||||||
|
~MainWindow() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
void closeEvent(QCloseEvent* event) override;
|
void closeEvent(QCloseEvent* event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
||||||
|
void layoutPanels();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onGameOver();
|
void onGameOver();
|
||||||
void onStateUpdated(Tick tick, int blocks, double speed);
|
|
||||||
void onEscapeMenuRequested();
|
void onEscapeMenuRequested();
|
||||||
void onLayoutDialogRequested(BuildingId shipyardId);
|
void onLayoutDialogRequested(BuildingId shipyardId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void layoutPanels();
|
|
||||||
|
|
||||||
std::string m_configDir;
|
std::string m_configDir;
|
||||||
VisualsConfig m_visuals;
|
VisualsConfig m_visuals;
|
||||||
Simulation* m_sim;
|
Simulation* m_sim;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "BeltSystem.h"
|
#include "BeltSystem.h"
|
||||||
|
#include "TickAdvancedEvent.h"
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
@@ -69,10 +70,10 @@ QString rotationLabel(Rotation r)
|
|||||||
{
|
{
|
||||||
switch (r)
|
switch (r)
|
||||||
{
|
{
|
||||||
case Rotation::North: return "North (↑)";
|
case Rotation::North: return QObject::tr("North (↑)");
|
||||||
case Rotation::East: return "East (→)";
|
case Rotation::East: return QObject::tr("East (→)");
|
||||||
case Rotation::South: return "South (↓)";
|
case Rotation::South: return QObject::tr("South (↓)");
|
||||||
case Rotation::West: return "West (←)";
|
case Rotation::West: return QObject::tr("West (←)");
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -96,13 +97,13 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
|||||||
|
|
||||||
m_titleLabel = new QLabel(this);
|
m_titleLabel = new QLabel(this);
|
||||||
m_recipeCombo = new QComboBox(this);
|
m_recipeCombo = new QComboBox(this);
|
||||||
m_clearBeltBtn = new QPushButton("Clear Items", this);
|
m_clearBeltBtn = new QPushButton(tr("Clear Items"), this);
|
||||||
m_filterALabel = new QLabel(this);
|
m_filterALabel = new QLabel(this);
|
||||||
m_filterAList = new QListWidget(this);
|
m_filterAList = new QListWidget(this);
|
||||||
m_filterBLabel = new QLabel(this);
|
m_filterBLabel = new QLabel(this);
|
||||||
m_filterBList = new QListWidget(this);
|
m_filterBList = new QListWidget(this);
|
||||||
m_layoutPreview = new ShipLayoutPreview(this);
|
m_layoutPreview = new ShipLayoutPreview(this);
|
||||||
m_configureLayoutBtn = new QPushButton("Configure Layout", this);
|
m_configureLayoutBtn = new QPushButton(tr("Configure Layout"), this);
|
||||||
m_buffersLabel = new QLabel(this);
|
m_buffersLabel = new QLabel(this);
|
||||||
m_buffersLabel->setWordWrap(true);
|
m_buffersLabel->setWordWrap(true);
|
||||||
|
|
||||||
@@ -136,6 +137,13 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
|||||||
this, &SelectedBuildingPanel::onSplitterFilterChanged);
|
this, &SelectedBuildingPanel::onSplitterFilterChanged);
|
||||||
|
|
||||||
buildEmpty();
|
buildEmpty();
|
||||||
|
|
||||||
|
registerForEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedBuildingPanel::~SelectedBuildingPanel()
|
||||||
|
{
|
||||||
|
unregisterForEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectedBuildingPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
|
void SelectedBuildingPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
|
||||||
@@ -192,7 +200,7 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
|||||||
QString progress;
|
QString progress;
|
||||||
if (s->completesAt == 0)
|
if (s->completesAt == 0)
|
||||||
{
|
{
|
||||||
progress = "Queued";
|
progress = tr("Queued");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -207,15 +215,15 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
|||||||
const Tick elapsed = m_sim->currentTick() - (s->completesAt - duration);
|
const Tick elapsed = m_sim->currentTick() - (s->completesAt - duration);
|
||||||
const int pct = static_cast<int>(
|
const int pct = static_cast<int>(
|
||||||
std::max(Tick(0), std::min(duration, elapsed)) * 100 / duration);
|
std::max(Tick(0), std::min(duration, elapsed)) * 100 / duration);
|
||||||
progress = QString::number(pct) + "% complete";
|
progress = tr("%1% complete").arg(pct);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
progress = "Building...";
|
progress = tr("Building...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_titleLabel->setText("(Building) " + buildingTypeName(s->type));
|
m_titleLabel->setText(tr("(Building) %1").arg(buildingTypeName(s->type)));
|
||||||
m_titleLabel->show();
|
m_titleLabel->show();
|
||||||
m_buffersLabel->setText(progress);
|
m_buffersLabel->setText(progress);
|
||||||
m_buffersLabel->show();
|
m_buffersLabel->show();
|
||||||
@@ -231,7 +239,7 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
|||||||
m_recipeCombo->blockSignals(true);
|
m_recipeCombo->blockSignals(true);
|
||||||
m_recipeCombo->clear();
|
m_recipeCombo->clear();
|
||||||
|
|
||||||
m_recipeCombo->addItem("(none)", QString());
|
m_recipeCombo->addItem(tr("(none)"), QString());
|
||||||
|
|
||||||
if (b->type == BuildingType::Shipyard)
|
if (b->type == BuildingType::Shipyard)
|
||||||
{
|
{
|
||||||
@@ -333,7 +341,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
|
|
||||||
if (!b->inputBuffer.counts.empty())
|
if (!b->inputBuffer.counts.empty())
|
||||||
{
|
{
|
||||||
bufText += "Input: ";
|
bufText += tr("Input: ");
|
||||||
for (const std::pair<const ItemType, int>& entry : b->inputBuffer.counts)
|
for (const std::pair<const ItemType, int>& entry : b->inputBuffer.counts)
|
||||||
{
|
{
|
||||||
int perCycle = 0;
|
int perCycle = 0;
|
||||||
@@ -389,7 +397,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
{
|
{
|
||||||
outCounts[item.type.id]++;
|
outCounts[item.type.id]++;
|
||||||
}
|
}
|
||||||
bufText += "Output: ";
|
bufText += tr("Output: ");
|
||||||
for (const RecipeOutput& out : recipe->outputs)
|
for (const RecipeOutput& out : recipe->outputs)
|
||||||
{
|
{
|
||||||
const std::map<std::string, int>::const_iterator it =
|
const std::map<std::string, int>::const_iterator it =
|
||||||
@@ -407,7 +415,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
{
|
{
|
||||||
outCounts[item.type.id]++;
|
outCounts[item.type.id]++;
|
||||||
}
|
}
|
||||||
bufText += "Output: ";
|
bufText += tr("Output: ");
|
||||||
for (const std::pair<const std::string, int>& entry : outCounts)
|
for (const std::pair<const std::string, int>& entry : outCounts)
|
||||||
{
|
{
|
||||||
bufText += QString::fromStdString(entry.first)
|
bufText += QString::fromStdString(entry.first)
|
||||||
@@ -436,7 +444,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bufText += QString("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1);
|
bufText += tr("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1);
|
||||||
|
|
||||||
if (b->production.has_value())
|
if (b->production.has_value())
|
||||||
{
|
{
|
||||||
@@ -446,11 +454,11 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
const Tick elapsed = currentTick - (completesAt - cycleTicks);
|
const Tick elapsed = currentTick - (completesAt - cycleTicks);
|
||||||
const int pct = static_cast<int>(
|
const int pct = static_cast<int>(
|
||||||
std::max(Tick(0), std::min(cycleTicks, elapsed)) * 100 / cycleTicks);
|
std::max(Tick(0), std::min(cycleTicks, elapsed)) * 100 / cycleTicks);
|
||||||
bufText += QString("Progress: %1%\n").arg(pct);
|
bufText += tr("Progress: %1%\n").arg(pct);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bufText += "Progress: idle\n";
|
bufText += tr("Progress: idle\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +496,7 @@ const ShipDef* SelectedBuildingPanel::findShipDef(const std::string& id) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectedBuildingPanel::onStateUpdated(Tick /*tick*/, int /*blocks*/, double /*speed*/)
|
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
|
||||||
{
|
{
|
||||||
if (m_singleBuildingId == kInvalidBuildingId) { return; }
|
if (m_singleBuildingId == kInvalidBuildingId) { return; }
|
||||||
const Building* b = m_sim->buildings().findBuilding(m_singleBuildingId);
|
const Building* b = m_sim->buildings().findBuilding(m_singleBuildingId);
|
||||||
@@ -496,7 +504,7 @@ void SelectedBuildingPanel::onStateUpdated(Tick /*tick*/, int /*blocks*/, double
|
|||||||
{
|
{
|
||||||
// If the panel was last showing this id as a construction site, the
|
// If the panel was last showing this id as a construction site, the
|
||||||
// full building UI (recipe combo, ports, etc.) hasn't been built yet.
|
// full building UI (recipe combo, ports, etc.) hasn't been built yet.
|
||||||
if (m_titleLabel->text().startsWith("(Building) "))
|
if (m_titleLabel->text().startsWith(tr("(Building) ")))
|
||||||
{
|
{
|
||||||
rebuild();
|
rebuild();
|
||||||
}
|
}
|
||||||
@@ -592,7 +600,7 @@ void SelectedBuildingPanel::buildSplitterFilters(QPoint splitterTile)
|
|||||||
const QString& dirLabel,
|
const QString& dirLabel,
|
||||||
const std::vector<ItemType>& filter)
|
const std::vector<ItemType>& filter)
|
||||||
{
|
{
|
||||||
label->setText(dirLabel + " filter (empty = all):");
|
label->setText(tr("%1 filter (empty = all):").arg(dirLabel));
|
||||||
list->blockSignals(true);
|
list->blockSignals(true);
|
||||||
list->clear();
|
list->clear();
|
||||||
for (const std::string& itemId : items)
|
for (const std::string& itemId : items)
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
|
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
|
#include "EventHandler.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "RecipesConfig.h"
|
#include "RecipesConfig.h"
|
||||||
#include "ShipLayout.h"
|
#include "ShipLayout.h"
|
||||||
#include "ShipsConfig.h"
|
#include "ShipsConfig.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "TickAdvancedEvent.h"
|
||||||
|
|
||||||
class Simulation;
|
class Simulation;
|
||||||
class ShipLayoutPreview;
|
class ShipLayoutPreview;
|
||||||
@@ -22,20 +24,24 @@ class QListWidget;
|
|||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
|
|
||||||
class SelectedBuildingPanel : public QWidget
|
class SelectedBuildingPanel : public QWidget,
|
||||||
|
public EventHandler<TickAdvancedEvent>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SelectedBuildingPanel(Simulation* sim, const GameConfig* config,
|
SelectedBuildingPanel(Simulation* sim, const GameConfig* config,
|
||||||
QWidget* parent = nullptr);
|
QWidget* parent = nullptr);
|
||||||
|
~SelectedBuildingPanel() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void layoutDialogRequested(BuildingId shipyardId);
|
void layoutDialogRequested(BuildingId shipyardId);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onSelectionChanged(const std::vector<BuildingId>& ids);
|
void onSelectionChanged(const std::vector<BuildingId>& ids);
|
||||||
void onStateUpdated(Tick tick, int blocks, double speed);
|
|
||||||
|
private:
|
||||||
|
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRecipeChanged(int comboIndex);
|
void onRecipeChanged(int comboIndex);
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ public:
|
|||||||
layout->setContentsMargins(4, 4, 4, 4);
|
layout->setContentsMargins(4, 4, 4, 4);
|
||||||
layout->setSpacing(4);
|
layout->setSpacing(4);
|
||||||
|
|
||||||
QPushButton* createBtn = new QPushButton("Create Blueprint", this);
|
QPushButton* createBtn = new QPushButton(tr("Create Blueprint"), this);
|
||||||
createBtn->setFixedHeight(36);
|
createBtn->setFixedHeight(36);
|
||||||
layout->addWidget(createBtn);
|
layout->addWidget(createBtn);
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ public:
|
|||||||
connect(createBtn, &QPushButton::clicked, this, [this]() {
|
connect(createBtn, &QPushButton::clicked, this, [this]() {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const QString name = QInputDialog::getText(
|
const QString name = QInputDialog::getText(
|
||||||
this, "Create Blueprint", "Blueprint name:",
|
this, tr("Create Blueprint"), tr("Blueprint name:"),
|
||||||
QLineEdit::Normal, QString(), &ok);
|
QLineEdit::Normal, QString(), &ok);
|
||||||
if (!ok || name.trimmed().isEmpty()) { return; }
|
if (!ok || name.trimmed().isEmpty()) { return; }
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|||||||
, m_removeButton(nullptr)
|
, m_removeButton(nullptr)
|
||||||
, m_gridWidget(nullptr)
|
, m_gridWidget(nullptr)
|
||||||
{
|
{
|
||||||
setWindowTitle("Configure Ship Layout");
|
setWindowTitle(tr("Configure Ship Layout"));
|
||||||
setModal(true);
|
setModal(true);
|
||||||
|
|
||||||
// Find the ship's layout grid.
|
// Find the ship's layout grid.
|
||||||
@@ -482,7 +482,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|||||||
this, &ShipLayoutDialog::onModuleButtonClicked);
|
this, &ShipLayoutDialog::onModuleButtonClicked);
|
||||||
|
|
||||||
// Remove button.
|
// Remove button.
|
||||||
m_removeButton = new QPushButton("Remove", this);
|
m_removeButton = new QPushButton(tr("Remove"), this);
|
||||||
m_removeButton->setCheckable(true);
|
m_removeButton->setCheckable(true);
|
||||||
m_removeButton->setFixedHeight(48);
|
m_removeButton->setFixedHeight(48);
|
||||||
if (col > 0)
|
if (col > 0)
|
||||||
@@ -513,8 +513,8 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|||||||
|
|
||||||
// Confirm / Cancel buttons.
|
// Confirm / Cancel buttons.
|
||||||
QHBoxLayout* bottomBar = new QHBoxLayout();
|
QHBoxLayout* bottomBar = new QHBoxLayout();
|
||||||
QPushButton* confirmBtn = new QPushButton("Confirm", this);
|
QPushButton* confirmBtn = new QPushButton(tr("Confirm"), this);
|
||||||
QPushButton* cancelBtn = new QPushButton("Cancel", this);
|
QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this);
|
||||||
bottomBar->addWidget(confirmBtn);
|
bottomBar->addWidget(confirmBtn);
|
||||||
bottomBar->addWidget(cancelBtn);
|
bottomBar->addWidget(cancelBtn);
|
||||||
rightLayout->addLayout(bottomBar);
|
rightLayout->addLayout(bottomBar);
|
||||||
|
|||||||
Reference in New Issue
Block a user