载入中...
搜索中...
未找到
RTS.cpp
浏览该文件的文档.
37Result<RTSEffectDefinition> resolveEffectDefinition(definitions::DefinitionRegistry& registry, std::string_view id,
54 if (const auto* integer = found->second.getIf<std::int64_t>()) return static_cast<double>(*integer);
64 !std::isfinite(*speed) || !std::isfinite(*damage) || !std::isfinite(*incoming) || !std::isfinite(*healing) ||
78 return Result<RTSEffectDefinition>::success(std::move(definition), Status::success(StatusCode::Applied));
84 DiagnosticCode::InvalidArgument, "RTS refund factor must be finite and positive", "refund.factor"));
88 const auto amount = static_cast<std::int64_t>(std::llround(static_cast<double>(item.amount.value()) * factor));
149 return Result<double>::success(found->second.isInt64() ? static_cast<double>(found->second.asInt())
169 return static_cast<std::size_t>(std::count_if(handles.begin(), handles.end(), [](const ecs::EntityHandle& handle) {
197 DiagnosticCode::InvalidArgument, "RTS script definition must be a canonical logical id", "definition"));
206 DiagnosticCode::InvalidArgument, "RTS combat stance must be passive, defensive, or aggressive", "stance"));
309 if (auto* faction = tryGetCurrent<Faction>(handle)) faction->intel()->enabled = static_cast<bool>(fogProvider_);
312void RTS::setCombatProviders(sensing::SensingWorld* sensing, combat::DamageRuntime* damage) noexcept {
326 Diagnostic::error(DiagnosticCode::NotFound, "RTS combat damage provider is not attached", "damage"));
350 if (player != nullptr && player->identity()->subject.isValid()) result.push_back(player->identity()->subject);
353 [](const SubjectRef& left, const SubjectRef& right) { return left.format() < right.format(); });
357Result<GameplayObservation> RTS::observeGameplay(const GameplaySession& session, SubjectRef instance) const {
364 DiagnosticCode::PreconditionViolation, "session does not control this RTS player", "instance"));
393Result<std::vector<GameplayActionDescriptor>> RTS::availableGameplayActions(const GameplaySession& session,
400 Diagnostic::error(DiagnosticCode::NotFound, "RTS gameplay player or unit was not found", "subject"));
403 DiagnosticCode::PreconditionViolation, "session does not control this RTS player", "instance"));
406 std::any_of(player->selection()->units.begin(), player->selection()->units.end(), [&](const auto& handle) {
407 return handle.table == unitHandle.table && handle.type == unitHandle.type && handle.id == unitHandle.id &&
412 DiagnosticCode::PreconditionViolation, "RTS unit is not in the player's selection", "subject"));
421Result<GameplayCommandReceipt> RTS::submitGameplay(const GameplaySession& session, SubjectRef instance,
429 DiagnosticCode::PreconditionViolation, "session does not control this RTS player", "instance"));
432 Diagnostic::error(DiagnosticCode::InvalidArgument, "command id must not be empty", "command.id"));
433 if (command.observedTick != player->selection()->tick || command.expectedRevision != player->selection()->revision)
435 DiagnosticCode::Conflict, "RTS command was based on a stale observation", "command.expectedRevision"));
450 Diagnostic::error(DiagnosticCode::Unsupported, "unsupported RTS gameplay action", "command.action"));
460 receipt.executionId = fanOutReceipt.orderIds.empty() ? std::string{} : fanOutReceipt.orderIds.front();
465 receipt.details = Value(Value::Object{{"accepted", Value(static_cast<std::int64_t>(fanOutReceipt.accepted))},
479 return Result<GameplayCommandReceipt>::success(std::move(receipt), Status::success(StatusCode::Applied));
482Result<GameplayObservation> RTS::advanceGameplay(const GameplaySession& session, SubjectRef instance,
490 DiagnosticCode::PreconditionViolation, "session does not control this RTS player", "instance"));
502Result<std::vector<GameplayEvent>> RTS::gameplayEvents(const GameplaySession& session, SubjectRef instance,
522 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS Unit requires a valid SubjectRef", "subject"));
523 if (ownsSubject(subject, scriptRuntime_ && scriptRuntime_->spawningProduction ? SubjectClaimScope::Live
526 Diagnostic::error(DiagnosticCode::Conflict, "RTS SubjectRef is already owned by this module", "subject"));
553 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS Building requires a valid SubjectRef", "subject"));
556 Diagnostic::error(DiagnosticCode::Conflict, "RTS SubjectRef is already owned by this module", "subject"));
571Result<ResourceNode*> RTS::newResourceNode(SubjectRef subject, std::string resourceType, float amount,
573 if (!validSubject(subject) || resourceType.empty() || !std::isfinite(amount) || amount < 0.0f ||
576 DiagnosticCode::InvalidArgument, "RTS resource node requires valid identity, stock, position and capacity",
580 Diagnostic::error(DiagnosticCode::Conflict, "RTS SubjectRef is already owned by this module", "subject"));
584 Diagnostic::error(DiagnosticCode::Failed, "ECS failed to create an RTS ResourceNode", "resourceNode"));
598 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS Player requires a valid SubjectRef", "subject"));
601 Diagnostic::error(DiagnosticCode::Conflict, "RTS SubjectRef is already owned by this module", "subject"));
613 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS Faction requires a valid SubjectRef", "subject"));
616 Diagnostic::error(DiagnosticCode::Conflict, "RTS SubjectRef is already owned by this module", "subject"));
657 auto runtime = weapon::WeaponDefinitionRuntime::create(*definitions_, definitionId, instanceId);
662 Diagnostic::error(DiagnosticCode::Failed, "ECS failed to create an RTS weapon instance", "weapon"));
675 if (auto* entity = dynamic_cast<weapon::WeaponEntity*>(ecs::try_get(weapons_.back()))) entity->release();
688 auto runtime = weapon::WeaponDefinitionRuntime::create(*definitions_, definitionId, instanceId);
693 Diagnostic::error(DiagnosticCode::Failed, "ECS failed to create an RTS weapon instance", "weapon"));
706 if (auto* entity = dynamic_cast<weapon::WeaponEntity*>(ecs::try_get(weapons_.back()))) entity->release();
716 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Faction does not belong to this facade", "faction"));
723 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(ecs::try_get(weapons_.back()))) weapon->release();
763Result<Building*> RTS::newFactionBuilding(Faction& faction, SubjectRef subject, LogicalId definition) {
766 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Faction does not belong to this facade", "faction"));
773 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(ecs::try_get(weapons_.back()))) weapon->release();
801 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS Match requires a valid SubjectRef", "subject"));
804 Diagnostic::error(DiagnosticCode::Conflict, "RTS SubjectRef is already owned by this module", "subject"));
813Result<void> RTS::configureMatch(Match& match, VictoryRule rule, std::string archetype, double targetValue) const {
819 Diagnostic::error(DiagnosticCode::Conflict, "RTS match rules are immutable after start", "match.phase"));
823 if (rule == VictoryRule::ResourceTarget && (archetype.empty() || !std::isfinite(targetValue) || targetValue <= 0.0))
836 DiagnosticCode::InvalidArgument, "RTS match and faction must share this composition root", "participant"));
850 DiagnosticCode::InvalidArgument, "RTS match and faction must share this composition root", "participant"));
865 Value::Object{{"faction", faction == nullptr ? std::string{} : faction->identity()->subject.format()},
878 return Result<Value>::success(Value(Value::Object{{"subject", match.identity()->subject.format()},
889Unit* RTS::findUnit(SubjectRef subject) const noexcept { return findSubject<Unit>(units_, subject); }
891Building* RTS::findBuilding(SubjectRef subject) const noexcept { return findSubject<Building>(buildings_, subject); }
899 std::any_of(scriptRuntime_->pendingProductionSubjects.begin(), scriptRuntime_->pendingProductionSubjects.end(),
902 return findSubject<Unit>(units_, subject) != nullptr || findSubject<Building>(buildings_, subject) != nullptr ||
904 findSubject<Player>(players_, subject) != nullptr || findSubject<Faction>(factions_, subject) != nullptr ||
908Result<void> RTS::setUnitStance(std::span<const SubjectRef> subjects, CombatStance stance, float leashRange) const {
909 if (stance != CombatStance::Passive && stance != CombatStance::Defensive && stance != CombatStance::Aggressive)
914 DiagnosticCode::InvalidArgument, "RTS combat leash range must be finite and non-negative", "leashRange"));
932 if (stance == CombatStance::Passive && unit->orders()->values.orderCount() == 0) combat->target = {};
937Result<void> RTS::setUnitMovementPriority(std::span<const SubjectRef> subjects, int priority) const {
956 DiagnosticCode::StaleHandle, "RTS worker assignment requires roots owned by this facade", "assignment"));
958 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "RTS worker must be alive", "worker"));
962 Diagnostic::error(DiagnosticCode::Conflict, "RTS unit is not configured as a resource worker", "worker"));
966 DiagnosticCode::Conflict, "RTS resource node is depleted or incompatible with the worker", "node"));
970 std::find(dropoff.dropoff()->acceptedResources.begin(), dropoff.dropoff()->acceptedResources.end(),
973 DiagnosticCode::Conflict, "RTS dropoff must be completed, friendly, and accept the resource", "dropoff"));
976 std::erase_if(assigned, [](const ecs::EntityHandle& handle) { return tryGetCurrent<Unit>(handle) == nullptr; });
982 Diagnostic::error(DiagnosticCode::Conflict, "RTS resource node worker capacity is full", "node"));
1007Result<void> RTS::setWorkerAutoAssignment(std::span<const SubjectRef> subjects, bool enabled) const {
1027Result<void> RTS::configureWorkforce(Faction& faction, bool autoConstruction, int maxBuildersPerSite, bool autoRepair,
1031 DiagnosticCode::StaleHandle, "RTS workforce faction does not belong to this facade", "faction"));
1048 DiagnosticCode::StaleHandle, "RTS builder assignment requires roots owned by this facade", "assignment"));
1049 if (!worker.durability()->alive || worker.containment()->container.isBound() || worker.worker()->buildRate <= 0.0f)
1051 DiagnosticCode::Conflict, "RTS builder must be alive, deployed, and construction-capable", "worker"));
1052 if (!building.integrity()->alive || building.construction()->progress >= 1.0f || building.construction()->paused)
1054 DiagnosticCode::Conflict, "RTS construction target must be alive, unfinished, and active", "building"));
1058 DiagnosticCode::Conflict, "RTS builder and construction target must share a faction", "assignment"));
1071 DiagnosticCode::StaleHandle, "RTS reserve-ammunition unit does not belong to this facade", "unit"));
1074 Diagnostic::error(DiagnosticCode::Conflict, "RTS reserve-ammunition unit must be alive", "unit"));
1078 Diagnostic::error(DiagnosticCode::NotFound, "RTS unit has no canonical weapon", "unit.weapon"));
1082 DiagnosticCode::Conflict, "RTS unit weapon has no finite reserve ammunition", "unit.weapon.resource"));
1086 DiagnosticCode::Conflict, "RTS unit weapon ammunition pool is infinite", "unit.weapon.ammoPool"));
1088 pool->state()->count = static_cast<int>(std::clamp<long long>(adjusted, 0, pool->state()->max));
1105 DiagnosticCode::StaleHandle, "RTS ammunition-supply unit does not belong to this facade", "unit"));
1108 Diagnostic::error(DiagnosticCode::Conflict, "RTS ammunition-supply unit must be alive", "unit"));
1120 DiagnosticCode::StaleHandle, "RTS ammunition-supply building does not belong to this facade", "building"));
1123 Diagnostic::error(DiagnosticCode::Conflict, "RTS ammunition-supply building must be alive", "building"));
1135 DiagnosticCode::StaleHandle, "RTS automatic-resupply unit does not belong to this facade", "unit"));
1138 Diagnostic::error(DiagnosticCode::Conflict, "RTS automatic-resupply unit must be alive", "unit"));
1143 if (current && (current.value().kind == OrderKind::Resupply || current.value().kind == OrderKind::SupplyRelay)) {
1163Result<FanOutReceipt> RTS::suppressArea(std::span<const SubjectRef> subjects, WorldPosition start, WorldPosition end,
1165 if (!std::isfinite(start.x) || !std::isfinite(start.y) || !std::isfinite(end.x) || !std::isfinite(end.y) ||
1183 Diagnostic::error(DiagnosticCode::Conflict, "RTS suppression unit must be alive, deployed, and armed",
1201Result<FanOutReceipt> RTS::escortUnits(std::span<const SubjectRef> subjects, SubjectRef protectedSubject,
1203 if (!std::isfinite(guardRadius) || !std::isfinite(spacing) || guardRadius <= 0.0f || spacing <= 0.0f)
1223 Diagnostic::error(DiagnosticCode::NotFound, "RTS escort target must be a live deployed unit or building",
1228 DiagnosticCode::Conflict, "RTS escort target must have an owning faction", "protectedSubject"));
1286 if (auto* weaponEntity = dynamic_cast<weapon::WeaponEntity*>(unit->weapon()->link.resolve())) {
1327 {"resourceNode", resourceNode == nullptr ? std::string{} : resourceNode->identity()->subject.format()},
1344 if (auto* owner = dynamic_cast<Faction*>(building->faction()->link.resolve()); owner != nullptr)
1514void RTS::recordDamageEvent(const combat::DamageRequest& request, const combat::DamageOutcome& outcome,
1530 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Unit does not belong to this facade", "unit"));
1534Result<void> RTS::setUnitAttribute(Unit& unit, std::string_view attribute, double value) const {
1537 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Unit does not belong to this facade", "unit"));
1541Result<effects::EffectHandle> RTS::applyEffect(Unit& unit, const RTSEffectDefinition& definition) const {
1544 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Unit does not belong to this facade", "unit"));
1548Result<effects::EffectHandle> RTS::applyEffect(Building& building, const RTSEffectDefinition& definition) const {
1551 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Building does not belong to this facade", "building"));
1585 return Result<double>::success(applied, Status::success(applied == 0.0 ? StatusCode::NoOp : StatusCode::Applied));
1588Result<effects::EffectHandle> RTS::applyStatusEffect(SubjectRef source, SubjectRef target, std::string effect,
1595 Diagnostic::error(DiagnosticCode::NotFound, "RTS status-effect source was not found", "source"));
1601 Diagnostic::error(DiagnosticCode::Conflict, "RTS status-effect target must be alive", "target"));
1612 Diagnostic::error(DiagnosticCode::Conflict, "RTS status-effect target must be alive", "target"));
1621 Diagnostic::error(DiagnosticCode::NotFound, "RTS status-effect target was not found", "target"));
1630 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Building does not belong to this facade", "building"));
1632 if (auto* faction = dynamic_cast<Faction*>(building.faction()->link.resolve()); faction != nullptr) {
1645Result<void> RTS::setProductionResourceReserve(Faction& faction, std::string resource, std::int64_t amount,
1649 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Faction does not belong to this facade", "faction"));
1664Result<RTSCancelProductionReceipt> RTS::cancelProduction(Building& building, resource::IResourceAccount& account,
1669 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Building does not belong to this facade", "building"));
1670 return RTSProductionActionAdapter::cancel(building, account, std::move(productionTaskId), std::move(orderId),
1674Result<std::size_t> RTS::step(const SimulationStep& simulationStep, IRTSActionExecutor& executor) {
1683 [this](const combat::DamageRequest& request, const combat::DamageOutcome& outcome, SimulationTick tick,
1685 const CombatFireEventSink fireEvents = [this](const CombatFireEvent& event, SimulationTick tick) {
1688 const LifecycleEventSink lifecycleEvents = [this](const LifecycleEvent& event, SimulationTick tick) {
1770 [this](const LifecycleEvent& event, SimulationTick tick) { recordLifecycleEvent(event, tick); });
1812 auto projectileImpacts = projectiles_.step(simulationStep, *damage_, projectileCollisionQuery_, damageEvents);
1818 CombatFireSystem::step(simulationStep, combatState_, *sensing_, *damage_, &projectiles_, fireLineQuery_,
1828 auto reinforcementPolicy = ReinforcementProductionPolicySystem::step(simulationStep, reinforcementCancel_);
1881 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS script step duration must be positive", "seconds"));
1911 return record.kind != "unit" || std::find(settled.begin(), settled.end(), record.taskId) != settled.end();
1913 return Result<std::size_t>::success(commands.value() + stepped.value(), Status::success(StatusCode::Applied));
1916Result<void> RTS::configureScriptWorld(int width, int height, float cellSize, float originX, float originY) {
1917 if (width <= 0 || height <= 0 || !std::isfinite(cellSize) || cellSize <= 0.0f || !std::isfinite(originX) ||
1948 const auto firstBlockedPoint = [this](WorldPosition from, WorldPosition to) -> std::optional<WorldPosition> {
1955 int previousX = static_cast<int>(std::floor((from.x - scriptRuntime_->originX) / scriptRuntime_->cellSize));
1956 int previousY = static_cast<int>(std::floor((from.y - scriptRuntime_->originY) / scriptRuntime_->cellSize));
1967 if (cellX < 0 || cellY < 0 || cellX >= scriptRuntime_->width || cellY >= scriptRuntime_->height ||
1975 const int x = static_cast<int>(std::floor((point.x - scriptRuntime_->originX) / scriptRuntime_->cellSize));
1976 const int y = static_cast<int>(std::floor((point.y - scriptRuntime_->originY) / scriptRuntime_->cellSize));
1978 return scriptRuntime_->terrainElevations[static_cast<std::size_t>(y * scriptRuntime_->width + x)];
1987 const auto terrainBlocks = [this, terrainAt](WorldPosition from, WorldPosition to, float fromHeight,
2005 firstBlockedPoint(from, to).has_value() || terrainBlocks(from, to, sourceHeight, targetHeight).has_value();
2008 setCombatHeightQuery([terrainAt, relativeHeight](WorldPosition from, WorldPosition to, ecs::EntityHandle source,
2019 return Result<std::optional<ProjectileCollision>>::success(std::nullopt, Status::success(StatusCode::NoOp));
2033 Diagnostic::error(DiagnosticCode::Conflict, "RTS script runtime is unavailable", "production"));
2037 DiagnosticCode::NotFound, "RTS produced unit has no reserved stable subject", "production.task"));
2060 setAIProductionRequest([this](Faction& faction, Building& producer, const LogicalId& definition) -> Result<void> {
2063 DiagnosticCode::Conflict, "RTS script AI production requires a configured world", "scriptRuntime"));
2066 DiagnosticCode::Conflict, "RTS script AI production identity sequence overflow", "ai.sequence"));
2118 setResourceCredit([accountForFaction](Unit& unit, const resource::CostSpec& cost) -> Result<resource::Receipt> {
2126 [accountForFaction](Unit& unit, Building&, const resource::CostSpec& cost) -> Result<resource::Receipt> {
2134 [accountForFaction](Building& building, const resource::CostSpec& cost) -> Result<resource::Receipt> {
2144 return Result<double>::success(static_cast<double>(balance.value()), Status::success(StatusCode::Applied));
2168 Diagnostic::error(DiagnosticCode::Conflict, "RTS script world is not configured", "scriptWorld"));
2176 scriptRuntime_->fovs[key] = std::make_unique<map::Fov>(scriptRuntime_->width, scriptRuntime_->height);
2216 if (x < 0 || y < 0 || x >= scriptRuntime_->width || y >= scriptRuntime_->height || !std::isfinite(elevation))
2234 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS terrain cell is outside the grid", "terrain.cell"));
2240Result<void> RTS::addScriptResource(Faction& faction, std::string resource, std::int64_t amount) {
2243 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Faction does not belong to this facade", "faction"));
2250 Diagnostic::error(DiagnosticCode::NotFound, "RTS script faction economy was not found", "faction"));
2262 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS Faction does not belong to this facade", "faction"));
2270 Diagnostic::error(DiagnosticCode::NotFound, "RTS script faction economy was not found", "faction"));
2274Result<void> RTS::configureScriptAI(Faction& faction, LogicalId workerDefinition, LogicalId armyDefinition,
2279 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS AI faction does not belong to this facade", "faction"));
2282 DiagnosticCode::Conflict, "RTS AI requires a configured script world and content", "scriptRuntime"));
2283 if (!workerDefinition.isValid() || !armyDefinition.isValid() || !targetBuildingDefinition.isValid() ||
2311Result<RTSBuildReceipt> RTS::queueScriptUnit(Building& producer, SubjectRef unitSubject, LogicalId unitDefinition,
2315 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS producer does not belong to this facade", "producer"));
2318 DiagnosticCode::Conflict, "RTS script world and content must be configured", "scriptRuntime"));
2321 DiagnosticCode::Conflict, "RTS produced unit subject is invalid or already owned", "unitSubject"));
2324 DiagnosticCode::InvalidArgument, "RTS production requires a unit definition", "unitDefinition"));
2339 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS unit definition lacks production cost or duration",
2349 if (resourceName == nullptr || resourceName->empty() || !costNumber || !timeNumber || !std::isfinite(*costNumber) ||
2363 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS producer faction link is stale", "producer.faction"));
2367 Diagnostic::error(DiagnosticCode::NotFound, "RTS producer economy was not found", "producer.faction"));
2373 auto receipt = build(producer, scriptRuntime_->actions, economy->second->account, std::move(cost).takeValue(),
2384Result<ReinforcementRequestReceipt> RTS::queueScriptReinforcement(Building& producer, SubjectRef unitSubject,
2388 DiagnosticCode::StaleHandle, "RTS reinforcement producer does not belong to this facade", "producer"));
2393 "RTS reinforcement requires a new stable subject and preferred unit definition", "reinforcement"));
2417 DiagnosticCode::PreconditionViolation, "RTS construction requires a same-faction builder", "builder"));
2418 if (!scriptRuntime_ || !scriptRuntime_->configured || definitions_ == nullptr || !buildingSubject.isValid() ||
2431 DiagnosticCode::InvalidArgument, "RTS building definition must be an object", "buildingDefinition"));
2444 if (resourceName == nullptr || resourceName->empty() || !std::isfinite(costNumber) || costNumber < 0.0 ||
2447 DiagnosticCode::InvalidArgument, "RTS building construction cost is invalid", "buildingDefinition"));
2451 Diagnostic::error(DiagnosticCode::NotFound, "RTS construction economy was not found", "faction"));
2462 economy->second->account.credit(cost.value()).ignore("compensate failed construction root creation");
2483 builder.orders()->values.restoreState(previousOrders.value()).ignore("restore construction builder orders");
2488 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(ecs::try_get(weapons_.back()))) weapon->release();
2507 "RTS construction cancellation requires a live unfinished script building", "building.construction"));
2513 DiagnosticCode::NotFound, "RTS construction payment record was not found", "building.construction"));
2517 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS construction faction is stale", "building.faction"));
2521 Diagnostic::error(DiagnosticCode::NotFound, "RTS construction economy was not found", "building.faction"));
2537 std::erase_if(faction->members()->buildings, [&](const auto& value) { return sameHandle(value, buildingHandle); });
2539 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(building.weapon()->link.resolve())) weapon->release();
2550 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS building does not belong to this facade", "building"));
2551 if (!scriptRuntime_ || !scriptRuntime_->configured || definitions_ == nullptr || !building.integrity()->alive ||
2559 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS building faction link is stale", "building.faction"));
2563 Diagnostic::error(DiagnosticCode::NotFound, "RTS building economy was not found", "building.faction"));
2564 auto definition = definitions_->resolve("building", std::string(building.definition()->id.name()));
2571 DiagnosticCode::InvalidArgument, "RTS building definition must be an object", "building.definition"));
2574 if (resourceIt == object->end() || costIt == object->end() || resourceIt->second.getIf<std::string>() == nullptr)
2576 DiagnosticCode::InvalidArgument, "RTS building definition lacks a sale cost", "building.definition"));
2594 DiagnosticCode::InvalidArgument, "RTS building sale values are invalid", "building.definition"));
2596 resource::CostSpec::single(*resourceIt->second.getIf<std::string>(), static_cast<std::int64_t>(costNumber));
2617 task->get().state == production::TaskState::Cancelled || task->get().state == production::TaskState::Failed)
2619 auto cancelled = cancelProduction(building, economy->second->account, record.taskId, record.orderId,
2626 std::erase_if(scriptRuntime_->paidProduction, [&](const auto& value) { return value.taskId == record.taskId; });
2633 auto evacuated = evacuateBuilding(building, {building.placement()->worldX + 2.0f, building.placement()->worldY});
2647 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(building.weapon()->link.resolve())) weapon->release();
2656Result<RTSBuildReceipt> RTS::queueScriptResearch(Building& producer, std::string upgrade, int priority) {
2659 DiagnosticCode::StaleHandle, "RTS research producer does not belong to this facade", "producer"));
2660 if (!scriptRuntime_ || !scriptRuntime_->configured || definitions_ == nullptr || upgrade.empty())
2662 DiagnosticCode::InvalidArgument, "RTS research requires a configured world and upgrade", "upgrade"));
2666 DiagnosticCode::StaleHandle, "RTS research producer faction link is stale", "producer.faction"));
2667 if (std::binary_search(faction->technology()->unlocked.begin(), faction->technology()->unlocked.end(), upgrade))
2674 for (int index = 0; index < static_cast<int>(candidateProduction->values.taskCount()); ++index) {
2693 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS upgrade definition must be an object", "upgrade"));
2703 if (const auto* integer = found->second.getIf<std::int64_t>()) return static_cast<double>(*integer);
2714 Diagnostic::error(DiagnosticCode::Conflict, "RTS building cannot research this upgrade", "producer"));
2718 DiagnosticCode::PreconditionViolation, "RTS research prerequisite is not unlocked", "upgrade"));
2719 if (resourceName.empty() || !costNumber || !researchTime || !std::isfinite(*costNumber) || *costNumber < 0.0 ||
2720 std::floor(*costNumber) != *costNumber || !std::isfinite(*researchTime) || *researchTime <= 0.0)
2722 DiagnosticCode::InvalidArgument, "RTS upgrade cost or research duration is invalid", "upgrade"));
2726 Diagnostic::error(DiagnosticCode::NotFound, "RTS research economy was not found", "producer.faction"));
2733 auto receipt = build(producer, scriptRuntime_->actions, economy->second->account, std::move(cost).takeValue(),
2747Result<RTSCancelProductionReceipt> RTS::cancelScriptProduction(Building& producer, int queueIndex) {
2750 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS producer does not belong to this facade", "producer"));
2754 "RTS production cancellation requires a configured world and valid queue index", "queueIndex"));
2756 for (int index = 0; index < static_cast<int>(producer.production()->values.taskCount()); ++index) {
2759 task->get().state != production::TaskState::Cancelled && task->get().state != production::TaskState::Failed)
2764 Diagnostic::error(DiagnosticCode::NotFound, "RTS producer has no cancellable production", "production"));
2768 Diagnostic::error(DiagnosticCode::NotFound, "RTS production queue index was not found", "queueIndex"));
2769 const auto record = std::find_if(scriptRuntime_->paidProduction.begin(), scriptRuntime_->paidProduction.end(),
2780 Diagnostic::error(DiagnosticCode::StaleHandle, "RTS producer faction link is stale", "producer.faction"));
2784 Diagnostic::error(DiagnosticCode::NotFound, "RTS producer economy was not found", "producer.faction"));
2794Result<void> RTS::setBuildingRally(Building& producer, CommandSpec command, bool groupedReinforcements) const {
2797 DiagnosticCode::StaleHandle, "RTS rally producer does not belong to this facade", "producer"));
2814 DiagnosticCode::StaleHandle, "RTS rally link requires two distinct owned producers", "producer"));
2838 DiagnosticCode::StaleHandle, "RTS rally producer does not belong to this facade", "producer"));
2862Result<void> RTS::setReinforcementTypeLimit(Building& producer, std::string unitType, std::size_t maximum) const {
2885Result<void> RTS::setReinforcementTypePriority(Building& producer, std::string unitType, int priority) const {
2886 if (!owns(buildings_, producer) || !producer.rally()->enabled || producer.rally()->combatGroup == 0 ||
2887 unitType.empty() || priority < 0 || definitions_ == nullptr || !definitions_->resolve("unit", unitType))
2907Result<void> RTS::setReinforcementFallback(Building& producer, std::string preferred, std::string fallback) const {
2908 if (!owns(buildings_, producer) || !producer.rally()->enabled || producer.rally()->combatGroup == 0 ||
2910 !definitions_->resolve("unit", preferred) || (!fallback.empty() && !definitions_->resolve("unit", fallback)))
2962Result<void> RTS::setReinforcementTransport(Building& producer, Unit* transport, std::size_t minimumLoad) const {
2965 DiagnosticCode::StaleHandle, "RTS rally producer does not belong to this facade", "producer"));
2976 if (!owns(units_, *transport) || transport->faction()->link.resolve() != producer.faction()->link.resolve() ||
2978 minimumLoad > transport->containment()->capacity || transport->containment()->container.isBound())
2981 "RTS reinforcement transport must be an uncontained friendly carrier with a valid minimum load",
3000Result<void> RTS::castScriptAbility(Unit& caster, std::string ability, SubjectRef target, WorldPosition point) {
3004 if (!scriptRuntime_ || !scriptRuntime_->configured || definitions_ == nullptr || ability.empty())
3014 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS ability definition must be an object", "ability"));
3056 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS ability target type is invalid", "targetType"));
3066 if (!range || !radius || !cooldown || !damage || !healing || !castTime || !tickInterval || !resourceCost ||
3083 auto effectDefinition = resolveEffectDefinition(*definitions_, statusEffect, caster.identity()->subject);
3097 const AbilityResourceDebit debit = [this](Unit& unit, const resource::CostSpec& cost) -> Result<resource::Receipt> {
3105 Diagnostic::error(DiagnosticCode::NotFound, "RTS ability economy was not found", "caster.faction"));
3109 [this](const combat::DamageRequest& request, const combat::DamageOutcome& outcome, SimulationTick tick,
3111 const LifecycleEventSink lifecycleEvents = [this](const LifecycleEvent& event, SimulationTick tick) {
3124 Diagnostic::error(DiagnosticCode::NotFound, "RTS unit has no active ability channel", "ability.channel"));
3132 recordLifecycleEvent({LifecycleEventKind::AbilityChannelCancelled, caster.identity()->subject, cancelledTarget,
3138Result<std::size_t> RTS::requestFireSupport(Unit& requester, WorldPosition center, float radius, int shotsPerResponder,
3142 DiagnosticCode::StaleHandle, "RTS fire-support requester does not belong to this facade", "requester"));
3149 DiagnosticCode::StaleHandle, "RTS fire-support requester does not belong to this facade", "requester"));
3154 if (!transport.identity()->subject.isValid() || findUnit(transport.identity()->subject) != &transport)
3156 DiagnosticCode::InvalidArgument, "RTS transport is not owned by this composition root", "transport"));
3160Result<std::size_t> RTS::evacuateBuilding(Building& building, WorldPosition destination) const {
3161 if (!building.identity()->subject.isValid() || findBuilding(building.identity()->subject) != &building)
3163 DiagnosticCode::InvalidArgument, "RTS building is not owned by this composition root", "building"));
3178 DiagnosticCode::Conflict, "RTS script world must be configured before queuing commands", "scriptWorld"));
3179 return scriptRuntime_->commandLog.queue(std::move(command), SimulationTick{scriptRuntime_->nextTick});
3185 Diagnostic::error(DiagnosticCode::Conflict, "RTS script runtime is not configured", "scriptWorld"));
3186 return Result<std::string>::success(scriptRuntime_->commandLog.exportText(), Status::success(StatusCode::Applied));
3192 DiagnosticCode::Conflict, "RTS script world must be configured before importing commands", "scriptWorld"));
3193 return scriptRuntime_->commandLog.importText(text, SimulationTick{scriptRuntime_->nextTick}, clearExisting);
3196std::uint64_t RTS::scriptTick() const noexcept {
3207 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS checkpoint name must not be empty", "name"));
3223 checkpoint.navigationCosts.reserve(static_cast<std::size_t>(scriptRuntime_->width * scriptRuntime_->height));
3242 const auto expectedCells = static_cast<std::size_t>(scriptRuntime_->width * scriptRuntime_->height);
3243 if (checkpoint.navigationCosts.size() != expectedCells || checkpoint.terrainElevations.size() != expectedCells)
3245 Diagnostic::error(DiagnosticCode::Conflict, "RTS checkpoint grid dimensions do not match", "grid"));
3248 if (current == scriptRuntime_->fovs.end() || !current->second || snapshot.width != scriptRuntime_->width ||
3251 Diagnostic::error(DiagnosticCode::Conflict, "RTS checkpoint fog topology does not match", "fog"));
3254 return std::any_of(players_.begin(), players_.end(), [subject](const ecs::EntityHandle& handle) {
3260 checkpoint.roots.units.size() == unitCount() && checkpoint.roots.buildings.size() == buildingCount() &&
3262 checkpoint.roots.players.size() == playerCount() && checkpoint.roots.factions.size() == factionCount() &&
3345 std::erase_if(scriptRuntime_->fovs, [&](const auto& entry) { return !checkpoint.fovs.contains(entry.first); });
3382 if (!scriptRuntime_ || !scriptRuntime_->configured || findFaction(faction.identity()->subject) != &faction)
3388 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS fog cell is outside the grid", "cell"));
3392 Diagnostic::error(DiagnosticCode::NotFound, "RTS faction fog provider was not found", "faction"));
3393 return Result<bool>::success(found->second->isVisible(x, y), Status::success(StatusCode::Applied));
3397 if (!scriptRuntime_ || !scriptRuntime_->configured || findFaction(faction.identity()->subject) != &faction)
3403 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS fog cell is outside the grid", "cell"));
3407 Diagnostic::error(DiagnosticCode::NotFound, "RTS faction fog provider was not found", "faction"));
3408 return Result<bool>::success(found->second->isExplored(x, y), Status::success(StatusCode::Applied));
3437 if (passenger != nullptr && passenger != &unit && owns(units_, *passenger)) removeUnitRoot(*passenger);
3440 if (reason == UnitRemovalReason::Gameplay && crowd_ != nullptr && unit.crowd()->link.isBound())
3444 if (sensing_ != nullptr) sensing_->remove(subjectKey).ignore("best-effort RTS sensing cleanup");
3451 std::erase_if(node->harvest()->workers, [&](const auto& value) { return sameHandle(value, handle); });
3456 std::erase_if(building->construction()->builders, [&](const auto& value) { return sameHandle(value, handle); });
3457 std::erase_if(building->garrison()->occupants, [&](const auto& value) { return sameHandle(value, handle); });
3458 std::erase_if(building->rally()->reinforcements, [&](const auto& value) { return sameHandle(value, handle); });
3468 std::erase_if(other->containment()->occupants, [&](const auto& value) { return sameHandle(value, handle); });
3469 if (other->containment()->container.isBound() && sameHandle(other->containment()->container.handle(), handle))
3478 std::erase_if(faction->members()->units, [&](const auto& value) { return sameHandle(value, handle); });
3482 std::erase_if(player->selection()->units, [&](const auto& value) { return sameHandle(value, handle); });
3486 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(unit.weapon()->link.resolve())) weapon->release();
3499 if (sensing_ != nullptr) sensing_->remove(subjectKey).ignore("best-effort RTS sensing cleanup");
3523 if (unit->containment()->container.isBound() && sameHandle(unit->containment()->container.handle(), handle))
3532 std::erase_if(faction->members()->buildings, [&](const auto& value) { return sameHandle(value, handle); });
3536 std::erase_if(player->selection()->buildings, [&](const auto& value) { return sameHandle(value, handle); });
3539 if (auto* weapon = dynamic_cast<weapon::WeaponEntity*>(building.weapon()->link.resolve())) weapon->release();
3543 std::erase_if(scriptRuntime_->paidConstruction, [&](const auto& value) { return value.building == subject; });
3585 Diagnostic::error(DiagnosticCode::NotFound, "RTS gameplay root identity was not found", "subject"));
3594 if (unit != nullptr && (!unit->durability()->alive || unit->durability()->state.health <= 0.0))
3620std::size_t RTS::resourceNodeCount() const noexcept { return countLive<ResourceNode>(resourceNodes_); }
3625Faction* RTS::findFaction(SubjectRef subject) const noexcept { return findSubject<Faction>(factions_, subject); }
3626Match* RTS::findMatch(SubjectRef subject) const noexcept { return findSubject<Match>(matches_, subject); }
3660 cls.addFunc("scriptTick", [](RTS* self) { return static_cast<std::int64_t>(self->scriptTick()); });
3662 cls.addFunc("configureSettlementRulesJson", [vm](RTS* self, const std::string& json) -> ssq::Table {
3689 [vm](RTS* self, const std::string& matchText, const std::string& ruleText, const std::string& archetype,
3714 [vm](RTS* self, const std::string& matchText, const std::string& factionText, int team) -> ssq::Table {
3738 "surrenderMatch", [vm](RTS* self, const std::string& matchText, const std::string& factionText) -> ssq::Table {
3759 return script::projectResult(vm, self->inspectMatch(*match), [](Value value) { return value; });
3809 [vm](RTS* self, const std::string& subjectText, const std::string& resource, float amount, float x, float y,
3834 [vm](RTS* self, int width, int height, float cellSize, float originX, float originY) -> ssq::Table {
3838 cls.addFunc("captureScriptCheckpoint", [vm](RTS* self, const std::string& name) -> ssq::Table {
3841 cls.addFunc("restoreScriptCheckpoint", [vm](RTS* self, const std::string& name) -> ssq::Table {
3848 return script::projectResult(vm, self->loadScriptContent(json), [](ContentImportReceipt receipt) {
3853 cls.addFunc("setScriptNavigationBlocked", [vm](RTS* self, int x, int y, bool blocked) -> ssq::Table {
3856 cls.addFunc("setScriptNavigationCost", [vm](RTS* self, int x, int y, float cost) -> ssq::Table {
3859 cls.addFunc("setScriptTerrainElevation", [vm](RTS* self, int x, int y, float elevation) -> ssq::Table {
3866 cls.addFunc("scriptCellVisible", [vm](RTS* self, const std::string& factionText, int x, int y) -> ssq::Table {
3877 cls.addFunc("scriptCellExplored", [vm](RTS* self, const std::string& factionText, int x, int y) -> ssq::Table {
3929 int attackThreshold, float thinkInterval, float formationSpacing, bool enabled) -> ssq::Table {
3945 self->configureScriptAI(*faction, worker.value(), army.value(), target.value(), desiredWorkers,
3985 vm, self->queueScriptReinforcement(*producer, unitSubject.value(), definition.value(), priority),
3995 const std::string& definitionText, float x, float y, const std::string& builderText) -> ssq::Table {
4013 self->startScriptConstruction(*faction, buildingSubject.value(), definition.value(), {x, y}, *builder),
4016 const auto buildingLifecycle = [vm](RTS* self, const std::string& buildingText, bool sell) -> ssq::Table {
4023 Diagnostic::error(DiagnosticCode::NotFound, "RTS script building was not found", "building")));
4024 auto result = sell ? self->sellScriptBuilding(*building) : self->cancelScriptConstruction(*building);
4025 return script::projectResult(vm, std::move(result), [](resource::Receipt) { return Value(true); });
4031 cls.addFunc("sellScriptBuilding", [buildingLifecycle](RTS* self, const std::string& buildingText) -> ssq::Table {
4036 [vm](RTS* self, const std::string& producerText, const std::string& upgrade, int priority) -> ssq::Table {
4051 "cancelScriptProduction", [vm](RTS* self, const std::string& producerText, int queueIndex) -> ssq::Table {
4057 vm, Status::failure(Diagnostic::error(DiagnosticCode::NotFound, "RTS script producer was not found",
4060 vm, self->cancelScriptProduction(*producer, queueIndex), [](RTSCancelProductionReceipt receipt) {
4083 cls.addFunc("cancelScriptAbility", [vm](RTS* self, const std::string& casterText) -> ssq::Table {
4106 return script::projectResult(vm, self->setBuildingRally(*producer, command, groupedReinforcements));
4109 [vm](RTS* self, const std::string& producerText, const std::string& sourceText) -> ssq::Table {
4123 cls.addFunc("clearBuildingRally", [vm](RTS* self, const std::string& producerText) -> ssq::Table {
4134 "setReinforcementLimit", [vm](RTS* self, const std::string& producerText, std::int64_t maximum) -> ssq::Table {
4146 return script::projectResult(vm, self->setReinforcementLimit(*producer, static_cast<std::size_t>(maximum)));
4168 [vm](RTS* self, const std::string& producerText, const std::string& unitType, int priority) -> ssq::Table {
4176 return script::projectResult(vm, self->setReinforcementTypePriority(*producer, unitType, priority));
4188 return script::projectResult(vm, self->setReinforcementFallback(*producer, preferred, fallback));
4228 vm, self->setReinforcementTransport(*producer, transport, static_cast<std::size_t>(minimumLoad)));
4234 cls.addFunc("importScriptCommandLog", [vm](RTS* self, const std::string& text, bool clearExisting) -> ssq::Table {
4238 [vm](RTS* self, std::int64_t tick, const std::string& factionText, const std::string& builderText,
4264 [vm](RTS* self, std::int64_t tick, const std::string& producerText, const std::string& unitSubjectText,
4286 [vm](RTS* self, std::int64_t tick, const std::string& producerText, const std::string& unitSubjectText,
4308 [vm](RTS* self, std::int64_t tick, const std::string& producerText, const std::string& upgrade,
4349 [vm](RTS* self, std::int64_t tick, const std::string& producerText, int queueIndex) -> ssq::Table {
4364 [vm](RTS* self, std::int64_t tick, const std::vector<std::string>& subjectTexts) -> ssq::Table {
4378 [vm](RTS* self, std::int64_t tick, const std::string& requesterText, float x, float y, float radius,
4411 const auto queueBuildingLifecycle = [vm](RTS* self, std::int64_t tick, const std::string& buildingText,
4426 [queueBuildingLifecycle](RTS* self, std::int64_t tick, const std::string& buildingText) -> ssq::Table {
4427 return queueBuildingLifecycle(self, tick, buildingText, RTSReplayOperation::CancelConstruction);
4430 [queueBuildingLifecycle](RTS* self, std::int64_t tick, const std::string& buildingText) -> ssq::Table {
4535 const auto queueUnitOrder = [vm](RTS* self, std::int64_t tick, const std::vector<std::string>& subjectTexts,
4562 [queueUnitOrder](RTS* self, std::int64_t tick, const std::vector<std::string>& subjects) -> ssq::Table {
4566 [queueUnitOrder](RTS* self, std::int64_t tick, const std::vector<std::string>& subjects) -> ssq::Table {
4570 [queueUnitOrder](RTS* self, std::int64_t tick, const std::vector<std::string>& subjects, float x,
4575 [queueUnitOrder](RTS* self, std::int64_t tick, const std::vector<std::string>& subjects, float x,
4577 return queueUnitOrder(self, tick, subjects, OrderKind::AttackGround, {x, y}, {}, append, -1.0f);
4581 [vm, queueUnitOrder](RTS* self, std::int64_t tick, const std::vector<std::string>& subjects, float x, float y,
4591 [vm, queueUnitOrder](RTS* self, std::int64_t tick, const std::vector<std::string>& subjects, float x, float y,
4649 [vm](RTS* self, const std::vector<std::string>& subjectTexts, float x, float y, bool append) -> ssq::Table {
4656 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command), fanOutValue);
4659 [vm](RTS* self, const std::vector<std::string>& subjectTexts, float startX, float startY, float endX,
4755 "assignBuilder", [vm](RTS* self, const std::string& workerText, const std::string& buildingText) -> ssq::Table {
4769 cls.addFunc("addUnitReserveAmmo", [vm](RTS* self, const std::string& subjectText, int rounds) -> ssq::Table {
4780 cls.addFunc("addUnitAmmoSupply", [vm](RTS* self, const std::string& subjectText, float rounds) -> ssq::Table {
4791 cls.addFunc("addBuildingAmmoSupply", [vm](RTS* self, const std::string& subjectText, float rounds) -> ssq::Table {
4802 cls.addFunc("setUnitAutoResupply", [vm](RTS* self, const std::string& subjectText, bool enabled) -> ssq::Table {
4814 [vm](RTS* self, const std::string& sourceText, const std::string& targetText, float amount) -> ssq::Table {
4819 return script::projectResult(vm, self->heal(source.value(), target.value(), static_cast<double>(amount)),
4824 [vm](RTS* self, const std::string& sourceText, const std::string& targetText, const std::string& effect,
4832 self->applyStatusEffect(source.value(), target.value(), effect, static_cast<double>(durationOverride)),
4840 [vm](RTS* self, const std::string& requesterText, float x, float y, float radius, int shotsPerResponder,
4859 cls.addFunc("cancelFireSupport", [vm](RTS* self, const std::string& requesterText) -> ssq::Table {
4872 [vm](RTS* self, const std::vector<std::string>& subjectTexts, float x, float y, float spacing) -> ssq::Table {
4881 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command, formation),
4902 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command), fanOutValue);
4922 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command), fanOutValue);
4942 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command), fanOutValue);
4945 [vm](RTS* self, const std::vector<std::string>& subjectTexts, const std::string& transportText,
4964 cls.addFunc("unloadTransport", [vm](RTS* self, const std::string& transportText, float x, float y) -> ssq::Table {
4970 vm, Status::failure(Diagnostic::error(DiagnosticCode::NotFound, "RTS transport identity was not found",
4975 cls.addFunc("evacuateBuilding", [vm](RTS* self, const std::string& buildingText, float x, float y) -> ssq::Table {
4981 vm, Status::failure(Diagnostic::error(DiagnosticCode::NotFound, "RTS building identity was not found",
4986 cls.addFunc("setUnitCloaked", [vm](RTS* self, const std::string& unitText, bool cloaked) -> ssq::Table {
4996 cls.addFunc("stopUnits", [vm](RTS* self, const std::vector<std::string>& subjectTexts) -> ssq::Table {
5001 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command), fanOutValue);
5003 cls.addFunc("holdUnits", [vm](RTS* self, const std::vector<std::string>& subjectTexts) -> ssq::Table {
5008 return script::projectResult(vm, self->commandUnits(std::move(subjects).takeValue(), command), fanOutValue);
Renderer- and ruleset-neutral gameplay action pipeline.
Resource-account adapter backed by an EconomyLedger.
Selective combat-stat adapter for RTS units.
RTS module owner and phase-one composition profile entry point.
The single Squirrel projection for common Result, Status and Value.
Strongly typed Weapon definition/runtime adapter.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
static std::optional< LogicalId > fromParts(std::string_view namespaceName, std::string_view name)
Builds a logical ID from separate namespace and name components.
Definition Identity.cpp:48
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
bool isValid() const noexcept
Returns whether this value contains a valid namespace and name.
Definition Identity.h:433
std::string_view name() const noexcept
Returns the name component as a view into this object.
Definition Identity.cpp:63
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Result< void > configureSettlementRules(const settlement::SettlementRuleSet &rules)
Replace the optional declarative buff/effect rules used by subsequent damage settlements.
Definition Damage.cpp:279
Result< settlement::SettlementResult > heal(CombatState &target, SubjectRef source, double amount) const
Resolve and atomically commit health restoration through the configured settlement pipeline.
Definition Damage.cpp:262
Deterministic registry of topic-neutral, versioned JSON definitions.
Definition Definitions.h:58
eve::ResultRef< const Definition > resolve(const std::string &type, const std::string &id) const
Resolves a definition through the common registry API.
Definition Definitions.cpp:313
Adapts one caller-owned EconomyLedger to IResourceAccount.
Definition EconomyLedgerResourceAccount.h:31
Dynamic field-of-view / fog-of-war facade. Phase A: 2D shadowcast + multi-revealer + explored memory....
Definition Fov.h:27
Pathfinding facade. Single-agent: A*. Group (same goal): Flow Field + follow. Grid/topology internals...
Definition Pathfinder.h:20
static eve::Result< CostSpec > create(std::vector< ResourceCost > items)
Validate and canonicalize resource items.
Definition ResourceAccount.cpp:74
static eve::Result< CostSpec > single(std::string_view resource, std::int64_t amount)
Build a one-resource cost.
Definition ResourceAccount.cpp:117
Provider-neutral resource account contract.
Definition ResourceAccount.h:316
virtual eve::Result< Receipt > credit(const CostSpec &cost)=0
Atomically credit all items immediately.
virtual eve::Result< Receipt > debit(const CostSpec &cost)=0
Atomically debit all items immediately.
Adapter that maps RTS orders to the existing action::ActionRuntime.
Definition RTSAction.h:61
Canonical attributes component adapter, backed by attributes::AttributeSet.
Definition RTSTypes.h:323
RTS building domain root with placement and production composition.
Definition RTSTypes.h:840
static Building * createBuilding(SubjectRef subject={}, LogicalId definition={})
Create a building and initialize its self handle and identity.
Definition RTSTypes.cpp:656
static Result< EntityLink > bind(ecs::EntityHandle handle)
Bind a live ECS handle, rejecting a null or stale handle.
Definition RTSTypes.h:184
Faction domain root; membership is a set of typed runtime handles.
Definition RTSTypes.h:1178
static Faction * createFaction(SubjectRef subject={})
Create a faction and initialize its self handle and identity.
Definition RTSTypes.cpp:684
static void clear(State &state) noexcept
Remove all revealers previously installed by this state.
Definition RTSSystems.cpp:1288
Interface isolating RTS orders from the shared action runtime.
Definition RTSAction.h:38
static Result< void > start(Match &match)
Validate rules/participants and transition Setup to Running.
Definition RTSMatch.cpp:106
static Result< void > addParticipant(Match &match, Faction &faction, int team)
Add one live faction before match start.
Definition RTSMatch.cpp:93
static Result< void > surrender(Match &match, Faction &faction)
Eliminate a participant and all of its live RTS entities.
Definition RTSMatch.cpp:132
Independent match composition root; factions may participate in different matches.
Definition RTSTypes.h:1295
static Match * createMatch(SubjectRef subject={})
Create a match and initialize every composition component. @ownership The active ECS table owns the e...
Definition RTSTypes.cpp:692
Player domain root; selection is RTS-local, authority/economy/social are links.
Definition RTSTypes.h:1119
static Player * createPlayer(SubjectRef subject={})
Create a player and initialize its self handle and identity.
Definition RTSTypes.cpp:676
static Result< void > apply(definitions::DefinitionRegistry ®istry, Unit &unit, const ArchetypeWeaponFactory &weaponFactory={})
Apply the Unit root's logical definition atomically.
Definition RTSArchetype.cpp:119
Deterministic lockstep input buffer and portable command log.
Definition RTSReplay.h:63
static eve::Result< void > ensure(Unit &unit)
Bind and seed default optional combat stats exactly once.
Definition RTSAttributes.cpp:24
Result< GameplayObservation > observeGameplay(const GameplaySession &session, SubjectRef instance) const override
Observe one provider-owned gameplay instance without mutation.
Definition RTS.cpp:357
Result< Player * > newPlayer(SubjectRef subject)
Create and own one Player root with a valid subject identity.
Definition RTS.cpp:595
Result< GameplayCommandReceipt > submitGameplay(const GameplaySession &session, SubjectRef instance, const GameplayCommand &command) override
Validate authority and atomically submit one domain command.
Definition RTS.cpp:421
Result< std::vector< GameplayEvent > > gameplayEvents(const GameplaySession &session, SubjectRef instance, std::uint64_t afterSequence) const override
Return events strictly newer than an instance-local sequence cursor.
Definition RTS.cpp:502
Result< Building * > newBuilding(SubjectRef subject, LogicalId definition={})
Create and own one Building root.
Definition RTS.cpp:550
Result< Value > inspectMatch(Match &match) const
Return an owning deterministic projection of one match lifecycle.
Definition RTS.cpp:854
void setCombatProviders(sensing::SensingWorld *sensing, combat::DamageRuntime *damage) noexcept
Attach canonical sensing and damage coordinators used by automatic combat.
Definition RTS.cpp:312
Result< std::vector< GameplayActionDescriptor > > availableGameplayActions(const GameplaySession &session, SubjectRef instance, SubjectRef subject) const override
Discover currently legal player-semantic actions for one subject.
Definition RTS.cpp:393
Result< Building * > newFactionBuilding(Faction &faction, SubjectRef subject, LogicalId definition={})
Atomically create a Building, bind its owning faction and register canonical membership.
Definition RTS.cpp:763
Result< Match * > newMatch(SubjectRef subject)
Create and own one independent RTS match composition root.
Definition RTS.cpp:798
void setDefinitionRegistry(definitions::DefinitionRegistry *registry) noexcept
Attach the canonical definition registry used to settle completed research.
Definition RTS.h:306
Result< void > addMatchParticipant(Match &match, Faction &faction, int team) const
Add one owned faction to one owned setup-phase match.
Definition RTS.cpp:833
Result< void > configureSettlementRules(const settlement::SettlementRuleSet &rules)
Replace declarative settlement rules on the active canonical damage provider.
Definition RTS.cpp:323
Result< void > surrenderMatch(Match &match, Faction &faction) const
Surrender one owned faction from one owned running match.
Definition RTS.cpp:847
Result< Faction * > newFaction(SubjectRef subject)
Create and own one Faction root with a valid subject identity.
Definition RTS.cpp:610
std::string_view gameplayDomain() const noexcept override
Stable provider domain used for discovery and routing.
Definition RTS.cpp:342
Result< ResourceNode * > newResourceNode(SubjectRef subject, std::string resourceType, float amount, WorldPosition position, std::size_t workerCapacity=1)
Create and own one harvestable resource node.
Definition RTS.cpp:571
Result< void > configureMatch(Match &match, VictoryRule rule, std::string archetype={}, double targetValue=0.0) const
Configure one owned setup-phase match victory rule.
Definition RTS.cpp:813
Result< void > rebuildState(const RTSStateSnapshot &snapshot)
Materialize a complete snapshot into an empty RTS module.
Definition RTSRestore.cpp:378
Result< Unit * > newUnit(SubjectRef subject, LogicalId definition={})
Create and own one Unit root.
Definition RTS.cpp:519
std::vector< SubjectRef > gameplayInstances() const override
Instances currently served, in publication order.
Definition RTS.cpp:344
Faction * findFaction(SubjectRef subject) const noexcept
Resolve a module-owned faction by persistent subject identity.
Definition RTS.cpp:3625
Result< FanOutReceipt > fanOut(Player::Selection &selection, const CommandSpec &command, const FormationSpec &formation) const
Fan one common command out through a player's selected unit handles.
Definition RTSCommands.cpp:4
void setFogProvider(FogProvider provider) noexcept
Install the faction-to-canonical-FOV resolver used by fog-of-war projection.
Definition RTS.cpp:305
Result< Unit * > newFactionUnit(Faction &faction, SubjectRef subject, LogicalId definition={})
Atomically create a Unit, bind its owning faction and register canonical membership.
Definition RTS.cpp:713
void setCrowdProvider(crowd::Crowd *crowd) noexcept
Attach the canonical Crowd provider used by Units with a bound Unit::Crowd link.
Definition RTS.cpp:330
Result< void > startMatch(Match &match) const
Start one owned match after participant/rule validation.
Definition RTS.cpp:840
Result< GameplayObservation > advanceGameplay(const GameplaySession &session, SubjectRef instance, const SimulationStep &step) override
Advance one provider-owned instance using injected deterministic time.
Definition RTS.cpp:482
Harvestable RTS resource node; deposited balances are owned by an external resource account.
Definition RTSTypes.h:1071
static ResourceNode * createResourceNode(SubjectRef subject={})
Create a resource node and initialize all composition components. @ownership The active ECS table own...
Definition RTSTypes.cpp:668
static Result< ServiceLink > bind(std::string_view key)
Bind a non-empty provider key.
Definition RTSTypes.h:243
static Unit * createUnit(SubjectRef subject={}, LogicalId definition={})
Create a unit and initialize its self handle and identity.
Definition RTSTypes.cpp:644
Gameplay-facing 2D candidate query service; it never values or selects targets.
Definition Sensing.h:151
Validated immutable-style rule collection installable into pipelines.
Definition SettlementRules.h:77
static eve::Result< SettlementRuleSet > fromJson(std::string_view json)
Decode and compile a strict versioned JSON rule document without mutating an existing rule set.
Definition SettlementRuleJson.cpp:165
static eve::Result< WeaponDefinitionRuntime > create(eve::definitions::DefinitionRegistry ®istry, eve::DefinitionRef definition, eve::PersistentId instanceId, eve::definition::ReloadPolicy policy=eve::definition::ReloadPolicy::RebuildInstance)
Create from a common weapon:<name> definition reference.
Definition WeaponDefinitionRuntime.cpp:424
Result< void > replay(const Trace &trace, IEnvironment &environment, double tolerance)
Reset and replay actual actions, checking all observations and failure evidence.
Definition Agent.cpp:258
@ Building
@ Flinch
@ Stagger
@ Knockdown
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
Definition EditingResult.h:43
@ Completed
@ Cancelled
Value fanOutValue(FanOutReceipt receipt)
Definition RTSScriptInternal.h:31
Result< std::vector< SubjectRef > > parseScriptSubjects(const std::vector< std::string > &texts)
Parse script UUID strings into stable subjects. @cost Linear in subject count; allocates one output v...
Definition RTSScriptInternal.h:20
Result< SubjectRef > parseScriptSubject(std::string_view text, std::string_view path)
Definition RTSScriptInternal.h:8
Definition RTS.cpp:24
std::function< Result< resource::Receipt >(Unit &, const resource::CostSpec &)> AbilityResourceDebit
Debits an ability resource cost through the game-owned canonical economy boundary.
Definition RTSSystems.h:492
DamageChannel
Authoritative damage route that produced one transient RTS frame event.
Definition RTSSystems.h:67
std::function< map::Fov *(Faction &)> FogProvider
Resolves the canonical FOV instance used by a faction; allies may deliberately share one instance.
Definition RTSSystems.h:310
std::function< void(const LifecycleEvent &, SimulationTick)> LifecycleEventSink
Read-only observer invoked after a non-combat lifecycle transition commits.
Definition RTSSystems.h:127
std::function< void(const CombatFireEvent &, SimulationTick)> CombatFireEventSink
Read-only observer invoked after a weapon consumes one authoritative shot.
Definition RTSSystems.h:91
const char * combatStanceName(CombatStance stance) noexcept
Return the stable lower-case spelling of a combat stance. @ownership Returns a borrowed pointer to im...
Definition RTSTypes.cpp:168
@ Attack
RTSReplayOperation
Stable operation kinds accepted by the shared deterministic RTS input stream.
Definition RTSReplay.h:20
@ Running
@ Setup
CombatFireEventKind
Transient weapon lifecycle event emitted after an authoritative firing attempt commits.
Definition RTSSystems.h:74
std::function< Result< weapon::WeaponEntity * >(std::string_view definitionId, PersistentId instanceId)> ArchetypeWeaponFactory
Factory that creates one canonical weapon instance from a registry definition.
Definition RTSArchetype.h:18
LifecycleEventKind
Non-combat RTS state transition exposed to presentation without entering authoritative state.
Definition RTSSystems.h:94
std::function< void(const combat::DamageRequest &request, const combat::DamageOutcome &outcome, SimulationTick tick, DamageChannel channel)> DamageEventSink
Read-only observer invoked after canonical damage has committed.
Definition RTSSystems.h:71
@ Defensive
@ Aggressive
@ Passive
const char * orderKindName(OrderKind kind) noexcept
Return the stable lower-case spelling of an RTS order kind.
Definition RTSTypes.cpp:177
@ DestroyHeadquarters
@ ResourceTarget
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
Definition SquirrelBinding.cpp:454
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
Definition SquirrelBinding.cpp:470
@ InvalidArgument
@ NotFound
@ PreconditionViolation
@ Unsupported
@ Failed
@ StaleHandle
@ Conflict
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
Definition Identity.h:254
@ NotFound
@ NoOp
@ Applied
@ PlayerEquivalent
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
Definition Container.h:602
Owning evidence that a gameplay authority accepted one command.
Definition GameplayControl.h:62
std::uint64_t resultingRevision
Definition GameplayControl.h:66
One player-semantic command submitted to a domain authority.
Definition GameplayControl.h:52
Owning event projection safe across frames and process boundaries.
Definition GameplayControl.h:71
Immutable owning observation of one authoritative gameplay instance.
Definition GameplayControl.h:43
Owning authorization context shared by player, script and automation adapters.
Definition GameplayControl.h:30
Mutable combat state owned by the gameplay domain for one subject.
Definition Damage.h:37
Complete owning audit result of one committed damage transaction.
Definition Damage.h:98
Optional bounded velocity sampling for RTS local avoidance.
Definition Crowd.h:48
A generation-qualified reference to one definition incarnation.
Definition DefinitionRuntime.h:69
A subject-agnostic continuous task retained for audit and save games.
Definition Production.h:158
static eve::Result< ResourceCost > create(std::string_view resourceId, std::int64_t amountValue)
Construct and validate one cost item.
Definition ResourceAccount.cpp:63
Typed ability policy; lifecycle effects remain owned by the canonical effect container.
Definition RTSTypes.h:133
Stable presentation payload for firing, launch, and miss feedback.
Definition RTSSystems.h:84
Absolute muzzle and target heights resolved by the authoritative map/game adapter.
Definition RTSSystems.h:586
One domain-neutral command submitted to a unit's generic order queue.
Definition RTSTypes.h:101
Result< void > validate() const
Validate coordinates and command metadata without mutating state.
Definition RTSTypes.cpp:201
Stable payload for one committed RTS lifecycle transition.
Definition RTSSystems.h:119
Earliest canonical collision found along one projectile sweep segment.
Definition RTSSystems.h:612
Typed RTS definition projected to the common lifecycle schema.
Definition RTSEffects.h:35
One process-independent command using stable subjects instead of ECS handles.
Definition RTSReplay.h:39
Complete RTS-owned state; v2 adds movement groups, v1 imports with no groups. @cost Linear in saved e...
Definition RTSSnapshot.h:172
Definition RTS.cpp:275
std::vector< PaidConstruction > paidConstruction
Definition RTS.cpp:241
std::vector< float > terrainElevations
Definition RTS.cpp:244
std::map< std::string, economy::EconomyLedger::Snapshot > economies
Definition RTS.cpp:237
std::map< std::string, SubjectRef > pendingProductionSubjects
Definition RTS.cpp:239
std::uint64_t aiProductionSequence
Definition RTS.cpp:246
std::vector< PaidProduction > paidProduction
Definition RTS.cpp:240
std::map< std::string, map::Fov::Snapshot > fovs
Definition RTS.cpp:238
std::vector< float > navigationCosts
Definition RTS.cpp:243
economy::EconomyLedgerResourceAccount account
Definition RTS.cpp:216
economy::EconomyLedger ledger
Definition RTS.cpp:215
SubjectRef resultSubject
Definition RTS.cpp:221
Definition RTS.cpp:213
std::map< std::string, Checkpoint > checkpoints
Definition RTS.cpp:262
std::vector< PaidConstruction > paidConstruction
Definition RTS.cpp:261
std::vector< PaidProduction > paidProduction
Definition RTS.cpp:260
std::map< std::string, SubjectRef > pendingProductionSubjects
Definition RTS.cpp:259
std::map< std::string, std::unique_ptr< EconomySlot > > economies
Definition RTS.cpp:257
std::vector< float > terrainElevations
Definition RTS.cpp:271
std::map< std::string, std::unique_ptr< map::Fov > > fovs
Definition RTS.cpp:258
definitions::DefinitionRegistry definitions
Definition RTS.cpp:255
std::uint64_t aiProductionSequence
Definition RTS.cpp:264