168 auto groups = captureMovementGroups();
171 auto* currentTable = ecs::current();
172 for (
const auto&
handle : units_) {
173 if (
handle.table != currentTable)
continue;
175 if (
unit ==
nullptr)
continue;
178 value.definition =
unit->definition()->id;
179 value.displayName =
unit->identity()->displayName;
180 auto attributes = RTSUnitAttributeAdapter::snapshot(*
unit);
182 value.attributes = std::move(attributes).takeValue();
183 value.attributes.owner = {};
184 value.tags =
unit->tags()->values.values();
185 value.effects =
unit->effects()->values.snapshot();
186 value.faction = subjectOf(
unit->faction()->link.resolve());
189 value.navigation.formationSpeedFactor = 1.f;
190 value.navigation.formationTarget.reset();
191 if (
value.navigation.trafficRecoveryTarget)
value.navigation.plannedOrderId.clear();
192 value.navigation.trafficRecoveryTarget.reset();
194 value.worker = {
unit->worker()->resourceType,
195 subjectOf(
unit->worker()->resourceNode.resolve()),
196 subjectOf(
unit->worker()->dropoff.resolve()),
197 unit->worker()->cargo,
198 unit->worker()->capacity,
199 unit->worker()->gatherRate,
200 unit->worker()->buildRate,
201 unit->worker()->repairRate,
202 unit->worker()->autoAssign};
204 value.combatTarget = subjectOf(
value.combat.target);
205 value.combat.target = {};
210 value.commandSource = subjectOf(
value.command.source);
211 value.commandUplink = subjectOf(
value.command.uplink);
212 value.command.source = {};
213 value.command.uplink = {};
215 if (
value.abilities.channel) {
216 value.abilityTarget = subjectOf(
value.abilities.channel->target);
217 value.abilities.channel->target = {};
220 value.containmentCapacity =
unit->containment()->capacity;
221 value.container = subjectOf(
unit->containment()->container.resolve());
222 value.occupants = subjectsOf(
unit->containment()->occupants);
224 value.supplyTarget = subjectOf(
value.supply.assignedTarget);
225 value.convoyLeader = subjectOf(
value.supply.convoyLeader);
226 value.supply.assignedTarget = {};
227 value.supply.convoyLeader = {};
230 value.fireSupportRequester = subjectOf(
value.artillery.fireSupportRequester);
231 value.observedFireSpotter = subjectOf(
value.artillery.observedFireSpotter);
232 value.artillery.fireSupportRequester = {};
233 value.artillery.observedFireSpotter = {};
235 value.escortTarget = subjectOf(
value.tactics.escortTarget);
236 value.tactics.escortTarget = {};
238 auto orders =
unit->orders()->values.snapshotState();
241 Diagnostic::error(DiagnosticCode::Failed,
"failed to snapshot RTS unit orders",
"units.orders"));
242 value.orders = std::move(orders).takeValue();
243 stabilizeOrders(
value.orders,
value.orderTargets);
246 for (
const auto&
handle : buildings_) {
247 if (
handle.table != currentTable)
continue;
249 if (building ==
nullptr)
continue;
252 value.definition = building->definition()->id;
253 value.displayName = building->identity()->displayName;
254 value.tags = building->tags()->values.values();
255 value.effects = building->effects()->values.snapshot();
256 value.faction = subjectOf(building->faction()->link.resolve());
257 value.placement = *building->placement();
258 value.construction = *building->construction();
259 value.builders = subjectsOf(
value.construction.builders);
260 value.construction.builders.clear();
261 value.integrity = *building->integrity();
262 value.shield = *building->shield();
263 value.capture = *building->capture();
264 value.capturingFaction = subjectOf(
value.capture.capturingFaction);
265 value.capture.capturingFaction = {};
266 value.dropoff = *building->dropoff();
267 value.rally = *building->rally();
268 value.rallyCommandTarget = subjectOf(
value.rally.command.targetEntity);
269 value.rallyTransport = subjectOf(
value.rally.transport);
270 value.reinforcements = subjectsOf(
value.rally.reinforcements);
271 value.rally.transport = {};
272 value.rally.command.targetEntity = {};
273 value.rally.reinforcements.clear();
274 value.combat = *building->combat();
275 value.combatTarget = subjectOf(
value.combat.target);
276 value.airDefenseNetworkRoot = subjectOf(
value.combat.airDefenseNetworkRoot);
277 value.combat.target = {};
278 value.combat.airDefenseNetworkRoot = {};
279 value.garrison = *building->garrison();
280 value.occupants = subjectsOf(
value.garrison.occupants);
281 value.garrison.occupants.clear();
282 value.supply = *building->supply();
283 value.vision = *building->vision();
284 value.technology = *building->technology();
285 value.infrastructure = *building->infrastructure();
286 value.command = *building->command();
287 value.indirectFire = *building->indirectFire();
288 auto production = building->production()->values.snapshot();
291 Diagnostic::error(DiagnosticCode::Failed,
"failed to snapshot RTS production",
"buildings.production"));
292 value.productionJson = std::move(production).takeValue();
293 auto orders = building->orders()->values.snapshotState();
296 DiagnosticCode::Failed,
"failed to snapshot RTS building orders",
"buildings.orders"));
297 value.orders = std::move(orders).takeValue();
298 stabilizeOrders(
value.orders,
value.orderTargets);
301 for (
const auto&
handle : resourceNodes_) {
302 if (
handle.table != currentTable)
continue;
304 if (
node ==
nullptr)
continue;
306 *
node->stock(),
node->harvest()->capacity,
307 subjectsOf(
node->harvest()->workers)});
309 for (
const auto&
handle : players_) {
310 if (
handle.table != currentTable)
continue;
311 auto* player =
dynamic_cast<Player*
>(ecs::try_get(
handle));
312 if (player ==
nullptr)
continue;
313 result.
players.push_back({player->identity()->subject, player->identity()->displayName,
314 subjectsOf(player->selection()->units), subjectsOf(player->selection()->buildings)});
316 for (
const auto&
handle : factions_) {
317 if (
handle.table != currentTable)
continue;
318 auto* faction =
dynamic_cast<Faction*
>(ecs::try_get(
handle));
319 if (faction ==
nullptr)
continue;
320 result.
factions.push_back({faction->identity()->subject, faction->identity()->displayName,
321 subjectsOf(faction->members()->units), subjectsOf(faction->members()->buildings),
322 *faction->strategy(), *faction->workforce(), *faction->productionPolicy(),
323 *faction->intel(), *faction->technology()});
325 for (
const auto&
handle : matches_) {
326 if (
handle.table != currentTable)
continue;
327 auto* match =
dynamic_cast<Match*
>(ecs::try_get(
handle));
328 if (match ==
nullptr)
continue;
331 value.rules = *match->rules();
332 value.state = *match->state();
333 value.events = *match->events();
334 for (
const auto& participant : match->participants()->entries)
335 value.participants.push_back({subjectOf(participant.faction.resolve()), participant.team,
336 participant.eliminated, participant.surrendered, participant.reason});
339 result.projectiles = projectiles_.snapshot();
344 auto captured = snapshotState();
346 auto state = std::move(captured).takeValue();
347 auto bySubject = [](
const auto&
left,
const auto&
right) {
return left.subject.format() <
right.subject.format(); };
348 std::sort(
state.units.begin(),
state.units.end(), bySubject);
349 std::sort(
state.buildings.begin(),
state.buildings.end(), bySubject);
350 std::sort(
state.resourceNodes.begin(),
state.resourceNodes.end(), bySubject);
351 std::sort(
state.players.begin(),
state.players.end(), bySubject);
352 std::sort(
state.factions.begin(),
state.factions.end(), bySubject);
353 std::sort(
state.matches.begin(),
state.matches.end(), bySubject);
356 for (
const auto& slot :
state.projectiles.runtime.slots) {
357 Value projectileState;
359 const auto& projectile = *slot.state;
362 {
"definition", projectile.definitionId.format()},
363 {
"generation",
static_cast<std::int64_t
>(projectile.handle.generation)},
364 {
"gravity", projectile.gravity},
365 {
"lifetime",
static_cast<std::int64_t
>(projectile.lifetime.nanoseconds())},
366 {
"mode",
static_cast<std::int64_t
>(projectile.mode)},
367 {
"positionX", projectile.position.x},
368 {
"positionY", projectile.position.y},
369 {
"positionZ", projectile.position.z},
370 {
"slot",
static_cast<std::int64_t
>(projectile.handle.slot)},
371 {
"turnRate", projectile.maxTurnRateDegrees},
372 {
"velocityX", projectile.velocity.x},
373 {
"velocityY", projectile.velocity.y},
374 {
"velocityZ", projectile.velocity.z}});
376 projectileSlots.emplace_back(
377 Value::Object{{
"generation",
static_cast<std::int64_t
>(slot.generation)}, {
"state", projectileState}});
383 {
"damageType",
payload.damageType},
384 {
"excludedTags", stringsValue(
payload.excludedTargetTags)},
385 {
"faction",
payload.faction.format()},
386 {
"friendlyFire",
payload.friendlyFire},
387 {
"key",
static_cast<std::int64_t
>(
payload.key)},
389 {
"requiredTags", stringsValue(
payload.requiredTargetTags)},
390 {
"source",
payload.source.format()},
391 {
"splashMinimum",
payload.splashMinimumDamageFactor},
392 {
"target",
payload.target.format()},
393 {
"targetPoint", positionValue(
payload.targetPoint)},
394 {
"targetHeight",
payload.targetHeight},
395 {
"targetsAir",
payload.targetsAir},
396 {
"targetsGround",
payload.targetsGround}});
400 Value::Array waypoints;
401 for (
auto point :
value.navigation.waypoints) waypoints.push_back(positionValue(
point));
402 Value::Array cooldowns;
403 for (
const auto& cooldown :
value.abilities.cooldowns)
404 cooldowns.emplace_back(
Value::
Object{{
"id", cooldown.id}, {
"remaining", cooldown.remaining}});
406 if (
value.abilities.channel) {
408 channel =
Value(Value::Object{{
"point", positionValue(
active.point)},
409 {
"spec", abilitySpecValue(
active.spec)},
410 {
"remaining",
active.remaining},
411 {
"startingHealth",
active.startingHealth},
412 {
"target",
value.abilityTarget.format()},
413 {
"tickRemaining",
active.tickRemaining}});
415 units.emplace_back(Value::Object{
417 Value(Value::Object{{
"channel", std::move(channel)}, {
"cooldowns",
Value(std::move(cooldowns))}})},
420 {
"autoCounterBattery",
value.artillery.autoCounterBattery},
421 {
"counterBatteryWindow",
static_cast<std::int64_t
>(
value.artillery.counterBatteryWindowTicks)},
422 {
"deployRemaining",
value.artillery.deployRemaining},
423 {
"deployTime",
value.artillery.deployTime},
424 {
"lastFirePosition", positionValue(
value.artillery.lastFirePosition)},
425 {
"lastFireTick",
static_cast<std::int64_t
>(
value.artillery.lastFireTick.value())},
426 {
"departedPosition", positionValue(
value.artillery.departedPosition)},
427 {
"hasDepartedPosition",
value.artillery.hasDepartedPosition},
428 {
"relocating",
value.artillery.relocating},
429 {
"relocationConflicts",
value.artillery.relocationConflictCount},
430 {
"relocationTarget", positionValue(
value.artillery.relocationTarget)},
431 {
"relocationThreat",
value.artillery.relocationThreat},
432 {
"positionInitialized",
value.artillery.positionInitialized},
433 {
"previous", positionValue({
value.artillery.previousX,
value.artillery.previousY})},
434 {
"observedFireSpotter",
value.observedFireSpotter.format()},
435 {
"requester",
value.fireSupportRequester.format()},
436 {
"shotSequence",
static_cast<std::int64_t
>(
value.artillery.shotSequence)},
437 {
"shotsRemaining",
value.artillery.suppressionShotsRemaining},
438 {
"shootAndScoot",
value.artillery.shootAndScootDistance},
439 {
"usingObservedFire",
value.artillery.usingObservedFire}})},
440 {
"attributes", attributesValue(
value.attributes)},
441 {
"combat",
Value(Value::Object{{
"acquisition",
value.combat.acquisitionRange},
442 {
"aimTolerance",
value.combat.aimToleranceDegrees},
443 {
"attackMove",
value.combat.attackMove},
444 {
"engagement",
value.combat.engagementRange},
445 {
"guard", positionValue({
value.combat.guardX,
value.combat.guardY})},
446 {
"guardSet",
value.combat.guardSet},
447 {
"hold",
value.combat.holdPosition},
448 {
"leash",
value.combat.leashRange},
449 {
"firingHeight",
value.combat.firingHeight},
450 {
"targetHeight",
value.combat.targetHeight},
451 {
"suppressionPerShot",
value.combat.suppressionPerShot},
452 {
"targetPriorities", prioritiesValue(
value.combat.targetPriorities)},
453 {
"shotSequence",
static_cast<std::int64_t
>(
value.combat.shotSequence)},
455 {
"target",
value.combatTarget.format()},
456 {
"turnRate",
value.combat.turnRateDegrees},
457 {
"upgradeDamage",
value.combat.upgradeDamageFactor}})},
458 {
"command",
Value(Value::Object{{
"capacity",
value.command.capacity},
459 {
"cost",
value.command.cost},
460 {
"inCommand",
value.command.inCommand},
461 {
"jammed",
value.command.jammed},
462 {
"load",
value.command.load},
463 {
"priority",
value.command.priority},
464 {
"range",
value.command.range},
465 {
"damageFactor",
value.command.outOfCommandDamageFactor},
466 {
"relayRequiresUplink",
value.command.relayRequiresUplink},
467 {
"relayActive",
value.command.relayActive},
468 {
"requires",
value.command.requiresCommand},
469 {
"speedFactor",
value.command.outOfCommandSpeedFactor},
470 {
"source",
value.commandSource.format()},
471 {
"uplink",
value.commandUplink.format()}})},
472 {
"container",
value.container.format()},
473 {
"containmentCapacity",
static_cast<std::int64_t
>(
value.containmentCapacity)},
474 {
"captureRate",
value.capture.rate},
475 {
"definition",
value.definition.format()},
476 {
"displayName",
value.displayName},
477 {
"durability",
Value(Value::Object{{
"alive",
value.durability.alive},
478 {
"health",
value.durability.state.health},
479 {
"maxHealth",
value.durability.state.maxHealth},
480 {
"maxPoise",
value.durability.state.maxPoise},
481 {
"poise",
value.durability.state.poise}})},
482 {
"effects", effectsValue(
value.effects)},
483 {
"faction",
value.faction.format()},
484 {
"morale",
Value(Value::Object{{
"active",
value.morale.active},
485 {
"auraRange",
value.morale.auraRange},
486 {
"auraRecovery",
value.morale.auraRecoveryBonus},
487 {
"auraSuppression",
value.morale.auraSuppressionFactor},
488 {
"capacity",
value.morale.capacity},
489 {
"recovery",
value.morale.recoveryRate},
490 {
"retreatDistance",
value.morale.retreatDistance},
491 {
"retreatEnabled",
value.morale.retreatEnabled},
492 {
"retreatThreshold",
value.morale.retreatThreshold},
493 {
"retreating",
value.morale.retreating},
494 {
"suppressedDamage",
value.morale.suppressedDamageFactor},
495 {
"suppressedSpeed",
value.morale.suppressedSpeedFactor},
496 {
"suppression",
value.morale.suppression}})},
497 {
"motion",
Value(Value::Object{{
"arrivalRadius",
value.motion.arrivalRadius},
498 {
"airborne",
value.motion.airborne},
499 {
"arrived",
value.motion.arrived},
500 {
"speed",
value.motion.speed},
501 {
"x",
value.motion.x},
502 {
"y",
value.motion.y}})},
504 Value(Value::Object{{
"goal", positionValue(
value.navigation.plannedGoal)},
505 {
"movementPriority",
value.navigation.movementPriority},
506 {
"order",
value.navigation.plannedOrderId},
507 {
"unreachable",
value.navigation.unreachable},
508 {
"patrolInitialized",
value.navigation.patrolInitialized},
509 {
"patrolOrigin", positionValue(
value.navigation.patrolOrigin)},
510 {
"patrolTowardTarget",
value.navigation.patrolTowardTarget},
511 {
"trafficWaiting",
value.navigation.trafficWaiting},
512 {
"unreachableReported",
value.navigation.unreachableReported},
513 {
"waypointIndex",
static_cast<std::int64_t
>(
value.navigation.waypointIndex)},
514 {
"waypoints",
Value(std::move(waypoints))}})},
515 {
"occupants", refsValue(
value.occupants)},
516 {
"orders", ordersValue(
value.orders,
value.orderTargets)},
517 {
"shield",
Value(Value::Object{{
"capacity",
value.shield.capacity},
518 {
"cooldown",
value.shield.cooldown},
519 {
"regenDelay",
value.shield.regenDelay},
520 {
"regenRate",
value.shield.regenRate},
521 {
"value",
value.shield.value}})},
522 {
"subject",
value.subject.format()},
523 {
"tags", stringsValue(
value.tags)},
524 {
"supply",
Value(Value::Object{{
"assignedTarget",
value.supplyTarget.format()},
525 {
"autoDispatch",
value.supply.autoDispatch},
526 {
"autoThreshold",
value.supply.autoThreshold},
527 {
"capacity",
value.supply.capacity},
528 {
"priority",
value.supply.priority},
529 {
"convoyIndex",
static_cast<std::int64_t
>(
value.supply.convoyIndex)},
530 {
"convoyLeader",
value.convoyLeader.format()},
531 {
"convoyWaiting",
value.supply.convoyWaiting},
532 {
"range",
value.supply.range},
533 {
"relay",
value.supply.relayEnabled},
534 {
"rendezvousActive",
value.supply.rendezvousActive},
535 {
"rendezvousAvoidedThreat",
value.supply.rendezvousAvoidedThreat},
536 {
"rendezvousPoint", positionValue(
value.supply.rendezvousPoint)},
537 {
"rendezvousThreat",
value.supply.rendezvousThreat},
538 {
"routeAvoidedThreat",
value.supply.routeAvoidedThreat},
539 {
"routeThreat",
value.supply.routeThreat},
540 {
"reserved",
value.supply.reservedStock},
541 {
"returning",
value.supply.returning},
542 {
"returnPoint", positionValue(
value.supply.returnPoint)},
543 {
"stock",
value.supply.stock},
544 {
"transferProgress",
value.supply.transferProgress},
545 {
"transferRate",
value.supply.transferRate}})},
546 {
"tactics",
Value(Value::Object{
547 {
"combatGroup",
static_cast<std::int64_t
>(
value.tactics.combatGroup)},
548 {
"coordinatedVolleyInterval",
value.tactics.coordinatedVolleyInterval},
549 {
"escortOffset", positionValue({
value.tactics.escortOffsetX,
value.tactics.escortOffsetY})},
550 {
"escortTarget",
value.escortTarget.format()},
551 {
"fireControlEffectiveness",
value.tactics.fireControlEffectiveness},
552 {
"guard", positionValue({
value.tactics.guardX,
value.tactics.guardY})},
553 {
"guardSet",
value.tactics.guardSet},
554 {
"protectionRange",
value.tactics.protectionRange},
555 {
"escortScreenSector",
value.tactics.escortScreenSector},
556 {
"escortSectorMatched",
value.tactics.escortSectorMatched},
557 {
"escortReinforcing",
value.tactics.escortReinforcing},
558 {
"escortReinforcementSector",
value.tactics.escortReinforcementSector},
559 {
"escortRearGuard",
value.tactics.escortRearGuard},
560 {
"escortInterceptTarget",
value.tactics.escortInterceptTarget.format()},
561 {
"escortHandoffCount",
static_cast<std::int64_t
>(
value.tactics.escortHandoffCount)},
562 {
"retreatCoverElapsed",
value.tactics.retreatCoverElapsed},
563 {
"retreatCovering",
value.tactics.retreatCovering},
564 {
"retreatFireTeam",
value.tactics.retreatFireTeam},
565 {
"threatSector",
value.tactics.threatSector},
566 {
"volleyHolding",
value.tactics.volleyHolding},
567 {
"volleyReleaseRemaining",
value.tactics.volleyReleaseRemaining}})},
568 {
"technology", stringsValue(
value.technology.applied)},
569 {
"veterancy",
Value(Value::Object{{
"eliteDamage",
value.veterancy.eliteDamageFactor},
570 {
"eliteHealth",
value.veterancy.eliteHealthFactor},
571 {
"eliteThreshold",
value.veterancy.eliteThreshold},
572 {
"experience",
value.veterancy.experience},
573 {
"level",
value.veterancy.level},
574 {
"veteranDamage",
value.veterancy.veteranDamageFactor},
575 {
"veteranHealth",
value.veterancy.veteranHealthFactor},
576 {
"veteranThreshold",
value.veterancy.veteranThreshold}})},
577 {
"vision",
Value(Value::Object{{
"cloaked",
value.vision.cloaked},
578 {
"detectionRange",
value.vision.detectionRange},
579 {
"detectionStrength",
value.vision.detectionStrength},
580 {
"enabled",
value.vision.enabled},
581 {
"jammingRange",
value.vision.jammingRange},
582 {
"radarRange",
value.vision.radarRange},
583 {
"radarResolution",
value.vision.radarResolution},
584 {
"sightRange",
value.vision.sightRange},
585 {
"stealth",
value.vision.stealth}})},
586 {
"worker",
Value(Value::Object{{
"auto",
value.worker.autoAssign},
587 {
"buildRate",
value.worker.buildRate},
588 {
"capacity",
value.worker.capacity},
589 {
"cargo",
value.worker.cargo},
590 {
"dropoff",
value.worker.dropoff.format()},
591 {
"gatherRate",
value.worker.gatherRate},
592 {
"repairRate",
value.worker.repairRate},
593 {
"resource",
value.worker.resourceType},
594 {
"resourceNode",
value.worker.resourceNode.format()}})}});
597 Value::Array buildings;
600 {
"builders", refsValue(
value.builders)},
601 {
"capturingFaction",
value.capturingFaction.format()},
602 {
"capture",
Value(Value::Object{{
"blockedByGarrison",
value.capture.blockedByGarrison},
603 {
"capturable",
value.capture.capturable},
604 {
"duration",
value.capture.durationSeconds},
605 {
"progress",
value.capture.progress}})},
606 {
"combat",
Value(Value::Object{
607 {
"acquisition",
value.combat.acquisitionRange},
608 {
"airDefenseNetworkRange",
value.combat.airDefenseNetworkRange},
609 {
"airDefenseNetworkRoot",
value.airDefenseNetworkRoot.format()},
610 {
"airDefenseNetworkSize",
static_cast<std::int64_t
>(
value.combat.airDefenseNetworkSize)},
611 {
"aimTolerance",
value.combat.aimToleranceDegrees},
612 {
"engagement",
value.combat.engagementRange},
613 {
"firingHeight",
value.combat.firingHeight},
614 {
"targetHeight",
value.combat.targetHeight},
615 {
"target",
value.combatTarget.format()},
616 {
"targetPriorities", prioritiesValue(
value.combat.targetPriorities)},
617 {
"shotSequence",
static_cast<std::int64_t
>(
value.combat.shotSequence)},
618 {
"turnRate",
value.combat.turnRateDegrees}})},
619 {
"command",
Value(Value::Object{{
"active",
value.command.active},
620 {
"capacity",
value.command.capacity},
621 {
"jammed",
value.command.jammed},
622 {
"jammingRange",
value.command.jammingRange},
623 {
"load",
value.command.load},
624 {
"range",
value.command.range}})},
625 {
"construction",
Value(Value::Object{{
"buildTime",
value.construction.buildTimeSeconds},
626 {
"paused",
value.construction.paused},
627 {
"progress",
value.construction.progress}})},
628 {
"definition",
value.definition.format()},
629 {
"displayName",
value.displayName},
630 {
"effects", effectsValue(
value.effects)},
631 {
"faction",
value.faction.format()},
632 {
"dropoff",
Value(Value::Object{{
"accepted", stringsValue(
value.dropoff.acceptedResources)},
633 {
"radius",
value.dropoff.radius}})},
634 {
"garrison",
Value(Value::Object{{
"capacity",
static_cast<std::int64_t
>(
value.garrison.capacity)},
635 {
"damageBonus",
value.garrison.damageBonusPerOccupant}})},
637 Value(Value::Object{{
"position", positionValue(
value.indirectFire.lastFirePosition)},
638 {
"tick",
static_cast<std::int64_t
>(
value.indirectFire.lastFireTick.value())}})},
639 {
"infrastructure",
Value(Value::Object{{
"buildInfluence",
value.infrastructure.buildInfluenceRadius},
640 {
"incomeProgress",
value.infrastructure.incomeProgress},
641 {
"incomeRate",
value.infrastructure.incomeRate},
642 {
"incomeResource",
value.infrastructure.incomeResource},
643 {
"powerConsumed",
value.infrastructure.powerConsumed},
644 {
"powerPriority",
value.infrastructure.powerPriority},
645 {
"powerProduced",
value.infrastructure.powerProduced},
646 {
"powered",
value.infrastructure.powered}})},
647 {
"integrity",
Value(Value::Object{{
"alive",
value.integrity.alive},
648 {
"health",
value.integrity.state.health},
649 {
"maxHealth",
value.integrity.state.maxHealth},
650 {
"maxPoise",
value.integrity.state.maxPoise},
651 {
"poise",
value.integrity.state.poise},
652 {
"repairCost",
value.integrity.repairCostPerHealth},
653 {
"repairRemainder",
value.integrity.repairCostRemainder},
654 {
"repairResource",
value.integrity.repairResource}})},
655 {
"occupants", refsValue(
value.occupants)},
656 {
"orders", ordersValue(
value.orders,
value.orderTargets)},
657 {
"placement",
Value(Value::Object{{
"cellX",
value.placement.cellX},
658 {
"cellY",
value.placement.cellY},
659 {
"placed",
value.placement.placed},
660 {
"rotation",
value.placement.rotation},
661 {
"worldX",
value.placement.worldX},
662 {
"worldY",
value.placement.worldY}})},
663 {
"production",
value.productionJson},
664 {
"reinforcements", refsValue(
value.reinforcements)},
665 {
"rally",
Value(Value::Object{
666 {
"combatGroup",
static_cast<std::int64_t
>(
value.rally.combatGroup)},
667 {
"blockedTask",
value.rally.blockedProductionTask},
668 {
"commandAppend",
value.rally.command.append},
669 {
"commandDefinition",
value.rally.command.definitionId},
670 {
"commandKind",
static_cast<std::int64_t
>(
value.rally.command.kind)},
671 {
"commandPriority",
value.rally.command.priority},
672 {
"commandRadius",
value.rally.command.radius},
673 {
"commandSecondary", positionValue(
value.rally.command.secondaryTarget)},
674 {
"commandTarget", positionValue(
value.rally.command.target)},
675 {
"commandTargetEntity",
value.rallyCommandTarget.format()},
676 {
"commandTimeout",
value.rally.command.timeoutSeconds},
677 {
"enabled",
value.rally.enabled},
678 {
"minimumLoad",
static_cast<std::int64_t
>(
value.rally.minimumTransportLoad)},
679 {
"reinforcementCapped",
value.rally.reinforcementCapped},
680 {
"reinforcementCappedSeconds",
value.rally.reinforcementCappedSeconds},
681 {
"reinforcementAutoCancelDelay",
value.rally.reinforcementAutoCancelDelay},
682 {
"reinforcementFallbacks", stringMapValue(
value.rally.reinforcementFallbacks)},
683 {
"reinforcementLimit",
static_cast<std::int64_t
>(
value.rally.reinforcementLimit)},
684 {
"reinforcementPausedTask",
value.rally.reinforcementPolicyPausedTask},
685 {
"reinforcementTypeLimits", integerMapValue(
value.rally.reinforcementTypeLimits)},
686 {
"reinforcementTypePriorities", integerMapValue(
value.rally.reinforcementTypePriorities)},
687 {
"spawnBlocked",
value.rally.productionSpawnBlocked},
688 {
"settledTasks", stringsValue(
value.rally.settledProductionTasks)},
689 {
"transport",
value.rallyTransport.format()},
690 {
"transportActive",
value.rally.transportActive}})},
691 {
"shield",
Value(Value::Object{{
"capacity",
value.shield.capacity},
692 {
"cooldown",
value.shield.cooldown},
693 {
"regenDelay",
value.shield.regenDelay},
694 {
"regenRate",
value.shield.regenRate},
695 {
"value",
value.shield.value}})},
696 {
"subject",
value.subject.format()},
697 {
"tags", stringsValue(
value.tags)},
699 Value(Value::Object{{
"capacity",
value.supply.capacity},
700 {
"productionCost",
static_cast<std::int64_t
>(
value.supply.productionCostPerRound)},
701 {
"productionProgress",
value.supply.productionProgress},
702 {
"productionRate",
value.supply.productionRate},
703 {
"productionResource",
value.supply.productionResource},
704 {
"range",
value.supply.range},
705 {
"stock",
value.supply.stock},
706 {
"transferProgress",
value.supply.transferProgress},
707 {
"transferRate",
value.supply.transferRate}})},
708 {
"technology", stringsValue(
value.technology.applied)},
709 {
"vision",
Value(Value::Object{{
"detectionRange",
value.vision.detectionRange},
710 {
"detectionStrength",
value.vision.detectionStrength},
711 {
"enabled",
value.vision.enabled},
712 {
"jammingRange",
value.vision.jammingRange},
713 {
"radarRange",
value.vision.radarRange},
714 {
"radarResolution",
value.vision.radarResolution},
715 {
"sightRange",
value.vision.sightRange}})}});
720 {
"displayName",
value.displayName},
721 {
"infinite",
value.stock.infinite},
722 {
"maximum",
value.stock.maximum},
723 {
"position", positionValue({
value.position.x,
value.position.y})},
724 {
"radius",
value.position.radius},
725 {
"remaining",
value.stock.remaining},
726 {
"resource",
value.stock.resourceType},
727 {
"subject",
value.subject.format()},
728 {
"workers", refsValue(
value.workers)}});
730 Value::Array players;
732 players.emplace_back(
Value::
Object{{
"buildings", refsValue(
value.buildings)},
733 {
"displayName",
value.displayName},
734 {
"subject",
value.subject.format()},
735 {
"units", refsValue(
value.units)}});
737 Value::Array factions;
739 Value::Array contacts;
740 for (
const auto& contact :
value.intel.contacts)
741 contacts.emplace_back(
Value::
Object{{
"age", contact.ageSeconds},
742 {
"detected", contact.detected},
743 {
"kind", contact.kind},
744 {
"position", positionValue(contact.position)},
745 {
"subject", contact.subject.format()},
746 {
"visible", contact.visible}});
747 Value::Object productionReserves;
748 for (
const auto& [
resource, reserve] :
value.productionPolicy.resourceReserves)
750 {
"minimumPriority", reserve.minimumPriority}}));
751 Value::Object unlockedDefinitions;
752 for (
const auto& [upgrade,
definition] :
value.technology.unlockedDefinitions)
754 factions.emplace_back(Value::Object{
755 {
"buildings", refsValue(
value.buildings)},
756 {
"contacts",
Value(std::move(contacts))},
757 {
"displayName",
value.displayName},
758 {
"intelEnabled",
value.intel.enabled},
760 Value(Value::Object{{
"attackThreshold",
value.strategy.attackThreshold},
761 {
"armyDefinition",
value.strategy.armyDefinition.format()},
762 {
"enabled",
value.strategy.enabled},
763 {
"formationSpacing",
value.strategy.formationSpacing},
764 {
"thinkAccumulator",
value.strategy.thinkAccumulator},
765 {
"thinkInterval",
value.strategy.thinkInterval},
766 {
"targetBuildingDefinition",
value.strategy.targetBuildingDefinition.format()},
767 {
"workerDefinition",
value.strategy.workerDefinition.format()},
768 {
"workers",
value.strategy.desiredWorkers}})},
769 {
"subject",
value.subject.format()},
770 {
"technology",
Value(Value::Object{{
"consumed", stringsValue(
value.technology.consumedTasks)},
771 {
"definitions",
Value(std::move(unlockedDefinitions))},
772 {
"unlocked", stringsValue(
value.technology.unlocked)}})},
773 {
"productionPolicy",
Value(Value::Object{{
"resourceReserves",
Value(std::move(productionReserves))}})},
774 {
"units", refsValue(
value.units)},
776 Value(Value::Object{{
"autoConstruction",
value.workforce.autoConstruction},
777 {
"autoRepair",
value.workforce.autoRepair},
778 {
"builders",
static_cast<std::int64_t
>(
value.workforce.maxBuildersPerSite)},
779 {
"repairers",
static_cast<std::int64_t
>(
value.workforce.maxRepairersPerBuilding)},
780 {
"reserve",
static_cast<std::int64_t
>(
value.workforce.reserveWorkers)}})}});
783 Value::Array matches;
785 Value::Array participants;
786 for (
const auto& participant :
value.participants)
787 participants.emplace_back(
Value::
Object{{
"eliminated", participant.eliminated},
788 {
"faction", participant.faction.format()},
789 {
"reason", participant.reason},
790 {
"surrendered", participant.surrendered},
791 {
"team", participant.team}});
795 {
"kind",
event.kind},
796 {
"reason",
event.reason},
797 {
"sequence",
static_cast<std::int64_t
>(
event.sequence)},
798 {
"team",
event.team}});
799 matches.emplace_back(Value::Object{{
"events",
Value(std::move(
events))},
800 {
"participants",
Value(std::move(participants))},
801 {
"phase",
static_cast<std::int64_t
>(
value.state.phase)},
802 {
"rule",
static_cast<std::int64_t
>(
value.rules.rule)},
803 {
"ruleArchetype",
value.rules.archetype},
804 {
"sequence",
static_cast<std::int64_t
>(
value.state.updateSequence)},
805 {
"subject",
value.subject.format()},
806 {
"target",
value.rules.targetValue},
807 {
"winningTeam",
value.state.winningTeam}});
809 Value::Array movementGroups;
810 for (
const auto&
group :
state.movementGroups) {
814 movementGroups.emplace_back(
815 Value::Object{{
"leadDistance",
group.leadDistance}, {
"members",
Value(std::move(
members))}});
817 Value root(Value::Object{{
"movementGroups",
Value(std::move(movementGroups))},
818 {
"buildings",
Value(std::move(buildings))},
819 {
"factions",
Value(std::move(factions))},
820 {
"matches",
Value(std::move(matches))},
821 {
"players",
Value(std::move(players))},
822 {
"projectiles",
Value(Value::Object{{
"payloads",
Value(std::move(projectilePayloads))},
823 {
"slots",
Value(std::move(projectileSlots))}})},
826 {
"version",
static_cast<std::int64_t
>(
state.version)}});
827 return root.toJson();