Compare commits

...

20 Commits

Author SHA1 Message Date
Paul Chote
3b05a0a9f2 Deploy builds to the local repository. 2019-08-18 10:34:03 +01:00
tovl
22dd61676c Define nearenough parameter for aircraft so they can exit movement early when stuck. 2019-08-18 10:34:03 +01:00
Paul Chote
ca75797169 Merge remote-tracking branch 'upstream/pr/16953' into devtest3 2019-08-18 00:36:24 +01:00
Paul Chote
b2e5379537 Merge remote-tracking branch 'upstream/pr/16949' into devtest3 2019-08-18 00:36:17 +01:00
Paul Chote
24247cc2e5 Merge remote-tracking branch 'upstream/pr/16935' into devtest3 2019-08-18 00:36:12 +01:00
Paul Chote
75bb879f84 Merge remote-tracking branch 'upstream/pr/16877' into devtest3 2019-08-18 00:36:04 +01:00
Paul Chote
d2b42fc9a1 Merge remote-tracking branch 'upstream/pr/16816' into devtest3 2019-08-18 00:35:57 +01:00
Paul Chote
a789a906ac Merge remote-tracking branch 'upstream/pr/16938' into devtest3 2019-08-18 00:35:49 +01:00
Paul Chote
cf2c842049 Merge remote-tracking branch 'upstream/pr/16945' into devtest3 2019-08-18 00:35:42 +01:00
Paul Chote
0f105d62d4 Merge remote-tracking branch 'upstream/pr/16954' into devtest3 2019-08-18 00:35:33 +01:00
tovl
85d138624e Add to AUTHORS. 2019-08-18 00:45:54 +02:00
tovl
2cde1effcb Fix units from transports appearing at load point. 2019-08-18 00:45:54 +02:00
tovl
4d29c39a6d Refactor FlyAttack to make strafing runs interruptible when target becomes invalid. 2019-08-17 22:54:02 +02:00
Paul Chote
003943514b Fix crash for invalid Resupply hosts. 2019-08-17 12:08:26 +01:00
Paul Chote
ba983754ba Recalculate visibility during moves too. 2019-08-17 11:58:09 +01:00
Paul Chote
0ff9f0dfa6 Fix incorrect shroud visibility for stationary units. 2019-08-17 11:58:07 +01:00
Paul Chote
474c912439 Discourage harvesters from wandering too far from the refinery. 2019-08-17 11:47:13 +01:00
Paul Chote
40b2768183 Lay mines in order from start to end. 2019-08-17 00:12:33 +01:00
Ivaylo Draganov
cbb5d072e3 Move hotkey dialog logic into SettingsLogic, fix bugs and improve usability of the dialog 2019-08-17 00:09:41 +03:00
abcdefg30
4541d2b8ff Allow single observers to use spectator team chat in mp 2019-08-16 21:49:16 +02:00
46 changed files with 610 additions and 586 deletions

View File

