mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Compare commits
21 commits
c2e07c3fc5
...
d843c7349b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d843c7349b | ||
|
|
192c1e9dc9 | ||
|
|
db8be29f82 | ||
|
|
150677672b | ||
|
|
427682563d | ||
|
|
24b50778fa | ||
|
|
b292c658e7 | ||
|
|
9506937fc3 | ||
|
|
ee463b1721 | ||
|
|
0efde44f46 | ||
|
|
1e09d33a9f | ||
|
|
8a7718cb6f | ||
|
|
9598c6c2e1 | ||
|
|
e6f75f2c33 | ||
|
|
6244d25f90 | ||
|
|
63629195ba | ||
|
|
2fae39df3b | ||
|
|
f1181bada0 | ||
|
|
309ed5ef28 | ||
|
|
3c8b7dfa7a | ||
|
|
32b9713781 |
68 changed files with 1104 additions and 452 deletions
|
|
@ -26,7 +26,7 @@ async function init(data)
|
|||
"argument": data
|
||||
} });
|
||||
};
|
||||
globalThis.cancelOnLoadGameError = async() =>
|
||||
globalThis.cancelOnLoadGameError = async(errorMessage) =>
|
||||
{
|
||||
Engine.ResetCursor();
|
||||
Engine.EndGame();
|
||||
|
|
|
|||
|
|
@ -2280,7 +2280,7 @@ AttackPlan.prototype.Serialize = function()
|
|||
"siegeState": this.siegeState,
|
||||
"position5TurnsAgo": this.position5TurnsAgo,
|
||||
"lastPosition": this.lastPosition,
|
||||
"position": clone(this.position),
|
||||
"position": this.position !== undefined ? clone(this.position) : undefined,
|
||||
"isBlocked": this.isBlocked,
|
||||
"targetPlayer": this.targetPlayer,
|
||||
"target": this.target !== undefined ? this.target.id() : undefined,
|
||||
|
|
|
|||
|
|
@ -286,17 +286,21 @@ Attack.prototype.CanAttack = function(target, wantedTypes)
|
|||
if (!cmpTargetPlayer || !cmpEntityPlayer)
|
||||
return false;
|
||||
|
||||
// Must be visible or miraged / with retainInFog flag, not completely hidden
|
||||
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
if (cmpRangeManager)
|
||||
{
|
||||
const visibility = cmpRangeManager.GetLosVisibility(target, cmpEntityPlayer.GetPlayerID());
|
||||
if (visibility == "hidden")
|
||||
return false;
|
||||
}
|
||||
|
||||
const types = this.GetAttackTypes(wantedTypes);
|
||||
const entityOwner = cmpEntityPlayer.GetPlayerID();
|
||||
const targetOwner = cmpTargetPlayer.GetPlayerID();
|
||||
const cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
|
||||
const cmpDiplomacy = QueryPlayerIDInterface(entityOwner, IID_Diplomacy);
|
||||
|
||||
// Check if the relative height difference is larger than the attack range
|
||||
// If the relative height is bigger, it means they will never be able to
|
||||
// reach each other, no matter how close they come.
|
||||
const heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
|
||||
|
||||
for (const type of types)
|
||||
{
|
||||
if (type != "Capture" && (!cmpDiplomacy?.IsEnemy(targetOwner) || !cmpHealth || !cmpHealth.GetHitpoints()))
|
||||
|
|
@ -305,7 +309,8 @@ Attack.prototype.CanAttack = function(target, wantedTypes)
|
|||
if (type == "Capture" && (!cmpCapturable || !cmpCapturable.CanCapture(entityOwner)))
|
||||
continue;
|
||||
|
||||
if (heightDiff > this.GetRange(type).max)
|
||||
// Check if the target is currently in range, or could ever be reached
|
||||
if (!this.IsTargetInRange(target, type) && !this.CanEverReachTarget(target, type))
|
||||
continue;
|
||||
|
||||
const restrictedClasses = this.GetRestrictedClasses(type);
|
||||
|
|
@ -319,6 +324,77 @@ Attack.prototype.CanAttack = function(target, wantedTypes)
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the target could potentially ever be reached with the given attack type,
|
||||
* as an optimistic estimate. This assumes the attacker can move to the closest
|
||||
* possible position to the target — ignoring obstructions and terrain features
|
||||
* (e.g., hills) that might help or hinder.
|
||||
*
|
||||
* This is a best-effort guess:
|
||||
* - It may return true even when the target is actually unreachable (e.g., turreted
|
||||
* units on walls with a height offset too large for the projectile to overcome).
|
||||
* - It may return false even when the target is reachable (e.g., a nearby hill could
|
||||
* provide enough elevation to hit a "too high" target, but we don't check for that).
|
||||
*
|
||||
* Currently these checks are mostly useful to determine if we can reach turreted units
|
||||
* (e.g. on a wall, outpost...).
|
||||
*
|
||||
* @param {number} targetId - The target entity ID.
|
||||
* @param {string} type - The attack type.
|
||||
* @return {boolean} - Whether the target is estimated to be reachable (see caveats above).
|
||||
*/
|
||||
Attack.prototype.CanEverReachTarget = function(targetId, type)
|
||||
{
|
||||
const cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
const cmpTargetPosition = Engine.QueryInterface(targetId, IID_Position);
|
||||
|
||||
const thisHeightOffset = cmpThisPosition.GetHeightOffset();
|
||||
const targetHeightOffset = cmpTargetPosition.GetHeightOffset();
|
||||
|
||||
const range = this.GetRange(type);
|
||||
|
||||
// Find the closest horizontal distance we could ever get to the target.
|
||||
// We first determine the closest horizontal distance we could ever get to the target,
|
||||
// accounting for turreted units inside buildings:
|
||||
// - If the building blocks movement, we can only reach its exterior edge.
|
||||
// - If the building is passable, we can walk right up to the turret point.
|
||||
const cmpTurretable = Engine.QueryInterface(targetId, IID_Turretable);
|
||||
const holderId = cmpTurretable?.HolderID();
|
||||
let closestDistance = 0;
|
||||
if (holderId && holderId != INVALID_ENTITY)
|
||||
{
|
||||
const cmpTurretHolder = Engine.QueryInterface(holderId, IID_TurretHolder);
|
||||
if (cmpTurretHolder)
|
||||
{
|
||||
const turretPoint = cmpTurretHolder.GetOccupiedTurretPoint(targetId);
|
||||
closestDistance = cmpTurretHolder.GetClosestApproachDistanceToTurretPoint(turretPoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (!range.parabolic)
|
||||
{
|
||||
// For non-parabolic attacks (e.g., "Melee" attack type), we check if the height offset
|
||||
// is within max range at the closest possible horizontal distance (simple 3D distance check).
|
||||
const heightDiff = Math.abs(targetHeightOffset - thisHeightOffset);
|
||||
return Math.sqrt(closestDistance * closestDistance + heightDiff * heightDiff) <= range.max;
|
||||
}
|
||||
|
||||
// For parabolic attacks (generally "Ranged" attack type), we use the parabolic formula
|
||||
// to determine if the height offset is surmountable at the closest possible distance.
|
||||
// Typical scenario: units on walls/towers may be unreachable if the attacker's
|
||||
// projectiles can't arc high enough, even at point-blank range.
|
||||
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
if (!cmpRangeManager)
|
||||
return true;
|
||||
|
||||
const yOrigin = this.GetAttackYOrigin(type);
|
||||
|
||||
const maxReachableHeightDiff = cmpRangeManager.GetMaxReachableParabolicHeight(
|
||||
range.max, yOrigin, closestDistance);
|
||||
|
||||
return targetHeightOffset - thisHeightOffset <= maxReachableHeightDiff;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns undefined if we have no preference or the lowest index of a preferred class.
|
||||
*/
|
||||
|
|
@ -353,12 +429,14 @@ Attack.prototype.GetPreference = function(target)
|
|||
*/
|
||||
Attack.prototype.GetFullAttackRange = function()
|
||||
{
|
||||
const ret = { "min": Infinity, "max": 0 };
|
||||
const ret = { "min": Infinity, "max": 0, "parabolic": false };
|
||||
for (const type of this.GetAttackTypes())
|
||||
{
|
||||
const range = this.GetRange(type);
|
||||
ret.min = Math.min(ret.min, range.min);
|
||||
ret.max = Math.max(ret.max, range.max);
|
||||
if (range.parabolic)
|
||||
ret.parabolic = true;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
|
@ -475,7 +553,39 @@ Attack.prototype.GetRange = function(type)
|
|||
let min = +(this.template[type].MinRange || 0);
|
||||
min = ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity);
|
||||
|
||||
return { "max": max, "min": min };
|
||||
return {
|
||||
"max": max,
|
||||
"min": min,
|
||||
"parabolic": type === "Ranged"
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the effective range for attacking a specific target, accounting
|
||||
* for elevation and projectile physics where applicable.
|
||||
* @param {number} target - The target entity ID.
|
||||
* @param {string} type - The attack type.
|
||||
* @return {{ min: number, max: number }} - The min and max effective range.
|
||||
*/
|
||||
Attack.prototype.GetEffectiveAttackRange = function(target, type)
|
||||
{
|
||||
const range = this.GetRange(type);
|
||||
|
||||
// Only Parabolic attacks get parabolic elevation adjustment
|
||||
if (!range.parabolic)
|
||||
return range;
|
||||
|
||||
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
if (!cmpRangeManager)
|
||||
return range;
|
||||
|
||||
const effectiveMax = cmpRangeManager.GetEffectiveParabolicRange(
|
||||
this.entity, target, range.max, this.GetAttackYOrigin(type));
|
||||
|
||||
if (effectiveMax < 0)
|
||||
return { "min": Infinity, "max": 0 }; // Out of range
|
||||
|
||||
return { "min": range.min, "max": effectiveMax };
|
||||
};
|
||||
|
||||
Attack.prototype.GetAttackYOrigin = function(type)
|
||||
|
|
@ -813,14 +923,9 @@ Attack.prototype.PerformAttack = function(type, target)
|
|||
*/
|
||||
Attack.prototype.IsTargetInRange = function(target, type)
|
||||
{
|
||||
const range = this.GetRange(type);
|
||||
return Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager).IsInTargetParabolicRange(
|
||||
this.entity,
|
||||
target,
|
||||
range.min,
|
||||
range.max,
|
||||
this.GetAttackYOrigin(type),
|
||||
false);
|
||||
const range = this.GetEffectiveAttackRange(target, type);
|
||||
return Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager).IsInTargetRange(
|
||||
this.entity, target, range.min, range.max, false);
|
||||
};
|
||||
|
||||
Attack.prototype.OnValueModification = function(msg)
|
||||
|
|
|
|||
|
|
@ -127,10 +127,20 @@ BuildingAI.prototype.SetupRangeQuery = function()
|
|||
|
||||
const range = cmpAttack.GetRange(attackType);
|
||||
const yOrigin = cmpAttack.GetAttackYOrigin(attackType);
|
||||
|
||||
// Get building's vision range
|
||||
const cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
|
||||
const visionRange = cmpVision ? cmpVision.GetRange() : 0;
|
||||
|
||||
// Base range
|
||||
const baseRange = Math.min(visionRange, range.max);
|
||||
|
||||
// This takes entity sizes into accounts, so no need to compensate for structure size.
|
||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
|
||||
this.entity, range.min, range.max, yOrigin,
|
||||
enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal"));
|
||||
this.entity, range.min, range.max, baseRange, yOrigin,
|
||||
enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal"),
|
||||
true // Allow mirages for attack queries
|
||||
);
|
||||
|
||||
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
|
||||
};
|
||||
|
|
@ -156,10 +166,17 @@ BuildingAI.prototype.SetupGaiaRangeQuery = function()
|
|||
const range = cmpAttack.GetRange(attackType);
|
||||
const yOrigin = cmpAttack.GetAttackYOrigin(attackType);
|
||||
|
||||
// Get building's vision range
|
||||
const cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
|
||||
const visionRange = cmpVision ? cmpVision.GetRange() : 0;
|
||||
|
||||
// Base range
|
||||
const baseRange = Math.min(visionRange, range.max);
|
||||
|
||||
// This query is only interested in Gaia entities that can attack.
|
||||
// This takes entity sizes into accounts, so no need to compensate for structure size.
|
||||
this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
|
||||
this.entity, range.min, range.max, yOrigin,
|
||||
this.entity, range.min, range.max, baseRange, yOrigin,
|
||||
[0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal"));
|
||||
|
||||
cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery);
|
||||
|
|
@ -170,7 +187,6 @@ BuildingAI.prototype.SetupGaiaRangeQuery = function()
|
|||
*/
|
||||
BuildingAI.prototype.OnRangeUpdate = function(msg)
|
||||
{
|
||||
|
||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
if (!cmpAttack)
|
||||
return;
|
||||
|
|
@ -189,10 +205,10 @@ BuildingAI.prototype.OnRangeUpdate = function(msg)
|
|||
|
||||
// Add new targets.
|
||||
for (const entity of msg.added)
|
||||
if (cmpAttack.CanAttack(entity))
|
||||
if (!this.targetUnits.includes(entity))
|
||||
this.targetUnits.push(entity);
|
||||
|
||||
// Remove targets outside of vision-range.
|
||||
// Remove targets out of range.
|
||||
for (const entity of msg.removed)
|
||||
{
|
||||
const index = this.targetUnits.indexOf(entity);
|
||||
|
|
@ -375,13 +391,7 @@ BuildingAI.prototype.FireArrows = function()
|
|||
{
|
||||
|
||||
const selectedTarget = targets[targetIndex].entityId;
|
||||
if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange(
|
||||
this.entity,
|
||||
selectedTarget,
|
||||
range.min,
|
||||
range.max,
|
||||
yOrigin,
|
||||
false))
|
||||
if (cmpAttack.CanAttack(selectedTarget, [attackType]))
|
||||
{
|
||||
cmpAttack.PerformAttack(attackType, selectedTarget);
|
||||
PlaySound("attack_" + attackType.toLowerCase(), this.entity);
|
||||
|
|
|
|||
|
|
@ -240,6 +240,39 @@ class TurretHolder
|
|||
return turret ? turret.name : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the closest horizontal distance an external entity could ever get
|
||||
* to the specified turret point. If the holder is passable, returns 0.
|
||||
* Otherwise returns the perpendicular distance from the turret point to the
|
||||
* nearest edge of the holder's obstruction.
|
||||
*
|
||||
* @param {string|Object} turretPoint - The turret point name or object.
|
||||
* @return {number} - The minimum possible horizontal distance.
|
||||
*/
|
||||
GetClosestApproachDistanceToTurretPoint(turretPoint)
|
||||
{
|
||||
if (typeof turretPoint === "string")
|
||||
turretPoint = this.TurretPointByName(turretPoint);
|
||||
if (!turretPoint)
|
||||
return 0;
|
||||
|
||||
const cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
|
||||
if (!cmpObstruction || !cmpObstruction.GetBlockMovementFlag(false))
|
||||
return 0;
|
||||
|
||||
const dxLocal = turretPoint.offset.x;
|
||||
const dzLocal = turretPoint.offset.z;
|
||||
|
||||
const halfSizes = cmpObstruction.GetObstructionHalfSizes();
|
||||
const hw = halfSizes.x;
|
||||
const hh = halfSizes.y;
|
||||
|
||||
if (hw == null || hh == null || hw < 0 || hh < 0)
|
||||
return 0;
|
||||
|
||||
return Math.max(0, Math.min(hw - Math.abs(dxLocal), hh - Math.abs(dzLocal)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number[]} - The turretted entityIDs.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4048,10 +4048,31 @@ UnitAI.prototype.SetupAttackRangeQuery = function(enable = true)
|
|||
return;
|
||||
|
||||
const range = this.GetQueryRange(IID_Attack);
|
||||
// Do not compensate for entity sizes: LOS doesn't, and UnitAI relies on that.
|
||||
this.losAttackRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity,
|
||||
range.min, range.max, players, IID_Resistance,
|
||||
cmpRangeManager.GetEntityFlagMask("normal"), false);
|
||||
if (range.parabolic)
|
||||
{
|
||||
const cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
const yOrigin = cmpAttack ? cmpAttack.GetAttackYOrigin("Ranged") : 0;
|
||||
|
||||
// Do not compensate for entity sizes: LOS doesn't, and UnitAI relies on that.
|
||||
this.losAttackRangeQuery = cmpRangeManager.CreateActiveParabolicQuery(
|
||||
this.entity,
|
||||
range.min,
|
||||
range.max,
|
||||
range.base,
|
||||
yOrigin,
|
||||
players,
|
||||
IID_Resistance,
|
||||
cmpRangeManager.GetEntityFlagMask("normal"),
|
||||
true // Allow mirages for attack queries
|
||||
);
|
||||
}
|
||||
else
|
||||
this.losAttackRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity,
|
||||
range.min, range.max, players, IID_Resistance,
|
||||
cmpRangeManager.GetEntityFlagMask("normal"),
|
||||
false,
|
||||
true // Allow mirages for attack queries
|
||||
);
|
||||
|
||||
if (enable)
|
||||
cmpRangeManager.EnableActiveQuery(this.losAttackRangeQuery);
|
||||
|
|
@ -4948,24 +4969,24 @@ UnitAI.prototype.MoveToTargetAttackRange = function(target, type)
|
|||
if (cmpFormation)
|
||||
target = cmpFormation.GetClosestMemberToEntity(this.entity);
|
||||
|
||||
if (type != "Ranged")
|
||||
return this.MoveToTargetRange(target, IID_Attack, type);
|
||||
|
||||
if (!this.CheckTargetVisible(target))
|
||||
return false;
|
||||
|
||||
const cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
if (!cmpAttack)
|
||||
return false;
|
||||
const range = cmpAttack.GetRange(type);
|
||||
|
||||
// In case the range returns negative, we are probably too high compared to the target. Hope we come close enough.
|
||||
const parabolicMaxRange = Math.max(0, Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEffectiveParabolicRange(this.entity, target, range.max, cmpAttack.GetAttackYOrigin(type)));
|
||||
const flatRange = cmpAttack.GetRange(type);
|
||||
const effectiveRange = cmpAttack.GetEffectiveAttackRange(target, type);
|
||||
if (effectiveRange.max < 0)
|
||||
return false;
|
||||
|
||||
// The parabole changes while walking so be cautious:
|
||||
const guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange;
|
||||
// The parabola changes while walking so be cautious:
|
||||
const guessedMaxRange = effectiveRange.max > flatRange.max ?
|
||||
(flatRange.max + effectiveRange.max) / 2 :
|
||||
effectiveRange.max;
|
||||
|
||||
return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange);
|
||||
return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, effectiveRange.min, guessedMaxRange);
|
||||
};
|
||||
|
||||
UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
|
||||
|
|
@ -5372,8 +5393,16 @@ UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force)
|
|||
if (!this.AbleToMove())
|
||||
return false;
|
||||
|
||||
// Check if we should chase based on stance
|
||||
if (this.GetStance().respondChase)
|
||||
return true;
|
||||
{
|
||||
// If we're allowed to chase beyond vision, always chase
|
||||
if (this.GetStance().respondChaseBeyondVision)
|
||||
return true;
|
||||
|
||||
// Otherwise, only chase if the target is within our personal vision
|
||||
return this.CheckTargetIsInVisionRange(target);
|
||||
}
|
||||
|
||||
// If we are guarding/escorting, chase at least as long as the guarded unit is in target range of the attacker
|
||||
if (this.isGuardOf)
|
||||
|
|
@ -6339,9 +6368,22 @@ UnitAI.prototype.FindWalkAndFightTargets = function()
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the detection range for the given interface, adjusted by stance.
|
||||
*
|
||||
* The query range depends on stance because it represents the distance at which
|
||||
* the unit should "notice" an enemy and potentially start moving toward it.
|
||||
*
|
||||
* @param {number} iid - IID_Vision, IID_Heal, or IID_Attack
|
||||
* @returns {{min: number, max: number, base: number, parabolic: boolean}}
|
||||
* 'parabolic' indicates that the caller
|
||||
* should use a parabolic range query (accounting for elevation) instead of a
|
||||
* flat 2D one. Generally used for projectile attacks.
|
||||
* 'base' is a non-parabolic 2D detection range that always counts as in-range.
|
||||
*/
|
||||
UnitAI.prototype.GetQueryRange = function(iid)
|
||||
{
|
||||
const ret = { "min": 0, "max": 0 };
|
||||
const ret = { "min": 0, "max": 0, "base": 0, "parabolic": false };
|
||||
|
||||
const cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
|
||||
if (!cmpVision)
|
||||
|
|
@ -6354,27 +6396,35 @@ UnitAI.prototype.GetQueryRange = function(iid)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (this.GetStance().respondStandGround)
|
||||
{
|
||||
const range = this.GetRange(iid);
|
||||
if (!range)
|
||||
return ret;
|
||||
ret.min = range.min;
|
||||
ret.max = Math.min(range.max, visionRange);
|
||||
}
|
||||
else if (this.GetStance().respondChase)
|
||||
ret.max = visionRange;
|
||||
const range = this.GetRange(iid);
|
||||
if (!range)
|
||||
return ret;
|
||||
|
||||
// The query range depends on stance because it represents the distance at which
|
||||
// the unit should "notice" an enemy and potentially start moving toward it.
|
||||
|
||||
// In all stances, always spot targets within effective attack/heal range.
|
||||
Object.assign(ret, range);
|
||||
|
||||
let nonParabolicMax = 0;
|
||||
if (this.GetStance().respondChase)
|
||||
// Chase: Always spot targets within vision range, so we can chase them.
|
||||
nonParabolicMax = visionRange;
|
||||
else if (this.GetStance().respondHoldGround)
|
||||
{
|
||||
const range = this.GetRange(iid);
|
||||
if (!range)
|
||||
return ret;
|
||||
ret.max = Math.min(range.max + visionRange / 2, visionRange);
|
||||
}
|
||||
// HoldGround: willing to move a bit, so spot targets within attack range + half vision.
|
||||
nonParabolicMax = Math.min(range.max + visionRange / 2, visionRange);
|
||||
|
||||
// StandGround: nonParabolicMax stays 0, using only parabolic range.
|
||||
|
||||
// We probably have stance 'passive' and we wouldn't have a range,
|
||||
// but as it is the default for healers we need to set it to something sane.
|
||||
else if (iid === IID_Heal)
|
||||
ret.max = visionRange;
|
||||
nonParabolicMax = visionRange;
|
||||
|
||||
if (ret.parabolic)
|
||||
ret.base = nonParabolicMax;
|
||||
else
|
||||
ret.max = nonParabolicMax;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ Engine.LoadComponentScript("interfaces/Formation.js");
|
|||
Engine.LoadComponentScript("interfaces/Health.js");
|
||||
Engine.LoadComponentScript("interfaces/Resistance.js");
|
||||
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
|
||||
Engine.LoadComponentScript("interfaces/Turretable.js");
|
||||
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
||||
Engine.LoadComponentScript("Attack.js");
|
||||
|
||||
let entityID = 903;
|
||||
|
|
@ -52,6 +54,16 @@ function attackComponentTest(defenderClass, isEnemy, test_function)
|
|||
"IsEnemy": () => isEnemy
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => true
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||
"GetEffectiveParabolicRange": () => 25,
|
||||
"GetMaxReachableParabolicHeight": () => 15,
|
||||
"GetLosVisibility": (target, owner) => "visible"
|
||||
});
|
||||
|
||||
const attacker = entityID;
|
||||
|
||||
AddMock(attacker, IID_Position, {
|
||||
|
|
@ -201,7 +213,7 @@ attackComponentTest(undefined, true, (attacker, cmpAttack, defender) =>
|
|||
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["Civilian"]);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80, "parabolic": true });
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Capture"), { "Capture": 8 });
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged"), {
|
||||
|
|
@ -416,3 +428,101 @@ function testAttackPreference()
|
|||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+4), undefined);
|
||||
}
|
||||
testAttackPreference();
|
||||
|
||||
function testCanEverReachTarget()
|
||||
{
|
||||
const attacker = ++entityID;
|
||||
|
||||
AddMock(attacker, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 0,
|
||||
"GetPosition2D": () => new Vector2D(1, 2)
|
||||
});
|
||||
|
||||
const cmpAttack = ConstructComponent(attacker, "Attack", {
|
||||
"Melee": {
|
||||
"Damage": { "Hack": 10, "Pierce": 0, "Crush": 0 },
|
||||
"MaxRange": 5
|
||||
},
|
||||
"Ranged": {
|
||||
"Damage": { "Hack": 0, "Pierce": 10, "Crush": 0 },
|
||||
"MaxRange": 30,
|
||||
"Projectile": { "Speed": 50, "Spread": 1, "Gravity": 1, "FriendlyFire": "false" }
|
||||
}
|
||||
});
|
||||
|
||||
// Melee target within 3D range
|
||||
{
|
||||
const defender = ++entityID;
|
||||
AddMock(defender, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 0
|
||||
});
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Melee"), true);
|
||||
}
|
||||
|
||||
// Melee target too high
|
||||
{
|
||||
const defender = ++entityID;
|
||||
AddMock(defender, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 10
|
||||
});
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Melee"), false);
|
||||
}
|
||||
|
||||
// Melee target at same height, within range (close distance)
|
||||
{
|
||||
const defender = ++entityID;
|
||||
AddMock(defender, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 4
|
||||
});
|
||||
// sqrt(0² + 4²) = 4 <= 5
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Melee"), true);
|
||||
}
|
||||
|
||||
// Ranged: target at same height — reachable from current position (check 1)
|
||||
{
|
||||
const defender = ++entityID;
|
||||
AddMock(defender, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 0,
|
||||
"GetPosition": () => new Vector3D(1, 0, 2)
|
||||
});
|
||||
// Need RangeManager mock for IsTargetInRange (check 1) to work
|
||||
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||
"GetEffectiveParabolicRange": () => 25,
|
||||
"GetMaxReachableParabolicHeight": () => 15
|
||||
});
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => true
|
||||
});
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Ranged"), true);
|
||||
}
|
||||
|
||||
// Ranged: target too high for parabolic arc even at closest approach (check 2)
|
||||
{
|
||||
const defender = ++entityID;
|
||||
AddMock(defender, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 20,
|
||||
"GetPosition": () => new Vector3D(1, 20, 2)
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||
"GetEffectiveParabolicRange": () => -1, // out of range
|
||||
"GetMaxReachableParabolicHeight": () => 10
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => false
|
||||
});
|
||||
|
||||
// heightDiff = 20 - 0 = 20, maxReachableHeightDiff = 10 → unreachable
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Ranged"), false);
|
||||
}
|
||||
}
|
||||
testCanEverReachTarget();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const enemyPlayer = 2;
|
|||
const alliedPlayer = 3;
|
||||
const turretHolderID = 9;
|
||||
const entitiesToTest = [10, 11, 12, 13];
|
||||
let entityID = 100;
|
||||
|
||||
AddMock(turretHolderID, IID_Ownership, {
|
||||
"GetOwner": () => player
|
||||
|
|
@ -244,3 +245,80 @@ cmpTurretHolder.OnOwnershipChanged({
|
|||
"from": INVALID_PLAYER
|
||||
});
|
||||
TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned));
|
||||
|
||||
// Test GetClosestApproachDistanceToTurretPoint
|
||||
{
|
||||
const holder = ++entityID;
|
||||
|
||||
// Mock the holder's obstruction
|
||||
AddMock(holder, IID_Obstruction, {
|
||||
"GetBlockMovementFlag": () => true,
|
||||
"GetObstructionHalfSizes": () => ({ "x": 10, "y": 15 })
|
||||
});
|
||||
|
||||
const cmpHolder = ConstructComponent(holder, "TurretHolder", {
|
||||
"TurretPoints": {
|
||||
"center": {
|
||||
"X": "0",
|
||||
"Y": "5.0",
|
||||
"Z": "0"
|
||||
},
|
||||
"edge": {
|
||||
"X": "8.0",
|
||||
"Y": "5.0",
|
||||
"Z": "0"
|
||||
},
|
||||
"corner": {
|
||||
"X": "10.0",
|
||||
"Y": "5.0",
|
||||
"Z": "15.0"
|
||||
},
|
||||
"outside": {
|
||||
"X": "15.0",
|
||||
"Y": "5.0",
|
||||
"Z": "0"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Center point (0,0) in 20x30 building → min(10, 15) = 10
|
||||
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("center"), 10);
|
||||
|
||||
// Edge point (8,0) in 20x30 building → min(10-8, 15-0) = 2
|
||||
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("edge"), 2);
|
||||
|
||||
// Corner point (10,15) in 20x30 building → min(10-10, 15-15) = 0
|
||||
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("corner"), 0);
|
||||
|
||||
// Outside point (15,0) in 20x30 building → min(10-15, 15-0) = -5 → clamped to 0
|
||||
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("outside"), 0);
|
||||
|
||||
// Nonexistent turret point
|
||||
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("nonexistent"), 0);
|
||||
|
||||
// Pass object directly
|
||||
const turretPoint = cmpHolder.TurretPointByName("center");
|
||||
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint(turretPoint), 10);
|
||||
|
||||
// Passable building (no obstruction or doesn't block movement)
|
||||
const passableHolder = ++entityID;
|
||||
AddMock(passableHolder, IID_Obstruction, {
|
||||
"GetBlockMovementFlag": () => false
|
||||
});
|
||||
const cmpHolderPassable = ConstructComponent(passableHolder, "TurretHolder", {
|
||||
"TurretPoints": {
|
||||
"center": { "X": "0", "Y": "5.0", "Z": "0" }
|
||||
}
|
||||
});
|
||||
TS_ASSERT_EQUALS(cmpHolderPassable.GetClosestApproachDistanceToTurretPoint("center"), 0);
|
||||
|
||||
// No obstruction component at all
|
||||
++entityID;
|
||||
const cmpHolderNoObst = ConstructComponent(entityID, "TurretHolder", {
|
||||
"TurretPoints": {
|
||||
"center": { "X": "0", "Y": "5.0", "Z": "0" }
|
||||
}
|
||||
});
|
||||
TS_ASSERT_EQUALS(cmpHolderNoObst.GetClosestApproachDistanceToTurretPoint("center"), 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||
if (cmpVisual && cmpNewVisual)
|
||||
cmpNewVisual.SetActorSeed(cmpVisual.GetActorSeed());
|
||||
|
||||
// Set ownership so turret checks work properly
|
||||
const cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
|
||||
const cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
|
||||
if (cmpOwnership && cmpNewOwnership)
|
||||
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
|
||||
|
||||
const cmpOldTurretable = Engine.QueryInterface(oldEnt, IID_Turretable);
|
||||
|
||||
// If the old entity is turreted, we need to handle it before copying position
|
||||
|
|
@ -39,9 +45,15 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||
// Check if it's allowed to occupy the turret point
|
||||
const cmpTurretHolderOfOldEnt = Engine.QueryInterface(cmpOldTurretable.HolderID(), IID_TurretHolder);
|
||||
|
||||
if (cmpTurretHolderOfNewEnt &&
|
||||
!cmpTurretHolderOfOldEnt.AllowedToOccupyTurretPoint(newEnt, cmpOldTurretable.GetTurretPointName(), true))
|
||||
cmpOldTurretable.LeaveTurret(true);
|
||||
if (cmpTurretHolderOfOldEnt)
|
||||
{
|
||||
// Find the actual turret point object using the old entity
|
||||
const turretPoint = cmpTurretHolderOfOldEnt.GetOccupiedTurretPoint(oldEnt);
|
||||
|
||||
if (!turretPoint || !cmpTurretHolderOfOldEnt.AllowedToOccupyTurretPoint(newEnt, turretPoint, true))
|
||||
cmpOldTurretable.LeaveTurret(true);
|
||||
// If allowed, don't leave the turret - OnEntityRenamed will handle the swap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,24 +98,6 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||
for (const entity of cmpTurretHolder.GetEntities())
|
||||
cmpNewTurretHolder.SetReservedTurretPoint(cmpTurretHolder.GetOccupiedTurretPointName(entity));
|
||||
|
||||
let owner;
|
||||
const cmpTerritoryDecay = Engine.QueryInterface(newEnt, IID_TerritoryDecay);
|
||||
if (cmpTerritoryDecay && cmpTerritoryDecay.HasTerritoryOwnership() && cmpNewPosition)
|
||||
{
|
||||
const pos = cmpNewPosition.GetPosition2D();
|
||||
const cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
|
||||
owner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
const cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
|
||||
if (cmpOwnership)
|
||||
owner = cmpOwnership.GetOwner();
|
||||
}
|
||||
const cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
|
||||
if (cmpNewOwnership)
|
||||
cmpNewOwnership.SetOwner(owner);
|
||||
|
||||
CopyControlGroups(oldEnt, newEnt);
|
||||
|
||||
// Rescale capture points
|
||||
|
|
|
|||
|
|
@ -963,6 +963,11 @@ SDL_Window* CVideoMode::GetWindow()
|
|||
return m_Window;
|
||||
}
|
||||
|
||||
bool CVideoMode::IsInitialized() const
|
||||
{
|
||||
return m_IsInitialised;
|
||||
}
|
||||
|
||||
void CVideoMode::SetWindowIcon()
|
||||
{
|
||||
// The window icon should be kept outside of art/textures/, or else it will be converted
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ public:
|
|||
int GetBPP() const;
|
||||
|
||||
bool IsVSyncEnabled() const;
|
||||
bool IsInitialized() const;
|
||||
|
||||
int GetDesktopXRes() const;
|
||||
int GetDesktopYRes() const;
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ struct Query
|
|||
CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
|
||||
entity_pos_t minRange;
|
||||
entity_pos_t maxRange;
|
||||
entity_pos_t baseRange; // Non-parabolic detection range
|
||||
entity_pos_t yOrigin; // Used for parabolas only.
|
||||
u32 ownersMask;
|
||||
i32 interface;
|
||||
|
|
@ -195,6 +196,7 @@ struct Query
|
|||
bool enabled;
|
||||
bool parabolic;
|
||||
bool accountForSize; // If true, the query accounts for unit sizes, otherwise it treats all entities as points.
|
||||
bool includeMirage; // Include mirage entities regardless of interface checks
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -320,6 +322,7 @@ struct SerializeHelper<Query>
|
|||
{
|
||||
serialize.NumberFixed_Unbounded("min range", value.minRange);
|
||||
serialize.NumberFixed_Unbounded("max range", value.maxRange);
|
||||
serialize.NumberFixed_Unbounded("baseRange", value.baseRange);
|
||||
serialize.NumberFixed_Unbounded("yOrigin", value.yOrigin);
|
||||
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
|
||||
serialize.NumberI32_Unbounded("interface", value.interface);
|
||||
|
|
@ -328,6 +331,7 @@ struct SerializeHelper<Query>
|
|||
serialize.Bool("enabled", value.enabled);
|
||||
serialize.Bool("parabolic",value.parabolic);
|
||||
serialize.Bool("account for size",value.accountForSize);
|
||||
serialize.Bool("includeMirage", value.includeMirage);
|
||||
}
|
||||
|
||||
void operator()(ISerializer& serialize, const char* name, Query& value, const CSimContext&)
|
||||
|
|
@ -963,21 +967,24 @@ public:
|
|||
|
||||
tag_t CreateActiveQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) override
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags,
|
||||
bool accountForSize, bool includeMirage) override
|
||||
{
|
||||
tag_t id = m_QueryNext++;
|
||||
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize);
|
||||
|
||||
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize);
|
||||
q.includeMirage = includeMirage;
|
||||
m_Queries[id] = q;
|
||||
return id;
|
||||
}
|
||||
|
||||
tag_t CreateActiveParabolicQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags) override
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t baseRange, entity_pos_t yOrigin,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags, bool includeMirage = false) override
|
||||
{
|
||||
tag_t id = m_QueryNext++;
|
||||
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, yOrigin, owners, requiredInterface, flags, true);
|
||||
|
||||
Query q = ConstructParabolicQuery(source, minRange, maxRange, baseRange, yOrigin, owners, requiredInterface, flags, true);
|
||||
q.includeMirage = includeMirage;
|
||||
m_Queries[id] = q;
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
@ -1268,10 +1275,35 @@ public:
|
|||
if (id == q.source.GetId())
|
||||
return false;
|
||||
|
||||
// Ignore if it's missing the required interface
|
||||
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface))
|
||||
// Check if this is a mirage entity
|
||||
CmpPtr<ICmpMirage> cmpMirage(GetSimContext(), id);
|
||||
bool isMirage = !!cmpMirage;
|
||||
|
||||
// If it's a mirage and we're not including mirages, skip it
|
||||
if (isMirage && !q.includeMirage)
|
||||
return false;
|
||||
|
||||
// If it's not a mirage, check interface normally
|
||||
if (!isMirage && q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface))
|
||||
return false;
|
||||
|
||||
// Skip hidden entities (not visible to source player)
|
||||
if (q.source.GetId() != INVALID_ENTITY)
|
||||
{
|
||||
// Look up the source's current owner
|
||||
EntityMap<EntityData>::const_iterator itSource = m_EntityData.find(q.source.GetId());
|
||||
if (itSource != m_EntityData.end())
|
||||
{
|
||||
player_id_t sourceOwner = itSource->second.owner;
|
||||
if (sourceOwner != INVALID_PLAYER)
|
||||
{
|
||||
LosVisibility vis = GetPlayerVisibility(entity.visibilities, sourceOwner);
|
||||
if (vis == LosVisibility::HIDDEN)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1295,13 +1327,18 @@ public:
|
|||
// Not the entire world, so check a parabolic range, or a regular range.
|
||||
else if (q.parabolic)
|
||||
{
|
||||
// The yOrigin is part of the 3D position, as the source is really that much heigher.
|
||||
// The yOrigin is part of the 3D position, as the source is really that much higher.
|
||||
CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
|
||||
CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
|
||||
CFixedVector3D(entity_pos_t::Zero(), q.yOrigin, entity_pos_t::Zero()) ;
|
||||
// Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange.
|
||||
CFixedVector3D pos3d = cmpSourcePosition->GetPosition() +
|
||||
CFixedVector3D(entity_pos_t::Zero(), q.yOrigin, entity_pos_t::Zero());
|
||||
// Get a quick list of entities that are potentially in range.
|
||||
// For parabolic queries, the search radius must cover:
|
||||
// 1. The baseRange circle (non-parabolic detection)
|
||||
// 2. The maximum possible horizontal extent of the parabolic range
|
||||
// Multiplying maxRange by 2 provides a safe upper bound for all possible height differences.
|
||||
entity_pos_t subdivisionRange = std::max(q.baseRange, q.maxRange * 2);
|
||||
subdivisionResultsBuffer.clear();
|
||||
m_Subdivision.GetNear(subdivisionResultsBuffer, pos, q.maxRange * 2);
|
||||
m_Subdivision.GetNear(subdivisionResultsBuffer, pos, subdivisionRange);
|
||||
|
||||
for (size_t i = 0; i < subdivisionResultsBuffer.size(); ++i)
|
||||
{
|
||||
|
|
@ -1311,6 +1348,20 @@ public:
|
|||
if (!TestEntityQuery(q, it->first, it->second))
|
||||
continue;
|
||||
|
||||
CFixedVector2D delta2D = CFixedVector2D(it->second.x, it->second.z) - pos;
|
||||
|
||||
// Check base range first
|
||||
bool inBaseRange = !q.baseRange.IsZero() && delta2D.CompareLength(q.baseRange) <= 0;
|
||||
|
||||
if (inBaseRange)
|
||||
{
|
||||
// In base range - no need for parabolic check
|
||||
if (q.minRange.IsZero() || delta2D.CompareLength(q.minRange) >= 0)
|
||||
r.push_back(it->first);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parabolic check for entities outside base range
|
||||
CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), subdivisionResultsBuffer[i]);
|
||||
if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
|
||||
continue;
|
||||
|
|
@ -1328,7 +1379,7 @@ public:
|
|||
continue;
|
||||
|
||||
if (!q.minRange.IsZero())
|
||||
if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange) < 0)
|
||||
if (delta2D.CompareLength(q.minRange) < 0)
|
||||
continue;
|
||||
|
||||
r.push_back(it->first);
|
||||
|
|
@ -1365,6 +1416,22 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute effective horizontal range given a reference range and height difference.
|
||||
*/
|
||||
static entity_pos_t ComputeParabolicRange(entity_pos_t range, entity_pos_t heightDiff)
|
||||
{
|
||||
if (heightDiff < -range / 2)
|
||||
return NEVER_IN_RANGE;
|
||||
|
||||
entity_pos_t effectiveRange;
|
||||
effectiveRange.SetInternalValue(static_cast<i32>(isqrt64(
|
||||
SQUARE_U64_FIXED(range) +
|
||||
static_cast<i64>(heightDiff.GetInternalValue()) * static_cast<i64>(range.GetInternalValue()) * 2
|
||||
)));
|
||||
return effectiveRange;
|
||||
}
|
||||
|
||||
entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t yOrigin) const override
|
||||
{
|
||||
// For non-positive ranges, just return the range.
|
||||
|
|
@ -1379,13 +1446,32 @@ public:
|
|||
if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
|
||||
return NEVER_IN_RANGE;
|
||||
|
||||
entity_pos_t heightDifference = cmpSourcePosition->GetHeightOffset() - cmpTargetPosition->GetHeightOffset() + yOrigin;
|
||||
if (heightDifference < -range / 2)
|
||||
return NEVER_IN_RANGE;
|
||||
// GetPosition() returns the world height (terrain + water + offset)
|
||||
CFixedVector3D sourcePos = cmpSourcePosition->GetPosition();
|
||||
CFixedVector3D targetPos = cmpTargetPosition->GetPosition();
|
||||
|
||||
entity_pos_t effectiveRange;
|
||||
effectiveRange.SetInternalValue(static_cast<i32>(isqrt64(SQUARE_U64_FIXED(range) + static_cast<i64>(heightDifference.GetInternalValue()) * static_cast<i64>(range.GetInternalValue()) * 2)));
|
||||
return effectiveRange;
|
||||
entity_pos_t heightDiff = sourcePos.Y - targetPos.Y + yOrigin;
|
||||
return ComputeParabolicRange(range, heightDiff);
|
||||
}
|
||||
|
||||
entity_pos_t GetMaxReachableParabolicHeight(entity_pos_t range, entity_pos_t yOrigin, entity_pos_t horizDistance) const override
|
||||
{
|
||||
// EffectiveRange² = range² + 2 * range * heightDiff
|
||||
// Solve for heightDiff when effectiveRange = horizDistance:
|
||||
// heightDiff = (horizDistance² - range²) / (2 * range)
|
||||
// Max target height above source = yOrigin - heightDiff
|
||||
// = yOrigin + (range² - horizDistance²) / (2 * range)
|
||||
//
|
||||
// If horizDistance > range, the result is less than yOrigin (can be negative),
|
||||
// meaning the source must be above the target to compensate for the extra horizontal distance.
|
||||
// The caller can decide if that's acceptable.
|
||||
i64 rangeSq = SQUARE_U64_FIXED(range);
|
||||
i64 distSq = SQUARE_U64_FIXED(horizDistance);
|
||||
i64 numerator = rangeSq - distSq;
|
||||
|
||||
entity_pos_t result;
|
||||
result.SetInternalValue(static_cast<i32>(numerator / static_cast<i64>(range.GetInternalValue() * 2)));
|
||||
return yOrigin + result;
|
||||
}
|
||||
|
||||
entity_pos_t GetElevationAdaptedRange(const CFixedVector3D& pos1, const CFixedVector3D& rot, entity_pos_t range, entity_pos_t yOrigin, entity_pos_t angle) const override
|
||||
|
|
@ -1501,6 +1587,8 @@ public:
|
|||
if (maxRange < entity_pos_t::Zero() && maxRange != ALWAYS_IN_RANGE)
|
||||
LOGWARNING("CCmpRangeManager: Invalid max range %f in query for entity %u", maxRange.ToDouble(), source);
|
||||
|
||||
CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), source);
|
||||
|
||||
Query q;
|
||||
q.enabled = false;
|
||||
q.parabolic = false;
|
||||
|
|
@ -1509,6 +1597,7 @@ public:
|
|||
q.maxRange = maxRange;
|
||||
q.yOrigin = entity_pos_t::Zero();
|
||||
q.accountForSize = accountForSize;
|
||||
q.includeMirage = false;
|
||||
|
||||
if (q.accountForSize && q.source.GetId() != INVALID_ENTITY && q.maxRange != ALWAYS_IN_RANGE)
|
||||
{
|
||||
|
|
@ -1545,12 +1634,14 @@ public:
|
|||
}
|
||||
|
||||
Query ConstructParabolicQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t baseRange, entity_pos_t yOrigin,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
|
||||
{
|
||||
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flagsMask, accountForSize);
|
||||
q.parabolic = true;
|
||||
q.yOrigin = yOrigin;
|
||||
q.baseRange = baseRange;
|
||||
q.includeMirage = false;
|
||||
return q;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -45,6 +45,14 @@ std::string ICmpObstruction::CheckFoundation_wrapper(const std::string& classNam
|
|||
}
|
||||
}
|
||||
|
||||
CFixedVector2D ICmpObstruction::GetObstructionHalfSizes_wrapper() const
|
||||
{
|
||||
ICmpObstructionManager::ObstructionSquare square;
|
||||
if (!GetObstructionSquare(square))
|
||||
return CFixedVector2D(entity_pos_t::FromInt(-1), entity_pos_t::FromInt(-1));
|
||||
return CFixedVector2D(square.hw, square.hh);
|
||||
}
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(Obstruction)
|
||||
DEFINE_INTERFACE_METHOD("GetSize", ICmpObstruction, GetSize)
|
||||
DEFINE_INTERFACE_METHOD("CheckShorePlacement", ICmpObstruction, CheckShorePlacement)
|
||||
|
|
@ -55,6 +63,7 @@ DEFINE_INTERFACE_METHOD("GetEntitiesBlockingConstruction", ICmpObstruction, GetE
|
|||
DEFINE_INTERFACE_METHOD("GetEntitiesDeletedUponConstruction", ICmpObstruction, GetEntitiesDeletedUponConstruction)
|
||||
DEFINE_INTERFACE_METHOD("SetActive", ICmpObstruction, SetActive)
|
||||
DEFINE_INTERFACE_METHOD("SetDisableBlockMovementPathfinding", ICmpObstruction, SetDisableBlockMovementPathfinding)
|
||||
DEFINE_INTERFACE_METHOD("GetObstructionHalfSizes", ICmpObstruction, GetObstructionHalfSizes_wrapper)
|
||||
DEFINE_INTERFACE_METHOD("GetBlockMovementFlag", ICmpObstruction, GetBlockMovementFlag)
|
||||
DEFINE_INTERFACE_METHOD("SetControlGroup", ICmpObstruction, SetControlGroup)
|
||||
DEFINE_INTERFACE_METHOD("GetControlGroup", ICmpObstruction, GetControlGroup)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -104,6 +104,12 @@ public:
|
|||
*/
|
||||
virtual std::string CheckFoundation_wrapper(const std::string& className, bool onlyCenterPoint) const;
|
||||
|
||||
/**
|
||||
* GetObstructionSquare wrapper for script calls.
|
||||
* @return [hw, hh] half-sizes of the obstruction square, or empty array on failure.
|
||||
*/
|
||||
virtual CFixedVector2D GetObstructionHalfSizes_wrapper() const;
|
||||
|
||||
/**
|
||||
* Test whether this entity is colliding with any obstructions that share its
|
||||
* control groups and block the creation of foundations.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ DEFINE_INTERFACE_METHOD("GetLosRevealWholeMap", ICmpRangeManager, GetLosRevealWh
|
|||
DEFINE_INTERFACE_METHOD("SetLosRevealWholeMapForAll", ICmpRangeManager, SetLosRevealWholeMapForAll)
|
||||
DEFINE_INTERFACE_METHOD("GetLosRevealWholeMapForAll", ICmpRangeManager, GetLosRevealWholeMapForAll)
|
||||
DEFINE_INTERFACE_METHOD("GetEffectiveParabolicRange", ICmpRangeManager, GetEffectiveParabolicRange)
|
||||
DEFINE_INTERFACE_METHOD("GetMaxReachableParabolicHeight", ICmpRangeManager, GetMaxReachableParabolicHeight)
|
||||
DEFINE_INTERFACE_METHOD("GetElevationAdaptedRange", ICmpRangeManager, GetElevationAdaptedRange)
|
||||
DEFINE_INTERFACE_METHOD("ActivateScriptedVisibility", ICmpRangeManager, ActivateScriptedVisibility)
|
||||
DEFINE_INTERFACE_METHOD("GetLosVisibility", ICmpRangeManager, GetLosVisibility_wrapper)
|
||||
|
|
|
|||
|
|
@ -161,29 +161,39 @@ public:
|
|||
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
|
||||
* @param flags if a entity in range has one of the flags set it will show up.
|
||||
* @param accountForSize if true, compensate for source/target entity sizes.
|
||||
* @param includeMirage if true, mirage entities are included in results without
|
||||
* interface checks (needed for targeting fogged enemies). Default false.
|
||||
* When false (default), mirages are excluded entirely.
|
||||
* @return unique non-zero identifier of query.
|
||||
*/
|
||||
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) = 0;
|
||||
virtual tag_t CreateActiveQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags,
|
||||
bool accountForSize, bool includeMirage = false) = 0;
|
||||
|
||||
/**
|
||||
* Construct an active query of a paraboloic form around the unit.
|
||||
/**
|
||||
* Construct an active query of a parabolic form around the unit.
|
||||
* The query will be disabled by default.
|
||||
* @param source the entity around which the range will be computed.
|
||||
* @param minRange non-negative minimum horizontal distance in metres (inclusive). MinRange doesn't do parabolic checks.
|
||||
* @param maxRange non-negative maximum distance in metres (inclusive) for units on the same elevation;
|
||||
* or -1.0 to ignore distance.
|
||||
* For units on a different height positions, a physical correct paraboloid with height=maxRange/2 above the unit is used to query them
|
||||
* @param baseRange non-negative base detection range in metres (inclusive) for simple 2D circle checks.
|
||||
* Units within this horizontal distance are always considered in range regardless of height.
|
||||
* Set to 0 to disable (original parabolic-only behavior).
|
||||
* @param yOrigin extra bonus so the source can be placed higher and shoot further
|
||||
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
|
||||
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
|
||||
* @param flags if a entity in range has one of the flags set it will show up.
|
||||
* @param includeMirage if true, mirage entities are included in results without
|
||||
* interface checks (needed for targeting fogged enemies). Default false.
|
||||
* NB: this one has no accountForSize parameter (assumed true), because we currently can only have 7 arguments for JS functions.
|
||||
* @return unique non-zero identifier of query.
|
||||
*/
|
||||
virtual tag_t CreateActiveParabolicQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags) = 0;
|
||||
|
||||
virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t baseRange, entity_pos_t yOrigin,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flags, bool includeMirage = false) = 0;
|
||||
|
||||
/**
|
||||
* Get the effective range in a parablic range query.
|
||||
|
|
@ -195,6 +205,18 @@ public:
|
|||
*/
|
||||
virtual entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t yOrigin) const = 0;
|
||||
|
||||
/**
|
||||
* Get the max height (relative to the source) a parabolic projectile can reach
|
||||
* at a given horizontal distance.
|
||||
* @param source the entity at the origin.
|
||||
* @param range the maximum parabolic range on flat terrain.
|
||||
* @param yOrigin height bonus for the source.
|
||||
* @param horizDistance the horizontal distance to check.
|
||||
* @return the maximum reachable height difference (target height - source height),
|
||||
* or a very negative value if the horizontal distance exceeds the range.
|
||||
*/
|
||||
virtual entity_pos_t GetMaxReachableParabolicHeight(entity_pos_t range, entity_pos_t yOrigin, entity_pos_t horizDistance) const = 0;
|
||||
|
||||
/**
|
||||
* Get the average elevation over 8 points on distance range around the entity
|
||||
* @param id the entity id to look around
|
||||
|
|
|
|||
|
|
@ -62,13 +62,13 @@ public:
|
|||
entity_id_t GetTurretParent() const override {return INVALID_ENTITY;}
|
||||
void UpdateTurretPosition() override {}
|
||||
std::set<entity_id_t>* GetTurrets() override { return nullptr; }
|
||||
bool IsInWorld() const override { return true; }
|
||||
void MoveOutOfWorld() override { }
|
||||
bool IsInWorld() const override { return m_InWorld; }
|
||||
void MoveOutOfWorld() override { m_InWorld = false; }
|
||||
void MoveTo(entity_pos_t /*x*/, entity_pos_t /*z*/) override { }
|
||||
void MoveAndTurnTo(entity_pos_t /*x*/, entity_pos_t /*z*/, entity_angle_t /*a*/) override { }
|
||||
void JumpTo(entity_pos_t /*x*/, entity_pos_t /*z*/) override { }
|
||||
void SetHeightOffset(entity_pos_t /*dy*/) override { }
|
||||
entity_pos_t GetHeightOffset() const override { return entity_pos_t::Zero(); }
|
||||
void SetHeightOffset(entity_pos_t dy) override { m_HeightOffset = dy; }
|
||||
entity_pos_t GetHeightOffset() const override { return m_HeightOffset; }
|
||||
void SetHeightFixed(entity_pos_t /*y*/) override { }
|
||||
entity_pos_t GetHeightFixed() const override { return entity_pos_t::Zero(); }
|
||||
entity_pos_t GetHeightAtFixed(entity_pos_t, entity_pos_t) const override { return entity_pos_t::Zero(); }
|
||||
|
|
@ -93,6 +93,8 @@ public:
|
|||
CMatrix3D GetInterpolatedTransform(float /*frameOffset*/) const override { return CMatrix3D(); }
|
||||
|
||||
CFixedVector3D m_Pos;
|
||||
entity_pos_t m_HeightOffset = entity_pos_t::Zero();
|
||||
bool m_InWorld = true;
|
||||
};
|
||||
|
||||
class MockObstructionRgm : public ICmpObstruction
|
||||
|
|
@ -153,7 +155,7 @@ public:
|
|||
{
|
||||
ComponentTestHelper test(*g_ScriptContext);
|
||||
|
||||
ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
ICmpRangeManager* rangeManager = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
MockVisionRgm vision;
|
||||
test.AddMock(100, IID_Vision, vision);
|
||||
|
|
@ -164,41 +166,41 @@ public:
|
|||
// This tests that the incremental computation produces the correct result
|
||||
// in various edge cases
|
||||
|
||||
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
|
||||
cmp->Verify();
|
||||
{ CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromDouble(257.95), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromInt(253), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
rangeManager->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
|
||||
rangeManager->Verify();
|
||||
{ CMessageCreate msg(100); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessageOwnershipChanged msg(100, -1, 1); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromDouble(257.95), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromInt(253), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(383), entity_pos_t::FromInt(84), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(348), entity_pos_t::FromInt(83), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(383), entity_pos_t::FromInt(84), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(348), entity_pos_t::FromInt(83), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
|
||||
std::mt19937 rng;
|
||||
for (size_t i = 0; i < 1024; ++i)
|
||||
{
|
||||
double x = std::uniform_real_distribution<double>(0.0, 512.0)(rng);
|
||||
double z = std::uniform_real_distribution<double>(0.0, 512.0)(rng);
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromDouble(x), entity_pos_t::FromDouble(z), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
cmp->Verify();
|
||||
{ CMessagePositionChanged msg(100, true, entity_pos_t::FromDouble(x), entity_pos_t::FromDouble(z), entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
rangeManager->Verify();
|
||||
}
|
||||
|
||||
// Test OwnershipChange, GetEntitiesByPlayer, GetNonGaiaEntities
|
||||
|
|
@ -207,22 +209,22 @@ public:
|
|||
for (player_id_t newOwner = 0; newOwner < 8; ++newOwner)
|
||||
{
|
||||
CMessageOwnershipChanged msg(100, previousOwner, newOwner);
|
||||
cmp->HandleMessage(msg, false);
|
||||
rangeManager->HandleMessage(msg, false);
|
||||
|
||||
for (player_id_t i = 0; i < 8; ++i)
|
||||
TS_ASSERT_EQUALS(cmp->GetEntitiesByPlayer(i).size(), i == newOwner ? 1 : 0);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEntitiesByPlayer(i).size(), i == newOwner ? 1 : 0);
|
||||
|
||||
TS_ASSERT_EQUALS(cmp->GetNonGaiaEntities().size(), newOwner > 0 ? 1 : 0);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetNonGaiaEntities().size(), newOwner > 0 ? 1 : 0);
|
||||
previousOwner = newOwner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_queries()
|
||||
void test_range_queries_distance_only()
|
||||
{
|
||||
ComponentTestHelper test(*g_ScriptContext);
|
||||
|
||||
ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
ICmpRangeManager* rangeManager = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
MockVisionRgm vision, vision2;
|
||||
MockPositionRgm position, position2;
|
||||
|
|
@ -235,100 +237,236 @@ public:
|
|||
test.AddMock(101, IID_Position, position2);
|
||||
test.AddMock(101, IID_Obstruction, obs2);
|
||||
|
||||
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
|
||||
cmp->Verify();
|
||||
{ CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
|
||||
{ CMessageCreate msg(101); cmp->HandleMessage(msg, false); }
|
||||
rangeManager->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
|
||||
rangeManager->Verify();
|
||||
{ CMessageCreate msg(100); rangeManager->HandleMessage(msg, false); }
|
||||
{ CMessageCreate msg(101); rangeManager->HandleMessage(msg, false); }
|
||||
|
||||
{ CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); }
|
||||
{ CMessageOwnershipChanged msg(101, -1, 1); cmp->HandleMessage(msg, false); }
|
||||
// Don't set ownership for either entity - leave both as INVALID_PLAYER.
|
||||
// This bypasses the visibility check in TestEntityQuery, allowing us to test
|
||||
// the core distance calculation logic independently of the LOS system.
|
||||
|
||||
auto move = [&cmp](entity_id_t ent, MockPositionRgm& pos, fixed x, fixed z) {
|
||||
auto move = [&rangeManager](entity_id_t ent, MockPositionRgm& pos, fixed x, fixed z) {
|
||||
pos.m_Pos = CFixedVector3D(x, fixed::Zero(), z);
|
||||
{ CMessagePositionChanged msg(ent, true, x, z, entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
|
||||
{ CMessagePositionChanged msg(ent, true, x, z, entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
};
|
||||
|
||||
move(100, position, fixed::FromInt(10), fixed::FromInt(10));
|
||||
move(101, position2, fixed::FromInt(10), fixed::FromInt(20));
|
||||
|
||||
std::vector<entity_id_t> nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
|
||||
// Query for owner -1 (INVALID_PLAYER) since both entities have no owner
|
||||
std::vector<entity_id_t> nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
|
||||
move(101, position2, fixed::FromInt(10), fixed::FromInt(10));
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
|
||||
|
||||
move(101, position2, fixed::FromInt(10), fixed::FromInt(13));
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
|
||||
|
||||
move(101, position2, fixed::FromInt(10), fixed::FromInt(15));
|
||||
// In range thanks to self obstruction size.
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
// In range thanks to target obstruction size.
|
||||
nearby = cmp->ExecuteQuery(101, fixed::FromInt(0), fixed::FromInt(4), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(101, fixed::FromInt(0), fixed::FromInt(4), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{100});
|
||||
|
||||
// Trickier: min-range is closest-to-closest, but rotation may change the real distance.
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(2), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(2), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(5), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(5), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
nearby = cmp->ExecuteQuery(100, fixed::FromInt(6), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(6), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
|
||||
nearby = cmp->ExecuteQuery(101, fixed::FromInt(5), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(101, fixed::FromInt(5), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{100});
|
||||
nearby = cmp->ExecuteQuery(101, fixed::FromInt(6), fixed::FromInt(50), {1}, 0, true);
|
||||
nearby = rangeManager->ExecuteQuery(101, fixed::FromInt(6), fixed::FromInt(50), {-1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
|
||||
|
||||
}
|
||||
|
||||
void test_IsInTargetParabolicRange()
|
||||
void test_range_queries_visibility_filtering()
|
||||
{
|
||||
ComponentTestHelper test(*g_ScriptContext);
|
||||
ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
ICmpRangeManager* rangeManager = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
MockVisionRgm vision, vision2;
|
||||
MockPositionRgm position, position2;
|
||||
MockObstructionRgm obs(fixed::FromInt(2)), obs2(fixed::Zero());
|
||||
test.AddMock(100, IID_Vision, vision);
|
||||
test.AddMock(100, IID_Position, position);
|
||||
test.AddMock(100, IID_Obstruction, obs);
|
||||
|
||||
test.AddMock(101, IID_Vision, vision2);
|
||||
test.AddMock(101, IID_Position, position2);
|
||||
test.AddMock(101, IID_Obstruction, obs2);
|
||||
|
||||
rangeManager->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
|
||||
rangeManager->Verify();
|
||||
{ CMessageCreate msg(100); rangeManager->HandleMessage(msg, false); }
|
||||
{ CMessageCreate msg(101); rangeManager->HandleMessage(msg, false); }
|
||||
|
||||
// Set ownership for both entities so they have proper owners
|
||||
{ CMessageOwnershipChanged msg(100, -1, 1); rangeManager->HandleMessage(msg, false); }
|
||||
{ CMessageOwnershipChanged msg(101, -1, 1); rangeManager->HandleMessage(msg, false); }
|
||||
|
||||
auto move = [&rangeManager](entity_id_t ent, MockPositionRgm& pos, fixed x, fixed z) {
|
||||
pos.m_Pos = CFixedVector3D(x, fixed::Zero(), z);
|
||||
{ CMessagePositionChanged msg(ent, true, x, z, entity_angle_t::Zero()); rangeManager->HandleMessage(msg, false); }
|
||||
};
|
||||
|
||||
move(100, position, fixed::FromInt(10), fixed::FromInt(10));
|
||||
move(101, position2, fixed::FromInt(10), fixed::FromInt(15));
|
||||
|
||||
// Note: Full LOS testing (vision range, terrain, fog) requires a full game world
|
||||
// with terrain, water, and proper pathfinding. That's beyond the scope of this
|
||||
// unit test. The visibility test here verifies that ExecuteQuery respects the
|
||||
// reveal whole map flag, which exercises the visibility check path in TestEntityQuery.
|
||||
|
||||
// Enable "reveal whole map" to force all entities to be visible
|
||||
rangeManager->SetLosRevealWholeMap(1, true);
|
||||
|
||||
// Process an update
|
||||
{ CMessageUpdate msg(fixed::FromInt(1)); rangeManager->HandleMessage(msg, false); }
|
||||
|
||||
// Entity 101 should be visible (due to reveal map) and in range
|
||||
std::vector<entity_id_t> nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(50), {1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
|
||||
// Disable "reveal whole map" to test hidden entities
|
||||
rangeManager->SetLosRevealWholeMap(1, false);
|
||||
{ CMessageUpdate msg(fixed::FromInt(1)); rangeManager->HandleMessage(msg, false); }
|
||||
|
||||
// Entity 101 should now be hidden because LOS isn't properly set up
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(50), {1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{});
|
||||
|
||||
// Re-enable reveal map to show it works again
|
||||
rangeManager->SetLosRevealWholeMap(1, true);
|
||||
{ CMessageUpdate msg(fixed::FromInt(1)); rangeManager->HandleMessage(msg, false); }
|
||||
|
||||
nearby = rangeManager->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(50), {1}, 0, true);
|
||||
TS_ASSERT_EQUALS(nearby, std::vector<entity_id_t>{101});
|
||||
}
|
||||
|
||||
void test_ParabolicRangeBasic()
|
||||
{
|
||||
ComponentTestHelper test(*g_ScriptContext);
|
||||
ICmpRangeManager* rangeManager = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
const entity_id_t source = 200;
|
||||
const entity_id_t target = 201;
|
||||
entity_pos_t range = fixed::FromInt(-3);
|
||||
entity_pos_t yOrigin = fixed::FromInt(-20);
|
||||
entity_pos_t range{fixed::FromInt(-3)};
|
||||
entity_pos_t yOrigin{fixed::FromInt(-20)};
|
||||
|
||||
// Invalid range.
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), range);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), range);
|
||||
|
||||
// No source ICmpPosition.
|
||||
range = fixed::FromInt(10);
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
|
||||
// No target ICmpPosition.
|
||||
MockPositionRgm cmpSourcePosition;
|
||||
test.AddMock(source, IID_Position, cmpSourcePosition);
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
|
||||
// Too much height difference.
|
||||
MockPositionRgm cmpTargetPosition;
|
||||
test.AddMock(target, IID_Position, cmpTargetPosition);
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
|
||||
// If no offset we get the range.
|
||||
range = fixed::FromInt(20);
|
||||
yOrigin = fixed::Zero();
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), range);
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, fixed::Zero(), yOrigin), fixed::Zero());
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), range);
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, fixed::Zero(), yOrigin), fixed::Zero());
|
||||
|
||||
// Normal case.
|
||||
// Normal case with yOrigin only (no terrain difference)
|
||||
yOrigin = fixed::FromInt(5);
|
||||
range = fixed::FromInt(10);
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), fixed::FromFloat(14.142136f));
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), fixed::FromFloat(14.142136f));
|
||||
|
||||
// Big range.
|
||||
range = fixed::FromInt(260);
|
||||
TS_ASSERT_EQUALS(cmp->GetEffectiveParabolicRange(source, target, range, yOrigin), fixed::FromFloat(264.952820f));
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), fixed::FromFloat(264.952820f));
|
||||
}
|
||||
|
||||
void test_ParabolicRangeWithTerrain()
|
||||
{
|
||||
ComponentTestHelper test(*g_ScriptContext);
|
||||
ICmpRangeManager* rangeManager = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
const entity_id_t source{200};
|
||||
const entity_id_t target{201};
|
||||
|
||||
MockPositionRgm sourcePos;
|
||||
MockPositionRgm targetPos;
|
||||
test.AddMock(source, IID_Position, sourcePos);
|
||||
test.AddMock(target, IID_Position, targetPos);
|
||||
|
||||
const entity_pos_t range{fixed::FromInt(100)};
|
||||
const entity_pos_t yOrigin{fixed::Zero()};
|
||||
|
||||
// Source on high ground (Y=10), target on low ground (Y=0)
|
||||
sourcePos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::FromInt(10), fixed::Zero());
|
||||
targetPos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::Zero(), fixed::FromInt(50));
|
||||
entity_pos_t effective = rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin);
|
||||
TS_ASSERT_DELTA(effective.ToFloat(), 109.5445f, 0.01f); // ~109.54
|
||||
|
||||
// Source on low ground (Y=0), target on high ground (Y=10)
|
||||
sourcePos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::Zero(), fixed::Zero());
|
||||
targetPos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::FromInt(10), fixed::FromInt(50));
|
||||
effective = rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin);
|
||||
TS_ASSERT_DELTA(effective.ToFloat(), 89.4427f, 0.01f); // ~89.44
|
||||
|
||||
// Source with height offset (Y=15), target on flat ground (Y=0), with yOrigin
|
||||
sourcePos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::FromInt(15), fixed::Zero());
|
||||
targetPos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::Zero(), fixed::FromInt(50));
|
||||
const entity_pos_t yOrigin2{fixed::FromInt(2)};
|
||||
effective = rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin2);
|
||||
TS_ASSERT_DELTA(effective.ToFloat(), 115.7583f, 0.01f); // ~115.76
|
||||
}
|
||||
|
||||
void test_ParabolicRangeTargetTooHigh()
|
||||
{
|
||||
ComponentTestHelper test(*g_ScriptContext);
|
||||
ICmpRangeManager* rangeManager = test.Add<ICmpRangeManager>(CID_RangeManager, "", SYSTEM_ENTITY);
|
||||
|
||||
const entity_id_t source{200};
|
||||
const entity_id_t target{201};
|
||||
|
||||
MockPositionRgm sourcePos;
|
||||
MockPositionRgm targetPos;
|
||||
test.AddMock(source, IID_Position, sourcePos);
|
||||
test.AddMock(target, IID_Position, targetPos);
|
||||
|
||||
// Source on flat ground (height=0), target very high (height=30)
|
||||
sourcePos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::Zero(), fixed::Zero());
|
||||
targetPos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::FromInt(30), fixed::Zero());
|
||||
|
||||
const entity_pos_t range{fixed::FromInt(50)};
|
||||
const entity_pos_t yOrigin{fixed::Zero()};
|
||||
|
||||
// heightDifference = 0 - 30 = -30, range/2 = 25
|
||||
// -30 < -25 → NEVER_IN_RANGE
|
||||
TS_ASSERT_EQUALS(rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin), NEVER_IN_RANGE);
|
||||
|
||||
// Target at borderline height (25)
|
||||
targetPos.m_Pos = CFixedVector3D(fixed::Zero(), fixed::FromInt(25), fixed::Zero());
|
||||
|
||||
const entity_pos_t effective = rangeManager->GetEffectiveParabolicRange(source, target, range, yOrigin);
|
||||
TS_ASSERT_DIFFERS(effective, NEVER_IN_RANGE);
|
||||
TS_ASSERT_EQUALS(effective, fixed::Zero());
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@
|
|||
|
||||
class wxWindow;
|
||||
|
||||
BEGIN_EVENT_TABLE(ActorEditor, AtlasWindow)
|
||||
wxBEGIN_EVENT_TABLE(ActorEditor, AtlasWindow)
|
||||
EVT_MENU(ID_CreateEntity, ActorEditor::OnCreateEntity)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
|
||||
ActorEditor::ActorEditor(wxWindow* parent)
|
||||
|
|
|
|||
|
|
@ -61,5 +61,5 @@ private:
|
|||
// but should be persisted so for convenience keep a copy of the last loaded file.
|
||||
AtObj m_Actor;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -39,9 +39,9 @@
|
|||
|
||||
class wxWindow;
|
||||
|
||||
BEGIN_EVENT_TABLE(ToolButton, wxButton)
|
||||
wxBEGIN_EVENT_TABLE(ToolButton, wxButton)
|
||||
EVT_BUTTON(wxID_ANY, ToolButton::OnClick)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
ToolButton::ToolButton
|
||||
(ToolManager& toolManager, wxWindow *parent, const wxString& label, const wxString& toolName, const wxSize& size, long style)
|
||||
|
|
@ -77,9 +77,9 @@ void ToolButton::SetSelectedAppearance(bool selected)
|
|||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BEGIN_EVENT_TABLE(ToolButtonBar, wxToolBar)
|
||||
wxBEGIN_EVENT_TABLE(ToolButtonBar, wxToolBar)
|
||||
EVT_TOOL(wxID_ANY, ToolButtonBar::OnTool)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
ToolButtonBar::ToolButtonBar(ToolManager& toolManager, wxWindow* parent, SectionLayout* sectionLayout, int baseID, long style)
|
||||
: wxToolBar(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ private:
|
|||
wxString m_Tool;
|
||||
bool m_Selected;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
class ToolButtonBar : public wxToolBar
|
||||
|
|
@ -68,5 +68,5 @@ private:
|
|||
std::map<int, Button> m_Buttons;
|
||||
SectionLayout* m_SectionLayout;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -107,7 +107,7 @@ void Canvas::OnMouse(wxMouseEvent& evt)
|
|||
HandleMouseEvent(evt);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(Canvas, wxGLCanvas)
|
||||
wxBEGIN_EVENT_TABLE(Canvas, wxGLCanvas)
|
||||
EVT_SIZE (Canvas::OnResize)
|
||||
EVT_LEFT_DCLICK (Canvas::OnMouse)
|
||||
EVT_LEFT_DOWN (Canvas::OnMouse)
|
||||
|
|
@ -121,4 +121,4 @@ BEGIN_EVENT_TABLE(Canvas, wxGLCanvas)
|
|||
EVT_MOUSEWHEEL (Canvas::OnMouse)
|
||||
EVT_MOTION (Canvas::OnMouse)
|
||||
EVT_MOUSE_CAPTURE_LOST(Canvas::OnMouseCaptureLost)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -45,5 +45,5 @@ private:
|
|||
wxPoint m_LastMousePos;
|
||||
bool m_MouseCaptured;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -153,11 +153,11 @@ void DraggableListCtrl::OnChar(wxKeyEvent& event)
|
|||
}
|
||||
|
||||
|
||||
BEGIN_EVENT_TABLE(DraggableListCtrl, EditableListCtrl)
|
||||
wxBEGIN_EVENT_TABLE(DraggableListCtrl, EditableListCtrl)
|
||||
EVT_LIST_BEGIN_DRAG(wxID_ANY, DraggableListCtrl::OnBeginDrag)
|
||||
EVT_LIST_ITEM_SELECTED(wxID_ANY, DraggableListCtrl::OnItemSelected)
|
||||
EVT_MOTION(DraggableListCtrl::OnMouseEvent)
|
||||
EVT_LEFT_UP(DraggableListCtrl::OnMouseEvent)
|
||||
EVT_CHAR(DraggableListCtrl::OnChar)
|
||||
EVT_MOUSE_CAPTURE_LOST(DraggableListCtrl::OnMouseCaptureLost)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public:
|
|||
private:
|
||||
long m_DragSource;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_DRAGGABLELISTCTRL
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -321,8 +321,8 @@ AtObj EditableListCtrl::FreezeData()
|
|||
|
||||
|
||||
|
||||
BEGIN_EVENT_TABLE(EditableListCtrl, wxListCtrl)
|
||||
wxBEGIN_EVENT_TABLE(EditableListCtrl, wxListCtrl)
|
||||
EVT_LEFT_DCLICK(EditableListCtrl::OnMouseEvent)
|
||||
EVT_RIGHT_DOWN(EditableListCtrl::OnMouseEvent)
|
||||
EVT_CHAR(EditableListCtrl::OnKeyDown)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ protected:
|
|||
|
||||
wxListItemAttr m_ListItemAttr[2]; // standard+alternate colors
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_EDITABLELISTCTRL
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -67,7 +67,7 @@ void QuickComboBox::OnChar(wxKeyEvent& event)
|
|||
event.Skip();
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(QuickComboBox, wxComboBox)
|
||||
wxBEGIN_EVENT_TABLE(QuickComboBox, wxComboBox)
|
||||
EVT_KILL_FOCUS(QuickComboBox::OnKillFocus)
|
||||
EVT_CHAR(QuickComboBox::OnChar)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -32,5 +32,5 @@ public:
|
|||
void OnChar(wxKeyEvent& event);
|
||||
|
||||
private:
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -79,13 +79,13 @@ public:
|
|||
event.Skip();
|
||||
}
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(FileCtrl_TextCtrl, wxTextCtrl)
|
||||
wxBEGIN_EVENT_TABLE(FileCtrl_TextCtrl, wxTextCtrl)
|
||||
EVT_KILL_FOCUS(FileCtrl_TextCtrl::OnKillFocus)
|
||||
EVT_CHAR(FileCtrl_TextCtrl::OnChar)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
class FileCtrl_Button : public wxButton
|
||||
{
|
||||
|
|
@ -105,13 +105,13 @@ public:
|
|||
|
||||
virtual void OnPress(wxCommandEvent& event)=0;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(FileCtrl_Button, wxButton)
|
||||
wxBEGIN_EVENT_TABLE(FileCtrl_Button, wxButton)
|
||||
EVT_KILL_FOCUS(FileCtrl_Button::OnKillFocus)
|
||||
EVT_BUTTON(wxID_ANY, FileCtrl_Button::OnPress)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -58,7 +58,7 @@ void QuickTextCtrl::OnChar(wxKeyEvent& event)
|
|||
event.Skip();
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(QuickTextCtrl, wxTextCtrl)
|
||||
wxBEGIN_EVENT_TABLE(QuickTextCtrl, wxTextCtrl)
|
||||
EVT_KILL_FOCUS(QuickTextCtrl::OnKillFocus)
|
||||
EVT_CHAR(QuickTextCtrl::OnChar)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -31,5 +31,5 @@ public:
|
|||
void OnChar(wxKeyEvent& event);
|
||||
|
||||
private:
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -265,7 +265,7 @@ void MapDialog::SaveFile()
|
|||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(MapDialog, wxDialog)
|
||||
wxBEGIN_EVENT_TABLE(MapDialog, wxDialog)
|
||||
EVT_BUTTON (wxID_CANCEL, MapDialog::OnCancel)
|
||||
EVT_BUTTON (wxID_OPEN, MapDialog::OnOpen)
|
||||
EVT_BUTTON (wxID_SAVE, MapDialog::OnSave)
|
||||
|
|
@ -273,4 +273,4 @@ BEGIN_EVENT_TABLE(MapDialog, wxDialog)
|
|||
EVT_LISTBOX_DCLICK (wxID_ANY, MapDialog::OnListBox)
|
||||
EVT_TEXT (ID_MapDialogFilename, MapDialog::OnFilename)
|
||||
EVT_NOTEBOOK_PAGE_CHANGED (ID_MapDialogNotebook, MapDialog::OnNotebookChanged)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ private:
|
|||
wxString m_FileName;
|
||||
MapDialogType m_Type;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_MAPDIALOG
|
||||
|
|
|
|||
|
|
@ -126,9 +126,9 @@ void MapResizeDialog::OnOK(wxCommandEvent& WXUNUSED(evt))
|
|||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(MapResizeDialog, wxDialog)
|
||||
wxBEGIN_EVENT_TABLE(MapResizeDialog, wxDialog)
|
||||
EVT_BUTTON(wxID_CANCEL, MapResizeDialog::OnCancel)
|
||||
EVT_BUTTON(wxID_OK, MapResizeDialog::OnOK)
|
||||
EVT_LISTBOX(wxID_ANY, MapResizeDialog::OnListBox)
|
||||
EVT_LISTBOX_DCLICK(wxID_ANY, MapResizeDialog::OnListBox)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ private:
|
|||
ssize_t m_NewSize;
|
||||
PseudoMiniMapPanel* m_MiniMap;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_MAPRESIZEDIALOG
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ void PseudoMiniMapPanel::EraseBackground(wxEraseEvent& WXUNUSED(evt))
|
|||
// Do nothing - don't erase to remove flicker.
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(PseudoMiniMapPanel, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(PseudoMiniMapPanel, wxPanel)
|
||||
EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseUp)
|
||||
EVT_LEFT_DOWN(PseudoMiniMapPanel::OnMouseDown)
|
||||
EVT_LEFT_UP(PseudoMiniMapPanel::OnMouseUp)
|
||||
|
|
@ -239,4 +239,4 @@ BEGIN_EVENT_TABLE(PseudoMiniMapPanel, wxPanel)
|
|||
EVT_MOTION(PseudoMiniMapPanel::OnMouseMove)
|
||||
EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseLeave)
|
||||
EVT_PAINT(PseudoMiniMapPanel::PaintEvent)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ private:
|
|||
bool m_SameOrGrowing;
|
||||
ssize_t m_NewSize;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_PSEUDOMINIMAPPANEL
|
||||
|
|
|
|||
|
|
@ -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,11 +27,11 @@
|
|||
|
||||
class wxWindow;
|
||||
|
||||
BEGIN_EVENT_TABLE(SnapSplitterWindow, wxSplitterWindow)
|
||||
wxBEGIN_EVENT_TABLE(SnapSplitterWindow, wxSplitterWindow)
|
||||
EVT_SPLITTER_SASH_POS_CHANGING(wxID_ANY, SnapSplitterWindow::OnSashPosChanging)
|
||||
EVT_SPLITTER_SASH_POS_CHANGED(wxID_ANY, SnapSplitterWindow::OnSashPosChanged)
|
||||
EVT_SPLITTER_DCLICK(wxID_ANY, SnapSplitterWindow::OnDoubleClick)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
SnapSplitterWindow::SnapSplitterWindow(wxWindow* parent, long style, const wxString& configPath)
|
||||
: wxSplitterWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
||||
|
|
|
|||
|
|
@ -43,5 +43,5 @@ private:
|
|||
int m_SnapTolerance;
|
||||
wxString m_ConfigPath;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -48,9 +48,9 @@ class wxWindow;
|
|||
|
||||
// WDR: event table for wxVirtualDirTreeCtrl
|
||||
|
||||
BEGIN_EVENT_TABLE(wxVirtualDirTreeCtrl, wxTreeCtrl)
|
||||
wxBEGIN_EVENT_TABLE(wxVirtualDirTreeCtrl, wxTreeCtrl)
|
||||
EVT_TREE_ITEM_EXPANDING(-1, wxVirtualDirTreeCtrl::OnExpanding)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
wxVirtualDirTreeCtrl::wxVirtualDirTreeCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name)
|
||||
: wxTreeCtrl(parent, id, pos, size, style, validator, name)
|
||||
|
|
|
|||
|
|
@ -482,7 +482,7 @@ private:
|
|||
// WDR: handler declarations for wxVirtualDirTreeCtrl
|
||||
|
||||
private:
|
||||
DECLARE_EVENT_TABLE()
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,10 +35,10 @@ class wxWindow;
|
|||
|
||||
IMPLEMENT_CLASS(AtlasDialog, wxDialog);
|
||||
|
||||
BEGIN_EVENT_TABLE(AtlasDialog, wxDialog)
|
||||
wxBEGIN_EVENT_TABLE(AtlasDialog, wxDialog)
|
||||
EVT_MENU(wxID_UNDO, AtlasDialog::OnUndo)
|
||||
EVT_MENU(wxID_REDO, AtlasDialog::OnRedo)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
|
||||
AtlasDialog::AtlasDialog(wxWindow* parent, const wxString& title, const wxSize& size)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ protected:
|
|||
private:
|
||||
AtlasWindowCommandProc m_CommandProc;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_ATLASDIALOG
|
||||
|
|
|
|||
|
|
@ -85,19 +85,19 @@ public:
|
|||
void OnSave(wxCommandEvent& WXUNUSED(event)) { EndModal(wxID_SAVE); }
|
||||
void OnNo(wxCommandEvent& WXUNUSED(event)) { EndModal(wxID_NO); }
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(SaveOnExitDialog, wxDialog)
|
||||
wxBEGIN_EVENT_TABLE(SaveOnExitDialog, wxDialog)
|
||||
EVT_BUTTON(wxID_SAVE, SaveOnExitDialog::OnSave)
|
||||
EVT_BUTTON(wxID_NO, SaveOnExitDialog::OnNo)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
IMPLEMENT_CLASS(AtlasWindow, wxFrame);
|
||||
|
||||
BEGIN_EVENT_TABLE(AtlasWindow, wxFrame)
|
||||
wxBEGIN_EVENT_TABLE(AtlasWindow, wxFrame)
|
||||
EVT_MENU(wxID_NEW, AtlasWindow::OnNew)
|
||||
// EVT_MENU(ID_Import, AtlasWindow::OnImport)
|
||||
// EVT_MENU(ID_Export, AtlasWindow::OnExport)
|
||||
|
|
@ -111,7 +111,7 @@ BEGIN_EVENT_TABLE(AtlasWindow, wxFrame)
|
|||
EVT_MENU(wxID_REDO, AtlasWindow::OnRedo)
|
||||
|
||||
EVT_CLOSE(AtlasWindow::OnClose)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
AtlasWindow::AtlasWindow(wxWindow* parent, const wxString& title, const wxSize& size)
|
||||
: wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, size),
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ private:
|
|||
|
||||
FileHistory m_FileHistory;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_ATLASWINDOW
|
||||
|
|
|
|||
|
|
@ -202,16 +202,6 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
// Alt+enter toggles fullscreen
|
||||
if (evt.GetKeyCode() == WXK_RETURN && wxGetKeyState(WXK_ALT))
|
||||
{
|
||||
if (m_ScenarioEditor.IsFullScreen())
|
||||
m_ScenarioEditor.ShowFullScreen(false);
|
||||
else
|
||||
m_ScenarioEditor.ShowFullScreen(true, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.GetKeyCode() == 'c')
|
||||
{
|
||||
POST_MESSAGE(CameraReset, ());
|
||||
|
|
@ -339,15 +329,15 @@ private:
|
|||
|
||||
ScenarioEditor& m_ScenarioEditor;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(GameCanvas, Canvas)
|
||||
wxBEGIN_EVENT_TABLE(GameCanvas, Canvas)
|
||||
EVT_KEY_DOWN(GameCanvas::OnKeyDown)
|
||||
EVT_KEY_UP(GameCanvas::OnKeyUp)
|
||||
EVT_CHAR(GameCanvas::OnChar)
|
||||
EVT_KILL_FOCUS(GameCanvas::OnKillFocus)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -382,7 +372,7 @@ enum
|
|||
ID_Toolbar // must be last in the list
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame)
|
||||
wxBEGIN_EVENT_TABLE(ScenarioEditor, wxFrame)
|
||||
EVT_CLOSE(ScenarioEditor::OnClose)
|
||||
EVT_TIMER(wxID_ANY, ScenarioEditor::OnTimer)
|
||||
|
||||
|
|
@ -417,7 +407,7 @@ BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame)
|
|||
EVT_MENU_OPEN(ScenarioEditor::OnMenuOpen)
|
||||
|
||||
EVT_IDLE(ScenarioEditor::OnIdle)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
static AtlasWindowCommandProc g_CommandProc;
|
||||
AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; }
|
||||
|
|
@ -520,6 +510,8 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent)
|
|||
menuView->AppendCheckItem(ID_Wireframe, _("&Wireframe"));
|
||||
menuView->AppendCheckItem(ID_SmoothFramerate, _("Smooth framerate"));
|
||||
menuView->AppendCheckItem(ID_BirdsEyeView, _("&Birds Eye View\tCtrl+B"));
|
||||
auto* fullscreenItem = menuView->AppendCheckItem(wxID_ANY, _("&Fullscreen\tAlt+Enter"));
|
||||
this->Bind(wxEVT_MENU, [this, fullscreenItem](auto&){ SetFullscreen(fullscreenItem->IsChecked()); });
|
||||
menuView->Append(ID_CameraReset, _("&Reset camera"));
|
||||
}
|
||||
|
||||
|
|
@ -701,6 +693,9 @@ void ScenarioEditor::OnClose(wxCloseEvent& event)
|
|||
|
||||
m_Timer.Stop();
|
||||
m_RenderTimer.Stop();
|
||||
// Wait for in-flight work to finish.
|
||||
qPing qry;
|
||||
qry.Post();
|
||||
|
||||
m_ToolManager.SetCurrentTool(_T(""));
|
||||
|
||||
|
|
@ -1006,6 +1001,11 @@ void ScenarioEditor::OnBirdsEyeView(wxCommandEvent& event)
|
|||
POST_MESSAGE(SetBirdsEyeView, (event.IsChecked()));
|
||||
}
|
||||
|
||||
void ScenarioEditor::SetFullscreen(const bool enabled)
|
||||
{
|
||||
ShowFullScreen(enabled, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION);
|
||||
}
|
||||
|
||||
void ScenarioEditor::OnDumpState(wxCommandEvent& event)
|
||||
{
|
||||
wxDateTime time = wxDateTime::Now();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public:
|
|||
void OnWireframe(wxCommandEvent& event);
|
||||
void OnSmoothFramerate(wxCommandEvent& event);
|
||||
void OnBirdsEyeView(wxCommandEvent& event);
|
||||
void SetFullscreen(const bool enabled);
|
||||
void OnCameraReset(wxCommandEvent& event);
|
||||
|
||||
void OnMessageTrace(wxCommandEvent& event);
|
||||
|
|
@ -119,7 +120,7 @@ private:
|
|||
};
|
||||
std::map<int, HelpItem> m_HelpData;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
#endif // INCLUDED_SCENARIOEDITOR
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ private:
|
|||
SidebarBook* m_Book;
|
||||
size_t m_Id;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(SidebarButton, wxBitmapButton)
|
||||
wxBEGIN_EVENT_TABLE(SidebarButton, wxBitmapButton)
|
||||
EVT_BUTTON(wxID_ANY, SidebarButton::OnClick)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
|
||||
class SidebarBook : public wxPanel
|
||||
|
|
@ -257,12 +257,12 @@ private:
|
|||
std::vector<SidebarPage> m_Pages;
|
||||
ssize_t m_SelectedPage;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(SidebarBook, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(SidebarBook, wxPanel)
|
||||
EVT_SIZE(SidebarBook::OnSize)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
void SidebarButton::OnClick(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ void CinemaSidebar::ReloadPathList()
|
|||
m_PathList->Append(*path.name);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(CinemaSidebar, Sidebar)
|
||||
wxBEGIN_EVENT_TABLE(CinemaSidebar, Sidebar)
|
||||
EVT_CHECKBOX(ID_PathsDrawing, CinemaSidebar::OnTogglePathsDrawing)
|
||||
EVT_BUTTON(ID_AddPath, CinemaSidebar::OnAddPath)
|
||||
EVT_BUTTON(ID_DeletePath, CinemaSidebar::OnDeletePath)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
|
|
|||
|
|
@ -47,5 +47,5 @@ private:
|
|||
wxListBox* m_PathList;
|
||||
wxTextCtrl* m_NewPathName;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -94,12 +94,12 @@ private:
|
|||
Shareable<float>& m_Var;
|
||||
float m_Min, m_Max;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(VariableSliderBox, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(VariableSliderBox, wxPanel)
|
||||
EVT_SCROLL(VariableSliderBox::OnScroll)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -152,12 +152,12 @@ private:
|
|||
wxComboBox* m_Combo;
|
||||
Shareable<std::wstring>& m_Var;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(VariableListBox, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(VariableListBox, wxPanel)
|
||||
EVT_COMBOBOX(wxID_ANY, VariableListBox::OnSelect)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -216,12 +216,12 @@ private:
|
|||
wxButton* m_Button;
|
||||
Shareable<AtlasMessage::Color>& m_Color;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(VariableColorBox, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(VariableColorBox, wxPanel)
|
||||
EVT_BUTTON(wxID_ANY, VariableColorBox::OnClick)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -361,8 +361,8 @@ void EnvironmentSidebar::OnPickWaterHeight(wxCommandEvent& WXUNUSED(evt))
|
|||
m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PickWaterHeight"), this);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(EnvironmentSidebar, Sidebar)
|
||||
wxBEGIN_EVENT_TABLE(EnvironmentSidebar, Sidebar)
|
||||
EVT_BUTTON(ID_RecomputeWaterData, EnvironmentSidebar::RecomputeWaterData)
|
||||
EVT_BUTTON(ID_PickWaterHeight, EnvironmentSidebar::OnPickWaterHeight)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
|
|
|
|||
|
|
@ -46,5 +46,5 @@ private:
|
|||
VariableListBox* m_WaterTypeList;
|
||||
ObservableScopedConnection m_Conn;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -150,14 +150,14 @@ public:
|
|||
float theta, phi;
|
||||
LightControl* m_LightControl;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(LightSphere, wxControl)
|
||||
wxBEGIN_EVENT_TABLE(LightSphere, wxControl)
|
||||
EVT_PAINT(LightSphere::OnPaint)
|
||||
EVT_MOTION(LightSphere::OnMouse)
|
||||
EVT_LEFT_DOWN(LightSphere::OnMouse)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
LightControl::LightControl(wxWindow* parent, const wxSize& size, Observable<AtlasMessage::sEnvironmentSettings>& environment)
|
||||
: wxPanel(parent), m_Environment(environment)
|
||||
|
|
|
|||
|
|
@ -158,16 +158,16 @@ private:
|
|||
std::vector<wxChoice*> m_PlayerCivChoices;
|
||||
Observable<AtObj>& m_MapSettings;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(MapSettingsControl, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(MapSettingsControl, wxPanel)
|
||||
EVT_TEXT(ID_MapName, MapSettingsControl::OnEdit)
|
||||
EVT_TEXT(ID_MapDescription, MapSettingsControl::OnEdit)
|
||||
EVT_TEXT(ID_MapPreview, MapSettingsControl::OnEdit)
|
||||
EVT_CHECKBOX(wxID_ANY, MapSettingsControl::OnEdit)
|
||||
EVT_CHOICE(wxID_ANY, MapSettingsControl::OnEdit)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
MapSettingsControl::MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor)
|
||||
: wxPanel(parent, wxID_ANY), m_MapSettings(scenarioEditor.GetMapSettings())
|
||||
|
|
@ -837,7 +837,7 @@ void MapSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt))
|
|||
POST_COMMAND(ResizeMap, (dlg.GetNewSize(), offset.x, offset.y));
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(MapSidebar, Sidebar)
|
||||
wxBEGIN_EVENT_TABLE(MapSidebar, Sidebar)
|
||||
EVT_BUTTON(ID_SimPlay, MapSidebar::OnSimPlay)
|
||||
EVT_BUTTON(ID_SimFast, MapSidebar::OnSimPlay)
|
||||
EVT_BUTTON(ID_SimSlow, MapSidebar::OnSimPlay)
|
||||
|
|
@ -848,4 +848,4 @@ BEGIN_EVENT_TABLE(MapSidebar, Sidebar)
|
|||
EVT_BUTTON(ID_ResizeMap, MapSidebar::OnResizeMap)
|
||||
EVT_BUTTON(ID_OpenPlayerPanel, MapSidebar::OnOpenPlayerPanel)
|
||||
EVT_CHOICE(ID_RandomScript, MapSidebar::OnRandomScript)
|
||||
END_EVENT_TABLE()
|
||||
wxEND_EVENT_TABLE()
|
||||
|
|
|
|||
|
|
@ -48,5 +48,5 @@ private:
|
|||
|
||||
int m_SimState;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ private:
|
|||
wxScrolledWindow* m_TemplateNames;
|
||||
|
||||
ObjectSidebarImpl* p;
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
struct ObjectSidebarImpl
|
||||
|
|
@ -450,13 +450,13 @@ void ObjectSidebar::OnToggleExactFilter(wxCommandEvent& WXUNUSED(evt))
|
|||
FilterObjects();
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(ObjectSidebar, Sidebar)
|
||||
wxBEGIN_EVENT_TABLE(ObjectSidebar, Sidebar)
|
||||
EVT_CHOICE(ID_ObjectType, ObjectSidebar::OnSelectType)
|
||||
EVT_TEXT(ID_ObjectFilter, ObjectSidebar::OnSelectFilter)
|
||||
EVT_LISTBOX(ID_SelectObject, ObjectSidebar::OnSelectObject)
|
||||
EVT_BUTTON(ID_ToggleViewer, ObjectSidebar::OnToggleViewer)
|
||||
EVT_CHECKBOX(ID_ObjectExactFilter, ObjectSidebar::OnToggleExactFilter)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -525,11 +525,11 @@ private:
|
|||
m_ObjectSettings.NotifyObserversExcept(m_ObjectConn);
|
||||
}
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox)
|
||||
wxBEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox)
|
||||
EVT_COMBOBOX(wxID_ANY, PlayerComboBox::OnSelect)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -792,7 +792,7 @@ void ObjectBottomBar::OnSpeed(wxCommandEvent& evt)
|
|||
p->ActorViewerPostToGame();
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)
|
||||
EVT_BUTTON(ID_ViewerWireframe, ObjectBottomBar::OnViewerSetting)
|
||||
EVT_BUTTON(ID_ViewerMove, ObjectBottomBar::OnViewerSetting)
|
||||
EVT_BUTTON(ID_ViewerGround, ObjectBottomBar::OnViewerSetting)
|
||||
|
|
@ -806,4 +806,4 @@ BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)
|
|||
EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting)
|
||||
EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting)
|
||||
EVT_BUTTON(ID_ViewerPropPoints, ObjectBottomBar::OnViewerSetting)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
|
|
|||
|
|
@ -45,5 +45,5 @@ private:
|
|||
|
||||
std::unique_ptr<ObjectSidebarImpl> m_Impl;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -124,12 +124,12 @@ public:
|
|||
private:
|
||||
wxWindow* m_Control;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(DefaultCheckbox, wxCheckBox)
|
||||
wxBEGIN_EVENT_TABLE(DefaultCheckbox, wxCheckBox)
|
||||
EVT_CHECKBOX(wxID_ANY, DefaultCheckbox::OnChecked)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
|
||||
class PlayerNotebookPage : public wxPanel
|
||||
|
|
@ -192,35 +192,35 @@ public:
|
|||
wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5);
|
||||
gridSizer->AddGrowableCol(2);
|
||||
|
||||
wxSpinCtrl* foodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
||||
wxSpinCtrl* foodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX);
|
||||
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultFood, foodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
||||
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Food")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
|
||||
gridSizer->Add(Tooltipped(foodCtrl,
|
||||
_("Initial value of food resource")), wxSizerFlags().Expand());
|
||||
m_Controls.food = foodCtrl;
|
||||
|
||||
wxSpinCtrl* woodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
||||
wxSpinCtrl* woodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX);
|
||||
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultWood, woodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
||||
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Wood")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
|
||||
gridSizer->Add(Tooltipped(woodCtrl,
|
||||
_("Initial value of wood resource")), wxSizerFlags().Expand());
|
||||
m_Controls.wood = woodCtrl;
|
||||
|
||||
wxSpinCtrl* metalCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
||||
wxSpinCtrl* metalCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX);
|
||||
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultMetal, metalCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
||||
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Metal")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
|
||||
gridSizer->Add(Tooltipped(metalCtrl,
|
||||
_("Initial value of metal resource")), wxSizerFlags().Expand());
|
||||
m_Controls.metal = metalCtrl;
|
||||
|
||||
wxSpinCtrl* stoneCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
||||
wxSpinCtrl* stoneCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX);
|
||||
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultStone, stoneCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
||||
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Stone")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
|
||||
gridSizer->Add(Tooltipped(stoneCtrl,
|
||||
_("Initial value of stone resource")), wxSizerFlags().Expand());
|
||||
m_Controls.stone = stoneCtrl;
|
||||
|
||||
wxSpinCtrl* popCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
||||
wxSpinCtrl* popCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX);
|
||||
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultPop, popCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
||||
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Pop limit")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
|
||||
gridSizer->Add(Tooltipped(popCtrl,
|
||||
|
|
@ -365,15 +365,15 @@ private:
|
|||
|
||||
PlayerPageControls m_Controls;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(PlayerNotebookPage, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(PlayerNotebookPage, wxPanel)
|
||||
EVT_BUTTON(ID_PlayerColor, PlayerNotebookPage::OnColor)
|
||||
EVT_BUTTON(ID_CameraSet, PlayerNotebookPage::OnCameraSet)
|
||||
EVT_BUTTON(ID_CameraView, PlayerNotebookPage::OnCameraView)
|
||||
EVT_BUTTON(ID_CameraClear, PlayerNotebookPage::OnCameraClear)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -440,12 +440,12 @@ protected:
|
|||
private:
|
||||
std::vector<PlayerNotebookPage*> m_Pages;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(PlayerNotebook, wxChoicebook)
|
||||
wxBEGIN_EVENT_TABLE(PlayerNotebook, wxChoicebook)
|
||||
EVT_CHOICEBOOK_PAGE_CHANGED(wxID_ANY, PlayerNotebook::OnPageChanged)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -477,6 +477,14 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void OnEditSpin(wxCommandEvent&)
|
||||
{
|
||||
if (!m_InGUIUpdate)
|
||||
{
|
||||
SendToEngine();
|
||||
}
|
||||
}
|
||||
|
||||
void OnPlayerColor(wxCommandEvent& WXUNUSED(evt))
|
||||
{
|
||||
if (!m_InGUIUpdate)
|
||||
|
|
@ -493,22 +501,22 @@ private:
|
|||
// and we don't want to handle the same event twice
|
||||
}
|
||||
|
||||
void OnNumPlayersSpin(wxSpinEvent& evt)
|
||||
void SetNumPlayers(int newNumPlayers)
|
||||
{
|
||||
if (!m_InGUIUpdate)
|
||||
{
|
||||
wxASSERT(evt.GetInt() > 0);
|
||||
wxASSERT(newNumPlayers > 0);
|
||||
|
||||
// When wxMessageBox pops up, wxSpinCtrl loses focus, which
|
||||
// forces another EVT_SPINCTRL event, which we don't want
|
||||
// to handle, so we check here for a change
|
||||
if (evt.GetInt() == (int)m_NumPlayers)
|
||||
if (newNumPlayers == (int)m_NumPlayers)
|
||||
{
|
||||
return; // No change
|
||||
}
|
||||
|
||||
size_t oldNumPlayers = m_NumPlayers;
|
||||
m_NumPlayers = evt.GetInt();
|
||||
m_NumPlayers = newNumPlayers;
|
||||
|
||||
if (m_NumPlayers < oldNumPlayers)
|
||||
{
|
||||
|
|
@ -565,24 +573,27 @@ private:
|
|||
Observable<AtObj>& m_MapSettings;
|
||||
size_t m_NumPlayers;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(PlayerSettingsControl, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(PlayerSettingsControl, wxPanel)
|
||||
EVT_BUTTON(ID_PlayerColor, PlayerSettingsControl::OnPlayerColor)
|
||||
EVT_BUTTON(ID_CameraSet, PlayerSettingsControl::OnEdit)
|
||||
EVT_BUTTON(ID_CameraClear, PlayerSettingsControl::OnEdit)
|
||||
EVT_CHECKBOX(wxID_ANY, PlayerSettingsControl::OnEdit)
|
||||
EVT_CHOICE(wxID_ANY, PlayerSettingsControl::OnEdit)
|
||||
EVT_TEXT(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersText)
|
||||
EVT_TEXT(wxID_ANY, PlayerSettingsControl::OnEdit)
|
||||
EVT_SPINCTRL(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersSpin)
|
||||
EVT_SPINCTRL(ID_PlayerFood, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_SPINCTRL(ID_PlayerWood, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_SPINCTRL(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_SPINCTRL(ID_PlayerStone, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_SPINCTRL(ID_PlayerPop, PlayerSettingsControl::OnEditSpin)
|
||||
END_EVENT_TABLE();
|
||||
EVT_TEXT_ENTER(ID_PlayerFood, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_TEXT_ENTER(ID_PlayerWood, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_TEXT_ENTER(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_TEXT_ENTER(ID_PlayerStone, PlayerSettingsControl::OnEditSpin)
|
||||
EVT_TEXT_ENTER(ID_PlayerPop, PlayerSettingsControl::OnEditSpin)
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor)
|
||||
: wxPanel(parent, wxID_ANY), m_InGUIUpdate(false), m_MapSettings(scenarioEditor.GetMapSettings()), m_NumPlayers(0)
|
||||
|
|
@ -604,9 +615,19 @@ PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& s
|
|||
|
||||
boxSizer->AddSpacer(10);
|
||||
|
||||
wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(topBox, ID_NumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1));
|
||||
numPlayersSpin->SetValue(MAX_NUM_PLAYERS);
|
||||
numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS);
|
||||
// NOTE: on MSW pressing Enter with wxSpinCtrl is used to navigate, ie
|
||||
// it triggers the default Window action which is ok / close. So we
|
||||
// have to explicitly generate the wxCommandEvent wxEVT_TEXT_ENTER and
|
||||
// capture it.
|
||||
// https://gitea.wildfiregames.com/0ad/0ad/issues/9026
|
||||
|
||||
wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(topBox, ID_NumPlayers,
|
||||
wxEmptyString, wxDefaultPosition, wxDefaultSize,
|
||||
wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER,
|
||||
1, MAX_NUM_PLAYERS, MAX_NUM_PLAYERS);
|
||||
auto numPlayerSetter = [this, numPlayersSpin](auto&){ SetNumPlayers(numPlayersSpin->GetValue()); };
|
||||
numPlayersSpin->Bind(wxEVT_SPINCTRL, numPlayerSetter);
|
||||
numPlayersSpin->Bind(wxEVT_TEXT_ENTER, numPlayerSetter);
|
||||
boxSizer->Add(numPlayersSpin);
|
||||
|
||||
gridSizer->Add(boxSizer);
|
||||
|
|
|
|||
|
|
@ -211,12 +211,12 @@ private:
|
|||
wxTimer m_Timer;
|
||||
wxString m_TextureName;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(TexturePreviewPanel, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(TexturePreviewPanel, wxPanel)
|
||||
EVT_TIMER(wxID_ANY, TexturePreviewPanel::OnTimer)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -324,10 +324,10 @@ void TerrainSidebar::OnShowPriorities(wxCommandEvent& evt)
|
|||
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked()));
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(TerrainSidebar, Sidebar)
|
||||
wxBEGIN_EVENT_TABLE(TerrainSidebar, Sidebar)
|
||||
EVT_CHOICE(ID_Passability, TerrainSidebar::OnPassabilityChoice)
|
||||
EVT_CHECKBOX(ID_ShowPriorities, TerrainSidebar::OnShowPriorities)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -531,14 +531,14 @@ private:
|
|||
};
|
||||
std::unordered_map<std::wstring, PreviewButton> m_PreviewButtons;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(TextureNotebookPage, wxPanel)
|
||||
wxBEGIN_EVENT_TABLE(TextureNotebookPage, wxPanel)
|
||||
EVT_BUTTON(wxID_ANY, TextureNotebookPage::OnButton)
|
||||
EVT_SIZE(TextureNotebookPage::OnSize)
|
||||
EVT_TIMER(wxID_ANY, TextureNotebookPage::OnTimer)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
|
||||
class TextureNotebook : public wxChoicebook
|
||||
|
|
@ -600,12 +600,12 @@ private:
|
|||
ScenarioEditor& m_ScenarioEditor;
|
||||
wxArrayString m_TerrainGroups;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(TextureNotebook, wxChoicebook)
|
||||
wxBEGIN_EVENT_TABLE(TextureNotebook, wxChoicebook)
|
||||
EVT_CHOICEBOOK_PAGE_CHANGED(wxID_ANY, TextureNotebook::OnPageChanged)
|
||||
END_EVENT_TABLE();
|
||||
wxEND_EVENT_TABLE();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -41,5 +41,5 @@ private:
|
|||
wxChoice* m_PassabilityChoice;
|
||||
TexturePreviewPanel* m_TexturePreview;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <wx/spinctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/toolbar.h>
|
||||
#include <wx/translation.h>
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ static Brush* g_Brush_CurrentlyActive = NULL; // only one brush can be active at
|
|||
const float Brush::STRENGTH_MULTIPLIER = 1024.f;
|
||||
|
||||
Brush::Brush()
|
||||
: m_Shape(CIRCLE), m_Size(4), m_Strength(1.f), m_IsActive(false)
|
||||
: m_Shape(Shape::CIRCLE), m_Size(4), m_Strength(1.f), m_IsActive(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +77,9 @@ int Brush::GetWidth() const
|
|||
{
|
||||
switch (m_Shape)
|
||||
{
|
||||
case CIRCLE:
|
||||
case Shape::CIRCLE:
|
||||
return m_Size;
|
||||
case SQUARE:
|
||||
case Shape::SQUARE:
|
||||
return m_Size;
|
||||
default:
|
||||
wxFAIL;
|
||||
|
|
@ -108,7 +109,7 @@ std::vector<float> Brush::GetData() const
|
|||
|
||||
switch (m_Shape)
|
||||
{
|
||||
case CIRCLE:
|
||||
case Shape::CIRCLE:
|
||||
{
|
||||
int i = 0;
|
||||
// All calculations are done in units of half-tiles, since that
|
||||
|
|
@ -131,7 +132,7 @@ std::vector<float> Brush::GetData() const
|
|||
break;
|
||||
}
|
||||
|
||||
case SQUARE:
|
||||
case Shape::SQUARE:
|
||||
{
|
||||
int i = 0;
|
||||
for (int y = 0; y < height; ++y)
|
||||
|
|
@ -144,6 +145,17 @@ std::vector<float> Brush::GetData() const
|
|||
return data;
|
||||
}
|
||||
|
||||
int Brush::GetSize() const
|
||||
{
|
||||
return m_Size;
|
||||
}
|
||||
|
||||
void Brush::SetSize(int size)
|
||||
{
|
||||
m_Size = size;
|
||||
Send();
|
||||
}
|
||||
|
||||
float Brush::GetStrength() const
|
||||
{
|
||||
return m_Strength;
|
||||
|
|
@ -152,115 +164,61 @@ float Brush::GetStrength() const
|
|||
void Brush::SetStrength(float strength)
|
||||
{
|
||||
m_Strength = strength;
|
||||
Send();
|
||||
}
|
||||
|
||||
void Brush::SetCircle(int size)
|
||||
void Brush::SetShape(Shape shape)
|
||||
{
|
||||
m_Shape = CIRCLE;
|
||||
m_Size = size;
|
||||
m_Shape = shape;
|
||||
Send();
|
||||
}
|
||||
|
||||
void Brush::SetSquare(int size)
|
||||
{
|
||||
m_Shape = SQUARE;
|
||||
m_Size = size;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BrushShapeCtrl : public wxRadioBox
|
||||
{
|
||||
public:
|
||||
BrushShapeCtrl(wxWindow* parent, wxArrayString& shapes, Brush& brush)
|
||||
: wxRadioBox(parent, wxID_ANY, _("Shape"), wxDefaultPosition, wxDefaultSize, shapes, 0, wxRA_SPECIFY_ROWS),
|
||||
m_Brush(brush)
|
||||
{
|
||||
SetSelection(m_Brush.m_Shape);
|
||||
}
|
||||
|
||||
private:
|
||||
Brush& m_Brush;
|
||||
|
||||
void OnChange(wxCommandEvent& WXUNUSED(evt))
|
||||
{
|
||||
m_Brush.m_Shape = (Brush::BrushShape)GetSelection();
|
||||
m_Brush.Send();
|
||||
}
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
};
|
||||
BEGIN_EVENT_TABLE(BrushShapeCtrl, wxRadioBox)
|
||||
EVT_RADIOBOX(wxID_ANY, BrushShapeCtrl::OnChange)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
|
||||
class BrushSizeCtrl: public wxSpinCtrl
|
||||
{
|
||||
public:
|
||||
BrushSizeCtrl(wxWindow* parent, Brush& brush)
|
||||
: wxSpinCtrl(parent, wxID_ANY, wxString::Format(_T("%d"), brush.m_Size), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 100, brush.m_Size),
|
||||
m_Brush(brush)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Brush& m_Brush;
|
||||
|
||||
void OnChange(wxSpinEvent& WXUNUSED(evt))
|
||||
{
|
||||
m_Brush.m_Size = GetValue();
|
||||
m_Brush.Send();
|
||||
}
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
};
|
||||
BEGIN_EVENT_TABLE(BrushSizeCtrl, wxSpinCtrl)
|
||||
EVT_SPINCTRL(wxID_ANY, BrushSizeCtrl::OnChange)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
|
||||
class BrushStrengthCtrl : public wxSpinCtrl
|
||||
{
|
||||
public:
|
||||
BrushStrengthCtrl(wxWindow* parent, Brush& brush)
|
||||
: wxSpinCtrl(parent, wxID_ANY, wxString::Format(_T("%d"), (int)(10.f*brush.m_Strength)), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 100, (int)(10.f*brush.m_Strength)),
|
||||
m_Brush(brush)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Brush& m_Brush;
|
||||
|
||||
void OnChange(wxSpinEvent& WXUNUSED(evt))
|
||||
{
|
||||
m_Brush.m_Strength = GetValue()/10.f;
|
||||
m_Brush.Send();
|
||||
}
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
};
|
||||
BEGIN_EVENT_TABLE(BrushStrengthCtrl, wxSpinCtrl)
|
||||
EVT_SPINCTRL(wxID_ANY, BrushStrengthCtrl::OnChange)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
|
||||
|
||||
void Brush::CreateUI(wxWindow* parent, wxSizer* sizer)
|
||||
{
|
||||
wxArrayString shapes; // Must match order of BrushShape enum
|
||||
// Must match order of Brush::Shape enum
|
||||
wxArrayString shapes;
|
||||
shapes.Add(_("Circle"));
|
||||
shapes.Add(_("Square"));
|
||||
|
||||
// TODO (maybe): get rid of the extra static box, by not using wxRadioBox
|
||||
sizer->Add(new BrushShapeCtrl(parent, shapes, *this), wxSizerFlags().Expand().Border(wxALL, 5));
|
||||
auto* brushShapeCtrl = new wxRadioBox(parent, wxID_ANY, _("Shape"), wxDefaultPosition, wxDefaultSize, shapes, 0, wxRA_SPECIFY_ROWS);
|
||||
brushShapeCtrl->Bind(wxEVT_RADIOBOX, [this, brushShapeCtrl](auto&){ SetShape(static_cast<Shape>(brushShapeCtrl->GetSelection())); });
|
||||
sizer->Add(brushShapeCtrl, wxSizerFlags().Expand().Border(wxALL, 5));
|
||||
|
||||
sizer->AddSpacer(5);
|
||||
|
||||
// TODO: These are yucky
|
||||
// NOTE: on MSW pressing Enter with wxSpinCtrl is used to navigate, ie it
|
||||
// triggers the default Window action which is ok / close. So we have to
|
||||
// explicitly generate the wxCommandEvent wxEVT_TEXT_ENTER and capture it.
|
||||
// https://gitea.wildfiregames.com/0ad/0ad/issues/9026
|
||||
|
||||
auto* brushSizeLabel = new wxStaticText(parent, wxID_ANY, _("Size"));
|
||||
auto* brushSizeCtrl = new wxSpinCtrl(parent, wxID_ANY,
|
||||
wxString::Format(_T("%d"), GetSize()),
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER,
|
||||
0, 100, GetSize());
|
||||
auto burshSizeSetter = [this, brushSizeCtrl](auto&){ SetSize(brushSizeCtrl->GetValue()); };
|
||||
brushSizeCtrl->Bind(wxEVT_SPINCTRL, burshSizeSetter);
|
||||
brushSizeCtrl->Bind(wxEVT_TEXT_ENTER, burshSizeSetter);
|
||||
|
||||
auto* brushStrengthLabel = new wxStaticText(parent, wxID_ANY, _("Strength"));
|
||||
auto* brushStrengthCtrl = new wxSpinCtrl(parent, wxID_ANY,
|
||||
wxString::Format(_T("%d"), static_cast<int>(10.f * GetStrength())),
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER,
|
||||
0, 100, static_cast<int>(10.f * GetStrength()));
|
||||
auto burshStrenghtSetter = [this, brushStrengthCtrl](auto&){ SetStrength(brushStrengthCtrl->GetValue() / 10.f); };
|
||||
brushStrengthCtrl->Bind(wxEVT_SPINCTRL, burshStrenghtSetter);
|
||||
brushStrengthCtrl->Bind(wxEVT_TEXT_ENTER, burshStrenghtSetter);
|
||||
|
||||
wxFlexGridSizer* spinnerSizer = new wxFlexGridSizer(2, 5, 5);
|
||||
spinnerSizer->AddGrowableCol(1);
|
||||
spinnerSizer->Add(new wxStaticText(parent, wxID_ANY, _("Size")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT));
|
||||
spinnerSizer->Add(new BrushSizeCtrl(parent, *this), wxSizerFlags().Expand());
|
||||
spinnerSizer->Add(new wxStaticText(parent, wxID_ANY, _("Strength")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT));
|
||||
spinnerSizer->Add(new BrushStrengthCtrl(parent, *this), wxSizerFlags().Expand());
|
||||
|
||||
spinnerSizer->Add(brushSizeLabel, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT));
|
||||
spinnerSizer->Add(brushSizeCtrl, wxSizerFlags().Expand());
|
||||
spinnerSizer->Add(brushStrengthLabel, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT));
|
||||
spinnerSizer->Add(brushStrengthCtrl, wxSizerFlags().Expand());
|
||||
|
||||
sizer->Add(spinnerSizer, wxSizerFlags().Expand().Border(wxALL, 5));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -29,6 +29,8 @@ class Brush
|
|||
friend class BrushSizeCtrl;
|
||||
friend class BrushStrengthCtrl;
|
||||
public:
|
||||
enum class Shape { CIRCLE = 0, SQUARE};
|
||||
|
||||
Brush();
|
||||
~Brush();
|
||||
|
||||
|
|
@ -38,8 +40,10 @@ public:
|
|||
int GetHeight() const;
|
||||
std::vector<float> GetData() const;
|
||||
|
||||
void SetCircle(int size);
|
||||
void SetSquare(int size);
|
||||
void SetShape(Shape shape);
|
||||
|
||||
int GetSize() const;
|
||||
void SetSize(int size);
|
||||
|
||||
float GetStrength() const;
|
||||
void SetStrength(float strength);
|
||||
|
|
@ -54,8 +58,7 @@ private:
|
|||
// If active, send SetBrush message to the game
|
||||
void Send();
|
||||
|
||||
enum BrushShape { CIRCLE = 0, SQUARE};
|
||||
BrushShape m_Shape;
|
||||
Shape m_Shape;
|
||||
int m_Size;
|
||||
float m_Strength;
|
||||
bool m_IsActive;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ class FillTerrain : public StateDrivenTool<FillTerrain>
|
|||
public:
|
||||
FillTerrain()
|
||||
{
|
||||
m_Brush.SetSquare(2);
|
||||
m_Brush.SetShape(Brush::Shape::SQUARE);
|
||||
m_Brush.SetSize(2);
|
||||
SetState(&Waiting);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ public:
|
|||
{
|
||||
SetState(&Waiting);
|
||||
|
||||
m_EyedropperBrush.SetSquare(2);
|
||||
m_EyedropperBrush.SetShape(Brush::Shape::SQUARE);
|
||||
m_EyedropperBrush.SetSize(2);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ class ReplaceTerrain : public StateDrivenTool<ReplaceTerrain>
|
|||
public:
|
||||
ReplaceTerrain()
|
||||
{
|
||||
m_Brush.SetSquare(2);
|
||||
m_Brush.SetShape(Brush::Shape::SQUARE);
|
||||
m_Brush.SetSize(2);
|
||||
SetState(&Waiting);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ MESSAGEHANDLER(InitAppWindow)
|
|||
|
||||
MESSAGEHANDLER(InitSDL)
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
|
||||
// When using GLX (Linux), SDL has to load the GL library to find
|
||||
// glXGetProcAddressARB before it can load any extensions.
|
||||
// When running in Atlas, we skip the SDL video initialisation code
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ MESSAGEHANDLER(GuiSwitchPage)
|
|||
|
||||
MESSAGEHANDLER(GuiMouseButtonEvent)
|
||||
{
|
||||
if (!g_VideoMode.IsInitialized())
|
||||
return;
|
||||
|
||||
SDL_Event ev{};
|
||||
ev.type = msg->pressed ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
|
||||
ev.button.button = msg->button;
|
||||
|
|
@ -132,6 +135,9 @@ MESSAGEHANDLER(GuiMouseButtonEvent)
|
|||
|
||||
MESSAGEHANDLER(GuiMouseMotionEvent)
|
||||
{
|
||||
if (!g_VideoMode.IsInitialized())
|
||||
return;
|
||||
|
||||
SDL_Event ev{};
|
||||
ev.type = SDL_MOUSEMOTION;
|
||||
float x, y;
|
||||
|
|
@ -143,6 +149,9 @@ MESSAGEHANDLER(GuiMouseMotionEvent)
|
|||
|
||||
MESSAGEHANDLER(GuiKeyEvent)
|
||||
{
|
||||
if (!g_VideoMode.IsInitialized())
|
||||
return;
|
||||
|
||||
SDL_Event ev{};
|
||||
ev.type = msg->pressed ? SDL_KEYDOWN : SDL_KEYUP;
|
||||
ev.key.keysym.sym = static_cast<SDL_Keycode>(static_cast<int>(msg->sdlkey));
|
||||
|
|
@ -152,6 +161,9 @@ MESSAGEHANDLER(GuiKeyEvent)
|
|||
|
||||
MESSAGEHANDLER(GuiCharEvent)
|
||||
{
|
||||
if (!g_VideoMode.IsInitialized())
|
||||
return;
|
||||
|
||||
// Simulate special 'text input' events in the SDL
|
||||
// This isn't quite compatible with WXWidget's handling,
|
||||
// so to avoid trouble we only send 'letter-like' ASCII input.
|
||||
|
|
|
|||
Loading…
Reference in a new issue