Files
dota_factory/modular_ships.md
2026-05-27 17:03:13 +02:00

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.