@@ -34,14 +34,6 @@ env:
script:
- travis_retry make all-dependencies
- make all
- make check
- make check-scripts
- make test
- make nunit
# Automatically update the trait documentation and Lua API
after_success:
- test $TRAVIS_PULL_REQUEST == "false" && cd packaging && ./update-wiki.sh $TRAVIS_BRANCH; cd ..
# Only watch the development branch and tagged release.
branches:
@@ -49,19 +41,10 @@ branches:
- /^release-.*$/
- /^playtest-.*$/
- /^pkgtest-.*$/
- /^devtest-.*$/
- /^prep-.*$/
- bleed
# Notify developers when build passed/failed.
notifications:
irc:
template:
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
channels:
- "irc.freenode.net#openra"
use_notice: true
skip_join: true
before_deploy:
- wget http://mirrors.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.03-2_all.deb
- wget http://mirrors.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.03-2_amd64.deb
@@ -73,14 +56,15 @@ before_deploy:
- cd packaging
- mkdir build
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
deploy:
provider: releases
api_key:
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
secure: BzLdjZHvDqd5jyyHS8KK8zrNiXEIQiGRS/RMfqK6LuFPC4mXYDE4C/0oLPe93ULtai6LgUUkwB+zKq4lDV109EBO8Xp0jXKdDLLsepGU5xRLt3Z2tT4Zj+Nx6daVjAJe1MaKO5Zo5ODi8kBploRnPCffA8AMzOyQ+OwwJNlcRSs=
file_glob: true
file: build/*
skip_cleanup: true
on:
all_branches: true
tags: true
repo: OpenRA/OpenRA
repo: pchote/OpenRA

View File

@@ -140,6 +140,7 @@ Also thanks to:
* Tim Mylemans (gecko)
* Tirili
* Tomas Einarsson (Mesacer)
* Tom van Leth (tovl)
* Tristan Keating (Kilkakon)
* Tristan Mühlbacher (MicroBit)
* UnknownProgrammer

View File

@@ -32,25 +32,54 @@ namespace OpenRA.Mods.Cnc.Activities
bool returnToBase;
Actor rearmTarget;
public LayMines(Actor self, CPos[] minefield)
public LayMines(Actor self, List<CPos> minefield = null)
{
minelayer = self.Trait<Minelayer>();
ammoPools = self.TraitsImplementing<AmmoPool>().ToArray();
movement = self.Trait<IMove>();
rearmableInfo = self.Info.TraitInfoOrDefault<RearmableInfo>();
if (minefield != null)
this.minefield = minefield.ToList();
this.minefield = minefield;
ChildHasPriority = false;
}
protected override void OnFirstRun(Actor self)
{
if (minefield == null)
minefield = new CPos[] { self.Location }.ToList();
minefield = new List<CPos> { self.Location };
}
CPos? NextValidCell(Actor self)
{
if (minefield == null)
return null;
// Assume that the closest cell will be valid
var candidateCells = minefield;
while (candidateCells.Count > 0)
{
var nextCell = candidateCells.First();
if (CanLayMine(self, nextCell))
return nextCell;
// Make a copy of minefield so we can exclude impassible cells
if (candidateCells == minefield)
candidateCells = minefield.ToList();
candidateCells.Remove(nextCell);
}
return null;
}
public override bool Tick(Actor self)
{
// Remove cells that have already been mined
minefield.RemoveAll(c => self.World.ActorMap.GetActorsAt(c)
.Any(a => a.Info.Name == minelayer.Info.Mine.ToLowerInvariant()));
if (!TickChild(self))
return false;
returnToBase = false;
if (IsCanceling)
@@ -74,7 +103,7 @@ namespace OpenRA.Mods.Cnc.Activities
return false;
}
if ((minefield == null || minefield.Contains(self.Location)) && ShouldLayMine(self, self.Location))
if ((minefield == null || minefield.Contains(self.Location)) && CanLayMine(self, self.Location))
{
LayMine(self);
QueueChild(new Wait(20)); // A little wait after placing each mine, for show
@@ -82,18 +111,11 @@ namespace OpenRA.Mods.Cnc.Activities
return false;
}
if (minefield != null && minefield.Count > 0)
var nextCell = NextValidCell(self);
if (nextCell != null)
{
// Don't get stuck forever here
for (var n = 0; n < 20; n++)
{
var p = minefield.Random(self.World.SharedRandom);
if (ShouldLayMine(self, p))
{
QueueChild(movement.MoveTo(p, 0));
return false;
}
}
QueueChild(movement.MoveTo(nextCell.Value, 0));
return false;
}
// TODO: Return somewhere likely to be safe (near rearm building) so we're not sitting out in the minefield.
@@ -108,15 +130,17 @@ namespace OpenRA.Mods.Cnc.Activities
if (minefield == null || minefield.Count == 0)
yield break;
yield return new TargetLineNode(Target.FromCell(self.World, minefield[0]), Color.Crimson);
var nextCell = NextValidCell(self);
if (nextCell != null)
yield return new TargetLineNode(Target.FromCell(self.World, nextCell.Value), Color.Crimson);
foreach (var c in minefield)
yield return new TargetLineNode(Target.FromCell(self.World, c), Color.Crimson, tile: minelayer.Tile);
}
static bool ShouldLayMine(Actor self, CPos p)
static bool CanLayMine(Actor self, CPos p)
{
// If there is no unit (other than me) here, we want to place a mine here
// If there is no unit (other than me) here, we can place a mine here
return self.World.ActorMap.GetActorsAt(p).All(a => a == self);
}
@@ -130,12 +154,11 @@ namespace OpenRA.Mods.Cnc.Activities
pool.TakeAmmo(self, 1);
}
self.World.AddFrameEndTask(
w => w.CreateActor(minelayer.Info.Mine, new TypeDictionary
{
new LocationInit(self.Location),
new OwnerInit(self.Owner),
}));
self.World.AddFrameEndTask(w => w.CreateActor(minelayer.Info.Mine, new TypeDictionary
{
new LocationInit(self.Location),
new OwnerInit(self.Owner),
}));
}
}
}

View File

@@ -41,9 +41,6 @@ namespace OpenRA.Mods.Cnc.Traits
{
public readonly MinelayerInfo Info;
// TODO: [Sync] when sync can cope with arrays!
public CPos[] Minefield = null;
public readonly Sprite Tile;
[Sync]
@@ -104,15 +101,16 @@ namespace OpenRA.Mods.Cnc.Traits
if (order.OrderString == "BeginMinefield")
minefieldStart = cell;
else if (order.OrderString == "PlaceMine")
self.QueueActivity(order.Queued, new LayMines(self, null));
self.QueueActivity(order.Queued, new LayMines(self));
else if (order.OrderString == "PlaceMinefield")
{
var movement = self.Trait<IPositionable>();
Minefield = GetMinefieldCells(minefieldStart, cell, Info.MinefieldDepth)
.Where(p => movement.CanEnterCell(p, null, false)).ToArray();
var minefield = GetMinefieldCells(minefieldStart, cell, Info.MinefieldDepth)
.Where(c => movement.CanEnterCell(c, null, false))
.OrderBy(c => (c - minefieldStart).LengthSquared).ToList();
self.QueueActivity(order.Queued, new LayMines(self, Minefield));
self.QueueActivity(order.Queued, new LayMines(self, minefield));
self.ShowTargetLines();
}
}

View File

@@ -191,7 +191,7 @@ namespace OpenRA.Mods.Cnc.Traits
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange,
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any) { return null; }
public Activity MoveIntoWorld(Actor self, int delay = 0) { return null; }
public Activity MoveToTarget(Actor self, Target target,
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
public Activity MoveIntoTarget(Actor self, Target target) { return null; }

View File

@@ -10,6 +10,7 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
@@ -23,10 +24,19 @@ namespace OpenRA.Mods.Common.Activities
readonly WDist maxRange;
readonly WDist minRange;
readonly Color? targetLineColor;
readonly WDist nearEnough;
readonly List<WPos> positionBuffer = new List<WPos>();
Target target;
Target lastVisibleTarget;
bool useLastVisibleTarget;
public Fly(Actor self, Target t, WDist nearEnough, WPos? initialTargetPosition = null, Color? targetLineColor = null)
: this(self, t, initialTargetPosition, targetLineColor)
{
this.nearEnough = nearEnough;
}
public Fly(Actor self, Target t, WPos? initialTargetPosition = null, Color? targetLineColor = null)
{
aircraft = self.Trait<Aircraft>();
@@ -166,6 +176,12 @@ namespace OpenRA.Mods.Common.Activities
return false;
}
// HACK: Consider ourselves blocked if we have moved by less than 64 WDist in the last five ticks
// Stop if we are blocked and close enough
if (positionBuffer.Count >= 5 && (positionBuffer.Last() - positionBuffer[0]).LengthSquared < 4096 &&
delta.HorizontalLengthSquared <= nearEnough.LengthSquared)
return true;
// The next move would overshoot, so consider it close enough or set final position if we CanSlide
if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared)
{
@@ -213,6 +229,10 @@ namespace OpenRA.Mods.Common.Activities
desiredFacing = aircraft.Facing;
}
positionBuffer.Add(self.CenterPosition);
if (positionBuffer.Count > 5)
positionBuffer.RemoveAt(0);
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude);
return false;

View File

@@ -24,7 +24,6 @@ namespace OpenRA.Mods.Common.Activities
readonly AttackAircraft attackAircraft;
readonly Rearmable rearmable;
readonly bool forceAttack;
readonly int ticksUntilTurn;
readonly Color? targetLineColor;
Target target;
@@ -35,6 +34,7 @@ namespace OpenRA.Mods.Common.Activities
bool useLastVisibleTarget;
bool hasTicked;
bool returnToBase;
int remainingTicksUntilTurn;
public FlyAttack(Actor self, Target target, bool forceAttack, Color? targetLineColor)
{
@@ -45,7 +45,6 @@ namespace OpenRA.Mods.Common.Activities
aircraft = self.Trait<Aircraft>();
attackAircraft = self.Trait<AttackAircraft>();
rearmable = self.TraitOrDefault<Rearmable>();
ticksUntilTurn = attackAircraft.Info.AttackTurnDelay;
// The target may become hidden between the initial order request and the first tick (e.g. if queued)
// Moving to any position (even if quite stale) is still better than immediately giving up
@@ -132,28 +131,33 @@ namespace OpenRA.Mods.Common.Activities
var delta = attackAircraft.GetTargetPosition(pos, target) - pos;
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
var isAirborne = self.World.Map.DistanceAboveTerrain(pos).Length >= aircraft.Info.MinAirborneAltitude;
if (!isAirborne)
QueueChild(new TakeOff(self));
QueueChild(new TakeOff(self));
if (attackAircraft.Info.AttackType == AirAttackType.Strafe)
var minimumRange = attackAircraft.Info.AttackType == AirAttackType.Strafe ? WDist.Zero : attackAircraft.GetMinimumRangeVersusTarget(target);
// When strafing we must move forward for a minimum number of ticks after passing the target.
if (remainingTicksUntilTurn > 0)
{
if (target.IsInRange(pos, attackAircraft.GetMinimumRange()))
QueueChild(new FlyTimed(ticksUntilTurn, self));
Fly.FlyTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
remainingTicksUntilTurn--;
}
QueueChild(new Fly(self, target, target.CenterPosition, Color.Red));
QueueChild(new FlyTimed(ticksUntilTurn, self));
}
else
// Move into range of the target.
else if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange))
QueueChild(aircraft.MoveWithinRange(target, minimumRange, lastVisibleMaximumRange, target.CenterPosition, Color.Red));
// The aircraft must keep moving forward even if it is already in an ideal position.
else if (!aircraft.Info.CanHover || attackAircraft.Info.AttackType == AirAttackType.Strafe)
{
var minimumRange = attackAircraft.GetMinimumRangeVersusTarget(target);
if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange))
QueueChild(new Fly(self, target, minimumRange, lastVisibleMaximumRange, target.CenterPosition, Color.Red));
else if (isAirborne) // Don't use 'else' to avoid conflict with TakeOff
Fly.VerticalTakeOffOrLandTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude);
Fly.FlyTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
remainingTicksUntilTurn = attackAircraft.Info.AttackTurnDelay;
}
// Turn to face the target if required.
else if (!attackAircraft.TargetInFiringArc(self, target, attackAircraft.Info.FacingTolerance))
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.TurnSpeed);
return false;
}

View File

@@ -39,6 +39,9 @@ namespace OpenRA.Mods.Common.Activities
if (aircraft.ForceLanding)
return;
if (self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length >= aircraft.Info.MinAirborneAltitude)
return;
// We are taking off, so remove reservation and influence in ground cells.
aircraft.UnReserve();
aircraft.RemoveInfluence();

View File

@@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.Activities
public abstract class Enter : Activity
{
enum EnterState { Approaching, Entering, Exiting }
enum EnterState { Approaching, Entering, Exiting, Finished }
readonly IMove move;
readonly Color? targetLineColor;
@@ -133,15 +133,18 @@ namespace OpenRA.Mods.Common.Activities
OnEnterComplete(self, target.Actor);
lastState = EnterState.Exiting;
QueueChild(move.MoveIntoWorld(self, self.Location));
return false;
}
case EnterState.Exiting:
return true;
{
QueueChild(move.MoveIntoWorld(self));
lastState = EnterState.Finished;
return false;
}
}
return false;
return true;
}
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)

View File

@@ -175,7 +175,8 @@ namespace OpenRA.Mods.Common.Activities
}
// Determine where to search from and how far to search:
var searchFromLoc = lastHarvestedCell ?? GetSearchFromProcLocation(self);
var procLoc = GetSearchFromProcLocation(self);
var searchFromLoc = lastHarvestedCell ?? procLoc;
var searchRadius = lastHarvestedCell.HasValue ? harvInfo.SearchFromHarvesterRadius : harvInfo.SearchFromProcRadius;
if (!searchFromLoc.HasValue)
{
@@ -185,6 +186,9 @@ namespace OpenRA.Mods.Common.Activities
var searchRadiusSquared = searchRadius * searchRadius;
var procPos = procLoc.HasValue ? (WPos?)self.World.Map.CenterOfCell(procLoc.Value) : null;
var harvPos = self.CenterPosition;
// Find any harvestable resources:
List<CPos> path;
using (var search = PathSearch.Search(self.World, mobile.Locomotor, self, true, loc =>
@@ -194,6 +198,21 @@ namespace OpenRA.Mods.Common.Activities
if ((loc - searchFromLoc.Value).LengthSquared > searchRadiusSquared)
return int.MaxValue;
// Add a cost modifier to harvestable cells to prefer resources that are closer to the refinery.
// This reduces the tendancy for harvesters to move in straight lines
if (procPos.HasValue && harvInfo.ResourceRefineryDirectionCostModifier > 0 && harv.CanHarvestCell(self, loc))
{
var pos = self.World.Map.CenterOfCell(loc);
// Calculate harv-cell-refinery angle (cosine rule)
var aSq = (harvPos - procPos.Value).LengthSquared;
var bSq = (pos - procPos.Value).LengthSquared;
var cSq = (pos - harvPos).LengthSquared;
var cosA = (int)(1024 * (bSq + cSq - aSq) / (2 * Exts.ISqrt(bSq * cSq)));
return harvInfo.ResourceRefineryDirectionCostModifier * cosA / 1024;
}
return 0;
})
.FromPoint(searchFromLoc.Value)

View File

@@ -19,14 +19,12 @@ namespace OpenRA.Mods.Common.Activities
{
readonly IPositionable pos;
readonly WVec fallVector;
readonly Actor ignore;
int groundLevel;
public Parachute(Actor self, Actor ignoreActor = null)
public Parachute(Actor self)
{
pos = self.TraitOrDefault<IPositionable>();
ignore = ignoreActor;
fallVector = new WVec(0, 0, self.Info.TraitInfo<ParachutableInfo>().FallRate);
IsInterruptible = false;
@@ -56,7 +54,7 @@ namespace OpenRA.Mods.Common.Activities
pos.SetPosition(self, centerPosition + new WVec(0, 0, groundLevel - centerPosition.Z));
foreach (var np in self.TraitsImplementing<INotifyParachute>())
np.OnLanded(self, ignore);
np.OnLanded(self);
}
}
}

View File

@@ -169,7 +169,7 @@ namespace OpenRA.Mods.Common.Activities
void OnResupplyEnding(Actor self, bool isHostInvalid = false)
{
var rp = host.Actor.TraitOrDefault<RallyPoint>();
var rp = !isHostInvalid ? host.Actor.TraitOrDefault<RallyPoint>() : null;
if (aircraft != null)
{
if (wasRepaired || isHostInvalid || (!stayOnResupplier && aircraft.Info.TakeOffOnResupply))

View File

@@ -15,7 +15,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Activities
{
class EnterTransport : Enter
class RideTransport : Enter
{
readonly Passenger passenger;
@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Activities
Cargo enterCargo;
Aircraft enterAircraft;
public EnterTransport(Actor self, Target target)
public RideTransport(Actor self, Target target)
: base(self, target, Color.Green)
{
passenger = self.Trait<Passenger>();
@@ -63,10 +63,6 @@ namespace OpenRA.Mods.Common.Activities
enterCargo.Load(enterActor, self);
w.Remove(self);
// Preemptively cancel any activities to avoid an edge-case where successively queued
// EnterTransports corrupt the actor state. Activities are cancelled again on unload
self.CancelActivity();
});
}

View File

@@ -121,9 +121,10 @@ namespace OpenRA.Mods.Common.Activities
var move = actor.Trait<IMove>();
var pos = actor.Trait<IPositionable>();
actor.CancelActivity();
pos.SetPosition(self, exitSubCell.Value.First, exitSubCell.Value.Second);
pos.SetVisualPosition(actor, spawn);
actor.QueueActivity(move.MoveIntoWorld(actor, exitSubCell.Value.First, exitSubCell.Value.Second));
actor.CancelActivity();
w.Add(actor);
});
}

View File

@@ -24,6 +24,16 @@ namespace OpenRA.Mods.Common
public int Value(World world) { return value; }
}
public class MoveIntoWorldDelayInit : IActorInit<int>
{
[FieldFromYamlKey]
readonly int value = 0;
public MoveIntoWorldDelayInit() { }
public MoveIntoWorldDelayInit(int init) { value = init; }
public int Value(World world) { return value; }
}
public class DynamicFacingInit : IActorInit<Func<int>>
{
readonly Func<int> func;

View File

@@ -46,7 +46,10 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Moves from outside the world into the cell grid.")]
public void MoveIntoWorld(CPos cell)
{
Self.QueueActivity(mobile.MoveIntoWorld(Self, cell, mobile.ToSubCell));
var pos = Self.CenterPosition;
mobile.SetPosition(Self, cell);
mobile.SetVisualPosition(Self, pos);
Self.QueueActivity(mobile.MoveIntoWorld(Self));
}
[ScriptActorPropertyActivity]
@@ -60,7 +63,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Move to and enter the transport.")]
public void EnterTransport(Actor transport)
{
Self.QueueActivity(new EnterTransport(Self, Target.FromActor(transport)));
Self.QueueActivity(new RideTransport(Self, Target.FromActor(transport)));
}
[Desc("Whether the actor can move (false if immobilized).")]

View File

@@ -22,12 +22,15 @@ namespace OpenRA.Mods.Common.Traits
[Desc("If >= 0, prevent cells that are this much higher than the actor from being revealed.")]
public readonly int MaxHeightDelta = -1;
[Desc("If > 0, force visibility to be recalculated if the unit moves within a cell by more than this distance.")]
public readonly WDist MoveRecalculationThreshold = new WDist(256);
[Desc("Possible values are CenterPosition (measure range from the center) and ",
"Footprint (measure range from the footprint)")]
public readonly VisibilityType Type = VisibilityType.Footprint;
}
public abstract class AffectsShroud : ConditionalTrait<AffectsShroudInfo>, ITick, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
public abstract class AffectsShroud : ConditionalTrait<AffectsShroudInfo>, ITick, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyMoving
{
static readonly PPos[] NoCells = { };
@@ -42,6 +45,9 @@ namespace OpenRA.Mods.Common.Traits
[Sync]
protected bool CachedTraitDisabled { get; private set; }
bool dirty;
WPos cachedPos;
protected abstract void AddCellsToPlayerShroud(Actor self, Player player, PPos[] uv);
protected abstract void RemoveCellsFromPlayerShroud(Actor self, Player player);
@@ -87,13 +93,19 @@ namespace OpenRA.Mods.Common.Traits
var projectedLocation = self.World.Map.CellContaining(projectedPos);
var traitDisabled = IsTraitDisabled;
var range = Range;
var pos = self.CenterPosition;
if (cachedLocation == projectedLocation && cachedRange == range && traitDisabled == CachedTraitDisabled)
if (Info.MoveRecalculationThreshold.Length > 0 && (pos - cachedPos).LengthSquared > Info.MoveRecalculationThreshold.LengthSquared)
dirty = true;
if (!dirty && cachedLocation == projectedLocation && cachedRange == range && traitDisabled == CachedTraitDisabled)
return;
cachedRange = range;
cachedLocation = projectedLocation;
CachedTraitDisabled = traitDisabled;
cachedPos = pos;
dirty = false;
var cells = ProjectedCells(self);
foreach (var p in self.World.Players)
@@ -122,5 +134,12 @@ namespace OpenRA.Mods.Common.Traits
}
public virtual WDist Range { get { return CachedTraitDisabled ? WDist.Zero : Info.Range; } }
void INotifyMoving.MovementTypeChanged(Actor self, MovementType type)
{
// Recalculate the visiblity at our final stop position
if (type == MovementType.None)
dirty = true;
}
}
}

View File

@@ -217,8 +217,10 @@ namespace OpenRA.Mods.Common.Traits
public Actor ReservedActor { get; private set; }
public bool MayYieldReservation { get; private set; }
public bool ForceLanding { get; private set; }
IEnumerable<CPos> landingCells = Enumerable.Empty<CPos>();
bool requireForceMove;
int moveIntoWorldDelay;
public static WPos GroundPosition(Actor self)
{
@@ -229,7 +231,6 @@ namespace OpenRA.Mods.Common.Traits
bool airborne;
bool cruising;
bool firstTick = true;
int airborneToken = ConditionManager.InvalidConditionToken;
int cruisingToken = ConditionManager.InvalidConditionToken;
@@ -250,6 +251,7 @@ namespace OpenRA.Mods.Common.Traits
SetPosition(self, init.Get<CenterPositionInit, WPos>());
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : Info.InitialFacing;
moveIntoWorldDelay = init.Contains<MoveIntoWorldDelayInit>() ? init.Get<MoveIntoWorldDelayInit, int>() : 0;
}
public WDist LandAltitude
@@ -307,6 +309,8 @@ namespace OpenRA.Mods.Common.Traits
notifyMoving = self.TraitsImplementing<INotifyMoving>().ToArray();
positionOffsets = self.TraitsImplementing<IAircraftCenterPositionOffset>().ToArray();
overrideAircraftLanding = self.TraitOrDefault<IOverrideAircraftLanding>();
self.QueueActivity(MoveIntoWorld(self, moveIntoWorldDelay));
}
void INotifyAddedToWorld.AddedToWorld(Actor self)
@@ -346,20 +350,6 @@ namespace OpenRA.Mods.Common.Traits
protected virtual void Tick(Actor self)
{
if (firstTick)
{
firstTick = false;
var host = GetActorBelow();
if (host == null)
return;
MakeReservation(host);
if (Info.TakeOffOnCreation)
UnReserve(true);
}
// Add land activity if LandOnCondition resolves to true and the actor can land at the current location.
if (!ForceLanding && landNow.HasValue && landNow.Value && airborne && CanLand(self.Location)
&& !((self.CurrentActivity is Land) || self.CurrentActivity is Turn))
@@ -840,7 +830,7 @@ namespace OpenRA.Mods.Common.Traits
public Activity MoveTo(CPos cell, int nearEnough, Color? targetLineColor = null)
{
return new Fly(self, Target.FromCell(self.World, cell), targetLineColor: targetLineColor);
return new Fly(self, Target.FromCell(self.World, cell), WDist.FromCells(nearEnough), targetLineColor: targetLineColor);
}
public Activity MoveTo(CPos cell, Actor ignoreActor, Color? targetLineColor = null)
@@ -868,9 +858,46 @@ namespace OpenRA.Mods.Common.Traits
initialTargetPosition, targetLineColor);
}
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
public Activity MoveIntoWorld(Actor self, int delay = 0)
{
return new Fly(self, Target.FromCell(self.World, cell, subCell));
return new MoveIntoWorldActivity(self, delay);
}
class MoveIntoWorldActivity : Activity
{
readonly Actor self;
readonly Aircraft aircraft;
readonly int delay;
public MoveIntoWorldActivity(Actor self, int delay = 0)
{
this.self = self;
aircraft = self.Trait<Aircraft>();
IsInterruptible = false;
this.delay = delay;
}
protected override void OnFirstRun(Actor self)
{
var host = aircraft.GetActorBelow();
if (host != null)
aircraft.MakeReservation(host);
if (delay > 0)
QueueChild(new Wait(delay));
}
public override bool Tick(Actor self)
{
if (!aircraft.Info.TakeOffOnCreation)
return true;
if (self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length <= aircraft.LandAltitude.Length)
QueueChild(new TakeOff(self));
aircraft.UnReserve();
return true;
}
}
public Activity MoveToTarget(Actor self, Target target,
@@ -1002,7 +1029,9 @@ namespace OpenRA.Mods.Common.Traits
UnReserve();
var target = Target.FromCell(self.World, cell);
self.QueueActivity(order.Queued, new Fly(self, target, targetLineColor: Color.Green));
// TODO: this should scale with unit selection group size.
self.QueueActivity(order.Queued, new Fly(self, target, WDist.FromCells(8), targetLineColor: Color.Green));
self.ShowTargetLines();
}
else if (orderString == "Land")

View File

@@ -78,7 +78,9 @@ namespace OpenRA.Mods.Common.Traits
var targetLocation = move.NearestMoveableCell(cell);
var assaultMoving = order.OrderString == "AssaultMove";
self.QueueActivity(new AttackMoveActivity(self, () => move.MoveTo(targetLocation, 1, targetLineColor: Color.OrangeRed), assaultMoving));
// TODO: this should scale with unit selection group size.
self.QueueActivity(new AttackMoveActivity(self, () => move.MoveTo(targetLocation, 8, targetLineColor: Color.OrangeRed), assaultMoving));
self.ShowTargetLines();
}
}

View File

@@ -29,9 +29,6 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Type tags on this exit.")]
public readonly HashSet<string> ProductionTypes = new HashSet<string>();
[Desc("AttackMove to a RallyPoint or stay where you are spawned.")]
public readonly bool MoveIntoWorld = true;
[Desc("Number of ticks to wait before moving into the world.")]
public readonly int ExitDelay = 0;

View File

@@ -11,6 +11,7 @@
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits.Render;
using OpenRA.Primitives;
using OpenRA.Traits;
@@ -106,7 +107,7 @@ namespace OpenRA.Mods.Common.Traits
}
void INotifyParachute.OnParachute(Actor self) { }
void INotifyParachute.OnLanded(Actor self, Actor ignore)
void INotifyParachute.OnLanded(Actor self)
{
// Check whether the crate landed on anything
var landedOn = self.World.ActorMap.GetActorsAt(self.Location)
@@ -237,6 +238,9 @@ namespace OpenRA.Mods.Common.Traits
var cs = self.World.WorldActor.TraitOrDefault<CrateSpawner>();
if (cs != null)
cs.IncrementCrates();
if (self.World.Map.DistanceAboveTerrain(CenterPosition) > WDist.Zero && self.TraitOrDefault<Parachutable>() != null)
self.QueueActivity(new Parachute(self));
}
void INotifyRemovedFromWorld.RemovedFromWorld(Actor self)

View File

@@ -82,11 +82,10 @@ namespace OpenRA.Mods.Common.Traits
self.World.AddFrameEndTask(w =>
{
pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell);
w.Add(pilot);
var dropPosition = pilot.CenterPosition + new WVec(0, 0, self.CenterPosition.Z - pilot.CenterPosition.Z);
pilotPositionable.SetVisualPosition(pilot, dropPosition);
pilot.QueueActivity(new Parachute(pilot));
w.Add(pilot);
});
Game.Sound.Play(SoundType.World, Info.ChuteSound, cp);

View File

@@ -71,6 +71,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("The pathfinding cost penalty applied for each harvester waiting to unload at a refinery.")]
public readonly int UnloadQueueCostModifier = 12;
[Desc("The pathfinding cost bonus/penalty applied for cells directly towards/away from the refinery.")]
public readonly int ResourceRefineryDirectionCostModifier = 100;
[Desc("Does the unit queue harvesting runs instead of individual harvest actions?")]
public readonly bool QueueFullLoad = false;

View File

@@ -144,6 +144,7 @@ namespace OpenRA.Mods.Common.Traits
{
readonly Actor self;
readonly Lazy<IEnumerable<int>> speedModifiers;
readonly int moveIntoWorldDelay;
#region IMove CurrentMovementTypes
MovementType movementTypes;
@@ -239,6 +240,8 @@ namespace OpenRA.Mods.Common.Traits
// Use LocationInit if you want to insert the actor into the ActorMap!
if (init.Contains<CenterPositionInit>())
SetVisualPosition(self, init.Get<CenterPositionInit, WPos>());
moveIntoWorldDelay = init.Contains<MoveIntoWorldDelayInit>() ? init.Get<MoveIntoWorldDelayInit, int>() : 0;
}
protected override void Created(Actor self)
@@ -251,6 +254,7 @@ namespace OpenRA.Mods.Common.Traits
Locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>()
.Single(l => l.Info.Name == Info.Locomotor);
self.QueueActivity(MoveIntoWorld(self, moveIntoWorldDelay));
base.Created(self);
}
@@ -563,22 +567,59 @@ namespace OpenRA.Mods.Common.Traits
return WrapMove(new Follow(self, target, minRange, maxRange, initialTargetPosition, targetLineColor));
}
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
public Activity MoveIntoWorld(Actor self, int delay = 0)
{
var pos = self.CenterPosition;
return new MoveIntoWorldActivity(self, delay);
}
if (subCell == SubCell.Any)
subCell = Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
class MoveIntoWorldActivity : Activity
{
readonly Actor self;
readonly Mobile mobile;
// TODO: solve/reduce cell is full problem
if (subCell == SubCell.Invalid)
subCell = self.World.Map.Grid.DefaultSubCell;
CPos cell;
SubCell subCell;
WPos pos;
int delay;
// Reserve the exit cell
SetPosition(self, cell, subCell);
SetVisualPosition(self, pos);
public MoveIntoWorldActivity(Actor self, int delay = 0)
{
this.self = self;
mobile = self.Trait<Mobile>();
IsInterruptible = false;
this.delay = delay;
}
return WrapMove(VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell), cell));
protected override void OnFirstRun(Actor self)
{
pos = self.CenterPosition;
if (self.World.Map.DistanceAboveTerrain(pos) > WDist.Zero && self.TraitOrDefault<Parachutable>() != null)
QueueChild(new Parachute(self));
}
public override bool Tick(Actor self)
{
pos = self.CenterPosition;
cell = mobile.ToCell;
subCell = mobile.ToSubCell;
if (subCell == SubCell.Any)
subCell = mobile.Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
// TODO: solve/reduce cell is full problem
if (subCell == SubCell.Invalid)
subCell = self.World.Map.Grid.DefaultSubCell;
// Reserve the exit cell
mobile.SetPosition(self, cell, subCell);
mobile.SetVisualPosition(self, pos);
if (delay > 0)
QueueChild(new Wait(delay));
QueueChild(mobile.VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell)));
return true;
}
}
public Activity MoveToTarget(Actor self, Target target,

View File

@@ -105,12 +105,10 @@ namespace OpenRA.Mods.Common.Traits
self.World.AddFrameEndTask(w =>
{
dropPositionable.SetPosition(dropActor, dropCell, dropSubCell);
w.Add(dropActor);
var dropPosition = dropActor.CenterPosition + new WVec(0, 0, self.CenterPosition.Z - dropActor.CenterPosition.Z);
dropPositionable.SetVisualPosition(dropActor, dropPosition);
dropActor.QueueActivity(new Parachute(dropActor));
w.Add(dropActor);
});
Game.Sound.Play(SoundType.World, info.ChuteSound, self.CenterPosition);

View File

@@ -62,6 +62,8 @@ namespace OpenRA.Mods.Common.Traits
readonly ParachutableInfo info;
readonly IPositionable positionable;
public Actor IgnoreActor;
ConditionManager conditionManager;
int parachutingToken = ConditionManager.InvalidConditionToken;
@@ -86,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits
parachutingToken = conditionManager.GrantCondition(self, info.ParachutingCondition);
}
void INotifyParachute.OnLanded(Actor self, Actor ignore)
void INotifyParachute.OnLanded(Actor self)
{
IsInAir = false;
@@ -100,7 +102,7 @@ namespace OpenRA.Mods.Common.Traits
if (positionable.CanEnterCell(cell, self))
return;
if (ignore != null && self.World.ActorMap.GetActorsAt(cell).Any(a => a != ignore))
if (IgnoreActor != null && self.World.ActorMap.GetActorsAt(cell).Any(a => a != IgnoreActor))
return;
var onWater = info.WaterTerrainTypes.Contains(self.World.Map.GetTerrainInfo(cell).Type);

View File

@@ -162,7 +162,7 @@ namespace OpenRA.Mods.Common.Traits
if (!order.Queued)
self.CancelActivity();
self.QueueActivity(new EnterTransport(self, order.Target));
self.QueueActivity(new RideTransport(self, order.Target));
self.ShowTargetLines();
}

View File

@@ -73,6 +73,8 @@ namespace OpenRA.Mods.Common.Traits
td.Add(new LocationInit(exit));
td.Add(new CenterPositionInit(spawn));
td.Add(new FacingInit(initialFacing));
if (exitinfo != null)
td.Add(new MoveIntoWorldDelayInit(exitinfo.ExitDelay));
}
self.World.AddFrameEndTask(w =>
@@ -81,16 +83,7 @@ namespace OpenRA.Mods.Common.Traits
var move = newUnit.TraitOrDefault<IMove>();
if (exitinfo != null && move != null)
{
if (exitinfo.MoveIntoWorld)
{
if (exitinfo.ExitDelay > 0)
newUnit.QueueActivity(new Wait(exitinfo.ExitDelay, false));
newUnit.QueueActivity(move.MoveIntoWorld(newUnit, exit));
newUnit.QueueActivity(new AttackMoveActivity(newUnit, () => move.MoveTo(exitLocation, 1, targetLineColor: Color.OrangeRed)));
}
}
newUnit.QueueActivity(new AttackMoveActivity(newUnit, () => move.MoveTo(exitLocation, 1, targetLineColor: Color.OrangeRed)));
if (!self.IsDead)
foreach (var t in self.TraitsImplementing<INotifyProduction>())

View File

@@ -127,25 +127,17 @@ namespace OpenRA.Mods.Common.Traits
td.Add(new LocationInit(exit));
td.Add(new CenterPositionInit(spawn));
td.Add(new FacingInit(initialFacing));
td.Add(new MoveIntoWorldDelayInit(exitinfo.ExitDelay));
}
self.World.AddFrameEndTask(w =>
{
var newUnit = self.World.CreateActor(producee.Name, td);
newUnit.Trait<Parachutable>().IgnoreActor = self;
newUnit.QueueActivity(new Parachute(newUnit, self));
var move = newUnit.TraitOrDefault<IMove>();
if (move != null)
{
if (exitinfo.MoveIntoWorld)
{
if (exitinfo.ExitDelay > 0)
newUnit.QueueActivity(new Wait(exitinfo.ExitDelay, false));
newUnit.QueueActivity(move.MoveIntoWorld(newUnit, exit));
newUnit.QueueActivity(new AttackMoveActivity(newUnit, () => move.MoveTo(exitLocation, 1, targetLineColor: Color.OrangeRed)));
}
}
if (!self.IsDead)
foreach (var t in self.TraitsImplementing<INotifyProduction>())

View File

@@ -81,7 +81,7 @@ namespace OpenRA.Mods.Common.Traits.Render
void INotifyParachute.OnParachute(Actor self) { }
void INotifyParachute.OnLanded(Actor self, Actor ignore)
void INotifyParachute.OnLanded(Actor self)
{
PlaySequence();
}

View File

@@ -145,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits
[RequireExplicitImplementation]
public interface INotifyResourceAccepted { void OnResourceAccepted(Actor self, Actor refinery, int amount); }
public interface INotifyParachute { void OnParachute(Actor self); void OnLanded(Actor self, Actor ignore); }
public interface INotifyParachute { void OnParachute(Actor self); void OnLanded(Actor self); }
[RequireExplicitImplementation]
public interface INotifyCapture { void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner, BitSet<CaptureType> captureTypes); }
@@ -435,7 +435,7 @@ namespace OpenRA.Mods.Common.Traits
WPos? initialTargetPosition = null, Color? targetLineColor = null);
Activity MoveToTarget(Actor self, Target target,
WPos? initialTargetPosition = null, Color? targetLineColor = null);
Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any);
Activity MoveIntoWorld(Actor self, int delay = 0);
Activity MoveIntoTarget(Actor self, Target target);
Activity VisualMove(Actor self, WPos fromPos, WPos toPos);
int EstimatedMoveDuration(Actor self, WPos fromPos, WPos toPos);

View File

@@ -0,0 +1,38 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA.Mods.Common.UpdateRules.Rules
{
public class RemoveMoveIntoWorldFromExit : UpdateRule
{
public override string Name { get { return "Remove MoveIntoWorld from Exit."; } }
public override string Description
{
get
{
return "The MoveIntoWorld parameter has been removed from the Exit trait because it no \n" +
"longer serves a purpose (aircraft can now use the same exit procedure as other \n" +
"units).";
}
}
public override IEnumerable<string> UpdateActorNode(ModData modData, MiniYamlNode actorNode)
{
foreach (var t in actorNode.ChildrenMatching("Exit"))
t.RemoveNodes("MoveIntoWorld");
yield break;
}
}
}

View File

@@ -133,6 +133,7 @@ namespace OpenRA.Mods.Common.UpdateRules
new AddCanSlide(),
new AddAircraftIdleBehavior(),
new RenameSearchRadius(),
new RemoveMoveIntoWorldFromExit(),
})
};

View File

@@ -24,10 +24,7 @@ namespace OpenRA.Mods.Common.Widgets
public int LeftMargin = 5;
public int RightMargin = 5;
public Action OnTakeFocus = () => { };
public Action OnLoseFocus = () => { };
public Action OnEscape = () => { };
public Action OnReturn = () => { };
public Func<bool> IsDisabled = () => false;
public Func<bool> IsValid = () => false;
@@ -49,7 +46,6 @@ namespace OpenRA.Mods.Common.Widgets
public override bool TakeKeyboardFocus()
{
OnTakeFocus();
return base.TakeKeyboardFocus();
}
@@ -62,6 +58,12 @@ namespace OpenRA.Mods.Common.Widgets
return base.YieldKeyboardFocus();
}
public bool ForceYieldKeyboardFocus()
{
OnLoseFocus();
return base.YieldKeyboardFocus();
}
public override bool HandleMouseInput(MouseInput mi)
{
if (IsDisabled())
@@ -95,15 +97,9 @@ namespace OpenRA.Mods.Common.Widgets
if (!HasKeyboardFocus || IgnoreKeys.Contains(e.Key))
return false;
if (e.Key != Keycode.ESCAPE && e.Key != Keycode.RETURN)
if (e.Key != Keycode.ESCAPE && e.Key != Keycode.RETURN && e.Key != Keycode.KP_ENTER)
Key = Hotkey.FromKeyInput(e);
if (e.Key == Keycode.ESCAPE)
OnEscape();
if (e.Key == Keycode.RETURN)
OnReturn();
YieldKeyboardFocus();
return true;

View File

@@ -1,149 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class HotkeyDialogLogic : ChromeLogic
{
readonly Widget panel;
readonly ButtonWidget resetButton, clearButton, cancelButton;
readonly LabelWidget duplicateNotice, defaultNotice, originalNotice;
readonly Action onSave;
readonly HotkeyDefinition definition;
readonly HotkeyManager manager;
readonly HotkeyEntryWidget hotkeyEntry;
readonly bool isFirstValidation = true;
Hotkey currentHotkey;
HotkeyDefinition duplicateHotkey;
bool isValid = false;
bool isValidating = false;
[ObjectCreator.UseCtor]
public HotkeyDialogLogic(Widget widget, Action onSave, HotkeyDefinition hotkeyDefinition, HotkeyManager hotkeyManager)
{
panel = widget;
this.onSave = onSave;
definition = hotkeyDefinition;
manager = hotkeyManager;
currentHotkey = manager[definition.Name].GetValue();
hotkeyEntry = panel.Get<HotkeyEntryWidget>("HOTKEY_ENTRY");
resetButton = panel.Get<ButtonWidget>("RESET_BUTTON");
clearButton = panel.Get<ButtonWidget>("CLEAR_BUTTON");
cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
duplicateNotice = panel.Get<LabelWidget>("DUPLICATE_NOTICE");
defaultNotice = panel.Get<LabelWidget>("DEFAULT_NOTICE");
originalNotice = panel.Get<LabelWidget>("ORIGINAL_NOTICE");
panel.Get<LabelWidget>("HOTKEY_LABEL").GetText = () => hotkeyDefinition.Description + ":";
duplicateNotice.TextColor = ChromeMetrics.Get<Color>("NoticeErrorColor");
duplicateNotice.GetText = () =>
{
return (duplicateHotkey != null) ? duplicateNotice.Text.F(duplicateHotkey.Description) : duplicateNotice.Text;
};
defaultNotice.TextColor = ChromeMetrics.Get<Color>("NoticeInfoColor");
originalNotice.TextColor = ChromeMetrics.Get<Color>("NoticeInfoColor");
originalNotice.Text = originalNotice.Text.F(hotkeyDefinition.Default.DisplayString());
resetButton.OnClick = Reset;
clearButton.OnClick = Clear;
cancelButton.OnClick = Cancel;
hotkeyEntry.Key = currentHotkey;
hotkeyEntry.IsValid = () => isValid;
hotkeyEntry.OnTakeFocus = OnHotkeyEntryTakeFocus;
hotkeyEntry.OnLoseFocus = OnHotkeyEntryLoseFocus;
hotkeyEntry.OnEscape = Cancel;
hotkeyEntry.OnReturn = Cancel;
hotkeyEntry.TakeKeyboardFocus();
Validate();
isFirstValidation = false;
}
void OnHotkeyEntryTakeFocus()
{
cancelButton.Disabled = manager.GetFirstDuplicate(definition.Name, currentHotkey, definition) != null;
}
void OnHotkeyEntryLoseFocus()
{
cancelButton.Disabled = true;
if (!isValidating)
Validate();
}
void Validate()
{
isValidating = true;
duplicateHotkey = manager.GetFirstDuplicate(definition.Name, hotkeyEntry.Key, definition);
isValid = duplicateHotkey == null;
duplicateNotice.Visible = !isValid;
clearButton.Disabled = !hotkeyEntry.Key.IsValid();
if (hotkeyEntry.Key == definition.Default || (!hotkeyEntry.Key.IsValid() && !definition.Default.IsValid()))
{
defaultNotice.Visible = !duplicateNotice.Visible;
originalNotice.Visible = false;
resetButton.Disabled = true;
}
else
{
defaultNotice.Visible = false;
originalNotice.Visible = !duplicateNotice.Visible;
resetButton.Disabled = false;
}
if (isValid && !isFirstValidation)
{
currentHotkey = hotkeyEntry.Key;
hotkeyEntry.YieldKeyboardFocus();
Save();
}
else
hotkeyEntry.TakeKeyboardFocus();
isValidating = false;
}
void Save()
{
manager.Set(definition.Name, hotkeyEntry.Key);
Game.Settings.Save();
onSave();
}
void Cancel()
{
cancelButton.Disabled = true;
hotkeyEntry.Key = currentHotkey;
Validate();
}
void Reset()
{
hotkeyEntry.Key = definition.Default;
Validate();
}
void Clear()
{
hotkeyEntry.Key = Hotkey.Invalid;
Validate();
}
}
}

View File

@@ -49,10 +49,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var players = world.Players.Where(p => p != world.LocalPlayer && !p.NonCombatant && !p.IsBot);
var isObserver = orderManager.LocalClient != null && orderManager.LocalClient.IsObserver;
var isOnlyObserver = isObserver && orderManager.LobbyInfo.Clients.All(c => c == orderManager.LocalClient || !c.IsObserver);
var observersExist = orderManager.LobbyInfo.Clients.Any(c => c.IsObserver);
var alwaysDisabled = world.IsReplay || world.LobbyInfo.NonBotClients.Count() == 1;
var disableTeamChat = alwaysDisabled || isOnlyObserver || (world.LocalPlayer != null && !players.Any(p => p.IsAlliedWith(world.LocalPlayer)));
var disableTeamChat = alwaysDisabled || (world.LocalPlayer != null && !players.Any(p => p.IsAlliedWith(world.LocalPlayer)));
var teamChat = !disableTeamChat;
tabCompletion.Commands = chatTraits.OfType<ChatCommands>().SelectMany(x => x.Commands.Keys).ToList();
@@ -73,47 +71,26 @@ namespace OpenRA.Mods.Common.Widgets.Logic
chatMode.GetText = () => teamChat && !disableTeamChat ? "Team" : "All";
chatMode.OnClick = () => teamChat ^= true;
// Team chat is disabled if we are the only spectator
// This changes as soon as a defeated player can talk in the spectator chat
if (!alwaysDisabled && isOnlyObserver)
// Enable teamchat if we are a player and die,
// or disable it when we are the only one left in the team
if (!alwaysDisabled && world.LocalPlayer != null)
{
chatMode.IsDisabled = () =>
{
if (world.IsGameOver)
return true;
disableTeamChat = players.All(p => p.WinState == WinState.Undefined);
return disableTeamChat;
};
}
else if (!alwaysDisabled && world.LocalPlayer != null)
{
chatMode.IsDisabled = () =>
{
if (world.IsGameOver)
return true;
// Check if we are the only living team member
if (players.All(p => p.WinState != WinState.Undefined || !p.IsAlliedWith(world.LocalPlayer)))
{
disableTeamChat = true;
return disableTeamChat;
}
// Still alive and nothing changed since the start
if (world.LocalPlayer.WinState == WinState.Undefined)
return disableTeamChat;
// At this point our player is dead
// Allow to chat with existing spectators
if (observersExist)
// The game is over for us, join spectator team chat
if (world.LocalPlayer.WinState != WinState.Undefined)
{
disableTeamChat = false;
return disableTeamChat;
}
// Or wait until another player died
disableTeamChat = players.All(p => p.WinState == WinState.Undefined);
// If team chat isn't already disabled, check if we are the only living team member
if (!disableTeamChat)
disableTeamChat = players.All(p => p.WinState != WinState.Undefined || !p.IsAlliedWith(world.LocalPlayer));
return disableTeamChat;
};
}

View File

@@ -39,6 +39,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SoundDevice soundDevice;
PanelType settingsPanel = PanelType.Display;
Widget hotkeyDialogRoot;
ButtonWidget resetHotkeyButton, clearHotkeyButton, selectedHotkeyButton;
LabelWidget duplicateHotkeyNotice, defaultHotkeyNotice, originalHotkeyNotice;
HotkeyEntryWidget hotkeyEntryWidget;
HotkeyDefinition duplicateHotkeyDefinition, selectedHotkeyDefinition;
bool isHotkeyValid = false;
static SettingsLogic()
{
var original = Game.Settings;
@@ -125,7 +132,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
ss.OnChange += x => field.SetValue(group, x);
}
static void BindHotkeyPref(HotkeyDefinition hd, HotkeyManager manager, Widget template, Widget parent, Widget remapDialogRoot, Widget remapDialogPlaceholder)
void BindHotkeyPref(HotkeyDefinition hd, Widget template, Widget parent)
{
var key = template.Clone() as Widget;
key.Id = hd.Name;
@@ -134,50 +141,31 @@ namespace OpenRA.Mods.Common.Widgets.Logic
key.Get<LabelWidget>("FUNCTION").GetText = () => hd.Description + ":";
var remapButton = key.Get<ButtonWidget>("HOTKEY");
WidgetUtils.TruncateButtonToTooltip(remapButton, manager[hd.Name].GetValue().DisplayString());
WidgetUtils.TruncateButtonToTooltip(remapButton, modData.Hotkeys[hd.Name].GetValue().DisplayString());
if (manager.GetFirstDuplicate(hd.Name, manager[hd.Name].GetValue(), hd) != null)
remapButton.GetColor = () => ChromeMetrics.Get<Color>("HotkeyColorInvalid");
remapButton.IsHighlighted = () => selectedHotkeyDefinition == hd;
remapButton.GetColor = () =>
{
return modData.Hotkeys.GetFirstDuplicate(hd.Name, modData.Hotkeys[hd.Name].GetValue(), hd) != null ?
ChromeMetrics.Get<Color>("HotkeyColorInvalid") :
ChromeMetrics.Get<Color>("HotkeyColor");
};
if (selectedHotkeyDefinition == hd)
{
selectedHotkeyButton = remapButton;
hotkeyEntryWidget.Key = modData.Hotkeys[hd.Name].GetValue();
ValidateHotkey();
}
remapButton.OnClick = () =>
{
remapDialogRoot.RemoveChildren();
if (remapButton.IsHighlighted())
{
remapButton.IsHighlighted = () => false;
if (remapDialogPlaceholder != null)
remapDialogPlaceholder.Visible = true;
return;
}
if (remapDialogPlaceholder != null)
remapDialogPlaceholder.Visible = false;
var siblings = parent.Children;
foreach (var sibling in siblings)
{
var button = sibling.GetOrNull<ButtonWidget>("HOTKEY");
if (button != null)
button.IsHighlighted = () => false;
}
remapButton.IsHighlighted = () => true;
Ui.LoadWidget("HOTKEY_DIALOG", remapDialogRoot, new WidgetArgs
{
{
"onSave", () =>
{
WidgetUtils.TruncateButtonToTooltip(remapButton, manager[hd.Name].GetValue().DisplayString());
remapButton.GetColor = () => ChromeMetrics.Get<Color>("ButtonTextColor");
}
},
{ "hotkeyDefinition", hd },
{ "hotkeyManager", manager },
});
selectedHotkeyDefinition = hd;
selectedHotkeyButton = remapButton;
hotkeyEntryWidget.Key = modData.Hotkeys[hd.Name].GetValue();
ValidateHotkey();
hotkeyEntryWidget.TakeKeyboardFocus();
};
parent.AddChild(key);
@@ -463,6 +451,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Action InitHotkeysPanel(Widget panel)
{
hotkeyDialogRoot = panel.Get("HOTKEY_DIALOG_ROOT");
var hotkeyList = panel.Get<ScrollPanelWidget>("HOTKEY_LIST");
hotkeyList.Layout = new GridLayout(hotkeyList);
var hotkeyHeader = hotkeyList.Get<ScrollItemWidget>("HEADER");
@@ -475,6 +464,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
MiniYaml hotkeyGroups;
if (logicArgs.TryGetValue("HotkeyGroups", out hotkeyGroups))
{
InitHotkeyRemapDialog();
foreach (var hg in hotkeyGroups.Nodes)
{
var templateNode = hg.Value.Nodes.FirstOrDefault(n => n.Key == "Template");
@@ -489,16 +480,28 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var types = FieldLoader.GetValue<string[]>("Types", typesNode.Value.Value);
var added = new HashSet<HotkeyDefinition>();
var template = templates.Get(templateNode.Value.Value);
var remapDialogRoot = panel.Get("HOTKEY_DIALOG_ROOT");
var remapDialogPlaceholder = panel.GetOrNull("HOTKEY_DIALOG_PLACEHOLDER");
foreach (var t in types)
{
foreach (var hd in modData.Hotkeys.Definitions.Where(k => k.Types.Contains(t)))
{
if (added.Add(hd))
BindHotkeyPref(hd, modData.Hotkeys, template, hotkeyList, remapDialogRoot, remapDialogPlaceholder);
{
if (selectedHotkeyDefinition == null)
selectedHotkeyDefinition = hd;
BindHotkeyPref(hd, template, hotkeyList);
}
}
}
}
}
return () => { };
return () =>
{
hotkeyEntryWidget.Key = modData.Hotkeys[selectedHotkeyDefinition.Name].GetValue();
hotkeyEntryWidget.ForceYieldKeyboardFocus();
};
}
Action ResetInputPanel(Widget panel)
@@ -532,7 +535,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var hd in modData.Hotkeys.Definitions)
{
modData.Hotkeys.Set(hd.Name, hd.Default);
panel.Get(hd.Name).Get<HotkeyEntryWidget>("HOTKEY").Key = hd.Default;
WidgetUtils.TruncateButtonToTooltip(panel.Get(hd.Name).Get<ButtonWidget>("HOTKEY"), hd.Default.DisplayString());
}
};
}
@@ -745,5 +748,78 @@ namespace OpenRA.Mods.Common.Widgets.Logic
else
Game.Renderer.ReleaseWindowMouseFocus();
}
void InitHotkeyRemapDialog()
{
hotkeyDialogRoot.Get<LabelWidget>("HOTKEY_LABEL").GetText = () => selectedHotkeyDefinition.Description + ":";
duplicateHotkeyNotice = hotkeyDialogRoot.Get<LabelWidget>("DUPLICATE_NOTICE");
duplicateHotkeyNotice.TextColor = ChromeMetrics.Get<Color>("NoticeErrorColor");
duplicateHotkeyNotice.GetText = () =>
{
return (duplicateHotkeyDefinition != null) ? duplicateHotkeyNotice.Text.F(duplicateHotkeyDefinition.Description) : duplicateHotkeyNotice.Text;
};
defaultHotkeyNotice = hotkeyDialogRoot.Get<LabelWidget>("DEFAULT_NOTICE");
defaultHotkeyNotice.TextColor = ChromeMetrics.Get<Color>("NoticeInfoColor");
originalHotkeyNotice = hotkeyDialogRoot.Get<LabelWidget>("ORIGINAL_NOTICE");
originalHotkeyNotice.TextColor = ChromeMetrics.Get<Color>("NoticeInfoColor");
originalHotkeyNotice.GetText = () => originalHotkeyNotice.Text.F(selectedHotkeyDefinition.Default.DisplayString());
resetHotkeyButton = hotkeyDialogRoot.Get<ButtonWidget>("RESET_HOTKEY_BUTTON");
resetHotkeyButton.OnClick = ResetHotkey;
clearHotkeyButton = hotkeyDialogRoot.Get<ButtonWidget>("CLEAR_HOTKEY_BUTTON");
clearHotkeyButton.OnClick = ClearHotkey;
hotkeyEntryWidget = hotkeyDialogRoot.Get<HotkeyEntryWidget>("HOTKEY_ENTRY");
hotkeyEntryWidget.IsValid = () => isHotkeyValid;
hotkeyEntryWidget.OnLoseFocus = ValidateHotkey;
}
void ValidateHotkey()
{
duplicateHotkeyDefinition = modData.Hotkeys.GetFirstDuplicate(selectedHotkeyDefinition.Name, hotkeyEntryWidget.Key, selectedHotkeyDefinition);
isHotkeyValid = duplicateHotkeyDefinition == null;
duplicateHotkeyNotice.Visible = !isHotkeyValid;
clearHotkeyButton.Disabled = !hotkeyEntryWidget.Key.IsValid();
if (hotkeyEntryWidget.Key == selectedHotkeyDefinition.Default || (!hotkeyEntryWidget.Key.IsValid() && !selectedHotkeyDefinition.Default.IsValid()))
{
defaultHotkeyNotice.Visible = !duplicateHotkeyNotice.Visible;
originalHotkeyNotice.Visible = false;
resetHotkeyButton.Disabled = true;
}
else
{
defaultHotkeyNotice.Visible = false;
originalHotkeyNotice.Visible = !duplicateHotkeyNotice.Visible;
resetHotkeyButton.Disabled = false;
}
if (isHotkeyValid)
SaveHotkey();
}
void SaveHotkey()
{
WidgetUtils.TruncateButtonToTooltip(selectedHotkeyButton, hotkeyEntryWidget.Key.DisplayString());
modData.Hotkeys.Set(selectedHotkeyDefinition.Name, hotkeyEntryWidget.Key);
Game.Settings.Save();
}
void ResetHotkey()
{
hotkeyEntryWidget.Key = selectedHotkeyDefinition.Default;
hotkeyEntryWidget.YieldKeyboardFocus();
}
void ClearHotkey()
{
hotkeyEntryWidget.Key = Hotkey.Invalid;
hotkeyEntryWidget.YieldKeyboardFocus();
}
}
}

View File

@@ -1,83 +0,0 @@
Background@HOTKEY_DIALOG:
Logic: HotkeyDialogLogic
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-gray
Children:
Label@HOTKEY_LABEL:
X: 15
Y: 14
Width: PARENT_RIGHT - 40
Height: 25
Font: Bold
HotkeyEntry@HOTKEY_ENTRY:
X: 15
Y: 40
Width: 382
Height: 25
Container@NOTICES:
X: 15
Y: 65
Width: PARENT_RIGHT - 40
Height: 25
Children:
Label@DEFAULT_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This is the default hotkey.
Label@ORIGINAL_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: The default is "{0}"
Label@DUPLICATE_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This hotkey is already used for "{0}"
Button@CLEAR_BUTTON:
X: PARENT_RIGHT - 65 - 15 - 2 * (WIDTH + 10)
Y: 40
Width: 25
Height: 25
TooltipText: Unbind the hotkey
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Children:
Image:
ImageCollection: lobby-bits
ImageName: kick
X: 7
Y: 8
IgnoreMouseOver: True
Button@RESET_BUTTON:
X: PARENT_RIGHT - 65 - 15 - WIDTH - 10
Y: 40
Width: 25
Height: 25
TooltipText: Reset to default
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Children:
Image@IMAGE_RELOAD:
X: 5
Y: 5
Width: 16
Height: 16
ImageCollection: reload-icon
ImageName: enabled
IgnoreMouseOver: True
Button@CANCEL_BUTTON:
X: PARENT_RIGHT - WIDTH - 15
Y: 40
Width: 65
Height: 25
Text: Cancel
TooltipText: Cancel the operation
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
TooltipContainer@TOOLTIP_CONTAINER:

View File

@@ -480,7 +480,7 @@ Container@SETTINGS_PANEL:
Width: PARENT_RIGHT - 15
TopBottomSpacing: 4
ItemSpacing: 4
Height: 210
Height: 242
Children:
ScrollItem@HEADER:
Width: 528
@@ -526,23 +526,80 @@ Container@SETTINGS_PANEL:
Height: 25
Align: Left
TooltipContainer: TOOLTIP_CONTAINER
Background@HOTKEY_DIALOG_PLACEHOLDER:
Y: 225
Background@HOTKEY_DIALOG_ROOT:
Y: 255
Width: PARENT_RIGHT - 15
Height: 105
Height: 75
Background: panel-gray
Children:
Label@HOTKEY_DIALOG_HELPTEXT:
Y: PARENT_BOTTOM / 2 - 12
Width: PARENT_RIGHT
Label@HOTKEY_LABEL:
X: 15
Y: 14
Width: 220 - 15 - 10
Height: 25
Font: Tiny
Align: Center
Text: Click on a hotkey to start rebinding
Container@HOTKEY_DIALOG_ROOT:
Y: 225
Width: PARENT_RIGHT - 15
Height: 105
Font: Bold
Align: Right
HotkeyEntry@HOTKEY_ENTRY:
X: 220
Y: 15
Width: 254
Height: 25
Container@NOTICES:
X: 220
Y: 40
Width: 254
Height: 25
Children:
Label@DEFAULT_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This is the default
Label@ORIGINAL_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: The default is "{0}"
Label@DUPLICATE_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This is already used for "{0}"
Button@CLEAR_HOTKEY_BUTTON:
X: PARENT_RIGHT - 25 - 15 - WIDTH - 10
Y: 15
Width: 25
Height: 25
TooltipText: Unbind the hotkey
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Children:
Image:
ImageCollection: lobby-bits
ImageName: kick
X: 7
Y: 8
IgnoreMouseOver: True
Button@RESET_HOTKEY_BUTTON:
X: PARENT_RIGHT - WIDTH - 15
Y: 15
Width: 25
Height: 25
TooltipText: Reset to default
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Children:
Image@IMAGE_RELOAD:
X: 5
Y: 5
Width: 16
Height: 16
ImageCollection: reload-icon
ImageName: enabled
IgnoreMouseOver: True
Container@ADVANCED_PANEL:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM

View File

@@ -128,7 +128,6 @@ ChromeLayout:
cnc|chrome/assetbrowser.yaml
cnc|chrome/missionbrowser.yaml
cnc|chrome/editor.yaml
cnc|chrome/dialog-hotkey.yaml
Voices:
cnc|audio/voices.yaml

View File

@@ -1,69 +0,0 @@
Background@HOTKEY_DIALOG:
Logic: HotkeyDialogLogic
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: dialog3
Children:
Label@HOTKEY_LABEL:
X: 20
Y: 14
Width: PARENT_RIGHT - 40
Height: 25
Font: Bold
HotkeyEntry@HOTKEY_ENTRY:
X: 20
Y: 40
Width: 280
Height: 25
Container@NOTICES:
X: 20
Y: 65
Width: PARENT_RIGHT - 40
Height: 25
Children:
Label@DEFAULT_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This is the default
Label@ORIGINAL_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: The default is "{0}"
Label@DUPLICATE_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This hotkey is already used for "{0}"
Button@CLEAR_BUTTON:
X: PARENT_RIGHT - 3 * WIDTH - 40
Y: 41
Width: 65
Height: 25
Text: Clear
TooltipText: Unbind the hotkey
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Button@RESET_BUTTON:
X: PARENT_RIGHT - 2 * WIDTH - 30
Y: 41
Width: 65
Height: 25
Text: Reset
TooltipText: Reset to default
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Button@CANCEL_BUTTON:
X: PARENT_RIGHT - WIDTH - 20
Y: 41
Width: 65
Height: 25
Text: Cancel
TooltipText: Cancel the operation
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
TooltipContainer@TOOLTIP_CONTAINER:

View File

@@ -482,7 +482,7 @@ Background@SETTINGS_PANEL:
Width: PARENT_RIGHT - 30
TopBottomSpacing: 4
ItemSpacing: 4
Height: 183
Height: 222
Children:
ScrollItem@HEADER:
BaseName: scrollheader
@@ -539,25 +539,69 @@ Background@SETTINGS_PANEL:
Height: 25
Align: Left
TooltipContainer: TOOLTIP_CONTAINER
Background@HOTKEY_DIALOG_PLACEHOLDER:
Background@HOTKEY_DIALOG_ROOT:
X: 15
Y: 232
Y: 270
Width: PARENT_RIGHT - 30
Height: 108
Height: 73
Background: dialog3
Children:
Label@HOTKEY_DIALOG_HELPTEXT:
Y: PARENT_BOTTOM / 2 - 12
Width: PARENT_RIGHT
Label@HOTKEY_LABEL:
X: 15
Y: 19
Width: 219 - 15 - 10
Height: 25
Font: Tiny
Align: Center
Text: Click on a hotkey to start rebinding
Container@HOTKEY_DIALOG_ROOT:
X: 15
Y: 232
Width: PARENT_RIGHT - 30
Height: 108
Font: Bold
Align: Right
HotkeyEntry@HOTKEY_ENTRY:
X: 219
Y: 20
Width: 170
Height: 25
Container@NOTICES:
X: 219
Y: 42
Width: 170
Height: 25
Children:
Label@DEFAULT_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This is the default
Label@ORIGINAL_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: The default is "{0}"
Label@DUPLICATE_NOTICE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Font: Tiny
Align: Left
Text: This is already used for "{0}"
Button@CLEAR_HOTKEY_BUTTON:
X: PARENT_RIGHT - 2 * WIDTH - 30
Y: 20
Width: 65
Height: 25
Text: Clear
Font: Bold
TooltipText: Unbind the hotkey
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Button@RESET_HOTKEY_BUTTON:
X: PARENT_RIGHT - WIDTH - 20
Y: 20
Width: 65
Height: 25
Text: Reset
Font: Bold
TooltipText: Reset to default
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Container@ADVANCED_PANEL:
X: 5
Y: 50

View File

@@ -109,7 +109,6 @@ ChromeLayout:
common|chrome/replaybrowser.yaml
common|chrome/gamesave-browser.yaml
common|chrome/gamesave-loading.yaml
common|chrome/dialog-hotkey.yaml
Weapons:
d2k|weapons/debris.yaml

View File

@@ -124,7 +124,6 @@ ChromeLayout:
common|chrome/confirmation-dialogs.yaml
common|chrome/editor.yaml
common|chrome/playerprofile.yaml
common|chrome/dialog-hotkey.yaml
Weapons:
ra|weapons/explosions.yaml

View File

@@ -1344,7 +1344,6 @@ HPAD:
RequiresCondition: !being-captured
SpawnOffset: 0,-256,0
ExitCell: 0,0
MoveIntoWorld: false
Facing: 224
RallyPoint:
Production:
@@ -1435,7 +1434,6 @@ AFLD:
RequiresCondition: !being-captured
ExitCell: 1,1
Facing: 192
MoveIntoWorld: false
RallyPoint:
Production:
Produces: Aircraft, Plane

View File

@@ -172,7 +172,6 @@ ChromeLayout:
common|chrome/missionbrowser.yaml
common|chrome/confirmation-dialogs.yaml
common|chrome/editor.yaml
common|chrome/dialog-hotkey.yaml
Voices:
ts|audio/voices.yaml