beyond-all-reason / beyond-all-reason/RecoilEngine

[Feature request] Unsynced units/features

Open
#644 7 comments 1 reaction 0 assignees View on GitHub
Dominant language
C++
Stars
679
Forks
290
Avg merge
3d 1h
Merged PRs (30d)
38

Description

Bill of materials to be done before starting this ticket:
* #1373
* optionally #1000 for a much simpler precedent of a new world object type

----

# User story, use case etc

Trees and rocks are visible in fog of war (via the feature visibility modrule), but this lets players see when they get reclaimed or crushed. I want them to appear intact until the player actually sees them. This is what ghosts generally do. But ghosts are bad for this case: they look quite different, they aren't clickable or have tooltip, and it's harder to make them work with things that call `Spring.GetAllFeatures()` (or visible features, or in rectangle, doesn't matter) and work on that set, for example reclaim value summary, and it wouldn't be passed to callins like DrawFeatureBla, for example for the swaying tree shader. I want something that behaves like a feature in the ways mentioned above, but is still unsynced, i.e. can be created and manipulated at will and doesn't affect other players.

This could be extended to units, where the benefit is much greater because units have order queues (which would be translated into something else, since the unit wouldn't act on its queue). Here are some ways one could use unsynced units:

1) initial queue and startpoint. Currently the widget doesn't implement all commands (can't jump or right-click to move) and assumes only one commander (so if you get a double commander then one gets the commands and one is left idle). If the commanders were unsynced units you could select them and give them commands using native engine UI instead of having a broken-ass emulation. At spawn, the unsynced units' queue would be copied over to the real commanders.

2) second factory queue, perhaps for different unit types - think how SC2 Zerg Hatchery has a separate queue for drones.

3) shared factory rally queues. If you want a second factory to have the same rally queue as the first, you need to replicate the queue. You could probably have some special "replicate queue" command but that still leaves one factory as the primary (what if it dies? Meanwhile an unsynced unit can't die and can be distinguished from one of N factories more easily). Similarly it could represent a ferry queue (independently of factories). Existing ZK ferry queue is quite limited (only handles move commands, requires special UI to give orders, looks different compared to regular queues), being able to use a native order queue would be a great UX improvement. Mockup screenshot (low visibility but the second factory is guarding a fake unit with a bunch of fight commands queued, and the first one is the existing ferry route with completely different UI compared to normal commands):
![image](https://user-images.githubusercontent.com/2573076/219027378-a106d721-7c6d-4e6a-b595-a7c4353fe606.png)

4) AI strategic command. You would have a unit representing an allied AI player and commands to the unit would be reinterpreted as requests for the AI to do something in a specific place. This is similar to the Red Alert 3 campaign UI where you can direct your AI ally, but again the benefit here is the UX of reusing native orders UI. Here's a mockup where the player directs an AI sitting in the middle to go through the bottom instead via an unsynced control unit:
![image](https://user-images.githubusercontent.com/2573076/221175571-7bd5e10f-de58-4ff2-a447-a2d58621c45c.png)

5) Warcraft style shops. Select the shop like a unit, rightclick to select buying unit, have items listed as commands on the command panel. This could also be done by having the shop be a real unit and sharing control, but that's an alternative with various pros and cons.
![image](https://user-images.githubusercontent.com/2573076/219031078-e33f3bab-2af9-4c26-8b74-697250cc8778.png)

6) limited control of ally units, in particular ZK strider hub would have an unsynced unit underneath that could allow placing nanoframes. Again, doable by allowing selection of the real unit but it's a different tradeoff. This is in many ways the same as the shop one above but I'm listing it separately because it's not just a modding thing, it would be desired in the actual ZK ( ZeroK-RTS/Zero-K#3630 ).
![image](https://user-images.githubusercontent.com/2573076/219031984-fe2de025-df0a-4804-b53e-86a4c7e0dc25.png)

7) get native tooltip and right-clickability on things like existing ghost site widgets, or on various in-world entities like lingering ground AoE effects like napalm or toxins (for example to see how long until they expire, or maybe to right-click them to remove like C&C Generals Ambulance removal of toxins/radiation), ditto for things like mexes / geospots / control points / those startpoint cones, or give a model etc to things like pickups that you can grab by running over them.

8) queue orders for units that are morphing to a different type, as if they were already that type (since that other type can have new abilities, for example a static unit can morph into a mobile one, or the morph is an upgrade and grants jump, etc). This includes things like a morph that splits the unit into two.

