#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.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Widgets.Logic; using OpenRA.Network; using OpenRA.Support; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [TraitLocation(SystemActors.World)] [Desc("Allows the map to have working spawnpoints. Also controls the 'Separate Team Spawns' checkbox in the lobby options.")] public class MapStartingLocationsInfo : TraitInfo, ILobbyOptions, IAssignSpawnPointsInfo { public readonly WDist InitialExploreRange = WDist.FromCells(5); [FluentReference] [Desc("Descriptive label for the spawn positions checkbox in the lobby.")] public readonly string SeparateTeamSpawnsCheckboxLabel = "checkbox-separate-team-spawns.label"; [FluentReference] [Desc("Tooltip description for the spawn positions checkbox in the lobby.")] public readonly string SeparateTeamSpawnsCheckboxDescription = "checkbox-separate-team-spawns.description"; [Desc("Default value of the spawn positions checkbox in the lobby.")] public readonly bool SeparateTeamSpawnsCheckboxEnabled = true; [Desc("Prevent the spawn positions state from being changed in the lobby.")] public readonly bool SeparateTeamSpawnsCheckboxLocked = false; [Desc("Whether to display the spawn positions checkbox in the lobby.")] public readonly bool SeparateTeamSpawnsCheckboxVisible = true; [Desc("Display order for the spawn positions checkbox in the lobby.")] public readonly int SeparateTeamSpawnsCheckboxDisplayOrder = 0; public override object Create(ActorInitializer init) { return new MapStartingLocations(this); } IEnumerable ILobbyOptions.LobbyOptions(MapPreview map) { yield return new LobbyBooleanOption( map, "separateteamspawns", SeparateTeamSpawnsCheckboxLabel, SeparateTeamSpawnsCheckboxDescription, SeparateTeamSpawnsCheckboxVisible, SeparateTeamSpawnsCheckboxDisplayOrder, SeparateTeamSpawnsCheckboxEnabled, SeparateTeamSpawnsCheckboxLocked); } sealed class AssignSpawnLocationsState { public CPos[] SpawnLocations; public List AvailableSpawnPoints; public readonly Dictionary OccupiedSpawnPoints = new(); } object IAssignSpawnPointsInfo.InitializeState(MapPreview map, Session lobbyInfo) { var state = new AssignSpawnLocationsState { // Initialize the list of unoccupied spawn points for AssignSpawnLocations to pick from SpawnLocations = map.SpawnPoints, AvailableSpawnPoints = LobbyUtils.AvailableSpawnPoints(map.SpawnPoints.Length, lobbyInfo) }; foreach (var kv in lobbyInfo.Slots) { var client = lobbyInfo.ClientInSlot(kv.Key); if (client == null || client.SpawnPoint == 0) continue; state.AvailableSpawnPoints.Remove(client.SpawnPoint); state.OccupiedSpawnPoints.Add(client.SpawnPoint, client); } return state; } int IAssignSpawnPointsInfo.AssignSpawnPoint(object stateObject, Session lobbyInfo, Session.Client client, MersenneTwister playerRandom) { var state = (AssignSpawnLocationsState)stateObject; var separateTeamSpawns = lobbyInfo.GlobalSettings.OptionOrDefault("separateteamspawns", SeparateTeamSpawnsCheckboxEnabled); if (client.SpawnPoint > 0 && client.SpawnPoint <= state.SpawnLocations.Length) return client.SpawnPoint; var spawnPoint = state.OccupiedSpawnPoints.Count == 0 || !separateTeamSpawns ? state.AvailableSpawnPoints.Random(playerRandom) : state.AvailableSpawnPoints // pick the most distant spawnpoint from everyone else .Select(s => (Cell: state.SpawnLocations[s - 1], Index: s)) .MaxBy(s => state.OccupiedSpawnPoints.Sum(kv => (state.SpawnLocations[kv.Key - 1] - s.Cell).LengthSquared)).Index; state.AvailableSpawnPoints.Remove(spawnPoint); state.OccupiedSpawnPoints.Add(spawnPoint, client); return spawnPoint; } } public class MapStartingLocations : IWorldLoaded, INotifyCreated, IAssignSpawnPoints { readonly MapStartingLocationsInfo info; readonly Dictionary occupiedSpawnPoints = new(); bool separateTeamSpawns; CPos[] spawnLocations; List availableSpawnPoints; public MapStartingLocations(MapStartingLocationsInfo info) { this.info = info; } void INotifyCreated.Created(Actor self) { separateTeamSpawns = self.World.LobbyInfo.GlobalSettings .OptionOrDefault("separateteamspawns", info.SeparateTeamSpawnsCheckboxEnabled); var spawns = new List(); foreach (var n in self.World.Map.ActorDefinitions) if (n.Value.Value == "mpspawn") spawns.Add(new ActorReference(n.Key, n.Value.ToDictionary()).GetValue()); spawnLocations = spawns.ToArray(); // Initialize the list of unoccupied spawn points for AssignSpawnLocations to pick from availableSpawnPoints = LobbyUtils.AvailableSpawnPoints(spawnLocations.Length, self.World.LobbyInfo); 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); } } CPos IAssignSpawnPoints.AssignHomeLocation(World world, Session.Client client, MersenneTwister playerRandom) { if (client.SpawnPoint > 0 && client.SpawnPoint <= spawnLocations.Length) return spawnLocations[client.SpawnPoint - 1]; var spawnPoint = occupiedSpawnPoints.Count == 0 || !separateTeamSpawns ? availableSpawnPoints.Random(playerRandom) : 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; availableSpawnPoints.Remove(spawnPoint); 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) { if (!p.Playable) continue; if (p == world.LocalPlayer) wr.Viewport.Center(world.Map.CenterOfCell(p.HomeLocation)); var cells = Shroud.ProjectedCellsInRange(world.Map, p.HomeLocation, info.InitialExploreRange) .ToList(); foreach (var q in world.Players) if (p.IsAlliedWith(q)) q.Shroud.ExploreProjectedCells(cells); } } } }