Faction Test v1.0.1.0
This commit is contained in:
@@ -18,9 +18,9 @@ namespace Barotrauma
|
||||
|
||||
public const ushort ReservedIDStart = ushort.MaxValue - 3;
|
||||
|
||||
public const ushort MaxEntityCount = ushort.MaxValue - 2; //ushort.MaxValue - 2 because 0 and ushort.MaxValue are reserved values
|
||||
public const ushort MaxEntityCount = ushort.MaxValue - 4; //ushort.MaxValue - 4 because the 4 values above are reserved values
|
||||
|
||||
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
private static readonly Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
public static IReadOnlyCollection<Entity> GetEntities()
|
||||
{
|
||||
return dictionary.Values;
|
||||
@@ -85,6 +85,28 @@ namespace Barotrauma
|
||||
this.Submarine = submarine;
|
||||
spawnTime = Timing.TotalTime;
|
||||
|
||||
if (dictionary.Count >= MaxEntityCount)
|
||||
{
|
||||
Dictionary<Identifier, int> entityCounts = new Dictionary<Identifier, int>();
|
||||
foreach (var entity in dictionary)
|
||||
{
|
||||
if (entity.Value is MapEntity me)
|
||||
{
|
||||
if (entityCounts.ContainsKey(me.Prefab.Identifier))
|
||||
{
|
||||
entityCounts[me.Prefab.Identifier]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
entityCounts[me.Prefab.Identifier] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
string errorMsg = $"Maximum amount of entities ({MaxEntityCount}) exceeded! Largest numbers of entities: " +
|
||||
string.Join(", ", entityCounts.OrderByDescending(kvp => kvp.Value).Take(10).Select(kvp => $"{kvp.Key}: {kvp.Value}"));
|
||||
throw new Exception(errorMsg);
|
||||
}
|
||||
|
||||
//give a unique ID
|
||||
ID = DetermineID(id, submarine);
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace Barotrauma
|
||||
hull.AddDecal(decal, worldPosition, decalSize, isNetworkEvent: false);
|
||||
}
|
||||
|
||||
Attack.DamageMultiplier = 1.0f;
|
||||
float displayRange = Attack.Range;
|
||||
if (damageSource is Item sourceItem)
|
||||
{
|
||||
@@ -192,6 +193,12 @@ namespace Barotrauma
|
||||
item.Condition -= item.MaxCondition * EmpStrength * distFactor;
|
||||
}
|
||||
|
||||
var lightComponent = item.GetComponent<LightComponent>();
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor, 10.0f);
|
||||
}
|
||||
|
||||
//discharge batteries
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
if (powerContainer != null)
|
||||
@@ -264,7 +271,7 @@ namespace Barotrauma
|
||||
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
|
||||
{
|
||||
float distFactor = 1.0f - dist / displayRange;
|
||||
float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
float damageAmount = Attack.GetItemDamage(1.0f, item.Prefab.ExplosionDamageMultiplier);
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }
|
||||
@@ -352,7 +359,7 @@ namespace Barotrauma
|
||||
if (affliction.DivideByLimbCount)
|
||||
{
|
||||
float limbCountFactor = distFactors.Count;
|
||||
if (affliction.Prefab.LimbSpecific && affliction.Prefab.AfflictionType == "damage")
|
||||
if (affliction.Prefab.LimbSpecific && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
|
||||
{
|
||||
// Shouldn't go above 15, or the damage can be unexpectedly low -> doesn't break armor
|
||||
// Effectively this makes large explosions more effective against large creatures (because more limbs are affected), but I don't think that's necessarily a bad thing.
|
||||
|
||||
@@ -4,6 +4,7 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -51,7 +52,6 @@ namespace Barotrauma
|
||||
//can ambient light get through the gap even if it's not open
|
||||
public bool PassAmbientLight;
|
||||
|
||||
|
||||
//a collider outside the gap (for example an ice wall next to the sub)
|
||||
//used by ragdolls to prevent them from ending up inside colliders when teleporting out of the sub
|
||||
private Body outsideCollisionBlocker;
|
||||
@@ -63,8 +63,43 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value)) { return; }
|
||||
if (value > open) { openedTimer = 1.0f; }
|
||||
if (value > open)
|
||||
{
|
||||
openedTimer = 1.0f;
|
||||
}
|
||||
if (connectedDoor == null && !IsHorizontal && linkedTo.Any(e => e is Hull))
|
||||
{
|
||||
if (value > open && value >= 1.0f)
|
||||
{
|
||||
InformWaypointsAboutGapState(this, open: true);
|
||||
}
|
||||
else if (value < open && open >= 1.0f)
|
||||
{
|
||||
InformWaypointsAboutGapState(this, open: false);
|
||||
}
|
||||
}
|
||||
open = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
|
||||
static void InformWaypointsAboutGapState(Gap gap, bool open)
|
||||
{
|
||||
foreach (var wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (IsWaypointRightAboveGap(gap, wp))
|
||||
{
|
||||
wp.OnGapStateChanged(open, gap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsWaypointRightAboveGap(Gap gap, WayPoint wp)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Path) { return false; }
|
||||
if (!gap.linkedTo.Contains(wp.CurrentHull)) { return false; }
|
||||
if (wp.Position.Y < gap.Rect.Top) { return false; }
|
||||
if (wp.Position.X > gap.Rect.Right) { return false; }
|
||||
if (wp.Position.X < gap.Rect.Left) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1083,7 +1083,7 @@ namespace Barotrauma
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
{
|
||||
//gap blocked if the door is not open or the predicted state is not open
|
||||
if ((!g.ConnectedDoor.IsOpen && !g.ConnectedDoor.IsBroken) || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
if ((g.ConnectedDoor.IsClosed && !g.ConnectedDoor.IsBroken) || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
{
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
Vector2 WorldPosition { get; }
|
||||
float Health { get; }
|
||||
|
||||
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound=true);
|
||||
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true);
|
||||
|
||||
|
||||
public readonly struct AttackEventData
|
||||
|
||||
@@ -50,6 +50,12 @@ namespace Barotrauma
|
||||
Description = TextManager.Get($"EntityDescription.{Identifier}");
|
||||
Tags = Enumerable.Empty<Identifier>().ToImmutableHashSet();
|
||||
|
||||
string description = element.GetAttributeString("description", string.Empty);
|
||||
if (!description.IsNullOrEmpty())
|
||||
{
|
||||
Description = Description.Fallback(description);
|
||||
}
|
||||
|
||||
List<ushort> containedItemIDs = new List<ushort>();
|
||||
foreach (XElement entityElement in element.Elements())
|
||||
{
|
||||
|
||||
@@ -1700,14 +1700,22 @@ namespace Barotrauma
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
bool tooClose = false;
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (Vector2.DistanceSquared(edge.Point1, position) < minDistSqr ||
|
||||
Vector2.DistanceSquared(edge.Point2, position) < minDistSqr ||
|
||||
MathUtils.LineSegmentToPointDistanceSquared(edge.Point1.ToPoint(), edge.Point2.ToPoint(), position.ToPoint()) < minDistSqr)
|
||||
|
||||
if (cell.IsPointInsideAABB(position, margin: minDistance))
|
||||
{
|
||||
tooClose = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
if (Vector2.DistanceSquared(edge.Point1, position) < minDistSqr ||
|
||||
Vector2.DistanceSquared(edge.Point2, position) < minDistSqr ||
|
||||
MathUtils.LineSegmentToPointDistanceSquared(edge.Point1.ToPoint(), edge.Point2.ToPoint(), position.ToPoint()) < minDistSqr)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tooClose) { tooCloseCells.Add(cell); }
|
||||
@@ -3247,7 +3255,8 @@ namespace Barotrauma
|
||||
{
|
||||
suitablePositions.RemoveAll(p => !filter(p));
|
||||
}
|
||||
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath))
|
||||
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath) || positionType.HasFlag(PositionType.Abyss) ||
|
||||
positionType.HasFlag(PositionType.Cave) || positionType.HasFlag(PositionType.AbyssCave))
|
||||
{
|
||||
suitablePositions.RemoveAll(p => IsPositionInsideWall(p.Position.ToVector2()));
|
||||
}
|
||||
@@ -3412,8 +3421,7 @@ namespace Barotrauma
|
||||
bool closeEnough = false;
|
||||
foreach (VoronoiCell cell in wall.Cells)
|
||||
{
|
||||
if (Math.Abs(cell.Center.X - worldPos.X) < (searchDepth + 1) * GridCellSize &&
|
||||
Math.Abs(cell.Center.Y - worldPos.Y) < (searchDepth + 1) * GridCellSize)
|
||||
if (cell.IsPointInsideAABB(worldPos, margin: (searchDepth + 1) * GridCellSize / 2))
|
||||
{
|
||||
closeEnough = true;
|
||||
break;
|
||||
|
||||
@@ -112,10 +112,9 @@ namespace Barotrauma
|
||||
(int)MathUtils.Round(generationParams.Height, Level.GridCellSize));
|
||||
}
|
||||
|
||||
public LevelData(XElement element, float? forceDifficulty = null)
|
||||
public LevelData(XElement element, float? forceDifficulty = null, bool clampDifficultyToBiome = false)
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "");
|
||||
Difficulty = forceDifficulty ?? element.GetAttributeFloat("difficulty", 0.0f);
|
||||
Size = element.GetAttributePoint("size", new Point(1000));
|
||||
Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type);
|
||||
|
||||
@@ -131,10 +130,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading a level. Could not find level generation params with the ID \"{generationParamsId}\".");
|
||||
GenerationParams = LevelGenerationParams.LevelParams.FirstOrDefault(l => l.Type == Type);
|
||||
if (GenerationParams == null)
|
||||
{
|
||||
GenerationParams = LevelGenerationParams.LevelParams.First();
|
||||
}
|
||||
GenerationParams ??= LevelGenerationParams.LevelParams.First();
|
||||
}
|
||||
|
||||
InitialDepth = element.GetAttributeInt("initialdepth", GenerationParams.InitialDepthMin);
|
||||
@@ -147,6 +143,12 @@ namespace Barotrauma
|
||||
Biome = Biome.Prefabs.First();
|
||||
}
|
||||
|
||||
Difficulty = forceDifficulty ?? element.GetAttributeFloat("difficulty", 0.0f);
|
||||
if (clampDifficultyToBiome)
|
||||
{
|
||||
Difficulty = MathHelper.Clamp(Difficulty, Biome.MinDifficulty, Biome.AdjustedMaxDifficulty);
|
||||
}
|
||||
|
||||
string[] prefabNames = element.GetAttributeStringArray("eventhistory", Array.Empty<string>());
|
||||
EventHistory.AddRange(EventPrefab.Prefabs.Where(p => prefabNames.Any(n => p.Identifier == n)).Select(p => p.Identifier));
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, "If there are multiple level generation parameters available for a level in a given biome, their commonness determines how likely it is for one to get selected."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes, "If there are multiple level generation parameters available for a level in a given biome, their commonness determines how likely it is for one to get selected."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -3,7 +3,6 @@ using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -63,10 +62,17 @@ namespace Barotrauma
|
||||
|
||||
public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
|
||||
|
||||
public bool Visited => GameMain.GameSession?.Map?.IsVisited(this) ?? false;
|
||||
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
public int LocationTypeChangeCooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Is some mission blocking this location from changing its type?
|
||||
/// </summary>
|
||||
public bool LocationTypeChangesBlocked => availableMissions.Any(m => m.Prefab.BlockLocationTypeChanges);
|
||||
|
||||
public string BaseName { get => baseName; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
@@ -96,6 +102,7 @@ namespace Barotrauma
|
||||
public class StoreInfo
|
||||
{
|
||||
public Identifier Identifier { get; }
|
||||
public Identifier MerchantFaction { get; private set; }
|
||||
public int Balance { get; set; }
|
||||
public List<PurchasedItem> Stock { get; } = new List<PurchasedItem>();
|
||||
public List<ItemPrefab> DailySpecials { get; } = new List<ItemPrefab>();
|
||||
@@ -105,6 +112,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int PriceModifier { get; set; }
|
||||
public Location Location { get; }
|
||||
private float MaxReputationModifier => Location.StoreMaxReputationModifier;
|
||||
|
||||
private StoreInfo(Location location)
|
||||
{
|
||||
@@ -129,6 +137,7 @@ namespace Barotrauma
|
||||
public StoreInfo(Location location, XElement storeElement) : this(location)
|
||||
{
|
||||
Identifier = storeElement.GetAttributeIdentifier("identifier", "");
|
||||
MerchantFaction = storeElement.GetAttributeIdentifier(nameof(MerchantFaction), "");
|
||||
Balance = storeElement.GetAttributeInt("balance", location.StoreInitialBalance);
|
||||
PriceModifier = storeElement.GetAttributeInt("pricemodifier", 0);
|
||||
// Backwards compatibility: before introducing support for multiple stores, this value was saved as a store element attribute
|
||||
@@ -285,13 +294,14 @@ namespace Barotrauma
|
||||
{
|
||||
price = Location.DailySpecialPriceModifier * price;
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(true);
|
||||
// Adjust by current reputation
|
||||
price *= GetReputationModifier(true);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
if (Location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
|
||||
var faction = GetMerchantOrLocationFactionIdentifier();
|
||||
if (!faction.IsEmpty && GameMain.GameSession.Campaign.GetFactionAffiliation(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
|
||||
@@ -317,8 +327,8 @@ namespace Barotrauma
|
||||
{
|
||||
price = Location.RequestGoodPriceModifier * price;
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(false);
|
||||
// Adjust by location reputation
|
||||
price *= GetReputationModifier(false);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
@@ -331,6 +341,45 @@ namespace Barotrauma
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
|
||||
public void SetMerchantFaction(Identifier factionIdentifier)
|
||||
{
|
||||
MerchantFaction = factionIdentifier;
|
||||
}
|
||||
|
||||
public Identifier GetMerchantOrLocationFactionIdentifier()
|
||||
{
|
||||
return MerchantFaction.IfEmpty(Location.Faction?.Prefab.Identifier ?? Identifier.Empty);
|
||||
}
|
||||
|
||||
public float GetReputationModifier(bool buying)
|
||||
{
|
||||
var factionIdentifier = GetMerchantOrLocationFactionIdentifier();
|
||||
var reputation = GameMain.GameSession.Campaign.GetFaction(factionIdentifier)?.Reputation;
|
||||
if (reputation == null) { return 1.0f; }
|
||||
if (buying)
|
||||
{
|
||||
if (reputation.Value > 0.0f)
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f - MaxReputationModifier, reputation.Value / reputation.MaxReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f + MaxReputationModifier, reputation.Value / reputation.MinReputation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reputation.Value > 0.0f)
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f + MaxReputationModifier, reputation.Value / reputation.MaxReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f - MaxReputationModifier, reputation.Value / reputation.MinReputation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Identifier.Value;
|
||||
@@ -392,6 +441,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void SelectMission(Mission mission)
|
||||
{
|
||||
if (!SelectedMissions.Contains(mission) && mission != null)
|
||||
@@ -454,19 +505,22 @@ namespace Barotrauma
|
||||
|
||||
public bool IsGateBetweenBiomes;
|
||||
|
||||
private struct LoadedMission
|
||||
private readonly struct LoadedMission
|
||||
{
|
||||
public MissionPrefab MissionPrefab { get; }
|
||||
public int OriginLocationIndex { get; }
|
||||
public int DestinationIndex { get; }
|
||||
public bool SelectedMission { get; }
|
||||
public readonly MissionPrefab MissionPrefab;
|
||||
public readonly int TimesAttempted;
|
||||
public readonly int OriginLocationIndex;
|
||||
public readonly int DestinationIndex;
|
||||
public readonly bool SelectedMission;
|
||||
|
||||
public LoadedMission(MissionPrefab prefab, int originLocationIndex, int destinationIndex, bool selectedMission)
|
||||
public LoadedMission(XElement element)
|
||||
{
|
||||
MissionPrefab = prefab;
|
||||
OriginLocationIndex = originLocationIndex;
|
||||
DestinationIndex = destinationIndex;
|
||||
SelectedMission = selectedMission;
|
||||
var id = element.GetAttributeIdentifier("prefabid", Identifier.Empty);
|
||||
MissionPrefab = MissionPrefab.Prefabs.TryGet(id, out var prefab) ? prefab : null;
|
||||
TimesAttempted = element.GetAttributeInt("timesattempted", 0);
|
||||
OriginLocationIndex = element.GetAttributeInt("origin", -1);
|
||||
DestinationIndex = element.GetAttributeInt("destinationindex", -1);
|
||||
SelectedMission = element.GetAttributeBool("selected", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,12 +631,9 @@ namespace Barotrauma
|
||||
killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", Array.Empty<int>()).ToHashSet();
|
||||
|
||||
System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationTypeId}\"!");
|
||||
if (Type == null)
|
||||
{
|
||||
Type = LocationType.Prefabs.First();
|
||||
}
|
||||
Type ??= LocationType.Prefabs.First();
|
||||
|
||||
LevelData = new LevelData(element.Element("Level"));
|
||||
LevelData = new LevelData(element.Element("Level"), clampDifficultyToBiome: true);
|
||||
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
|
||||
@@ -661,14 +712,11 @@ namespace Barotrauma
|
||||
loadedMissions = new List<LoadedMission>();
|
||||
foreach (XElement childElement in missionsElement.GetChildElements("mission"))
|
||||
{
|
||||
var id = childElement.GetAttributeString("prefabid", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
var prefab = MissionPrefab.Prefabs.Find(p => p.Identifier == id);
|
||||
if (prefab == null) { continue; }
|
||||
var origin = childElement.GetAttributeInt("origin", -1);
|
||||
var destination = childElement.GetAttributeInt("destinationindex", -1);
|
||||
var selected = childElement.GetAttributeBool("selected", false);
|
||||
loadedMissions.Add(new LoadedMission(prefab, origin, destination, selected));
|
||||
var loadedMission = new LoadedMission(childElement);
|
||||
if (loadedMission.MissionPrefab != null)
|
||||
{
|
||||
loadedMissions.Add(loadedMission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -693,7 +741,7 @@ namespace Barotrauma
|
||||
Type = newType;
|
||||
Name = Type.NameFormats == null || !Type.NameFormats.Any() ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
|
||||
if (Type.HasOutpost)
|
||||
if (Type.HasOutpost && Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Faction == null)
|
||||
{
|
||||
@@ -734,30 +782,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return; }
|
||||
var mission = InstantiateMission(missionPrefab, connection);
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
AddMission(InstantiateMission(missionPrefab, connection));
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return; }
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
AddMission(InstantiateMission(missionPrefab));
|
||||
}
|
||||
|
||||
public Mission UnlockMissionByIdentifier(Identifier identifier)
|
||||
@@ -778,14 +810,7 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
AddMission(mission);
|
||||
return mission;
|
||||
}
|
||||
return null;
|
||||
@@ -816,14 +841,7 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
AddMission(mission);
|
||||
return mission;
|
||||
}
|
||||
else
|
||||
@@ -835,6 +853,20 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AddMission(Mission mission)
|
||||
{
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#else
|
||||
(GameMain.GameSession?.Campaign as MultiPlayerCampaign)?.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
|
||||
#endif
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, out LocationConnection connection)
|
||||
{
|
||||
if (prefab.IsAllowed(this, this))
|
||||
@@ -933,6 +965,7 @@ namespace Barotrauma
|
||||
{
|
||||
mission.OriginLocation = map.Locations[loadedMission.OriginLocationIndex];
|
||||
}
|
||||
mission.TimesAttempted = loadedMission.TimesAttempted;
|
||||
availableMissions.Add(mission);
|
||||
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
|
||||
}
|
||||
@@ -1332,33 +1365,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float GetStoreReputationModifier(bool buying)
|
||||
{
|
||||
if (Reputation == null) { return 1.0f; }
|
||||
if (buying)
|
||||
{
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetExtraSpecialSalesCount()
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
@@ -1488,6 +1494,7 @@ namespace Barotrauma
|
||||
{
|
||||
var storeElement = new XElement("store",
|
||||
new XAttribute("identifier", store.Identifier.Value),
|
||||
new XAttribute(nameof(store.MerchantFaction), store.MerchantFaction),
|
||||
new XAttribute("balance", store.Balance),
|
||||
new XAttribute("pricemodifier", store.PriceModifier));
|
||||
foreach (PurchasedItem item in store.Stock)
|
||||
@@ -1532,6 +1539,7 @@ namespace Barotrauma
|
||||
missionsElement.Add(new XElement("mission",
|
||||
new XAttribute("prefabid", mission.Prefab.Identifier),
|
||||
new XAttribute("destinationindex", destinationIndex),
|
||||
new XAttribute(nameof(Mission.TimesAttempted), mission.TimesAttempted),
|
||||
new XAttribute("origin", originIndex),
|
||||
new XAttribute("selected", selectedMissions.Contains(mission))));
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ namespace Barotrauma
|
||||
|
||||
public bool IsEnterable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can this location type be used in the random, non-campaign levels that don't take place in any specific zone
|
||||
/// </summary>
|
||||
public bool AllowInRandomLevels { get; private set; }
|
||||
|
||||
public bool UsePortraitInRandomLoadingScreens
|
||||
{
|
||||
get;
|
||||
@@ -115,6 +120,7 @@ namespace Barotrauma
|
||||
UsePortraitInRandomLoadingScreens = element.GetAttributeBool(nameof(UsePortraitInRandomLoadingScreens), true);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
AllowInRandomLevels = element.GetAttributeBool(nameof(AllowInRandomLevels), true);
|
||||
|
||||
ShowSonarMarker = element.GetAttributeBool("showsonarmarker", true);
|
||||
|
||||
@@ -263,14 +269,31 @@ namespace Barotrauma
|
||||
return names[rand.Next() % names.Length];
|
||||
}
|
||||
|
||||
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false)
|
||||
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false, Func<LocationType, bool> predicate = null)
|
||||
{
|
||||
Debug.Assert(Prefabs.Any(), "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
|
||||
|
||||
LocationType[] allowedLocationTypes =
|
||||
Prefabs.Where(lt => (!zone.HasValue || lt.CommonnessPerZone.ContainsKey(zone.Value)) && (!requireOutpost || lt.HasOutpost))
|
||||
Prefabs.Where(lt =>
|
||||
(predicate == null || predicate(lt)) && IsValid(lt))
|
||||
.OrderBy(p => p.UintIdentifier).ToArray();
|
||||
|
||||
bool IsValid(LocationType lt)
|
||||
{
|
||||
if (requireOutpost && !lt.HasOutpost) { return false; }
|
||||
if (zone.HasValue)
|
||||
{
|
||||
if (!lt.CommonnessPerZone.ContainsKey(zone.Value)) { return false; }
|
||||
}
|
||||
//if zone is not defined, this is a "random" (non-campaign) level
|
||||
//-> don't choose location types that aren't allowed in those
|
||||
else if (!lt.AllowInRandomLevels)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowedLocationTypes.Length == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using static Barotrauma.LocationTypeChange;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -58,7 +58,7 @@ namespace Barotrauma
|
||||
public Requirement(XElement element, LocationTypeChange change)
|
||||
{
|
||||
RequiredLocations = element.GetAttributeIdentifierArray("requiredlocations", element.GetAttributeIdentifierArray("requiredadjacentlocations", Array.Empty<Identifier>())).ToImmutableArray();
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 1);
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 0);
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
|
||||
RequireBeaconStation = element.GetAttributeBool("requirebeaconstation", false);
|
||||
@@ -91,37 +91,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool AnyWithinDistance(Location startLocation, int distance)
|
||||
{
|
||||
return Map.LocationOrConnectionWithinDistance(
|
||||
startLocation,
|
||||
maxDistance: distance,
|
||||
criteria: MatchesLocation,
|
||||
connectionCriteria: MatchesConnection);
|
||||
}
|
||||
|
||||
public bool MatchesLocation(Location location)
|
||||
{
|
||||
return RequiredLocations.Contains(location.Type.Identifier) && !location.IsCriticallyRadiated();
|
||||
}
|
||||
|
||||
public bool AnyWithinDistance(Location location, int maxDistance, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
public bool MatchesConnection(LocationConnection connection)
|
||||
{
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && MatchesLocation(location)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
if (RequireBeaconStation && connection.LevelData.HasBeaconStation && connection.LevelData.IsBeaconActive)
|
||||
{
|
||||
if (RequireBeaconStation && connection.LevelData.HasBeaconStation && connection.LevelData.IsBeaconActive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (RequireHuntingGrounds && connection.LevelData.HasHuntingGrounds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinDistance(otherLocation, maxDistance, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (RequireHuntingGrounds && connection.LevelData.HasHuntingGrounds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -227,8 +220,9 @@ namespace Barotrauma
|
||||
if (location.LocationTypeChangeCooldown > 0) { return 0.0f; }
|
||||
if (location.IsGateBetweenBiomes) { return 0.0f; }
|
||||
|
||||
if (DisallowedAdjacentLocations.Any() &&
|
||||
AnyWithinDistance(location, DisallowedProximity, (otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
if (DisallowedAdjacentLocations.Any() &&
|
||||
Map.LocationOrConnectionWithinDistance(location, DisallowedProximity,
|
||||
(otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -247,7 +241,6 @@ namespace Barotrauma
|
||||
probability *= requirement.Probability;
|
||||
}
|
||||
}
|
||||
|
||||
if (location.ProximityTimer.ContainsKey(requirement))
|
||||
{
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximityForProbabilityIncrease))
|
||||
@@ -266,25 +259,5 @@ namespace Barotrauma
|
||||
|
||||
return probability;
|
||||
}
|
||||
|
||||
private bool AnyWithinDistance(Location location, int maxDistance, Func<Location, bool> predicate, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && predicate(location)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinDistance(otherLocation, maxDistance, predicate, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +70,13 @@ namespace Barotrauma
|
||||
public List<Location> Locations { get; private set; }
|
||||
|
||||
private readonly List<Location> locationsDiscovered = new List<Location>();
|
||||
private readonly List<Location> outpostsVisited = new List<Location>();
|
||||
private readonly List<Location> locationsVisited = new List<Location>();
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
private bool wasLocationDiscoveryOrderTracked = true;
|
||||
private bool trackedLocationDiscoveryAndVisitOrder = true;
|
||||
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
@@ -939,6 +939,12 @@ namespace Barotrauma
|
||||
|
||||
public void MoveToNextLocation()
|
||||
{
|
||||
if (SelectedLocation == null && Level.Loaded?.EndLocation != null)
|
||||
{
|
||||
//force the location at the end of the level to be selected, even if it's been deselect on the map
|
||||
//(e.g. due to returning to an empty location the beginning of the level during the round)
|
||||
SelectLocation(Level.Loaded.EndLocation);
|
||||
}
|
||||
if (SelectedConnection == null)
|
||||
{
|
||||
if (!endLocations.Contains(CurrentLocation))
|
||||
@@ -1043,12 +1049,24 @@ namespace Barotrauma
|
||||
Location prevSelected = SelectedLocation;
|
||||
SelectedLocation = Locations[index];
|
||||
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
|
||||
SelectedConnection =
|
||||
Connections.Find(c => c.Locations.Contains(currentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
|
||||
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
if (currentDisplayLocation == SelectedLocation)
|
||||
{
|
||||
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedConnection =
|
||||
Connections.Find(c => c.Locations.Contains(currentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
|
||||
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
}
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
string errorMsg =
|
||||
$"A locked connection was selected ({SelectedConnection.Locations[0].Name} -> {SelectedConnection.Locations[1].Name}." +
|
||||
$" Current location: {CurrentLocation}, current display location: {currentDisplayLocation}).\n"
|
||||
+ Environment.StackTrace.CleanupStackTrace();
|
||||
GameAnalyticsManager.AddErrorEventOnce("MapSelectLocation:LockedConnectionSelected", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
}
|
||||
if (prevSelected != SelectedLocation)
|
||||
{
|
||||
@@ -1266,51 +1284,6 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public int DistanceToClosestLocationWithOutpost(Location startingLocation, out Location endingLocation)
|
||||
{
|
||||
if (startingLocation.Type.HasOutpost)
|
||||
{
|
||||
endingLocation = startingLocation;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iterations = 0;
|
||||
int distance = 0;
|
||||
endingLocation = null;
|
||||
|
||||
List<Location> testedLocations = new List<Location>();
|
||||
List<Location> locationsToTest = new List<Location> { startingLocation };
|
||||
|
||||
while (endingLocation == null && iterations < 100)
|
||||
{
|
||||
List<Location> nextTestingBatch = new List<Location>();
|
||||
for (int i = 0; i < locationsToTest.Count; i++)
|
||||
{
|
||||
Location testLocation = locationsToTest[i];
|
||||
for (int j = 0; j < testLocation.Connections.Count; j++)
|
||||
{
|
||||
Location potentialOutpost = testLocation.Connections[j].OtherLocation(testLocation);
|
||||
if (potentialOutpost.Type.HasOutpost)
|
||||
{
|
||||
distance = iterations + 1;
|
||||
endingLocation = potentialOutpost;
|
||||
}
|
||||
else if (!testedLocations.Contains(potentialOutpost))
|
||||
{
|
||||
nextTestingBatch.Add(potentialOutpost);
|
||||
}
|
||||
}
|
||||
|
||||
testedLocations.Add(testLocation);
|
||||
}
|
||||
|
||||
locationsToTest = nextTestingBatch;
|
||||
iterations++;
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
private bool ChangeLocationType(CampaignMode campaign, Location location, LocationTypeChange change)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
@@ -1321,6 +1294,8 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
if (location.LocationTypeChangesBlocked) { return false; }
|
||||
|
||||
if (newType.OutpostTeam != location.Type.OutpostTeam ||
|
||||
newType.HasOutpost != location.Type.HasOutpost)
|
||||
{
|
||||
@@ -1338,6 +1313,50 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool LocationOrConnectionWithinDistance(Location startLocation, int maxDistance, Func<Location, bool> criteria, Func<LocationConnection, bool> connectionCriteria = null)
|
||||
{
|
||||
return GetDistanceToClosestLocationOrConnection(startLocation, maxDistance, criteria, connectionCriteria) <= maxDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the shortest distance from the start location to another location that satisfies the specified criteria.
|
||||
/// </summary>
|
||||
/// <returns>The distance to a matching location, or int.MaxValue if none are found.</returns>
|
||||
public static int GetDistanceToClosestLocationOrConnection(Location startLocation, int maxDistance, Func<Location, bool> criteria, Func<LocationConnection, bool> connectionCriteria = null)
|
||||
{
|
||||
int distance = 0;
|
||||
var locationsToTest = new List<Location>() { startLocation };
|
||||
var nextBatchToTest = new HashSet<Location>();
|
||||
var checkedLocations = new HashSet<Location>();
|
||||
while (locationsToTest.Any())
|
||||
{
|
||||
foreach (var location in locationsToTest)
|
||||
{
|
||||
checkedLocations.Add(location);
|
||||
if (criteria(location)) { return distance; }
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
if (connectionCriteria != null && connectionCriteria(connection))
|
||||
{
|
||||
return distance;
|
||||
}
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
nextBatchToTest.Add(otherLocation);
|
||||
}
|
||||
}
|
||||
if (distance > maxDistance) { return int.MaxValue; }
|
||||
}
|
||||
distance++;
|
||||
locationsToTest.Clear();
|
||||
locationsToTest.AddRange(nextBatchToTest);
|
||||
nextBatchToTest.Clear();
|
||||
}
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
|
||||
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change);
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
@@ -1356,29 +1375,38 @@ namespace Barotrauma
|
||||
public void Visit(Location location)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (!location.HasOutpost()) { return; }
|
||||
if (outpostsVisited.Contains(location)) { return; }
|
||||
outpostsVisited.Add(location);
|
||||
if (locationsVisited.Contains(location)) { return; }
|
||||
locationsVisited.Add(location);
|
||||
RemoveFogOfWarProjSpecific(location);
|
||||
}
|
||||
|
||||
public void ClearLocationHistory()
|
||||
{
|
||||
locationsDiscovered.Clear();
|
||||
outpostsVisited.Clear();
|
||||
locationsVisited.Clear();
|
||||
}
|
||||
|
||||
public int? GetDiscoveryIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (!trackedLocationDiscoveryAndVisitOrder) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return locationsDiscovered.IndexOf(location);
|
||||
}
|
||||
|
||||
public int? GetVisitIndex(Location location)
|
||||
public int? GetVisitIndex(Location location, bool includeLocationsWithoutOutpost = false)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (!trackedLocationDiscoveryAndVisitOrder) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return outpostsVisited.IndexOf(location);
|
||||
int index = locationsVisited.IndexOf(location);
|
||||
if (includeLocationsWithoutOutpost) { return index; }
|
||||
int noOutpostLocations = 0;
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
if (locationsVisited[i] is not Location l) { continue; }
|
||||
if (l.HasOutpost()) { continue; }
|
||||
noOutpostLocations++;
|
||||
}
|
||||
return index - noOutpostLocations;
|
||||
}
|
||||
|
||||
public bool IsDiscovered(Location location)
|
||||
@@ -1387,6 +1415,14 @@ namespace Barotrauma
|
||||
return locationsDiscovered.Contains(location);
|
||||
}
|
||||
|
||||
public bool IsVisited(Location location)
|
||||
{
|
||||
if (location is null) { return false; }
|
||||
return locationsVisited.Contains(location);
|
||||
}
|
||||
|
||||
partial void RemoveFogOfWarProjSpecific(Location location);
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
@@ -1431,11 +1467,13 @@ namespace Barotrauma
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
|
||||
// Backwards compatibility
|
||||
// Backwards compatibility: if the discovery status is defined in the location element,
|
||||
// the game was saved using when the discovery order still wasn't being tracked
|
||||
if (subElement.GetAttributeBool("discovered", false))
|
||||
{
|
||||
Discover(location);
|
||||
wasLocationDiscoveryOrderTracked = false;
|
||||
Visit(location);
|
||||
trackedLocationDiscoveryAndVisitOrder = false;
|
||||
}
|
||||
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
@@ -1472,32 +1510,45 @@ namespace Barotrauma
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
case "discovered":
|
||||
bool trackedVisitedEmptyLocations = subElement.GetAttributeBool("trackedvisitedemptylocations", false);
|
||||
foreach (var childElement in subElement.GetChildElements("location"))
|
||||
{
|
||||
int index = childElement.GetAttributeInt("i", -1);
|
||||
if (index < 0) { continue; }
|
||||
if (Locations[index] is not Location l) { continue; }
|
||||
Discover(l);
|
||||
if (GetLocation(childElement) is Location l)
|
||||
{
|
||||
Discover(l);
|
||||
if (!trackedVisitedEmptyLocations)
|
||||
{
|
||||
if (!l.HasOutpost())
|
||||
{
|
||||
Visit(l);
|
||||
}
|
||||
trackedLocationDiscoveryAndVisitOrder = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "visited":
|
||||
foreach (var childElement in subElement.GetChildElements("location"))
|
||||
{
|
||||
int index = childElement.GetAttributeInt("i", -1);
|
||||
if (index < 0) { continue; }
|
||||
if (Locations[index] is not Location l) { continue; }
|
||||
Visit(l);
|
||||
if (GetLocation(childElement) is Location l)
|
||||
{
|
||||
Visit(l);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Location GetLocation(XElement element)
|
||||
{
|
||||
int index = element.GetAttributeInt("i", -1);
|
||||
if (index < 0) { return null; }
|
||||
return Locations[index];
|
||||
}
|
||||
}
|
||||
|
||||
void Discover(Location location)
|
||||
{
|
||||
this.Discover(location, checkTalents: false);
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
@@ -1509,9 +1560,6 @@ namespace Barotrauma
|
||||
location?.InstantiateLoadedMissions(this);
|
||||
}
|
||||
|
||||
#if RELEASE
|
||||
TODO: MAKE SURE THE VERSION NUMBER BELOW IS CORRECT FOR THE FULL RELEASE (OR WHICHEVER UPDATE WE ADD THE FACTIONS IN)
|
||||
#endif
|
||||
//backwards compatibility:
|
||||
//if the save is from a version prior to the addition of faction-specific outposts, assign factions
|
||||
if (version < new Version(1, 0) && Locations.None(l => l.Faction != null || l.SecondaryFaction != null))
|
||||
@@ -1599,7 +1647,8 @@ namespace Barotrauma
|
||||
|
||||
if (locationsDiscovered.Any())
|
||||
{
|
||||
var discoveryElement = new XElement("discovered");
|
||||
var discoveryElement = new XElement("discovered",
|
||||
new XAttribute("trackedvisitedemptylocations", true));
|
||||
foreach (Location location in locationsDiscovered)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
@@ -1609,10 +1658,10 @@ namespace Barotrauma
|
||||
mapElement.Add(discoveryElement);
|
||||
}
|
||||
|
||||
if (outpostsVisited.Any())
|
||||
if (locationsVisited.Any())
|
||||
{
|
||||
var visitElement = new XElement("visited");
|
||||
foreach (Location location in outpostsVisited)
|
||||
foreach (Location location in locationsVisited)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
|
||||
@@ -59,7 +59,24 @@ namespace Barotrauma
|
||||
//the position and dimensions of the entity
|
||||
protected Rectangle rect;
|
||||
|
||||
public bool ExternalHighlight = false;
|
||||
protected static readonly HashSet<MapEntity> highlightedEntities = new HashSet<MapEntity>();
|
||||
|
||||
public static IEnumerable<MapEntity> HighlightedEntities => highlightedEntities;
|
||||
|
||||
|
||||
private bool externalHighlight = false;
|
||||
public bool ExternalHighlight
|
||||
{
|
||||
get { return externalHighlight; }
|
||||
set
|
||||
{
|
||||
if (value != externalHighlight)
|
||||
{
|
||||
externalHighlight = value;
|
||||
CheckIsHighlighted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//is the mouse inside the rect
|
||||
private bool isHighlighted;
|
||||
@@ -67,7 +84,14 @@ namespace Barotrauma
|
||||
public bool IsHighlighted
|
||||
{
|
||||
get { return isHighlighted || ExternalHighlight; }
|
||||
set { isHighlighted = value; }
|
||||
set
|
||||
{
|
||||
if (value != IsHighlighted)
|
||||
{
|
||||
isHighlighted = value;
|
||||
CheckIsHighlighted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Rectangle Rect
|
||||
@@ -362,6 +386,30 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void CheckIsHighlighted()
|
||||
{
|
||||
if (IsHighlighted || ExternalHighlight)
|
||||
{
|
||||
highlightedEntities.Add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
highlightedEntities.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly List<MapEntity> tempHighlightedEntities = new List<MapEntity>();
|
||||
public static void ClearHighlightedEntities()
|
||||
{
|
||||
tempHighlightedEntities.Clear();
|
||||
tempHighlightedEntities.AddRange(highlightedEntities);
|
||||
foreach (var entity in tempHighlightedEntities)
|
||||
{
|
||||
entity.IsHighlighted = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public abstract MapEntity Clone();
|
||||
|
||||
public static List<MapEntity> Clone(List<MapEntity> entitiesToClone)
|
||||
@@ -649,6 +697,9 @@ namespace Barotrauma
|
||||
List<MapEntity> entities = new List<MapEntity>();
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.ThrowIfStartRoundCancellationRequested();
|
||||
#endif
|
||||
string typeName = element.Name.ToString();
|
||||
|
||||
Type t;
|
||||
|
||||
@@ -174,6 +174,9 @@ namespace Barotrauma
|
||||
|
||||
public abstract Sprite Sprite { get; }
|
||||
|
||||
public virtual bool CanSpriteFlipX { get; } = false;
|
||||
public virtual bool CanSpriteFlipY { get; } = false;
|
||||
|
||||
public abstract string OriginalName { get; }
|
||||
|
||||
public abstract LocalizedString Name { get; }
|
||||
|
||||
@@ -150,10 +150,13 @@ namespace Barotrauma
|
||||
|
||||
selectedModules.Clear();
|
||||
//select which module types the outpost should consist of
|
||||
List<Identifier> pendingModuleFlags =
|
||||
onlyEntrance ?
|
||||
(generationParams.ModuleCounts.FirstOrDefault()?.Identifier.ToEnumerable() ?? Enumerable.Empty<Identifier>()).ToList() :
|
||||
SelectModules(outpostModules, location, generationParams);
|
||||
List<Identifier> pendingModuleFlags = new List<Identifier>();
|
||||
if (generationParams.ModuleCounts.Any())
|
||||
{
|
||||
pendingModuleFlags = onlyEntrance ?
|
||||
generationParams.ModuleCounts[0].Identifier.ToEnumerable().ToList() :
|
||||
SelectModules(outpostModules, location, generationParams);
|
||||
}
|
||||
|
||||
foreach (Identifier flag in pendingModuleFlags)
|
||||
{
|
||||
@@ -1446,6 +1449,16 @@ namespace Barotrauma
|
||||
{
|
||||
me.HiddenInGame =
|
||||
location?.Faction?.Prefab != FactionPrefab.Prefabs[layerAsIdentifier];
|
||||
#if CLIENT
|
||||
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
|
||||
if (me.HiddenInGame && me is Item item)
|
||||
{
|
||||
foreach (var lightComponent in item.GetComponents<LightComponent>())
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly ContentXElement ConfigElement;
|
||||
|
||||
public readonly bool CanSpriteFlipX;
|
||||
public readonly bool CanSpriteFlipY;
|
||||
public override bool CanSpriteFlipX { get; }
|
||||
public override bool CanSpriteFlipY { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If null, the orientation is determined automatically based on the dimensions of the structure instances
|
||||
|
||||
@@ -1379,19 +1379,6 @@ namespace Barotrauma
|
||||
Info = new SubmarineInfo(info);
|
||||
|
||||
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
|
||||
|
||||
//place the sub above the top of the level
|
||||
HiddenSubPosition = HiddenSubStartPosition;
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null)
|
||||
{
|
||||
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
||||
}
|
||||
|
||||
foreach (Submarine sub in loaded)
|
||||
{
|
||||
HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f);
|
||||
}
|
||||
|
||||
IdOffset = IdRemap.DetermineNewOffset();
|
||||
|
||||
List<MapEntity> newEntities = new List<MapEntity>();
|
||||
@@ -1419,7 +1406,6 @@ namespace Barotrauma
|
||||
|
||||
Vector2 center = Vector2.Zero;
|
||||
var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this);
|
||||
|
||||
if (matchingHulls.Any())
|
||||
{
|
||||
Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
|
||||
@@ -1441,6 +1427,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
subBody = new SubmarineBody(this, showErrorMessages);
|
||||
|
||||
//place the sub above the top of the level
|
||||
HiddenSubPosition = HiddenSubStartPosition;
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null)
|
||||
{
|
||||
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
||||
}
|
||||
foreach (Submarine sub in loaded)
|
||||
{
|
||||
HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f);
|
||||
}
|
||||
Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition);
|
||||
subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f);
|
||||
|
||||
@@ -1745,57 +1742,66 @@ namespace Barotrauma
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
if (Unloading)
|
||||
{
|
||||
DebugConsole.AddWarning($"Called {nameof(Submarine.Unload)} when already unloading.");
|
||||
return;
|
||||
}
|
||||
|
||||
Unloading = true;
|
||||
try
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
RoundSound.RemoveAllRoundSounds();
|
||||
GameMain.LightManager?.ClearLights();
|
||||
RoundSound.RemoveAllRoundSounds();
|
||||
GameMain.LightManager?.ClearLights();
|
||||
#endif
|
||||
|
||||
var _loaded = new List<Submarine>(loaded);
|
||||
foreach (Submarine sub in _loaded)
|
||||
{
|
||||
sub.Remove();
|
||||
}
|
||||
|
||||
loaded.Clear();
|
||||
|
||||
visibleEntities = null;
|
||||
|
||||
if (GameMain.GameScreen.Cam != null) { GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; }
|
||||
|
||||
RemoveAll();
|
||||
|
||||
if (Item.ItemList.Count > 0)
|
||||
{
|
||||
List<Item> items = new List<Item>(Item.ItemList);
|
||||
foreach (Item item in items)
|
||||
var _loaded = new List<Submarine>(loaded);
|
||||
foreach (Submarine sub in _loaded)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while unloading submarines - item \"" + item.Name + "\" (ID:" + item.ID + ") not removed");
|
||||
try
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while removing \"" + item.Name + "\"!", e);
|
||||
}
|
||||
sub.Remove();
|
||||
}
|
||||
Item.ItemList.Clear();
|
||||
|
||||
loaded.Clear();
|
||||
|
||||
visibleEntities = null;
|
||||
|
||||
if (GameMain.GameScreen.Cam != null) { GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; }
|
||||
|
||||
RemoveAll();
|
||||
|
||||
if (Item.ItemList.Count > 0)
|
||||
{
|
||||
List<Item> items = new List<Item>(Item.ItemList);
|
||||
foreach (Item item in items)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while unloading submarines - item \"" + item.Name + "\" (ID:" + item.ID + ") not removed");
|
||||
try
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while removing \"" + item.Name + "\"!", e);
|
||||
}
|
||||
}
|
||||
Item.ItemList.Clear();
|
||||
}
|
||||
|
||||
Ragdoll.RemoveAll();
|
||||
PhysicsBody.RemoveAll();
|
||||
GameMain.World = null;
|
||||
|
||||
Powered.Grids.Clear();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
Unloading = false;
|
||||
}
|
||||
|
||||
Ragdoll.RemoveAll();
|
||||
|
||||
PhysicsBody.RemoveAll();
|
||||
|
||||
GameMain.World?.Clear();
|
||||
GameMain.World = null;
|
||||
|
||||
Powered.Grids.Clear();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
Unloading = false;
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
@@ -1862,18 +1868,18 @@ namespace Barotrauma
|
||||
{
|
||||
if (node == null || node.Waypoint == null) { continue; }
|
||||
var wp = node.Waypoint;
|
||||
if (wp.isObstructed) { continue; }
|
||||
if (wp.IsObstructed) { continue; }
|
||||
foreach (var connection in node.connections)
|
||||
{
|
||||
var connectedWp = connection.Waypoint;
|
||||
if (connectedWp.isObstructed) { continue; }
|
||||
if (connectedWp.IsObstructed) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition);
|
||||
var body = PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false);
|
||||
if (body != null)
|
||||
{
|
||||
connectedWp.isObstructed = true;
|
||||
wp.isObstructed = true;
|
||||
connectedWp.IsObstructed = true;
|
||||
wp.IsObstructed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1892,11 +1898,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (node == null || node.Waypoint == null) { continue; }
|
||||
var wp = node.Waypoint;
|
||||
if (wp.isObstructed) { continue; }
|
||||
if (wp.IsObstructed) { continue; }
|
||||
foreach (var connection in node.connections)
|
||||
{
|
||||
var connectedWp = connection.Waypoint;
|
||||
if (connectedWp.isObstructed || connectedWp.Ladders != null) { continue; }
|
||||
if (connectedWp.IsObstructed || connectedWp.Ladders != null) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
|
||||
var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true);
|
||||
@@ -1904,8 +1910,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (body.UserData is Structure wall && !wall.IsPlatform || body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
|
||||
{
|
||||
connectedWp.isObstructed = true;
|
||||
wp.isObstructed = true;
|
||||
connectedWp.IsObstructed = true;
|
||||
wp.IsObstructed = true;
|
||||
if (!obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
|
||||
{
|
||||
nodes = new HashSet<PathNode>();
|
||||
@@ -1927,7 +1933,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
|
||||
{
|
||||
nodes.ForEach(n => n.Waypoint.isObstructed = false);
|
||||
nodes.ForEach(n => n.Waypoint.IsObstructed = false);
|
||||
nodes.Clear();
|
||||
obstructedNodes.Remove(otherSub);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private float depthDamageTimer = 10.0f;
|
||||
private float damageSoundTimer = 10.0f;
|
||||
|
||||
private readonly Submarine submarine;
|
||||
|
||||
@@ -507,36 +508,58 @@ namespace Barotrauma
|
||||
if (Level.Loaded == null) { return; }
|
||||
|
||||
//camera shake and sounds start playing 500 meters before crush depth
|
||||
float depthEffectThreshold = 500.0f;
|
||||
if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth - depthEffectThreshold || Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth - depthEffectThreshold)
|
||||
const float CosmeticEffectThreshold = -500.0f;
|
||||
//breaches won't get any more severe 500 meters below crush depth
|
||||
const float MaxEffectThreshold = 500.0f;
|
||||
const float MinWallDamageProbability = 0.1f;
|
||||
const float MaxWallDamageProbability = 1.0f;
|
||||
const float MinWallDamage = 50f;
|
||||
const float MaxWallDamage = 500.0f;
|
||||
const float MinCameraShake = 5f;
|
||||
const float MaxCameraShake = 50.0f;
|
||||
|
||||
if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth + CosmeticEffectThreshold || Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth + CosmeticEffectThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
depthDamageTimer -= deltaTime;
|
||||
if (depthDamageTimer > 0.0f) { return; }
|
||||
|
||||
#if CLIENT
|
||||
SoundPlayer.PlayDamageSound("pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f);
|
||||
#endif
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
damageSoundTimer -= deltaTime;
|
||||
if (damageSoundTimer <= 0.0f)
|
||||
{
|
||||
if (wall.Submarine != submarine) { continue; }
|
||||
|
||||
float wallCrushDepth = wall.CrushDepth;
|
||||
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
|
||||
if (pastCrushDepth > 0)
|
||||
{
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
|
||||
}
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Clamp(pastCrushDepth * 0.001f, 1.0f, 50.0f));
|
||||
}
|
||||
#if CLIENT
|
||||
SoundPlayer.PlayDamageSound("pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f);
|
||||
#endif
|
||||
damageSoundTimer = Rand.Range(5.0f, 10.0f);
|
||||
}
|
||||
|
||||
depthDamageTimer = 10.0f;
|
||||
depthDamageTimer -= deltaTime;
|
||||
if (depthDamageTimer <= 0.0f)
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != submarine) { continue; }
|
||||
|
||||
float wallCrushDepth = wall.CrushDepth;
|
||||
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
|
||||
float pastCrushDepthRatio = Math.Clamp(pastCrushDepth / MaxEffectThreshold, 0.0f, 1.0f);
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f) > MathHelper.Lerp(MinWallDamageProbability, MaxWallDamageProbability, pastCrushDepthRatio)) { continue; }
|
||||
|
||||
float damage = MathHelper.Lerp(MinWallDamage, MaxWallDamage, pastCrushDepthRatio);
|
||||
if (pastCrushDepth > 0)
|
||||
{
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, damage, levelWallDamage: 0.0f);
|
||||
#if CLIENT
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", Rand.Range(0.0f, 100.0f), wall.WorldPosition, 2000.0f);
|
||||
#endif
|
||||
}
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Lerp(MinCameraShake, MaxCameraShake, pastCrushDepthRatio));
|
||||
}
|
||||
}
|
||||
depthDamageTimer = Rand.Range(5.0f, 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void FlipX()
|
||||
|
||||
@@ -479,7 +479,6 @@ namespace Barotrauma
|
||||
hashTask = new Task(() =>
|
||||
{
|
||||
hash = Md5Hash.CalculateForString(doc.ToString(), Md5Hash.StringHashOptions.IgnoreWhitespace);
|
||||
Md5Hash.Cache.Add(FilePath, hash, DateTime.UtcNow);
|
||||
});
|
||||
hashTask.Start();
|
||||
}
|
||||
@@ -532,11 +531,15 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Calculated from <see cref="SubmarineElement"/>. Can be used when the sub hasn't been loaded and we can't access <see cref="Submarine.RealWorldCrushDepth"/>.
|
||||
/// </summary>
|
||||
public float GetRealWorldCrushDepth()
|
||||
public bool IsCrushDepthDefinedInStructures(out float realWorldCrushDepth)
|
||||
{
|
||||
if (SubmarineElement == null) { return Level.DefaultRealWorldCrushDepth; }
|
||||
if (SubmarineElement == null)
|
||||
{
|
||||
realWorldCrushDepth = Level.DefaultRealWorldCrushDepth;
|
||||
return false;
|
||||
}
|
||||
bool structureCrushDepthsDefined = false;
|
||||
float realWorldCrushDepth = float.PositiveInfinity;
|
||||
realWorldCrushDepth = float.PositiveInfinity;
|
||||
foreach (var structureElement in SubmarineElement.GetChildElements("structure"))
|
||||
{
|
||||
string name = structureElement.Attribute("name")?.Value ?? "";
|
||||
@@ -554,7 +557,7 @@ namespace Barotrauma
|
||||
{
|
||||
realWorldCrushDepth = Level.DefaultRealWorldCrushDepth;
|
||||
}
|
||||
return realWorldCrushDepth;
|
||||
return structureCrushDepthsDefined;
|
||||
}
|
||||
public void AddOutpostNPCIdentifierOrTag(Character npc, Identifier idOrTag)
|
||||
{
|
||||
@@ -595,7 +598,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SaveUtil.CompressStringToFile(filePath, doc.ToString());
|
||||
Md5Hash.Cache.Remove(filePath);
|
||||
}
|
||||
|
||||
public static void AddToSavedSubs(SubmarineInfo subInfo)
|
||||
@@ -776,7 +778,7 @@ namespace Barotrauma
|
||||
|
||||
float price = Price;
|
||||
|
||||
if (location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(faction, characterList) is FactionAffiliation.Positive)
|
||||
if (location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
price *= 1f - characterList.Max(static c => c.GetStatValue(StatTypes.ShipyardBuyMultiplierAffiliated));
|
||||
}
|
||||
|
||||
@@ -29,7 +29,32 @@ namespace Barotrauma
|
||||
|
||||
private HashSet<Identifier> tags;
|
||||
|
||||
public bool isObstructed;
|
||||
public bool IsObstructed;
|
||||
|
||||
public bool IsInWater => CurrentHull == null || CurrentHull.Surface > Position.Y;
|
||||
|
||||
// Waypoints linked to doors are traversable, unless they are obstructed, because we filter them out in the setter of Gap.Open.
|
||||
// The only way to add the open gaps should be by calling OnGapStateSchanged.
|
||||
public bool IsTraversable => !IsObstructed && (openGaps == null || openGaps.Count == 0 || IsInWater);
|
||||
|
||||
private HashSet<Gap> openGaps;
|
||||
/// <summary>
|
||||
/// Only called by a Gap when the state changes.
|
||||
/// So in practice used like an event callback, although technically just a method
|
||||
/// (It would be cleaner to use an actual event in Gap.cs, but event registering and unregistering might cause an extra hassle)
|
||||
/// </summary>
|
||||
public void OnGapStateChanged(bool open, Gap gap)
|
||||
{
|
||||
openGaps ??= new HashSet<Gap>();
|
||||
if (open)
|
||||
{
|
||||
openGaps.Add(gap);
|
||||
}
|
||||
else
|
||||
{
|
||||
openGaps.Remove(gap);
|
||||
}
|
||||
}
|
||||
|
||||
private ushort gapId;
|
||||
public Gap ConnectedGap
|
||||
@@ -989,6 +1014,12 @@ namespace Barotrauma
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (Submarine == null)
|
||||
{
|
||||
// Don't try to connect waypoints that are not linked to any submarines to hulls, stairs, gaps etc.
|
||||
// Used to cause weird pathfinding errors on some outpost modules, because the waypoints of the main path or side path got linked to a hull in the outpost.
|
||||
return;
|
||||
}
|
||||
InitializeLinks();
|
||||
FindHull();
|
||||
FindStairs();
|
||||
|
||||
Reference in New Issue
Block a user