Faction Test 100.4.0.0
This commit is contained in:
@@ -1026,7 +1026,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
branch.DamageVisualizationTimer = 1.0f;
|
||||
}
|
||||
|
||||
if (branch.IsRootGrowth && root != null && root.Health > 0.0f) { return; }
|
||||
if (branch.IsRootGrowth && root is { Health: > 0.0f }) { return; }
|
||||
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
@@ -1035,7 +1035,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
{
|
||||
// damage is handled server side
|
||||
if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -1059,6 +1059,11 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
if (type == AttackType.Fire)
|
||||
{
|
||||
if (attacker is not null)
|
||||
{
|
||||
damage *= 1f + attacker.GetStatValue(StatTypes.BallastFloraDamageMultiplier);
|
||||
}
|
||||
|
||||
if (IsInWater(branch))
|
||||
{
|
||||
damage *= 1f - SubmergedWaterResistance;
|
||||
@@ -1066,7 +1071,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
if (defenseCooldown <= 0)
|
||||
{
|
||||
if (!(StateMachine.State is DefendWithPumpState))
|
||||
if (StateMachine.State is not DefendWithPumpState)
|
||||
{
|
||||
StateMachine.EnterState(new DefendWithPumpState(branch, ClaimedTargets, attacker));
|
||||
defenseCooldown = 180f;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -131,17 +130,23 @@ namespace Barotrauma
|
||||
if (damageSource is Item sourceItem)
|
||||
{
|
||||
var launcher = sourceItem.GetComponent<Projectile>()?.Launcher;
|
||||
displayRange *=
|
||||
1.0f
|
||||
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius)
|
||||
displayRange *=
|
||||
1.0f
|
||||
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius)
|
||||
+ (launcher?.GetQualityModifier(Quality.StatType.ExplosionRadius) ?? 0);
|
||||
Attack.DamageMultiplier *=
|
||||
1.0f
|
||||
Attack.DamageMultiplier *=
|
||||
1.0f
|
||||
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage)
|
||||
+ (launcher?.GetQualityModifier(Quality.StatType.ExplosionDamage) ?? 0);
|
||||
Attack.SourceItem ??= sourceItem;
|
||||
}
|
||||
|
||||
if (attacker is not null)
|
||||
{
|
||||
displayRange *= 1f + attacker.GetStatValue(StatTypes.ExplosionRadiusMultiplier);
|
||||
Attack.DamageMultiplier *= 1f + attacker.GetStatValue(StatTypes.ExplosionDamageMultiplier);
|
||||
}
|
||||
|
||||
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f);
|
||||
@@ -151,6 +156,10 @@ namespace Barotrauma
|
||||
Color flashColor = Color.Lerp(Color.Transparent, screenColor, Math.Max((screenColorRange - cameraDist) / screenColorRange, 0.0f));
|
||||
Screen.Selected.ColorFade(flashColor, Color.Transparent, screenColorDuration);
|
||||
}
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
item.GetComponent<Sonar>()?.RegisterExplosion(this, worldPosition);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (displayRange < 0.1f) { return; }
|
||||
@@ -171,13 +180,13 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
|
||||
if (distSqr > displayRangeSqr) continue;
|
||||
if (distSqr > displayRangeSqr) { continue; }
|
||||
|
||||
float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange;
|
||||
|
||||
//damage repairable power-consuming items
|
||||
var powered = item.GetComponent<Powered>();
|
||||
if (powered == null || !powered.VulnerableToEMP) continue;
|
||||
if (powered == null || !powered.VulnerableToEMP) { continue; }
|
||||
if (item.Repairables.Any())
|
||||
{
|
||||
item.Condition -= item.MaxCondition * EmpStrength * distFactor;
|
||||
@@ -187,7 +196,7 @@ namespace Barotrauma
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
if (powerContainer != null)
|
||||
{
|
||||
powerContainer.Charge -= powerContainer.Capacity * EmpStrength * distFactor;
|
||||
powerContainer.Charge -= powerContainer.GetCapacity() * EmpStrength * distFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +207,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
|
||||
if (distSqr > displayRangeSqr) continue;
|
||||
if (distSqr > displayRangeSqr) { continue; }
|
||||
|
||||
float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange;
|
||||
//repair repairable items
|
||||
@@ -355,25 +364,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AbilityAttackData attackData = new AbilityAttackData(Attack, c, attacker);
|
||||
if (attackData.Afflictions != null)
|
||||
if (attack.Afflictions.Any() || attack.Stun > 0.0f)
|
||||
{
|
||||
modifiedAfflictions.AddRange(attackData.Afflictions);
|
||||
}
|
||||
AbilityAttackData attackData = new AbilityAttackData(Attack, c, attacker);
|
||||
if (attackData.Afflictions != null)
|
||||
{
|
||||
modifiedAfflictions.AddRange(attackData.Afflictions);
|
||||
}
|
||||
|
||||
//use a position slightly from the limb's position towards the explosion
|
||||
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
|
||||
Vector2 dir = worldPosition - limb.WorldPosition;
|
||||
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier * attackData.DamageMultiplier);
|
||||
damages.Add(limb, attackResult.Damage);
|
||||
//use a position slightly from the limb's position towards the explosion
|
||||
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
|
||||
Vector2 dir = worldPosition - limb.WorldPosition;
|
||||
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier * attackData.DamageMultiplier);
|
||||
damages.Add(limb, attackResult.Damage);
|
||||
}
|
||||
|
||||
if (attack.StatusEffects != null && attack.StatusEffects.Any())
|
||||
{
|
||||
attack.SetUser(attacker);
|
||||
var statusEffectTargets = new List<ISerializableEntity>() { c, limb };
|
||||
var statusEffectTargets = new List<ISerializableEntity>();
|
||||
foreach (StatusEffect statusEffect in attack.StatusEffects)
|
||||
{
|
||||
statusEffectTargets.Clear();
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) { statusEffectTargets.Add(c); }
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb)) { statusEffectTargets.Add(limb); }
|
||||
statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets);
|
||||
statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets);
|
||||
statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets);
|
||||
@@ -430,7 +445,7 @@ namespace Barotrauma
|
||||
damagedStructureList.Clear();
|
||||
foreach (MapEntity entity in MapEntity.mapEntityList)
|
||||
{
|
||||
if (!(entity is Structure structure)) { continue; }
|
||||
if (entity is not Structure structure) { continue; }
|
||||
if (ignoredSubmarines != null && entity.Submarine != null && ignoredSubmarines.Contains(entity.Submarine)) { continue; }
|
||||
|
||||
if (structure.HasBody &&
|
||||
@@ -479,7 +494,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
|
||||
if (Level.Loaded.ExtraWalls[i] is not DestructibleLevelWall destructibleWall) { continue; }
|
||||
foreach (var cell in destructibleWall.Cells)
|
||||
{
|
||||
if (cell.IsPointInside(worldPosition))
|
||||
@@ -502,7 +517,7 @@ namespace Barotrauma
|
||||
return damagedStructures;
|
||||
}
|
||||
|
||||
public void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
|
||||
public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
|
||||
{
|
||||
List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -11,13 +14,24 @@ namespace Barotrauma
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly bool IsEndBiome;
|
||||
public readonly int EndBiomeLocationCount;
|
||||
|
||||
public readonly float MinDifficulty;
|
||||
private readonly float maxDifficulty;
|
||||
public float ActualMaxDifficulty => maxDifficulty;
|
||||
public float AdjustedMaxDifficulty => maxDifficulty - 0.1f;
|
||||
|
||||
|
||||
public readonly ImmutableHashSet<int> AllowedZones;
|
||||
|
||||
private readonly SubmarineAvailability? submarineAvailability;
|
||||
private readonly ImmutableHashSet<SubmarineAvailability> submarineAvailabilityOverrides;
|
||||
|
||||
public readonly record struct SubmarineAvailability(
|
||||
Identifier LocationType,
|
||||
SubmarineClass Class = SubmarineClass.Undefined,
|
||||
int MaxTier = 0);
|
||||
|
||||
public Biome(ContentXElement element, LevelGenerationParametersFile file) : base(file, ParseIdentifier(element))
|
||||
{
|
||||
OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
|
||||
@@ -31,9 +45,31 @@ namespace Barotrauma
|
||||
element.GetAttributeString("description", ""));
|
||||
|
||||
IsEndBiome = element.GetAttributeBool("endbiome", false);
|
||||
EndBiomeLocationCount = Math.Max(1, element.GetAttributeInt("endbiomelocationcount", 1));
|
||||
|
||||
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
|
||||
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
|
||||
maxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
|
||||
|
||||
var submarineAvailabilityOverrides = new HashSet<SubmarineAvailability>();
|
||||
if (element.GetChildElement("submarines") is ContentXElement availabilityElement)
|
||||
{
|
||||
submarineAvailability = GetAvailability(availabilityElement);
|
||||
foreach (var overrideElement in availabilityElement.GetChildElements("override"))
|
||||
{
|
||||
var availabilityOverride = GetAvailability(overrideElement);
|
||||
submarineAvailabilityOverrides.Add(availabilityOverride);
|
||||
}
|
||||
}
|
||||
this.submarineAvailabilityOverrides = submarineAvailabilityOverrides.ToImmutableHashSet();
|
||||
|
||||
static SubmarineAvailability GetAvailability(ContentXElement element)
|
||||
{
|
||||
return new SubmarineAvailability(
|
||||
LocationType: element.GetAttributeIdentifier("locationtype", Identifier.Empty),
|
||||
Class: element.GetAttributeEnum("class", SubmarineClass.Undefined),
|
||||
MaxTier: element.GetAttributeInt("maxtier", 0));
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier ParseIdentifier(ContentXElement element)
|
||||
@@ -47,6 +83,31 @@ namespace Barotrauma
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass subClass, Identifier locationType)
|
||||
{
|
||||
if (!submarineAvailability.HasValue)
|
||||
{
|
||||
// If the availability is not explicitly defined, make all subs available
|
||||
return SubmarineInfo.HighestTier;
|
||||
}
|
||||
int maxTier = submarineAvailability.Value.MaxTier;
|
||||
if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == subClass) is SubmarineAvailability locationAndClassOverride)
|
||||
{
|
||||
maxTier = locationAndClassOverride.MaxTier;
|
||||
}
|
||||
else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == SubmarineClass.Undefined) is SubmarineAvailability locationOverride)
|
||||
{
|
||||
maxTier = locationOverride.MaxTier;
|
||||
}
|
||||
else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == Identifier.Empty && a.Class == subClass) is SubmarineAvailability classOverride)
|
||||
{
|
||||
maxTier = classOverride.MaxTier;
|
||||
}
|
||||
return maxTier;
|
||||
}
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info, Identifier locationType) => info.Tier <= HighestSubmarineTierAvailable(info.SubmarineClass, locationType);
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,11 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Level : Entity, IServerSerializable
|
||||
{
|
||||
public enum PlacementType
|
||||
{
|
||||
Top, Bottom
|
||||
}
|
||||
|
||||
public enum EventType
|
||||
{
|
||||
SingleDestructibleWall,
|
||||
@@ -61,6 +66,7 @@ namespace Barotrauma
|
||||
[Flags]
|
||||
public enum PositionType
|
||||
{
|
||||
None = 0,
|
||||
MainPath = 0x1,
|
||||
SidePath = 0x2,
|
||||
Cave = 0x4,
|
||||
@@ -68,7 +74,8 @@ namespace Barotrauma
|
||||
Wreck = 0x10,
|
||||
BeaconStation = 0x20,
|
||||
Abyss = 0x40,
|
||||
AbyssCave = 0x80
|
||||
AbyssCave = 0x80,
|
||||
Outpost = 0x100,
|
||||
}
|
||||
|
||||
public struct InterestingPosition
|
||||
@@ -413,6 +420,9 @@ namespace Barotrauma
|
||||
get { return LevelData.Type; }
|
||||
}
|
||||
|
||||
|
||||
public bool IsEndBiome => LevelData.Biome != null && LevelData.Biome.IsEndBiome;
|
||||
|
||||
/// <summary>
|
||||
/// Is there a loaded level set and is it an outpost?
|
||||
/// </summary>
|
||||
@@ -447,7 +457,7 @@ namespace Barotrauma
|
||||
|
||||
private Level(LevelData levelData) : base(null, 0)
|
||||
{
|
||||
this.LevelData = levelData;
|
||||
LevelData = levelData;
|
||||
borders = new Rectangle(Point.Zero, levelData.Size);
|
||||
}
|
||||
|
||||
@@ -482,11 +492,8 @@ namespace Barotrauma
|
||||
EntitiesBeforeGenerate = GetEntities().ToList();
|
||||
EntityCountBeforeGenerate = EntitiesBeforeGenerate.Count();
|
||||
|
||||
if (LevelData.ForceOutpostGenerationParams == null)
|
||||
{
|
||||
StartLocation = startLocation;
|
||||
EndLocation = endLocation;
|
||||
}
|
||||
StartLocation = startLocation;
|
||||
EndLocation = endLocation;
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenStart);
|
||||
SetEqualityCheckValue(LevelGenStage.LevelGenParams, unchecked((int)GenerationParams.UintIdentifier));
|
||||
@@ -887,6 +894,12 @@ namespace Barotrauma
|
||||
// remove unnecessary cells and create some holes at the bottom of the level
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
if (GenerationParams.NoLevelGeometry)
|
||||
{
|
||||
cells.ForEach(c => c.CellType = CellType.Removed);
|
||||
cells.Clear();
|
||||
}
|
||||
|
||||
cells = cells.Except(pathCells).ToList();
|
||||
//remove cells from the edges and bottom of the map because a clean-cut edge of the level looks bad
|
||||
cells.ForEachMod(c =>
|
||||
@@ -2539,7 +2552,8 @@ namespace Barotrauma
|
||||
levelResources.Add((itemPrefab, commonnessInfo));
|
||||
}
|
||||
else if (itemPrefab.LevelQuantity.TryGetValue(GenerationParams.Identifier, out var fixedQuantityResourceInfo) ||
|
||||
itemPrefab.LevelQuantity.TryGetValue(Identifier.Empty, out fixedQuantityResourceInfo))
|
||||
itemPrefab.LevelQuantity.TryGetValue(LevelData.Biome.Identifier, out fixedQuantityResourceInfo) ||
|
||||
itemPrefab.LevelQuantity.TryGetValue(Identifier.Empty, out fixedQuantityResourceInfo))
|
||||
{
|
||||
fixedResources.Add((itemPrefab, fixedQuantityResourceInfo));
|
||||
}
|
||||
@@ -3545,6 +3559,13 @@ namespace Barotrauma
|
||||
|
||||
var subDoc = SubmarineInfo.OpenFile(contentFile.Path.Value);
|
||||
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
||||
SubmarineInfo info = new SubmarineInfo(contentFile.Path.Value)
|
||||
{
|
||||
Type = type
|
||||
};
|
||||
|
||||
//place downwards by default
|
||||
var placement = info.BeaconStationInfo?.Placement ?? PlacementType.Bottom;
|
||||
|
||||
// Add some margin so that the sub doesn't block the path entirely. It's still possible that some larger subs can't pass by.
|
||||
Point paddedDimensions = new Point(subBorders.Width + 3000, subBorders.Height + 3000);
|
||||
@@ -3565,7 +3586,7 @@ namespace Barotrauma
|
||||
attemptsLeft--;
|
||||
if (TryGetSpawnPoint(out spawnPoint))
|
||||
{
|
||||
success = TryPositionSub(subBorders, subName, ref spawnPoint);
|
||||
success = TryPositionSub(subBorders, subName, placement, ref spawnPoint);
|
||||
if (success)
|
||||
{
|
||||
break;
|
||||
@@ -3586,10 +3607,6 @@ namespace Barotrauma
|
||||
{
|
||||
Debug.WriteLine($"Sub {subName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds} (ms)");
|
||||
tempSW.Restart();
|
||||
SubmarineInfo info = new SubmarineInfo(contentFile.Path.Value)
|
||||
{
|
||||
Type = type
|
||||
};
|
||||
Submarine sub = new Submarine(info);
|
||||
if (type == SubmarineType.Wreck)
|
||||
{
|
||||
@@ -3639,10 +3656,10 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
bool TryPositionSub(Rectangle subBorders, string subName, ref Vector2 spawnPoint)
|
||||
{
|
||||
bool TryPositionSub(Rectangle subBorders, string subName, PlacementType placement, ref Vector2 spawnPoint)
|
||||
{
|
||||
positions.Add(spawnPoint);
|
||||
bool bottomFound = TryRaycastToBottom(subBorders, ref spawnPoint);
|
||||
bool bottomFound = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
positions.Add(spawnPoint);
|
||||
|
||||
bool leftSideBlocked = IsSideBlocked(subBorders, false);
|
||||
@@ -3650,21 +3667,21 @@ namespace Barotrauma
|
||||
int step = 5;
|
||||
if (rightSideBlocked && !leftSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, ref spawnPoint, -step);
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, -step);
|
||||
}
|
||||
else if (leftSideBlocked && !rightSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, ref spawnPoint, step);
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, step);
|
||||
}
|
||||
else if (!bottomFound)
|
||||
{
|
||||
if (!leftSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, ref spawnPoint, -step);
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, -step);
|
||||
}
|
||||
else if (!rightSideBlocked)
|
||||
{
|
||||
bottomFound = TryMove(subBorders, ref spawnPoint, step);
|
||||
bottomFound = TryMove(subBorders, placement, ref spawnPoint, step);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3694,14 +3711,14 @@ namespace Barotrauma
|
||||
}
|
||||
return !isBlocked && bottomFound;
|
||||
|
||||
bool TryMove(Rectangle subBorders, ref Vector2 spawnPoint, float amount)
|
||||
bool TryMove(Rectangle subBorders, PlacementType placement, ref Vector2 spawnPoint, float amount)
|
||||
{
|
||||
float maxMovement = 5000;
|
||||
float totalAmount = 0;
|
||||
bool foundBottom = TryRaycastToBottom(subBorders, ref spawnPoint);
|
||||
bool foundBottom = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
while (!IsSideBlocked(subBorders, amount > 0))
|
||||
{
|
||||
foundBottom = TryRaycastToBottom(subBorders, ref spawnPoint);
|
||||
foundBottom = TryRaycast(subBorders, placement, ref spawnPoint);
|
||||
totalAmount += amount;
|
||||
spawnPoint = new Vector2(spawnPoint.X + amount, spawnPoint.Y);
|
||||
if (Math.Abs(totalAmount) > maxMovement)
|
||||
@@ -3730,7 +3747,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryRaycastToBottom(Rectangle subBorders, ref Vector2 spawnPoint)
|
||||
bool TryRaycast(Rectangle subBorders, PlacementType placement, ref Vector2 spawnPoint)
|
||||
{
|
||||
// Shoot five rays and pick the highest hit point.
|
||||
int rayCount = 5;
|
||||
@@ -3756,16 +3773,18 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
var simPos = ConvertUnits.ToSimUnits(rayStart);
|
||||
var body = Submarine.PickBody(simPos, new Vector2(simPos.X, -1),
|
||||
customPredicate: f => f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static && !ExtraWalls.Any(w => w.Body == f.Body),
|
||||
var body = Submarine.PickBody(simPos, new Vector2(simPos.X, placement == PlacementType.Bottom ? -1 : Size.Y + 1),
|
||||
customPredicate: f => f.Body == TopBarrier || f.Body == BottomBarrier || (f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static && !ExtraWalls.Any(w => w.Body == f.Body)),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
||||
if (body != null)
|
||||
{
|
||||
positions[i] = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + new Vector2(0, subBorders.Height / 2);
|
||||
positions[i] =
|
||||
ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) +
|
||||
new Vector2(0, subBorders.Height / 2 * (placement == PlacementType.Bottom ? 1 : -1));
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
float highestPoint = positions.Max(p => p.Y);
|
||||
float highestPoint = placement == PlacementType.Bottom ? positions.Max(p => p.Y) : positions.Min(p => p.Y);
|
||||
spawnPoint = new Vector2(spawnPoint.X, highestPoint);
|
||||
return hit;
|
||||
}
|
||||
@@ -3939,7 +3958,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SubmarineInfo outpostInfo;
|
||||
Submarine outpost;
|
||||
Submarine outpost = null;
|
||||
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null)
|
||||
@@ -3953,10 +3972,14 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
var suitableParams = OutpostGenerationParams.OutpostParams
|
||||
.Where(p => p.LevelType == null || LevelData.Type == p.LevelType)
|
||||
.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
suitableParams = OutpostGenerationParams.OutpostParams
|
||||
.Where(p => p.LevelType == null || LevelData.Type == p.LevelType)
|
||||
.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
|
||||
@@ -4042,52 +4065,70 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
DockingPort outpostPort = null;
|
||||
closestDistance = float.MaxValue;
|
||||
foreach (DockingPort port in DockingPort.List)
|
||||
Vector2 spawnPos;
|
||||
if (GenerationParams.ForceOutpostPosition != Vector2.Zero)
|
||||
{
|
||||
if (port.IsHorizontal || port.Docked) { continue; }
|
||||
if (port.Item.Submarine != outpost) { continue; }
|
||||
//the outpost port has to be at the bottom of the outpost
|
||||
if (port.Item.WorldPosition.Y > outpost.WorldPosition.Y) { continue; }
|
||||
float dist = Math.Abs(port.Item.WorldPosition.X - outpost.WorldPosition.X);
|
||||
if (dist < closestDistance)
|
||||
spawnPos = new Vector2(Size.X * GenerationParams.ForceOutpostPosition.X, Size.Y * GenerationParams.ForceOutpostPosition.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
DockingPort outpostPort = null;
|
||||
closestDistance = float.MaxValue;
|
||||
foreach (DockingPort port in DockingPort.List)
|
||||
{
|
||||
outpostPort = port;
|
||||
closestDistance = dist;
|
||||
if (port.IsHorizontal || port.Docked) { continue; }
|
||||
if (port.Item.Submarine != outpost) { continue; }
|
||||
//the outpost port has to be at the bottom of the outpost
|
||||
if (port.Item.WorldPosition.Y > outpost.WorldPosition.Y) { continue; }
|
||||
float dist = Math.Abs(port.Item.WorldPosition.X - outpost.WorldPosition.X);
|
||||
if (dist < closestDistance)
|
||||
{
|
||||
outpostPort = port;
|
||||
closestDistance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float subDockingPortOffset = subPort == null ? 0.0f : subPort.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X;
|
||||
//don't try to compensate if the port is very far from the sub's center of mass
|
||||
if (Math.Abs(subDockingPortOffset) > 5000.0f)
|
||||
{
|
||||
subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -5000.0f, 5000.0f);
|
||||
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Info.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
|
||||
DebugConsole.NewMessage(warningMsg, Color.Orange);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
|
||||
}
|
||||
|
||||
float? outpostDockingPortOffset = null;
|
||||
if (outpostPort != null)
|
||||
{
|
||||
outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
|
||||
//don't try to compensate if the port is very far from the outpost's center of mass
|
||||
if (Math.Abs(outpostDockingPortOffset.Value) > 5000.0f)
|
||||
float subDockingPortOffset = subPort == null ? 0.0f : subPort.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X;
|
||||
//don't try to compensate if the port is very far from the sub's center of mass
|
||||
if (Math.Abs(subDockingPortOffset) > 5000.0f)
|
||||
{
|
||||
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset.Value, -5000.0f, 5000.0f);
|
||||
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
|
||||
subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -5000.0f, 5000.0f);
|
||||
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Info.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
|
||||
DebugConsole.NewMessage(warningMsg, Color.Orange);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
|
||||
}
|
||||
|
||||
float? outpostDockingPortOffset = null;
|
||||
if (outpostPort != null)
|
||||
{
|
||||
outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
|
||||
//don't try to compensate if the port is very far from the outpost's center of mass
|
||||
if (Math.Abs(outpostDockingPortOffset.Value) > 5000.0f)
|
||||
{
|
||||
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset.Value, -5000.0f, 5000.0f);
|
||||
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
|
||||
DebugConsole.NewMessage(warningMsg, Color.Orange);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
|
||||
}
|
||||
}
|
||||
|
||||
spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1);
|
||||
if (Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1);
|
||||
if (Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
|
||||
}
|
||||
outpost.SetPosition(spawnPos, forceUndockFromStaticSubmarines: false);
|
||||
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.Submarine == outpost && wp.SpawnType != SpawnType.Path)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(wp.WorldPosition.ToPoint(), PositionType.Outpost, outpost));
|
||||
}
|
||||
}
|
||||
|
||||
if ((i == 0) == !Mirrored)
|
||||
{
|
||||
StartOutpost = outpost;
|
||||
@@ -4106,13 +4147,12 @@ namespace Barotrauma
|
||||
outpost.Info.Name = EndLocation.Name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateBeaconStation()
|
||||
{
|
||||
if (!LevelData.HasBeaconStation) { return; }
|
||||
if (!LevelData.HasBeaconStation && string.IsNullOrEmpty(GenerationParams.ForceBeaconStation)) { return; }
|
||||
var beaconStationFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<BeaconStationFile>())
|
||||
.OrderBy(f => f.UintIdentifier).ToList();
|
||||
@@ -4123,27 +4163,40 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var beaconInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsBeacon);
|
||||
for (int i = beaconStationFiles.Count - 1; i >= 0; i--)
|
||||
ContentFile contentFile = null;
|
||||
if (!string.IsNullOrEmpty(GenerationParams.ForceBeaconStation))
|
||||
{
|
||||
var beaconStationFile = beaconStationFiles[i];
|
||||
var matchingInfo = beaconInfos.SingleOrDefault(info => info.FilePath == beaconStationFile.Path.Value);
|
||||
Debug.Assert(matchingInfo != null);
|
||||
if (matchingInfo?.BeaconStationInfo is BeaconStationInfo beaconInfo)
|
||||
contentFile = beaconStationFiles.FirstOrDefault(f => f.Path == GenerationParams.ForceBeaconStation);
|
||||
if (contentFile == null)
|
||||
{
|
||||
if (LevelData.Difficulty < beaconInfo.MinLevelDifficulty || LevelData.Difficulty > beaconInfo.MaxLevelDifficulty)
|
||||
{
|
||||
beaconStationFiles.RemoveAt(i);
|
||||
}
|
||||
DebugConsole.ThrowError($"Failed to find the beacon station \"{GenerationParams.ForceBeaconStation}\". Using a random one instead...");
|
||||
}
|
||||
}
|
||||
if (beaconStationFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"No BeaconStation files found for the level difficulty {LevelData.Difficulty}!");
|
||||
return;
|
||||
}
|
||||
var contentFile = beaconStationFiles.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
string beaconStationName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path.Value);
|
||||
|
||||
if (contentFile == null)
|
||||
{
|
||||
for (int i = beaconStationFiles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var beaconStationFile = beaconStationFiles[i];
|
||||
var matchingInfo = beaconInfos.SingleOrDefault(info => info.FilePath == beaconStationFile.Path.Value);
|
||||
Debug.Assert(matchingInfo != null);
|
||||
if (matchingInfo?.BeaconStationInfo is BeaconStationInfo beaconInfo)
|
||||
{
|
||||
if (LevelData.Difficulty < beaconInfo.MinLevelDifficulty || LevelData.Difficulty > beaconInfo.MaxLevelDifficulty)
|
||||
{
|
||||
beaconStationFiles.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (beaconStationFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"No BeaconStation files found for the level difficulty {LevelData.Difficulty}!");
|
||||
return;
|
||||
}
|
||||
contentFile = beaconStationFiles.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
string beaconStationName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path.Value);
|
||||
BeaconStation = SpawnSubOnPath(beaconStationName, contentFile, SubmarineType.BeaconStation);
|
||||
if (BeaconStation == null)
|
||||
{
|
||||
@@ -4207,7 +4260,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool allowDisconnectedWires = true;
|
||||
bool allowDamagedWalls = true;
|
||||
if (BeaconStation.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
if (BeaconStation?.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
{
|
||||
allowDisconnectedWires = info.AllowDisconnectedWires;
|
||||
allowDamagedWalls = info.AllowDamagedWalls;
|
||||
@@ -4324,6 +4377,10 @@ namespace Barotrauma
|
||||
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
|
||||
// Deduce the job from the selected prefab
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||
if (selectedPrefab != null)
|
||||
{
|
||||
job = selectedPrefab.GetJobPrefab();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedPrefab == null) { continue; }
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly Biome Biome;
|
||||
|
||||
public readonly LevelGenerationParams GenerationParams;
|
||||
public LevelGenerationParams GenerationParams { get; private set; }
|
||||
|
||||
public bool HasBeaconStation;
|
||||
public bool IsBeaconActive;
|
||||
@@ -57,8 +57,9 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int? MinMainPathWidth;
|
||||
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
public readonly List<Identifier> EventHistory = new List<Identifier>();
|
||||
public readonly List<Identifier> NonRepeatableEvents = new List<Identifier>();
|
||||
public readonly HashSet<Identifier> UsedUniqueSets = new HashSet<Identifier>();
|
||||
|
||||
public bool EventsExhausted { get; set; }
|
||||
|
||||
@@ -137,16 +138,17 @@ namespace Barotrauma
|
||||
Biome = Biome.Prefabs.First();
|
||||
}
|
||||
|
||||
string[] prefabNames = element.GetAttributeStringArray("eventhistory", new string[] { });
|
||||
EventHistory.AddRange(EventPrefab.Prefabs.Where(p => prefabNames.Any(n => p.Identifier == n)));
|
||||
string[] prefabNames = element.GetAttributeStringArray("eventhistory", Array.Empty<string>());
|
||||
EventHistory.AddRange(EventPrefab.Prefabs.Where(p => prefabNames.Any(n => p.Identifier == n)).Select(p => p.Identifier));
|
||||
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
|
||||
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", Array.Empty<string>());
|
||||
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)).Select(p => p.Identifier));
|
||||
|
||||
UsedUniqueSets = element.GetAttributeIdentifierArray(nameof(UsedUniqueSets), Array.Empty<Identifier>()).ToHashSet();
|
||||
|
||||
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates level data using the properties of the connection (seed, size, difficulty)
|
||||
/// </summary>
|
||||
@@ -243,6 +245,11 @@ namespace Barotrauma
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public void ReassignGenerationParams(string seed)
|
||||
{
|
||||
GenerationParams = LevelGenerationParams.GetRandom(seed, Type, Difficulty, Biome.Identifier);
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
{
|
||||
var newElement = new XElement("Level",
|
||||
@@ -277,13 +284,19 @@ namespace Barotrauma
|
||||
{
|
||||
if (EventHistory.Any())
|
||||
{
|
||||
newElement.Add(new XAttribute("eventhistory", string.Join(',', EventHistory.Select(p => p.Identifier))));
|
||||
newElement.Add(new XAttribute("eventhistory", string.Join(',', EventHistory)));
|
||||
}
|
||||
if (NonRepeatableEvents.Any())
|
||||
{
|
||||
newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents.Select(p => p.Identifier))));
|
||||
newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents)));
|
||||
}
|
||||
}
|
||||
|
||||
if (UsedUniqueSets.Any())
|
||||
{
|
||||
newElement.Add(new XAttribute(nameof(UsedUniqueSets), string.Join(',', UsedUniqueSets)));
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
|
||||
public Color WaterParticleColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private Vector2 startPosition;
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 StartPosition
|
||||
@@ -142,6 +149,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 forceOutpostPosition;
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, "Position of the outpost (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner). If set to 0,0, the outpost is placed in a suitable position automatically."), Editable(DecimalCount = 2)]
|
||||
public Vector2 ForceOutpostPosition
|
||||
{
|
||||
get { return forceOutpostPosition; }
|
||||
set
|
||||
{
|
||||
forceOutpostPosition = new Vector2(
|
||||
MathHelper.Clamp(value.X, 0.0f, 1.0f),
|
||||
MathHelper.Clamp(value.Y, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, "Should there be a hole in the wall next to the end outpost (can be used to prevent players from having to backtrack if they approach the outpost from the wrong side of the main path's walls)."), Editable]
|
||||
public bool CreateHoleNextToEnd
|
||||
{
|
||||
@@ -156,6 +176,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, ""), Editable]
|
||||
public bool NoLevelGeometry
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1000, IsPropertySaveable.Yes, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
@@ -478,14 +505,14 @@ namespace Barotrauma
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int RuinCount { get; set; }
|
||||
|
||||
// TODO: Move the wreck parameters under a separate class?
|
||||
#region Wreck parameters
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MinWreckCount { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The maximum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MaxWreckCount { get; set; }
|
||||
|
||||
// TODO: Move the wreck parameters under a separate class?
|
||||
#region Wreck parameters
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of corpses per wreck."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int MinCorpseCount { get; set; }
|
||||
|
||||
@@ -503,7 +530,10 @@ namespace Barotrauma
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "The min water percentage of randomly flooding hulls in wrecks."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float WreckFloodingHullMaxWaterPercentage { get; set; }
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ForceBeaconStation { get; set; }
|
||||
|
||||
[Serialize(0.4f, IsPropertySaveable.Yes, description: "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open."), Editable()]
|
||||
public float BottomHoleProbability
|
||||
@@ -519,6 +549,14 @@ namespace Barotrauma
|
||||
private set { waterParticleScale = Math.Max(value, 0.01f); }
|
||||
}
|
||||
|
||||
private Vector2 waterParticleVelocity;
|
||||
[Serialize("0,10", IsPropertySaveable.Yes, description: "How fast the water particle texture scrolls."), Editable]
|
||||
public Vector2 WaterParticleVelocity
|
||||
{
|
||||
get { return waterParticleVelocity; }
|
||||
private set { waterParticleVelocity = value; }
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, IsPropertySaveable.Yes, description: "Size of the level wall texture."), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallTextureSize
|
||||
{
|
||||
@@ -533,6 +571,34 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0,0", IsPropertySaveable.Yes), Editable]
|
||||
public Vector2 FlashInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0,0,0,0", IsPropertySaveable.Yes), Editable]
|
||||
public Color FlashColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool PlayNoiseLoopInOutpostLevel
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float WaterAmbienceVolume
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, IsPropertySaveable.Yes, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
public float WallEdgeExpandOutwardsAmount
|
||||
{
|
||||
@@ -556,8 +622,13 @@ namespace Barotrauma
|
||||
public Sprite WallSpriteDestroyed { get; private set; }
|
||||
public Sprite WaterParticles { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
public Sounds.Sound FlashSound { get; private set; }
|
||||
#endif
|
||||
|
||||
#warning TODO: this should be in the unit test project (#3164)
|
||||
public static void CheckValidity()
|
||||
|
||||
{
|
||||
foreach (Biome biome in Biome.Prefabs)
|
||||
{
|
||||
@@ -661,6 +732,11 @@ namespace Barotrauma
|
||||
case "waterparticles":
|
||||
WaterParticles = new Sprite(subElement);
|
||||
break;
|
||||
#if CLIENT
|
||||
case "flashsound":
|
||||
FlashSound = GameMain.SoundManager.LoadSound(subElement);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-25
@@ -542,38 +542,41 @@ namespace Barotrauma
|
||||
GlobalForceDecreaseTimer = 0.0f;
|
||||
}
|
||||
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
if (updateableObjects is not null)
|
||||
{
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
{
|
||||
obj.NetworkUpdateTimer -= deltaTime;
|
||||
if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
|
||||
obj.NeedsNetworkSyncing = false;
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
{
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
obj.NetworkUpdateTimer -= deltaTime;
|
||||
if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
|
||||
obj.NeedsNetworkSyncing = false;
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
|
||||
if (obj.PhysicsBody != null)
|
||||
{
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
|
||||
/*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = -obj.PhysicsBody.Rotation;*/
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
{
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.PhysicsBody != null)
|
||||
{
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
|
||||
/*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = -obj.PhysicsBody.Rotation;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name => Identifier.Value;
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
|
||||
@@ -215,7 +215,7 @@ namespace Barotrauma
|
||||
PhysicsBody.FarseerBody.SetIsSensor(element.GetAttributeBool("sensor", true));
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
|
||||
ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
|
||||
ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.Radius, PhysicsBody.Width / 2.0f), PhysicsBody.Height / 2.0f));
|
||||
|
||||
PhysicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
|
||||
}
|
||||
@@ -661,7 +661,7 @@ namespace Barotrauma
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
@@ -747,7 +747,7 @@ namespace Barotrauma
|
||||
Vector2 baseVel = GetWaterFlowVelocity();
|
||||
if (baseVel.LengthSquared() < 0.1f) return Vector2.Zero;
|
||||
|
||||
float triggerSize = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
|
||||
float triggerSize = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.Radius, PhysicsBody.Width / 2.0f), PhysicsBody.Height / 2.0f));
|
||||
float dist = Vector2.Distance(viewPosition, WorldPosition);
|
||||
if (dist > triggerSize) return Vector2.Zero;
|
||||
|
||||
|
||||
@@ -476,7 +476,7 @@ namespace Barotrauma
|
||||
bool leaveBehind = false;
|
||||
if (sub.Submarine != null && !sub.DockedTo.Contains(sub.Submarine))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndExit || Submarine.MainSub.AtStartExit);
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEitherExit);
|
||||
if (Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
leaveBehind = sub.AtEndExit != Submarine.MainSub.AtEndExit;
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
public bool Discovered { get; private set; }
|
||||
public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
|
||||
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
@@ -83,7 +83,11 @@ namespace Barotrauma
|
||||
|
||||
public int PortraitId { get; private set; }
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
public Faction Faction { get; set; }
|
||||
|
||||
public Faction SecondaryFaction { get; set; }
|
||||
|
||||
public Reputation Reputation => Faction?.Reputation;
|
||||
|
||||
public int TurnsInRadiation { get; set; }
|
||||
|
||||
@@ -135,7 +139,7 @@ namespace Barotrauma
|
||||
foreach (var stockElement in storeElement.GetChildElements("stock"))
|
||||
{
|
||||
var identifier = stockElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
if (identifier.IsEmpty || !(ItemPrefab.FindByIdentifier(identifier) is ItemPrefab prefab)) { continue; }
|
||||
if (identifier.IsEmpty || ItemPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab) { continue; }
|
||||
int qty = stockElement.GetAttributeInt("qty", 0);
|
||||
if (qty < 1) { continue; }
|
||||
Stock.Add(new PurchasedItem(prefab, qty, buyer: null));
|
||||
@@ -157,7 +161,7 @@ namespace Barotrauma
|
||||
foreach (var childElement in element.GetChildElements("item"))
|
||||
{
|
||||
var id = childElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
if (id.IsEmpty || !(ItemPrefab.FindByIdentifier(id) is ItemPrefab prefab)) { continue; }
|
||||
if (id.IsEmpty || ItemPrefab.FindByIdentifier(id) is not ItemPrefab prefab) { continue; }
|
||||
specials.Add(prefab);
|
||||
}
|
||||
return specials;
|
||||
@@ -240,7 +244,7 @@ namespace Barotrauma
|
||||
availableStock.Add(stockItem.ItemPrefab, weight);
|
||||
}
|
||||
DailySpecials.Clear();
|
||||
int extraSpecialSalesCount = Location.GetExtraSpecialSalesCount();
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
for (int i = 0; i < Location.DailySpecialsCount + extraSpecialSalesCount; i++)
|
||||
{
|
||||
if (availableStock.None()) { break; }
|
||||
@@ -283,6 +287,18 @@ namespace Barotrauma
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(true);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
if (Location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(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)));
|
||||
}
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
|
||||
}
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -303,6 +319,14 @@ namespace Barotrauma
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(false);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
price *= 1f + characters.Max(static c => c.GetStatValue(StatTypes.StoreSellMultiplier));
|
||||
price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, tag)));
|
||||
}
|
||||
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -465,7 +489,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Create a location from save data
|
||||
/// </summary>
|
||||
public Location(XElement element)
|
||||
public Location(CampaignMode campaign, XElement element)
|
||||
{
|
||||
Identifier locationTypeId = element.GetAttributeIdentifier("type", "");
|
||||
bool typeNotFound = GetTypeOrFallback(locationTypeId, out LocationType type);
|
||||
@@ -478,13 +502,23 @@ namespace Barotrauma
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
StepsSinceSpecialsUpdated = element.GetAttributeInt("stepssincespecialsupdated", 0);
|
||||
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
StepsSinceSpecialsUpdated = element.GetAttributeInt("stepssincespecialsupdated", 0);
|
||||
|
||||
var factionIdentifier = element.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
if (!factionIdentifier.IsEmpty)
|
||||
{
|
||||
Faction = campaign.Factions.Find(f => f.Prefab.Identifier == factionIdentifier);
|
||||
}
|
||||
var secondaryFactionIdentifier = element.GetAttributeIdentifier("secondaryfaction", Identifier.Empty);
|
||||
if (!secondaryFactionIdentifier.IsEmpty)
|
||||
{
|
||||
SecondaryFaction = campaign.Factions.Find(f => f.Prefab.Identifier == secondaryFactionIdentifier);
|
||||
}
|
||||
Identifier biomeId = element.GetAttributeIdentifier("biome", Identifier.Empty);
|
||||
if (biomeId != Identifier.Empty)
|
||||
{
|
||||
@@ -641,7 +675,7 @@ namespace Barotrauma
|
||||
return new Location(position, zone, rand, requireOutpost, forceLocationType, existingLocations);
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
public void ChangeType(CampaignMode campaign, LocationType newType)
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
@@ -656,34 +690,49 @@ namespace Barotrauma
|
||||
Type = newType;
|
||||
Name = Type.NameFormats == null || !Type.NameFormats.Any() ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
if (Type.HasOutpost)
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandomUnsynced());
|
||||
if (Faction == null)
|
||||
{
|
||||
Faction = campaign.GetRandomFaction(Rand.RandSync.Unsynced);
|
||||
}
|
||||
if (SecondaryFaction == null)
|
||||
{
|
||||
SecondaryFaction = campaign.GetRandomSecondaryFaction(Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
else
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandomUnsynced());
|
||||
Faction = null;
|
||||
SecondaryFaction = null;
|
||||
}
|
||||
|
||||
UnlockInitialMissions(Rand.RandSync.Unsynced);
|
||||
|
||||
CreateStores(force: true);
|
||||
}
|
||||
|
||||
public void UnlockInitialMissions()
|
||||
public void UnlockInitialMissions(Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
||||
{
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(Rand.RandSync.ServerAndClient));
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync));
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom(Rand.RandSync.ServerAndClient));
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom(randSync));
|
||||
}
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
|
||||
{
|
||||
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();
|
||||
@@ -693,7 +742,12 @@ namespace Barotrauma
|
||||
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();
|
||||
@@ -703,6 +757,7 @@ namespace Barotrauma
|
||||
public Mission UnlockMissionByIdentifier(Identifier identifier)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; }
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
|
||||
var missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == identifier);
|
||||
if (missionPrefab == null)
|
||||
@@ -717,6 +772,10 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
@@ -728,7 +787,8 @@ namespace Barotrauma
|
||||
|
||||
public Mission UnlockMissionByTag(Identifier tag)
|
||||
{
|
||||
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Any(t => t == tag));
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Contains(tag));
|
||||
if (!matchingMissions.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.");
|
||||
@@ -750,6 +810,10 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
@@ -960,12 +1024,22 @@ namespace Barotrauma
|
||||
|
||||
private string RandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (!type.ForceLocationName.IsNullOrEmpty())
|
||||
{
|
||||
baseName = type.ForceLocationName.Value;
|
||||
return baseName;
|
||||
}
|
||||
baseName = type.GetRandomName(rand, existingLocations);
|
||||
if (type.NameFormats == null || !type.NameFormats.Any()) { return baseName; }
|
||||
nameFormatIndex = rand.Next() % type.NameFormats.Count;
|
||||
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
|
||||
}
|
||||
|
||||
public void ForceName(string name)
|
||||
{
|
||||
baseName = Name = name;
|
||||
}
|
||||
|
||||
public void LoadStores(XElement locationElement)
|
||||
{
|
||||
UpdateStoreIdentifiers();
|
||||
@@ -1050,13 +1124,21 @@ namespace Barotrauma
|
||||
|
||||
public int GetAdjustedMechanicalCost(int cost)
|
||||
{
|
||||
float discount = Reputation.Value / Reputation.MaxReputation * (MechanicalMaxDiscountPercentage / 100.0f);
|
||||
return (int) Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
|
||||
float discount = 0.0f;
|
||||
if (Reputation != null)
|
||||
{
|
||||
discount = Reputation.Value / Reputation.MaxReputation * (MechanicalMaxDiscountPercentage / 100.0f);
|
||||
}
|
||||
return (int)Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
|
||||
}
|
||||
|
||||
public int GetAdjustedHealCost(int cost)
|
||||
{
|
||||
float discount = Reputation.Value / Reputation.MaxReputation * (HealMaxDiscountPercentage / 100.0f);
|
||||
float discount = 0.0f;
|
||||
if (Reputation != null)
|
||||
{
|
||||
discount = Reputation.Value / Reputation.MaxReputation * (HealMaxDiscountPercentage / 100.0f);
|
||||
}
|
||||
return (int) Math.Ceiling((1.0f - discount) * cost * PriceMultiplier);
|
||||
}
|
||||
|
||||
@@ -1125,7 +1207,7 @@ namespace Barotrauma
|
||||
public void UpdateStores()
|
||||
{
|
||||
// In multiplayer, stores should be updated by the server and loaded from save data by clients
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
if (Stores == null)
|
||||
{
|
||||
CreateStores();
|
||||
@@ -1167,13 +1249,10 @@ namespace Barotrauma
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
store.Stock.Clear();
|
||||
store.Stock.AddRange(stock);
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval || store.DailySpecials.Count() != DailySpecialsCount + extraSpecialSalesCount)
|
||||
{
|
||||
store.GenerateSpecials();
|
||||
}
|
||||
store.GeneratePriceModifier();
|
||||
}
|
||||
|
||||
StepsSinceSpecialsUpdated++;
|
||||
foreach (var identifier in storesToRemove)
|
||||
{
|
||||
Stores.Remove(identifier);
|
||||
@@ -1184,6 +1263,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSpecials()
|
||||
{
|
||||
if (GameMain.NetworkMember is { IsClient: true } || Stores is null) { return; }
|
||||
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
|
||||
foreach (StoreInfo store in Stores.Values)
|
||||
{
|
||||
if (StepsSinceSpecialsUpdated < SpecialsUpdateInterval && store.DailySpecials.Count == DailySpecialsCount + extraSpecialSalesCount) { continue; }
|
||||
|
||||
store.GenerateSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStoreIdentifiers()
|
||||
{
|
||||
StoreIdentifiers.Clear();
|
||||
@@ -1231,6 +1324,7 @@ namespace Barotrauma
|
||||
|
||||
public float GetStoreReputationModifier(bool buying)
|
||||
{
|
||||
if (Reputation == null) { return 1.0f; }
|
||||
if (buying)
|
||||
{
|
||||
if (Reputation.Value > 0.0f)
|
||||
@@ -1255,35 +1349,37 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int GetExtraSpecialSalesCount()
|
||||
public static int GetExtraSpecialSalesCount()
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (!characters.Any()) { return 0; }
|
||||
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass)
|
||||
{
|
||||
if (Discovered) { return; }
|
||||
Discovered = true;
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
|
||||
}
|
||||
if (!HasOutpost()) { return 0; }
|
||||
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined);
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info)
|
||||
{
|
||||
return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
|
||||
}
|
||||
|
||||
public void Reset(CampaignMode campaign)
|
||||
{
|
||||
if (Type != OriginalType)
|
||||
{
|
||||
ChangeType(OriginalType);
|
||||
ChangeType(campaign, OriginalType);
|
||||
PendingLocationTypeChange = null;
|
||||
}
|
||||
CreateStores(force: true);
|
||||
ClearMissions();
|
||||
LevelData?.EventHistory?.Clear();
|
||||
UnlockInitialMissions();
|
||||
Discovered = false;
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
@@ -1294,7 +1390,6 @@ namespace Barotrauma
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
|
||||
@@ -1302,6 +1397,16 @@ namespace Barotrauma
|
||||
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange),
|
||||
new XAttribute(nameof(TurnsInRadiation).ToLower(), TurnsInRadiation),
|
||||
new XAttribute("stepssincespecialsupdated", StepsSinceSpecialsUpdated));
|
||||
|
||||
if (Faction != null)
|
||||
{
|
||||
locationElement.Add(new XAttribute("faction", Faction.Prefab.Identifier));
|
||||
}
|
||||
if (SecondaryFaction != null)
|
||||
{
|
||||
locationElement.Add(new XAttribute("secondaryfaction", SecondaryFaction.Prefab.Identifier));
|
||||
}
|
||||
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
@@ -1423,7 +1528,7 @@ namespace Barotrauma
|
||||
HireManager?.Remove();
|
||||
}
|
||||
|
||||
class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
public class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
{
|
||||
public AbilityLocation(Location location)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace Barotrauma
|
||||
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly LocalizedString ForceLocationName;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
@@ -38,7 +41,13 @@ namespace Barotrauma
|
||||
|
||||
public bool IsEnterable { get; private set; }
|
||||
|
||||
public bool UseInMainMenu
|
||||
public bool UsePortraitInMainMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool UsePortraitInRandomLoadingScreens
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -70,6 +79,13 @@ namespace Barotrauma
|
||||
public Sprite Sprite { get; private set; }
|
||||
public Sprite RadiationSprite { get; }
|
||||
|
||||
private readonly Identifier forceOutpostGenerationParamsIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, only event sets that explicitly define this location type in <see cref="EventSet.LocationTypeIdentifiers"/> can be selected at this location. Defaults to false.
|
||||
/// </summary>
|
||||
public bool IgnoreGenericEvents { get; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
@@ -88,6 +104,8 @@ namespace Barotrauma
|
||||
public int DailySpecialsCount { get; } = 1;
|
||||
public int RequestedGoodsCount { get; } = 1;
|
||||
|
||||
public readonly bool ShowSonarMarker = true;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"LocationType (" + Identifier + ")";
|
||||
@@ -96,13 +114,17 @@ namespace Barotrauma
|
||||
public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
{
|
||||
Name = TextManager.Get("LocationName." + Identifier, "unknown");
|
||||
Description = TextManager.Get("LocationDescription." + Identifier, "");
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
UsePortraitInMainMenu = element.GetAttributeBool(nameof(UsePortraitInMainMenu), element.GetAttributeBool("useinmainmenu", false));
|
||||
UsePortraitInRandomLoadingScreens = element.GetAttributeBool(nameof(UsePortraitInRandomLoadingScreens), true);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
|
||||
ShowSonarMarker = element.GetAttributeBool("showsonarmarker", true);
|
||||
|
||||
MissionIdentifiers = element.GetAttributeIdentifierArray("missionidentifiers", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
MissionTags = element.GetAttributeIdentifierArray("missiontags", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
|
||||
@@ -110,26 +132,37 @@ namespace Barotrauma
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeIdentifier(nameof(ReplaceInRadiation), Identifier.Empty);
|
||||
|
||||
forceOutpostGenerationParamsIdentifier = element.GetAttributeIdentifier("forceoutpostgenerationparams", Identifier.Empty);
|
||||
|
||||
IgnoreGenericEvents = element.GetAttributeBool(nameof(IgnoreGenericEvents), false);
|
||||
|
||||
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
|
||||
Enum.TryParse(teamStr, out OutpostTeam);
|
||||
|
||||
string[] rawNamePaths = element.GetAttributeStringArray("namefile", new string[] { "Content/Map/locationNames.txt" });
|
||||
names = new List<string>();
|
||||
foreach (string rawPath in rawNamePaths)
|
||||
if (element.GetAttribute("name") != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
|
||||
names.AddRange(File.ReadAllLines(path.Value).ToList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
|
||||
}
|
||||
ForceLocationName = TextManager.Get(element.GetAttributeString("name", string.Empty));
|
||||
}
|
||||
if (!names.Any())
|
||||
else
|
||||
{
|
||||
names.Add("ERROR: No names found");
|
||||
string[] rawNamePaths = element.GetAttributeStringArray("namefile", new string[] { "Content/Map/locationNames.txt" });
|
||||
names = new List<string>();
|
||||
foreach (string rawPath in rawNamePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
|
||||
names.AddRange(File.ReadAllLines(path.Value).ToList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
|
||||
}
|
||||
}
|
||||
if (!names.Any())
|
||||
{
|
||||
names.Add("ERROR: No names found");
|
||||
}
|
||||
}
|
||||
|
||||
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
|
||||
@@ -261,6 +294,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostGenerationParams GetForcedOutpostGenerationParams()
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.TryGet(forceOutpostGenerationParamsIdentifier, out var parameters))
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,24 +141,25 @@ namespace Barotrauma
|
||||
|
||||
private readonly bool requireChangeMessages;
|
||||
private readonly string messageTag;
|
||||
private ImmutableArray<string>? messages = null;
|
||||
public IReadOnlyList<string> Messages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!messages.HasValue)
|
||||
{
|
||||
messages = TextManager.GetAll(messageTag).ToImmutableArray();
|
||||
if (messages.Value.None())
|
||||
{
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError($"No messages defined for the location type change {CurrentType} -> {ChangeToType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages.Value;
|
||||
public IReadOnlyList<string> GetMessages(Faction faction)
|
||||
{
|
||||
if (faction != null && TextManager.ContainsTag(messageTag + "." + faction.Prefab.Identifier))
|
||||
{
|
||||
return TextManager.GetAll(messageTag + "." + faction.Prefab.Identifier).ToImmutableArray();
|
||||
}
|
||||
|
||||
if (TextManager.ContainsTag(messageTag))
|
||||
{
|
||||
return TextManager.GetAll(messageTag).ToImmutableArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError($"No messages defined for the location type change {CurrentType} -> {ChangeToType}");
|
||||
}
|
||||
return Enumerable.Empty<string>().ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly NamedEvent<LocationChangeInfo> OnLocationChanged = new NamedEvent<LocationChangeInfo>();
|
||||
|
||||
public Location EndLocation { get; private set; }
|
||||
private List<Location> endLocations = new List<Location>();
|
||||
public IReadOnlyList<Location> EndLocations { get { return endLocations; } }
|
||||
|
||||
public Location StartLocation { get; private set; }
|
||||
|
||||
@@ -68,10 +69,15 @@ 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>();
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
private bool wasLocationDiscoveryOrderTracked = true;
|
||||
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
@@ -112,7 +118,7 @@ namespace Barotrauma
|
||||
Locations.Add(null);
|
||||
}
|
||||
lairsFound |= subElement.GetAttributeString("type", "").Equals("lair", StringComparison.OrdinalIgnoreCase);
|
||||
Locations[i] = new Location(subElement);
|
||||
Locations[i] = new Location(campaign, subElement);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
|
||||
@@ -122,11 +128,6 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}".ToIdentifier(), -100, 100, Rand.Range(-10, 11, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
|
||||
List<XElement> connectionElements = new List<XElement>();
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -182,23 +183,73 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
int endLocationindex = element.GetAttributeInt("endlocation", -1);
|
||||
if (endLocationindex > 0 && endLocationindex < Locations.Count)
|
||||
|
||||
if (element.GetAttribute("endlocation") != null)
|
||||
{
|
||||
EndLocation = Locations[endLocationindex];
|
||||
//backwards compatibility
|
||||
int endLocationIndex = element.GetAttributeInt("endlocation", -1);
|
||||
if (endLocationIndex > 0 && endLocationIndex < Locations.Count)
|
||||
{
|
||||
endLocations.Add(Locations[endLocationIndex]);
|
||||
Locations[endLocationIndex].LevelData.ReassignGenerationParams(Seed);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationIndex}, location count: {Locations.Count}).");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationindex}, location count: {Locations.Count}).");
|
||||
foreach (Location location in Locations)
|
||||
int[] endLocationindices = element.GetAttributeIntArray("endlocations", Array.Empty<int>());
|
||||
foreach (int endLocationIndex in endLocationindices)
|
||||
{
|
||||
if (EndLocation == null || location.MapPosition.X > EndLocation.MapPosition.X)
|
||||
if (endLocationIndex > 0 && endLocationIndex < Locations.Count)
|
||||
{
|
||||
EndLocation = location;
|
||||
endLocations.Add(Locations[endLocationIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationIndex}, location count: {Locations.Count}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!endLocations.Any())
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. No end location(s) found. Choosing the rightmost location as the end location...");
|
||||
Location endLocation = null;
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (endLocation == null || location.MapPosition.X > endLocation.MapPosition.X)
|
||||
{
|
||||
endLocation = location;
|
||||
}
|
||||
}
|
||||
endLocations.Add(endLocation);
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Assert(endLocations.First().Biome != null, "End location biome was null.");
|
||||
System.Diagnostics.Debug.Assert(endLocations.First().Biome.IsEndBiome, "The biome of the end location isn't the end biome.");
|
||||
|
||||
//backwards compatibility (or support for loading maps created with mods that modify the end biome setup):
|
||||
//if there's too few end locations, create more
|
||||
int missingOutpostCount = endLocations.First().Biome.EndBiomeLocationCount - endLocations.Count;
|
||||
|
||||
Location firstEndLocation = EndLocations[0];
|
||||
for (int i = 0; i < missingOutpostCount; i++)
|
||||
{
|
||||
Vector2 mapPos = new Vector2(
|
||||
MathHelper.Lerp(firstEndLocation.MapPosition.X, Width, MathHelper.Lerp(0.2f, 0.8f, i / (float)missingOutpostCount)),
|
||||
Height * MathHelper.Lerp(0.2f, 1.0f, (float)rand.NextDouble()));
|
||||
var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations)
|
||||
{
|
||||
Biome = endLocations.First().Biome
|
||||
};
|
||||
newEndLocation.LevelData = new LevelData(newEndLocation, difficulty: 100.0f);
|
||||
Locations.Add(newEndLocation);
|
||||
endLocations.Add(newEndLocation);
|
||||
}
|
||||
|
||||
//backwards compatibility: if the map contained the now-removed lairs and has no hunting grounds, create some hunting grounds
|
||||
if (lairsFound && !Connections.Any(c => c.LevelData.HasHuntingGrounds))
|
||||
{
|
||||
@@ -209,6 +260,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var endLocation in EndLocations)
|
||||
{
|
||||
if (endLocation.Type?.ForceLocationName != null &&
|
||||
!endLocation.Type.ForceLocationName.IsNullOrEmpty())
|
||||
{
|
||||
endLocation.ForceName(endLocation.Type.ForceLocationName.Value);
|
||||
}
|
||||
}
|
||||
|
||||
AssignEndLocationLevelData();
|
||||
|
||||
//backwards compatibility: if locations go out of bounds (map saved with different generation parameters before width/height were included in the xml)
|
||||
float maxX = Locations.Select(l => l.MapPosition.X).Max();
|
||||
if (maxX > Width) { Width = (int)(maxX + 10); }
|
||||
@@ -226,18 +288,13 @@ namespace Barotrauma
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
Generate(campaign.Settings);
|
||||
Generate(campaign);
|
||||
|
||||
if (Locations.Count == 0)
|
||||
{
|
||||
throw new Exception($"Generating a campaign map failed (no locations created). Width: {Width}, height: {Height}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}".ToIdentifier(), -100, 100, Rand.Range(-10, 11, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.Identifier != "outpost") { continue; }
|
||||
@@ -258,6 +315,20 @@ namespace Barotrauma
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
StartLocation.SecondaryFaction = null;
|
||||
var startOutpostFaction = campaign?.Factions.FirstOrDefault(f => f.Prefab.StartOutpost);
|
||||
if (startOutpostFaction != null)
|
||||
{
|
||||
StartLocation.Faction = startOutpostFaction;
|
||||
foreach (var connection in StartLocation.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(StartLocation);
|
||||
if (otherLocation.HasOutpost() && otherLocation.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
otherLocation.Faction = startOutpostFaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +353,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.Discover(true);
|
||||
if (campaign.IsSinglePlayer && campaign.Settings.TutorialEnabled && LocationType.Prefabs.TryGet("tutorialoutpost", out var tutorialOutpost))
|
||||
{
|
||||
CurrentLocation.ChangeType(campaign, tutorialOutpost);
|
||||
}
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
CurrentLocation.CreateStores();
|
||||
|
||||
foreach (var location in Locations)
|
||||
@@ -297,7 +373,7 @@ namespace Barotrauma
|
||||
|
||||
#region Generation
|
||||
|
||||
private void Generate(CampaignSettings settings)
|
||||
private void Generate(CampaignMode campaign)
|
||||
{
|
||||
Connections.Clear();
|
||||
Locations.Clear();
|
||||
@@ -515,12 +591,14 @@ namespace Barotrauma
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1])
|
||||
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1] &&
|
||||
connectionsBetweenZones[zone1].None(c => c.Locations.Contains(connection.Locations[0]) || c.Locations.Contains(connection.Locations[1])))
|
||||
{
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
var gateFactions = campaign.Factions.Where(f => f.Prefab.ControlledOutpostPercentage > 0).OrderBy(f => f.Prefab.Identifier).ToList();
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
int zone1 = GetZoneIndex(Connections[i].Locations[0].MapPosition.X);
|
||||
@@ -528,9 +606,9 @@ namespace Barotrauma
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; }
|
||||
|
||||
if (generationParams.GateCount[Math.Min(zone1, zone2)] == 0) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones[Math.Min(zone1, zone2)].Contains(Connections[i]))
|
||||
int leftZone = Math.Min(zone1, zone2);
|
||||
if (generationParams.GateCount[leftZone] == 0) { continue; }
|
||||
if (!connectionsBetweenZones[leftZone].Contains(Connections[i]))
|
||||
{
|
||||
Connections.RemoveAt(i);
|
||||
}
|
||||
@@ -542,10 +620,17 @@ namespace Barotrauma
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
|
||||
{
|
||||
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
|
||||
leftMostLocation.ChangeType(
|
||||
campaign,
|
||||
LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
|
||||
if (leftMostLocation.Type.HasOutpost && campaign != null && gateFactions.Any())
|
||||
{
|
||||
leftMostLocation.Faction = gateFactions[connectionsBetweenZones[leftZone].IndexOf(Connections[i]) % gateFactions.Count];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,20 +698,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CreateEndLocation();
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, CalculateDifficulty(location.MapPosition.X, location.Biome));
|
||||
if (location.Type.HasOutpost && campaign != null && location.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
location.Faction ??= campaign.GetRandomFaction(Rand.RandSync.ServerAndClient);
|
||||
location.SecondaryFaction ??= campaign.GetRandomSecondaryFaction(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.LevelData = new LevelData(connection);
|
||||
}
|
||||
|
||||
CreateEndLocation(campaign);
|
||||
|
||||
float CalculateDifficulty(float mapPosition, Biome biome)
|
||||
{
|
||||
float settingsFactor = settings.LevelDifficultyMultiplier;
|
||||
float settingsFactor = campaign.Settings.LevelDifficultyMultiplier;
|
||||
float minDifficulty = 0;
|
||||
float maxDifficulty = 100;
|
||||
float difficulty = mapPosition / Width * 100;
|
||||
@@ -695,18 +786,18 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(Connections.All(c => c.Biome != null));
|
||||
}
|
||||
|
||||
private void CreateEndLocation()
|
||||
private void CreateEndLocation(CampaignMode campaign)
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
Vector2 endPos = new Vector2(Width - zoneWidth / 2, Height / 2);
|
||||
Vector2 endPos = new Vector2(Width - zoneWidth * 0.7f, Height / 2);
|
||||
float closestDist = float.MaxValue;
|
||||
EndLocation = Locations.First();
|
||||
var endLocation = Locations.First();
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(endPos, location.MapPosition);
|
||||
if (location.Biome.IsEndBiome && dist < closestDist)
|
||||
{
|
||||
EndLocation = location;
|
||||
endLocation = location;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
@@ -720,17 +811,39 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (EndLocation == null || previousToEndLocation == null) { return; }
|
||||
if (endLocation == null || previousToEndLocation == null) { return; }
|
||||
|
||||
endLocations = new List<Location>() { endLocation };
|
||||
if (endLocation.Biome.EndBiomeLocationCount > 1)
|
||||
{
|
||||
FindConnectedEndLocations(endLocation);
|
||||
|
||||
void FindConnectedEndLocations(Location currLocation)
|
||||
{
|
||||
if (endLocations.Count >= endLocation.Biome.EndBiomeLocationCount) { return; }
|
||||
foreach (var connection in currLocation.Connections)
|
||||
{
|
||||
if (connection.Biome != endLocation.Biome) { continue; }
|
||||
var otherLocation = connection.OtherLocation(currLocation);
|
||||
if (otherLocation != null && !endLocations.Contains(otherLocation))
|
||||
{
|
||||
if (endLocations.Count >= endLocation.Biome.EndBiomeLocationCount) { return; }
|
||||
endLocations.Add(otherLocation);
|
||||
FindConnectedEndLocations(otherLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
previousToEndLocation.ChangeType(locationType);
|
||||
previousToEndLocation.ChangeType(campaign, locationType);
|
||||
}
|
||||
|
||||
//remove all locations from the end biome except the end location
|
||||
for (int i = Locations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Locations[i].Biome.IsEndBiome && Locations[i] != EndLocation)
|
||||
if (Locations[i].Biome.IsEndBiome)
|
||||
{
|
||||
for (int j = Locations[i].Connections.Count - 1; j >= 0; j--)
|
||||
{
|
||||
@@ -741,7 +854,10 @@ namespace Barotrauma
|
||||
otherLocation?.Connections.Remove(connection);
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
Locations.RemoveAt(i);
|
||||
if (!endLocations.Contains(Locations[i]))
|
||||
{
|
||||
Locations.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,22 +874,38 @@ namespace Barotrauma
|
||||
}
|
||||
var newConnection = new LocationConnection(previousToEndLocation, connectTo)
|
||||
{
|
||||
Biome = EndLocation.Biome,
|
||||
Biome = endLocation.Biome,
|
||||
Difficulty = 100.0f
|
||||
};
|
||||
newConnection.LevelData = new LevelData(newConnection);
|
||||
Connections.Add(newConnection);
|
||||
previousToEndLocation.Connections.Add(newConnection);
|
||||
connectTo.Connections.Add(newConnection);
|
||||
}
|
||||
|
||||
var endConnection = new LocationConnection(previousToEndLocation, EndLocation)
|
||||
var endConnection = new LocationConnection(previousToEndLocation, endLocation)
|
||||
{
|
||||
Biome = EndLocation.Biome,
|
||||
Biome = endLocation.Biome,
|
||||
Difficulty = 100.0f
|
||||
};
|
||||
endConnection.LevelData = new LevelData(endConnection);
|
||||
Connections.Add(endConnection);
|
||||
previousToEndLocation.Connections.Add(endConnection);
|
||||
EndLocation.Connections.Add(endConnection);
|
||||
endLocation.Connections.Add(endConnection);
|
||||
|
||||
AssignEndLocationLevelData();
|
||||
}
|
||||
|
||||
private void AssignEndLocationLevelData()
|
||||
{
|
||||
for (int i = 0; i < endLocations.Count; i++)
|
||||
{
|
||||
var outpostParams = OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.ForceToEndLocationIndex == i);
|
||||
if (outpostParams != null)
|
||||
{
|
||||
endLocations[i].LevelData.ForceOutpostGenerationParams = outpostParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpandBiomes(List<LocationConnection> seeds)
|
||||
@@ -807,20 +939,44 @@ namespace Barotrauma
|
||||
{
|
||||
if (SelectedConnection == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n"+Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
if (!endLocations.Contains(CurrentLocation))
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (SelectedLocation == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no location selected).\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
if (endLocations.Contains(CurrentLocation))
|
||||
{
|
||||
int currentEndLocationIndex = endLocations.IndexOf(CurrentLocation);
|
||||
if (currentEndLocationIndex < endLocations.Count - 1)
|
||||
{
|
||||
//more end locations to go, progress to the next one
|
||||
SelectedLocation = endLocations[currentEndLocationIndex + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
//at the last end location, end of campaign
|
||||
SelectedLocation = StartLocation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
SelectedConnection.Passed = true;
|
||||
if (SelectedConnection != null)
|
||||
{
|
||||
SelectedConnection.Passed = true;
|
||||
}
|
||||
|
||||
CurrentLocation = SelectedLocation;
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
@@ -851,7 +1007,7 @@ namespace Barotrauma
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
if (prevLocation != CurrentLocation)
|
||||
@@ -966,7 +1122,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ProgressWorld(CampaignMode.TransitionType transitionType, float roundDuration)
|
||||
public void ProgressWorld(CampaignMode campaign, CampaignMode.TransitionType transitionType, float roundDuration)
|
||||
{
|
||||
//one step per 10 minutes of play time
|
||||
int steps = (int)Math.Floor(roundDuration / (60.0f * 10.0f));
|
||||
@@ -979,13 +1135,23 @@ namespace Barotrauma
|
||||
steps = Math.Min(steps, 5);
|
||||
for (int i = 0; i < steps; i++)
|
||||
{
|
||||
ProgressWorld();
|
||||
ProgressWorld(campaign);
|
||||
}
|
||||
|
||||
// always update specials every step
|
||||
for (int i = 0; i < Math.Max(1, steps); i++)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Discovered) { continue; }
|
||||
location.UpdateSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
Radiation?.OnStep(steps);
|
||||
}
|
||||
|
||||
private void ProgressWorld()
|
||||
private void ProgressWorld(CampaignMode campaign)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
@@ -1013,14 +1179,14 @@ namespace Barotrauma
|
||||
|
||||
if (location == CurrentLocation || location == SelectedLocation || location.IsGateBetweenBiomes) { continue; }
|
||||
|
||||
if (!ProgressLocationTypeChanges(location) && location.Discovered)
|
||||
if (!ProgressLocationTypeChanges(campaign, location) && location.Discovered)
|
||||
{
|
||||
location.UpdateStores();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ProgressLocationTypeChanges(Location location)
|
||||
private bool ProgressLocationTypeChanges(CampaignMode campaign, Location location)
|
||||
{
|
||||
location.TimeSinceLastTypeChange++;
|
||||
location.LocationTypeChangeCooldown--;
|
||||
@@ -1040,7 +1206,7 @@ namespace Barotrauma
|
||||
location.PendingLocationTypeChange.Value.parentMission);
|
||||
if (location.PendingLocationTypeChange.Value.delay <= 0)
|
||||
{
|
||||
return ChangeLocationType(location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
return ChangeLocationType(campaign, location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1073,7 +1239,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return ChangeLocationType(location, selectedTypeChange);
|
||||
return ChangeLocationType(campaign, location, selectedTypeChange);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1143,7 +1309,7 @@ namespace Barotrauma
|
||||
return distance;
|
||||
}
|
||||
|
||||
private bool ChangeLocationType(Location location, LocationTypeChange change)
|
||||
private bool ChangeLocationType(CampaignMode campaign, Location location, LocationTypeChange change)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
|
||||
@@ -1158,7 +1324,7 @@ namespace Barotrauma
|
||||
{
|
||||
location.ClearMissions();
|
||||
}
|
||||
location.ChangeType(newType);
|
||||
location.ChangeType(campaign, newType);
|
||||
ChangeLocationTypeProjSpecific(location, prevName, change);
|
||||
foreach (var requirement in change.Requirements)
|
||||
{
|
||||
@@ -1174,13 +1340,58 @@ namespace Barotrauma
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
public void Discover(Location location, bool checkTalents = true)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (locationsDiscovered.Contains(location)) { return; }
|
||||
locationsDiscovered.Add(location);
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Location.AbilityLocation(location)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Visit(Location location)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (!location.HasOutpost()) { return; }
|
||||
if (outpostsVisited.Contains(location)) { return; }
|
||||
outpostsVisited.Add(location);
|
||||
}
|
||||
|
||||
public void ClearLocationHistory()
|
||||
{
|
||||
locationsDiscovered.Clear();
|
||||
outpostsVisited.Clear();
|
||||
}
|
||||
|
||||
public int? GetDiscoveryIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return locationsDiscovered.IndexOf(location);
|
||||
}
|
||||
|
||||
public int? GetVisitIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return outpostsVisited.IndexOf(location);
|
||||
}
|
||||
|
||||
public bool IsDiscovered(Location location)
|
||||
{
|
||||
if (location is null) { return false; }
|
||||
return locationsDiscovered.Contains(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
{
|
||||
Map map = new Map(campaign, element);
|
||||
map.LoadState(element, false);
|
||||
map.LoadState(campaign, element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
#endif
|
||||
@@ -1190,17 +1401,18 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load the state of an existing map from xml (current state of locations, where the crew is now, etc).
|
||||
/// </summary>
|
||||
public void LoadState(XElement element, bool showNotifications)
|
||||
public void LoadState(CampaignMode campaign, XElement element, bool showNotifications)
|
||||
{
|
||||
ClearAnimQueue();
|
||||
SetLocation(element.GetAttributeInt("currentlocation", 0));
|
||||
|
||||
if (!Version.TryParse(element.GetAttributeString("version", ""), out _))
|
||||
if (!Version.TryParse(element.GetAttributeString("version", ""), out Version version))
|
||||
{
|
||||
DebugConsole.ThrowError("Incompatible map save file, loading the game failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
ClearLocationHistory();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1216,26 +1428,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
|
||||
// Backwards compatibility
|
||||
if (subElement.GetAttributeBool("discovered", false))
|
||||
{
|
||||
location.Discover(checkTalents: false);
|
||||
}
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
Discover(location);
|
||||
wasLocationDiscoveryOrderTracked = false;
|
||||
}
|
||||
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
string prevLocationName = location.Name;
|
||||
LocationType prevLocationType = location.Type;
|
||||
LocationType newLocationType = LocationType.Prefabs.Find(lt => lt.Identifier == locationType) ?? LocationType.Prefabs.First();
|
||||
location.ChangeType(newLocationType);
|
||||
location.ChangeType(campaign, newLocationType);
|
||||
if (showNotifications && prevLocationType != location.Type)
|
||||
{
|
||||
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType == location.Type.Identifier);
|
||||
@@ -1246,6 +1451,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var factionIdentifier = subElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
location.Faction = factionIdentifier.IsEmpty ? null : campaign.Factions.Find(f => f.Prefab.Identifier == factionIdentifier);
|
||||
|
||||
var secondaryFactionIdentifier = subElement.GetAttributeIdentifier("secondaryfaction", Identifier.Empty);
|
||||
location.SecondaryFaction = secondaryFactionIdentifier.IsEmpty ? null : campaign.Factions.Find(f => f.Prefab.Identifier == secondaryFactionIdentifier);
|
||||
|
||||
location.LoadStores(subElement);
|
||||
location.LoadMissions(subElement);
|
||||
|
||||
@@ -1258,6 +1469,36 @@ namespace Barotrauma
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
case "discovered":
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1266,6 +1507,27 @@ 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))
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.HasOutpost && campaign != null && location.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
location.Faction = campaign.GetRandomFaction(Rand.RandSync.ServerAndClient);
|
||||
if (location != StartLocation)
|
||||
{
|
||||
location.SecondaryFaction = campaign.GetRandomSecondaryFaction(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int currentLocationConnection = element.GetAttributeInt("currentlocationconnection", -1);
|
||||
if (currentLocationConnection >= 0)
|
||||
{
|
||||
@@ -1304,7 +1566,7 @@ namespace Barotrauma
|
||||
mapElement.Add(new XAttribute("height", Height));
|
||||
mapElement.Add(new XAttribute("selectedlocation", SelectedLocationIndex));
|
||||
mapElement.Add(new XAttribute("startlocation", Locations.IndexOf(StartLocation)));
|
||||
mapElement.Add(new XAttribute("endlocation", Locations.IndexOf(EndLocation)));
|
||||
mapElement.Add(new XAttribute("endlocations", string.Join(',', EndLocations.Select(e => Locations.IndexOf(e)))));
|
||||
mapElement.Add(new XAttribute("seed", Seed));
|
||||
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
@@ -1333,6 +1595,30 @@ namespace Barotrauma
|
||||
mapElement.Add(Radiation.Save());
|
||||
}
|
||||
|
||||
if (locationsDiscovered.Any())
|
||||
{
|
||||
var discoveryElement = new XElement("discovered");
|
||||
foreach (Location location in locationsDiscovered)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
discoveryElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(discoveryElement);
|
||||
}
|
||||
|
||||
if (outpostsVisited.Any())
|
||||
{
|
||||
var visitElement = new XElement("visited");
|
||||
foreach (Location location in outpostsVisited)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
visitElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(visitElement);
|
||||
}
|
||||
|
||||
element.Add(mapElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
_spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999f);
|
||||
_spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999999f);
|
||||
if (this is Item) { _spriteOverrideDepth = Math.Min(_spriteOverrideDepth, 0.9f); }
|
||||
SpriteDepthOverrideIsSet = true;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Barotrauma
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MaxLevelDifficulty { get; set; }
|
||||
|
||||
[Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable]
|
||||
public Level.PlacementType Placement { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -12,23 +8,23 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly static PrefabCollection<NPCSet> Sets = new PrefabCollection<NPCSet>();
|
||||
|
||||
|
||||
private readonly ImmutableArray<HumanPrefab> Humans;
|
||||
|
||||
private bool Disposed { get; set; }
|
||||
|
||||
public NPCSet(ContentXElement element, NPCSetsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file, Identifier)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public static HumanPrefab? Get(Identifier setIdentifier, Identifier npcidentifier)
|
||||
public static HumanPrefab? Get(Identifier setIdentifier, Identifier npcidentifier, bool logError = true)
|
||||
{
|
||||
HumanPrefab? prefab = Sets.Where(set => set.Identifier == setIdentifier).SelectMany(npcSet => npcSet.Humans.Where(npcSetHuman => npcSetHuman.Identifier == npcidentifier)).FirstOrDefault();
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find human prefab \"{npcidentifier}\" from \"{setIdentifier}\".");
|
||||
if (logError)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find human prefab \"{npcidentifier}\" from \"{setIdentifier}\".");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return prefab;
|
||||
|
||||
@@ -23,6 +23,15 @@ namespace Barotrauma
|
||||
get { return allowedLocationTypes; }
|
||||
}
|
||||
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes), Editable(MinValueInt = -1, MaxValueInt = 10)]
|
||||
public int ForceToEndLocationIndex
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(10, IsPropertySaveable.Yes), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
public int TotalModuleCount
|
||||
{
|
||||
@@ -30,6 +39,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Should the generator append generic (module flag \"none\") modules to the outpost to reach the total module count."), Editable]
|
||||
public bool AppendToReachTotalModuleCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(200.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float MinHallwayLength
|
||||
{
|
||||
@@ -79,6 +95,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool DrawBehindSubs
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MinWaterPercentage
|
||||
{
|
||||
@@ -93,26 +116,38 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
public LevelData.LevelType? LevelType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
public ContentPath OutpostFilePath { get; set; }
|
||||
|
||||
public class ModuleCount
|
||||
{
|
||||
public Identifier Identifier;
|
||||
public int Count;
|
||||
public int Order;
|
||||
|
||||
public Identifier RequiredFaction;
|
||||
|
||||
public ModuleCount(ContentXElement element)
|
||||
{
|
||||
Identifier = element.GetAttributeIdentifier("flag", element.GetAttributeIdentifier("moduletype", ""));
|
||||
Count = element.GetAttributeInt("count", 0);
|
||||
Order = element.GetAttributeInt("order", 0);
|
||||
RequiredFaction = element.GetAttributeIdentifier("requiredfaction", Identifier.Empty);
|
||||
}
|
||||
|
||||
public ModuleCount(Identifier id, int count)
|
||||
{
|
||||
Identifier = id;
|
||||
Count = count;
|
||||
RequiredFaction = Identifier.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +165,20 @@ namespace Barotrauma
|
||||
private readonly HumanPrefab humanPrefab = null;
|
||||
private readonly Identifier setIdentifier = Identifier.Empty;
|
||||
private readonly Identifier npcIdentifier = Identifier.Empty;
|
||||
|
||||
public readonly Identifier FactionIdentifier = Identifier.Empty;
|
||||
|
||||
public Entry(HumanPrefab humanPrefab)
|
||||
public Entry(HumanPrefab humanPrefab, Identifier factionIdentifier)
|
||||
{
|
||||
this.humanPrefab = humanPrefab;
|
||||
this.FactionIdentifier = factionIdentifier;
|
||||
}
|
||||
|
||||
public Entry(Identifier setIdentifier, Identifier npcIdentifier)
|
||||
public Entry(Identifier setIdentifier, Identifier npcIdentifier, Identifier factionIdentifier)
|
||||
{
|
||||
this.setIdentifier = setIdentifier;
|
||||
this.npcIdentifier = npcIdentifier;
|
||||
this.FactionIdentifier = factionIdentifier;
|
||||
}
|
||||
|
||||
public HumanPrefab HumanPrefab
|
||||
@@ -148,12 +187,12 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<Entry> entries = new List<Entry>();
|
||||
|
||||
public void Add(HumanPrefab humanPrefab)
|
||||
=> entries.Add(new Entry(humanPrefab));
|
||||
public void Add(HumanPrefab humanPrefab, Identifier factionIdentifier)
|
||||
=> entries.Add(new Entry(humanPrefab, factionIdentifier));
|
||||
|
||||
|
||||
public void Add(Identifier setIdentifier, Identifier npcIdentifier)
|
||||
=> entries.Add(new Entry(setIdentifier, npcIdentifier));
|
||||
public void Add(Identifier setIdentifier, Identifier npcIdentifier, Identifier factionIdentifier)
|
||||
=> entries.Add(new Entry(setIdentifier, npcIdentifier, factionIdentifier));
|
||||
|
||||
public IEnumerator<HumanPrefab> GetEnumerator()
|
||||
{
|
||||
@@ -165,12 +204,23 @@ namespace Barotrauma
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public IEnumerable<HumanPrefab> GetByFaction(IEnumerable<FactionPrefab> factions)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry.FactionIdentifier == Identifier.Empty || factions.Any(f => f.Identifier == entry.FactionIdentifier))
|
||||
{
|
||||
yield return entry.HumanPrefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => entries.Count;
|
||||
|
||||
public HumanPrefab this[int index] => entries[index].HumanPrefab;
|
||||
}
|
||||
|
||||
private readonly ImmutableArray<IReadOnlyList<HumanPrefab>> humanPrefabCollections;
|
||||
private readonly ImmutableArray<NpcCollection> humanPrefabCollections;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
@@ -182,8 +232,23 @@ namespace Barotrauma
|
||||
Name = element.GetAttributeString("name", Identifier.Value);
|
||||
allowedLocationTypes = element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty<Identifier>()).ToHashSet();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
if (element.GetAttribute("leveltype") != null)
|
||||
{
|
||||
string levelTypeStr = element.GetAttributeString("leveltype", "");
|
||||
if (Enum.TryParse(levelTypeStr, out LevelData.LevelType parsedLevelType))
|
||||
{
|
||||
LevelType = parsedLevelType;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.");
|
||||
}
|
||||
}
|
||||
|
||||
OutpostFilePath = element.GetAttributeContentPath(nameof(OutpostFilePath));
|
||||
|
||||
var humanPrefabCollections = new List<IReadOnlyList<HumanPrefab>>();
|
||||
var humanPrefabCollections = new List<NpcCollection>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -196,14 +261,14 @@ namespace Barotrauma
|
||||
foreach (var npcElement in subElement.Elements())
|
||||
{
|
||||
Identifier from = npcElement.GetAttributeIdentifier("from", Identifier.Empty);
|
||||
|
||||
Identifier faction = npcElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
if (from != Identifier.Empty)
|
||||
{
|
||||
newCollection.Add(from, npcElement.GetAttributeIdentifier("identifier", Identifier.Empty));
|
||||
newCollection.Add(from, npcElement.GetAttributeIdentifier("identifier", Identifier.Empty), faction);
|
||||
}
|
||||
else
|
||||
{
|
||||
newCollection.Add(new HumanPrefab(npcElement, file, npcSetIdentifier: from));
|
||||
newCollection.Add(new HumanPrefab(npcElement, file, npcSetIdentifier: from), faction);
|
||||
}
|
||||
}
|
||||
humanPrefabCollections.Add(newCollection);
|
||||
@@ -251,10 +316,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<HumanPrefab> GetHumanPrefabs(Rand.RandSync randSync)
|
||||
public IReadOnlyList<HumanPrefab> GetHumanPrefabs(IEnumerable<FactionPrefab> factions, Rand.RandSync randSync)
|
||||
{
|
||||
if (!humanPrefabCollections.Any()) { return Array.Empty<HumanPrefab>(); }
|
||||
return humanPrefabCollections.GetRandom(randSync);
|
||||
|
||||
var collection = humanPrefabCollections.GetRandom(randSync);
|
||||
return collection.GetByFaction(factions).ToImmutableList();
|
||||
}
|
||||
|
||||
public ImmutableHashSet<Identifier> GetStoreIdentifiers()
|
||||
|
||||
@@ -143,9 +143,9 @@ namespace Barotrauma
|
||||
//select which module types the outpost should consist of
|
||||
List<Identifier> pendingModuleFlags =
|
||||
onlyEntrance ?
|
||||
generationParams.ModuleCounts.First().Identifier.ToEnumerable().ToList() :
|
||||
SelectModules(outpostModules, generationParams);
|
||||
|
||||
(generationParams.ModuleCounts.FirstOrDefault()?.Identifier.ToEnumerable() ?? Enumerable.Empty<Identifier>()).ToList() :
|
||||
SelectModules(outpostModules, location, generationParams);
|
||||
|
||||
foreach (Identifier flag in pendingModuleFlags)
|
||||
{
|
||||
if (flag == "none") { continue; }
|
||||
@@ -237,15 +237,21 @@ namespace Barotrauma
|
||||
wp.FindHull();
|
||||
}
|
||||
}
|
||||
EnableFactionSpecificEntities(sub, location);
|
||||
return sub;
|
||||
}
|
||||
remainingTries--;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Failed to generate an outpost without overlapping modules. Trying to use a pre-built outpost instead...");
|
||||
#else
|
||||
DebugConsole.NewMessage("Failed to generate an outpost without overlapping modules. Trying to use a pre-built outpost instead...");
|
||||
#endif
|
||||
|
||||
var outpostFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<OutpostFile>())
|
||||
.Where(f => !TutorialPrefab.Prefabs.Any(tp => tp.OutpostPath == f.Path))
|
||||
.OrderBy(f => f.UintIdentifier).ToArray();
|
||||
if (!outpostFiles.Any())
|
||||
{
|
||||
@@ -258,6 +264,7 @@ namespace Barotrauma
|
||||
sub = new Submarine(prebuiltOutpostInfo);
|
||||
sub.Info.OutpostGenerationParams = generationParams;
|
||||
location?.RemoveTakenItems();
|
||||
EnableFactionSpecificEntities(sub, location);
|
||||
return sub;
|
||||
|
||||
List<MapEntity> loadEntities(Submarine sub)
|
||||
@@ -296,18 +303,27 @@ namespace Barotrauma
|
||||
hull.SetModuleTags(selectedModule.Info.OutpostModuleInfo.ModuleFlags);
|
||||
}
|
||||
|
||||
selectedModule.HullBounds = new Rectangle(
|
||||
hullEntities.Min(e => e.WorldRect.X), hullEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height),
|
||||
hullEntities.Max(e => e.WorldRect.Right), hullEntities.Max(e => e.WorldRect.Y));
|
||||
selectedModule.HullBounds = new Rectangle(
|
||||
selectedModule.HullBounds.X, selectedModule.HullBounds.Y,
|
||||
selectedModule.HullBounds.Width - selectedModule.HullBounds.X, selectedModule.HullBounds.Height - selectedModule.HullBounds.Y);
|
||||
selectedModule.Bounds = new Rectangle(
|
||||
wallEntities.Min(e => e.WorldRect.X), wallEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height),
|
||||
wallEntities.Max(e => e.WorldRect.Right), wallEntities.Max(e => e.WorldRect.Y));
|
||||
selectedModule.Bounds = new Rectangle(
|
||||
selectedModule.Bounds.X, selectedModule.Bounds.Y,
|
||||
selectedModule.Bounds.Width - selectedModule.Bounds.X, selectedModule.Bounds.Height - selectedModule.Bounds.Y);
|
||||
if (!hullEntities.Any())
|
||||
{
|
||||
selectedModule.HullBounds = new Rectangle(Point.Zero, Submarine.GridSize.ToPoint());
|
||||
}
|
||||
else
|
||||
{
|
||||
Point min = new Point(hullEntities.Min(e => e.WorldRect.X), hullEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height));
|
||||
Point max = new Point(hullEntities.Max(e => e.WorldRect.Right), hullEntities.Max(e => e.WorldRect.Y));
|
||||
selectedModule.HullBounds = new Rectangle(min, max - min);
|
||||
}
|
||||
|
||||
if (!wallEntities.Any())
|
||||
{
|
||||
selectedModule.Bounds = new Rectangle(Point.Zero, Submarine.GridSize.ToPoint());
|
||||
}
|
||||
else
|
||||
{
|
||||
Point min = new Point(wallEntities.Min(e => e.WorldRect.X), wallEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height));
|
||||
Point max = new Point(wallEntities.Max(e => e.WorldRect.Right), wallEntities.Max(e => e.WorldRect.Y));
|
||||
selectedModule.Bounds = new Rectangle(min, max - min);
|
||||
}
|
||||
|
||||
if (selectedModule.PreviousModule != null)
|
||||
{
|
||||
@@ -396,6 +412,23 @@ namespace Barotrauma
|
||||
{
|
||||
LockUnusedDoors(selectedModules, entities, generationParams.RemoveUnusedGaps);
|
||||
}
|
||||
if (generationParams.DrawBehindSubs)
|
||||
{
|
||||
foreach (var entity in allEntities)
|
||||
{
|
||||
if (entity is Structure structure)
|
||||
{
|
||||
//eww
|
||||
structure.SpriteDepth = MathHelper.Lerp(0.999f, 0.9999f, structure.SpriteDepth);
|
||||
#if CLIENT
|
||||
foreach (var light in structure.Lights)
|
||||
{
|
||||
light.IsBackground = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
AlignLadders(selectedModules, entities);
|
||||
PowerUpOutpost(entities.SelectMany(e => e.Value));
|
||||
if (generationParams.MaxWaterPercentage > 0.0f)
|
||||
@@ -426,7 +459,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Select the number and types of the modules to use in the outpost
|
||||
/// </summary>
|
||||
private static List<Identifier> SelectModules(IEnumerable<SubmarineInfo> modules, OutpostGenerationParams generationParams)
|
||||
private static List<Identifier> SelectModules(IEnumerable<SubmarineInfo> modules, Location location, OutpostGenerationParams generationParams)
|
||||
{
|
||||
int totalModuleCount = generationParams.TotalModuleCount;
|
||||
var pendingModuleFlags = new List<Identifier>();
|
||||
@@ -437,23 +470,29 @@ namespace Barotrauma
|
||||
while (pendingModuleFlags.Count < totalModuleCount && availableModulesFound)
|
||||
{
|
||||
availableModulesFound = false;
|
||||
foreach (var moduleFlag in generationParams.ModuleCounts)
|
||||
foreach (var moduleCount in generationParams.ModuleCounts)
|
||||
{
|
||||
if (pendingModuleFlags.Count(m => m == moduleFlag.Identifier) >= generationParams.GetModuleCount(moduleFlag.Identifier))
|
||||
if (!moduleCount.RequiredFaction.IsEmpty &&
|
||||
location.Faction?.Prefab.Identifier != moduleCount.RequiredFaction &&
|
||||
location.SecondaryFaction?.Prefab.Identifier != moduleCount.RequiredFaction)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!modules.Any(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag.Identifier)))
|
||||
if (pendingModuleFlags.Count(m => m == moduleCount.Identifier) >= generationParams.GetModuleCount(moduleCount.Identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to add a module to the outpost (no modules with the flag \"{moduleFlag.Identifier}\" found).");
|
||||
continue;
|
||||
}
|
||||
if (!modules.Any(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleCount.Identifier)))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to add a module to the outpost (no modules with the flag \"{moduleCount.Identifier}\" found).");
|
||||
continue;
|
||||
}
|
||||
availableModulesFound = true;
|
||||
pendingModuleFlags.Add(moduleFlag.Identifier);
|
||||
pendingModuleFlags.Add(moduleCount.Identifier);
|
||||
}
|
||||
}
|
||||
pendingModuleFlags.OrderBy(f => generationParams.ModuleCounts.First(m => m.Identifier == f)).ThenBy(f => Rand.Value(Rand.RandSync.ServerAndClient));
|
||||
while (pendingModuleFlags.Count < totalModuleCount)
|
||||
pendingModuleFlags.OrderBy(f => generationParams.ModuleCounts.First(m => m.Identifier == f).Order).ThenBy(f => Rand.Value(Rand.RandSync.ServerAndClient));
|
||||
while (pendingModuleFlags.Count < totalModuleCount && generationParams.AppendToReachTotalModuleCount)
|
||||
{
|
||||
//don't place "none" modules at the end because
|
||||
// a. "filler rooms" at the end of a hallway are pointless
|
||||
@@ -696,6 +735,14 @@ namespace Barotrauma
|
||||
rect.Location += (module.Offset + module.MoveOffset).ToPoint();
|
||||
rect.Y += module.Bounds.Height;
|
||||
|
||||
Vector2? selfGapPos1 = null;
|
||||
Vector2? selfGapPos2 = null;
|
||||
if (module.PreviousModule != null)
|
||||
{
|
||||
selfGapPos1 = module.Offset + module.ThisGap.Position + module.MoveOffset;
|
||||
selfGapPos2 = module.PreviousModule.Offset + module.PreviousGap.Position + module.PreviousModule.MoveOffset;
|
||||
}
|
||||
|
||||
foreach (PlacedModule otherModule in modules)
|
||||
{
|
||||
if (otherModule == module || otherModule.PreviousModule == null || otherModule.PreviousModule == module) { continue; }
|
||||
@@ -710,7 +757,17 @@ namespace Barotrauma
|
||||
|
||||
Vector2 gapPos1 = otherModule.Offset + otherModule.ThisGap.Position + gapEdgeOffset + otherModule.MoveOffset;
|
||||
Vector2 gapPos2 = otherModule.PreviousModule.Offset + otherModule.PreviousGap.Position + gapEdgeOffset + otherModule.PreviousModule.MoveOffset;
|
||||
if (Submarine.RectContains(rect, gapPos1) || Submarine.RectContains(rect, gapPos2) || MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
|
||||
if (Submarine.RectContains(rect, gapPos1) ||
|
||||
Submarine.RectContains(rect, gapPos2) ||
|
||||
MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//check if the connection overlaps with this module's connection
|
||||
if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
|
||||
!gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
|
||||
MathUtils.LinesIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1362,6 +1419,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnableFactionSpecificEntities(Submarine sub, Location location)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != sub) { continue; }
|
||||
|
||||
var layerAsIdentifier = me.Layer.ToIdentifier();
|
||||
if (FactionPrefab.Prefabs.ContainsKey(layerAsIdentifier))
|
||||
{
|
||||
me.HiddenInGame =
|
||||
location?.Faction?.Prefab != FactionPrefab.Prefabs[layerAsIdentifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities, bool removeUnusedGaps)
|
||||
{
|
||||
foreach (PlacedModule module in placedModules)
|
||||
@@ -1564,7 +1636,12 @@ namespace Barotrauma
|
||||
List<HumanPrefab> killedCharacters = new List<HumanPrefab>();
|
||||
List<(HumanPrefab HumanPrefab, CharacterInfo CharacterInfo)> selectedCharacters
|
||||
= new List<(HumanPrefab HumanPrefab, CharacterInfo CharacterInfo)>();
|
||||
var humanPrefabs = outpost.Info.OutpostGenerationParams.GetHumanPrefabs(Rand.RandSync.ServerAndClient);
|
||||
|
||||
List<FactionPrefab> factions = new List<FactionPrefab>();
|
||||
if (location?.Faction != null) { factions.Add(location.Faction.Prefab); }
|
||||
if (location?.SecondaryFaction != null) { factions.Add(location.SecondaryFaction.Prefab); }
|
||||
|
||||
var humanPrefabs = outpost.Info.OutpostGenerationParams.GetHumanPrefabs(factions, Rand.RandSync.ServerAndClient);
|
||||
foreach (HumanPrefab humanPrefab in humanPrefabs)
|
||||
{
|
||||
if (humanPrefab is null) { continue; }
|
||||
@@ -1583,7 +1660,7 @@ namespace Barotrauma
|
||||
for (int tries = 0; tries < 100; tries++)
|
||||
{
|
||||
var characterInfo = killedCharacter.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
|
||||
if (!location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
|
||||
if (location != null && !location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
|
||||
{
|
||||
selectedCharacters.Add((killedCharacter, characterInfo));
|
||||
break;
|
||||
@@ -1605,11 +1682,11 @@ namespace Barotrauma
|
||||
npc.AnimController.FindHull(gotoTarget.WorldPosition, setSubmarine: true);
|
||||
npc.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
npc.HumanPrefab = humanPrefab;
|
||||
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
|
||||
outpost.Info.AddOutpostNPCIdentifierOrTag(npc, humanPrefab.Identifier);
|
||||
foreach (Identifier tag in humanPrefab.GetTags())
|
||||
{
|
||||
outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List<Character>());
|
||||
outpost.Info.AddOutpostNPCIdentifierOrTag(npc, tag);
|
||||
}
|
||||
outpost.Info.OutpostNPCs[humanPrefab.Identifier].Add(npc);
|
||||
if (GameMain.NetworkMember?.ServerSettings != null && !GameMain.NetworkMember.ServerSettings.KillableNPCs)
|
||||
{
|
||||
npc.CharacterHealth.Unkillable = true;
|
||||
|
||||
@@ -27,11 +27,20 @@ namespace Barotrauma
|
||||
public bool DisplayNonEmpty { get; } = false;
|
||||
public Identifier StoreIdentifier { get; }
|
||||
|
||||
public bool RequiresUnlock { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used when both <see cref="MinAvailableAmount"/> and <see cref="MaxAvailableAmount"/> are set to 0.
|
||||
/// </summary>
|
||||
public const int DefaultAmount = 5;
|
||||
|
||||
private readonly Dictionary<Identifier, float> minReputation = new Dictionary<Identifier, float>();
|
||||
|
||||
/// <summary>
|
||||
/// Minimum reputation needed to buy the item (Key = faction ID, Value = min rep)
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<Identifier, float> MinReputation => minReputation;
|
||||
|
||||
/// <summary>
|
||||
/// Support for the old style of determining item prices
|
||||
/// when there were individual Price elements for each location type
|
||||
@@ -48,11 +57,12 @@ namespace Barotrauma
|
||||
int maxAmount = GetMaxAmount(element);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
RequiresUnlock = element.GetAttributeBool("requiresunlock", false);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought,
|
||||
int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f,
|
||||
bool displayNonEmpty = false, string storeIdentifier = null)
|
||||
bool displayNonEmpty = false, bool requiresUnlock = false, string storeIdentifier = null)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
@@ -64,6 +74,20 @@ namespace Barotrauma
|
||||
CanBeSpecial = canBeSpecial;
|
||||
DisplayNonEmpty = displayNonEmpty;
|
||||
StoreIdentifier = new Identifier(storeIdentifier);
|
||||
RequiresUnlock = requiresUnlock;
|
||||
}
|
||||
|
||||
private void LoadReputationRestrictions(XElement priceInfoElement)
|
||||
{
|
||||
foreach (XElement childElement in priceInfoElement.GetChildElements("reputation"))
|
||||
{
|
||||
Identifier factionId = childElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
float rep = childElement.GetAttributeFloat("min", 0.0f);
|
||||
if (!factionId.IsEmpty && rep > 0)
|
||||
{
|
||||
minReputation.Add(factionId, rep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<PriceInfo> CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
|
||||
@@ -78,6 +102,7 @@ namespace Barotrauma
|
||||
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false);
|
||||
bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true));
|
||||
bool requiresUnlock = element.GetAttributeBool("requiresunlock", false);
|
||||
foreach (XElement childElement in element.GetChildElements("price"))
|
||||
{
|
||||
float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
@@ -91,26 +116,30 @@ namespace Barotrauma
|
||||
}
|
||||
string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier);
|
||||
// TODO: Add some error messages if we have defined the min or max amount while the item is not sold
|
||||
var priceInfo = new PriceInfo((int)(priceMultiplier * basePrice),
|
||||
sold,
|
||||
sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial,
|
||||
storeMinLevelDifficulty,
|
||||
storeBuyingMultiplier,
|
||||
displayNonEmpty,
|
||||
storeIdentifier);
|
||||
var priceInfo = new PriceInfo(price: (int)(priceMultiplier * basePrice),
|
||||
canBeBought: sold,
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial: canBeSpecial,
|
||||
minLevelDifficulty: storeMinLevelDifficulty,
|
||||
buyingPriceMultiplier: storeBuyingMultiplier,
|
||||
displayNonEmpty: displayNonEmpty,
|
||||
requiresUnlock: requiresUnlock,
|
||||
storeIdentifier: storeIdentifier);
|
||||
priceInfo.LoadReputationRestrictions(childElement);
|
||||
priceInfos.Add(priceInfo);
|
||||
}
|
||||
bool soldElsewhere = soldByDefault && element.GetAttributeBool("soldelsewhere", element.GetAttributeBool("soldeverywhere", false));
|
||||
defaultPrice = new PriceInfo(basePrice,
|
||||
soldElsewhere,
|
||||
soldElsewhere ? minAmount : 0,
|
||||
soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial,
|
||||
minLevelDifficulty,
|
||||
buyingPriceMultiplier,
|
||||
displayNonEmpty);
|
||||
defaultPrice = new PriceInfo(price: basePrice,
|
||||
canBeBought: soldElsewhere,
|
||||
minAmount: soldElsewhere ? minAmount : 0,
|
||||
maxAmount: soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial: canBeSpecial,
|
||||
minLevelDifficulty: minLevelDifficulty,
|
||||
buyingPriceMultiplier: buyingPriceMultiplier,
|
||||
displayNonEmpty: displayNonEmpty,
|
||||
requiresUnlock: requiresUnlock);
|
||||
defaultPrice.LoadReputationRestrictions(element);
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
|
||||
@@ -213,9 +213,21 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Level.Loaded == null) { return false; }
|
||||
if (Level.Loaded.EndOutpost != null && DockedTo.Contains(Level.Loaded.EndOutpost))
|
||||
if (Level.Loaded.EndOutpost != null)
|
||||
{
|
||||
return true;
|
||||
if (DockedTo.Contains(Level.Loaded.EndOutpost))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (Level.Loaded.EndOutpost.exitPoints.Any())
|
||||
{
|
||||
return IsAtOutpostExit(Level.Loaded.EndOutpost);
|
||||
}
|
||||
}
|
||||
else if (Level.Loaded.Type == LevelData.LevelType.Outpost && Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
//in outpost levels, the outpost is always the start outpost: check it if has an exit
|
||||
return IsAtOutpostExit(Level.Loaded.StartOutpost);
|
||||
}
|
||||
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.EndExitPosition) < Level.ExitDistance * Level.ExitDistance);
|
||||
}
|
||||
@@ -226,14 +238,44 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Level.Loaded == null) { return false; }
|
||||
if (Level.Loaded.StartOutpost != null && DockedTo.Contains(Level.Loaded.StartOutpost))
|
||||
if (Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
return true;
|
||||
if (DockedTo.Contains(Level.Loaded.StartOutpost))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (Level.Loaded.StartOutpost.exitPoints.Any())
|
||||
{
|
||||
return IsAtOutpostExit(Level.Loaded.StartOutpost);
|
||||
}
|
||||
}
|
||||
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.StartExitPosition) < Level.ExitDistance * Level.ExitDistance);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AtEitherExit => AtStartExit || AtEndExit;
|
||||
|
||||
private bool IsAtOutpostExit(Submarine outpost)
|
||||
{
|
||||
if (outpost.exitPoints.Any())
|
||||
{
|
||||
Rectangle worldBorders = Borders;
|
||||
worldBorders.Location += WorldPosition.ToPoint();
|
||||
foreach (var exitPoint in outpost.exitPoints)
|
||||
{
|
||||
if (exitPoint.ExitPointSize != Point.Zero)
|
||||
{
|
||||
if (RectsOverlap(worldBorders, exitPoint.ExitPointWorldRect)) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RectContains(worldBorders, exitPoint.WorldPosition)) { return true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public new Vector2 DrawPosition
|
||||
{
|
||||
@@ -284,6 +326,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<WayPoint> exitPoints = new List<WayPoint>();
|
||||
public IReadOnlyList<WayPoint> ExitPoints { get { return exitPoints; } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Barotrauma.Submarine (" + (Info?.Name ?? "[NULL INFO]") + ", " + IdOffset + ")";
|
||||
@@ -350,12 +395,23 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public WreckAI WreckAI { get; private set; }
|
||||
public SubmarineTurretAI TurretAI { get; private set; }
|
||||
|
||||
public bool CreateWreckAI()
|
||||
{
|
||||
WreckAI = WreckAI.Create(this);
|
||||
return WreckAI != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AI that operates all the turrets on a sub, same as Thalamus but only operates the turrets.
|
||||
/// </summary>
|
||||
public bool CreateTurretAI()
|
||||
{
|
||||
TurretAI = new SubmarineTurretAI(this);
|
||||
return TurretAI != null;
|
||||
}
|
||||
|
||||
public void DisableWreckAI()
|
||||
{
|
||||
if (WreckAI == null)
|
||||
@@ -991,6 +1047,7 @@ namespace Barotrauma
|
||||
{
|
||||
WreckAI?.Update(deltaTime);
|
||||
}
|
||||
TurretAI?.Update(deltaTime);
|
||||
|
||||
if (subBody?.Body == null) { return; }
|
||||
|
||||
@@ -1114,7 +1171,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Submarine != this) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || !item.HasTag("ballast") || item.CurrentHull == null) { continue; }
|
||||
if (pump == null || item.CurrentHull == null) { continue; }
|
||||
if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
pump.FlowPercentage = 0.0f;
|
||||
ballastHulls.Add(item.CurrentHull);
|
||||
}
|
||||
@@ -1388,7 +1446,6 @@ namespace Barotrauma
|
||||
if (info.IsOutpost)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
bool indestructible =
|
||||
@@ -1459,10 +1516,15 @@ namespace Barotrauma
|
||||
MapEntity.MapLoaded(newEntities, true);
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this)
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is LinkedSubmarine linkedSub)
|
||||
{
|
||||
linkedSub.LinkDummyToMainSubmarine();
|
||||
}
|
||||
else if (me is WayPoint wayPoint && wayPoint.SpawnType.HasFlag(SpawnType.ExitPoint))
|
||||
{
|
||||
exitPoints.Add(wayPoint);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Hull hull in matchingHulls)
|
||||
|
||||
@@ -18,6 +18,13 @@ namespace Barotrauma
|
||||
{
|
||||
public const float NeutralBallastPercentage = 0.07f;
|
||||
|
||||
public const Category CollidesWith =
|
||||
Physics.CollisionItem |
|
||||
Physics.CollisionLevel |
|
||||
Physics.CollisionCharacter |
|
||||
Physics.CollisionProjectile |
|
||||
Physics.CollisionWall;
|
||||
|
||||
const float HorizontalDrag = 0.01f;
|
||||
const float VerticalDrag = 0.05f;
|
||||
const float MaxDrag = 0.1f;
|
||||
@@ -146,9 +153,13 @@ namespace Barotrauma
|
||||
farseerBody.CollidesWith = collidesWith;
|
||||
farseerBody.Enabled = false;
|
||||
farseerBody.UserData = this;
|
||||
if (sub.Info.IsOutpost)
|
||||
{
|
||||
farseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
foreach (var mapEntity in MapEntity.mapEntityList)
|
||||
{
|
||||
if (mapEntity.Submarine != submarine || !(mapEntity is Structure wall)) { continue; }
|
||||
if (mapEntity.Submarine != submarine || mapEntity is not Structure wall) { continue; }
|
||||
|
||||
bool hasCollider = wall.HasBody && !wall.IsPlatform && wall.StairDirection == Direction.None;
|
||||
Rectangle rect = wall.Rect;
|
||||
@@ -185,13 +196,20 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != submarine) { continue; }
|
||||
if (item.StaticBodyConfig == null || item.Submarine != submarine) { continue; }
|
||||
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(item.Position);
|
||||
if (item.GetComponent<Door>() is Door door)
|
||||
{
|
||||
door.OutsideSubmarineFixture = farseerBody.CreateRectangle(door.Body.Width, door.Body.Height, 5.0f, simPos, collisionCategory, collidesWith);
|
||||
door.OutsideSubmarineFixture.UserData = item;
|
||||
}
|
||||
|
||||
if (item.StaticBodyConfig == null) { continue; }
|
||||
|
||||
float radius = item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f) * item.Scale;
|
||||
float width = item.StaticBodyConfig.GetAttributeFloat("width", 0.0f) * item.Scale;
|
||||
float height = item.StaticBodyConfig.GetAttributeFloat("height", 0.0f) * item.Scale;
|
||||
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(item.Position);
|
||||
float simRadius = ConvertUnits.ToSimUnits(radius);
|
||||
float simWidth = ConvertUnits.ToSimUnits(width);
|
||||
float simHeight = ConvertUnits.ToSimUnits(height);
|
||||
@@ -623,7 +641,7 @@ namespace Barotrauma
|
||||
attackMultiplier = enemyAI.ActiveAttack.SubmarineImpactMultiplier;
|
||||
}
|
||||
|
||||
if (impactMass * attackMultiplier > MinImpactLimbMass)
|
||||
if (impactMass * attackMultiplier > MinImpactLimbMass && Body.BodyType != BodyType.Static)
|
||||
{
|
||||
Vector2 normal =
|
||||
Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
#if DEBUG
|
||||
using System.IO;
|
||||
@@ -314,6 +315,7 @@ namespace Barotrauma
|
||||
Tier = original.Tier;
|
||||
IsManuallyOutfitted = original.IsManuallyOutfitted;
|
||||
Tags = original.Tags;
|
||||
OutpostGenerationParams = original.OutpostGenerationParams;
|
||||
if (original.OutpostModuleInfo != null)
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
@@ -554,6 +556,14 @@ namespace Barotrauma
|
||||
}
|
||||
return realWorldCrushDepth;
|
||||
}
|
||||
public void AddOutpostNPCIdentifierOrTag(Character npc, Identifier idOrTag)
|
||||
{
|
||||
if (!OutpostNPCs.ContainsKey(idOrTag))
|
||||
{
|
||||
OutpostNPCs.Add(idOrTag, new List<Character>());
|
||||
}
|
||||
OutpostNPCs[idOrTag].Add(npc);
|
||||
}
|
||||
|
||||
//saving/loading ----------------------------------------------------
|
||||
public void SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
|
||||
@@ -747,6 +757,36 @@ namespace Barotrauma
|
||||
return doc;
|
||||
}
|
||||
|
||||
public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1;
|
||||
public int GetPrice(Location location = null, ImmutableHashSet<Character> characterList = null)
|
||||
{
|
||||
if (location is null)
|
||||
{
|
||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation is { } currentLocation)
|
||||
{
|
||||
location = currentLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return Price;
|
||||
}
|
||||
}
|
||||
|
||||
characterList ??= GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
|
||||
float price = Price;
|
||||
|
||||
if (location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(faction, characterList) is FactionAffiliation.Positive)
|
||||
{
|
||||
price *= 1f - characterList.Max(static c => c.GetStatValue(StatTypes.ShipyardBuyMultiplierAffiliated));
|
||||
}
|
||||
price *= 1f - characterList.Max(static c => c.GetStatValue(StatTypes.ShipyardBuyMultiplier));
|
||||
|
||||
return (int)price;
|
||||
}
|
||||
|
||||
public static int GetDefaultTier(int price) => price > 20000 ? HighestTier : price > 10000 ? 2 : 1;
|
||||
|
||||
public const int HighestTier = 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using Barotrauma.Extensions;
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8 };
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32 };
|
||||
|
||||
partial class WayPoint : MapEntity
|
||||
{
|
||||
@@ -54,6 +54,12 @@ namespace Barotrauma
|
||||
set { spawnType = value; }
|
||||
}
|
||||
|
||||
public Point ExitPointSize { get; private set; }
|
||||
|
||||
public Rectangle ExitPointWorldRect => new Rectangle(
|
||||
(int)WorldPosition.X - ExitPointSize.X / 2, (int)WorldPosition.Y + ExitPointSize.Y / 2,
|
||||
ExitPointSize.X, ExitPointSize.Y);
|
||||
|
||||
public Action<WayPoint> OnLinksChanged { get; set; }
|
||||
|
||||
public override string Name
|
||||
@@ -140,7 +146,9 @@ namespace Barotrauma
|
||||
{ "Cargo", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384,0,128,128)) },
|
||||
{ "Corpse", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512,0,128,128)) },
|
||||
{ "Ladder", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,128,128,128)) },
|
||||
{ "Door", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,128,128,128)) }
|
||||
{ "Door", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,128,128,128)) },
|
||||
{ "Submarine", new Sprite("Content/UI/CommandUIBackground.png", new Rectangle(0,896,128,128)) },
|
||||
{ "ExitPoint", new Sprite("Content/UI/CommandUIBackground.png", new Rectangle(0,896,128,128)) }
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1018,7 +1026,6 @@ namespace Barotrauma
|
||||
int.Parse(element.GetAttribute("y").Value),
|
||||
(int)Submarine.GridSize.X, (int)Submarine.GridSize.Y);
|
||||
|
||||
|
||||
Enum.TryParse(element.GetAttributeString("spawn", "Path"), out SpawnType spawnType);
|
||||
WayPoint w = new WayPoint(spawnType == SpawnType.Path ? Type.WayPoint : Type.SpawnPoint, rect, submarine, idRemap.GetOffsetId(element))
|
||||
{
|
||||
@@ -1036,6 +1043,8 @@ namespace Barotrauma
|
||||
w.IdCardTags = idCardTagString.Split(',');
|
||||
}
|
||||
|
||||
w.ExitPointSize = element.GetAttributePoint("exitpointsize", Point.Zero);
|
||||
|
||||
w.tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToHashSet();
|
||||
|
||||
Identifier jobIdentifier = element.GetAttributeIdentifier("job", Identifier.Empty);
|
||||
@@ -1076,6 +1085,10 @@ namespace Barotrauma
|
||||
new XAttribute("x", (int)(rect.X - Submarine.HiddenSubPosition.X)),
|
||||
new XAttribute("y", (int)(rect.Y - Submarine.HiddenSubPosition.Y)),
|
||||
new XAttribute("spawn", spawnType));
|
||||
if (SpawnType == SpawnType.ExitPoint)
|
||||
{
|
||||
element.Add(new XAttribute("exitpointsize", XMLExtensions.PointToString(ExitPointSize)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(IdCardDesc)) element.Add(new XAttribute("idcarddesc", IdCardDesc));
|
||||
if (idCardTags.Length > 0)
|
||||
|
||||
Reference in New Issue
Block a user