载入中...
搜索中...
未找到
RTSSnapshot.cpp
浏览该文件的文档.
1#include "rts/RTS.h"
3
4#include "common/Value.h"
5
6#include <algorithm>
7#include <unordered_set>
8
9namespace eve::rts {
10namespace {
11
13
14template <typename T>
15std::vector<SubjectRef> subjectsOf(const std::vector<T>& handles) {
16 std::vector<SubjectRef> result;
17 result.reserve(handles.size());
18 for (const auto& handle : handles) {
19 const auto subject = subjectOf(handle);
20 if (subject.isValid()) result.push_back(subject);
21 }
22 return result;
23}
24
25void stabilizeOrders(OrderComponent::Snapshot& state, std::map<std::string, SubjectRef>& targets) {
26 for (auto& [id, command] : state.extended) {
27 const auto subject = subjectOf(command.targetEntity);
28 if (subject.isValid()) targets[id] = subject;
29 command.targetEntity = {};
30 }
31}
32
33Value refsValue(const std::vector<SubjectRef>& values) {
34 Value::Array result;
35 for (SubjectRef value : values) result.emplace_back(value.format());
36 return Value(std::move(result));
37}
38
39Value stringsValue(const std::vector<std::string>& values) {
40 Value::Array result;
41 for (const auto& value : values) result.emplace_back(value);
42 return Value(std::move(result));
43}
44
45Value prioritiesValue(const std::map<std::string, float>& values) {
46 Value::Object result;
47 for (const auto& [key, value] : values) result.emplace(key, value);
48 return Value(std::move(result));
49}
50
51template <typename T>
52Value integerMapValue(const std::map<std::string, T>& values) {
53 Value::Object result;
54 for (const auto& [key, value] : values) result.emplace(key, static_cast<std::int64_t>(value));
55 return Value(std::move(result));
56}
57
58Value stringMapValue(const std::map<std::string, std::string>& values) {
59 Value::Object result;
60 for (const auto& [key, value] : values) result.emplace(key, value);
61 return Value(std::move(result));
62}
63
64Value positionValue(WorldPosition value) { return Value(Value::Object{{"x", value.x}, {"y", value.y}}); }
65
66Value abilitySpecValue(const AbilitySpec& value) {
67 return Value(Value::Object{{"appliesEffect", value.appliesEffect},
68 {"castTime", value.castTime},
69 {"caster", value.casterDefinition.format()},
70 {"channelTick", value.channelTickInterval},
71 {"cooldown", value.cooldown},
72 {"damage", value.damage},
73 {"damageType", value.damageType},
74 {"effectDuration", value.effect.duration},
75 {"effectId", value.effect.id},
76 {"effectKind", static_cast<std::int64_t>(value.effect.kind)},
77 {"effectMagnitude", value.effect.magnitude},
78 {"effectPeriod", value.effect.period},
79 {"effectMaxStacks", static_cast<std::int64_t>(value.effect.policy.maxStacks)},
80 {"effectStackMode", static_cast<std::int64_t>(value.effect.policy.stackMode)},
81 {"effectSource", value.effect.source},
82 {"effectTags", stringsValue(value.effect.tags)},
83 {"healing", value.healing},
84 {"id", value.id},
85 {"interruptOnDamage", value.interruptOnDamage},
86 {"radius", value.radius},
87 {"range", value.range},
88 {"resourceCost", value.resourceCost},
89 {"resourceType", value.resourceType},
90 {"target", static_cast<std::int64_t>(value.target)}});
91}
92
93Value attributesValue(const attributes::AttributeProjectionSnapshot& value) {
94 Value::Array bases;
95 for (const auto& base : value.bases)
96 bases.emplace_back(Value::Object{{"attribute", base.attribute}, {"base", base.base}});
97 Value::Array modifiers;
98 for (const auto& modifier : value.modifiers)
99 modifiers.emplace_back(Value::Object{{"attribute", modifier.attribute},
100 {"id", modifier.id},
101 {"operation", static_cast<std::int64_t>(modifier.operation)},
102 {"policy", modifier.policyId},
103 {"priority", static_cast<std::int64_t>(modifier.priority)},
104 {"sequence", static_cast<std::int64_t>(modifier.sequence)},
105 {"source", modifier.source},
106 {"value", modifier.value}});
107 return Value(Value::Object{{"bases", Value(std::move(bases))},
108 {"modifiers", Value(std::move(modifiers))},
109 {"revision", static_cast<std::int64_t>(value.capturedRevision.value())}});
110}
111
112Value effectsValue(const RTSEffectSnapshot& value) {
113 Value::Array instances;
114 for (int index = 0; index < value.effects.effectCount(); ++index) {
115 const auto* effect = value.effects.effectAt(index);
116 if (effect == nullptr) continue;
117 instances.emplace_back(Value::Object{{"duration", effect->duration},
118 {"id", effect->id},
119 {"magnitude", effect->magnitude},
120 {"payload", effect->payload.toJson()},
121 {"period", effect->period},
122 {"periodElapsed", effect->periodElapsed},
123 {"priority", effect->priority},
124 {"remaining", effect->remaining},
125 {"source", effect->source},
126 {"policyDuration", static_cast<std::int64_t>(effect->policy.duration)},
127 {"policyMagnitude", static_cast<std::int64_t>(effect->policy.magnitude)},
128 {"policyMaxStacks", static_cast<std::int64_t>(effect->policy.maxStacks)},
129 {"policyOverflow", static_cast<std::int64_t>(effect->policy.overflow)},
130 {"policyStackCount", static_cast<std::int64_t>(effect->policy.stackCount)},
131 {"policyStackMode", static_cast<std::int64_t>(effect->policy.stackMode)},
132 {"stackCount", static_cast<std::int64_t>(effect->stackCount)},
133 {"stackKey", effect->stackKey},
134 {"subject", effect->subject},
135 {"tags", stringsValue(effect->tags)},
136 {"type", effect->type}});
137 }
138 return Value(Value::Object{{"commandInterrupts", static_cast<std::int64_t>(value.target.commandInterrupts)},
139 {"effects", Value(std::move(instances))},
140 {"morale", value.target.morale},
141 {"productionLocked", value.target.productionLocked},
142 {"suppression", static_cast<std::int64_t>(value.target.suppression)}});
143}
144
145Value ordersValue(const OrderComponent::Snapshot& orders, const std::map<std::string, SubjectRef>& targets) {
146 Value::Array extended;
147 for (const auto& [id, command] : orders.extended) {
148 auto target = targets.find(id);
149 extended.emplace_back(
150 Value::Object{{"append", command.append},
151 {"definition", command.definitionId},
152 {"id", id},
153 {"kind", static_cast<std::int64_t>(command.kind)},
154 {"priority", command.priority},
155 {"radius", command.radius},
156 {"secondary", positionValue(command.secondaryTarget)},
157 {"target", positionValue(command.target)},
158 {"targetEntity", target == targets.end() ? std::string{} : target->second.format()},
159 {"timeout", command.timeoutSeconds}});
160 }
161 return Value(Value::Object{{"extended", Value(std::move(extended))}, {"queue", orders.queueJson}});
162}
163
164} // namespace
165
166Result<RTSStateSnapshot> RTS::snapshotState() const {
167 RTSStateSnapshot result;
168 auto groups = captureMovementGroups();
169 if (!groups) return Result<RTSStateSnapshot>::failure(groups.status());
170 result.movementGroups = std::move(groups).takeValue();
171 auto* currentTable = ecs::current();
172 for (const auto& handle : units_) {
173 if (handle.table != currentTable) continue;
174 auto* unit = dynamic_cast<Unit*>(ecs::try_get(handle));
175 if (unit == nullptr) continue;
177 value.subject = unit->identity()->subject;
178 value.definition = unit->definition()->id;
179 value.displayName = unit->identity()->displayName;
180 auto attributes = RTSUnitAttributeAdapter::snapshot(*unit);
181 if (!attributes) return Result<RTSStateSnapshot>::failure(attributes.status());
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());
187 value.motion = *unit->motion();
188 value.navigation = *unit->navigation();
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();
193 value.vision = *unit->vision();
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};
203 value.combat = *unit->combat();
204 value.combatTarget = subjectOf(value.combat.target);
205 value.combat.target = {};
206 value.durability = *unit->durability();
207 value.shield = *unit->shield();
208 value.veterancy = *unit->veterancy();
209 value.command = *unit->command();
210 value.commandSource = subjectOf(value.command.source);
211 value.commandUplink = subjectOf(value.command.uplink);
212 value.command.source = {};
213 value.command.uplink = {};
214 value.abilities = *unit->abilities();
215 if (value.abilities.channel) {
216 value.abilityTarget = subjectOf(value.abilities.channel->target);
217 value.abilities.channel->target = {};
218 }
219 value.capture = *unit->capture();
220 value.containmentCapacity = unit->containment()->capacity;
221 value.container = subjectOf(unit->containment()->container.resolve());
222 value.occupants = subjectsOf(unit->containment()->occupants);
223 value.supply = *unit->supply();
224 value.supplyTarget = subjectOf(value.supply.assignedTarget);
225 value.convoyLeader = subjectOf(value.supply.convoyLeader);
226 value.supply.assignedTarget = {};
227 value.supply.convoyLeader = {};
228 value.morale = *unit->morale();
229 value.artillery = *unit->artillery();
230 value.fireSupportRequester = subjectOf(value.artillery.fireSupportRequester);
231 value.observedFireSpotter = subjectOf(value.artillery.observedFireSpotter);
232 value.artillery.fireSupportRequester = {};
233 value.artillery.observedFireSpotter = {};
234 value.tactics = *unit->tactics();
235 value.escortTarget = subjectOf(value.tactics.escortTarget);
236 value.tactics.escortTarget = {};
237 value.technology = *unit->technology();
238 auto orders = unit->orders()->values.snapshotState();
239 if (!orders)
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);
244 result.units.push_back(std::move(value));
245 }
246 for (const auto& handle : buildings_) {
247 if (handle.table != currentTable) continue;
248 auto* building = dynamic_cast<Building*>(ecs::try_get(handle));
249 if (building == nullptr) continue;
251 value.subject = building->identity()->subject;
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();
289 if (!production)
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();
294 if (!orders)
295 return Result<RTSStateSnapshot>::failure(Diagnostic::error(
296 DiagnosticCode::Failed, "failed to snapshot RTS building orders", "buildings.orders"));
297 value.orders = std::move(orders).takeValue();
298 stabilizeOrders(value.orders, value.orderTargets);
299 result.buildings.push_back(std::move(value));
300 }
301 for (const auto& handle : resourceNodes_) {
302 if (handle.table != currentTable) continue;
303 auto* node = dynamic_cast<ResourceNode*>(ecs::try_get(handle));
304 if (node == nullptr) continue;
305 result.resourceNodes.push_back({node->identity()->subject, node->identity()->displayName, *node->position(),
306 *node->stock(), node->harvest()->capacity,
307 subjectsOf(node->harvest()->workers)});
308 }
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)});
315 }
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()});
324 }
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;
330 value.subject = match->identity()->subject;
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});
337 result.matches.push_back(std::move(value));
338 }
339 result.projectiles = projectiles_.snapshot();
340 return Result<RTSStateSnapshot>::success(std::move(result), Status::success(StatusCode::Applied));
341}
342
343Result<std::string> RTS::canonicalStateJson() const {
344 auto captured = snapshotState();
345 if (!captured) return Result<std::string>::failure(captured.status());
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);
354
355 Value::Array projectileSlots;
356 for (const auto& slot : state.projectiles.runtime.slots) {
357 Value projectileState;
358 if (slot.state) {
359 const auto& projectile = *slot.state;
360 projectileState =
361 Value(Value::Object{{"age", static_cast<std::int64_t>(projectile.age.nanoseconds())},
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}});
375 }
376 projectileSlots.emplace_back(
377 Value::Object{{"generation", static_cast<std::int64_t>(slot.generation)}, {"state", projectileState}});
378 }
379 Value::Array projectilePayloads;
380 for (const auto& payload : state.projectiles.payloads)
381 projectilePayloads.emplace_back(Value::Object{{"blocked", payload.blockedByObstacles},
382 {"damage", payload.damage},
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)},
388 {"radius", payload.radius},
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}});
397
398 Value::Array units;
399 for (const auto& value : state.units) {
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}});
405 Value channel;
406 if (value.abilities.channel) {
407 const auto& active = *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}});
414 }
415 units.emplace_back(Value::Object{
416 {"abilities",
417 Value(Value::Object{{"channel", std::move(channel)}, {"cooldowns", Value(std::move(cooldowns))}})},
418 {"artillery",
419 Value(Value::Object{
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)},
454 {"stance", combatStanceName(value.combat.stance)},
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}})},
503 {"navigation",
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()}})}});
595 }
596
597 Value::Array buildings;
598 for (const auto& value : state.buildings)
599 buildings.emplace_back(Value::Object{
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}})},
636 {"indirectFire",
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)},
698 {"supply",
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}})}});
716
717 Value::Array nodes;
718 for (const auto& value : state.resourceNodes)
719 nodes.emplace_back(Value::Object{{"capacity", static_cast<std::int64_t>(value.workerCapacity)},
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)}});
729
730 Value::Array players;
731 for (const auto& value : state.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)}});
736
737 Value::Array factions;
738 for (const auto& value : state.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)
749 productionReserves.emplace(resource, Value(Value::Object{{"amount", reserve.amount},
750 {"minimumPriority", reserve.minimumPriority}}));
751 Value::Object unlockedDefinitions;
752 for (const auto& [upgrade, definition] : value.technology.unlockedDefinitions)
753 unlockedDefinitions.emplace(upgrade, Value(definition));
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},
759 {"strategy",
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)},
775 {"workforce",
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)}})}});
781 }
782
783 Value::Array matches;
784 for (const auto& value : state.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}});
792 Value::Array events;
793 for (const auto& event : value.events.values)
794 events.emplace_back(Value::Object{{"faction", event.faction.format()},
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}});
808 }
809 Value::Array movementGroups;
810 for (const auto& group : state.movementGroups) {
811 Value::Array members;
812 for (const auto& member : group.members)
813 members.emplace_back(Value::Object{{"subject", member.subject.format()}, {"orderId", member.orderId}});
814 movementGroups.emplace_back(
815 Value::Object{{"leadDistance", group.leadDistance}, {"members", Value(std::move(members))}});
816 }
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))}})},
824 {"resourceNodes", Value(std::move(nodes))},
825 {"units", Value(std::move(units))},
826 {"version", static_cast<std::int64_t>(state.version)}});
827 return root.toJson();
828}
829
830Result<ContentId> RTS::stateHash(const SnapshotHashProvider& hashProvider) const {
831 if (!hashProvider)
833 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS state hash provider is required", "hashProvider"));
834 auto canonical = canonicalStateJson();
835 if (!canonical) return Result<ContentId>::failure(canonical.status());
836 return hashProvider(canonical.value());
837}
838
839} // namespace eve::rts
LogicalId target
double value
Value::Object payload
bool & active
int root
Definition AnimSmr.cpp:119
int subject
Definition AnimSmr.cpp:163
building::EdgeCurveGroup group
std::vector< BuildingInstanceSnapshot > instances
std::map< std::string, Var > values
std::uint32_t key
HexVec3 left
HexVec3 right
std::vector< BvhNode > nodes
uint32_t groups
Definition OnnxGpgpu.cpp:39
std::unique_ptr< ParticleEffect > effect
std::string id
Definition PlayHost.cpp:108
PrimitiveHandle handle
RTS module owner and phase-one composition profile entry point.
const RoadNode * node
std::string resource
TacticalUnit * unit
Battle::Events events
std::vector< UnitCandidate > units
LogicalId definition
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
uint32_t index
Owning, renderer-independent dynamic values.
glm::vec3 point
Move-only operation result carrying either a value or Status.
Definition Result.h:155
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
RTS building domain root with placement and production composition.
Definition RTSTypes.h:840
Faction domain root; membership is a set of typed runtime handles.
Definition RTSTypes.h:1178
Independent match composition root; factions may participate in different matches.
Definition RTSTypes.h:1295
Player domain root; selection is RTS-local, authority/economy/social are links.
Definition RTSTypes.h:1119
Harvestable RTS resource node; deposited balances are owned by an external resource account.
Definition RTSTypes.h:1071
RTS unit domain root.
Definition RTSTypes.h:495
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
SubjectRef subjectOf(ecs::Entity *entity)
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
std::function< Result< ContentId >(std::string_view canonicalInput)> SnapshotHashProvider
Injected content-digest implementation used by snapshots.
Definition Snapshot.h:36
One module-owned building snapshot with stable RTS relationships.
Definition RTSSnapshot.h:70
Independent RTS match state and retained deterministic events.
Complete RTS-owned state; v2 adds movement groups, v1 imports with no groups. @cost Linear in saved e...
std::vector< RTSPlayerSnapshot > players
std::vector< RTSResourceNodeSnapshot > resourceNodes
std::vector< RTSUnitSnapshot > units
std::vector< RTSFactionSnapshot > factions
std::vector< RTSMatchSnapshot > matches
std::vector< RTSBuildingSnapshot > buildings
std::vector< RTSMovementGroupSnapshot > movementGroups
One module-owned unit snapshot; external provider links are intentionally excluded.
Definition RTSSnapshot.h:29