Release v0.15.12.0
This commit is contained in:
@@ -306,7 +306,7 @@ namespace Barotrauma
|
||||
case 0:
|
||||
|
||||
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
|
||||
requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireKill.All(c => c.Removed || c.IsDead || (c.LockHands && c.Submarine == Submarine.MainSub)) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.RuinGeneration;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AlienRuinMission : Mission
|
||||
{
|
||||
private readonly string[] targetItemIdentifiers;
|
||||
private readonly string[] targetEnemyIdentifiers;
|
||||
private readonly int minEnemyCount;
|
||||
private readonly HashSet<Entity> existingTargets = new HashSet<Entity>();
|
||||
private readonly HashSet<Character> spawnedTargets = new HashSet<Character>();
|
||||
private readonly HashSet<Entity> allTargets = new HashSet<Entity>();
|
||||
|
||||
private Ruin TargetRuin { get; set; }
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State == 0)
|
||||
{
|
||||
return allTargets.Where(t => (t is Item i && !IsItemDestroyed(i)) || (t is Character c && !IsEnemyDefeated(c))).Select(t => t.WorldPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AlienRuinMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
targetItemIdentifiers = prefab.ConfigElement.GetAttributeStringArray("targetitems", new string[0], convertToLowerInvariant: true);
|
||||
targetEnemyIdentifiers = prefab.ConfigElement.GetAttributeStringArray("targetenemies", new string[0], convertToLowerInvariant: true);
|
||||
minEnemyCount = prefab.ConfigElement.GetAttributeInt("minenemycount", 0);
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
existingTargets.Clear();
|
||||
spawnedTargets.Clear();
|
||||
allTargets.Clear();
|
||||
if (IsClient) { return; }
|
||||
TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.Server);
|
||||
if (TargetRuin == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins");
|
||||
return;
|
||||
}
|
||||
if (targetItemIdentifiers.Length < 1 && targetEnemyIdentifiers.Length < 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition");
|
||||
return;
|
||||
}
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
if (!targetItemIdentifiers.Contains(item.Prefab.Identifier)) { continue; }
|
||||
if (item.Submarine != TargetRuin.Submarine) { continue; }
|
||||
existingTargets.Add(item);
|
||||
allTargets.Add(item);
|
||||
}
|
||||
int existingEnemyCount = 0;
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(character.SpeciesName)) { continue; }
|
||||
if (!targetEnemyIdentifiers.Contains(character.SpeciesName.ToLowerInvariant())) { continue; }
|
||||
if (character.Submarine != TargetRuin.Submarine) { continue; }
|
||||
existingTargets.Add(character);
|
||||
allTargets.Add(character);
|
||||
existingEnemyCount++;
|
||||
}
|
||||
if (existingEnemyCount < minEnemyCount)
|
||||
{
|
||||
var enemyPrefabs = new HashSet<CharacterPrefab>();
|
||||
foreach (string identifier in targetEnemyIdentifiers)
|
||||
{
|
||||
var prefab = CharacterPrefab.FindBySpeciesName(identifier);
|
||||
if (prefab != null)
|
||||
{
|
||||
enemyPrefabs.Add(prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\"");
|
||||
}
|
||||
}
|
||||
if (enemyPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < (minEnemyCount - existingEnemyCount); i++)
|
||||
{
|
||||
var prefab = enemyPrefabs.GetRandom();
|
||||
var spawnPos = TargetRuin.Submarine.GetWaypoints(false).GetRandom(w => w.CurrentHull != null)?.WorldPosition;
|
||||
if (!spawnPos.HasValue)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned");
|
||||
return;
|
||||
}
|
||||
var newEnemy = Character.Create(prefab.Identifier, spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
spawnedTargets.Add(newEnemy);
|
||||
allTargets.Add(newEnemy);
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("********** CLEAR RUIN MISSION INFO **********");
|
||||
DebugConsole.NewMessage($"Existing item targets: {existingTargets.Count - existingEnemyCount}");
|
||||
DebugConsole.NewMessage($"Existing enemy targets: {existingEnemyCount}");
|
||||
DebugConsole.NewMessage($"Spawned enemy targets: {spawnedTargets.Count}");
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (!AllTargetsEliminated()) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllTargetsEliminated()
|
||||
{
|
||||
foreach (var target in allTargets)
|
||||
{
|
||||
if (target is Item targetItem)
|
||||
{
|
||||
if (!IsItemDestroyed(targetItem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target is Character targetEnemy)
|
||||
{
|
||||
if (!IsEnemyDefeated(targetEnemy))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in Alien Ruin mission (\"{Prefab.Identifier}\"): unexpected target of type {target?.GetType()?.ToString()}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsItemDestroyed(Item item) => item == null || item.Removed || item.Condition <= 0.0f;
|
||||
|
||||
private bool IsEnemyDefeated(Character enemy) => enemy == null ||enemy.Removed || enemy.IsDead;
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (State == 2)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
failed = !completed && State > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ namespace Barotrauma
|
||||
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
CoroutineManager.InvokeAfter(() =>
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
|
||||
@@ -140,6 +140,12 @@ namespace Barotrauma
|
||||
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
// If we are not at the location of the mission, skip the calculation of the reward
|
||||
if (GameMain.GameSession?.StartLocation != Locations[0])
|
||||
{
|
||||
return calculatedReward;
|
||||
}
|
||||
|
||||
bool missionsChanged = false;
|
||||
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
||||
{
|
||||
@@ -192,55 +198,14 @@ namespace Barotrauma
|
||||
if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; }
|
||||
}
|
||||
|
||||
private ItemPrefab FindItemPrefab(XElement element)
|
||||
{
|
||||
ItemPrefab itemPrefab;
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in cargo mission \"" + Name + "\" - use item identifiers instead of names to configure the items.");
|
||||
string itemName = element.GetAttributeString("name", "");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemName + "\" not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
}
|
||||
}
|
||||
return itemPrefab;
|
||||
}
|
||||
|
||||
|
||||
private void LoadItemAsChild(XElement element, Item parent)
|
||||
{
|
||||
ItemPrefab itemPrefab = FindItemPrefab(element);
|
||||
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true);
|
||||
if (cargoSpawnPos == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found");
|
||||
return;
|
||||
}
|
||||
Vector2? position = GetCargoSpawnPosition(itemPrefab, out Submarine cargoRoomSub);
|
||||
if (!position.HasValue) { return; }
|
||||
|
||||
var cargoRoom = cargoSpawnPos.CurrentHull;
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 position = new Vector2(
|
||||
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||
|
||||
var item = new Item(itemPrefab, position, cargoRoom.Submarine)
|
||||
var item = new Item(itemPrefab, position.Value, cargoRoomSub)
|
||||
{
|
||||
SpawnedInOutpost = true,
|
||||
AllowStealing = false
|
||||
|
||||
@@ -13,16 +13,5 @@ namespace Barotrauma
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
}
|
||||
#elif SERVER
|
||||
|
||||
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace Barotrauma
|
||||
{
|
||||
partial class MineralMission : Mission
|
||||
{
|
||||
private Dictionary<string, Pair<int, float>> ResourceClusters { get; } = new Dictionary<string, Pair<int, float>>();
|
||||
private Dictionary<string, List<Item>> SpawnedResources { get; } = new Dictionary<string, List<Item>>();
|
||||
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>();
|
||||
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>();
|
||||
private readonly Dictionary<string, (int amount, float rotation)> resourceClusters = new Dictionary<string, (int amount, float rotation)>();
|
||||
private readonly Dictionary<string, List<Item>> spawnedResources = new Dictionary<string, List<Item>>();
|
||||
private readonly Dictionary<string, Item[]> relevantLevelResources = new Dictionary<string, Item[]>();
|
||||
private readonly List<Tuple<string, Vector2>> missionClusterPositions = new List<Tuple<string, Vector2>>();
|
||||
|
||||
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
return MissionClusterPositions
|
||||
.Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1]))
|
||||
return missionClusterPositions
|
||||
.Where(p => spawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(spawnedResources[p.Item1]))
|
||||
.Select(p => p.Item2);
|
||||
}
|
||||
}
|
||||
@@ -33,53 +33,53 @@ namespace Barotrauma
|
||||
{
|
||||
var identifier = c.GetAttributeString("identifier", null);
|
||||
if (string.IsNullOrWhiteSpace(identifier)) { continue; }
|
||||
if (ResourceClusters.ContainsKey(identifier))
|
||||
if (resourceClusters.ContainsKey(identifier))
|
||||
{
|
||||
ResourceClusters[identifier].First++;
|
||||
resourceClusters[identifier] = (resourceClusters[identifier].amount + 1, resourceClusters[identifier].rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResourceClusters.Add(identifier, new Pair<int, float>(1, 0.0f));
|
||||
resourceClusters.Add(identifier, (1, 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
if (SpawnedResources.Any())
|
||||
if (spawnedResources.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})");
|
||||
throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
SpawnedResources.Clear();
|
||||
spawnedResources.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (RelevantLevelResources.Any())
|
||||
if (relevantLevelResources.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})");
|
||||
throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
RelevantLevelResources.Clear();
|
||||
relevantLevelResources.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (MissionClusterPositions.Any())
|
||||
if (missionClusterPositions.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})");
|
||||
throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
MissionClusterPositions.Clear();
|
||||
missionClusterPositions.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
caves.Clear();
|
||||
|
||||
if (IsClient) { return; }
|
||||
foreach (var kvp in ResourceClusters)
|
||||
foreach (var kvp in resourceClusters)
|
||||
{
|
||||
var prefab = ItemPrefab.Find(null, kvp.Key);
|
||||
if (prefab == null)
|
||||
@@ -88,15 +88,14 @@ namespace Barotrauma
|
||||
"couldn't find an item prefab with the identifier " + kvp.Key);
|
||||
continue;
|
||||
}
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation);
|
||||
if (spawnedResources.Count < kvp.Value.First)
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.amount, out float rotation);
|
||||
if (spawnedResources.Count < kvp.Value.amount)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in MineralMission - " +
|
||||
"spawned " + spawnedResources.Count + "/" + kvp.Value.First + " of " + prefab.Name);
|
||||
"spawned " + spawnedResources.Count + "/" + kvp.Value.amount + " of " + prefab.Name);
|
||||
}
|
||||
if (spawnedResources.None()) { continue; }
|
||||
SpawnedResources.Add(kvp.Key, spawnedResources);
|
||||
kvp.Value.Second = rotation;
|
||||
this.spawnedResources.Add(kvp.Key, spawnedResources);
|
||||
|
||||
foreach (Level.Cave cave in Level.Loaded.Caves)
|
||||
{
|
||||
@@ -142,7 +141,7 @@ namespace Barotrauma
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
foreach (var kvp in SpawnedResources)
|
||||
foreach (var kvp in spawnedResources)
|
||||
{
|
||||
foreach (var i in kvp.Value)
|
||||
{
|
||||
@@ -152,33 +151,33 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
SpawnedResources.Clear();
|
||||
RelevantLevelResources.Clear();
|
||||
MissionClusterPositions.Clear();
|
||||
spawnedResources.Clear();
|
||||
relevantLevelResources.Clear();
|
||||
missionClusterPositions.Clear();
|
||||
failed = !completed && state > 0;
|
||||
}
|
||||
|
||||
private void FindRelevantLevelResources()
|
||||
{
|
||||
RelevantLevelResources.Clear();
|
||||
foreach (var identifier in ResourceClusters.Keys)
|
||||
relevantLevelResources.Clear();
|
||||
foreach (var identifier in resourceClusters.Keys)
|
||||
{
|
||||
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
|
||||
i.Submarine == null && i.ParentInventory == null &&
|
||||
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
|
||||
.ToArray();
|
||||
RelevantLevelResources.Add(identifier, items);
|
||||
relevantLevelResources.Add(identifier, items);
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnoughHaveBeenCollected()
|
||||
{
|
||||
foreach (var kvp in ResourceClusters)
|
||||
foreach (var kvp in resourceClusters)
|
||||
{
|
||||
if (RelevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
|
||||
if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
|
||||
{
|
||||
var collected = availableResources.Count(r => HasBeenCollected(r));
|
||||
var needed = kvp.Value.First;
|
||||
var needed = kvp.Value.amount;
|
||||
if (collected < needed) { return false; }
|
||||
}
|
||||
else
|
||||
@@ -210,8 +209,8 @@ namespace Barotrauma
|
||||
|
||||
private void CalculateMissionClusterPositions()
|
||||
{
|
||||
MissionClusterPositions.Clear();
|
||||
foreach (var kvp in SpawnedResources)
|
||||
missionClusterPositions.Clear();
|
||||
foreach (var kvp in spawnedResources)
|
||||
{
|
||||
if (kvp.Value.None()) { continue; }
|
||||
var pos = Vector2.Zero;
|
||||
@@ -222,7 +221,7 @@ namespace Barotrauma
|
||||
itemCount++;
|
||||
}
|
||||
pos /= itemCount;
|
||||
MissionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
|
||||
missionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -25,7 +27,7 @@ namespace Barotrauma
|
||||
state = value;
|
||||
TryTriggerEvents(state);
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(this, state);
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
#endif
|
||||
ShowMessage(State);
|
||||
}
|
||||
@@ -343,19 +345,57 @@ namespace Barotrauma
|
||||
public void GiveReward()
|
||||
{
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; }
|
||||
campaign.Money += GetReward(Submarine.MainSub);
|
||||
int reward = GetReward(Submarine.MainSub);
|
||||
|
||||
float baseExperienceGain = reward * 0.09f;
|
||||
|
||||
float difficultyMultiplier = 1 + level.Difficulty / 100f;
|
||||
baseExperienceGain *= difficultyMultiplier;
|
||||
|
||||
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters();
|
||||
|
||||
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
|
||||
var experienceGainMultiplier = new AbilityValue(1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
|
||||
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
|
||||
|
||||
int experienceGain = (int)(baseExperienceGain * experienceGainMultiplier.Value);
|
||||
#if CLIENT
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
}
|
||||
#else
|
||||
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
//give the experience to the stored characterinfo if the client isn't currently controlling a character
|
||||
(c.Character?.Info ?? c.CharacterInfo)?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// apply money gains afterwards to prevent them from affecting XP gains
|
||||
var moneyGainMission = new AbilityValueMission(1f, this);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, moneyGainMission));
|
||||
crewCharacters.ForEach(c => moneyGainMission.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
|
||||
campaign.Money += (int)(reward * moneyGainMission.Value);
|
||||
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
character.Info.MissionsCompletedSinceDeath++;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, float> reputationReward in ReputationRewards)
|
||||
{
|
||||
if (reputationReward.Key.Equals("location", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Locations[0].Reputation.Value += reputationReward.Value;
|
||||
Locations[1].Reputation.Value += reputationReward.Value;
|
||||
Locations[0].Reputation.AddReputation(reputationReward.Value);
|
||||
Locations[1].Reputation.AddReputation(reputationReward.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(reputationReward.Key, StringComparison.OrdinalIgnoreCase));
|
||||
if (faction != null) { faction.Reputation.Value += reputationReward.Value; }
|
||||
if (faction != null) { faction.Reputation.AddReputation(reputationReward.Value); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,5 +483,55 @@ namespace Barotrauma
|
||||
|
||||
return spawnedCharacter;
|
||||
}
|
||||
|
||||
protected ItemPrefab FindItemPrefab(XElement element)
|
||||
{
|
||||
ItemPrefab itemPrefab;
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items");
|
||||
string itemName = element.GetAttributeString("name", "");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found");
|
||||
}
|
||||
}
|
||||
return itemPrefab;
|
||||
}
|
||||
|
||||
protected Vector2? GetCargoSpawnPosition(ItemPrefab itemPrefab, out Submarine cargoRoomSub)
|
||||
{
|
||||
cargoRoomSub = null;
|
||||
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true);
|
||||
if (cargoSpawnPos == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found");
|
||||
return null;
|
||||
}
|
||||
|
||||
var cargoRoom = cargoSpawnPos.CurrentHull;
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room");
|
||||
return null;
|
||||
}
|
||||
|
||||
cargoRoomSub = cargoRoom.Submarine;
|
||||
|
||||
return new Vector2(
|
||||
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ namespace Barotrauma
|
||||
Escort = 0x100,
|
||||
Pirate = 0x200,
|
||||
GoTo = 0x400,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo
|
||||
ScanAlienRuins = 0x800,
|
||||
ClearAlienRuins = 0x1000,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo | ScanAlienRuins | ClearAlienRuins
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
@@ -40,7 +42,9 @@ namespace Barotrauma
|
||||
{ MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.Escort, typeof(EscortMission) },
|
||||
{ MissionType.Pirate, typeof(PirateMission) },
|
||||
{ MissionType.GoTo, typeof(GoToMission) }
|
||||
{ MissionType.GoTo, typeof(GoToMission) },
|
||||
{ MissionType.ScanAlienRuins, typeof(ScanMission) },
|
||||
{ MissionType.ClearAlienRuins, typeof(AlienRuinMission) }
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
@@ -372,6 +376,11 @@ namespace Barotrauma
|
||||
var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to));
|
||||
if (connection?.LevelData == null || !connection.LevelData.HasBeaconStation || connection.LevelData.IsBeaconActive) { return false; }
|
||||
}
|
||||
else if (Type == MissionType.ScanAlienRuins || Type == MissionType.ClearAlienRuins)
|
||||
{
|
||||
var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to));
|
||||
if (connection?.LevelData == null || connection.LevelData.GenerationParams.RuinCount < 1) { return false; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,11 @@ namespace Barotrauma
|
||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
|
||||
if (!path.Unreachable)
|
||||
{
|
||||
preferredSpawnPos = path.Nodes[Rand.Range(0, path.Nodes.Count - 1)].WorldPosition; // spawn the sub in a random point in the path if possible
|
||||
var validNodes = path.Nodes.FindAll(n => !Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(n.WorldPosition))));
|
||||
if (validNodes.Any())
|
||||
{
|
||||
preferredSpawnPos = validNodes.GetRandom().WorldPosition; // spawn the sub in a random point in the path if possible
|
||||
}
|
||||
}
|
||||
|
||||
int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead
|
||||
@@ -382,11 +386,11 @@ namespace Barotrauma
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)));
|
||||
private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m));
|
||||
|
||||
private bool Survived(Character character)
|
||||
private bool DeadOrCaptured(Character character)
|
||||
{
|
||||
return character != null && !character.Removed && !character.IsDead;
|
||||
return character != null && !character.Removed && (character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub));
|
||||
}
|
||||
|
||||
public override void End()
|
||||
|
||||
@@ -126,12 +126,12 @@ namespace Barotrauma
|
||||
item = suitableItems.FirstOrDefault(it => Vector2.DistanceSquared(it.WorldPosition, position) < 1000.0f);
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
item = suitableItems.FirstOrDefault(it => it.ParentRuin != null && it.ParentRuin.Area.Contains(position));
|
||||
break;
|
||||
case Level.PositionType.Wreck:
|
||||
foreach (Item it in suitableItems)
|
||||
{
|
||||
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
if (it.Submarine?.Info == null) { continue; }
|
||||
if (spawnPositionType == Level.PositionType.Ruin && it.Submarine.Info.Type != SubmarineType.Ruin) { continue; }
|
||||
if (spawnPositionType == Level.PositionType.Wreck && it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
Rectangle worldBorders = it.Submarine.Borders;
|
||||
worldBorders.Location += it.Submarine.WorldPosition.ToPoint();
|
||||
if (Submarine.RectContains(worldBorders, it.WorldPosition))
|
||||
@@ -178,10 +178,10 @@ namespace Barotrauma
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
if (it.Submarine != null || it.ParentRuin != null) { continue; }
|
||||
if (it.Submarine != null) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
if (it.ParentRuin == null) { continue; }
|
||||
if (it.Submarine?.Info == null || !it.Submarine.Info.IsRuin) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Wreck:
|
||||
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
@@ -247,8 +247,8 @@ namespace Barotrauma
|
||||
|
||||
public override void End()
|
||||
{
|
||||
var root = item.GetRootContainer() ?? item;
|
||||
if (root.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed)
|
||||
var root = item?.GetRootContainer() ?? item;
|
||||
if (root?.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.RuinGeneration;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class ScanMission : Mission
|
||||
{
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> startingItems = new List<Item>();
|
||||
private readonly List<Scanner> scanners = new List<Scanner>();
|
||||
private readonly Dictionary<Item, ushort> parentInventoryIDs = new Dictionary<Item, ushort>();
|
||||
private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
|
||||
private readonly int targetsToScan;
|
||||
private readonly Dictionary<WayPoint, bool> scanTargets = new Dictionary<WayPoint, bool>();
|
||||
private readonly HashSet<WayPoint> newTargetsScanned = new HashSet<WayPoint>();
|
||||
private readonly float minTargetDistance;
|
||||
|
||||
|
||||
private Ruin TargetRuin { get; set; }
|
||||
|
||||
private bool AllTargetsScanned
|
||||
{
|
||||
get
|
||||
{
|
||||
return scanTargets.Any() && scanTargets.All(kvp => kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else if (scanTargets.Any())
|
||||
{
|
||||
return scanTargets
|
||||
.Where(kvp => !kvp.Value)
|
||||
.Select(kvp => kvp.Key.WorldPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ScanMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
targetsToScan = prefab.ConfigElement.GetAttributeInt("targets", 1);
|
||||
minTargetDistance = prefab.ConfigElement.GetAttributeFloat("mintargetdistance", 0.0f);
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
Reset();
|
||||
|
||||
if (IsClient) { return; }
|
||||
|
||||
if (itemConfig == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize a Scan mission: item config is not set");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var element in itemConfig.Elements())
|
||||
{
|
||||
LoadItem(element, null);
|
||||
}
|
||||
GetScanners();
|
||||
|
||||
TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.Server);
|
||||
if (TargetRuin == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize a Scan mission: level contains no alien ruins");
|
||||
return;
|
||||
}
|
||||
|
||||
var ruinWaypoints = TargetRuin.Submarine.GetWaypoints(false);
|
||||
ruinWaypoints.RemoveAll(wp => wp.CurrentHull == null);
|
||||
if (ruinWaypoints.Count < targetsToScan)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})");
|
||||
return;
|
||||
}
|
||||
var availableWaypoints = new List<WayPoint>();
|
||||
float minTargetDistanceSquared = minTargetDistance * minTargetDistance;
|
||||
for (int tries = 0; tries < 15; tries++)
|
||||
{
|
||||
scanTargets.Clear();
|
||||
availableWaypoints.Clear();
|
||||
availableWaypoints.AddRange(ruinWaypoints);
|
||||
for (int i = 0; i < targetsToScan; i++)
|
||||
{
|
||||
var selectedWaypoint = availableWaypoints.GetRandom(randSync: Rand.RandSync.Server);
|
||||
scanTargets.Add(selectedWaypoint, false);
|
||||
availableWaypoints.Remove(selectedWaypoint);
|
||||
if (i < (targetsToScan - 1))
|
||||
{
|
||||
availableWaypoints.RemoveAll(wp => wp.CurrentHull == selectedWaypoint.CurrentHull);
|
||||
availableWaypoints.RemoveAll(wp => Vector2.DistanceSquared(wp.WorldPosition, selectedWaypoint.WorldPosition) < minTargetDistanceSquared);
|
||||
if (availableWaypoints.None())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scanTargets.Count >= targetsToScan)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Successfully initialized a Scan mission: targets set on try #{tries + 1}", Color.Green);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
if ((tries + 1) % 5 == 0)
|
||||
{
|
||||
float reducedMinTargetDistance = (1.0f - (((tries + 1) / 5) * 0.1f)) * minTargetDistance;
|
||||
minTargetDistanceSquared = reducedMinTargetDistance * reducedMinTargetDistance;
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Reducing minimum distance between Scan mission targets (new min: {reducedMinTargetDistance}) to reach the required target count", Color.Yellow);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (scanTargets.Count < targetsToScan)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})");
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
startingItems.Clear();
|
||||
parentInventoryIDs.Clear();
|
||||
parentItemContainerIndices.Clear();
|
||||
scanners.Clear();
|
||||
TargetRuin = null;
|
||||
scanTargets.Clear();
|
||||
}
|
||||
|
||||
private void LoadItem(XElement element, Item parent)
|
||||
{
|
||||
var itemPrefab = FindItemPrefab(element);
|
||||
Vector2? position = GetCargoSpawnPosition(itemPrefab, out Submarine cargoRoomSub);
|
||||
if (!position.HasValue) { return; }
|
||||
var item = new Item(itemPrefab, position.Value, cargoRoomSub);
|
||||
item.FindHull();
|
||||
startingItems.Add(item);
|
||||
if (parent?.GetComponent<ItemContainer>() is ItemContainer itemContainer)
|
||||
{
|
||||
parentInventoryIDs.Add(item, parent.ID);
|
||||
parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(itemContainer));
|
||||
parent.Combine(item, user: null);
|
||||
}
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
int amount = subElement.GetAttributeInt("amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
LoadItem(subElement, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetScanners()
|
||||
{
|
||||
foreach (var startingItem in startingItems)
|
||||
{
|
||||
if (startingItem.GetComponent<Scanner>() is Scanner scanner)
|
||||
{
|
||||
scanner.OnScanStarted += OnScanStarted;
|
||||
if (!IsClient)
|
||||
{
|
||||
scanner.OnScanCompleted += OnScanCompleted;
|
||||
}
|
||||
scanners.Add(scanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScanStarted(Scanner scanner)
|
||||
{
|
||||
float scanRadiusSquared = scanner.ScanRadius * scanner.ScanRadius;
|
||||
foreach (var kvp in scanTargets)
|
||||
{
|
||||
if (!IsValidScanPosition(scanner, kvp, scanRadiusSquared)) { continue; }
|
||||
scanner.DisplayProgressBar = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScanCompleted(Scanner scanner)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
newTargetsScanned.Clear();
|
||||
float scanRadiusSquared = scanner.ScanRadius * scanner.ScanRadius;
|
||||
foreach (var kvp in scanTargets)
|
||||
{
|
||||
if (!IsValidScanPosition(scanner, kvp, scanRadiusSquared)) { continue; }
|
||||
newTargetsScanned.Add(kvp.Key);
|
||||
}
|
||||
foreach (var wp in newTargetsScanned)
|
||||
{
|
||||
scanTargets[wp] = true;
|
||||
}
|
||||
#if SERVER
|
||||
// Server should make sure that the clients' scan target status is in-sync
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool IsValidScanPosition(Scanner scanner, KeyValuePair<WayPoint, bool> scanStatus, float scanRadiusSquared)
|
||||
{
|
||||
if (scanStatus.Value) { return false; }
|
||||
if (scanStatus.Key.Submarine != scanner.Item.Submarine) { return false; }
|
||||
if (Vector2.DistanceSquared(scanStatus.Key.WorldPosition, scanner.Item.WorldPosition) > scanRadiusSquared) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (!AllTargetsScanned) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (State == 2 && AllScannersReturned())
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
foreach (var scanner in scanners)
|
||||
{
|
||||
if (scanner.Item != null && !scanner.Item.Removed)
|
||||
{
|
||||
scanner.OnScanStarted -= OnScanStarted;
|
||||
scanner.OnScanCompleted -= OnScanCompleted;
|
||||
scanner.Item.Remove();
|
||||
}
|
||||
}
|
||||
Reset();
|
||||
failed = !completed && state > 0;
|
||||
|
||||
bool AllScannersReturned()
|
||||
{
|
||||
foreach (var scanner in scanners)
|
||||
{
|
||||
if (scanner?.Item == null || scanner.Item.Removed) { return false; }
|
||||
var owner = scanner.Item.GetRootInventoryOwner();
|
||||
if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (owner is Character c && c.Info != null && GameMain.GameSession.CrewManager.CharacterInfos.Contains(c.Info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user