88 lines
4.3 KiB
Markdown
88 lines
4.3 KiB
Markdown
# 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.
|