9) queue units in queued factories. Factories would be unsynced units when queued and before actually created, and then when created they would inherit the unsynced unit's queue.

10) queue orders for units leaving some sort of facility as if leaving a factory, without that facility being a factory (example: rally queue for planes leaving airpad, or for units leaving a teleport like ZK Djinn without the Djinn itself acting on that queue, or retreat points).

11) formations. Think ZK circle guard, or the supcom2 group UI. The unsynced unit represents the formation itself and the real units can have commands that put them in positions relative to the formation.

## Technicalities

This would be a very large task and a lot can go wrong (since everything currently assumes units and features are synced). Additionally there are many questions where the answer isn't obvious yet (if I select all units of a type by Ctrl+Z, does it select the unsynced ones as well? what does a widget do if none of the unitDefs provided by the game suffice for its purpose? etc). Some questions have seemingly obvious answers (what about featureID number pool? -> since the IDs should never be sent on network it can be drawn from a separate ID pool over 2^16) but reality will likely verify this. So perhaps if this gets taken up it would be ideal to do in parts so proof of concept could be made an issues discovered. Here's a suggestion for a bunch of specific implementation details:

* add `Spring.CreateUnsyncedFeature(featureDef, x, y, z, ...)` with the same interface as `CreateFeature` to UnsyncedCtrl. The name doesn't matter much, maybe it could have just `CreateFeature` since some unsynced functions already differ under the same name (for example GiveOrderToUnit), or maybe it could be something like CreateVirtualFeature, doesn't matter.
* add `Spring.SetUnsyncedFeatureHealth` and other control interfaces that mimic the real ones to UnsyncedCtrl. These can of course only control unsynced units.
* make unsynced `Spring.GetFeatureHealth` return whatever health is set (note, no "unsynced" in the name, this is the existing function that works with synced features as well. But only returns anything for unsynced units in unsynced contexts.). Ditto for the other getters. In particular, `Spring.GetAllFeatures` (and similar that return a list) returns unsynced features too.
* add some sort of `Spring.IsFeatureSynced(featureID)` -> nil (not valid at all), true (is synced), or false (unsynced). Cannot return false if ran from a synced context
* drawn normally and passed to `wupget:DrawFeatureBla`.
* intercepted by mouse raytraces, so has a tooltip, is passed to `wupget:DefaultCommand` and right-clicking produces an order request. If a synced unit gets a unit-targeted order targeted onto an unsynced unit, this gets passed to `wupget:UnitCommand` normally (or whichever was the callin before the command gets sent over the network) at which point the code should probably translate it to a real command; then the order gets dropped.
* is not subject to updates (so does not follow physics, does not do things like smoking if it's an unsynced geovent etc)
* is not counted in synced area-based code like AoE damage, does not respond to synced raytraces, etc

If that turns out usable then maybe it can get extended to units in a similar way. The main thing units do that features don't is that they have commandAI - they should produce a UnitCommand event but (regardless of whether the target is synced or another unsynced units) it goes directly to the unit's actual queue (as opposed to synced units that take a network roundtrip when given orders from unsynced; similar to how synced GiveOrder works) and the unit also doesn't act on the queue. Units also have a script, I'm not sure how to approach this but that is something to think about later.

Contributor guide

Open the contributing guide

Research direction

Start with UnsyncedCtrl and the proposed Spring.CreateUnsyncedFeature interface, then trace how synced units and features are represented, selected, rendered, raytraced, and exposed to callins. A first milestone should define and implement a proof of concept for unsynced features, including control, getters, lists, drawing, mouse interaction, and clear separation from synced simulation; validate it before extending the design to units.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
game-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.