Command Line Interface and API-GUI convergence?
See original GitHub issueIs your feature request related to a problem? Please describe. Managing large empires is tedious and painful. A lot of stuff, like build order, unit promotions, finding resources or freeing them up, is highly repetitive.
I like a slick UI as much as the next guy. But in an open-sourced program with clean code, it’s frustrating to have to spend several minutes and hundreds of mouse clicks to do something that could be described with a single line of scripting or with a reusable function.
Describe the solution you’d like It would be great to have a CLI that can be used to access all information and perform all actions that are currently available in the UI.
E.G.:
len(Empire.Cities)
to count the number of cities to better plan strategic resource use, instead of having to manually count in the Overview screen.
[city for city in Empire.Cities if any("Aluminium" in building.Consumes for building in city.Buildings)]
to get a list of all cities that have Spaceship Factories or Hydro Plants, instead of having to click into dozens of cities and scroll through the entire building list for each.
Empire.Cities.map((city) => city.Governor.SetPriority("Food"))
to tell all cities to stop assigning scientists and focus on population growth (once governors get implemented).
[city for city in Empire.Cities if city.Tiles.has("Aluminium")]
to figure out where your strategic resources are, instead of having to stare at the map for several minutes, checking all city centers separately, and still missing them.
UITools.Messages.ResourceDiscovery("Uranium", target="Mongolia")
to manually re-trigger the “You have discovered [X] sources of [Resource]!” notification, but have it target and cycle through resources on visible tiles near a foreign country, instead of having to spend several minutes staring at every tile and then finding out that you’ve still missed some after you’ve already declared and ended a war.
for i, city in enumerate(sorted(Empire.Cities, key=lambda city: -city.Stats.Production.Base)); do if i < Empire.Resources["Coal"]; then city.Production.prepend("Factory"); fi; city.Production.append("NukeSub"*3, "Cruiser"); done
to automatically build as many factories in your largest cities as you have coal resources, and queue up three nuclear submarines and one cruiser to be built in all cities.
buildorder = ["Monument", "Granary", ...]; for city in Empire.Cities: if not city.Production: for building in buildorder: try: city.Production.append(building)
to have cities follow a custom automatic build order.
UITools.Map.HighlightCoords(tile.Coord for tile in Map.Tiles if "Coal" in tile.Resources)
to display some sort of visual overlay for all visible coals resources, instead of again having to scour every tile by eye.
TurnData.GetMovements().map((move) => UITools.Map.DrawArrow(move.From, move.To, move.isAttack ? "Red" : "Blue"))
to display some sort of visual overlay that shows how every visible unit on the map moved last turn, with red arrows indicating attacks and blue arrows for regular movements.
Empire.Units.map((unit) => [unit, unit.Tile.GetNearestUnit(["Interception" ])]).filter(([unit, aa]) => !(aa.InRange(unit))).map(([unit, aa]) => UITools.Map.DrawArrow(unit.Tile.Coord, aa.Tile.Coord))
to locate all your own units that aren’t currently covered by any air defences, and draw a visual overlay on the map pointing them to the nearest AA units. (Or plug in the visible unit list of another empire to figure out where you can safely airstrike.)
To automatically promote all melee units to be generalized, and automatically promote all naval ranged units to maintain a 2:1 ratio of land and naval specialization:
PromotionRatios = {
("Naval", "Ranged"): {
("Bombardment I", "Bombardment II", "Range"): 2,
("Targeting I", "Targeting II", "Sentry"): 1
},
("Melee"): {
("Shock I", "Drill I", "Cover I", "Cover II"): 1
}
}
def GetUnitTypeSig(unit):
sig = []
for c, t, f in ((unit.Type.isNaval, "Naval", None), (unit.Type.isRanged, "Ranged", "Melee")):
s = t if c else f
if c and s:
sig.append(s)
return tuple(s)
def CheckUnitMatchesPromotions(unit, promotions):
return unit.Promotions and all(p in promotions for p in unit.Promotions)
def PromoteUnitTowards(unit, promotions):
for promo in promotions:
try:
unit.Promotions.Select(promo)
except XPError:
pass
def AutoPromoteAll():
unitsets = {sig: [] for sig in PromotionRatios}
for unit in Empire.Units:
sig = GetUnitTypeSig(unit)
if sig in unitsets:
unitsets[sig].append(unit)
for sig, units in unitsets.items():
targetratios = PromotionRatios[sig]
currentcounts = {promos: len([u for u in units if CheckUnitMatchesPromotions(unit, promos)]) for promos in targetratios}
for unit in units:
if unit.Promotions.available:
selectedpromos = min({promos: currentcounts[promos]/target for promos, target in targetratios.items()}.items(), key=lambda i: i[1])[0]
PromoteUnitTowards(unit, selectedpromos)
if CheckUnitMatchesPromotions(unit, selectedpromos):
currentcounts[selectedpromos] += 1
AutoPromoteAll()
To quickly distribute cruise missiles evenly across cities, submarines, and missile cruisers:
airbases = [...Empire.Cities, ...(Empire.Units.filter((unit) => unit.hasUnique("Can carry [] Missile units")))];
var ismissile = (unit) => unit.hasUnique("Self-destructs when attacking");
num_missiles = Empire.Units.filter(ismissile).length;
maxmissiles = Math.ceil(num_missiles/airbases.length);
for (let base of airbases) {
while (base.carriedUnits.filter(ismissile).length > maxmissiles) {
let missile = base.carriedUnits.find(ismissile);
missile.actions.doRebase([...missile.RebaseOptions].sort((baseoption) => baseoption.Units.filter(ismissile).length)[0]);
}
}
…You get the idea
Describe alternatives you’ve considered Massive UI-bloat to special-case all possible visualizations and automations.
Arbitrary code execution, and custom UI elements in the modding API, then making bindings and packing a Python interpreter or Javascript engine or something into a mod.
Fork of this project that adds this. Or binary mod for Sid Meier’s Civ V.
Additional context IMHO it would be cool if the internal API and exposed UI had a 1:1 mapping— If the console exposed some version of the same calls as are made by UI elements, and all checks for legal operations were shared between the console and UI, so there wouldn’t be anything cheaty about playing this way. (Being able to add duplicate buildings and previously being able to select incompatible social policies by clicking quickly suggests that definitions of legal actions are currently not separated from UI code?)
Triggering a drop-down with the backtick key, like in a lot of publishers’ games, would be a sensible way to expose it on Desktop I think. Maybe an on-screen button could be hidden by default, and toggleable in the Options menu, for mobile and mouse users.
With a flag to enable a separate set of illegal commands/cheats, I suppose this could also make it easier to test normal features? Cheats.Enable()
, Cheats.InstantProduction = 1
, Cheats.SpawnAtTile("Lancer")
, Empire.ActiveUnit.XP += 100
, etc. (Properties like .XP
would usually be available as well, but they would be read-only until cheats are turned on.)
If implemented, being able to define one (or multiple) start-up scripts/macros to populate the namespace with user-defined constants/functions would be a no-brainer.
An idiomatic and expressive high-level language would be ideal IMO. Gracefully handling null values and such would be good for mapping idempotent operations (E.G. selling a building, choosing a promotion, setting a focus) to lots of cities/units without having to either wrap in try
blocks or run .filter()
s. Small quirks for enhancing usability like coercing everything to the same capitalization and having reasonable aliases (“Nuclear Submarine”, “NukeSub”, “NukeSubmarine”, etc.) could be nice too.
Converging the player GUI and a CLI/API may also have other benefits— Being able to easily plug Unciv into a machine learning experiment, for example, or quickly prototype traditional AI behaviours.
Perhaps popular user scripts could be a source of proven candidates for reimplementation as part of the core game.
I know this is a very big ask, may not fit within the scope of this project, may require infeasible architectural changes, or simply might not interest any current developers.
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (3 by maintainers)
Top GitHub Comments
I see no reason why not 😃
cheats for unciv would absolutely be great