Compare commits

...

22 commits

Author SHA1 Message Date
Ralph Sennhauser
6b4ecbdc40
Create dedicated stylesheet for Atlas
Some checks are pending
checkrefs / lfscheck (push) Waiting to run
checkrefs / checkrefs (push) Waiting to run
lint / cppcheck (push) Waiting to run
lint / copyright (push) Waiting to run
lint / jenkinsfiles (push) Waiting to run
pre-commit / build (push) Waiting to run
This is meant as a central place to tweak layout per platform.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-15 21:27:42 +02:00
Ralph Sennhauser
149baf116b
Replace all use of POSIX truncate
Use `<filesystem>` instead of `truncate` and remove `truncate`
portability wrapper.

Add a helper function `StatusFromSystemError` to convert error_code to
`Status`.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-15 15:33:59 +02:00
Ralph Sennhauser
57f5b73458
Remove unused wrapper for POSIX rmdir
std::filesystem::remove can be used instead.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-15 15:33:59 +02:00
Ralph Sennhauser
518ed74496
Replace all use of POSIX unlink
Use `<filesystem>` instead of `unlink` and remove `unlink` portability
wrapper.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-15 15:33:59 +02:00
Vladislav Belov
4259c78150
Removes unused Get/SetDepthTextureBits methods
Some checks are pending
checkrefs / lfscheck (push) Waiting to run
checkrefs / checkrefs (push) Waiting to run
lint / cppcheck (push) Waiting to run
lint / copyright (push) Waiting to run
lint / jenkinsfiles (push) Waiting to run
pre-commit / build (push) Waiting to run
Methods were added in f903b83674.
Methods became unused in 12e2428495.
2026-06-15 00:35:04 +02:00
Ralph Sennhauser
71400e8045
Drop unused lowlevel functions
and associated types. Aka posix dirent.h abstraction.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-14 21:58:59 +02:00
Ralph Sennhauser
43e7dbc6da
Use std::filesystem for filesystem abstraction
Drop the use of lowlevel interfaces and consitently use the cpp
interface decupling it from systm dependent code.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-14 21:58:59 +02:00
phosit
2f2cbb96bf
Remove this.isDeserialized from BaseAI
Some checks are pending
checkrefs / lfscheck (push) Waiting to run
checkrefs / checkrefs (push) Waiting to run
lint / cppcheck (push) Waiting to run
lint / copyright (push) Waiting to run
lint / jenkinsfiles (push) Waiting to run
pre-commit / build (push) Waiting to run
`this.isDeserialized` is only required for AIs which `Deserialize`
function depends on the game state.
2026-06-14 17:52:54 +02:00
phosit
a4b580991b
Move this.turn to PetraBot
The `BaseAI` shouldn't make assumptions whether the AI runs every turn.
2026-06-14 17:52:54 +02:00
phosit
babe9e5c18
Remove this.events from AIs
`this.events` is only valid during one turn. When it is used in the next
turnn it might be outdated. So it makes no sense to keep it alive untill
the next turn.
2026-06-14 17:52:54 +02:00
phosit
cbce748b8c
Remove moduleName from Petras data.json
It was accidentally re-introduced in 88dc947b6a.
2026-06-14 13:21:26 +02:00
Ralph Sennhauser
1ab55e7f2e
Remove leftover code from wxCollapsiblePane use
There is a workaround for an old bug with the use of wxCollapsiblePane.
As the widget itself is no longer in use, just remove the now dead code.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-13 18:34:09 +02:00
Ralph Sennhauser
b4fe426963
Replace notebook with choicebook
The terrain selection notebook is painful to navigate with a large
amount of tabs. Further the control is broken on macOS. As such replace
the wxNotebook with a wxChoicebook.

Fixes: #8705
Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-13 16:57:32 +02:00
Ralph Sennhauser
8cb4f5e4a3
Fix negative content width GTK warnings in Atlas
Interdependent content width in terrain selection panel results in GTK
throwing warnings. Don't expand the grid cell content.

Also wrap the grid to keep spacing consistent and not variable for the
terrain buttons.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-13 13:38:44 +02:00
Ralph Sennhauser
9388692a47
Add bird view to Atlas
Fixes: #2657
Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-12 21:54:01 +02:00
Ralph Sennhauser
fa9584fdc0
Fix some Atlas header includes
iwyu 0.26 now want's full declaration of TYPE in case of
std::vector<TYPE>, which is good as it allows the header to be
self standing.

There are some other changes suggested in part of the headers not being
updated when they should have been and some due to changes in iwyu
itself.

Ref: #8086
Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-12 21:22:36 +02:00
Ralph Sennhauser
7f4377c086
Treat <wx/defs.h> as private header
Add the header to iwyu mappings as private as the wx documentation
doesn't suggest otherwise.

Ref: #8086
Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-12 21:22:36 +02:00
animus
1034b55037 Rename class names postifxed with "Man" to "Manager".
A regex-based
search across the codebase confirms there are no other instances
of "Man" that should be renamed to "Manager".
Variables were not renamed.
2026-06-12 18:35:32 +02:00
animus
b1627f5158 Remove redundant virtual keywords
This commit performs code cleanup to improve
code clarity and consistency:

Remove redundant 'virtual' keywords from methods that are already marked
with 'final' or 'override', as well as reducing redundant 'override
final's to 'final'.
2026-06-12 18:35:32 +02:00
Jonny McCullagh
917275d6cb Add more tips incl the Arsenal and Great Hall
Fixes #8914
2026-06-12 10:44:41 +02:00
mehmed-faheim-arslan
50e1f51755 Draw trainer section before tree section
Previously, TreeSection was drawn before TrainerSection.
Reversing the order ensures TrainerSection layout is resolved first,
which fixes scroll state being computed against an incomplete
structree page render.

Fixes #8893
2026-06-10 20:55:52 +02:00
mehmed-faheim-arslan
39b1311fac Allow players to set rally points on allied buildings
Instead of storing a single flat list of positions and data per
building, RallyPoint now stores them keyed by player ID. This lets
mutual allies independently set and display rally points on each
other's structures.

The GUI now allows selecting allied buildings with a rally point
and only shows the viewing player's own rally point data.
GuiInterface gets an OnUpdate handler to keep displayed positions
in sync when rally point targets move.

GetRallyPointCommands now takes raw position and data arrays instead
of a component reference. The network command field is also renamed
from "entities" to "structures".

Fixes #3115
2026-06-10 00:09:48 +02:00
134 changed files with 867 additions and 872 deletions

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c10d449ac14ca84e468309cfb7c360d694ac2d06dee03f76a32e33e6e2c99e4
size 421569

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0eef7e19416045c7c2a5cc7c03ef90a2dff0f667c451cec538ea8aebedd6e042
size 469669

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:546104d960caafa6d5b34202d5a61d41576e9063aa2b50427a8f24b20d49fd66
size 449456

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13042e6bbf40855741059c9ee3874f43832a805ca3361ab31b81f0c7f386ee1e
size 497581

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c9691682467622e4a52a1253a20b86ff0b89a9b31c2a12f9fce68e2735e4ebe
size 436276

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f8520c067e68a687dd15c9459e791f5737d6b7457142e58db7b5e86d80b848c
size 532484

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f82c6568f4d5e5013e68122a6e042bad37a3098d19b9a9289faac3c08cde880d
size 479929

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4563953d9d3f90318fd8526289206fbed2b9fa6c2accb06ac80b2d1983872169
size 522691

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c09534c41b36b971e7408ab7613542af0b53f5a3d0b9b1f2a151fc8ff1839781
size 457293

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f9660381b55c90597e32f893b88e5347050d83b3dfaf78b950f2bc3024e14afd
size 443508

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85512671818c8fd964c4b6224158480a21d00e4195d0ab6b36f1435151ec1e36
size 450454

View file

@ -199,6 +199,7 @@
{ "nick": "mattlott", "name": "Matt Lott" },
{ "nick": "maveric", "name": "Anton Protko" },
{ "nick": "mbusy", "name": "Maxime Busy" },
{ "nick": "mehmedarslan", "name": "Mehmed Faheim Arslan/Sai Kaushik" },
{ "nick": "Mentula" },
{ "nick": "Micnasty", "name": "Travis Gorkin" },
{ "name": "Mikołaj \"Bajter\" Korcz" },

View file

@ -53,8 +53,8 @@ class StructreePage extends ReferencePage
this.CivHistory.caption = this.civData[this.activeCiv].History || "";
const templateLists = this.TemplateLister.getTemplateLists(this.activeCiv);
this.TreeSection.draw(templateLists.structures, this.activeCiv);
this.TrainerSection.draw(templateLists.units, this.activeCiv);
this.TreeSection.draw(templateLists.structures, this.activeCiv);
}
}

View file

@ -0,0 +1,6 @@
ARSENAL
Construct Siege Engines, train Champion Infantry Crossbowmen.
Research Siege Engine technologies to improve unit capabilities.
Garrison Siege Engines to regenerate their health.
Available to all civilizations in City Phase.
Available to Macedonians in Town Phase, giving early access to Bolt Shooters.

View file

@ -0,0 +1,6 @@
CORRAL
Train animals for food.
Garrison up to 8 animals to gain a food trickle.
Each garrisoned animal adds its full food trickle.
Research Stockbreeding to breed domestic animals faster.
Available to all civilizations in Village Phase.

View file

@ -0,0 +1,4 @@
COUNCIL CHAMBER
Train Athenian Heroes and research special technologies.
Garrison Heroes inside to regenerate their health.
Available only to the Athenians.

View file

@ -0,0 +1,4 @@
GREAT HALL
Train Champion Axemen and Log Rams.
Available only to the Germans in Town Phase.
Use it to strengthen your army before City Phase.

View file

@ -0,0 +1,4 @@
GYMNASIUM
Train Champions, including Athenian Marines.
Research Iphicratean Reforms to unlock Athenian Marines at Docks and Triremes.
Available only to Athens.

View file

@ -0,0 +1,4 @@
HOPLITE TRADITION
Hoplites -25% training time, -50% promotion experience, and +10% health.
Research it at the Civic Center in Town Phase.
Available only to the Athenians and Spartans.

View file

@ -0,0 +1,4 @@
LIBRARY
Reduces structure costs by 15%.
Reduces structure build time by 15%.
Available to the Macedonians, Ptolemies and Seleucids.

View file

@ -0,0 +1,5 @@
MILITARY COLONY
Can be built only in own or neutral territory.
Available only to the Ptolemies and Seleucids in Town Phase.
Use it to extend your territory without building a full Civic Center.
Trains civ-specific Mercenary units for expansion and frontier defense.

View file

@ -0,0 +1,5 @@
REGICIDE
Choose the "Regicide" victory condition in the game setup.
You start the match with a Hero from your civilization.
Defeat an opponent by killing their Hero.
Garrison your Hero inside a structure for protection if "Hero Garrison" is enabled.

