Skip to content

Commit

Permalink
Merge branch 'ca-engine/1.02-modifications' into ca-engine/1.02
Browse files Browse the repository at this point in the history
  • Loading branch information
darkademic committed May 27, 2024
2 parents 3a3314c + fa54888 commit eca31dd
Show file tree
Hide file tree
Showing 25 changed files with 551 additions and 26 deletions.
2 changes: 1 addition & 1 deletion OpenRA.Game/Map/MapPreview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPrevie
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));

var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules ?? ""));
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
newData.SetCustomRules(modData, this, rulesYaml, null);
}
Expand Down
3 changes: 3 additions & 0 deletions OpenRA.Game/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ public class GameSettings
[Desc("Allow mods to enable the Discord service that can interact with a local Discord client.")]
public bool EnableDiscordService = true;

// added for CA
public bool SelectionTooltip = true;

public TextNotificationPoolFilters TextNotificationPoolFilters = TextNotificationPoolFilters.Feedback | TextNotificationPoolFilters.Transients;
}

Expand Down
4 changes: 2 additions & 2 deletions OpenRA.Mods.Cnc/Traits/Disguise.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public enum RevealDisguiseType
}

[Desc("Provides access to the disguise command, which makes the actor appear to be another player's actor.")]
sealed class DisguiseInfo : TraitInfo
public class DisguiseInfo : TraitInfo
{
[VoiceReference]
public readonly string Voice = "Action";
Expand Down Expand Up @@ -99,7 +99,7 @@ sealed class DisguiseInfo : TraitInfo
public override object Create(ActorInitializer init) { return new Disguise(init.Self, this); }
}

sealed class Disguise : IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack,
public class Disguise : IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack,
INotifyDamage, INotifyLoadCargo, INotifyUnloadCargo, INotifyDemolition, INotifyInfiltration, ITick
{
public ActorInfo AsActor { get; private set; }
Expand Down
12 changes: 9 additions & 3 deletions OpenRA.Mods.Common/Activities/Air/FlyAttack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,17 @@ public override bool Tick(Actor self)
if (source == AttackSource.AttackMove)
return true;

// AbortOnResupply cancels the current activity (after resupplying) plus any queued activities
if (attackAircraft.Info.AbortOnResupply)
var queuedMovement = NextActivity is Fly || NextActivity is AttackMoveActivity;

// AbortOnResupply cancels the current activity (after resupplying) plus any queued activities that aren't just moving
if (attackAircraft.Info.AbortOnResupply && !queuedMovement)
NextActivity?.Cancel(self);

QueueChild(new ReturnToBase(self));
if (attackAircraft.Info.AbortOnResupply && queuedMovement)
Queue(new ReturnToBase(self));
else
QueueChild(new ReturnToBase(self));

returnToBase = true;
return attackAircraft.Info.AbortOnResupply;
}
Expand Down
7 changes: 6 additions & 1 deletion OpenRA.Mods.Common/Activities/Air/Land.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,13 @@ public override bool Tick(Actor self)
return false;
}

var sound = aircraft.Info.LandingSounds.RandomOrDefault(Game.CosmeticRandom);

if (aircraft.Info.LandingSounds.Length > 0)
Game.Sound.Play(SoundType.World, aircraft.Info.LandingSounds, self.World, aircraft.CenterPosition);
{
var shouldStart = aircraft.Info.AudibleThroughFog || (!self.World.ShroudObscures(self.CenterPosition) && !self.World.FogObscures(self.CenterPosition));
Game.Sound.Play(SoundType.World, sound, self.CenterPosition, shouldStart ? aircraft.Info.Volume : 0f);
}

foreach (var notify in self.TraitsImplementing<INotifyLanding>())
notify.Landing(self);
Expand Down
5 changes: 4 additions & 1 deletion OpenRA.Mods.Common/Activities/Air/TakeOff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ protected override void OnFirstRun(Actor self)

if (self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length > aircraft.Info.MinAirborneAltitude)
return;

var shouldStart = aircraft.Info.AudibleThroughFog || (!self.World.ShroudObscures(self.CenterPosition) && !self.World.FogObscures(self.CenterPosition));
var sound = aircraft.Info.TakeoffSounds.RandomOrDefault(Game.CosmeticRandom);

