use meters in config
This commit is contained in:
@@ -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.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.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.departureIntervalSeconds = requireDouble(tbl["world"]["departure_interval_seconds"], file, "world.departure_interval_seconds");
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ struct WorldConfig
|
||||
int refundPercentage; // REQ-BLD-DEMOLISH
|
||||
int startingBuildingBlocks; // REQ-HQ-STARTING-BLOCKS
|
||||
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
|
||||
double departureIntervalSeconds; // REQ-SHP-RALLY
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ void EntityAdmin::clear()
|
||||
}
|
||||
|
||||
entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
||||
float maxSpeedPerTick, float mainAccelPerTick,
|
||||
float maneuveringAccelPerTick, float angularAccelPerTick,
|
||||
float maxRotationSpeedPerTick, float sensorRange,
|
||||
float maxSpeed_tpt, float mainAcceleration_tptt,
|
||||
float maneuveringAcceleration_tptt, float maxAngularAcceleration_rptt,
|
||||
float maxRotationSpeed_rpt, float sensorRange_tiles,
|
||||
int level, const std::string& schematicId, bool isEnemy)
|
||||
{
|
||||
entt::entity entity = createEntity();
|
||||
@@ -50,17 +50,17 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
||||
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
||||
add<FacingComponent>(entity, FacingComponent{0.0f});
|
||||
add<DynamicBodyComponent>(entity, DynamicBodyComponent{
|
||||
maxSpeedPerTick,
|
||||
mainAccelPerTick,
|
||||
maneuveringAccelPerTick,
|
||||
angularAccelPerTick,
|
||||
maxRotationSpeedPerTick,
|
||||
QVector2D(0.0f, 0.0f), // velocity
|
||||
0.0f, // angularVelocity
|
||||
QVector2D(0.0f, 0.0f), // linearAcceleration
|
||||
0.0f // angularAcceleration
|
||||
maxSpeed_tpt,
|
||||
mainAcceleration_tptt,
|
||||
maneuveringAcceleration_tptt,
|
||||
maxAngularAcceleration_rptt,
|
||||
maxRotationSpeed_rpt,
|
||||
QVector2D(0.0f, 0.0f), // velocity_tpt
|
||||
0.0f, // angularVelocity_rpt
|
||||
QVector2D(0.0f, 0.0f), // linearAcceleration_tptt
|
||||
0.0f // angularAcceleration_rptt
|
||||
});
|
||||
add<SensorRangeComponent>(entity, SensorRangeComponent{sensorRange});
|
||||
add<SensorRangeComponent>(entity, SensorRangeComponent{sensorRange_tiles});
|
||||
add<ShipIdentityComponent>(entity, ShipIdentityComponent{level, schematicId});
|
||||
add<MovementIntentComponent>(entity, MovementIntentComponent{0, QVector2D(0.0f, 0.0f)});
|
||||
return entity;
|
||||
|
||||
@@ -53,9 +53,9 @@ public:
|
||||
// -- Factory methods ----------------------------------------------------
|
||||
|
||||
entt::entity spawnShip(QVector2D position, float hp, float maxHp,
|
||||
float maxSpeedPerTick, float mainAccelPerTick,
|
||||
float maneuveringAccelPerTick, float angularAccelPerTick,
|
||||
float maxRotationSpeedPerTick, float sensorRange,
|
||||
float maxSpeed_tpt, float mainAcceleration_tptt,
|
||||
float maneuveringAcceleration_tptt, float maxAngularAcceleration_rptt,
|
||||
float maxRotationSpeed_rpt, float sensorRange_tiles,
|
||||
int level, const std::string& schematicId, bool isEnemy);
|
||||
|
||||
entt::entity spawnStation(QPoint anchor, QSize footprint,
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
|
||||
struct DynamicBodyComponent
|
||||
{
|
||||
// --- dynamics parameters (formerly ShipDynamics) ---
|
||||
float maxSpeedPerTick;
|
||||
float mainAccelerationPerTick;
|
||||
float maneuveringAccelerationPerTick;
|
||||
float angularAccelerationPerTick;
|
||||
float maxRotationSpeedPerTick;
|
||||
// --- dynamics parameters ---
|
||||
float maxSpeed_tpt; // tiles/tick
|
||||
float mainAcceleration_tptt; // tiles/tick²
|
||||
float maneuveringAcceleration_tptt; // tiles/tick²
|
||||
float maxAngularAcceleration_rptt; // rad/tick²
|
||||
float maxRotationSpeed_rpt; // rad/tick
|
||||
|
||||
// --- integrated state ---
|
||||
QVector2D velocity;
|
||||
float angularVelocity;
|
||||
QVector2D velocity_tpt; // tiles/tick
|
||||
float angularVelocity_rpt; // rad/tick
|
||||
|
||||
// --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem ---
|
||||
QVector2D linearAcceleration;
|
||||
float angularAcceleration;
|
||||
QVector2D linearAcceleration_tptt; // tiles/tick²
|
||||
float angularAcceleration_rptt; // rad/tick²
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
struct RepairBehaviorComponent
|
||||
{
|
||||
std::optional<entt::entity> currentTarget;
|
||||
float maxRepairRange = 0.0f;
|
||||
float maxRepairRange_tiles = 0.0f;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
struct RepairToolComponent
|
||||
{
|
||||
float ratePerTick;
|
||||
float range;
|
||||
float range_tiles;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
|
||||
@@ -10,5 +10,5 @@ struct SalvageBehaviorComponent
|
||||
{
|
||||
std::optional<QVector2D> scrapTarget;
|
||||
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 current;
|
||||
float collectionRange;
|
||||
float collectionRange_tiles;
|
||||
int collectionIntervalTicks;
|
||||
int cooldownTicksRemaining;
|
||||
};
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
struct SensorRangeComponent
|
||||
{
|
||||
float value;
|
||||
float value_tiles;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
struct WeaponComponent
|
||||
{
|
||||
float damage;
|
||||
float range;
|
||||
float range_tiles;
|
||||
float fireRateHz;
|
||||
float cooldownTicks;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
|
||||
@@ -132,7 +132,7 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
||||
PositionComponent& pos, FactionComponent& faction,
|
||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||
{
|
||||
const float range = sensor.value;
|
||||
const float range = sensor.value_tiles;
|
||||
|
||||
// Validate current target.
|
||||
bool targetValid = false;
|
||||
@@ -251,7 +251,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
bool enemyNearby = false;
|
||||
for (const EnemyInfo& enemy : enemies)
|
||||
{
|
||||
if ((enemy.position - pos.value).length() <= sensor.value)
|
||||
if ((enemy.position - pos.value).length() <= sensor.value_tiles)
|
||||
{
|
||||
enemyNearby = true;
|
||||
break;
|
||||
@@ -285,7 +285,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
if (!targetValid)
|
||||
{
|
||||
rb.currentTarget = std::nullopt;
|
||||
float bestDist = sensor.value;
|
||||
float bestDist = sensor.value_tiles;
|
||||
|
||||
for (const RepairableInfo& r : repairables)
|
||||
{
|
||||
@@ -355,7 +355,7 @@ void AiSystem::tickRepairTools(EntityAdmin& admin)
|
||||
const float dist =
|
||||
(admin.get<PositionComponent>(preferred).value
|
||||
- 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;
|
||||
th.hp = std::min(th.hp + rt.ratePerTick, th.maxHp);
|
||||
@@ -366,7 +366,7 @@ void AiSystem::tickRepairTools(EntityAdmin& admin)
|
||||
|
||||
// Preferred target unavailable; scan for nearest damaged friendly in range.
|
||||
rt.currentTarget = std::nullopt;
|
||||
float bestDist = rt.range;
|
||||
float bestDist = rt.range_tiles;
|
||||
for (const RepairableInfo& r : repairables)
|
||||
{
|
||||
if (r.isEnemy) { continue; }
|
||||
@@ -441,7 +441,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
PositionComponent& pos,
|
||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||
{
|
||||
const float collectRange = salvageBehavior.maxCollectionRange;
|
||||
const float collectRange = salvageBehavior.maxCollectionRange_tiles;
|
||||
const AggregatedCargo& cargoState = cargoByShip[e];
|
||||
|
||||
// Assign nearest SalvageBay if needed.
|
||||
@@ -530,7 +530,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
}
|
||||
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))
|
||||
{
|
||||
++c.current;
|
||||
@@ -555,7 +555,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
}
|
||||
else
|
||||
{
|
||||
float bestDist = sensor.value;
|
||||
float bestDist = sensor.value_tiles;
|
||||
std::optional<QVector2D> bestPos;
|
||||
for (const ScrapInfo& si : allScrap)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ void CombatSystem::resolveWeapon(
|
||||
{
|
||||
const float distanceSquared =
|
||||
(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;
|
||||
}
|
||||
@@ -81,8 +81,8 @@ void CombatSystem::resolveWeapon(
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
const float acquisitionRange = admin.hasAll<SensorRangeComponent>(shipEntity)
|
||||
? admin.get<SensorRangeComponent>(shipEntity).value
|
||||
: weapon.range;
|
||||
? admin.get<SensorRangeComponent>(shipEntity).value_tiles
|
||||
: weapon.range_tiles;
|
||||
float bestDistanceSquared = acquisitionRange * acquisitionRange;
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||
[&](entt::entity candidate, const ShipIdentityComponent& /*si*/,
|
||||
|
||||
@@ -28,27 +28,27 @@ void DynamicBodySystem::tick(EntityAdmin& admin)
|
||||
DynamicBodyComponent& body)
|
||||
{
|
||||
// Integrate angular velocity, clamp to max rotation speed, then advance facing.
|
||||
body.angularVelocity += body.angularAcceleration;
|
||||
body.angularVelocity = std::max(-body.maxRotationSpeedPerTick,
|
||||
std::min(body.angularVelocity,
|
||||
body.maxRotationSpeedPerTick));
|
||||
facing.radians = wrapAngle(facing.radians + body.angularVelocity);
|
||||
body.angularVelocity_rpt += body.angularAcceleration_rptt;
|
||||
body.angularVelocity_rpt = std::max(-body.maxRotationSpeed_rpt,
|
||||
std::min(body.angularVelocity_rpt,
|
||||
body.maxRotationSpeed_rpt));
|
||||
facing.radians = wrapAngle(facing.radians + body.angularVelocity_rpt);
|
||||
|
||||
// Integrate linear velocity and cap to max speed.
|
||||
body.velocity += body.linearAcceleration;
|
||||
const float speed = body.velocity.length();
|
||||
if (speed > body.maxSpeedPerTick)
|
||||
body.velocity_tpt += body.linearAcceleration_tptt;
|
||||
const float speed = body.velocity_tpt.length();
|
||||
if (speed > body.maxSpeed_tpt)
|
||||
{
|
||||
body.velocity = body.velocity.normalized() * body.maxSpeedPerTick;
|
||||
body.velocity_tpt = body.velocity_tpt.normalized() * body.maxSpeed_tpt;
|
||||
}
|
||||
|
||||
// Advance position.
|
||||
pos.value += body.velocity;
|
||||
pos.value += body.velocity_tpt;
|
||||
|
||||
// Reset per-tick fields so stale values don't linger if the intent
|
||||
// system is skipped for this entity in a future tick.
|
||||
body.linearAcceleration = QVector2D(0.0f, 0.0f);
|
||||
body.angularAcceleration = 0.0f;
|
||||
body.linearAcceleration_tptt = QVector2D(0.0f, 0.0f);
|
||||
body.angularAcceleration_rptt = 0.0f;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,16 +32,16 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
if (intent.priority == 0)
|
||||
{
|
||||
// No movement intent: brake using available thrust.
|
||||
const float linearBraking = std::min(body.velocity.length(),
|
||||
body.maneuveringAccelerationPerTick);
|
||||
body.linearAcceleration = (body.velocity.length() > 0.0001f)
|
||||
? -body.velocity.normalized() * linearBraking
|
||||
const float linearBraking = std::min(body.velocity_tpt.length(),
|
||||
body.maneuveringAcceleration_tptt);
|
||||
body.linearAcceleration_tptt = (body.velocity_tpt.length() > 0.0001f)
|
||||
? -body.velocity_tpt.normalized() * linearBraking
|
||||
: QVector2D(0.0f, 0.0f);
|
||||
|
||||
const float angBraking = std::min(std::abs(body.angularVelocity),
|
||||
body.angularAccelerationPerTick);
|
||||
body.angularAcceleration =
|
||||
(body.angularVelocity >= 0.0f) ? -angBraking : angBraking;
|
||||
const float angBraking = std::min(std::abs(body.angularVelocity_rpt),
|
||||
body.maxAngularAcceleration_rptt);
|
||||
body.angularAcceleration_rptt =
|
||||
(body.angularVelocity_rpt >= 0.0f) ? -angBraking : angBraking;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
{
|
||||
// Already at target: no new thrust. The ship drifts; it will
|
||||
// re-approach next tick once it has moved away.
|
||||
body.linearAcceleration = QVector2D(0.0f, 0.0f);
|
||||
body.angularAcceleration = 0.0f;
|
||||
body.linearAcceleration_tptt = QVector2D(0.0f, 0.0f);
|
||||
body.angularAcceleration_rptt = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
const float desiredAngle = std::atan2(delta.y(), delta.x());
|
||||
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,
|
||||
body.angularAccelerationPerTick));
|
||||
body.maxAngularAcceleration_rptt));
|
||||
|
||||
float newAngVel = body.angularVelocity + rotDelta;
|
||||
float newAngVel = body.angularVelocity_rpt + rotDelta;
|
||||
|
||||
// Overshoot prevention: if the accumulated angular velocity already
|
||||
// exceeds the remaining angle, snap it to exactly that angle so the
|
||||
@@ -77,8 +77,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
newAngVel = angleDiff;
|
||||
}
|
||||
|
||||
body.angularAcceleration = newAngVel - body.angularVelocity;
|
||||
// DynamicBodySystem applies the clamp to maxRotationSpeedPerTick after
|
||||
body.angularAcceleration_rptt = newAngVel - body.angularVelocity_rpt;
|
||||
// DynamicBodySystem applies the clamp to maxRotationSpeed_rpt after
|
||||
// integrating, so we do not clamp here.
|
||||
|
||||
// --- Linear acceleration ---
|
||||
@@ -90,22 +90,22 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
const QVector2D facingVec(std::cos(projectedRadians),
|
||||
std::sin(projectedRadians));
|
||||
|
||||
const float manAccel = body.maneuveringAccelerationPerTick;
|
||||
const float stoppingDist = (body.maxSpeedPerTick * body.maxSpeedPerTick)
|
||||
const float manAccel = body.maneuveringAcceleration_tptt;
|
||||
const float stoppingDist = (body.maxSpeed_tpt * body.maxSpeed_tpt)
|
||||
/ (2.0f * manAccel);
|
||||
// Cap to dist so the ship never overshoots the target in a single tick.
|
||||
const float baseDesiredSpeed = (dist <= stoppingDist)
|
||||
? std::sqrt(2.0f * manAccel * dist)
|
||||
: body.maxSpeedPerTick;
|
||||
: body.maxSpeed_tpt;
|
||||
const float desiredSpeed = std::min(dist, baseDesiredSpeed);
|
||||
|
||||
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,
|
||||
QVector2D::dotProduct(velError, facingVec));
|
||||
const float mainApplied = std::min(mainAligned,
|
||||
body.mainAccelerationPerTick);
|
||||
body.mainAcceleration_tptt);
|
||||
const QVector2D mainDelta = facingVec * mainApplied;
|
||||
|
||||
const QVector2D remaining = velError - mainDelta;
|
||||
@@ -114,7 +114,7 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
? remaining.normalized() * manAccel
|
||||
: remaining;
|
||||
|
||||
body.linearAcceleration = mainDelta + maneuverDelta;
|
||||
body.linearAcceleration_tptt = mainDelta + maneuverDelta;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -62,30 +62,32 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
|
||||
const double x = static_cast<double>(level);
|
||||
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 maxHp = hp;
|
||||
float maxSpeedPerTick = static_cast<float>(def->movement.speedFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float mainAccelPerTick = static_cast<float>(
|
||||
def->movement.mainAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float maneuveringAccelPerTick = static_cast<float>(
|
||||
def->movement.maneuveringAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float angularAccelPerTick = static_cast<float>(
|
||||
def->movement.angularAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float maxRotationSpeedPerTick = static_cast<float>(
|
||||
def->movement.maxRotationSpeedFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float sensorRange = static_cast<float>(
|
||||
def->sensor.sensorRangeFormula.evaluate(x));
|
||||
float maxSpeed_tpt = static_cast<float>(def->movement.speedFormula.evaluate(x))
|
||||
/ tileSize / tickRate;
|
||||
float mainAcceleration_tptt = static_cast<float>(
|
||||
def->movement.mainAccelerationFormula.evaluate(x))
|
||||
/ tileSize / tickRate;
|
||||
float maneuveringAcceleration_tptt = static_cast<float>(
|
||||
def->movement.maneuveringAccelerationFormula.evaluate(x))
|
||||
/ tileSize / tickRate;
|
||||
float maxAngularAcceleration_rptt = static_cast<float>(
|
||||
def->movement.angularAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float maxRotationSpeed_rpt = static_cast<float>(
|
||||
def->movement.maxRotationSpeedFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float sensorRange_tiles = static_cast<float>(
|
||||
def->sensor.sensorRangeFormula.evaluate(x))
|
||||
/ tileSize;
|
||||
|
||||
entt::entity entity = m_admin.spawnShip(
|
||||
position, hp, maxHp,
|
||||
maxSpeedPerTick, mainAccelPerTick, maneuveringAccelPerTick,
|
||||
angularAccelPerTick, maxRotationSpeedPerTick, sensorRange,
|
||||
maxSpeed_tpt, mainAcceleration_tptt, maneuveringAcceleration_tptt,
|
||||
maxAngularAcceleration_rptt, maxRotationSpeed_rpt, sensorRange_tiles,
|
||||
level, schematicId, isEnemy);
|
||||
|
||||
// Determine module list: configured layout takes precedence over default.
|
||||
@@ -109,8 +111,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
WeaponComponent w;
|
||||
w.damage = static_cast<float>(
|
||||
modDef->weaponCapability->damageFormula.evaluate(mx));
|
||||
w.range = static_cast<float>(
|
||||
modDef->weaponCapability->attackRangeFormula.evaluate(mx));
|
||||
w.range_tiles = static_cast<float>(
|
||||
modDef->weaponCapability->attackRangeFormula.evaluate(mx)) / tileSize;
|
||||
w.fireRateHz = static_cast<float>(
|
||||
modDef->weaponCapability->attackRateFormula.evaluate(mx));
|
||||
w.cooldownTicks = 0.0f;
|
||||
@@ -128,8 +130,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
cargo.capacity = static_cast<int>(
|
||||
modDef->salvageCapability->cargoCapacityFormula.evaluate(mx));
|
||||
cargo.current = 0;
|
||||
cargo.collectionRange = static_cast<float>(
|
||||
modDef->salvageCapability->collectionRangeFormula.evaluate(mx));
|
||||
cargo.collectionRange_tiles = static_cast<float>(
|
||||
modDef->salvageCapability->collectionRangeFormula.evaluate(mx)) / tileSize;
|
||||
const double rate = modDef->salvageCapability->collectionRateFormula.evaluate(mx);
|
||||
cargo.collectionIntervalTicks = (rate > 0.0)
|
||||
? static_cast<int>(kTickRateHz / rate + 0.5)
|
||||
@@ -148,8 +150,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
rt.ratePerTick = static_cast<float>(
|
||||
modDef->repairCapability->repairRateFormula.evaluate(mx))
|
||||
/ static_cast<float>(kTickRateHz);
|
||||
rt.range = static_cast<float>(
|
||||
modDef->repairCapability->repairRangeFormula.evaluate(mx));
|
||||
rt.range_tiles = static_cast<float>(
|
||||
modDef->repairCapability->repairRangeFormula.evaluate(mx)) / tileSize;
|
||||
rt.currentTarget = std::nullopt;
|
||||
|
||||
entt::entity child = m_admin.createModuleEntity();
|
||||
@@ -207,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.
|
||||
auto applyMod = [](float& stat, const std::string& name,
|
||||
const std::map<std::string, std::pair<double, double>>& mods)
|
||||
@@ -226,30 +248,30 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
|
||||
SensorRangeComponent& sensor = m_admin.get<SensorRangeComponent>(entity);
|
||||
|
||||
applyMod(health.maxHp, "hp", hullMods);
|
||||
applyMod(health.maxHp, "hp", hullMods);
|
||||
health.hp = health.maxHp;
|
||||
applyMod(dynamics.maxSpeedPerTick, "speed", hullMods);
|
||||
applyMod(dynamics.mainAccelerationPerTick, "main_acceleration", hullMods);
|
||||
applyMod(dynamics.maneuveringAccelerationPerTick, "maneuvering_acceleration", hullMods);
|
||||
applyMod(dynamics.angularAccelerationPerTick, "angular_acceleration", hullMods);
|
||||
applyMod(dynamics.maxRotationSpeedPerTick, "max_rotation_speed", hullMods);
|
||||
applyMod(sensor.value, "sensor_range", hullMods);
|
||||
applyMod(dynamics.maxSpeed_tpt, "speed", hullMods);
|
||||
applyMod(dynamics.mainAcceleration_tptt, "main_acceleration", hullMods);
|
||||
applyMod(dynamics.maneuveringAcceleration_tptt, "maneuvering_acceleration", hullMods);
|
||||
applyMod(dynamics.maxAngularAcceleration_rptt, "angular_acceleration", hullMods);
|
||||
applyMod(dynamics.maxRotationSpeed_rpt, "max_rotation_speed", hullMods);
|
||||
applyMod(sensor.value_tiles, "sensor_range", hullMods);
|
||||
}
|
||||
|
||||
// Apply weapon modifiers to each weapon child.
|
||||
for (entt::entity child : weaponChildren)
|
||||
{
|
||||
WeaponComponent& w = m_admin.get<WeaponComponent>(child);
|
||||
applyMod(w.damage, "damage", weaponMods);
|
||||
applyMod(w.range, "attack_range", weaponMods);
|
||||
applyMod(w.fireRateHz, "attack_rate", weaponMods);
|
||||
applyMod(w.damage, "damage", weaponMods);
|
||||
applyMod(w.range_tiles, "attack_range", weaponMods);
|
||||
applyMod(w.fireRateHz, "attack_rate", weaponMods);
|
||||
}
|
||||
|
||||
// Apply salvage modifiers to each salvage child.
|
||||
for (entt::entity child : salvageChildren)
|
||||
{
|
||||
SalvageCargoComponent& c = m_admin.get<SalvageCargoComponent>(child);
|
||||
float fRange = c.collectionRange;
|
||||
float fRange = c.collectionRange_tiles;
|
||||
float fCapacity = static_cast<float>(c.capacity);
|
||||
// Apply rate modifier: compute rate from interval, apply multiplier, convert back.
|
||||
float fRate = (c.collectionIntervalTicks > 0)
|
||||
@@ -258,7 +280,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
applyMod(fRange, "collection_range", salvageMods);
|
||||
applyMod(fCapacity, "cargo_capacity", salvageMods);
|
||||
applyMod(fRate, "collection_rate", salvageMods);
|
||||
c.collectionRange = fRange;
|
||||
c.collectionRange_tiles = fRange;
|
||||
c.capacity = static_cast<int>(fCapacity + 0.5f);
|
||||
c.collectionIntervalTicks = (fRate > 0.0f)
|
||||
? static_cast<int>(static_cast<float>(kTickRateHz) / fRate + 0.5f)
|
||||
@@ -269,8 +291,8 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
for (entt::entity child : repairChildren)
|
||||
{
|
||||
RepairToolComponent& rt = m_admin.get<RepairToolComponent>(child);
|
||||
applyMod(rt.ratePerTick, "repair_rate", repairMods);
|
||||
applyMod(rt.range, "repair_range", repairMods);
|
||||
applyMod(rt.ratePerTick, "repair_rate", repairMods);
|
||||
applyMod(rt.range_tiles, "repair_range", repairMods);
|
||||
}
|
||||
|
||||
// --- Pass 3: attach behavior components based on capability presence -----
|
||||
@@ -292,14 +314,14 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
float maxCollRange = 0.0f;
|
||||
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; }
|
||||
}
|
||||
|
||||
SalvageBehaviorComponent sb;
|
||||
sb.scrapTarget = std::nullopt;
|
||||
sb.deliveryBay = kInvalidBuildingId;
|
||||
sb.maxCollectionRange = maxCollRange;
|
||||
sb.scrapTarget = std::nullopt;
|
||||
sb.deliveryBay = kInvalidBuildingId;
|
||||
sb.maxCollectionRange_tiles = maxCollRange;
|
||||
m_admin.addComponent<SalvageBehaviorComponent>(entity, sb);
|
||||
}
|
||||
|
||||
@@ -308,13 +330,13 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
float maxRepairRange = 0.0f;
|
||||
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; }
|
||||
}
|
||||
|
||||
RepairBehaviorComponent rb;
|
||||
rb.currentTarget = std::nullopt;
|
||||
rb.maxRepairRange = maxRepairRange;
|
||||
rb.currentTarget = std::nullopt;
|
||||
rb.maxRepairRange_tiles = maxRepairRange;
|
||||
m_admin.addComponent<RepairBehaviorComponent>(entity, rb);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ QPointF BeltSystem::slotWorldPos(QPoint tile, Rotation dir, double progress)
|
||||
// Construction / placement
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
BeltSystem::BeltSystem(double beltSpeedTilesPerSecond)
|
||||
: m_progressPerTick(beltSpeedTilesPerSecond * kTickDurationSeconds)
|
||||
BeltSystem::BeltSystem(double beltSpeed_tps)
|
||||
: m_progressPerTick_tpt(beltSpeed_tps * kTickDurationSeconds)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ void BeltSystem::advanceProgress()
|
||||
|
||||
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.
|
||||
const double absoluteCap = 1.0 - i * 0.25;
|
||||
@@ -442,7 +442,7 @@ void BeltSystem::advanceProgress()
|
||||
|
||||
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;
|
||||
if (st.back[i].progress > absoluteCap)
|
||||
{
|
||||
@@ -465,7 +465,7 @@ void BeltSystem::advanceProgress()
|
||||
|
||||
if (st.frontA)
|
||||
{
|
||||
st.frontA->progress += m_progressPerTick;
|
||||
st.frontA->progress += m_progressPerTick_tpt;
|
||||
if (st.frontA->progress > 1.0)
|
||||
{
|
||||
st.frontA->progress = 1.0;
|
||||
@@ -474,7 +474,7 @@ void BeltSystem::advanceProgress()
|
||||
|
||||
if (st.frontB)
|
||||
{
|
||||
st.frontB->progress += m_progressPerTick;
|
||||
st.frontB->progress += m_progressPerTick_tpt;
|
||||
if (st.frontB->progress > 1.0)
|
||||
{
|
||||
st.frontB->progress = 1.0;
|
||||
@@ -492,7 +492,7 @@ void BeltSystem::advanceTunnelProgress()
|
||||
|
||||
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;
|
||||
if (te.itemSlots[i].progress > absoluteCap)
|
||||
@@ -518,7 +518,7 @@ void BeltSystem::advanceTunnelProgress()
|
||||
|
||||
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;
|
||||
if (tx.itemSlots[i].progress > absoluteCap)
|
||||
@@ -542,7 +542,7 @@ void BeltSystem::advanceTunnelProgress()
|
||||
for (std::size_t i = 0; i < link.items.size(); ++i)
|
||||
{
|
||||
TunnelTransitItem& ti = link.items[i];
|
||||
ti.progress += m_progressPerTick;
|
||||
ti.progress += m_progressPerTick_tpt;
|
||||
if (ti.progress > link.length)
|
||||
{
|
||||
ti.progress = link.length;
|
||||
|
||||
@@ -31,7 +31,7 @@ struct VisualItem
|
||||
class BeltSystem
|
||||
{
|
||||
public:
|
||||
explicit BeltSystem(double beltSpeedTilesPerSecond);
|
||||
explicit BeltSystem(double beltSpeed_tps);
|
||||
|
||||
// -- Placement -----------------------------------------------------------
|
||||
// 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
|
||||
};
|
||||
|
||||
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>, SplitterTile> m_splitters;
|
||||
|
||||
@@ -33,7 +33,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
, m_hqProxyEntity(entt::null)
|
||||
, m_playerStation1Entity(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[1] = entt::null;
|
||||
@@ -110,7 +110,7 @@ void Simulation::reset(unsigned int seed)
|
||||
m_schematicDropEvents.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_config,
|
||||
m_beltSystem,
|
||||
@@ -244,11 +244,13 @@ void Simulation::placeInitialStructures()
|
||||
const float psHp = static_cast<float>(
|
||||
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
||||
|
||||
const float tileSize = static_cast<float>(m_config.world.tileSize_m);
|
||||
|
||||
WeaponComponent psWeapon;
|
||||
psWeapon.damage = static_cast<float>(
|
||||
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
||||
psWeapon.range = static_cast<float>(
|
||||
m_config.stations.playerStation.rangeFormula.evaluate(psLevel));
|
||||
psWeapon.range_tiles = static_cast<float>(
|
||||
m_config.stations.playerStation.rangeFormula.evaluate(psLevel)) / tileSize;
|
||||
psWeapon.fireRateHz = static_cast<float>(
|
||||
m_config.stations.playerStation.fireRateFormula.evaluate(psLevel));
|
||||
psWeapon.cooldownTicks = 0.0f;
|
||||
@@ -303,6 +305,7 @@ void Simulation::placeInitialStructures()
|
||||
|
||||
void Simulation::placeEnemyStationSet(int generation)
|
||||
{
|
||||
const float tileSize = static_cast<float>(m_config.world.tileSize_m);
|
||||
const ParsedSurfaceMask esParsed =
|
||||
parseSurfaceMask(m_config.stations.enemyStation.surfaceMask, Rotation::East);
|
||||
|
||||
@@ -318,8 +321,8 @@ void Simulation::placeEnemyStationSet(int generation)
|
||||
WeaponComponent esWeapon;
|
||||
esWeapon.damage = static_cast<float>(
|
||||
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
||||
esWeapon.range = static_cast<float>(
|
||||
m_config.stations.enemyStation.rangeFormula.evaluate(genD));
|
||||
esWeapon.range_tiles = static_cast<float>(
|
||||
m_config.stations.enemyStation.rangeFormula.evaluate(genD)) / tileSize;
|
||||
esWeapon.fireRateHz = static_cast<float>(
|
||||
m_config.stations.enemyStation.fireRateFormula.evaluate(genD));
|
||||
esWeapon.cooldownTicks = 0.0f;
|
||||
|
||||
Reference in New Issue
Block a user