Rewrite spawn point assignment logic.
This commit is contained in:
@@ -122,7 +122,7 @@ namespace OpenRA
|
|||||||
Team = client.Team,
|
Team = client.Team,
|
||||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
|
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||||
Fingerprint = client.Fingerprint
|
Fingerprint = client.Fingerprint
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,19 @@ namespace OpenRA
|
|||||||
public bool LockColor = false;
|
public bool LockColor = false;
|
||||||
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
|
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the "Home" location, which can be used by traits and scripts to e.g. set the initial camera
|
||||||
|
/// location or choose the map edge for reinforcements.
|
||||||
|
/// This will usually be overridden for client (lobby slot) players with a location based on the Spawn index
|
||||||
|
/// </summary>
|
||||||
|
public CPos HomeLocation = CPos.Zero;
|
||||||
|
|
||||||
public bool LockSpawn = false;
|
public bool LockSpawn = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the initial spawn point index that is used to override the "Home" location for client (lobby slot) players.
|
||||||
|
/// Map players always ignore this and use HomeLocation directly.
|
||||||
|
/// </summary>
|
||||||
public int Spawn = 0;
|
public int Spawn = 0;
|
||||||
|
|
||||||
public bool LockTeam = false;
|
public bool LockTeam = false;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ namespace OpenRA
|
|||||||
public readonly bool NonCombatant = false;
|
public readonly bool NonCombatant = false;
|
||||||
public readonly bool Playable = true;
|
public readonly bool Playable = true;
|
||||||
public readonly int ClientIndex;
|
public readonly int ClientIndex;
|
||||||
|
public readonly CPos HomeLocation;
|
||||||
public readonly PlayerReference PlayerReference;
|
public readonly PlayerReference PlayerReference;
|
||||||
public readonly bool IsBot;
|
public readonly bool IsBot;
|
||||||
public readonly string BotType;
|
public readonly string BotType;
|
||||||
@@ -65,8 +66,13 @@ namespace OpenRA
|
|||||||
/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
|
/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
|
||||||
public readonly FactionInfo DisplayFaction;
|
public readonly FactionInfo DisplayFaction;
|
||||||
|
|
||||||
|
/// <summary>The spawn point index that was assigned for client-based players.</summary>
|
||||||
|
public readonly int SpawnPoint;
|
||||||
|
|
||||||
|
/// <summary>The spawn point index (including 0 for Random) that was selected in the lobby for client-based players.</summary>
|
||||||
|
public readonly int DisplaySpawnPoint;
|
||||||
|
|
||||||
public WinState WinState = WinState.Undefined;
|
public WinState WinState = WinState.Undefined;
|
||||||
public int SpawnPoint;
|
|
||||||
public bool HasObjectives = false;
|
public bool HasObjectives = false;
|
||||||
public bool Spectating;
|
public bool Spectating;
|
||||||
|
|
||||||
@@ -153,6 +159,11 @@ namespace OpenRA
|
|||||||
BotType = client.Bot;
|
BotType = client.Bot;
|
||||||
Faction = ChooseFaction(world, client.Faction, !pr.LockFaction);
|
Faction = ChooseFaction(world, client.Faction, !pr.LockFaction);
|
||||||
DisplayFaction = ChooseDisplayFaction(world, client.Faction);
|
DisplayFaction = ChooseDisplayFaction(world, client.Faction);
|
||||||
|
|
||||||
|
var assignSpawnPoints = world.WorldActor.TraitOrDefault<IAssignSpawnPoints>();
|
||||||
|
HomeLocation = assignSpawnPoints?.AssignHomeLocation(world, client) ?? pr.HomeLocation;
|
||||||
|
SpawnPoint = assignSpawnPoints?.SpawnPointForPlayer(this) ?? client.SpawnPoint;
|
||||||
|
DisplaySpawnPoint = client.SpawnPoint;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -166,6 +177,8 @@ namespace OpenRA
|
|||||||
BotType = pr.Bot;
|
BotType = pr.Bot;
|
||||||
Faction = ChooseFaction(world, pr.Faction, false);
|
Faction = ChooseFaction(world, pr.Faction, false);
|
||||||
DisplayFaction = ChooseDisplayFaction(world, pr.Faction);
|
DisplayFaction = ChooseDisplayFaction(world, pr.Faction);
|
||||||
|
HomeLocation = pr.HomeLocation;
|
||||||
|
SpawnPoint = DisplaySpawnPoint = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Spectating)
|
if (!Spectating)
|
||||||
|
|||||||
@@ -373,6 +373,13 @@ namespace OpenRA.Traits
|
|||||||
void CreateServerPlayers(MapPreview map, Session lobbyInfo, List<GameInformation.Player> players);
|
void CreateServerPlayers(MapPreview map, Session lobbyInfo, List<GameInformation.Player> players);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequireExplicitImplementation]
|
||||||
|
public interface IAssignSpawnPoints
|
||||||
|
{
|
||||||
|
CPos AssignHomeLocation(World world, Session.Client client);
|
||||||
|
int SpawnPointForPlayer(Player player);
|
||||||
|
}
|
||||||
|
|
||||||
public interface IBotInfo : ITraitInfoInterface
|
public interface IBotInfo : ITraitInfoInterface
|
||||||
{
|
{
|
||||||
string Type { get; }
|
string Type { get; }
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ namespace OpenRA
|
|||||||
using (new PerfTimer(iwl.GetType().Name + ".WorldLoaded"))
|
using (new PerfTimer(iwl.GetType().Name + ".WorldLoaded"))
|
||||||
iwl.WorldLoaded(this, wr);
|
iwl.WorldLoaded(this, wr);
|
||||||
|
|
||||||
|
var assignSpawnLocations = WorldActor.TraitOrDefault<IAssignSpawnPoints>();
|
||||||
gameInfo.StartTimeUtc = DateTime.UtcNow;
|
gameInfo.StartTimeUtc = DateTime.UtcNow;
|
||||||
foreach (var player in Players)
|
foreach (var player in Players)
|
||||||
gameInfo.AddPlayer(player, OrderManager.LobbyInfo);
|
gameInfo.AddPlayer(player, OrderManager.LobbyInfo);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.Scripting
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var c = Player.World.LobbyInfo.Clients.FirstOrDefault(i => i.Index == Player.ClientIndex);
|
var c = Player.World.LobbyInfo.Clients.FirstOrDefault(i => i.Index == Player.ClientIndex);
|
||||||
return c != null ? c.Team : 0;
|
return c?.Team ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,19 +51,17 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
var owner = self.Owner;
|
var owner = self.Owner;
|
||||||
var map = owner.World.Map;
|
var map = owner.World.Map;
|
||||||
var aircraftInfo = self.World.Map.Rules.Actors[info.ActorType].TraitInfo<AircraftInfo>();
|
var aircraftInfo = self.World.Map.Rules.Actors[info.ActorType].TraitInfo<AircraftInfo>();
|
||||||
var mpStart = owner.World.WorldActor.TraitOrDefault<MPStartLocations>();
|
|
||||||
|
|
||||||
CPos startPos;
|
CPos startPos;
|
||||||
CPos endPos;
|
CPos endPos;
|
||||||
WAngle spawnFacing;
|
WAngle spawnFacing;
|
||||||
|
|
||||||
if (info.BaselineSpawn && mpStart != null)
|
if (info.BaselineSpawn)
|
||||||
{
|
{
|
||||||
var spawn = mpStart.Start[owner];
|
|
||||||
var bounds = map.Bounds;
|
var bounds = map.Bounds;
|
||||||
var center = new MPos(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2).ToCPos(map);
|
var center = new MPos(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2).ToCPos(map);
|
||||||
var spawnVec = spawn - center;
|
var spawnVec = owner.HomeLocation - center;
|
||||||
startPos = spawn + spawnVec * (Exts.ISqrt((bounds.Height * bounds.Height + bounds.Width * bounds.Width) / (4 * spawnVec.LengthSquared)));
|
startPos = owner.HomeLocation + spawnVec * (Exts.ISqrt((bounds.Height * bounds.Height + bounds.Width * bounds.Width) / (4 * spawnVec.LengthSquared)));
|
||||||
endPos = startPos;
|
endPos = startPos;
|
||||||
var spawnDirection = new WVec((self.Location - startPos).X, (self.Location - startPos).Y, 0);
|
var spawnDirection = new WVec((self.Location - startPos).X, (self.Location - startPos).Y, 0);
|
||||||
spawnFacing = spawnDirection.Yaw;
|
spawnFacing = spawnDirection.Yaw;
|
||||||
|
|||||||
@@ -9,11 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Network;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.Traits
|
namespace OpenRA.Mods.Common.Traits
|
||||||
@@ -58,13 +57,13 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MPStartLocations : IWorldLoaded, INotifyCreated
|
public class MPStartLocations : IWorldLoaded, INotifyCreated, IAssignSpawnPoints
|
||||||
{
|
{
|
||||||
readonly MPStartLocationsInfo info;
|
readonly MPStartLocationsInfo info;
|
||||||
|
readonly Dictionary<int, Session.Client> occupiedSpawnPoints = new Dictionary<int, Session.Client>();
|
||||||
public readonly Dictionary<Player, CPos> Start = new Dictionary<Player, CPos>();
|
|
||||||
|
|
||||||
bool separateTeamSpawns;
|
bool separateTeamSpawns;
|
||||||
|
CPos[] spawnLocations;
|
||||||
|
List<int> availableSpawnPoints;
|
||||||
|
|
||||||
public MPStartLocations(MPStartLocationsInfo info)
|
public MPStartLocations(MPStartLocationsInfo info)
|
||||||
{
|
{
|
||||||
@@ -75,71 +74,69 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
{
|
{
|
||||||
separateTeamSpawns = self.World.LobbyInfo.GlobalSettings
|
separateTeamSpawns = self.World.LobbyInfo.GlobalSettings
|
||||||
.OptionOrDefault("separateteamspawns", info.SeparateTeamSpawnsCheckboxEnabled);
|
.OptionOrDefault("separateteamspawns", info.SeparateTeamSpawnsCheckboxEnabled);
|
||||||
|
|
||||||
|
var spawns = new List<CPos>();
|
||||||
|
foreach (var n in self.World.Map.ActorDefinitions)
|
||||||
|
if (n.Value.Value == "mpspawn")
|
||||||
|
spawns.Add(new ActorReference(n.Key, n.Value.ToDictionary()).GetValue<LocationInit, CPos>());
|
||||||
|
|
||||||
|
spawnLocations = spawns.ToArray();
|
||||||
|
|
||||||
|
// Initialize the list of unoccupied spawn points for AssignSpawnLocations to pick from
|
||||||
|
availableSpawnPoints = Enumerable.Range(1, spawnLocations.Length).ToList();
|
||||||
|
foreach (var kv in self.World.LobbyInfo.Slots)
|
||||||
|
{
|
||||||
|
var client = self.World.LobbyInfo.ClientInSlot(kv.Key);
|
||||||
|
if (client == null || client.SpawnPoint == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
availableSpawnPoints.Remove(client.SpawnPoint);
|
||||||
|
occupiedSpawnPoints.Add(client.SpawnPoint, client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WorldLoaded(World world, WorldRenderer wr)
|
CPos IAssignSpawnPoints.AssignHomeLocation(World world, Session.Client client)
|
||||||
{
|
{
|
||||||
var spawns = world.Actors.Where(a => a.Info.Name == "mpspawn")
|
if (client.SpawnPoint > 0 && client.SpawnPoint <= spawnLocations.Length)
|
||||||
.Select(a => a.Location)
|
return spawnLocations[client.SpawnPoint - 1];
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != null)
|
var spawnPoint = occupiedSpawnPoints.Count == 0 || !separateTeamSpawns
|
||||||
.Select(c => spawns[c.SpawnPoint - 1]).ToList();
|
? availableSpawnPoints.Random(world.SharedRandom)
|
||||||
var available = spawns.Except(taken).ToList();
|
: availableSpawnPoints // pick the most distant spawnpoint from everyone else
|
||||||
|
.Select(s => (Cell: spawnLocations[s - 1], Index: s))
|
||||||
|
.MaxBy(s => occupiedSpawnPoints.Sum(kv => (spawnLocations[kv.Key - 1] - s.Cell).LengthSquared)).Index;
|
||||||
|
|
||||||
// Set spawn
|
availableSpawnPoints.Remove(spawnPoint);
|
||||||
foreach (var kv in world.LobbyInfo.Slots)
|
occupiedSpawnPoints.Add(spawnPoint, client);
|
||||||
|
return spawnLocations[spawnPoint - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
int IAssignSpawnPoints.SpawnPointForPlayer(Player player)
|
||||||
|
{
|
||||||
|
foreach (var kv in occupiedSpawnPoints)
|
||||||
|
if (kv.Value.Index == player.ClientIndex)
|
||||||
|
return kv.Key;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr)
|
||||||
|
{
|
||||||
|
foreach (var p in world.Players)
|
||||||
{
|
{
|
||||||
var player = FindPlayerInSlot(world, kv.Key);
|
if (!p.Playable)
|
||||||
if (player == null) continue;
|
continue;
|
||||||
|
|
||||||
var client = world.LobbyInfo.ClientInSlot(kv.Key);
|
if (p == world.LocalPlayer)
|
||||||
var spid = (client == null || client.SpawnPoint == 0)
|
wr.Viewport.Center(world.Map.CenterOfCell(p.HomeLocation));
|
||||||
? ChooseSpawnPoint(world, available, taken)
|
|
||||||
: spawns[client.SpawnPoint - 1];
|
|
||||||
|
|
||||||
Start.Add(player, spid);
|
var cells = Shroud.ProjectedCellsInRange(world.Map, p.HomeLocation, info.InitialExploreRange)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
player.SpawnPoint = (client == null || client.SpawnPoint == 0)
|
|
||||||
? spawns.IndexOf(spid) + 1
|
|
||||||
: client.SpawnPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explore allied shroud
|
|
||||||
var map = world.Map;
|
|
||||||
foreach (var p in Start.Keys)
|
|
||||||
{
|
|
||||||
var cells = Shroud.ProjectedCellsInRange(map, Start[p], info.InitialExploreRange);
|
|
||||||
foreach (var q in world.Players)
|
foreach (var q in world.Players)
|
||||||
if (p.IsAlliedWith(q))
|
if (p.IsAlliedWith(q))
|
||||||
q.Shroud.ExploreProjectedCells(world, cells);
|
q.Shroud.ExploreProjectedCells(world, cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set viewport
|
|
||||||
if (world.LocalPlayer != null && Start.ContainsKey(world.LocalPlayer))
|
|
||||||
wr.Viewport.Center(map.CenterOfCell(Start[world.LocalPlayer]));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Player FindPlayerInSlot(World world, string pr)
|
|
||||||
{
|
|
||||||
return world.Players.FirstOrDefault(p => p.PlayerReference.Name == pr);
|
|
||||||
}
|
|
||||||
|
|
||||||
CPos ChooseSpawnPoint(World world, List<CPos> available, List<CPos> taken)
|
|
||||||
{
|
|
||||||
if (available.Count == 0)
|
|
||||||
throw new InvalidOperationException("No free spawnpoint.");
|
|
||||||
|
|
||||||
var n = taken.Count == 0 || !separateTeamSpawns
|
|
||||||
? world.SharedRandom.Next(available.Count)
|
|
||||||
: available // pick the most distant spawnpoint from everyone else
|
|
||||||
.Select((k, i) => (Cell: k, Index: i))
|
|
||||||
.MaxBy(a => taken.Sum(t => (t - a.Cell).LengthSquared)).Index;
|
|
||||||
|
|
||||||
var sp = available[n];
|
|
||||||
available.RemoveAt(n);
|
|
||||||
taken.Add(sp);
|
|
||||||
return sp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ using OpenRA.Traits;
|
|||||||
namespace OpenRA.Mods.Common.Traits
|
namespace OpenRA.Mods.Common.Traits
|
||||||
{
|
{
|
||||||
[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.")]
|
[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 SpawnMPUnitsInfo : TraitInfo, Requires<MPStartLocationsInfo>, Requires<MPStartUnitsInfo>, ILobbyOptions
|
public class SpawnMPUnitsInfo : TraitInfo, Requires<MPStartUnitsInfo>, ILobbyOptions
|
||||||
{
|
{
|
||||||
public readonly string StartingUnitsClass = "none";
|
public readonly string StartingUnitsClass = "none";
|
||||||
|
|
||||||
@@ -67,11 +67,12 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
public void WorldLoaded(World world, WorldRenderer wr)
|
public void WorldLoaded(World world, WorldRenderer wr)
|
||||||
{
|
{
|
||||||
foreach (var s in world.WorldActor.Trait<MPStartLocations>().Start)
|
foreach (var p in world.Players)
|
||||||
SpawnUnitsForPlayer(world, s.Key, s.Value);
|
if (p.Playable)
|
||||||
|
SpawnUnitsForPlayer(world, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpawnUnitsForPlayer(World w, Player p, CPos sp)
|
void SpawnUnitsForPlayer(World w, Player p)
|
||||||
{
|
{
|
||||||
var spawnClass = p.PlayerReference.StartingUnitsClass ?? w.LobbyInfo.GlobalSettings
|
var spawnClass = p.PlayerReference.StartingUnitsClass ?? w.LobbyInfo.GlobalSettings
|
||||||
.OptionOrDefault("startingunits", info.StartingUnitsClass);
|
.OptionOrDefault("startingunits", info.StartingUnitsClass);
|
||||||
@@ -88,7 +89,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
var facing = unitGroup.BaseActorFacing.HasValue ? unitGroup.BaseActorFacing.Value : new WAngle(w.SharedRandom.Next(1024));
|
var facing = unitGroup.BaseActorFacing.HasValue ? unitGroup.BaseActorFacing.Value : new WAngle(w.SharedRandom.Next(1024));
|
||||||
w.CreateActor(unitGroup.BaseActor.ToLowerInvariant(), new TypeDictionary
|
w.CreateActor(unitGroup.BaseActor.ToLowerInvariant(), new TypeDictionary
|
||||||
{
|
{
|
||||||
new LocationInit(sp + unitGroup.BaseActorOffset),
|
new LocationInit(p.HomeLocation + unitGroup.BaseActorOffset),
|
||||||
new OwnerInit(p),
|
new OwnerInit(p),
|
||||||
new SkipMakeAnimsInit(),
|
new SkipMakeAnimsInit(),
|
||||||
new FacingInit(facing),
|
new FacingInit(facing),
|
||||||
@@ -98,7 +99,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (!unitGroup.SupportActors.Any())
|
if (!unitGroup.SupportActors.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var supportSpawnCells = w.Map.FindTilesInAnnulus(sp, unitGroup.InnerSupportRadius + 1, unitGroup.OuterSupportRadius);
|
var supportSpawnCells = w.Map.FindTilesInAnnulus(p.HomeLocation, unitGroup.InnerSupportRadius + 1, unitGroup.OuterSupportRadius);
|
||||||
|
|
||||||
foreach (var s in unitGroup.SupportActors)
|
foreach (var s in unitGroup.SupportActors)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user