View file

@ -0,0 +1,5 @@
RELICS
Relics are catafalques that hold the remains of great leaders.
Each Relic provides a military or economic aura bonus to its owner.
Enable "Capture the Relic" in the game setup to win by controlling all Relics.
Use "Relic Count" and "Relic Duration" to set how many Relics spawn and how long they must be held.

View file

@ -0,0 +1,6 @@
TEMPLE OF ISIS
Train Ptolemaic Heroes and Healers.
Research healing technologies and special cult technologies.
Research Serapis Cult for a metal trickle.
Research Pharaonic Cult to improve hero health regeneration.
Available only to the Ptolemies in City Phase.

View file

@ -49,6 +49,12 @@
"civic_center.png"
]
},
{
"textFile": "corral.txt",
"imageFiles": [
"corral.png"
]
},
{
"textFile": "defense_tower.txt",
"imageFiles": [
@ -209,6 +215,12 @@
"embassies.png"
]
},
{
"textFile": "arsenal.txt",
"imageFiles": [
"arsenal.png"
]
},
{
"textFile": "experience.txt",
"imageFiles": [
@ -228,6 +240,12 @@
"formations.png"
]
},
{
"textFile": "great_hall.txt",
"imageFiles": [
"great_hall.png"
]
},
{
"textFile": "heroes.txt",
"imageFiles": [
@ -240,12 +258,24 @@
"loot.png"
]
},
{
"textFile": "library.txt",
"imageFiles": [
"library.png"
]
},
{
"textFile": "map_flare.txt",
"imageFiles": [
"map_flare.png"
]
},
{
"textFile": "military_colony.txt",
"imageFiles": [
"military_colony.png"
]
},
{
"textFile": "pikemen.txt",
"imageFiles": [
@ -362,6 +392,12 @@
"control_groups.png"
]
},
{
"textFile": "council_chamber.txt",
"imageFiles": [
"council_chamber.png"
]
},
{
"textFile": "default_formation.txt",
"imageFiles": [
@ -392,6 +428,18 @@
"freehand_position.png"
]
},
{
"textFile": "gymnasium.txt",
"imageFiles": [
"gymnasium.png"
]
},
{
"textFile": "hoplite_tradition.txt",
"imageFiles": [
"hoplite_tradition.png"
]
},
{
"textFile": "lighthouse.txt",
"imageFiles": [
@ -428,6 +476,18 @@
"achaemenid_architecture.png"
]
},
{
"textFile": "regicide.txt",
"imageFiles": [
"regicide.png"
]
},
{
"textFile": "relic.txt",
"imageFiles": [
"relic.png"
]
},
{
"textFile": "resource_lost.txt",
"imageFiles": [
@ -458,6 +518,12 @@
"syntagma.png"
]
},
{
"textFile": "temple_of_isis.txt",
"imageFiles": [
"temple_of_isis.png"
]
},
{
"textFile": "whales.txt",
"imageFiles": [

View file

@ -263,7 +263,7 @@ function determineAction(x, y, fromMiniMap)
if (!entState)
return undefined;
if (!selection.every(ownsEntity) &&
if (!selection.every(ent => ownsEntity(ent) || isMutualAllyBuilding(ent)) &&
!(g_SimState.players[g_ViewedPlayer] &&
g_SimState.players[g_ViewedPlayer].controlsAll))
return undefined;
@ -318,6 +318,13 @@ function ownsEntity(ent)
return entState && entState.player == g_ViewedPlayer;
}
function isMutualAllyBuilding(ent)
{
const entState = GetEntityState(ent);
return entState && entState.rallyPoint &&
g_Players[entState.player]?.isMutualAlly[g_ViewedPlayer];
}
function isAttackMovePressed()
{
return Engine.HotkeyIsPressed("session.attackmove") ||

View file

@ -331,6 +331,8 @@ async function init(initData, hotloadData)
resumeGame();
});
g_DiplomacyColors.updateDisplayedPlayerColors();
const promise = Promise.race([new Promise((_, reject) =>
{
if (g_IsNetworked)

View file

@ -1125,7 +1125,7 @@ var g_UnitActions =
Engine.PostNetworkCommand({
"type": "set-rallypoint",
"entities": selection,
"structures": selection,
"x": position.x,
"z": position.z,
"data": action.data,
@ -1355,7 +1355,7 @@ var g_UnitActions =
{
Engine.PostNetworkCommand({
"type": "unset-rallypoint",
"entities": selection
"structures": selection
});
// Remove displayed rally point

View file

@ -6,9 +6,6 @@ export function BaseAI(settings)
return;
this.player = settings.player;
// played turn, in case you don't want the AI to play every turn.
this.turn = 0;
}
/** Return a simple object (using no classes etc) that will be serialized into saved games */
@ -23,7 +20,6 @@ BaseAI.prototype.Serialize = function()
*/
BaseAI.prototype.Deserialize = function(data, sharedScript)
{
this.isDeserialized = true;
};
BaseAI.prototype.Init = function(state, playerID, sharedAI)
@ -49,14 +45,7 @@ BaseAI.prototype.CustomInit = function()
BaseAI.prototype.HandleMessage = function(state, playerID, sharedAI)
{
PlayerID = playerID;
this.events = sharedAI.events;
this.territoryMap = sharedAI.territoryMap;
if (this.isDeserialized)
{
this.Init(state, playerID, sharedAI);
this.isDeserialized = false;
}
this.OnUpdate(sharedAI);
};

View file

@ -996,13 +996,13 @@ export const Entity = Class({
"setRallyPoint": function(target, command)
{
const data = { "command": command, "target": target.id() };
Engine.PostCommand(PlayerID, { "type": "set-rallypoint", "entities": [this.id()], "x": target.position()[0], "z": target.position()[1], "data": data });
Engine.PostCommand(PlayerID, { "type": "set-rallypoint", "structures": [this.id()], "x": target.position()[0], "z": target.position()[1], "data": data });
return this;
},
"unsetRallyPoint": function()
{
Engine.PostCommand(PlayerID, { "type": "unset-rallypoint", "entities": [this.id()] });
Engine.PostCommand(PlayerID, { "type": "unset-rallypoint", "structures": [this.id()] });
return this;
},

View file

@ -9,6 +9,8 @@ export function PetraBot(settings)
{
BaseAI.call(this, settings);
// played turn, because Petra doesn't play every turn.
this.turn = 0;
this.playedTurn = 0;
this.elapsedTime = 0;
@ -89,17 +91,20 @@ PetraBot.prototype.CustomInit = function(gameState)
PetraBot.prototype.OnUpdate = function(sharedScript)
{
if (this.isDeserialized)
this.Init(state, playerID, sharedAI);
if (this.gameFinished || this.gameState.playerData.state == "defeated")
return;
for (const i in this.events)
for (const i in sharedScript.events)
{
if (i == "AIMetadata") // not used inside petra
continue;
if (this.savedEvents[i] !== undefined)
this.savedEvents[i] = this.savedEvents[i].concat(this.events[i]);
this.savedEvents[i] = this.savedEvents[i].concat(sharedScript.events[i]);
else
this.savedEvents[i] = this.events[i];
this.savedEvents[i] = sharedScript.events[i];
}
// Run the update every n turns, offset depending on player ID to balance the load

View file

@ -1,7 +1,6 @@
{
"name": "Petra Bot",
"description": "Petra is the default 0 A.D. AI bot. Please report issues to Wildfire Games.\n\nThe AI has six levels of difficulty: Sandbox, Very Easy, Easy, Medium, Hard, and Very Hard. Besides adjusting its strategy depending on the level, the AI also receives advantages or handycaps for certain rates, such as trade gain and efficiency of resource gathering. The AI can be quite challenging for beginners, so it is recommended to start with Very Easy or Easy until until you feel confident.",
"moduleName" : "PETRA",
"constructor": "PetraBot",
"filename": "_petrabot.js",
"useShared": true

View file

@ -397,7 +397,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
const cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
ret.rallyPoint = { "position": cmpRallyPoint.GetPositions()[0] }; // undefined or {x,z} object
ret.rallyPoint = { "position": cmpRallyPoint.GetPositions(player)[0] }; // undefined or {x,z} object
const cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
@ -1035,6 +1035,16 @@ GuiInterface.prototype.GetNonGaiaEntities = function()
return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetNonGaiaEntities();
};
/**
* @param {number} entity - The entityID to verify.
* @param {number} player - The playerID to check against.
* @return {boolean}.
*/
function IsOwnedByPlayerOrMutualAlly(entity, player)
{
return IsOwnedByPlayer(player, entity) || IsOwnedByMutualAllyOfPlayer(player, entity);
}
/**
* Displays the rally points of a given list of entities (carried in cmd.entities).
*
@ -1046,14 +1056,12 @@ GuiInterface.prototype.GetNonGaiaEntities = function()
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
const cmpPlayer = QueryPlayerIDInterface(player);
// If there are some rally points already displayed, first hide them.
for (const ent of this.entsRallyPointsDisplayed)
for (const { ent } of this.entsRallyPointsDisplayed)
{
const cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.SetDisplayed(false);
cmpRallyPointRenderer.Reset();
}
this.entsRallyPointsDisplayed = [];
@ -1071,11 +1079,8 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
if (!cmpRallyPoint)
continue;
// Verify the owner.
const cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
if (!IsOwnedByPlayerOrMutualAlly(ent, player))
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position.
@ -1083,8 +1088,8 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
if (cmd.x && cmd.z)
pos = cmd;
else
// May return undefined if no rally point is set.
pos = cmpRallyPoint.GetPositions()[0];
// may return undefined if no rally point is set.
pos = cmpRallyPoint.GetPositions(player)[0];
if (pos)
{
@ -1093,23 +1098,52 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
if ("queued" in cmd)
{
if (cmd.queued == true)
{
// check by re adding all existing positions before appending the new queued one.
const existingPositions = cmpRallyPoint.GetPositions(player);
for (const posi of existingPositions)
cmpRallyPointRenderer.AddPosition(new Vector2D(posi.x, posi.z));
cmpRallyPointRenderer.AddPosition(new Vector2D(pos.x, pos.z));
}
else
cmpRallyPointRenderer.SetPosition(new Vector2D(pos.x, pos.z));
}
else if (!cmpRallyPointRenderer.IsSet())
{
// Rebuild the renderer when not set (when reading saved game or in case of building update).
for (const posi of cmpRallyPoint.GetPositions())
const positions = cmpRallyPoint.GetPositions(player);
for (const posi of positions)
cmpRallyPointRenderer.AddPosition(new Vector2D(posi.x, posi.z));
}
cmpRallyPointRenderer.SetDisplayed(true);
// Remember which entities have their rally points displayed so we can hide them again.
this.entsRallyPointsDisplayed.push(ent);
this.entsRallyPointsDisplayed.push({ ent, player });
}
}
};
GuiInterface.prototype.OnUpdate = function()
{
for (const { ent, player } of this.entsRallyPointsDisplayed)
{
const cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (!cmpRallyPointRenderer)
continue;
const cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
const positions = cmpRallyPoint.GetPositions(player);
// Update renderer positions so the path follows moving targets.
for (let i = 0; i < positions.length; i++)
cmpRallyPointRenderer.UpdatePosition(i, new Vector2D(positions[i].x, positions[i].z));
}
};
GuiInterface.prototype.AddTargetMarker = function(player, cmd)
{
const ent = Engine.AddLocalEntity(cmd.template);

View file

@ -5,67 +5,68 @@ RallyPoint.prototype.Schema =
RallyPoint.prototype.Init = function()
{
this.pos = [];
this.data = [];
this.perPlayer = {};
};
RallyPoint.prototype.AddPosition = function(x, z)
RallyPoint.prototype.GetOwner = function()
{
this.pos.push({
"x": x,
"z": z
});
return Engine.QueryInterface(this.entity, IID_Ownership)?.GetOwner();
};
RallyPoint.prototype.HasPositions = function()
RallyPoint.prototype.AddPosition = function(x, z, player = this.GetOwner())
{
return this.pos.length > 0;
if (!this.perPlayer[player])
this.perPlayer[player] = { "pos": [], "data": [] };
this.perPlayer[player].pos.push({ "x": x, "z": z });
};
RallyPoint.prototype.HasPositions = function(player = this.GetOwner())
{
return !!this.perPlayer[player]?.pos.length;
};
RallyPoint.prototype.GetFirstPosition = function()
{
return this.pos.length ? Vector2D.from3D(this.pos[0]) : new Vector2D(-1, -1);
const pos = this.perPlayer[this.GetOwner()]?.pos;
return pos?.length ? Vector2D.from3D(pos[0]) : new Vector2D(-1, -1);
};
RallyPoint.prototype.GetPositions = function()
RallyPoint.prototype.GetPositions = function(player = this.GetOwner())
{
// Update positions for moving target entities
const playerEntry = this.perPlayer[player];
if (!playerEntry)
return [];
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
// We must not affect the simulation state here (modifications of the
// RallyPointRenderer are allowed though), so copy the state
var ret = [];
for (var i = 0; i < this.pos.length; i++)
// We must not affect the simulation state here, so copy the state
const ret = [];
for (let i = 0; i < playerEntry.pos.length; i++)
{
ret.push(this.pos[i]);
ret.push(playerEntry.pos[i]);
// Update the rallypoint coordinates if the target is alive
if (!this.data[i] || !this.data[i].target || !this.TargetIsAlive(this.data[i].target))
if (!playerEntry.data[i]?.target || !this.TargetIsAlive(playerEntry.data[i].target))
continue;
// and visible
if (cmpRangeManager && cmpOwnership &&
cmpRangeManager.GetLosVisibility(this.data[i].target, cmpOwnership.GetOwner()) != "visible")
// and visible to the player who set this rally point
if (cmpRangeManager &&
cmpRangeManager.GetLosVisibility(playerEntry.data[i].target, player) != "visible")
continue;
// Get the actual position of the target entity
var cmpPosition = Engine.QueryInterface(this.data[i].target, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
const cmpPosition = Engine.QueryInterface(playerEntry.data[i].target, IID_Position);
if (!cmpPosition?.IsInWorld())
continue;
var targetPosition = cmpPosition.GetPosition2D();
const targetPosition = cmpPosition.GetPosition2D();
if (!targetPosition)
continue;
if (this.pos[i].x == targetPosition.x && this.pos[i].z == targetPosition.y)
if (playerEntry.pos[i].x == targetPosition.x && playerEntry.pos[i].z == targetPosition.y)
continue;
ret[i] = { "x": targetPosition.x, "z": targetPosition.y };
var cmpRallyPointRenderer = Engine.QueryInterface(this.entity, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.UpdatePosition(i, targetPosition);
}
return ret;
@ -73,31 +74,24 @@ RallyPoint.prototype.GetPositions = function()
// Extra data for the rally point, should have a command property and then helpful data for that command
// See getActionInfo in gui/input.js
RallyPoint.prototype.AddData = function(data)
RallyPoint.prototype.AddData = function(data, player = this.GetOwner())
{
this.data.push(data);
if (!this.perPlayer[player])
this.perPlayer[player] = { "pos": [], "data": [] };
this.perPlayer[player].data.push(data);
};
// Returns an array with the data associated with this rally point. Each element has the structure:
// {"type": "walk/gather/garrison/...", "target": targetEntityId, "resourceType": "tree/fruit/ore/..."} where target
// and resourceType (specific resource type) are optional, also target may be an invalid entity, check for existence.
RallyPoint.prototype.GetData = function()
RallyPoint.prototype.GetData = function(player = this.GetOwner())
{
return this.data;
return this.perPlayer[player]?.data ?? [];
};
RallyPoint.prototype.Unset = function()
RallyPoint.prototype.Unset = function(player = this.GetOwner())
{
this.pos = [];
this.data = [];
};
RallyPoint.prototype.Reset = function()
{
this.Unset();
var cmpRallyPointRenderer = Engine.QueryInterface(this.entity, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.Reset();
delete this.perPlayer[player];
};
/**
@ -107,50 +101,49 @@ RallyPoint.prototype.Reset = function()
*/
RallyPoint.prototype.OrderToRallyPoint = function(entity, ignore = [])
{
const cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
return;
const owner = cmpOwnership.GetOwner();
const cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership);
if (!cmpEntOwnership || cmpEntOwnership.GetOwner() != owner)
if (!cmpEntOwnership)
return;
const entOwner = cmpEntOwnership.GetOwner();
if (!this.HasPositions(entOwner))
return;
const commands = GetRallyPointCommands(this, [entity]);
const playerEntry = this.perPlayer[entOwner];
const commands = GetRallyPointCommands(playerEntry.pos, playerEntry.data, [entity]);
if (!commands.length ||
commands[0].target == this.entity && ignore.includes(commands[0].type))
return;
for (const command of commands)
ProcessCommand(owner, command);
ProcessCommand(entOwner, command);
};
RallyPoint.prototype.OnGlobalEntityRenamed = function(msg)
{
for (const data of this.data)
{
if (!data)
continue;
if (data.target && data.target == msg.entity)
data.target = msg.newentity;
if (data.source && data.source == msg.entity)
data.source = msg.newentity;
}
for (const playerEntry of Object.values(this.perPlayer))
for (const data of playerEntry.data)
{
if (data?.target == msg.entity)
data.target = msg.newentity;
if (data?.source == msg.entity)
data.source = msg.newentity;
}
if (msg.entity != this.entity)
return;
const cmpRallyPointNew = Engine.QueryInterface(msg.newentity, IID_RallyPoint);
if (cmpRallyPointNew)
{
const rallyCoords = this.GetPositions();
const rallyData = this.GetData();
for (let i = 0; i < rallyCoords.length; ++i)
for (const player in this.perPlayer)
{
cmpRallyPointNew.AddPosition(rallyCoords[i].x, rallyCoords[i].z);
cmpRallyPointNew.AddData(rallyData[i]);
const playerEntry = this.perPlayer[player];
for (let i = 0; i < playerEntry.pos.length; ++i)
{
cmpRallyPointNew.AddPosition(playerEntry.pos[i].x, playerEntry.pos[i].z, +player);
cmpRallyPointNew.AddData(playerEntry.data[i], +player);
}
}
}
};
RallyPoint.prototype.OnOwnershipChanged = function(msg)
@ -159,7 +152,7 @@ RallyPoint.prototype.OnOwnershipChanged = function(msg)
if (msg.from == INVALID_PLAYER || msg.to == INVALID_PLAYER)
return;
this.Reset();
this.perPlayer = {};
};
/**

View file

@ -229,7 +229,7 @@ Trainer.prototype.Item.prototype.Spawn = function()
const cmpRallyPoint = Engine.QueryInterface(this.trainer, IID_RallyPoint);
if (cmpRallyPoint)
{
const data = cmpRallyPoint.GetData()[0];
const data = cmpRallyPoint.GetData(this.player)[0];
if (data?.target && data.target == this.trainer && data.command == "garrison")
autoGarrison = true;
}
@ -294,7 +294,7 @@ Trainer.prototype.Item.prototype.Spawn = function()
}
if (spawnedEnts.length && cmpRallyPoint)
for (const com of GetRallyPointCommands(cmpRallyPoint, spawnedEnts))
for (const com of GetRallyPointCommands(cmpRallyPoint.GetPositions(this.player), cmpRallyPoint.GetData(this.player), spawnedEnts))
{
// Tag this command as coming from a rally point
com.fromRallyPoint = true;

View file

@ -8,6 +8,7 @@ function initialRallyPointTest(test_function)
ResetState();
const entityID = 123;
AddMock(entityID, IID_Ownership, { "GetOwner": () => 1 });
const cmpRallyPoint = ConstructComponent(entityID, "RallyPoint", {});
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetData(), []);
@ -50,12 +51,6 @@ initialRallyPointTest((cmpRallyPoint) =>
return true;
});
initialRallyPointTest((cmpRallyPoint) =>
{
cmpRallyPoint.Reset();
return true;
});
// Construction
initialRallyPointTest((cmpRallyPoint) =>
{
@ -83,3 +78,135 @@ initialRallyPointTest((cmpRallyPoint) =>
cmpRallyPoint.OnOwnershipChanged({ "from": 2, "to": 0 });
return true;
});
// Per-player rally point tests
{
ResetState();
const entityID = 123;
let ownerPlayer = 1;
AddMock(entityID, IID_Ownership, { "GetOwner": () => ownerPlayer });
const cmpRallyPoint = ConstructComponent(entityID, "RallyPoint", {});
const player2 = 2;
const player3 = 3;
// Initially no per-player positions
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player2), []);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetData(player2), []);
TS_ASSERT(!cmpRallyPoint.HasPositions(player2));
// Add per-player rally point for player 2
cmpRallyPoint.AddPosition(10, 20, player2);
cmpRallyPoint.AddData({ "command": "walk" }, player2);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player2), [{ "x": 10, "z": 20 }]);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetData(player2), [{ "command": "walk" }]);
TS_ASSERT(cmpRallyPoint.HasPositions(player2));
// Add a second waypoint for player 2
cmpRallyPoint.AddPosition(30, 40, player2);
cmpRallyPoint.AddData({ "command": "garrison" }, player2);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player2),
[{ "x": 10, "z": 20 }, { "x": 30, "z": 40 }]);
// Player 3 is unaffected
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player3), []);
TS_ASSERT(!cmpRallyPoint.HasPositions(player3));
// Add per-player rally point for player 3
cmpRallyPoint.AddPosition(50, 60, player3);
cmpRallyPoint.AddData({ "command": "walk" }, player3);
TS_ASSERT(cmpRallyPoint.HasPositions(player3));
// Unset clears player 2 positions and data
cmpRallyPoint.Unset(player2);
TS_ASSERT(!cmpRallyPoint.HasPositions(player2));
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player2), []);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetData(player2), []);
// Player 3 is unaffected
TS_ASSERT(cmpRallyPoint.HasPositions(player3));
// Unset removes player 3 entry
cmpRallyPoint.Unset(player3);
TS_ASSERT(!cmpRallyPoint.HasPositions(player3));
// Per-player data is cleared on ownership change
cmpRallyPoint.AddPosition(10, 20, player2);
cmpRallyPoint.AddData({ "command": "walk" }, player2);
TS_ASSERT(cmpRallyPoint.HasPositions(player2));
cmpRallyPoint.OnOwnershipChanged({ "from": 1, "to": 2 });
ownerPlayer = 2;
TS_ASSERT(!cmpRallyPoint.HasPositions(player2));
// The owner's rally point entry does not affect allied players' entries
cmpRallyPoint.AddPosition(100, 200);
cmpRallyPoint.AddData({ "command": "walk" });
cmpRallyPoint.AddPosition(300, 400, player3);
cmpRallyPoint.AddData({ "command": "walk" }, player3);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(), [{ "x": 100, "z": 200 }]);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player3), [{ "x": 300, "z": 400 }]);
// Unset does not affect per-player data
cmpRallyPoint.Unset();
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(), []);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player3), [{ "x": 300, "z": 400 }]);
}
// Ownership change construction/destruction preserves per-player data
{
ResetState();
const entityID = 123;
const cmpRallyPoint = ConstructComponent(entityID, "RallyPoint", {});
const player2 = 2;
cmpRallyPoint.AddPosition(10, 20, player2);
cmpRallyPoint.AddData({ "command": "walk" }, player2);
// Construction: from INVALID_PLAYER should not clear per-player data
cmpRallyPoint.OnOwnershipChanged({ "from": INVALID_PLAYER, "to": 1 });
TS_ASSERT(cmpRallyPoint.HasPositions(player2));
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player2), [{ "x": 10, "z": 20 }]);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetData(player2), [{ "command": "walk" }]);
// Destruction: to INVALID_PLAYER should not clear per-player data
cmpRallyPoint.OnOwnershipChanged({ "from": 1, "to": INVALID_PLAYER });
TS_ASSERT(cmpRallyPoint.HasPositions(player2));
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPoint.GetPositions(player2), [{ "x": 10, "z": 20 }]);
}
// OnGlobalEntityRenamed migrates per-player rally point data to the new entity
{
ResetState();
const oldEntityID = 123;
const newEntityID = 456;
const player2 = 2;
const player3 = 3;
AddMock(oldEntityID, IID_Ownership, { "GetOwner": () => 1 });
AddMock(newEntityID, IID_Ownership, { "GetOwner": () => 1 });
const cmpRallyPointOld = ConstructComponent(oldEntityID, "RallyPoint", {});
const cmpRallyPointNew = ConstructComponent(newEntityID, "RallyPoint", {});
cmpRallyPointOld.AddPosition(100, 200);
cmpRallyPointOld.AddData({ "command": "walk" });
cmpRallyPointOld.AddPosition(10, 20, player2);
cmpRallyPointOld.AddData({ "command": "walk" }, player2);
cmpRallyPointOld.AddPosition(30, 40, player3);
cmpRallyPointOld.AddData({ "command": "garrison" }, player3);
cmpRallyPointOld.OnGlobalEntityRenamed({ "entity": oldEntityID, "newentity": newEntityID });
// New entity receives owner and per-player rally point data
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPointNew.GetPositions(), [{ "x": 100, "z": 200 }]);
TS_ASSERT(cmpRallyPointNew.HasPositions(player2));
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPointNew.GetPositions(player2), [{ "x": 10, "z": 20 }]);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPointNew.GetData(player2), [{ "command": "walk" }]);
TS_ASSERT(cmpRallyPointNew.HasPositions(player3));
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPointNew.GetPositions(player3), [{ "x": 30, "z": 40 }]);
TS_ASSERT_UNEVAL_EQUALS(cmpRallyPointNew.GetData(player3), [{ "command": "garrison" }]);
// Rename for an unrelated entity does not migrate to new entity
ResetState();
const cmpRP1 = ConstructComponent(oldEntityID, "RallyPoint", {});
const cmpRP2 = ConstructComponent(newEntityID, "RallyPoint", {});
cmpRP1.AddPosition(10, 20, player2);
cmpRP1.OnGlobalEntityRenamed({ "entity": 999, "newentity": newEntityID });
TS_ASSERT(!cmpRP2.HasPositions(player2));
}

View file

@ -434,27 +434,31 @@ var g_Commands = {
"set-rallypoint": function(player, cmd, data)
{
for (const ent of data.entities)
const structures = FilterEntityListWithAllies(cmd.structures || [], player, data.controlAllUnits);
for (const structure of structures)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
if (!cmd.queued)
cmpRallyPoint.Unset();
const cmpRallyPoint = Engine.QueryInterface(structure, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
cmpRallyPoint.AddPosition(cmd.x, cmd.z);
cmpRallyPoint.AddData(clone(cmd.data));
}
if (!cmd.queued)
cmpRallyPoint.Unset(player);
cmpRallyPoint.AddPosition(cmd.x, cmd.z, player);
cmpRallyPoint.AddData(clone(cmd.data), player);
}
},
"unset-rallypoint": function(player, cmd, data)
{
for (const ent of data.entities)
const structures = FilterEntityListWithAllies(cmd.structures || [], player, data.controlAllUnits);
for (const structure of structures)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
cmpRallyPoint.Reset();
const cmpRallyPoint = Engine.QueryInterface(structure, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
cmpRallyPoint.Unset(player);
}
},

View file

@ -1,18 +1,16 @@
// Returns an array of commands suitable for ProcessCommand() based on the rally point data.
// This assumes that the rally point has a valid position.
function GetRallyPointCommands(cmpRallyPoint, spawnedEnts)
function GetRallyPointCommands(rallyPos, data, spawnedEnts)
{
const data = cmpRallyPoint.GetData();
const rallyPos = cmpRallyPoint.GetPositions();
const ret = [];
for (let i = 0; i < rallyPos.length; ++i)
{
// Look and see if there is a command in the rally point data, otherwise just walk there.
let command = data[i] && data[i].command ? data[i].command : "walk";
let command = data[i]?.command ?? "walk";
// If a target was set and the target no longer exists, or no longer
// has a valid position, then just walk to the rally point.
if (data[i] && data[i].target)
if (data[i]?.target)
{
const cmpPosition = Engine.QueryInterface(data[i].target, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -169,52 +169,52 @@ protected:
// BaseProxyHandler interface below
// Handler for `object.x`
virtual bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const override final;
bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const final;
// Handler for `object.x = y;`
virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
JS::HandleValue receiver, JS::ObjectOpResult& result) const final;
// Handler for `delete object.x;`
virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const override final;
bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const final;
// The following methods are not provided by BaseProxyHandler.
// We provide defaults that do nothing (some raise JS exceptions).
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) const override final;
bool getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) const final;
// Throw an exception is JS code attempts defining a property.
virtual bool defineProperty(JSContext*, JS::HandleObject /*proxy*/, JS::HandleId,
bool defineProperty(JSContext*, JS::HandleObject /*proxy*/, JS::HandleId,
JS::Handle<JS::PropertyDescriptor>, JS::ObjectOpResult& /*result*/) const override
{
return false;
}
virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override final;
bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const final;
// Nothing to enumerate.
virtual bool enumerate(JSContext*, JS::HandleObject /*proxy*/,
bool enumerate(JSContext*, JS::HandleObject /*proxy*/,
JS::MutableHandleIdVector /*props*/) const override
{
return true;
}
// Throw an exception is JS attempts to query the prototype.
virtual bool getPrototypeIfOrdinary(JSContext*, JS::HandleObject /*proxy*/, bool* /*isOrdinary*/,
bool getPrototypeIfOrdinary(JSContext*, JS::HandleObject /*proxy*/, bool* /*isOrdinary*/,
JS::MutableHandleObject /*protop*/) const override
{
return false;
}
// Throw an exception - no prototype to set.
virtual bool setImmutablePrototype(JSContext*, JS::HandleObject /*proxy*/,
bool setImmutablePrototype(JSContext*, JS::HandleObject /*proxy*/,
bool* /*succeeded*/) const override
{
return false;
}
// We are not extensible.
virtual bool preventExtensions(JSContext*, JS::HandleObject /*proxy*/,
bool preventExtensions(JSContext*, JS::HandleObject /*proxy*/,
JS::ObjectOpResult& /*result*/) const override
{
return true;
}
virtual bool isExtensible(JSContext*, JS::HandleObject /*proxy*/, bool* extensible) const override
bool isExtensible(JSContext*, JS::HandleObject /*proxy*/, bool* extensible) const override
{
*extensible = false;
return true;

View file

@ -87,23 +87,23 @@ class MapCache : public GUIProxyProps
public:
virtual ~MapCache() {};
virtual bool has(const std::string& name) const override
bool has(const std::string& name) const override
{
return m_Functions.find(name) != m_Functions.end();
}
virtual JSObject* get(const std::string& name) const override
JSObject* get(const std::string& name) const override
{
return m_Functions.at(name).get();
}
virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
{
m_Functions[name].init(rq.cx, JS_GetFunctionObject(function));
return true;
}
virtual std::vector<std::string_view> getPropsNames() const override
std::vector<std::string_view> getPropsNames() const override
{
std::vector<std::string_view> result;
result.reserve(m_Functions.size());

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -141,7 +141,7 @@ public:
return mask;
}
virtual bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const override
bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const override
{
if (m_Data.empty())
return false;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -26,28 +26,23 @@
#include "precompiled.h"
#include "file_system.h"
#include "lib/debug.h"
#include "lib/file/file_system.h"
#include "lib/posix/posix_filesystem.h"
#include "lib/sysdep/filesystem.h"
#include "lib/sysdep/os.h"
#include <boost/version.hpp>
#include <cerrno>
#include <cstring>
#include <chrono>
#include <filesystem>
#include <memory>
#include <string>
struct WDIR;
bool DirectoryExists(const OsPath& path)
{
WDIR* dir = wopendir(path);
if(dir)
try
{
wclosedir(dir);
return true;
return std::filesystem::is_directory(path.string());
}
catch (std::filesystem::filesystem_error& err)
{
debug_printf("DirectoryExists: failed to check if directory '%s' exists, reason: %s\n", path.string8().c_str(), err.what());
}
return false;
}
@ -55,158 +50,152 @@ bool DirectoryExists(const OsPath& path)
bool FileExists(const OsPath& pathname)
{
struct stat s;
const bool exists = wstat(pathname, &s) == 0;
return exists;
try
{
return std::filesystem::is_regular_file(pathname.string());
}
catch (std::filesystem::filesystem_error& err)
{
debug_printf("FileExists: failed to check if file '%s' exists, reason: %s\n", pathname.string8().c_str(), err.what());
}
return false;
}
u64 FileSize(const OsPath& pathname)
{
struct stat s;
ENSURE(wstat(pathname, &s) == 0);
return s.st_size;
try
{
return static_cast<u64>(std::filesystem::file_size(pathname.string()));
}
catch (std::filesystem::filesystem_error& err)
{
debug_printf("FileSize: failed to get filesize for '%s', reason: %s\n", pathname.string8().c_str(), err.what());
}
return 0;
}
Status GetFileInfo(const OsPath& pathname, CFileInfo* pPtrInfo)
{
errno = 0;
struct stat s;
memset(&s, 0, sizeof(s));
if(wstat(pathname, &s) != 0)
WARN_RETURN(StatusFromErrno());
*pPtrInfo = CFileInfo(pathname.Filename(), s.st_size, s.st_mtime);
try
{
const std::filesystem::path path{pathname.string()};
*pPtrInfo = CFileInfo(path.filename().wstring(), static_cast<u64>(std::filesystem::file_size(path)),
static_cast<time_t>(std::chrono::duration_cast<std::chrono::seconds>(std::filesystem::last_write_time(path).time_since_epoch()).count()));
}
catch (std::filesystem::filesystem_error& err)
{
debug_printf("GetFileInfo: failed to get file info for '%s', reason: %s\n", pathname.string8().c_str(), err.what());
return ERR::EXCEPTION;
}
return INFO::OK;
}
struct DirDeleter
{
void operator()(WDIR* osDir) const
{
const int ret = wclosedir(osDir);
ENSURE(ret == 0);
}
};
Status GetDirectoryEntries(const OsPath& path, CFileInfos* files, DirectoryNames* subdirectoryNames)
{
// open directory
errno = 0;
WDIR* pDir = wopendir(path);
if(!pDir)
return StatusFromErrno(); // NOWARN
std::shared_ptr<WDIR> osDir(pDir, DirDeleter());
for(;;)
try
{
errno = 0;
struct wdirent* osEnt = wreaddir(osDir.get());
if(!osEnt)
for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(path.string()))
{
// no error, just no more entries to return
if(!errno)
return INFO::OK;
WARN_RETURN(StatusFromErrno());
}
for(size_t i = 0; osEnt->d_name[i] != '\0'; i++)
RETURN_STATUS_IF_ERR(Path::Validate(osEnt->d_name[i]));
const std::wstring_view name{osEnt->d_name};
// get file information (mode, size, mtime)
struct stat s;
#if OS_WIN
// .. return wdirent directly (much faster than calling stat).
RETURN_STATUS_IF_ERR(wreaddir_stat_np(osDir.get(), &s));
#else
// .. call regular stat().
errno = 0;
const OsPath pathname = path / OsPath(osEnt->d_name);
if(wstat(pathname, &s) != 0)
{
if(errno == ENOENT)
if (entry.is_directory() && entry.path().filename() != "." && entry.path().filename() != ".." && subdirectoryNames)
{
// TODO: This should be displayed to the user as a LOGWARNING when this code is
// moved to ps/
debug_printf("The path \"%s\" cannot be found. It is probably a dangling link "
"pointing to a non-existent path.\n", pathname.string8().c_str());
continue;
subdirectoryNames->emplace_back(entry.path().filename());
}
else if (entry.is_regular_file() && files)
{
files->emplace_back(entry.path().filename().wstring(), static_cast<u64>(entry.file_size()),
static_cast<time_t>(std::chrono::duration_cast<std::chrono::seconds>(entry.last_write_time().time_since_epoch()).count()));
}
WARN_RETURN(StatusFromErrno());
}
#endif
if(files && S_ISREG(s.st_mode))
files->emplace_back(osEnt->d_name, s.st_size, s.st_mtime);
else if(subdirectoryNames && S_ISDIR(s.st_mode) && name != L"." && name != L"..")
subdirectoryNames->emplace_back(osEnt->d_name);
}
catch (std::filesystem::filesystem_error& err)
{
debug_printf("GetDirectoryEntries: failed to get directory entries for'%s', reason: %s\n", path.string8().c_str(), err.what());
return ERR::EXCEPTION;
}
return INFO::OK;
}
namespace
{
std::filesystem::perms ModeTToPerms(mode_t mode)
{
using std::filesystem::perms;
perms perm{perms::none};
if (mode | S_IRUSR)
perm |= perms::owner_read;
if (mode | S_IWUSR)
perm |= perms::owner_write;
if (mode | S_IXUSR)
perm |= perms::owner_exec;
if (mode | S_IRGRP)
perm |= perms::group_read;
if (mode | S_IWGRP)
perm |= perms::group_write;
if (mode | S_IXGRP)
perm |= perms::group_exec;
if (mode | S_IROTH)
perm |= perms::others_read;
if (mode | S_IWOTH)
perm |= perms::others_write;
if (mode | S_IXOTH)
perm |= perms::others_exec;
return perm;
}
Status CreateDirectoriesImpl(const std::filesystem::path& path, const std::filesystem::perms& perms)
{
if (std::filesystem::exists(path))
return ERR::FAIL;
if (!std::filesystem::is_directory(path.parent_path()))
{
const Status status = CreateDirectoriesImpl(path.parent_path(), perms);
if (status != INFO::OK)
return status;
}
std::filesystem::create_directory(path);
std::filesystem::permissions(path, perms);
return INFO::OK;
}
} // namespace
Status CreateDirectories(const OsPath& path, mode_t mode, bool breakpoint)
{
if(path.empty())
return INFO::OK;
struct stat s;
if(wstat(path, &s) == 0)
try
{
if(!S_ISDIR(s.st_mode)) // encountered a file
WARN_RETURN(ERR::FAIL);
return INFO::OK;
return CreateDirectoriesImpl(std::filesystem::path(path.string()), ModeTToPerms(mode));
}
// If we were passed a path ending with '/', strip the '/' now so that
// we can consistently use Parent to find parent directory names
if(path.IsDirectory())
return CreateDirectories(path.Parent(), mode, breakpoint);
RETURN_STATUS_IF_ERR(CreateDirectories(path.Parent(), mode));
errno = 0;
if(wmkdir(path, mode) != 0)
catch (std::filesystem::filesystem_error& err)
{
debug_printf("CreateDirectories: failed to mkdir %s (mode %d)\n", path.string8().c_str(), mode);
debug_printf("CreateDirectories: failed to create directories '%s', reason: %s\n", path.string8().c_str(), err.what());
if (breakpoint)
WARN_RETURN(StatusFromErrno());
else
return StatusFromErrno();
WARN_RETURN(ERR::EXCEPTION);
else
return ERR::EXCEPTION;
}
return INFO::OK;
}
Status DeleteDirectory(const OsPath& path)
{
// note: we have to recursively empty the directory before it can
// be deleted (required by Windows and POSIX rmdir()).
CFileInfos files; DirectoryNames subdirectoryNames;
RETURN_STATUS_IF_ERR(GetDirectoryEntries(path, &files, &subdirectoryNames));
// delete files
for(size_t i = 0; i < files.size(); i++)
try
{
const OsPath pathname = path / files[i].Name();
errno = 0;
if(wunlink(pathname) != 0)
WARN_RETURN(StatusFromErrno());
std::filesystem::remove_all(path.string());
}
catch (std::filesystem::filesystem_error& err)
{
debug_printf("DeleteDirectory: failed to delete directory '%s', reason: %s\n", path.string8().c_str(), err.what());
return ERR::EXCEPTION;
}
// recurse over subdirectoryNames
for(size_t i = 0; i < subdirectoryNames.size(); i++)
RETURN_STATUS_IF_ERR(DeleteDirectory(path / subdirectoryNames[i]));
errno = 0;
if(wrmdir(path) != 0)
WARN_RETURN(StatusFromErrno());
return INFO::OK;
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -38,7 +38,7 @@
#include "lib/posix/posix_aio.h" // LIO_READ, LIO_WRITE
#include "lib/posix/posix_types.h"
#include "lib/status.h"
#include "lib/sysdep/filesystem.h" // wtruncate
#include "lib/sysdep/filesystem.h"
#include "lib/sysdep/os.h"
#include "lib/sysdep/rtl.h"
#include "lib/types.h"
@ -47,7 +47,9 @@
#include <cstddef>
#include <cstdint>
#include <fcntl.h>
#include <filesystem>
#include <memory>
#include <system_error>
namespace ERR
{
@ -332,9 +334,12 @@ static inline Status Store(const OsPath& pathname, const void* data, size_t size
RETURN_STATUS_IF_ERR(io::Run(op, p, completedHook, issueHook));
file.Close(); // (required by wtruncate)
file.Close(); // (required by resize_file)
RETURN_STATUS_IF_ERR(wtruncate(pathname, size));
std::error_code ec{};
std::filesystem::resize_file(pathname.string(), size, ec);
if (ec)
return StatusFromSystemError(ec);
return INFO::OK;
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -100,6 +100,16 @@ int ErrnoFromStatus(Status status)
return EPERM;
}
Status StatusFromSystemError(std::error_code& ec)
{
if (!ec)
return INFO::OK;
std::error_condition cond = ec.default_error_condition();
if (cond.category() != std::generic_category())
return ERR::FAIL;
const StatusDefinition* def = DefinitionFromErrno(cond.value());
return def ? def->status : ERR::FAIL;
}
Status StatusFromErrno()
{

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -165,6 +165,7 @@ To summarize: +/-1SHHCC (S=subsystem, HH=header, CC=code number)
#include "lib/types.h"
#include <cstddef>
#include <system_error>
// an integral type allows defining error codes in separate headers,
// but is not as type-safe as an enum. use Lint's 'strong type' checking
@ -246,6 +247,11 @@ extern int ErrnoFromStatus(Status status);
**/
extern Status StatusFromErrno();
/**
* @return Status equivalent of error_code, or ERR::FAIL if there's no equivalent.
*/
extern Status StatusFromSystemError(std::error_code& ec);
// note: other conversion routines (e.g. to/from Win32) are implemented in
// the corresponding modules to keep this header portable.

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -31,32 +31,6 @@
#include "lib/posix/posix_filesystem.h" // mode_t
//
// dirent.h
//
struct WDIR;
struct wdirent
{
// note: SUSv3 describes this as a "char array" but of unspecified size.
// we declare as a pointer to avoid having to copy the string.
wchar_t* d_name;
};
extern WDIR* wopendir(const OsPath& path);
extern wdirent* wreaddir(WDIR*);
// return status for the file returned by the last successful
// wreaddir call from the given directory stream.
// currently sets st_size, st_mode, and st_mtime; the rest are zeroed.
// non-portable, but considerably faster than stat(). used by dir_ForEachSortedEntry.
extern int wreaddir_stat_np(WDIR*, struct stat*);
extern int wclosedir(WDIR*);
//
// fcntl.h
//
@ -82,22 +56,6 @@ extern int wopen(const OsPath& pathname, int oflag, mode_t mode);
extern int wclose(int fd);
//
// unistd.h
//
// waio requires offsets and sizes to be multiples of the sector size.
// to allow arbitrarily sized files, we truncate them after I/O.
// however, ftruncate cannot be used since it is also subject to the
// sector-alignment requirement. instead, the file must be closed and
// this function called.
int wtruncate(const OsPath& pathname, off_t length);
int wunlink(const OsPath& pathname);
int wrmdir(const OsPath& path);
//
// stdlib.h
//

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -42,13 +42,6 @@
#include <fcntl.h>
#include <string>
struct WDIR
{
DIR* d;
wchar_t name[PATH_MAX];
wdirent ent;
};
#if OS_ANDROID
// The Crystax NDK seems to do weird things with opendir etc.
@ -84,36 +77,6 @@ void init_libc() { }
#endif
WDIR* wopendir(const OsPath& path)
{
init_libc();
DIR* d = opendir(OsString(path).c_str());
if(!d)
return 0;
WDIR* wd = new WDIR;
wd->d = d;
wd->name[0] = '\0';
wd->ent.d_name = wd->name;
return wd;
}
struct wdirent* wreaddir(WDIR* wd)
{
dirent* ent = readdir(wd->d);
if(!ent)
return 0;
wcscpy_s(wd->name, ARRAY_SIZE(wd->name), OsPath(ent->d_name).string().c_str());
return &wd->ent;
}
int wclosedir(WDIR* wd)
{
int ret = closedir(wd->d);
delete wd;
return ret;
}
int wopen(const OsPath& pathname, int oflag)
{
ENSURE(!(oflag & O_CREAT));
@ -130,22 +93,6 @@ int wclose(int fd)
return close(fd);
}
int wtruncate(const OsPath& pathname, off_t length)
{
return truncate(OsString(pathname).c_str(), length);
}
int wunlink(const OsPath& pathname)
{
return unlink(OsString(pathname).c_str());
}
int wrmdir(const OsPath& path)
{
return rmdir(OsString(path).c_str());
}
int wrename(const OsPath& pathnameOld, const OsPath& pathnameNew)
{
return rename(OsString(pathnameOld).c_str(), OsString(pathnameNew).c_str());

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -130,7 +130,7 @@ extern Status waio_close(int fd);
// @param size is rounded up to a multiple of maxSectorSize (required by
// SetEndOfFile; this could be avoided by using the undocumented
// NtSetInformationFile or SetFileInformationByHandle on Vista and later).
// use wtruncate after I/O is complete to chop off the excess padding.
// use truncate after I/O is complete to chop off the excess padding.
//
// NB: writes that extend a file (i.e. ALL WRITES when creating new files)
// are synchronous, which prevents overlapping I/O and other work.

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -30,55 +30,6 @@
#include <atomic>
//-----------------------------------------------------------------------------
// WDIR suballocator
//-----------------------------------------------------------------------------
// most applications only need a single WDIR at a time. we avoid expensive
// heap allocations by reusing a single static instance. if it is already
// in use, we allocate further instances dynamically.
// NB: this is thread-safe due to CAS.
struct WDIR // POD
{
HANDLE hFind;
WIN32_FIND_DATAW findData; // indeterminate if hFind == INVALID_HANDLE_VALUE
// wreaddir will return the address of this member.
// (must be stored in WDIR to allow multiple independent
// wopendir/wreaddir sequences).
struct wdirent ent;
// used by wreaddir to skip the first FindNextFileW. (a counter is
// easy to test/update and also provides useful information.)
size_t numCalls;
};
static WDIR wdir_storage;
static std::atomic<bool> wdir_in_use{ false };
static inline WDIR* wdir_alloc()
{
if(!wdir_in_use.exchange(true)) // gained ownership
return &wdir_storage;
// already in use (rare) - allocate from heap
return new WDIR;
}
static inline void wdir_free(WDIR* d)
{
if(d == &wdir_storage)
{
const bool ok = wdir_in_use.exchange(false); // relinquish ownership
ENSURE(ok); // ensure it wasn't double-freed
}
else // allocated from heap
delete d;
}
//-----------------------------------------------------------------------------
// dirent.h
//-----------------------------------------------------------------------------
@ -127,112 +78,6 @@ static bool IsValidDirectory(const OsPath& path)
return true;
}
// Return owning pointer or nullptr on error.
[[nodiscard]] WDIR* wopendir(const OsPath& path)
{
WinScopedPreserveLastError s;
if(!IsValidDirectory(path))
{
errno = ENOENT;
return 0;
}
WDIR* d = wdir_alloc();
d->numCalls = 0;
// NB: "c:\\path" only returns information about that directory;
// trailing slashes aren't allowed. append "\\*" to retrieve its entries.
OsPath searchPath = path/"*";
// (we don't defer FindFirstFileW until wreaddir because callers
// expect us to return 0 if directory reading will/did fail.)
d->hFind = FindFirstFileW(OsString(searchPath).c_str(), &d->findData);
if(d->hFind != INVALID_HANDLE_VALUE)
return d; // success
const DWORD nativeError{GetLastError()};
if(nativeError == ERROR_NO_MORE_FILES)
return d; // success, but directory is empty
Status status = StatusFromWin();
// release the WDIR allocated above (this is preferable to
// always copying the large WDIR or findData from a temporary)
wdir_free(d);
if(nativeError == ERROR_PATH_NOT_FOUND)
// TODO: This should be displayed to the user as a LOGWARNING when this code is moved to ps/
debug_printf("The path \"%s\" cannot be found. It is probably a dangling link "
"pointing to a non-existent path.\n", path.string8().c_str());
else
WARN_IF_ERR(status);
errno = ErrnoFromStatus(status);
return 0;
}
struct wdirent* wreaddir(WDIR* d)
{
// directory is empty and d->findData is indeterminate
if(d->hFind == INVALID_HANDLE_VALUE)
return 0;
WinScopedPreserveLastError s;
// until end of directory or a valid entry was found:
for(;;)
{
if(d->numCalls++ != 0) // (skip first call to FindNextFileW - see wopendir)
{
if(!FindNextFileW(d->hFind, &d->findData))
{
if(GetLastError() == ERROR_NO_MORE_FILES)
SetLastError(0);
else // unexpected error
DEBUG_WARN_ERR(StatusFromWin());
return 0; // end of directory or error
}
}
// only accept non-hidden and non-system entries - otherwise,
// callers might encounter errors when attempting to open them.
if((d->findData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) == 0)
{
d->ent.d_name = d->findData.cFileName; // (NB: d_name is a pointer)
return &d->ent;
}
}
}
int wreaddir_stat_np(WDIR* d, struct stat* s)
{
// NTFS stores UTC but FAT stores local times, which are incorrectly
// translated to UTC based on the _current_ DST settings. we no longer
// bother checking the filesystem, since that's either unreliable or
// expensive. timestamps may therefore be off after a DST transition,
// which means our cached files would be regenerated.
FILETIME* filetime = &d->findData.ftLastWriteTime;
memset(s, 0, sizeof(*s));
s->st_size = (off_t)u64_from_u32(d->findData.nFileSizeHigh, d->findData.nFileSizeLow);
s->st_mode = (unsigned short)((d->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG);
s->st_mtime = wtime_utc_filetime_to_time_t(filetime);
return 0;
}
int wclosedir(WDIR* d)
{
FindClose(d->hFind);
wdir_free(d);
return 0;
}
//-----------------------------------------------------------------------------
// fcntl.h
@ -286,33 +131,6 @@ int wclose(int fd)
}
int wtruncate(const OsPath& pathname, off_t length)
{
// (re-open the file to avoid the FILE_FLAG_NO_BUFFERING
// sector-alignment restriction)
HANDLE hFile = CreateFileW(OsString(pathname).c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
ENSURE(hFile != INVALID_HANDLE_VALUE);
LARGE_INTEGER ofs; ofs.QuadPart = length;
WARN_IF_FALSE(SetFilePointerEx(hFile, ofs, 0, FILE_BEGIN));
WARN_IF_FALSE(SetEndOfFile(hFile));
WARN_IF_FALSE(CloseHandle(hFile));
return 0;
}
int wunlink(const OsPath& pathname)
{
return _wunlink(OsString(pathname).c_str());
}
int wrmdir(const OsPath& path)
{
return _wrmdir(OsString(path).c_str());
}
OsPath wrealpath(const OsPath& pathname)
{
wchar_t resolved[PATH_MAX];

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -49,6 +49,7 @@
#include <boost/algorithm/string/split.hpp>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fmt/printf.h>
#include <initializer_list>
#include <js/Array.h>
@ -56,6 +57,7 @@
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/Value.h>
#include <system_error>
class ScriptInterface;
@ -545,7 +547,9 @@ bool ModIo::ParseMods(const ScriptInterface& scriptInterface, std::string& err)
void ModIo::DeleteDownloadedFile()
{
if (wunlink(m_DownloadFilePath) != 0)
std::error_code ec{};
std::filesystem::remove(m_DownloadFilePath.string(), ec);
if (ec)
LOGERROR("Failed to delete temporary file.");
m_DownloadFilePath = OsPath();
}

View file

@ -51,10 +51,12 @@
#include <cstdint>
#include <ctime>
#include <filesystem>
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <memory>
#include <sstream>
#include <system_error>
#include <utility>
class ScriptInterface;
@ -343,7 +345,9 @@ bool SavedGames::DeleteSavedGame(const std::wstring& name)
return false; // Error
// Delete actual file
if (wunlink(realpath) != 0)
std::error_code ec{};
std::filesystem::remove(realpath.string(), ec);
if (ec)
return false; // Error
// Successfully deleted file

View file

@ -42,6 +42,7 @@
#include <SDL_events.h>
#include <SDL_quit.h>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <js/Array.h>
@ -50,6 +51,7 @@
#include <js/Value.h>
#include <map>
#include <string>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
@ -110,7 +112,8 @@ bool VisualReplay::ReadCacheFile(const ScriptInterface& scriptInterface, JS::Mut
}
LOGWARNING("The replay cache file is corrupted, it will be deleted");
wunlink(GetCacheFilePath());
std::error_code ec{};
std::filesystem::remove(GetCacheFilePath().string(), ec);
return false;
}
@ -123,7 +126,8 @@ void VisualReplay::StoreCacheFile(const ScriptInterface& scriptInterface, JS::Ha
cacheStream << Script::StringifyJSON(rq, &replaysRooted);
cacheStream.close();
wunlink(GetCacheFilePath());
std::error_code ec{};
std::filesystem::remove(GetCacheFilePath().string(), ec);
if (RenameFile(GetTempCacheFilePath(), GetCacheFilePath()))
LOGERROR("Could not store the replay cache");
}

View file

@ -43,6 +43,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fmt/format.h>
#include <js/Array.h>
#include <js/PropertyAndElement.h>
@ -53,6 +54,7 @@
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
class ScriptInterface;
@ -288,11 +290,15 @@ bool DeleteCampaignSave(const CStrW& filePath)
OsPath realPath;
if (filePath.Left(16) != L"saves/campaigns/" || filePath.Right(12) != L".0adcampaign")
return false;
return VfsFileExists(filePath) &&
g_VFS->GetRealPath(filePath, realPath) == INFO::OK &&
g_VFS->RemoveFile(filePath) == INFO::OK &&
wunlink(realPath) == 0;
if (!VfsFileExists(filePath))
return false;
if (g_VFS->GetRealPath(filePath, realPath) != INFO::OK)
return false;
if (g_VFS->RemoveFile(filePath) != INFO::OK)
return false;
std::error_code ec;
std::filesystem::remove(realPath.string(), ec);
return !ec;
}
void RegisterScriptFunctions_ReadWriteAnywhere(const ScriptRequest& rq,

View file

@ -703,23 +703,6 @@ void ShadowMap::BindTo(
}
}
// Depth texture bits
int ShadowMap::GetDepthTextureBits() const
{
return m->DepthTextureBits;
}
void ShadowMap::SetDepthTextureBits(int bits)
{
if (bits != m->DepthTextureBits)
{
m->Texture.reset();
m->Width = m->Height = 0;
m->DepthTextureBits = bits;
}
}
void ShadowMap::RenderDebugBounds(Renderer::Backend::IDeviceCommandContext& deviceCommandContext)
{
// Render various shadow bounds:

View file

@ -47,22 +47,6 @@ public:
*/
void RecreateTexture();
/**
* GetDepthTextureBits: Return the number of bits to use for depth textures when
* enabled.
*
* @return depth texture bit depth
*/
int GetDepthTextureBits() const;
/**
* SetDepthTextureBits: Sets the number of bits to use for depth textures when enabled.
* Possible values are 16, 24, 32 and 0 (= use default)
*
* @param bits number of bits
*/
void SetDepthTextureBits(int bits);
/**
* SetupFrame: Configure light space for the given camera and light direction,
* create the shadow texture if necessary, etc.

View file

@ -60,6 +60,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
@ -70,6 +71,7 @@
#include <optional>
#include <set>
#include <sstream>
#include <system_error>
class CSimulation2Impl
{
@ -321,19 +323,26 @@ void CSimulation2Impl::ReportSerializationFailure(
const OsPath path = createDateIndexSubdirectory(psLogDir() / "serializationtest");
debug_printf("Writing serializationtest-data to %s\n", path.string8().c_str());
// Clean up obsolete files from previous runs
wunlink(path / "hash.before.a");
wunlink(path / "hash.before.b");
wunlink(path / "debug.before.a");
wunlink(path / "debug.before.b");
wunlink(path / "state.before.a");
wunlink(path / "state.before.b");
wunlink(path / "hash.after.a");
wunlink(path / "hash.after.b");
wunlink(path / "debug.after.a");
wunlink(path / "debug.after.b");
wunlink(path / "state.after.a");
wunlink(path / "state.after.b");
// Try to clean up obsolete files from previous runs.
constexpr auto namesToRemove{std::to_array<std::string_view>({
"hash.before.a",
"hash.before.b",
"debug.before.a",
"debug.before.b",
"state.before.a",
"state.before.b",
"hash.after.a",
"hash.after.b",
"debug.after.a",
"debug.after.b",
"state.after.a",
"state.after.b",
})};
const std::filesystem::path fspath{path.string()};
std::error_code ec{};
for (const std::string_view nameToRemove : namesToRemove)
std::filesystem::remove(fspath / nameToRemove, ec);
if (primaryStateBefore)
DumpSerializationTestState(*primaryStateBefore, path, L"before.a");

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -56,7 +56,7 @@
#include <string>
#include <vector>
class MockPathfinderTerrMan : public ICmpPathfinder
class MockPathfinderTerrManager : public ICmpPathfinder
{
public:
DEFAULT_MOCK_COMPONENT()
@ -64,38 +64,38 @@ public:
// Test data
Grid<NavcellData> m_PassabilityGrid;
virtual pass_class_t GetPassabilityClass(const std::string&) const override { return 0; }
virtual const Grid<NavcellData>& GetPassabilityGrid() override { return m_PassabilityGrid; }
pass_class_t GetPassabilityClass(const std::string&) const override { return 0; }
const Grid<NavcellData>& GetPassabilityGrid() override { return m_PassabilityGrid; }
// Irrelevant part of the mock.
virtual void GetPassabilityClasses(std::map<std::string, pass_class_t>&) const override {}
virtual void GetPassabilityClasses(std::map<std::string, pass_class_t>&, std::map<std::string, pass_class_t>&) const override {}
virtual entity_pos_t GetClearance(pass_class_t) const override { return entity_pos_t::FromInt(1); }
virtual entity_pos_t GetMaximumClearance() const override { return entity_pos_t::FromInt(1); }
virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const override { static GridUpdateInformation gridInfo; return gridInfo; }
virtual void FlushAIPathfinderDirtinessInformation() override {}
virtual Grid<u16> ComputeShoreGrid(bool = false) override { return Grid<u16> {}; }
virtual u32 ComputePathAsync(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, entity_id_t) override { return 1; }
virtual void ComputePathImmediate(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, WaypointPath&) const override {}
virtual u32 ComputeShortPathAsync(entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, bool, entity_id_t, entity_id_t) override { return 1; }
virtual WaypointPath ComputeShortPathImmediate(const ShortPathRequest&) const override { return WaypointPath(); }
virtual void SetDebugPath(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override {}
virtual bool IsGoalReachable(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override { return false; }
virtual std::vector<CFixedVector2D> DistributeAround(std::vector<entity_id_t>, entity_pos_t, entity_pos_t) const override { return {}; }
virtual bool CheckMovement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t) const override { return false; }
virtual ICmpObstruction::EFoundationCheck CheckUnitPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t, bool = false) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t, bool) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
virtual void SetDebugOverlay(bool) override {}
virtual void SetHierDebugOverlay(bool) override {}
virtual void SendRequestedPaths() override {}
virtual void StartProcessingMoves(bool) override {}
virtual void UpdateGrid() override {}
virtual void GetDebugData(u32&, double&, Grid<u8>&) const override {}
virtual void SetAtlasOverlay(bool, pass_class_t = 0) override {}
void GetPassabilityClasses(std::map<std::string, pass_class_t>&) const override {}
void GetPassabilityClasses(std::map<std::string, pass_class_t>&, std::map<std::string, pass_class_t>&) const override {}
entity_pos_t GetClearance(pass_class_t) const override { return entity_pos_t::FromInt(1); }
entity_pos_t GetMaximumClearance() const override { return entity_pos_t::FromInt(1); }
const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const override { static GridUpdateInformation gridInfo; return gridInfo; }
void FlushAIPathfinderDirtinessInformation() override {}
Grid<u16> ComputeShoreGrid(bool = false) override { return Grid<u16> {}; }
u32 ComputePathAsync(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, entity_id_t) override { return 1; }
void ComputePathImmediate(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, WaypointPath&) const override {}
u32 ComputeShortPathAsync(entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, bool, entity_id_t, entity_id_t) override { return 1; }
WaypointPath ComputeShortPathImmediate(const ShortPathRequest&) const override { return WaypointPath(); }
void SetDebugPath(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override {}
bool IsGoalReachable(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override { return false; }
std::vector<CFixedVector2D> DistributeAround(std::vector<entity_id_t>, entity_pos_t, entity_pos_t) const override { return {}; }
bool CheckMovement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t) const override { return false; }
ICmpObstruction::EFoundationCheck CheckUnitPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t, bool = false) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t, bool) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
void SetDebugOverlay(bool) override {}
void SetHierDebugOverlay(bool) override {}
void SendRequestedPaths() override {}
void StartProcessingMoves(bool) override {}
void UpdateGrid() override {}
void GetDebugData(u32&, double&, Grid<u8>&) const override {}
void SetAtlasOverlay(bool, pass_class_t = 0) override {}
};
class MockPlayerMgrTerrMan : public ICmpPlayerManager
class MockPlayerMgrTerrManager : public ICmpPlayerManager
{
public:
DEFAULT_MOCK_COMPONENT()
@ -104,7 +104,7 @@ public:
entity_id_t GetPlayerByID(int32_t id) override { return id + 1; }
};
class MockTerrInfTerrMan : public ICmpTerritoryInfluence
class MockTerrInfTerrManager : public ICmpTerritoryInfluence
{
public:
DEFAULT_MOCK_COMPONENT()
@ -116,7 +116,7 @@ public:
u32 m_Radius = 0;
};
class MockOwnershipTerrMan : public ICmpOwnership
class MockOwnershipTerrManager : public ICmpOwnership
{
public:
DEFAULT_MOCK_COMPONENT()
@ -126,7 +126,7 @@ public:
void SetOwnerQuiet(player_id_t) override {};
};
class MockPositionTerrMan : public ICmpPosition
class MockPositionTerrManager : public ICmpPosition
{
public:
DEFAULT_MOCK_COMPONENT()
@ -195,19 +195,19 @@ public:
ComponentTestHelper test(*g_ScriptContext);
ICmpTerritoryManager* cmp = test.Add<ICmpTerritoryManager>(CID_TerritoryManager, "", SYSTEM_ENTITY);
MockPathfinderTerrMan pathfinder;
MockPathfinderTerrManager pathfinder;
test.AddMock(SYSTEM_ENTITY, IID_Pathfinder, pathfinder);
MockPlayerMgrTerrMan playerMan;
MockPlayerMgrTerrManager playerMan;
test.AddMock(SYSTEM_ENTITY, IID_PlayerManager, playerMan);
pathfinder.m_PassabilityGrid.resize(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5, ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5);
MockTerrInfTerrMan terrInf;
MockTerrInfTerrManager terrInf;
test.AddMock(5, IID_TerritoryInfluence, terrInf);
MockOwnershipTerrMan ownership;
MockOwnershipTerrManager ownership;
test.AddMock(5, IID_Ownership, ownership);
MockPositionTerrMan position;
MockPositionTerrManager position;
test.AddMock(5, IID_Position, position);
position.m_Pos = CFixedVector3D(

View file

@ -43,7 +43,7 @@ public:
protected:
void NotifyFinishedOwnCommands(u32 turn) override;
virtual void NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) override;
void NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) override;
};
#endif // INCLUDED_LOCALTURNMANAGER

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,7 +18,9 @@
#include "AtlasObject.h"
#include "AtlasObjectImpl.h"
#include <assert.h>
#include <cassert>
#include <cstddef>
#include <iterator>
#include <sstream>
#define ATSMARTPTR_IMPL(T) \

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,6 +18,7 @@
#include "AtlasObject.h"
#include <string>
#include <utility>
#ifdef _MSC_VER
// Avoid complaints about unreachable code; the optimiser is realising

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,7 +18,6 @@
#include "tools/atlas/AtlasObject/AtlasObject.h"
#include "tools/atlas/AtlasUI/CustomControls/Windows/AtlasWindow.h"
#include <wx/defs.h>
#include <wx/event.h>
class ActorEditorListCtrl;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -31,7 +31,6 @@
#include <vector>
#include <wx/chartype.h>
#include <wx/colour.h>
#include <wx/defs.h>
#include <wx/toolbar.h>
#include <wx/translation.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,7 +27,6 @@
#include <cstddef>
#include <vector>
#include <wx/chartype.h>
#include <wx/defs.h>
#include <wx/gdicmn.h>
#include <wx/listctrl.h>
#include <wx/panel.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,7 +27,6 @@
#include <cstddef>
#include <vector>
#include <wx/chartype.h>
#include <wx/defs.h>
#include <wx/gdicmn.h>
#include <wx/listctrl.h>
#include <wx/panel.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -26,7 +26,6 @@
#include <cstddef>
#include <vector>
#include <wx/chartype.h>
#include <wx/defs.h>
#include <wx/gdicmn.h>
#include <wx/listctrl.h>
#include <wx/panel.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -16,7 +16,6 @@
*/
#include <wx/button.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/toolbar.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -17,7 +17,6 @@
#include <map>
#include <wx/button.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/string.h>
#include <wx/toolbar.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -15,7 +15,6 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/gdicmn.h>
#include <wx/glcanvas.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,7 +22,6 @@
#include <wx/chartype.h>
#include <wx/colourdata.h>
#include <wx/config.h>
#include <wx/defs.h>
#include <wx/regex.h>
class wxWindow;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,7 +27,6 @@
#include "tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrl.h"
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/listctrl.h>
#include <wx/string.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,7 +19,6 @@
#include "tools/atlas/AtlasUI/General/AtlasWindowCommand.h"
#include <vector>
#include <wx/defs.h>
#include <wx/object.h>
class DraggableListCtrl;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,18 +18,16 @@
#ifndef INCLUDED_EDITABLELISTCTRL
#define INCLUDED_EDITABLELISTCTRL
#include "tools/atlas/AtlasObject/AtlasObject.h"
#include "tools/atlas/AtlasUI/General/IAtlasSerialiser.h"
#include <vector>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/listctrl.h>
#include <wx/string.h>
#include <wx/toolbar.h>
#include <wx/validate.h>
class AtIter;
class AtObj;
class FieldEditCtrl;
class wxPoint;
class wxRect;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,12 +19,10 @@
#include "tools/atlas/AtlasUI/General/AtlasWindowCommand.h"
#include <vector>
#include <wx/defs.h>
#include <wx/object.h>
#include <wx/string.h>
class EditableListCtrl;
class wxClassInfo;
class EditCommand_Dialog : public AtlasWindowCommand
{

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -35,7 +35,6 @@
#include <wx/colordlg.h>
#include <wx/colour.h>
#include <wx/debug.h>
#include <wx/defs.h>
#include <wx/filename.h>
#include <wx/regex.h>
#include <wx/window.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -25,7 +25,6 @@
#include <cstddef>
#include <wx/combobox.h>
#include <wx/defs.h>
#include <wx/log.h>
#include <wx/object.h>
#include <wx/string.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -16,7 +16,6 @@
*/
#include <wx/combobox.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/validate.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -15,13 +15,11 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/defs.h>
#include <wx/object.h>
#include <wx/panel.h>
#include <wx/validate.h>
class wxButton;
class wxClassInfo;
class wxRect;
class wxString;
class wxTextCtrl;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -15,7 +15,6 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/textctrl.h>
#include <wx/validate.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,7 +18,6 @@
#ifndef INCLUDED_MAPDIALOG
#define INCLUDED_MAPDIALOG
#include <wx/defs.h>
#include <wx/dialog.h>
#include <wx/event.h>
#include <wx/string.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -24,7 +24,6 @@
#include "tools/atlas/GameInterface/Messages.h"
#include "tools/atlas/GameInterface/Shareable.h"
#include <string>
#include <wx/button.h>
#include <wx/clntdata.h>
#include <wx/gdicmn.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,7 +18,6 @@
#ifndef INCLUDED_MAPRESIZEDIALOG
#define INCLUDED_MAPRESIZEDIALOG
#include <wx/defs.h>
#include <wx/dialog.h>
#include <wx/event.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -31,7 +31,6 @@
#include <wx/colour.h>
#include <wx/dcbuffer.h>
#include <wx/dcgraph.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/graphics.h>
#include <wx/mousestate.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,7 +21,6 @@
#include <map>
#include <wx/bitmap.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/gdicmn.h>
#include <wx/image.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -15,7 +15,6 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/splitter.h>
#include <wx/string.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -26,7 +26,6 @@
#include <wx/arrstr.h>
#include <wx/chartype.h>
#include <wx/debug.h>
#include <wx/defs.h>
#include <wx/dynarray.h>
#include <wx/event.h>
#include <wx/string.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,12 +21,10 @@
#include "tools/atlas/AtlasUI/General/AtlasWindowCommandProc.h"
#include "tools/atlas/AtlasUI/General/IAtlasSerialiser.h"
#include <wx/defs.h>
#include <wx/dialog.h>
#include <wx/event.h>
#include <wx/object.h>
class wxClassInfo;
class wxPanel;
class wxSize;
class wxString;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -25,12 +25,11 @@
#include "tools/atlas/AtlasUI/General/AtlasWindowCommandProc.h"
#include "tools/atlas/AtlasUI/General/Datafile.h"
#include <boost/signals2/optional_last_value.hpp>
#include <cassert>
#include <list>
#include <string>
#include <wx/artprov.h>
#include <wx/bitmap.h>
#include <wx/bmpbndl.h>
#include <wx/button.h>
#include <wx/chartype.h>
#include <wx/config.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,16 +22,13 @@
#include "tools/atlas/AtlasUI/General/AtlasWindowCommandProc.h"
#include "tools/atlas/AtlasUI/General/IAtlasSerialiser.h"
#include <boost/function.hpp>
#include <boost/signals2/signal.hpp>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/filename.h>
#include <wx/frame.h>
#include <wx/object.h>
#include <wx/string.h>
class wxClassInfo;
class wxMenu;
class wxMenuBar;
class wxMenuItem;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,12 +22,10 @@
#include <wx/chartype.h>
#include <wx/cmdproc.h>
#include <wx/defs.h>
#include <wx/object.h>
#include <wx/string.h>
class IAtlasSerialiser;
class wxClassInfo;
class AtlasWindowCommand : public wxCommand
{

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -34,7 +34,6 @@
#include <wx/cmdargs.h>
#include <wx/config.h>
#include <wx/debug.h>
#include <wx/defs.h>
#include <wx/file.h>
#include <wx/fileconf.h>
#include <wx/filename.h>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -20,7 +20,7 @@
#include "KeyMap.h"
#include <SDL_keycode.h>
#include <wx/defs.h>
#include <wx/event.h>
int GetSDLKeyFromWxKeyCode(int wxkey)
{

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -40,7 +40,6 @@
#include "tools/atlas/GameInterface/SharedTypes.h"
#include <cstddef>
#include <list>
#include <string>
#include <utility>
#include <wx/busyinfo.h>
@ -174,6 +173,12 @@ private:
if (KeyScroll(evt, true))
return;
if (evt.GetKeyCode() == 'B')
{
POST_MESSAGE(ToggleBirdsEyeView, ());
return;
}
POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), true));
evt.Skip();

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -28,7 +28,6 @@
#include <map>
#include <vector>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/frame.h>
#include <wx/icon.h>

View file

@ -28,6 +28,7 @@
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h"
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.h"
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h"
#include "tools/atlas/AtlasUI/ScenarioEditor/StyleSheet.h"
#include <cstddef>
#include <utility>
@ -37,7 +38,6 @@
#include <wx/bmpbuttn.h>
#include <wx/chartype.h>
#include <wx/colour.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/filename.h>
#include <wx/gdicmn.h>
@ -310,10 +310,10 @@ void SectionLayout::Build(ScenarioEditor& scenarioEditor)
#undef ADD_SIDEBAR
m_VertSplitter->SetDefaultSashPosition(-BOTTOMBAR_SIZE);
m_VertSplitter->SetDefaultSashPosition(-Atlas::Style::BOTTOMBAR_DEFAULT_SIZE);
m_VertSplitter->Initialize(m_Canvas);
m_HorizSplitter->SetDefaultSashPosition(SIDEBAR_SIZE);
m_HorizSplitter->SetDefaultSashPosition(Atlas::Style::SIDEBAR_DEFAULT_SIZE);
m_HorizSplitter->SplitVertically(m_SidebarBook, m_VertSplitter);
}

View file

@ -27,18 +27,6 @@ class SnapSplitterWindow;
class wxString;
class wxWindow;
// Some platform dependent sizes
#if defined(__WXGTK__)
#define SIDEBAR_SIZE 285
#define BOTTOMBAR_SIZE 200
#elif defined(__WXOSX__) || defined(__WXMAC__)
#define SIDEBAR_SIZE 285
#define BOTTOMBAR_SIZE 210
#else // __MSW__
#define SIDEBAR_SIZE 235
#define BOTTOMBAR_SIZE 180
#endif
class SectionLayout
{
public:

View file

@ -17,7 +17,6 @@
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h"
#include <wx/defs.h>
#include <wx/event.h>
class ScenarioEditor;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -20,7 +20,6 @@
#include "Sidebar.h"
#include <cstddef>
#include <wx/defs.h>
#include <wx/sizer.h>
#include <wx/window.h>

View file

@ -29,9 +29,7 @@
#include "tools/atlas/GameInterface/Shareable.h"
#include "tools/atlas/GameInterface/SharedTypes.h"
#include <cmath>
#include <cstddef>
#include <list>
#include <numbers>
#include <string>
#include <vector>

View file

@ -18,7 +18,6 @@
#include "tools/atlas/AtlasUI/General/Observable.h"
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h"
#include <wx/defs.h>
#include <wx/event.h>
class ScenarioEditor;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -25,11 +25,9 @@
#include <algorithm>
#include <cmath>
#include <list>
#include <wx/bitmap.h>
#include <wx/control.h>
#include <wx/dcclient.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/gdicmn.h>
#include <wx/image.h>

Some files were not shown because too many files have changed in this diff Show more