Compare commits
20 Commits
devtest-20
...
devtest-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b05a0a9f2 | ||
|
|
22dd61676c | ||
|
|
ca75797169 | ||
|
|
b2e5379537 | ||
|
|
24247cc2e5 | ||
|
|
75bb879f84 | ||
|
|
d2b42fc9a1 | ||
|
|
a789a906ac | ||
|
|
cf2c842049 | ||
|
|
0f105d62d4 | ||
|
|
85d138624e | ||
|
|
2cde1effcb | ||
|
|
4d29c39a6d | ||
|
|
003943514b | ||
|
|
ba983754ba | ||
|
|
0ff9f0dfa6 | ||
|
|
474c912439 | ||
|
|
40b2768183 | ||
|
|
cbb5d072e3 | ||
|
|
4541d2b8ff |
24
.travis.yml
24
.travis.yml
@@ -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
|
||||
1
AUTHORS
1
AUTHORS
@@ -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
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).")]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>())
|
||||
|
||||
@@ -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>())
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,7 @@ namespace OpenRA.Mods.Common.UpdateRules
|
||||
new AddCanSlide(),
|
||||
new AddAircraftIdleBehavior(),
|
||||
new RenameSearchRadius(),
|
||||
new RemoveMoveIntoWorldFromExit(),
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user