if (aircraft.Info.TakeoffSounds.Length > 0)
Game.Sound.Play(SoundType.World, aircraft.Info.TakeoffSounds, self.World, aircraft.CenterPosition);
Game.Sound.Play(SoundType.World, sound, self.CenterPosition, shouldStart ? aircraft.Info.Volume : 0f);

foreach (var notify in self.TraitsImplementing<INotifyTakeOff>())
notify.TakeOff(self);
Expand Down
8 changes: 7 additions & 1 deletion OpenRA.Mods.Common/Activities/PickupUnit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class PickupUnit : Activity

enum PickupState { Intercept, LockCarryable, Pickup }
PickupState state = PickupState.Intercept;
bool reserveFailed;

public PickupUnit(Actor self, Actor cargo, int delay, Color? targetLineColor)
{
Expand All @@ -52,7 +53,10 @@ protected override void OnFirstRun(Actor self)
{
// The cargo might have become invalid while we were moving towards it.
if (cargo.IsDead || carryable.IsTraitDisabled || !cargo.AppearsFriendlyTo(self))
{
reserveFailed = true;
return;
}

if (carryall.ReserveCarryable(self, cargo))
{
Expand All @@ -61,11 +65,13 @@ protected override void OnFirstRun(Actor self)
QueueChild(new Fly(self, Target.FromActor(cargo)));
QueueChild(new FlyIdle(self, idleTurn: false));
}
else
reserveFailed = true;
}

public override bool Tick(Actor self)
{
if (IsCanceling)
if (IsCanceling || reserveFailed)
return true;

if (cargo.IsDead || carryable.IsTraitDisabled || !cargo.AppearsFriendlyTo(self) || cargo != carryall.Carryable)
Expand Down
6 changes: 6 additions & 0 deletions OpenRA.Mods.Common/Traits/Air/Aircraft.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ public class AircraftInfo : PausableConditionalTraitInfo, IPositionableInfo, IFa
[Desc("Sounds to play when the actor is landing.")]
public readonly string[] LandingSounds = Array.Empty<string>();

[Desc("Do the sounds play under shroud or fog.")]
public readonly bool AudibleThroughFog = false;

[Desc("Volume the sounds played at.")]
public readonly float Volume = 1f;

[Desc("The distance of the resupply base that the aircraft will wait for its turn.")]
public readonly WDist WaitDistanceFromResupplyBase = new(3072);

Expand Down
149 changes: 149 additions & 0 deletions OpenRA.Mods.Common/Traits/BotModules/BridgeRepairBotModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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 System.Collections.Generic;
using System.Linq;
using OpenRA.Traits;

namespace OpenRA.Mods.Common.Traits
{
[Desc("Manages AI legacy bridge repair logic.")]
public class BridgeRepairBotModuleInfo : ConditionalTraitInfo
{
[ActorReference]
[Desc("Actor types that can repair bridges (via `RepairsBridges`).",
"Leave this empty to disable the trait.")]
public readonly HashSet<string> RepairActorTypes = new HashSet<string>();

[Desc("Avoid enemy actors nearby when searching for bridges in need of repair.",
"Should be somewhere near the max weapon range.")]
public readonly WDist EnemyAvoidanceRadius = WDist.FromCells(8);

[Desc("Minimum delay (in ticks) between trying to scan for repair targets.")]
public readonly int MinimumWaitDelay = 300;

public override object Create(ActorInitializer init) { return new BridgeRepairBotModule(init.Self, this); }
}

public class BridgeRepairBotModule : ConditionalTrait<BridgeRepairBotModuleInfo>, IBotTick
{
readonly World world;
readonly Player player;
readonly Predicate<Actor> unitCannotBeOrderedOrIsIdle;

// Units that the bot already knows about and has given a repair order. Any unit not on this list needs to be given a new order.
readonly List<Actor> activeRepairers = new List<Actor>();

int waitDelayTicks;

IBotRequestUnitProduction[] requestUnitProduction;

public BridgeRepairBotModule(Actor self, BridgeRepairBotModuleInfo info)
: base(info)
{
world = self.World;
player = self.Owner;

if (world.Type == WorldType.Editor)
return;

unitCannotBeOrderedOrIsIdle = a => a.Owner != player || a.IsDead || !a.IsInWorld || a.IsIdle;
}

protected override void TraitEnabled(Actor self)
{
// Avoid all AIs reevaluating assignments on the same tick, randomize their initial evaluation delay.
waitDelayTicks = world.LocalRandom.Next(Info.MinimumWaitDelay);

requestUnitProduction = player.PlayerActor.TraitsImplementing<IBotRequestUnitProduction>().ToArray();
}

void IBotTick.BotTick(IBot bot)
{
if (--waitDelayTicks <= 0)
{
waitDelayTicks = Info.MinimumWaitDelay;
QueueRepairOrders(bot);
}
}

void QueueRepairOrders(IBot bot)
{
if (!Info.RepairActorTypes.Any() || player.WinState != WinState.Undefined)
return;

activeRepairers.RemoveAll(unitCannotBeOrderedOrIsIdle);

var targetOptions = world.ActorsWithTrait<LegacyBridgeHut>().Where(
b => b.Trait.BridgeDamageState == DamageState.Dead);

if (!targetOptions.Any())
return;

var newUnits = world.ActorsHavingTrait<IPositionable>()
.Where(a => a.Owner == player && !activeRepairers.Contains(a));

var repairers = newUnits
.Where(a => a.IsIdle && Info.RepairActorTypes.Contains(a.Info.Name) && a.Info.HasTraitInfo<RepairsBridgesInfo>())
.Select(a => new TraitPair<RepairsBridges>(a, a.TraitOrDefault<RepairsBridges>()))
.Where(tp => tp.Trait != null)
.ToArray();

foreach (var repairer in repairers)
{
var nearestTargets = targetOptions.OrderBy(target => (target.Actor.CenterPosition - repairer.Actor.CenterPosition).LengthSquared);
foreach (var nearestTarget in nearestTargets)
{
if (activeRepairers.Contains(repairer.Actor))
continue;

var safeTarget = SafePath(repairer.Actor, nearestTarget.Actor);
if (safeTarget.Type == TargetType.Invalid)
continue;

bot.QueueOrder(new Order("RepairBridge", repairer.Actor, safeTarget, true));
AIUtils.BotDebug("AI ({0}): Ordered {1} to repair {2}", player.ClientIndex, repairer.Actor, nearestTarget.Actor);
activeRepairers.Add(repairer.Actor);
}
}

// Request a new repairer on demand.
var unitBuilder = requestUnitProduction.FirstOrDefault(Exts.IsTraitEnabled);
if (unitBuilder != null)
{
var engineers = AIUtils.CountActorByCommonName(Info.RepairActorTypes, player);
if (engineers == 0)
unitBuilder.RequestUnitProduction(bot, Info.RepairActorTypes.Random(world.LocalRandom));
}
}

Target SafePath(Actor repairer, Actor target)
{
var mobile = repairer.Trait<Mobile>();
var locomotor = mobile.Locomotor;

if (!mobile.PathFinder.PathExistsForLocomotor(locomotor, repairer.Location, target.Location))
return Target.Invalid;

var path = mobile.PathFinder.FindPathToTargetCellByPredicate(
repairer, new[] { repairer.Location }, loc => true, BlockedByActor.Stationary,
loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), Info.EnemyAvoidanceRadius)
.Where(u => !u.IsDead && repairer.Owner.RelationshipWith(u.Owner) == PlayerRelationship.Enemy && repairer.IsTargetableBy(u))
.Sum(u => Math.Max(WDist.Zero.Length, Info.EnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length)));

if (path.Count == 0)
return Target.Invalid;

return Target.FromActor(target);
}
}
}
15 changes: 13 additions & 2 deletions OpenRA.Mods.Common/Traits/Cloak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public class CloakInfo : PausableConditionalTraitInfo
public readonly string CloakSound = null;
public readonly string UncloakSound = null;

