Unstable 0.1400.0.0
This commit is contained in:
@@ -240,7 +240,7 @@ namespace Barotrauma
|
||||
{
|
||||
TryStartConversation(speaker);
|
||||
}
|
||||
else
|
||||
else if (speaker.ActiveConversation != this)
|
||||
{
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
speaker.ActiveConversation = this;
|
||||
@@ -352,6 +352,14 @@ namespace Barotrauma
|
||||
ShowDialog(speaker, targetCharacter);
|
||||
|
||||
dialogOpened = true;
|
||||
if (speaker != null)
|
||||
{
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
partial void ShowDialog(Character speaker, Character targetCharacter);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class NPCChangeTeamAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string NPCTag { get; set; }
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int TeamTag { get; set; }
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool AddToCrew { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public NPCChangeTeamAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
CharacterTeamType newTeam = (CharacterTeamType)TeamTag;
|
||||
// characters will still remain on friendlyNPC team for rest of the tick
|
||||
npc.SetOriginalTeam(newTeam);
|
||||
|
||||
if (AddToCrew && (newTeam == CharacterTeamType.Team1 || newTeam == CharacterTeamType.Team2))
|
||||
{
|
||||
npc.Info.StartItemsGiven = true;
|
||||
|
||||
GameMain.GameSession.CrewManager.AddCharacter(npc);
|
||||
foreach (Item item in npc.Inventory.AllItems)
|
||||
{
|
||||
item.AllowStealing = true;
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew });
|
||||
#endif
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(NPCChangeTeamAction)} -> (NPCTag: {NPCTag.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,12 +102,12 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (spawned) { return; }
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(NPCSetIdentifier) && !string.IsNullOrEmpty(NPCIdentifier))
|
||||
{
|
||||
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
@@ -255,10 +255,9 @@ namespace Barotrauma
|
||||
|
||||
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
|
||||
|
||||
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false).ToList();
|
||||
if (moduleFlags != null && moduleFlags.Any())
|
||||
{
|
||||
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList();
|
||||
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Any(moduleFlags.Contains) ?? false).ToList();
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints;
|
||||
@@ -267,8 +266,10 @@ namespace Barotrauma
|
||||
|
||||
if (spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag)))
|
||||
.Where(wp => wp.ConnectedDoor == null && !wp.isObstructed);
|
||||
var spawnPoints = potentialSpawnPoints
|
||||
.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag)))
|
||||
.Where(wp => wp.ConnectedDoor == null && !wp.isObstructed);
|
||||
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
@@ -293,6 +294,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//don't spawn in an airlock module if there are other options
|
||||
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock") ?? false);
|
||||
if (airlockSpawnPoints.Count() < validSpawnPoints.Count())
|
||||
{
|
||||
validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -30,6 +31,9 @@ namespace Barotrauma
|
||||
[Serialize(true, true, description: "If true, dead/unconscious characters cannot trigger the action.")]
|
||||
public bool DisableIfTargetIncapacitated { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "If true, one target must interact with the other to trigger the action.")]
|
||||
public bool WaitForInteraction { get; set; }
|
||||
|
||||
private float distance;
|
||||
|
||||
public TriggerAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
@@ -44,12 +48,15 @@ namespace Barotrauma
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
ResetTargetIcons();
|
||||
isRunning = false;
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public bool isRunning = false;
|
||||
|
||||
private Either<Character, Item> npcOrItem = null;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
@@ -81,20 +88,101 @@ namespace Barotrauma
|
||||
if (DisableInCombat && IsInCombat(e2)) { continue; }
|
||||
if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated)) { continue; }
|
||||
|
||||
Vector2 pos1 = e1.WorldPosition;
|
||||
Vector2 pos2 = e2.WorldPosition;
|
||||
distance = Vector2.Distance(pos1, pos2);
|
||||
if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
|
||||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
|
||||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius)
|
||||
if (WaitForInteraction)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
Character player = null;
|
||||
Character npc = null;
|
||||
Item item = null;
|
||||
npcOrItem?.TryGet(out npc);
|
||||
npcOrItem?.TryGet(out item);
|
||||
if (e1 is Character char1)
|
||||
{
|
||||
if (char1.IsBot) { npc ??= char1; }
|
||||
else { player = char1; }
|
||||
}
|
||||
else
|
||||
{
|
||||
item ??= e1 as Item;
|
||||
}
|
||||
if (e2 is Character char2)
|
||||
{
|
||||
if (char2.IsBot) { npc ??= char2; }
|
||||
else { player = char2; }
|
||||
}
|
||||
else
|
||||
{
|
||||
item ??= e2 as Item;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
{
|
||||
if (npc != null)
|
||||
{
|
||||
if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
|
||||
{
|
||||
npcOrItem = npc;
|
||||
npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
|
||||
#if CLIENT
|
||||
npc.SetCustomInteract(
|
||||
Trigger,
|
||||
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
|
||||
#else
|
||||
npc.SetCustomInteract(
|
||||
Trigger,
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
#endif
|
||||
npc.RequireConsciousnessForCustomInteract = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (item != null)
|
||||
{
|
||||
npcOrItem = item;
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.Examine;
|
||||
if (player.SelectedConstruction == item ||
|
||||
player.Inventory.Contains(item) ||
|
||||
(player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 pos1 = e1.WorldPosition;
|
||||
Vector2 pos2 = e2.WorldPosition;
|
||||
distance = Vector2.Distance(pos1, pos2);
|
||||
if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
|
||||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
|
||||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetTargetIcons()
|
||||
{
|
||||
if (npcOrItem == null) { return; }
|
||||
if (npcOrItem.TryGet(out Character npc))
|
||||
{
|
||||
npc.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
npc.SetCustomInteract(null, null);
|
||||
npc.RequireConsciousnessForCustomInteract = true;
|
||||
}
|
||||
else if (npcOrItem.TryGet(out Item item))
|
||||
{
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCloseEnoughToHull(Entity e, out Hull hull)
|
||||
{
|
||||
hull = null;
|
||||
@@ -157,6 +245,7 @@ namespace Barotrauma
|
||||
|
||||
private void Trigger(Entity entity1, Entity entity2)
|
||||
{
|
||||
ResetTargetIcons();
|
||||
if (!string.IsNullOrEmpty(ApplyToTarget1))
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyToTarget1, entity1);
|
||||
@@ -174,7 +263,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (string.IsNullOrEmpty(TargetModuleType))
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (Distance: {((int)distance).ColorizeObject()}, Radius: {Radius.ColorizeObject()}, TargetTags: {Target1Tag.ColorizeObject()}, {Target2Tag.ColorizeObject()})";
|
||||
return
|
||||
$"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (" +
|
||||
(WaitForInteraction ?
|
||||
$"Selected non-player target: {(npcOrItem?.ToString() ?? "<null>").ColorizeObject()}, " :
|
||||
$"Distance: {((int)distance).ColorizeObject()}, ") +
|
||||
$"Radius: {Radius.ColorizeObject()}, " +
|
||||
$"TargetTags: {Target1Tag.ColorizeObject()}, " +
|
||||
$"{Target2Tag.ColorizeObject()})";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
private float calculateDistanceTraveledTimer;
|
||||
private float distanceTraveled;
|
||||
|
||||
private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger;
|
||||
private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger, monsterTotalStrength;
|
||||
|
||||
private float roundDuration;
|
||||
|
||||
@@ -694,47 +694,103 @@ namespace Barotrauma
|
||||
// enemy amount --------------------------------------------------------
|
||||
|
||||
enemyDanger = 0.0f;
|
||||
monsterTotalStrength = 0;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead || character.IsIncapacitated || !character.Enabled || character.IsPet || character.Params.CompareGroup("human")) { continue; }
|
||||
if (character.IsIncapacitated || !character.Enabled || character.IsPet || character.Params.CompareGroup("human")) { continue; }
|
||||
|
||||
if (!(character.AIController is EnemyAIController enemyAI)) { continue; }
|
||||
|
||||
if (!enemyAI.AIParams.StayInAbyss)
|
||||
{
|
||||
// Ignore abyss monsters because they can stay active for quite great distances. They'll be taken into account when they target the sub.
|
||||
monsterTotalStrength += enemyAI.CombatStrength;
|
||||
}
|
||||
|
||||
// Example combat strengths:
|
||||
// Hammerheadspawn 1
|
||||
// Moloch Pupa 1
|
||||
// Terminal cell 20
|
||||
// Leucocyte 40
|
||||
// Husk 90
|
||||
// Crawler 100
|
||||
// Unarmored Mudraptor 140
|
||||
// Spineling 150
|
||||
// Tigerthresher 200
|
||||
// Armored Mudraptor 210
|
||||
// Watcher 400
|
||||
// Golden Hammerhead 400
|
||||
// Hammerhead 500
|
||||
// Hammerhead Matriarch 550
|
||||
// Bonethresher 600
|
||||
// Moloch 1250
|
||||
// Black Moloch 1500
|
||||
// Endworm 10000
|
||||
if (character.CurrentHull?.Submarine != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
|
||||
{
|
||||
//crawler inside the sub adds 0.1f to enemy danger, mantis 0.25f
|
||||
enemyDanger += enemyAI.CombatStrength / 100.0f;
|
||||
// Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42
|
||||
enemyDanger += enemyAI.CombatStrength / 500.0f;
|
||||
}
|
||||
else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
//enemy outside and targeting the sub or something in it
|
||||
//moloch adds 0.24 to enemy danger, a crawler 0.02
|
||||
enemyDanger += enemyAI.CombatStrength / 1000.0f;
|
||||
// Enemy outside targeting the sub or something in it
|
||||
// -> One Crawler adds 0.02, a Mudraptor 0.042, a Hammerhead 0.1, and a Moloch 0.25.
|
||||
enemyDanger += enemyAI.CombatStrength / 5000.0f;
|
||||
}
|
||||
}
|
||||
// Add a portion of the total strength of active monsters to the enemy danger so that we don't spawn too many monsters around the sub.
|
||||
// On top of the existing value, so if 10 crawlers are targeting the sub simultaneously from outside, the final value would be: 0.02 x 10 + 0.2 = 0.4.
|
||||
// And if they get inside, we add 0.1 per crawler on that.
|
||||
// So, in practice the danger per enemy that is attacking the sub is half of what it would be when the enemy is not targeting the sub.
|
||||
// 10 Crawlers -> +0.2 (0.4 in total if all target the sub from outside).
|
||||
// 5 Mudraptors -> +0.21 (0.42 in total, before they get inside).
|
||||
// 3 Hammerheads -> +0.3 (0.6 in total, if they all target the sub).
|
||||
// 2 Molochs -> +0.5 (1.0 in total, if both target the sub).
|
||||
enemyDanger += monsterTotalStrength / 5000f;
|
||||
enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f);
|
||||
|
||||
// The definitions above aim for that we never spawn more monsters that the player (and the performance) can handle.
|
||||
// Some examples that result in the max intensity even when the creatures would just idle around.
|
||||
// The values are theoretical, because in practice many of the monsters are targeting the sub, which will double the danger of those monster and effectively halve the max monster count.
|
||||
// In practice we don't use the max intensity. For example on level 50 we use max intensity 50, which would mean that we'd halve the numbers below.
|
||||
// There's no hard cap for the monster count, but if the amount of monsters is higher than this, we don't spawn more monsters from the events:
|
||||
// 50 Crawlers (We shouldn't actually ever spawn that many. 12 is the max per event, but theoretically 25 crawlers would result in max intensity).
|
||||
// 25 Tigerthreshers (Max 9 per event. 12 targeting the sub at the same time results in max intensity).
|
||||
// 10 Hammerheads (Max 3 per event. 5 targeting the sub at the same time results in max intensity).
|
||||
// 4 Molochs (Max 2 per event and 2 targeting the sub at the same time results in max intensity).
|
||||
|
||||
// hull status (gaps, flooding, fire) --------------------------------------------------------
|
||||
|
||||
float holeCount = 0.0f;
|
||||
float waterAmount = 0.0f;
|
||||
float totalHullVolume = 0.0f;
|
||||
float dryHullVolume = 0.0f;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (hull.RoomName != null && hull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (GameMain.GameSession?.GameMode is PvPMode)
|
||||
{
|
||||
if (hull.Submarine.TeamID != CharacterTeamType.Team1 && hull.Submarine.TeamID != CharacterTeamType.Team2) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hull.Submarine.TeamID != CharacterTeamType.Team1) { continue; }
|
||||
}
|
||||
fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
|
||||
if (hull.IsWetRoom) { continue; }
|
||||
foreach (Gap gap in hull.ConnectedGaps)
|
||||
{
|
||||
if (!gap.IsRoomToRoom) holeCount += gap.Open;
|
||||
if (!gap.IsRoomToRoom)
|
||||
{
|
||||
holeCount += gap.Open;
|
||||
}
|
||||
}
|
||||
waterAmount += hull.WaterVolume;
|
||||
totalHullVolume += hull.Volume;
|
||||
fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
|
||||
dryHullVolume += hull.Volume;
|
||||
}
|
||||
if (totalHullVolume > 0)
|
||||
if (dryHullVolume > 0)
|
||||
{
|
||||
floodingAmount = waterAmount / totalHullVolume;
|
||||
floodingAmount = waterAmount / dryHullVolume;
|
||||
}
|
||||
|
||||
//hull integrity at 0.0 if there are 10 or more wide-open holes
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Barotrauma
|
||||
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
|
||||
Commonness[""] = 1.0f;
|
||||
Commonness[""] = element.GetAttributeFloat("commonness", 1.0f);
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
|
||||
+16
-28
@@ -35,8 +35,8 @@ namespace Barotrauma
|
||||
|
||||
protected bool wasDocked;
|
||||
|
||||
public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations) :
|
||||
base(prefab, locations)
|
||||
public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations, Submarine sub) :
|
||||
base(prefab, locations, sub)
|
||||
{
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
|
||||
@@ -84,14 +84,7 @@ namespace Barotrauma
|
||||
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
string characterIdentifier = element.GetAttributeString("identifier", "");
|
||||
string characterFrom = element.GetAttributeString("from", "");
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
@@ -128,32 +121,27 @@ namespace Barotrauma
|
||||
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.Server), randSync: Rand.RandSync.Server);
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, spawnPos.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
if (element.GetAttributeBool("requirerescue", false))
|
||||
{
|
||||
requireRescue.Add(spawnedCharacter);
|
||||
spawnedCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnedCharacter.TeamID = CharacterTeamType.None;
|
||||
}
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos);
|
||||
humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false);
|
||||
bool requiresRescue = element.GetAttributeBool("requirerescue", false);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None, spawnPos, giveTags: true);
|
||||
|
||||
if (spawnPos is WayPoint wp)
|
||||
{
|
||||
spawnedCharacter.GiveIdCardTags(wp);
|
||||
}
|
||||
|
||||
if (requiresRescue)
|
||||
{
|
||||
requireRescue.Add(spawnedCharacter);
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
requireKill.Add(spawnedCharacter);
|
||||
}
|
||||
characters.Add(spawnedCharacter);
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
}
|
||||
|
||||
private void LoadMonster(CharacterPrefab monsterPrefab, XElement element, Submarine submarine)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Barotrauma
|
||||
private Point monsterCountRange;
|
||||
private readonly string sonarLabel;
|
||||
|
||||
public BeaconMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
|
||||
public BeaconMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
swarmSpawned = false;
|
||||
|
||||
|
||||
@@ -17,15 +17,97 @@ namespace Barotrauma
|
||||
|
||||
private int requiredDeliveryAmount;
|
||||
|
||||
public CargoMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
private readonly List<(XElement element, ItemContainer container)> itemsToSpawn = new List<(XElement element, ItemContainer container)>();
|
||||
private int? rewardPerCrate;
|
||||
private int calculatedReward;
|
||||
private int maxItemCount;
|
||||
|
||||
private Submarine sub;
|
||||
|
||||
public CargoMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
this.sub = sub;
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeInt("requireddeliveryamount", 0);
|
||||
DetermineCargo();
|
||||
}
|
||||
|
||||
private void DetermineCargo()
|
||||
{
|
||||
if (this.sub == null || itemConfig == null)
|
||||
{
|
||||
calculatedReward = Prefab.Reward;
|
||||
return;
|
||||
}
|
||||
|
||||
itemsToSpawn.Clear();
|
||||
List<(ItemContainer container, int freeSlots)> containers = sub.GetCargoContainers();
|
||||
containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); });
|
||||
|
||||
maxItemCount = 0;
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
||||
maxItemCount += maxCount;
|
||||
}
|
||||
|
||||
for (int i = 0; i < containers.Count; i++)
|
||||
{
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
|
||||
ItemPrefab itemPrefab = FindItemPrefab(subElement);
|
||||
while (containers[i].freeSlots > 0 && containers[i].container.Inventory.CanBePut(itemPrefab))
|
||||
{
|
||||
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
||||
itemsToSpawn.Add((subElement, containers[i].container));
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemsToSpawn.Any())
|
||||
{
|
||||
itemsToSpawn.Add((itemConfig.Elements().First(), null));
|
||||
}
|
||||
|
||||
calculatedReward = 0;
|
||||
foreach (var itemToSpawn in itemsToSpawn)
|
||||
{
|
||||
int price = itemToSpawn.element.GetAttributeInt("reward", Prefab.Reward / itemsToSpawn.Count);
|
||||
if (rewardPerCrate.HasValue)
|
||||
{
|
||||
if (price != rewardPerCrate.Value) { rewardPerCrate = -1; }
|
||||
}
|
||||
else
|
||||
{
|
||||
rewardPerCrate = price;
|
||||
}
|
||||
calculatedReward += price;
|
||||
}
|
||||
if (rewardPerCrate.HasValue && rewardPerCrate < 0) { rewardPerCrate = null; }
|
||||
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
}
|
||||
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
if (sub != this.sub)
|
||||
{
|
||||
this.sub = sub;
|
||||
DetermineCargo();
|
||||
}
|
||||
return calculatedReward;
|
||||
}
|
||||
|
||||
private void InitItems()
|
||||
{
|
||||
this.sub = Submarine.MainSub;
|
||||
DetermineCargo();
|
||||
|
||||
items.Clear();
|
||||
parentInventoryIDs.Clear();
|
||||
parentItemContainerIndices.Clear();
|
||||
@@ -36,9 +118,9 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
foreach (var (element, container) in itemsToSpawn)
|
||||
{
|
||||
LoadItemAsChild(subElement, null);
|
||||
LoadItemAsChild(element, container?.Item);
|
||||
}
|
||||
|
||||
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
|
||||
@@ -49,7 +131,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadItemAsChild(XElement element, Item parent)
|
||||
private ItemPrefab FindItemPrefab(XElement element)
|
||||
{
|
||||
ItemPrefab itemPrefab;
|
||||
if (element.Attribute("name") != null)
|
||||
@@ -60,7 +142,6 @@ namespace Barotrauma
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemName + "\" not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -70,15 +151,15 @@ namespace Barotrauma
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
return itemPrefab;
|
||||
}
|
||||
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + element.Name.ToString() + "\" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -88,7 +169,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var cargoRoom = cargoSpawnPos.CurrentHull;
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -7,6 +6,7 @@ namespace Barotrauma
|
||||
partial class CombatMission : Mission
|
||||
{
|
||||
private Submarine[] subs;
|
||||
// TODO: not used
|
||||
private List<Character>[] crews;
|
||||
|
||||
private readonly string[] descriptions;
|
||||
@@ -45,8 +45,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public CombatMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
public CombatMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
descriptions = new string[]
|
||||
{
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EscortMission : Mission
|
||||
{
|
||||
private readonly XElement characterConfig;
|
||||
private readonly XElement itemConfig;
|
||||
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterDictionary = new Dictionary<Character, List<Item>>();
|
||||
|
||||
private readonly int baseEscortedCharacters;
|
||||
private readonly float scalingEscortedCharacters;
|
||||
private readonly float terroristChance;
|
||||
|
||||
private Character vipCharacter;
|
||||
|
||||
private readonly List<Character> terroristCharacters = new List<Character>();
|
||||
private bool terroristsShouldAct = false;
|
||||
private float terroristDistanceSquared;
|
||||
private const string TerroristTeamChangeIdentifier = "terrorist";
|
||||
|
||||
public EscortMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
// Should reflect different escortables, prisoners, VIPs, passengers (where does this comment refer to?)
|
||||
|
||||
baseEscortedCharacters = prefab.ConfigElement.GetAttributeInt("baseescortedcharacters", 1);
|
||||
scalingEscortedCharacters = prefab.ConfigElement.GetAttributeFloat("scalingescortedcharacters", 0);
|
||||
terroristChance = prefab.ConfigElement.GetAttributeFloat("terroristchance", 0);
|
||||
itemConfig = prefab.ConfigElement.Element("TerroristItems");
|
||||
}
|
||||
|
||||
public override int Reward
|
||||
{
|
||||
get
|
||||
{
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
return Prefab.Reward * multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
int CalculateScalingEscortedCharacterCount(bool inMission = false)
|
||||
{
|
||||
if (Submarine.MainSub == null || Submarine.MainSub.Info == null) // UI logic failing to get the correct value is not important, but the mission logic must succeed
|
||||
{
|
||||
if (inMission)
|
||||
{
|
||||
DebugConsole.ThrowError("MainSub was null when trying to retrieve submarine size for determining escorted character count!");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * Submarine.MainSub.Info.RecommendedCrewSizeMin);
|
||||
}
|
||||
|
||||
private void InitEscort()
|
||||
{
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
// VIP transport mission characters stay in the same location; other characters roam at will
|
||||
// could be replaced with a designated waypoint for VIPs, such as cargo or crew
|
||||
WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
|
||||
Rand.RandSync randSync = Rand.RandSync.Server;
|
||||
|
||||
if (terroristChance > 0f)
|
||||
{
|
||||
// in terrorist missions, reroll characters each retry to avoid confusion as to who the terrorists are
|
||||
randSync = Rand.RandSync.Unsynced;
|
||||
}
|
||||
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
int count = CalculateScalingEscortedCharacterCount(inMission: true);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterDictionary, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
|
||||
if (spawnedCharacter.AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.InitMentalStateManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCharacters()
|
||||
{
|
||||
int scalingCharacterCount = CalculateScalingEscortedCharacterCount(inMission: true);
|
||||
|
||||
if (scalingCharacterCount * characterConfig.Elements().Count() != characters.Count)
|
||||
{
|
||||
DebugConsole.AddWarning("Character count did not match expected character count in InitCharacters of EscortMission");
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
string escortIdentifier = element.GetAttributeString("escortidentifier", string.Empty);
|
||||
string colorIdentifier = element.GetAttributeString("color", string.Empty);
|
||||
for (int k = 0; k < scalingCharacterCount; k++)
|
||||
{
|
||||
// for each element defined, we need to initialize that type of character equal to the scaling escorted character count
|
||||
characters[k + i].IsEscorted = true;
|
||||
if (escortIdentifier != string.Empty)
|
||||
{
|
||||
if (escortIdentifier == "vip")
|
||||
{
|
||||
vipCharacter = characters[k + i];
|
||||
}
|
||||
}
|
||||
characters[k + i].UniqueNameColor = element.GetAttributeColor("color", Color.LightGreen);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!IsClient && terroristChance > 0f)
|
||||
{
|
||||
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
|
||||
terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
|
||||
|
||||
terroristCharacters.Clear();
|
||||
characters.Shuffle();
|
||||
characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
|
||||
|
||||
terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
DebugConsole.AddWarning(character.Name + " is a terrorist.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
if (characters.Count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"characters.Count > 0 ({characters.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Character list was not empty at the start of a escort mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
characters.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (characterConfig == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
InitEscort();
|
||||
InitCharacters();
|
||||
}
|
||||
}
|
||||
|
||||
void TryToTriggerTerrorists()
|
||||
{
|
||||
if (terroristsShouldAct)
|
||||
{
|
||||
// decoupled from range check to prevent from weirdness if players handcuff a terrorist and move backwards
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
if (character.HasTeamChange(TerroristTeamChangeIdentifier))
|
||||
{
|
||||
// already triggered
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsAlive(character) && !character.IsIncapacitated && !character.LockHands)
|
||||
{
|
||||
character.TryAddNewTeamChange(TerroristTeamChangeIdentifier, new ActiveTeamChange(CharacterTeamType.None, ActiveTeamChange.TeamChangePriorities.Willful, aggressiveBehavior: true));
|
||||
character.Speak(TextManager.Get("dialogterroristannounce"), null, Rand.Range(0.5f, 3f));
|
||||
XElement randomElement = itemConfig.Elements().GetRandom(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty);
|
||||
if (randomElement != null)
|
||||
{
|
||||
HumanPrefab.InitializeItem(character, randomElement, character.Submarine, humanPrefab: null, createNetworkEvents: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) < terroristDistanceSquared)
|
||||
{
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.ObjectiveManager.AddObjective(new AIObjectiveEscapeHandcuffs(character, humanAI.ObjectiveManager, shouldSwitchTeams: false, beginInstantly: true));
|
||||
}
|
||||
}
|
||||
terroristsShouldAct = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool NonTerroristsStillAlive(IEnumerable<Character> characterList)
|
||||
{
|
||||
return characterList.Any(c => !terroristCharacters.Contains(c) && IsAlive(c));
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
int newState = State;
|
||||
TryToTriggerTerrorists();
|
||||
switch (State)
|
||||
{
|
||||
case 0: // base
|
||||
if (!NonTerroristsStillAlive(characters))
|
||||
{
|
||||
newState = 1;
|
||||
}
|
||||
if (terroristCharacters.Any() && terroristCharacters.All(c => !IsAlive(c)))
|
||||
{
|
||||
newState = 2;
|
||||
}
|
||||
break;
|
||||
case 1: // failure
|
||||
break;
|
||||
case 2: // terrorists killed
|
||||
if (!NonTerroristsStillAlive(characters))
|
||||
{
|
||||
newState = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
State = newState;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Survived(Character character)
|
||||
{
|
||||
return IsAlive(character) && character.CurrentHull != null && character.CurrentHull.Submarine == Submarine.MainSub;
|
||||
}
|
||||
|
||||
private bool IsAlive(Character character)
|
||||
{
|
||||
return character != null && !character.Removed && !character.IsDead;
|
||||
}
|
||||
|
||||
private bool IsCaptured(Character character)
|
||||
{
|
||||
return character.LockHands && character.HasTeamChange(TerroristTeamChangeIdentifier);
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
bool terroristsSurvived = terroristCharacters.Any(c => Survived(c) && !IsCaptured(c));
|
||||
bool friendliesSurvived = characters.Except(terroristCharacters).Any(c => Survived(c));
|
||||
bool vipDied = false;
|
||||
|
||||
if (vipCharacter != null)
|
||||
{
|
||||
vipDied = !Survived(vipCharacter);
|
||||
}
|
||||
|
||||
if (friendliesSurvived && !terroristsSurvived && !vipDied)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// characters that survived will take their items with them, in case players tried to be crafty and steal them
|
||||
// this needs to run here in case players abort the mission by going back home
|
||||
// TODO: I think this might feel like a bug.
|
||||
foreach (var characterItem in characterDictionary)
|
||||
{
|
||||
if (Survived(characterItem.Key) || !completed)
|
||||
{
|
||||
foreach (Item item in characterItem.Value)
|
||||
{
|
||||
if (!item.Removed)
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
failed = !completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public MineralMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
|
||||
public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
var configElement = prefab.ConfigElement.Element("Items");
|
||||
foreach (var c in configElement.GetChildElements("Item"))
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -61,14 +62,19 @@ namespace Barotrauma
|
||||
//private set { description = value; }
|
||||
}
|
||||
|
||||
protected string descriptionWithoutReward;
|
||||
|
||||
public virtual bool AllowUndocking
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public int Reward
|
||||
public virtual int Reward
|
||||
{
|
||||
get { return Prefab.Reward; }
|
||||
get
|
||||
{
|
||||
return Prefab.Reward;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, float> ReputationRewards
|
||||
@@ -92,6 +98,16 @@ namespace Barotrauma
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual int TeamCount
|
||||
{
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public virtual SubmarineInfo EnemySubmarineInfo
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get { return Enumerable.Empty<Vector2>(); }
|
||||
@@ -113,7 +129,7 @@ namespace Barotrauma
|
||||
get { return Prefab.Difficulty; }
|
||||
}
|
||||
|
||||
public Mission(MissionPrefab prefab, Location[] locations)
|
||||
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(locations.Length == 2);
|
||||
|
||||
@@ -138,8 +154,12 @@ namespace Barotrauma
|
||||
Messages[m] = Messages[m].Replace("[location" + (n + 1) + "]", locationName);
|
||||
}
|
||||
}
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward)}‖end‖";
|
||||
if (description != null) { description = description.Replace("[reward]", rewardText); }
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))}‖end‖";
|
||||
if (description != null)
|
||||
{
|
||||
descriptionWithoutReward = description;
|
||||
description = description.Replace("[reward]", rewardText);
|
||||
}
|
||||
if (successMessage != null) { successMessage = successMessage.Replace("[reward]", rewardText); }
|
||||
if (failureMessage != null) { failureMessage = failureMessage.Replace("[reward]", rewardText); }
|
||||
for (int m = 0; m < Messages.Count; m++)
|
||||
@@ -181,7 +201,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (randomNumber <= missionPrefab.Commonness)
|
||||
{
|
||||
return missionPrefab.Instantiate(locations);
|
||||
return missionPrefab.Instantiate(locations, Submarine.MainSub);
|
||||
}
|
||||
randomNumber -= missionPrefab.Commonness;
|
||||
}
|
||||
@@ -189,6 +209,11 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual int GetReward(Submarine sub)
|
||||
{
|
||||
return Prefab.Reward;
|
||||
}
|
||||
|
||||
public void Start(Level level)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -232,7 +257,7 @@ namespace Barotrauma
|
||||
public void GiveReward()
|
||||
{
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; }
|
||||
campaign.Money += Reward;
|
||||
campaign.Money += GetReward(Submarine.MainSub);
|
||||
|
||||
foreach (KeyValuePair<string, float> reputationReward in ReputationRewards)
|
||||
{
|
||||
@@ -287,5 +312,48 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public virtual void AdjustLevelData(LevelData levelData) { }
|
||||
|
||||
// putting these here since both escort and pirate missions need them. could be tucked away into another class that they can inherit from (or use composition)
|
||||
protected HumanPrefab CreateHumanPrefabFromElement(XElement element)
|
||||
{
|
||||
HumanPrefab humanPrefab = null;
|
||||
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission \"" + Name + "\" - use character identifiers instead of names to configure the characters.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
string characterIdentifier = element.GetAttributeString("identifier", "");
|
||||
string characterFrom = element.GetAttributeString("from", "");
|
||||
humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn character for mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
return humanPrefab;
|
||||
}
|
||||
|
||||
protected Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.Server, bool giveTags = true)
|
||||
{
|
||||
if (positionToStayIn == null)
|
||||
{
|
||||
positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
||||
}
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
characterInfo.TeamID = teamType;
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||
humanPrefab.GiveItems(spawnedCharacter, submarine, Rand.RandSync.Server, createNetworkEvents: false);
|
||||
|
||||
characters.Add(spawnedCharacter);
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
|
||||
return spawnedCharacter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace Barotrauma
|
||||
Combat = 0x40,
|
||||
OutpostDestroy = 0x80,
|
||||
OutpostRescue = 0x100,
|
||||
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | OutpostDestroy | OutpostRescue
|
||||
Escort = 0x200,
|
||||
Pirate = 0x400,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | OutpostDestroy | OutpostRescue | Escort | Pirate
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
@@ -38,6 +39,8 @@ namespace Barotrauma
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
{ MissionType.OutpostDestroy, typeof(OutpostDestroyMission) },
|
||||
{ MissionType.OutpostRescue, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.Escort, typeof(EscortMission) },
|
||||
{ MissionType.Pirate, typeof(PirateMission) }
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
@@ -286,16 +289,20 @@ namespace Barotrauma
|
||||
|
||||
if (CoOpMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = CoOpMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
constructor = CoOpMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) });
|
||||
}
|
||||
else if (PvPMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = PvPMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
constructor = PvPMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) });
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - unsupported mission type \"" + Type.ToString() + "\"");
|
||||
}
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!");
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
@@ -333,9 +340,9 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public Mission Instantiate(Location[] locations)
|
||||
public Mission Instantiate(Location[] locations, Submarine sub)
|
||||
{
|
||||
return constructor?.Invoke(new object[] { this, locations }) as Mission;
|
||||
return constructor?.Invoke(new object[] { this, locations, sub }) as Mission;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public MonsterMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
public MonsterMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
string speciesName = prefab.ConfigElement.GetAttributeString("monsterfile", null);
|
||||
if (!string.IsNullOrEmpty(speciesName))
|
||||
|
||||
@@ -46,8 +46,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public NestMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
public NestMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostDestroyMission(MissionPrefab prefab, Location[] locations) :
|
||||
base(prefab, locations)
|
||||
public OutpostDestroyMission(MissionPrefab prefab, Location[] locations, Submarine sub) :
|
||||
base(prefab, locations, sub)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
@@ -96,10 +96,10 @@ namespace Barotrauma
|
||||
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
Vector2 spawnPos = spawnPoint.WorldPosition;
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null)
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
|
||||
{
|
||||
spawnPos = new Vector2(
|
||||
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X, wp.CurrentHull.WorldRect.Right),
|
||||
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
|
||||
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
|
||||
}
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class PirateMission : Mission
|
||||
{
|
||||
private readonly XElement characterConfig;
|
||||
private readonly XElement submarineConfig;
|
||||
|
||||
private Submarine enemySub;
|
||||
private Item reactorItem;
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterDictionary = new Dictionary<Character, List<Item>>();
|
||||
|
||||
// Update the last sighting periodically so that the players can find the pirate sub even if they have lost the track of it.
|
||||
private readonly float pirateSightingUpdateFrequency = 30;
|
||||
private float pirateSightingUpdateTimer;
|
||||
private Vector2? lastSighting;
|
||||
|
||||
public override int TeamCount => 2;
|
||||
|
||||
private bool outsideOfSonarRange;
|
||||
|
||||
private readonly List<Vector2> patrolPositions = new List<Vector2>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
var empty = Enumerable.Empty<Vector2>();
|
||||
if (outsideOfSonarRange)
|
||||
{
|
||||
return State switch
|
||||
{
|
||||
0 => patrolPositions,
|
||||
1 => lastSighting.HasValue ? lastSighting.Value.ToEnumerable() : empty,
|
||||
_ => empty,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SubmarineInfo submarineInfo;
|
||||
|
||||
public override SubmarineInfo EnemySubmarineInfo => submarineInfo;
|
||||
|
||||
public PirateMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
submarineConfig = prefab.ConfigElement.Element("Submarine");
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
|
||||
string submarineIdentifier = submarineConfig.GetAttributeString("identifier", string.Empty);
|
||||
|
||||
if (submarineIdentifier == string.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError("No identifier used for submarine for pirate mission!");
|
||||
return;
|
||||
}
|
||||
// maybe a little redundant
|
||||
var contentFile = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.EnemySubmarine).FirstOrDefault(x => x.Path == submarineIdentifier);
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No submarine file found with the identifier!");
|
||||
return;
|
||||
}
|
||||
submarineInfo = new SubmarineInfo(contentFile.Path);
|
||||
}
|
||||
|
||||
private void CreateMissionPositions(out Vector2 preferredSpawnPos)
|
||||
{
|
||||
Vector2 patrolPos = enemySub.WorldPosition;
|
||||
Point subSize = enemySub.GetDockedBorders().Size;
|
||||
|
||||
if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos))
|
||||
{
|
||||
DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this);
|
||||
}
|
||||
if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f))
|
||||
{
|
||||
DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this);
|
||||
}
|
||||
|
||||
patrolPos = enemySub.FindSpawnPos(patrolPos, subSize);
|
||||
|
||||
patrolPositions.Add(patrolPos);
|
||||
patrolPositions.Add(preferredSpawnPos);
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
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
|
||||
}
|
||||
|
||||
int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead
|
||||
preferredSpawnPos = enemySub.FindSpawnPos(preferredSpawnPos, new Point(subSize.X + graceDistance, subSize.Y + graceDistance));
|
||||
}
|
||||
}
|
||||
|
||||
private void InitPirateShip(Vector2 spawnPos)
|
||||
{
|
||||
enemySub.NeutralizeBallast();
|
||||
if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
reactor.PowerUpImmediately();
|
||||
reactorItem = reactor.Item;
|
||||
}
|
||||
enemySub.EnableMaintainPosition();
|
||||
enemySub.SetPosition(spawnPos);
|
||||
enemySub.TeamID = CharacterTeamType.None;
|
||||
}
|
||||
|
||||
private void InitPirates()
|
||||
{
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
|
||||
if (characterConfig == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)");
|
||||
return;
|
||||
}
|
||||
|
||||
bool commanderAssigned = false;
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterDictionary, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
{
|
||||
bool isCommander = element.GetAttributeBool("iscommander", false);
|
||||
if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
|
||||
{
|
||||
humanAIController.InitShipCommandManager();
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
{
|
||||
humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos);
|
||||
}
|
||||
commanderAssigned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
if (characters.Count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"characters.Count > 0 ({characters.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Character list was not empty at the start of a pirate mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
characters.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (patrolPositions.Count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"patrolPositions.Count > 0 ({patrolPositions.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Patrol point list was not empty at the start of a pirate mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
patrolPositions.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
enemySub = Submarine.MainSubs[1];
|
||||
|
||||
if (enemySub == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Enemy Submarine was not created. SubmarineInfo is likely not defined.");
|
||||
// TODO: should we set the state to something here?
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 spawnPos = Level.Loaded.EndPosition; // in case TryGetInterestingPosition fails, though this should not happen
|
||||
CreateMissionPositions(out spawnPos); // patrol positions are not explicitly replicated, instead they are acquired the same way the server acquires them
|
||||
#if DEBUG
|
||||
if (IsClient)
|
||||
{
|
||||
DebugConsole.NewMessage("The patrol positions set by client were: ");
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage("The patrol positions set by server were: ");
|
||||
}
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
{
|
||||
DebugConsole.NewMessage("Patrol pos: " + patrolPos);
|
||||
}
|
||||
#endif
|
||||
if (!IsClient)
|
||||
{
|
||||
InitPirateShip(spawnPos);
|
||||
}
|
||||
|
||||
// flipping the sub on the frame it is moved into place must be done after it's been moved, or it breaks item connections to the submarine
|
||||
// creating the pirates have to be done after the sub has been flipped, or it seems to break the AI pathing
|
||||
enemySub.FlipX();
|
||||
enemySub.ShowSonarMarker = false;
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
InitPirates();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
int newState = State;
|
||||
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
|
||||
outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange;
|
||||
if (State < 2 && CheckWinState())
|
||||
{
|
||||
newState = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
for (int i = patrolPositions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Vector2.DistanceSquared(patrolPositions[i], Submarine.MainSub.WorldPosition) < sqrSonarRange)
|
||||
{
|
||||
patrolPositions.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
if (!outsideOfSonarRange || patrolPositions.None())
|
||||
{
|
||||
newState = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (outsideOfSonarRange)
|
||||
{
|
||||
if (lastSighting.HasValue && Vector2.DistanceSquared(lastSighting.Value, Submarine.MainSub.WorldPosition) < sqrSonarRange)
|
||||
{
|
||||
lastSighting = null;
|
||||
}
|
||||
pirateSightingUpdateTimer -= deltaTime;
|
||||
if (pirateSightingUpdateTimer < 0)
|
||||
{
|
||||
pirateSightingUpdateTimer = pirateSightingUpdateFrequency;
|
||||
lastSighting = enemySub.WorldPosition;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastSighting = enemySub.WorldPosition;
|
||||
pirateSightingUpdateTimer = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)) || reactorItem.Condition <= 0f);
|
||||
|
||||
private bool Survived(Character character)
|
||||
{
|
||||
return character != null && !character.Removed && !character.IsDead;
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (state == 2)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
failed = !completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public SalvageMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
public SalvageMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
containerTag = prefab.ConfigElement.GetAttributeString("containertag", "");
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
|
||||
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 1000), 0, 3000);
|
||||
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
@@ -192,8 +192,8 @@ namespace Barotrauma
|
||||
spawnPos = Vector2.Zero;
|
||||
var availablePositions = GetAvailableSpawnPositions();
|
||||
var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
|
||||
bool isSubOrWreck = spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Wreck;
|
||||
if (affectSubImmediately && !isSubOrWreck && spawnPosType != Level.PositionType.Abyss)
|
||||
bool isRuinOrWreck = spawnPosType.HasFlag(Level.PositionType.Ruin) || spawnPosType.HasFlag(Level.PositionType.Wreck);
|
||||
if (affectSubImmediately && !isRuinOrWreck && !spawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
if (availablePositions.None())
|
||||
{
|
||||
@@ -264,7 +264,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isSubOrWreck)
|
||||
if (!isRuinOrWreck)
|
||||
{
|
||||
float minDistance = 20000;
|
||||
var refSub = GetReferenceSub();
|
||||
@@ -375,7 +375,7 @@ namespace Barotrauma
|
||||
if (spawnPending)
|
||||
{
|
||||
//wait until there are no submarines at the spawnpos
|
||||
if (spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath || spawnPosType == Level.PositionType.Abyss)
|
||||
if (spawnPosType.HasFlag(Level.PositionType.MainPath) || spawnPosType.HasFlag(Level.PositionType.SidePath) || spawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
@@ -387,7 +387,7 @@ namespace Barotrauma
|
||||
|
||||
//if spawning in a ruin/cave, wait for someone to be close to it to spawning
|
||||
//unnecessary monsters in places the players might never visit during the round
|
||||
if (spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Cave || spawnPosType == Level.PositionType.Wreck)
|
||||
if (spawnPosType.HasFlag(Level.PositionType.Ruin) || spawnPosType.HasFlag(Level.PositionType.Cave) || spawnPosType.HasFlag(Level.PositionType.Wreck))
|
||||
{
|
||||
bool someoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 0.8f;
|
||||
@@ -415,16 +415,19 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
if (spawnPosType == Level.PositionType.Abyss || spawnPosType == Level.PositionType.AbyssCave)
|
||||
if (spawnPosType.HasFlag(Level.PositionType.Abyss) || spawnPosType.HasFlag(Level.PositionType.AbyssCave))
|
||||
{
|
||||
bool anyInAbyss = false;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (submarine.WorldPosition.Y > 0)
|
||||
if (submarine.Info.Type != SubmarineType.Player || submarine == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle) { continue; }
|
||||
if (submarine.WorldPosition.Y < 0)
|
||||
{
|
||||
return;
|
||||
anyInAbyss = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!anyInAbyss) { return; }
|
||||
}
|
||||
|
||||
spawnPending = false;
|
||||
@@ -432,7 +435,15 @@ namespace Barotrauma
|
||||
//+1 because Range returns an integer less than the max value
|
||||
int amount = Rand.Range(minAmount, maxAmount + 1);
|
||||
monsters = new List<Character>();
|
||||
float offsetAmount = spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath ? scatter : 100;
|
||||
float scatterAmount = scatter;
|
||||
if (spawnPosType.HasFlag(Level.PositionType.SidePath))
|
||||
{
|
||||
scatterAmount = Math.Min(scatter, Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath).Min(t => t.MinWidth) / 2);
|
||||
}
|
||||
else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
|
||||
{
|
||||
scatterAmount = 100;
|
||||
}
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
string seed = Level.Loaded.Seed + i.ToString();
|
||||
@@ -443,8 +454,8 @@ namespace Barotrauma
|
||||
|
||||
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
|
||||
|
||||
Vector2 pos = spawnPos.Value + Rand.Vector(offsetAmount);
|
||||
if (spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath)
|
||||
Vector2 pos = spawnPos.Value + Rand.Vector(scatterAmount);
|
||||
if (scatterAmount > 100)
|
||||
{
|
||||
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user