#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * 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.Graphics; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [TraitLocation(SystemActors.World)] [Desc("Spawn base actor at the spawnpoint and support units in an annulus around the base actor. Both are defined at MPStartUnits. Attach this to the world actor.")] public class SpawnStartingUnitsInfo : TraitInfo, Requires, NotBefore, ILobbyOptions { public readonly string StartingUnitsClass = "none"; [TranslationReference] [Desc("Descriptive label for the starting units option in the lobby.")] public readonly string DropdownLabel = "dropdown-starting-units.label"; [TranslationReference] [Desc("Tooltip description for the starting units option in the lobby.")] public readonly string DropdownDescription = "dropdown-starting-units.description"; [Desc("Prevent the starting units option from being changed in the lobby.")] public readonly bool DropdownLocked = false; [Desc("Whether to display the starting units option in the lobby.")] public readonly bool DropdownVisible = true; [Desc("Display order for the starting units option in the lobby.")] public readonly int DropdownDisplayOrder = 0; IEnumerable ILobbyOptions.LobbyOptions(MapPreview map) { var startingUnits = new Dictionary(); // Duplicate classes are defined for different race variants foreach (var t in map.WorldActorInfo.TraitInfos()) startingUnits[t.Class] = map.GetLocalisedString(t.ClassName); if (startingUnits.Count > 0) yield return new LobbyOption(map, "startingunits", DropdownLabel, DropdownDescription, DropdownVisible, DropdownDisplayOrder, startingUnits, StartingUnitsClass, DropdownLocked); } public override object Create(ActorInitializer init) { return new SpawnStartingUnits(this); } } public class SpawnStartingUnits : IWorldLoaded { readonly SpawnStartingUnitsInfo info; public SpawnStartingUnits(SpawnStartingUnitsInfo info) { this.info = info; } public void WorldLoaded(World world, WorldRenderer wr) { foreach (var p in world.Players) if (p.Playable) SpawnUnitsForPlayer(world, p); } void SpawnUnitsForPlayer(World w, Player p) { var spawnClass = p.PlayerReference.StartingUnitsClass ?? w.LobbyInfo.GlobalSettings .OptionOrDefault("startingunits", info.StartingUnitsClass); var unitGroup = w.Map.Rules.Actors[SystemActors.World].TraitInfos() .Where(g => g.Class == spawnClass && g.Factions != null && g.Factions.Contains(p.Faction.InternalName)) .RandomOrDefault(w.SharedRandom); if (unitGroup == null) throw new InvalidOperationException($"No starting units defined for faction {p.Faction.InternalName} with class {spawnClass}"); if (unitGroup.BaseActor != null) { var facing = unitGroup.BaseActorFacing ?? new WAngle(w.SharedRandom.Next(1024)); w.CreateActor(unitGroup.BaseActor.ToLowerInvariant(), new TypeDictionary { new LocationInit(p.HomeLocation + unitGroup.BaseActorOffset), new OwnerInit(p), new SkipMakeAnimsInit(), new FacingInit(facing), }); } if (unitGroup.SupportActors.Length == 0) return; var supportSpawnCells = w.Map.FindTilesInAnnulus(p.HomeLocation, unitGroup.InnerSupportRadius + 1, unitGroup.OuterSupportRadius); foreach (var s in unitGroup.SupportActors) { var actorRules = w.Map.Rules.Actors[s.ToLowerInvariant()]; var ip = actorRules.TraitInfo(); var validCell = supportSpawnCells.Shuffle(w.SharedRandom).FirstOrDefault(c => ip.CanEnterCell(w, null, c)); if (validCell == CPos.Zero) { Log.Write("debug", $"No cells available to spawn starting unit {s} for player {p}"); continue; } var subCell = ip.SharesCell ? w.ActorMap.FreeSubCell(validCell) : 0; var facing = unitGroup.SupportActorsFacing ?? new WAngle(w.SharedRandom.Next(1024)); w.CreateActor(s.ToLowerInvariant(), new TypeDictionary { new OwnerInit(p), new LocationInit(validCell), new SubCellInit(subCell), new FacingInit(facing), }); } } } }