(965c31410) v0.10.4.0
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -8,22 +10,62 @@ namespace Barotrauma
|
||||
{
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
const int MaxMoney = int.MaxValue / 2; //about 1 billion
|
||||
const int InitialMoney = 2500;
|
||||
public const int MaxInitialSubmarinePrice = 6000;
|
||||
|
||||
//duration of the cinematic + credits at the end of the campaign
|
||||
protected const float EndCinematicDuration = 240.0f;
|
||||
//duration of the camera transition at the end of a round
|
||||
protected const float EndTransitionDuration = 5.0f;
|
||||
//there can be no events before this time has passed during the 1st campaign round
|
||||
const float FirstRoundEventDelay = 30.0f;
|
||||
|
||||
public enum InteractionType { None, Talk, Map, Crew, Store, Repair, Upgrade, PurchaseSub }
|
||||
|
||||
public readonly CargoManager CargoManager;
|
||||
public UpgradeManager UpgradeManager;
|
||||
|
||||
public List<Faction> Factions;
|
||||
|
||||
public CampaignMetadata CampaignMetadata;
|
||||
|
||||
public enum TransitionType
|
||||
{
|
||||
None,
|
||||
//leaving a location level
|
||||
LeaveLocation,
|
||||
//progressing to next location level
|
||||
ProgressToNextLocation,
|
||||
//returning to previous location level
|
||||
ReturnToPreviousLocation,
|
||||
//returning to previous location (one with no level/outpost, the player is taken to the map screen and must choose their next destination)
|
||||
ReturnToPreviousEmptyLocation,
|
||||
//progressing to an empty location (one with no level/outpost, the player is taken to the map screen and must choose their next destination)
|
||||
ProgressToNextEmptyLocation,
|
||||
//end of campaign (reached end location)
|
||||
End
|
||||
}
|
||||
|
||||
public bool IsFirstRound { get; protected set; } = true;
|
||||
|
||||
public bool DisableEvents
|
||||
{
|
||||
get { return IsFirstRound && Timing.TotalTime < GameMain.GameSession.RoundStartTime + FirstRoundEventDelay; }
|
||||
}
|
||||
|
||||
public bool CheatsEnabled;
|
||||
|
||||
const int InitialMoney = 8700;
|
||||
public const int HullRepairCost = 500, ItemRepairCost = 500, ShuttleReplaceCost = 1000;
|
||||
|
||||
protected bool watchmenSpawned;
|
||||
protected Character startWatchman, endWatchman;
|
||||
protected bool wasDocked;
|
||||
|
||||
//key = dialog flag, double = Timing.TotalTime when the line was last said
|
||||
private Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
private readonly Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
|
||||
public bool PurchasedHullRepairs, PurchasedLostShuttles, PurchasedItemRepairs;
|
||||
|
||||
public bool InitialSuppliesSpawned;
|
||||
public SubmarineInfo PendingSubmarineSwitch;
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
@@ -43,28 +85,47 @@ namespace Barotrauma
|
||||
public int Money
|
||||
{
|
||||
get { return money; }
|
||||
set { money = Math.Max(value, 0); }
|
||||
set { money = MathHelper.Clamp(value, 0, MaxMoney); }
|
||||
}
|
||||
|
||||
public CampaignMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
public LevelData NextLevel
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
protected CampaignMode(GameModePreset preset)
|
||||
: base(preset)
|
||||
{
|
||||
Money = InitialMoney;
|
||||
CargoManager = new CargoManager(this);
|
||||
CargoManager = new CargoManager(this);
|
||||
}
|
||||
|
||||
public void GenerateMap(string seed)
|
||||
/// <summary>
|
||||
/// The location that's displayed as the "current one" in the map screen. Normally the current outpost or the location at the start of the level,
|
||||
/// but when selecting the next destination at the end of the level at an uninhabited location we use the location at the end
|
||||
/// </summary>
|
||||
public Location CurrentDisplayLocation
|
||||
{
|
||||
map = new Map(seed);
|
||||
get
|
||||
{
|
||||
if (Level.Loaded != null && !Level.Loaded.Generating &&
|
||||
Level.Loaded.Type == LevelData.LevelType.LocationConnection &&
|
||||
GetAvailableTransition(out _, out _) == TransitionType.ProgressToNextEmptyLocation)
|
||||
{
|
||||
return Level.Loaded.EndLocation;
|
||||
}
|
||||
return Level.Loaded?.StartLocation ?? Map.CurrentLocation;
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
|
||||
public List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
|
||||
{
|
||||
//leave subs behind if they're not docked to the leaving sub and not at the same exit
|
||||
return Submarine.Loaded.FindAll(s =>
|
||||
s != leavingSub &&
|
||||
!leavingSub.DockedTo.Contains(s) &&
|
||||
s.Info.Type == SubmarineInfo.SubmarineType.Player &&
|
||||
s.Info.Type == SubmarineType.Player &&
|
||||
(s.AtEndPosition != leavingSub.AtEndPosition || s.AtStartPosition != leavingSub.AtStartPosition));
|
||||
}
|
||||
|
||||
@@ -72,20 +133,18 @@ namespace Barotrauma
|
||||
{
|
||||
base.Start();
|
||||
dialogLastSpoken.Clear();
|
||||
watchmenSpawned = false;
|
||||
startWatchman = null;
|
||||
endWatchman = null;
|
||||
characterOutOfBoundsTimer.Clear();
|
||||
|
||||
if (PurchasedHullRepairs)
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine))
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -wall.Prefab.Health);
|
||||
wall.AddDamage(i, -wall.MaxHealth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,125 +154,539 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || item.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
if (item.Submarine == null || item.Submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine))
|
||||
{
|
||||
if (item.GetComponent<Items.Components.Repairable>() != null)
|
||||
{
|
||||
item.Condition = item.Prefab.Health;
|
||||
item.Condition = item.MaxCondition;
|
||||
}
|
||||
}
|
||||
}
|
||||
PurchasedItemRepairs = false;
|
||||
}
|
||||
PurchasedLostShuttles = false;
|
||||
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
|
||||
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
public void InitCampaignData()
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (!IsRunning) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
if (!watchmenSpawned)
|
||||
Factions = new List<Faction>();
|
||||
foreach (FactionPrefab factionPrefab in FactionPrefab.Prefabs)
|
||||
{
|
||||
if (Level.Loaded.StartOutpost != null) { startWatchman = SpawnWatchman(Level.Loaded.StartOutpost); }
|
||||
if (Level.Loaded.EndOutpost != null) { endWatchman = SpawnWatchman(Level.Loaded.EndOutpost); }
|
||||
watchmenSpawned = true;
|
||||
#if SERVER
|
||||
(this as MultiPlayerCampaign).LastUpdateID++;
|
||||
Factions.Add(new Faction(CampaignMetadata, factionPrefab));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadNewLevel()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CoroutineManager.IsCoroutineRunning("LevelTransition"))
|
||||
{
|
||||
DebugConsole.ThrowError("Level transition already running.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Level.Loaded == null || Submarine.MainSub == null)
|
||||
{
|
||||
LoadInitialLevel();
|
||||
return;
|
||||
}
|
||||
|
||||
var availableTransition = GetAvailableTransition(out LevelData nextLevel, out Submarine leavingSub);
|
||||
|
||||
if (availableTransition == TransitionType.None)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to load a new campaign level. No available level transitions " +
|
||||
"(current location: " + (map.CurrentLocation?.Name ?? "null") + ", " +
|
||||
"selected location: " + (map.SelectedLocation?.Name ?? "null") + ", " +
|
||||
"leaving sub: " + (leavingSub?.Info?.Name ?? "null") + ", " +
|
||||
"at start: " + (leavingSub?.AtStartPosition.ToString() ?? "null") + ", " +
|
||||
"at end: " + (leavingSub?.AtEndPosition.ToString() ?? "null") + ")\n" +
|
||||
Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
if (nextLevel == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to load a new campaign level. No available level transitions " +
|
||||
"(transition type: " + availableTransition + ", " +
|
||||
"current location: " + (map.CurrentLocation?.Name ?? "null") + ", " +
|
||||
"selected location: " + (map.SelectedLocation?.Name ?? "null") + ", " +
|
||||
"leaving sub: " + (leavingSub?.Info?.Name ?? "null") + ", " +
|
||||
"at start: " + (leavingSub?.AtStartPosition.ToString() ?? "null") + ", " +
|
||||
"at end: " + (leavingSub?.AtEndPosition.ToString() ?? "null") + ")\n" +
|
||||
Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
#if CLIENT
|
||||
ShowCampaignUI = ForceMapUI = false;
|
||||
#endif
|
||||
DebugConsole.NewMessage("Transitioning to " + (nextLevel?.Seed ?? "null") +
|
||||
" (current location: " + (map.CurrentLocation?.Name ?? "null") + ", " +
|
||||
"selected location: " + (map.SelectedLocation?.Name ?? "null") + ", " +
|
||||
"leaving sub: " + (leavingSub?.Info?.Name ?? "null") + ", " +
|
||||
"at start: " + (leavingSub?.AtStartPosition.ToString() ?? "null") + ", " +
|
||||
"at end: " + (leavingSub?.AtEndPosition.ToString() ?? "null") + ", " +
|
||||
"transition type: " + availableTransition + ")");
|
||||
|
||||
IsFirstRound = false;
|
||||
bool mirror = map.SelectedConnection != null && map.CurrentLocation != map.SelectedConnection.Locations[0];
|
||||
CoroutineManager.StartCoroutine(DoLevelTransition(availableTransition, nextLevel, leavingSub, mirror), "LevelTransition");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the first level and start the round after loading a save file
|
||||
/// </summary>
|
||||
protected abstract void LoadInitialLevel();
|
||||
|
||||
protected abstract IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null);
|
||||
|
||||
/// <summary>
|
||||
/// Which type of transition between levels is currently possible (if any)
|
||||
/// </summary>
|
||||
public TransitionType GetAvailableTransition(out LevelData nextLevel, out Submarine leavingSub)
|
||||
{
|
||||
if (Level.Loaded == null || Submarine.MainSub == null)
|
||||
{
|
||||
nextLevel = null;
|
||||
leavingSub = null;
|
||||
return TransitionType.None;
|
||||
}
|
||||
|
||||
leavingSub = GetLeavingSub();
|
||||
if (leavingSub == null)
|
||||
{
|
||||
nextLevel = null;
|
||||
return TransitionType.None;
|
||||
}
|
||||
|
||||
//currently travelling from location to another
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
||||
{
|
||||
if (leavingSub.AtEndPosition)
|
||||
{
|
||||
if (Map.EndLocation != null && map.SelectedLocation == Map.EndLocation)
|
||||
{
|
||||
nextLevel = map.StartLocation.LevelData;
|
||||
return TransitionType.End;
|
||||
}
|
||||
if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.Type.HasOutpost && Level.Loaded.EndOutpost != null)
|
||||
{
|
||||
nextLevel = Level.Loaded.EndLocation.LevelData;
|
||||
return TransitionType.ProgressToNextLocation;
|
||||
}
|
||||
else if (map.SelectedConnection != null)
|
||||
{
|
||||
nextLevel = Level.Loaded.LevelData != map.SelectedConnection?.LevelData || (map.SelectedConnection.Locations[0] == Level.Loaded.EndLocation == Level.Loaded.Mirrored) ?
|
||||
map.SelectedConnection.LevelData : null;
|
||||
return TransitionType.ProgressToNextEmptyLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextLevel = null;
|
||||
return TransitionType.ProgressToNextEmptyLocation;
|
||||
}
|
||||
}
|
||||
else if (leavingSub.AtStartPosition)
|
||||
{
|
||||
if (map.CurrentLocation.Type.HasOutpost && Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
nextLevel = map.CurrentLocation.LevelData;
|
||||
return TransitionType.ReturnToPreviousLocation;
|
||||
}
|
||||
else if (map.SelectedLocation != null && map.SelectedLocation != map.CurrentLocation && !map.CurrentLocation.Type.HasOutpost &&
|
||||
(Level.Loaded.LevelData != map.SelectedConnection.LevelData))
|
||||
{
|
||||
nextLevel = map.SelectedConnection.LevelData;
|
||||
return TransitionType.LeaveLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextLevel = map.SelectedConnection?.LevelData;
|
||||
return TransitionType.ReturnToPreviousEmptyLocation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nextLevel = null;
|
||||
return TransitionType.None;
|
||||
}
|
||||
}
|
||||
else if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
nextLevel = map.SelectedLocation == null ? null : map.SelectedConnection?.LevelData;
|
||||
return nextLevel == null ? TransitionType.None : TransitionType.LeaveLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which submarine is at a position where it can leave the level and enter another one (if any).
|
||||
/// </summary>
|
||||
private Submarine GetLeavingSub()
|
||||
{
|
||||
//in single player, only the sub the controlled character is inside can transition between levels
|
||||
//in multiplayer, if there's subs at both ends of the level, only the one with more players inside can transition
|
||||
//TODO: ignore players who don't have the permission to trigger a transition between levels?
|
||||
var leavingPlayers = Character.CharacterList.Where(c => !c.IsDead && (c == Character.Controlled || c.IsRemotePlayer));
|
||||
|
||||
//allow leaving if inside an outpost, and the submarine is either docked to it or close enough
|
||||
Submarine leavingSubAtStart = GetLeavingSubAtStart(leavingPlayers);
|
||||
Submarine leavingSubAtEnd = GetLeavingSubAtEnd(leavingPlayers);
|
||||
|
||||
if (Level.IsLoadedOutpost)
|
||||
{
|
||||
leavingSubAtStart ??= Submarine.MainSub;
|
||||
leavingSubAtEnd ??= Submarine.MainSub;
|
||||
}
|
||||
int playersInSubAtStart = leavingSubAtStart == null ? 0 :
|
||||
leavingPlayers.Count(c => c.Submarine == leavingSubAtStart || leavingSubAtStart.DockedTo.Contains(c.Submarine) || (Level.Loaded.StartOutpost != null && c.Submarine == Level.Loaded.StartOutpost));
|
||||
int playersInSubAtEnd = leavingSubAtEnd == null ? 0 :
|
||||
leavingPlayers.Count(c => c.Submarine == leavingSubAtEnd || leavingSubAtEnd.DockedTo.Contains(c.Submarine) || (Level.Loaded.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost));
|
||||
|
||||
if (playersInSubAtStart == 0 && playersInSubAtEnd == 0) { return null; }
|
||||
|
||||
return playersInSubAtStart > playersInSubAtEnd ? leavingSubAtStart : leavingSubAtEnd;
|
||||
|
||||
static Submarine GetLeavingSubAtStart(IEnumerable<Character> leavingPlayers)
|
||||
{
|
||||
if (Level.Loaded.StartOutpost == null)
|
||||
{
|
||||
#if SERVER
|
||||
if (string.IsNullOrEmpty(character.OwnerClientEndPoint)) { continue; }
|
||||
#else
|
||||
if (!CrewManager.GetCharacters().Contains(character)) { continue; }
|
||||
#endif
|
||||
if (character.Submarine == Level.Loaded.StartOutpost &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, startWatchman.WorldPosition) < 500.0f * 500.0f)
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartPosition, ignoreOutposts: true);
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if there's a sub docked to the outpost, we can leave the level
|
||||
if (Level.Loaded.StartOutpost.DockedTo.Any())
|
||||
{
|
||||
CreateDialog(new List<Character> { startWatchman }, "EnterStartOutpost", 5 * 60.0f);
|
||||
var dockedSub = Level.Loaded.StartOutpost.DockedTo.FirstOrDefault();
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
else if (character.Submarine == Level.Loaded.EndOutpost &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, endWatchman.WorldPosition) < 500.0f * 500.0f)
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection && !leavingPlayers.Any(s => s.Submarine == Level.Loaded.StartOutpost)) { return null; }
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartOutpost.WorldPosition, ignoreOutposts: true);
|
||||
if (closestSub == null || !closestSub.AtStartPosition) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
}
|
||||
|
||||
static Submarine GetLeavingSubAtEnd(IEnumerable<Character> leavingPlayers)
|
||||
{
|
||||
//no "end" in outpost levels
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost) { return null; }
|
||||
|
||||
if (Level.Loaded.EndOutpost == null)
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndPosition, ignoreOutposts: true);
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if there's a sub docked to the outpost, we can leave the level
|
||||
if (Level.Loaded.EndOutpost.DockedTo.Any())
|
||||
{
|
||||
CreateDialog(new List<Character> { endWatchman }, "EnterEndOutpost", 5 * 60.0f);
|
||||
var dockedSub = Level.Loaded.EndOutpost.DockedTo.FirstOrDefault();
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection && !leavingPlayers.Any(s => s.Submarine == Level.Loaded.EndOutpost)) { return null; }
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndOutpost.WorldPosition, ignoreOutposts: true);
|
||||
if (closestSub == null || !closestSub.AtEndPosition) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void CreateDialog(List<Character> speakers, string conversationTag, float minInterval)
|
||||
public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
|
||||
{
|
||||
if (dialogLastSpoken.TryGetValue(conversationTag, out double lastTime))
|
||||
List<Item> takenItems = new List<Item>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (Timing.TotalTime - lastTime < minInterval) { return; }
|
||||
}
|
||||
|
||||
CrewManager.AddConversation(
|
||||
NPCConversation.CreateRandom(speakers, new List<string>() { conversationTag }));
|
||||
dialogLastSpoken[conversationTag] = Timing.TotalTime;
|
||||
}
|
||||
|
||||
private Character SpawnWatchman(Submarine outpost)
|
||||
{
|
||||
WayPoint watchmanSpawnpoint = WayPoint.WayPointList.Find(wp => wp.Submarine == outpost);
|
||||
if (watchmanSpawnpoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to spawn a watchman at the outpost. No spawnpoints found inside the outpost.");
|
||||
return null;
|
||||
}
|
||||
|
||||
string seed = outpost == Level.Loaded.StartOutpost ? map.SelectedLocation.Name : map.CurrentLocation.Name;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
JobPrefab watchmanJob = JobPrefab.Get("watchman");
|
||||
var variant = Rand.Range(0, watchmanJob.Variants, Rand.RandSync.Server);
|
||||
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: watchmanJob, variant: variant);
|
||||
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
||||
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
||||
InitializeWatchman(spawnedCharacter);
|
||||
var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager;
|
||||
if (objectiveManager != null)
|
||||
{
|
||||
var moveOrder = new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false);
|
||||
moveOrder.Completed += () =>
|
||||
if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; }
|
||||
if ((!(item.GetRootInventoryOwner()?.Submarine?.Info?.IsOutpost ?? false)) || item.Submarine == null || !item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
// Turn towards the center of the sub. Doesn't work in all possible cases, but this is the simplest solution for now.
|
||||
spawnedCharacter.AnimController.TargetDir = spawnedCharacter.Submarine.WorldPosition.X > spawnedCharacter.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
};
|
||||
objectiveManager.SetOrder(moveOrder);
|
||||
takenItems.Add(item);
|
||||
}
|
||||
}
|
||||
if (watchmanJob != null)
|
||||
map.CurrentLocation.RegisterTakenItems(takenItems);
|
||||
|
||||
map.CurrentLocation.AddToStock(CargoManager.SoldItems);
|
||||
CargoManager.ClearSoldItemsProjSpecific();
|
||||
map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems);
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
spawnedCharacter.GiveJobItems();
|
||||
CargoManager.ClearItemsInBuyCrate();
|
||||
CargoManager.ClearItemsInSellCrate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
CargoManager.ClearItemsInBuyCrate();
|
||||
}
|
||||
else if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
CargoManager.ClearItemsInSellCrate();
|
||||
}
|
||||
}
|
||||
|
||||
if (Level.Loaded?.StartOutpost != null)
|
||||
{
|
||||
List<Character> killedCharacters = new List<Character>();
|
||||
foreach (Character c in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value))
|
||||
{
|
||||
if (!c.IsDead && !c.Removed) { continue; }
|
||||
killedCharacters.Add(c);
|
||||
}
|
||||
map.CurrentLocation.RegisterKilledCharacters(killedCharacters);
|
||||
Level.Loaded.StartOutpost.Info.OutpostNPCs.Clear();
|
||||
}
|
||||
|
||||
List<Character> deadCharacters = Character.CharacterList.FindAll(c => c.IsDead);
|
||||
foreach (Character c in deadCharacters)
|
||||
{
|
||||
if (c.IsDead)
|
||||
{
|
||||
CrewManager.RemoveCharacterInfo(c.Info);
|
||||
c.DespawnNow();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (CharacterInfo ci in CrewManager.CharacterInfos)
|
||||
{
|
||||
ci?.ResetCurrentOrder();
|
||||
}
|
||||
|
||||
foreach (DockingPort port in DockingPort.List)
|
||||
{
|
||||
if (port.Door != null &
|
||||
port.Item.Submarine.Info.Type == SubmarineType.Player &&
|
||||
port.DockingTarget?.Item?.Submarine != null &&
|
||||
port.DockingTarget.Item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
port.Door.IsOpen = false;
|
||||
}
|
||||
}
|
||||
return spawnedCharacter;
|
||||
}
|
||||
|
||||
protected void InitializeWatchman(Character character)
|
||||
|
||||
public void EndCampaign()
|
||||
{
|
||||
foreach (LocationConnection connection in Map.Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f);
|
||||
connection.LevelData.Difficulty = connection.Difficulty;
|
||||
}
|
||||
foreach (Location location in Map.Locations)
|
||||
{
|
||||
location.CreateStore(force: true);
|
||||
location.ClearMissions();
|
||||
}
|
||||
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
|
||||
Map.SelectLocation(-1);
|
||||
EndCampaignProjSpecific();
|
||||
}
|
||||
|
||||
protected virtual void EndCampaignProjSpecific() { }
|
||||
|
||||
public bool TryHireCharacter(Location location, CharacterInfo characterInfo)
|
||||
{
|
||||
if (Money < characterInfo.Salary) { return false; }
|
||||
|
||||
characterInfo.IsNewHire = true;
|
||||
|
||||
location.RemoveHireableCharacter(characterInfo);
|
||||
CrewManager.AddCharacterInfo(characterInfo);
|
||||
Money -= characterInfo.Salary;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void NPCInteract(Character npc, Character interactor)
|
||||
{
|
||||
if (!npc.AllowCustomInteract) { return; }
|
||||
NPCInteractProjSpecific(npc, interactor);
|
||||
string coroutineName = "DoCharacterWait." + (npc?.ID ?? Entity.NullEntityID);
|
||||
if (!CoroutineManager.IsCoroutineRunning(coroutineName))
|
||||
{
|
||||
CoroutineManager.StartCoroutine(DoCharacterWait(npc, interactor), coroutineName);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> DoCharacterWait(Character npc, Character interactor)
|
||||
{
|
||||
if (npc == null || interactor == null) { yield return CoroutineStatus.Failure; }
|
||||
|
||||
HumanAIController humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI == null) { yield return CoroutineStatus.Failure; }
|
||||
|
||||
OrderInfo? prevSpeakerOrder = null;
|
||||
if (humanAI.CurrentOrder != null)
|
||||
{
|
||||
prevSpeakerOrder = new OrderInfo(humanAI.CurrentOrder, humanAI.CurrentOrderOption);
|
||||
}
|
||||
var waitOrder = Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase));
|
||||
humanAI.SetOrder(waitOrder, option: string.Empty, orderGiver: null, speak: false);
|
||||
humanAI.FaceTarget(interactor);
|
||||
|
||||
while (!npc.Removed && !interactor.Removed &&
|
||||
Vector2.DistanceSquared(npc.WorldPosition, interactor.WorldPosition) < 300.0f * 300.0f &&
|
||||
humanAI.CurrentOrder == waitOrder &&
|
||||
humanAI.AllowCampaignInteraction() &&
|
||||
!interactor.IsIncapacitated)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
ShowCampaignUI = false;
|
||||
#endif
|
||||
|
||||
if (humanAI.CurrentOrder == waitOrder)
|
||||
{
|
||||
if (prevSpeakerOrder != null)
|
||||
{
|
||||
humanAI.SetOrder(prevSpeakerOrder.Value.Order, prevSpeakerOrder.Value.OrderOption, orderGiver: null, speak: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
humanAI.SetOrder(null, string.Empty, orderGiver: null, speak: false);
|
||||
}
|
||||
}
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
partial void NPCInteractProjSpecific(Character npc, Character interactor);
|
||||
|
||||
public void AssignNPCMenuInteraction(Character character, InteractionType interactionType)
|
||||
{
|
||||
character.CampaignInteractionType = interactionType;
|
||||
if (interactionType == InteractionType.None)
|
||||
{
|
||||
character.SetCustomInteract(null, null);
|
||||
return;
|
||||
}
|
||||
character.CharacterHealth.UseHealthWindow = false;
|
||||
character.CharacterHealth.Unkillable = true;
|
||||
character.CanInventoryBeAccessed = false;
|
||||
character.CanBeDragged = false;
|
||||
character.TeamID = Character.TeamType.FriendlyNPC;
|
||||
//character.CanInventoryBeAccessed = false;
|
||||
character.SetCustomInteract(
|
||||
WatchmanInteract,
|
||||
#if CLIENT
|
||||
hudText: TextManager.GetWithVariable("TalkHint", "[key]", GameMain.Config.KeyBindText(InputType.Select)));
|
||||
NPCInteract,
|
||||
#if CLIENT
|
||||
hudText: TextManager.GetWithVariable("CampaignInteraction." + interactionType, "[key]", GameMain.Config.KeyBindText(InputType.Use)));
|
||||
#else
|
||||
hudText: TextManager.Get("TalkHint"));
|
||||
hudText: TextManager.Get("CampaignInteraction." + interactionType));
|
||||
#endif
|
||||
}
|
||||
|
||||
protected abstract void WatchmanInteract(Character watchman, Character interactor);
|
||||
|
||||
private readonly Dictionary<Character, float> characterOutOfBoundsTimer = new Dictionary<Character, float>();
|
||||
|
||||
protected void KeepCharactersCloseToOutpost(float deltaTime)
|
||||
{
|
||||
const float MaxDist = 3000.0f;
|
||||
const float MinDist = 2500.0f;
|
||||
|
||||
if (!Level.IsLoadedOutpost) { return; }
|
||||
|
||||
Rectangle worldBorders = Submarine.MainSub.GetDockedBorders();
|
||||
worldBorders.Location += Submarine.MainSub.WorldPosition.ToPoint();
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if ((c != Character.Controlled && !c.IsRemotePlayer) ||
|
||||
c.Removed || c.IsDead || c.IsIncapacitated || c.Submarine != null)
|
||||
{
|
||||
if (characterOutOfBoundsTimer.ContainsKey(c))
|
||||
{
|
||||
c.OverrideMovement = null;
|
||||
characterOutOfBoundsTimer.Remove(c);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c.WorldPosition.Y < worldBorders.Y - worldBorders.Height - MaxDist)
|
||||
{
|
||||
if (!characterOutOfBoundsTimer.ContainsKey(c))
|
||||
{
|
||||
characterOutOfBoundsTimer.Add(c, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterOutOfBoundsTimer[c] += deltaTime;
|
||||
}
|
||||
}
|
||||
else if (c.WorldPosition.Y > worldBorders.Y - worldBorders.Height - MinDist)
|
||||
{
|
||||
if (characterOutOfBoundsTimer.ContainsKey(c))
|
||||
{
|
||||
c.OverrideMovement = null;
|
||||
characterOutOfBoundsTimer.Remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Character, float> character in characterOutOfBoundsTimer)
|
||||
{
|
||||
if (character.Value <= 0.0f)
|
||||
{
|
||||
if (IsSinglePlayer)
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(
|
||||
TextManager.Get("RadioAnnouncerName"),
|
||||
TextManager.Get("TooFarFromOutpostWarning"),
|
||||
Networking.ChatMessageType.Default,
|
||||
sender: null);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if SERVER
|
||||
foreach (Networking.Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
|
||||
GameMain.Server.SendDirectChatMessage(Networking.ChatMessage.Create(
|
||||
TextManager.Get("RadioAnnouncerName"),
|
||||
TextManager.Get("TooFarFromOutpostWarning"), Networking.ChatMessageType.Default, null), c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
character.Key.OverrideMovement = Vector2.UnitY * 10.0f;
|
||||
#if CLIENT
|
||||
Character.DisableControls = true;
|
||||
#endif
|
||||
//if the character doesn't get back up in 10 seconds (something blocking the way?), teleport it closer
|
||||
if (character.Value > 10.0f)
|
||||
{
|
||||
Vector2 teleportPos = character.Key.WorldPosition;
|
||||
teleportPos += Vector2.Normalize(Submarine.MainSub.WorldPosition - character.Key.WorldPosition) * 100.0f;
|
||||
character.Key.AnimController.SetPosition(ConvertUnits.ToSimUnits(teleportPos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OutpostNPCAttacked(Character npc, Character attacker, AttackResult attackResult)
|
||||
{
|
||||
if (npc == null || attacker == null || npc.IsDead || npc.TurnedHostileByEvent) { return; }
|
||||
if (npc.TeamID != Character.TeamType.FriendlyNPC) { return; }
|
||||
if (!attacker.IsRemotePlayer && attacker != Character.Controlled) { return; }
|
||||
Location location = Map?.CurrentLocation;
|
||||
if (location != null)
|
||||
{
|
||||
location.Reputation.Value -= attackResult.Damage * Reputation.ReputationLossPerNPCDamage;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Save(XElement element);
|
||||
|
||||
public void LogState()
|
||||
|
||||
+19
-5
@@ -25,6 +25,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private XElement itemData;
|
||||
private XElement healthData;
|
||||
|
||||
partial void InitProjSpecific(Client client);
|
||||
public CharacterCampaignData(Client client)
|
||||
@@ -32,6 +33,8 @@ namespace Barotrauma
|
||||
Name = client.Name;
|
||||
InitProjSpecific(client);
|
||||
|
||||
healthData = new XElement("health");
|
||||
client.Character.CharacterHealth.Save(healthData);
|
||||
if (client.Character.Inventory != null)
|
||||
{
|
||||
itemData = new XElement("inventory");
|
||||
@@ -61,10 +64,24 @@ namespace Barotrauma
|
||||
case "inventory":
|
||||
itemData = subElement;
|
||||
break;
|
||||
case "health":
|
||||
healthData = subElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh(Character character)
|
||||
{
|
||||
healthData = new XElement("health");
|
||||
character.CharacterHealth.Save(healthData);
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
itemData = new XElement("inventory");
|
||||
character.SaveInventory(character.Inventory, itemData);
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement("CharacterCampaignData",
|
||||
@@ -73,11 +90,8 @@ namespace Barotrauma
|
||||
new XAttribute("steamid", SteamID));
|
||||
|
||||
CharacterInfo?.Save(element);
|
||||
|
||||
if (itemData != null)
|
||||
{
|
||||
element.Add(itemData);
|
||||
}
|
||||
if (itemData != null) { element.Add(itemData); }
|
||||
if (healthData != null) { element.Add(healthData); }
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -8,14 +8,10 @@ namespace Barotrauma
|
||||
public static List<GameModePreset> PresetList = new List<GameModePreset>();
|
||||
|
||||
protected DateTime startTime;
|
||||
|
||||
protected bool isRunning;
|
||||
|
||||
|
||||
protected GameModePreset preset;
|
||||
|
||||
private string endMessage;
|
||||
|
||||
protected CrewManager CrewManager
|
||||
|
||||
public CrewManager CrewManager
|
||||
{
|
||||
get { return GameMain.GameSession?.CrewManager; }
|
||||
}
|
||||
@@ -25,11 +21,6 @@ namespace Barotrauma
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get { return isRunning; }
|
||||
}
|
||||
|
||||
public bool IsSinglePlayer
|
||||
{
|
||||
get { return preset.IsSinglePlayer; }
|
||||
@@ -40,9 +31,9 @@ namespace Barotrauma
|
||||
get { return preset.Name; }
|
||||
}
|
||||
|
||||
public string EndMessage
|
||||
public virtual bool Paused
|
||||
{
|
||||
get { return endMessage; }
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public GameModePreset Preset
|
||||
@@ -50,7 +41,7 @@ namespace Barotrauma
|
||||
get { return preset; }
|
||||
}
|
||||
|
||||
public GameMode(GameModePreset preset, object param)
|
||||
public GameMode(GameModePreset preset)
|
||||
{
|
||||
this.preset = preset;
|
||||
}
|
||||
@@ -58,10 +49,6 @@ namespace Barotrauma
|
||||
public virtual void Start()
|
||||
{
|
||||
startTime = DateTime.Now;
|
||||
|
||||
endMessage = "The round has ended!";
|
||||
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
public virtual void ShowStartMessage() { }
|
||||
@@ -69,8 +56,6 @@ namespace Barotrauma
|
||||
public virtual void AddToGUIUpdateList()
|
||||
{
|
||||
#if CLIENT
|
||||
if (!isRunning) return;
|
||||
|
||||
GameMain.GameSession?.CrewManager.AddToGUIUpdateList();
|
||||
#endif
|
||||
}
|
||||
@@ -80,15 +65,10 @@ namespace Barotrauma
|
||||
CrewManager?.Update(deltaTime);
|
||||
}
|
||||
|
||||
public virtual void End(string endMessage = "")
|
||||
public virtual void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
if (endMessage != "" || this.endMessage == null) this.endMessage = endMessage;
|
||||
|
||||
GameMain.GameSession.EndRound(endMessage);
|
||||
}
|
||||
|
||||
|
||||
public virtual void Remove() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,15 @@ namespace Barotrauma
|
||||
{
|
||||
public static List<GameModePreset> List = new List<GameModePreset>();
|
||||
|
||||
public readonly ConstructorInfo Constructor;
|
||||
public static GameModePreset SinglePlayerCampaign;
|
||||
public static GameModePreset MultiPlayerCampaign;
|
||||
public static GameModePreset Tutorial;
|
||||
public static GameModePreset Mission;
|
||||
public static GameModePreset TestMode;
|
||||
public static GameModePreset Sandbox;
|
||||
public static GameModePreset DevSandbox;
|
||||
|
||||
public readonly Type GameModeType;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
@@ -26,7 +34,7 @@ namespace Barotrauma
|
||||
Description = TextManager.Get("GameModeDescription." + identifier, returnNull: true) ?? "";
|
||||
Identifier = identifier;
|
||||
|
||||
Constructor = type.GetConstructor(new Type[] { typeof(GameModePreset), typeof(object) });
|
||||
GameModeType = type;
|
||||
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
Votable = votable;
|
||||
@@ -34,23 +42,17 @@ namespace Barotrauma
|
||||
List.Add(this);
|
||||
}
|
||||
|
||||
public GameMode Instantiate(object param)
|
||||
{
|
||||
object[] lobject = new object[] { this, param };
|
||||
return (GameMode)Constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
#if CLIENT
|
||||
new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true);
|
||||
new GameModePreset("subtest", typeof(SubTestMode), true);
|
||||
new GameModePreset("tutorial", typeof(TutorialMode), true);
|
||||
new GameModePreset("devsandbox", typeof(GameMode), true);
|
||||
Tutorial = new GameModePreset("tutorial", typeof(TutorialMode), true);
|
||||
DevSandbox = new GameModePreset("devsandbox", typeof(GameMode), true);
|
||||
SinglePlayerCampaign = new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true);
|
||||
TestMode = new GameModePreset("testmode", typeof(TestGameMode), true);
|
||||
#endif
|
||||
new GameModePreset("sandbox", typeof(GameMode), false);
|
||||
new GameModePreset("mission", typeof(MissionMode), false);
|
||||
new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
Sandbox = new GameModePreset("sandbox", typeof(GameMode), false);
|
||||
Mission = new GameModePreset("mission", typeof(MissionMode), false);
|
||||
MultiPlayerCampaign = new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
partial class MissionMode : GameMode
|
||||
{
|
||||
private Mission mission;
|
||||
private readonly Mission mission;
|
||||
|
||||
public override Mission Mission
|
||||
{
|
||||
@@ -12,26 +12,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
public MissionMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
public MissionMode(GameModePreset preset, MissionPrefab missionPrefab)
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
if (param is MissionType missionType)
|
||||
{
|
||||
mission = Mission.LoadRandom(locations, GameMain.NetLobbyScreen.LevelSeed, false, missionType);
|
||||
}
|
||||
else if (param is MissionPrefab missionPrefab)
|
||||
{
|
||||
mission = missionPrefab.Instantiate(locations);
|
||||
}
|
||||
else if (param is Mission)
|
||||
{
|
||||
mission = (Mission)param;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.ArgumentException("Unrecognized MissionMode parameter \"" + param + "\"");
|
||||
}
|
||||
mission = missionPrefab.Instantiate(locations);
|
||||
}
|
||||
|
||||
public MissionMode(GameModePreset preset, MissionType missionType, string seed)
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
mission = Mission.LoadRandom(locations, seed, false, missionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
-124
@@ -1,10 +1,8 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -16,7 +14,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastUpdateID < 1) lastUpdateID++;
|
||||
if (GameMain.Server != null && lastUpdateID < 1) { lastUpdateID++; }
|
||||
#endif
|
||||
return lastUpdateID;
|
||||
}
|
||||
@@ -29,141 +27,59 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastSaveID < 1) lastSaveID++;
|
||||
if (GameMain.Server != null && lastSaveID < 1) { lastSaveID++; }
|
||||
#endif
|
||||
return lastSaveID;
|
||||
}
|
||||
set { lastSaveID = value; }
|
||||
set
|
||||
{
|
||||
#if SERVER
|
||||
//trigger a campaign update to notify the clients of the changed save ID
|
||||
lastUpdateID++;
|
||||
#endif
|
||||
lastSaveID = value;
|
||||
}
|
||||
}
|
||||
|
||||
public UInt16 PendingSaveID
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private static byte currentCampaignID;
|
||||
|
||||
public byte CampaignID
|
||||
{
|
||||
get; private set;
|
||||
get; set;
|
||||
}
|
||||
|
||||
public MultiPlayerCampaign(GameModePreset preset, object param) :
|
||||
base(preset, param)
|
||||
private MultiPlayerCampaign() : base(GameModePreset.MultiPlayerCampaign)
|
||||
{
|
||||
currentCampaignID++;
|
||||
CampaignID = currentCampaignID;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
if (GameMain.NetworkMember.IsServer) lastUpdateID++;
|
||||
}
|
||||
|
||||
public override void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
bool success =
|
||||
GameMain.Client.ConnectedClients.Any(c => c.Character != null && !c.Character.IsDead);
|
||||
|
||||
GameMain.GameSession.EndRound("");
|
||||
GameMain.GameSession.CrewManager.EndRound();
|
||||
|
||||
if (success)
|
||||
{
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SERVER
|
||||
lastUpdateID++;
|
||||
|
||||
bool success =
|
||||
GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||
|
||||
success = success || (GameMain.Server.Character != null && !GameMain.Server.Character.IsDead);
|
||||
|
||||
/*if (success)
|
||||
{
|
||||
if (subsToLeaveBehind == null || leavingSub == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Leaving submarine not selected -> selecting the closest one");
|
||||
|
||||
leavingSub = GetLeavingSub();
|
||||
|
||||
subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
}
|
||||
}*/
|
||||
|
||||
GameMain.GameSession.EndRound("");
|
||||
|
||||
//client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has an alive character)
|
||||
characterData.RemoveAll(cd => cd.HasSpawned);
|
||||
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.Character?.Info != null && !c.Character.IsDead)
|
||||
{
|
||||
c.Character.ResetCurrentOrder();
|
||||
c.CharacterInfo = c.Character.Info;
|
||||
characterData.Add(new CharacterCampaignData(c));
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
bool atEndPosition = Submarine.MainSub.AtEndPosition;
|
||||
|
||||
/*if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (atEndPosition)
|
||||
{
|
||||
map.MoveToNextLocation();
|
||||
|
||||
//select a random location to make sure we've got some destination
|
||||
//to head towards even if the host/clients don't select anything
|
||||
map.SelectRandomLocation(true);
|
||||
}
|
||||
map.ProgressWorld();
|
||||
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
}
|
||||
#endif
|
||||
CampaignMetadata = new CampaignMetadata(this);
|
||||
UpgradeManager = new UpgradeManager(this);
|
||||
InitCampaignData();
|
||||
}
|
||||
|
||||
partial void SetDelegates();
|
||||
|
||||
public static MultiPlayerCampaign LoadNew(XElement element)
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(GameModePreset.List.Find(gm => gm.Identifier == "multiplayercampaign"), null);
|
||||
campaign.Load(element);
|
||||
campaign.SetDelegates();
|
||||
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
//only the server generates the map, the clients load it from a save file
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
campaign.map = new Map(campaign, mapSeed);
|
||||
}
|
||||
campaign.InitProjSpecific();
|
||||
return campaign;
|
||||
}
|
||||
|
||||
public static MultiPlayerCampaign LoadNew(XElement element)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
campaign.Load(element);
|
||||
campaign.InitProjSpecific();
|
||||
campaign.IsFirstRound = false;
|
||||
return campaign;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public static string GetCharacterDataSavePath(string savePath)
|
||||
{
|
||||
return Path.Combine(SaveUtil.MultiplayerSaveFolder, Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
|
||||
@@ -174,10 +90,12 @@ namespace Barotrauma
|
||||
return GetCharacterDataSavePath(GameMain.GameSession.SavePath);
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
/// <summary>
|
||||
/// Loads the campaign from an XML element. Creates the map if it hasn't been created yet, otherwise updates the state of the map.
|
||||
/// </summary>
|
||||
private void Load(XElement element)
|
||||
{
|
||||
Money = element.GetAttributeInt("money", 0);
|
||||
InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false);
|
||||
CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
|
||||
if (CheatsEnabled)
|
||||
{
|
||||
@@ -195,6 +113,12 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
List<SubmarineInfo> availableSubs = new List<SubmarineInfo>();
|
||||
List<SubmarineInfo> sourceList = new List<SubmarineInfo>();
|
||||
sourceList.AddRange(SubmarineInfo.SavedSubmarines);
|
||||
#endif
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -203,19 +127,55 @@ namespace Barotrauma
|
||||
if (map == null)
|
||||
{
|
||||
//map not created yet, loading this campaign for the first time
|
||||
map = Map.LoadNew(subElement);
|
||||
map = Map.Load(this, subElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
//map already created, update it
|
||||
//if we're not downloading the initial save file (LastSaveID > 0),
|
||||
//show notifications about location type changes
|
||||
map.Load(subElement, LastSaveID > 0);
|
||||
map.LoadState(subElement, LastSaveID > 0);
|
||||
}
|
||||
break;
|
||||
case "metadata":
|
||||
CampaignMetadata = new CampaignMetadata(this, subElement);
|
||||
break;
|
||||
case "pendingupgrades":
|
||||
UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: false);
|
||||
break;
|
||||
case "bots" when GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer:
|
||||
CrewManager.HasBots = subElement.GetAttributeBool("hasbots", false);
|
||||
CrewManager.AddCharacterElements(subElement);
|
||||
break;
|
||||
case "cargo":
|
||||
CargoManager?.LoadPurchasedItems(subElement);
|
||||
break;
|
||||
#if SERVER
|
||||
case "availablesubs":
|
||||
foreach (XElement availableSub in subElement.Elements())
|
||||
{
|
||||
string subName = availableSub.GetAttributeString("name", "");
|
||||
SubmarineInfo matchingSub = sourceList.Find(s => s.Name == subName);
|
||||
if (matchingSub != null) { availableSubs.Add(matchingSub); }
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
CampaignMetadata ??= new CampaignMetadata(this);
|
||||
UpgradeManager ??= new UpgradeManager(this);
|
||||
|
||||
InitCampaignData();
|
||||
#if SERVER
|
||||
// Fallback if using a save with no available subs assigned, use vanilla submarines
|
||||
if (availableSubs.Count == 0)
|
||||
{
|
||||
GameMain.NetLobbyScreen.CampaignSubmarines.AddRange(sourceList.FindAll(s => s.IsCampaignCompatible && s.IsVanillaSubmarine()));
|
||||
}
|
||||
|
||||
GameMain.NetLobbyScreen.CampaignSubmarines = availableSubs;
|
||||
|
||||
characterData.Clear();
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
|
||||
|
||||
Reference in New Issue
Block a user