[Desc("Do the sounds play under shroud or fog.")]
public readonly bool AudibleThroughFog = false;

[Desc("Volume the sounds played at.")]
public readonly float Volume = 1f;

[PaletteReference(nameof(IsPlayerPalette))]
public readonly string Palette = "cloak";
public readonly bool IsPlayerPalette = false;
Expand Down Expand Up @@ -178,6 +184,11 @@ IEnumerable<Rectangle> IRenderModifier.ModifyScreenBounds(Actor self, WorldRende
return bounds;
}

bool SoundShouldStart(Actor self)
{
return Info.AudibleThroughFog || (!self.World.ShroudObscures(self.CenterPosition) && !self.World.FogObscures(self.CenterPosition));
}

void ITick.Tick(Actor self)
{
if (!IsTraitDisabled && !IsTraitPaused)
Expand All @@ -202,7 +213,7 @@ void ITick.Tick(Actor self)
if (!(firstTick && Info.InitialDelay == 0) && (otherCloaks == null || !otherCloaks.Any(a => a.Cloaked)))
{
var pos = self.CenterPosition;
Game.Sound.Play(SoundType.World, Info.CloakSound, self.CenterPosition);
Game.Sound.Play(SoundType.World, Info.CloakSound, pos, SoundShouldStart(self) ? Info.Volume : 0f);

Func<WPos> posfunc = () => self.CenterPosition + Info.EffectOffset;
if (!Info.EffectTracksActor)
Expand All @@ -227,7 +238,7 @@ void ITick.Tick(Actor self)
if (!(firstTick && Info.InitialDelay == 0) && (otherCloaks == null || !otherCloaks.Any(a => a.Cloaked)))
{
var pos = self.CenterPosition;
Game.Sound.Play(SoundType.World, Info.CloakSound, pos);
Game.Sound.Play(SoundType.World, Info.UncloakSound, pos, SoundShouldStart(self) ? Info.Volume : 0f);

Func<WPos> posfunc = () => self.CenterPosition + Info.EffectOffset;
if (!Info.EffectTracksActor)
Expand Down
40 changes: 39 additions & 1 deletion OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class GrantConditionOnDeployInfo : PausableConditionalTraitInfo, IEditorA
[Desc("Can this actor deploy on slopes?")]
public readonly bool CanDeployOnRamps = false;

[Desc("Does this actor need to synchronize it's deployment with other actors?")]
public readonly bool SmartDeploy = false;

[CursorReference]
[Desc("Cursor to display when able to (un)deploy the actor.")]
public readonly string DeployCursor = "deploy";
Expand Down Expand Up @@ -183,7 +186,42 @@ Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued)
return new Order("GrantConditionOnDeploy", self, queued);
}

bool IIssueDeployOrder.CanIssueDeployOrder(Actor self, bool queued) { return !IsTraitPaused && !IsTraitDisabled; }
bool IIssueDeployOrder.CanIssueDeployOrder(Actor self, bool queued) { return !IsTraitPaused && !IsTraitDisabled && IsGroupDeployNeeded(self); }

bool IsGroupDeployNeeded(Actor self)
{
if (!Info.SmartDeploy)
return true;

var actors = self.World.Selection.Actors;

bool hasDeployedActors = false;
bool hasUndeployedActors = false;

foreach (var a in actors)
{
GrantConditionOnDeploy gcod = null;
if (!a.IsDead && a.IsInWorld)
gcod = a.TraitOrDefault<GrantConditionOnDeploy>();

if (!hasDeployedActors && gcod != null && (gcod.DeployState == DeployState.Deploying || gcod.DeployState == DeployState.Deployed))
hasDeployedActors = true;

if (!hasUndeployedActors && gcod != null && (gcod.DeployState == DeployState.Undeploying || gcod.DeployState == DeployState.Undeployed))
hasUndeployedActors = true;

if (!self.IsDead && !self.Disposed && hasDeployedActors && hasUndeployedActors)
{
var self_gcod = self.TraitOrDefault<GrantConditionOnDeploy>();
if (self_gcod.DeployState == DeployState.Undeploying || self_gcod.DeployState == DeployState.Undeployed)
return true;

return false;
}
}

return true;
}

public void ResolveOrder(Actor self, Order order)
{
Expand Down
Loading

0 comments on commit eca31dd

Please sign in to comment.