v0.12.0.2
This commit is contained in:
@@ -91,6 +91,9 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
|
||||
#endif
|
||||
|
||||
private static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
|
||||
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
|
||||
|
||||
public enum NetworkHeader
|
||||
{
|
||||
Spawn,
|
||||
@@ -199,6 +202,9 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
[Serialize(5f, true, "How much damage is taken from open fires")]
|
||||
public float FireVulnerability { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, "How much resistance against fire is gained while submerged.")]
|
||||
public float SubmergedWaterResistance { get; set; }
|
||||
|
||||
[Serialize(0.8f, true, "What depth the branches will be drawn on")]
|
||||
public float BranchDepth { get; set; }
|
||||
|
||||
@@ -295,6 +301,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
LoadPrefab(prefab.Element);
|
||||
StateMachine = new BallastFloraStateMachine(this);
|
||||
if (firstGrowth) { GenerateStem(); }
|
||||
_entityList.Add(this);
|
||||
}
|
||||
|
||||
partial void LoadPrefab(XElement element);
|
||||
@@ -427,7 +434,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
GUI.AddMessage($"{(int)branch.AccumulatedDamage}", GUI.Style.Red, GetWorldPosition() + branch.Position, Vector2.UnitY * 10.0f, 3f, playSound: false);
|
||||
var pos = (Parent?.Position ?? Vector2.Zero) + Offset + branch.Position;
|
||||
GUI.AddMessage($"{(int)branch.AccumulatedDamage}", GUI.Style.Red, pos, Vector2.UnitY * 10.0f, 3f, playSound: false, subId: Parent?.Submarine?.ID ?? -1);
|
||||
}
|
||||
#elif SERVER
|
||||
SendNetworkMessage(this, NetworkHeader.BranchDamage, branch, branch.AccumulatedDamage);
|
||||
@@ -469,7 +477,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UpdateSelfDamage(deltaTime);
|
||||
|
||||
if (Anger > 1f)
|
||||
@@ -862,6 +870,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
public void DamageBranch(BallastFloraBranch branch, float amount, AttackType type, Character? attacker = null)
|
||||
{
|
||||
float damage = amount;
|
||||
// damage is handled server side currently
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
@@ -875,7 +884,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
if (IsInWater(branch))
|
||||
{
|
||||
return;
|
||||
damage *= 1f - SubmergedWaterResistance;
|
||||
}
|
||||
|
||||
if (defenseCooldown <= 0)
|
||||
@@ -883,24 +892,24 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
if (!(StateMachine.State is DefendWithPumpState))
|
||||
{
|
||||
StateMachine.EnterState(new DefendWithPumpState(branch, ClaimedTargets, attacker));
|
||||
defenseCooldown = 60f;
|
||||
defenseCooldown = 180f;
|
||||
}
|
||||
|
||||
defenseCooldown = 10f;
|
||||
}
|
||||
}
|
||||
|
||||
branch.AccumulatedDamage += amount;
|
||||
branch.AccumulatedDamage += damage;
|
||||
|
||||
branch.Health -= amount;
|
||||
branch.Health -= damage;
|
||||
|
||||
if (type != AttackType.Other)
|
||||
{
|
||||
Anger += amount * 0.001f;
|
||||
Anger += damage * 0.001f;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
GameMain.Server?.KarmaManager?.OnBallastFloraDamaged(attacker, amount);
|
||||
GameMain.Server?.KarmaManager?.OnBallastFloraDamaged(attacker, damage);
|
||||
#endif
|
||||
|
||||
if (branch.Health < 0)
|
||||
@@ -923,6 +932,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
target.Infector = null;
|
||||
}
|
||||
|
||||
_entityList.Remove(this);
|
||||
}
|
||||
|
||||
public void RemoveBranch(BallastFloraBranch branch)
|
||||
@@ -1028,6 +1039,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
target.Infector = null;
|
||||
}
|
||||
|
||||
StateMachine?.State?.Exit();
|
||||
|
||||
// clean up leftover (can probably be removed)
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
|
||||
@@ -12,8 +12,9 @@ namespace Barotrauma
|
||||
public const ushort NullEntityID = 0;
|
||||
public const ushort EntitySpawnerID = ushort.MaxValue;
|
||||
public const ushort RespawnManagerID = ushort.MaxValue - 1;
|
||||
public const ushort DummyID = ushort.MaxValue - 2;
|
||||
|
||||
public const ushort ReservedIDStart = ushort.MaxValue - 2;
|
||||
public const ushort ReservedIDStart = ushort.MaxValue - 3;
|
||||
|
||||
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
public static IEnumerable<Entity> GetEntities()
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Barotrauma
|
||||
private readonly float screenColorRange, screenColorDuration;
|
||||
|
||||
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
|
||||
private bool playTinnitus;
|
||||
private bool applyFireEffects;
|
||||
private readonly float flashDuration;
|
||||
private readonly float? flashRange;
|
||||
@@ -63,6 +64,8 @@ namespace Barotrauma
|
||||
underwaterBubble = element.GetAttributeBool("underwaterbubble", true);
|
||||
smoke = element.GetAttributeBool("smoke", true);
|
||||
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", true);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames);
|
||||
|
||||
flash = element.GetAttributeBool("flash", true);
|
||||
@@ -225,7 +228,7 @@ namespace Barotrauma
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
|
||||
{
|
||||
if (attack.Range <= 0.0f) { return; }
|
||||
|
||||
@@ -323,10 +326,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (c == Character.Controlled && !c.IsDead)
|
||||
if (c == Character.Controlled && !c.IsDead && playTinnitus)
|
||||
{
|
||||
Limb head = c.AnimController.GetLimb(LimbType.Head);
|
||||
if (damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor))
|
||||
if (head != null && damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor))
|
||||
{
|
||||
PlayTinnitusProjSpecific(headFactor);
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ namespace Barotrauma
|
||||
//GetApproximateDistance returns float.MaxValue if there's no path through open gaps between the hulls (e.g. if there's a door/wall in between)
|
||||
if (hull.GetApproximateDistance(Position, c.Position, c.CurrentHull, 10000.0f) > size.X + DamageRange)
|
||||
{
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
float dmg = (float)Math.Sqrt(Math.Min(500, size.X)) * deltaTime / c.AnimController.Limbs.Count(l => !l.IsSevered && !l.Hidden);
|
||||
@@ -346,11 +346,17 @@ namespace Barotrauma
|
||||
//don't apply OnFire effects if the item is inside a fireproof container
|
||||
//(or if it's inside a container that's inside a fireproof container, etc)
|
||||
Item container = item.Container;
|
||||
bool fireProof = false;
|
||||
while (container != null)
|
||||
{
|
||||
if (container.FireProof) return;
|
||||
if (container.FireProof)
|
||||
{
|
||||
fireProof = true;
|
||||
break;
|
||||
}
|
||||
container = container.Container;
|
||||
}
|
||||
if (fireProof) { continue; }
|
||||
|
||||
float range = (float)Math.Sqrt(size.X) * 10.0f;
|
||||
if (item.Position.X < position.X - range || item.Position.X > position.X + size.X + range) { continue; }
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -120,10 +118,17 @@ namespace Barotrauma
|
||||
return "Gap";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Gap(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (rectangle, Submarine.MainSub)
|
||||
{ }
|
||||
: this(rectangle, Submarine.MainSub)
|
||||
{
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { this }, false));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public Gap(Rectangle rect, Submarine submarine)
|
||||
: this(rect, rect.Width < rect.Height, submarine)
|
||||
@@ -233,6 +238,13 @@ namespace Barotrauma
|
||||
{
|
||||
Hull[] hulls = new Hull[2];
|
||||
|
||||
foreach (var linked in linkedTo)
|
||||
{
|
||||
if (linked is Hull hull)
|
||||
{
|
||||
hull.ConnectedGaps.Remove(this);
|
||||
}
|
||||
}
|
||||
linkedTo.Clear();
|
||||
|
||||
Vector2[] searchPos = new Vector2[2];
|
||||
@@ -595,7 +607,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 rayStart = ConvertUnits.ToSimUnits(WorldPosition);
|
||||
Vector2 rayEnd = rayStart + rayDir * 500.0f;
|
||||
Vector2 rayEnd = rayStart + rayDir * 5.0f;
|
||||
|
||||
var levelCells = Level.Loaded.GetCells(WorldPosition, searchDepth: 1);
|
||||
foreach (var cell in levelCells)
|
||||
|
||||
@@ -397,7 +397,12 @@ namespace Barotrauma
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (prefab, rectangle, Submarine.MainSub)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { this }, false));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
@@ -791,7 +796,7 @@ namespace Barotrauma
|
||||
//make waves propagate through horizontal gaps
|
||||
foreach (Gap gap in ConnectedGaps)
|
||||
{
|
||||
if (this != gap.linkedTo[0] as Hull)
|
||||
if (this != gap.linkedTo.FirstOrDefault() as Hull)
|
||||
{
|
||||
//let the first linked hull handle the water propagation
|
||||
continue;
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Barotrauma
|
||||
Vector2 WorldPosition { get; }
|
||||
Vector2 SimPosition { get; }
|
||||
Submarine Submarine { get; }
|
||||
bool IgnoreByAI => false;
|
||||
}
|
||||
|
||||
interface IIgnorable : ISpatialEntity
|
||||
{
|
||||
bool IgnoreByAI { get; }
|
||||
bool OrderedToBeIgnored { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,21 @@ namespace Barotrauma
|
||||
var containerElement = entityElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("itemcontainer", StringComparison.OrdinalIgnoreCase));
|
||||
if (containerElement == null) { continue; }
|
||||
|
||||
var itemIds = containerElement.GetAttributeIntArray("contained", new int[0]);
|
||||
containedItemIDs.AddRange(itemIds.Select(id => (ushort)id));
|
||||
string containedString = containerElement.GetAttributeString("contained", "");
|
||||
string[] itemIdStrings = containedString.Split(',');
|
||||
var itemIds = new List<ushort>[itemIdStrings.Length];
|
||||
for (int i = 0; i < itemIdStrings.Length; i++)
|
||||
{
|
||||
itemIds[i] ??= new List<ushort>();
|
||||
foreach (string idStr in itemIdStrings[i].Split(';'))
|
||||
{
|
||||
if (int.TryParse(idStr, out int id))
|
||||
{
|
||||
itemIds[i].Add((ushort)id);
|
||||
containedItemIDs.Add((ushort)id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int minX = int.MaxValue, minY = int.MaxValue;
|
||||
@@ -110,16 +123,18 @@ namespace Barotrauma
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
{
|
||||
var loaded = CreateInstance(rect.Location.ToVector2(), Submarine.MainSub);
|
||||
#if CLIENT
|
||||
var loaded = CreateInstance(rect.Location.ToVector2(), Submarine.MainSub, selectInstance: Screen.Selected == GameMain.SubEditorScreen);
|
||||
if (Screen.Selected is SubEditorScreen)
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(loaded, false, handleInventoryBehavior: false));
|
||||
}
|
||||
#else
|
||||
var loaded = CreateInstance(rect.Location.ToVector2(), Submarine.MainSub);
|
||||
#endif
|
||||
}
|
||||
|
||||
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectPrefabs = false)
|
||||
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectInstance = false)
|
||||
{
|
||||
int idOffset = Entity.FindFreeID(1);
|
||||
if (MapEntity.mapEntityList.Any()) { idOffset = MapEntity.mapEntityList.Max(e => e.ID); }
|
||||
@@ -148,7 +163,7 @@ namespace Barotrauma
|
||||
|
||||
MapEntity.MapLoaded(entities, true);
|
||||
#if CLIENT
|
||||
if (Screen.Selected == GameMain.SubEditorScreen && selectPrefabs)
|
||||
if (Screen.Selected == GameMain.SubEditorScreen && selectInstance)
|
||||
{
|
||||
MapEntity.SelectedList.Clear();
|
||||
entities.ForEach(MapEntity.AddSelection);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -135,6 +136,13 @@ namespace Barotrauma
|
||||
case "walledge":
|
||||
WallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = subElement.GetAttributeString("leveltype", "").ToLowerInvariant();
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,5 +201,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
foreach (KeyValuePair<string, float> overrideCommonness in OverrideCommonness)
|
||||
{
|
||||
bool elementFound = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("overridecommonness", StringComparison.OrdinalIgnoreCase)
|
||||
&& subElement.GetAttributeString("leveltype", "").Equals(overrideCommonness.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
subElement.Attribute("commonness").Value = overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture);
|
||||
elementFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!elementFound)
|
||||
{
|
||||
element.Add(new XElement("overridecommonness",
|
||||
new XAttribute("leveltype", overrideCommonness.Key),
|
||||
new XAttribute("commonness", overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,14 +169,16 @@ namespace Barotrauma
|
||||
sw2.Start();
|
||||
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
|
||||
if (targetCells.Count == 0) { return pathCells; }
|
||||
|
||||
VoronoiCell currentCell = targetCells[0];
|
||||
currentCell.CellType = CellType.Path;
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
int currentTargetIndex = 0;
|
||||
|
||||
int iterationsLeft = cells.Count;
|
||||
int iterationsLeft = cells.Count / 2;
|
||||
|
||||
do
|
||||
{
|
||||
@@ -189,10 +191,14 @@ namespace Barotrauma
|
||||
if (adjacentCell == null) { continue; }
|
||||
double dist = MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
|
||||
dist += MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, currentCell.Site.Coord.X, currentCell.Site.Coord.Y) * 0.5f;
|
||||
//disfavor small edges to prevent generating a very small passage
|
||||
if (Vector2.Distance(currentCell.Edges[i].Point1, currentCell.Edges[i].Point2) < 200.0f)
|
||||
|
||||
//disfavor short edges to prevent generating a very small passage
|
||||
if (Vector2.DistanceSquared(currentCell.Edges[i].Point1, currentCell.Edges[i].Point2) < 150.0f * 150.0f)
|
||||
{
|
||||
dist += 1000000;
|
||||
//divide by the number of times the current cell has been used
|
||||
// prevents the path from getting "stuck" (jumping back and forth between adjacent cells)
|
||||
// if there's no other way to the destination than going through a short edge
|
||||
dist *= 10.0f / Math.Max(pathCells.Count(c => c == currentCell), 1.0f);
|
||||
}
|
||||
if (dist < smallestDist)
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Barotrauma
|
||||
public DestructibleLevelWall(List<Vector2> vertices, Color color, Level level, float? health = null, bool giftWrap = false)
|
||||
: base (vertices, color, level, giftWrap)
|
||||
{
|
||||
MaxHealth = health ?? MathHelper.Clamp(Body.Mass, 100.0f, 1000.0f);
|
||||
MaxHealth = health ?? MathHelper.Clamp(Body.Mass * 0.5f, 50.0f, 1000.0f);
|
||||
Cells.ForEach(c => c.IsDestructible = true);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,14 +45,35 @@ namespace Barotrauma
|
||||
public bool IsValid;
|
||||
public Submarine Submarine;
|
||||
public Ruin Ruin;
|
||||
public Cave Cave;
|
||||
|
||||
public InterestingPosition(Point position, PositionType positionType, bool isValid = true, Submarine submarine = null, Ruin ruin = null)
|
||||
public InterestingPosition(Point position, PositionType positionType, Submarine submarine = null, bool isValid = true)
|
||||
{
|
||||
Position = position;
|
||||
PositionType = positionType;
|
||||
IsValid = isValid;
|
||||
Submarine = submarine;
|
||||
Ruin = null;
|
||||
Cave = null;
|
||||
}
|
||||
|
||||
public InterestingPosition(Point position, PositionType positionType, Ruin ruin, bool isValid = true)
|
||||
{
|
||||
Position = position;
|
||||
PositionType = positionType;
|
||||
IsValid = isValid;
|
||||
Submarine = null;
|
||||
Ruin = ruin;
|
||||
Cave = null;
|
||||
}
|
||||
public InterestingPosition(Point position, PositionType positionType, Cave cave, bool isValid = true)
|
||||
{
|
||||
Position = position;
|
||||
PositionType = positionType;
|
||||
IsValid = isValid;
|
||||
Submarine = null;
|
||||
Ruin = null;
|
||||
Cave = cave;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +127,8 @@ namespace Barotrauma
|
||||
|
||||
public Point StartPos, EndPos;
|
||||
|
||||
public bool DisplayOnSonar;
|
||||
|
||||
public readonly CaveGenerationParams CaveGenerationParams;
|
||||
|
||||
public Cave(CaveGenerationParams caveGenerationParams, Rectangle area, Point startPos, Point endPos)
|
||||
@@ -375,6 +398,7 @@ namespace Barotrauma
|
||||
minWidth = Math.Min(minWidth, MaxSubmarineWidth);
|
||||
}
|
||||
minWidth = Math.Min(minWidth, borders.Width / 5);
|
||||
LevelData.MinMainPathWidth = minWidth;
|
||||
|
||||
Rectangle pathBorders = borders;
|
||||
pathBorders.Inflate(
|
||||
@@ -435,6 +459,7 @@ namespace Barotrauma
|
||||
Point siteVariance = GenerationParams.VoronoiSiteVariance;
|
||||
siteCoordsX = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
||||
siteCoordsY = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
||||
int caveSiteInterval = 500;
|
||||
for (int x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X)
|
||||
{
|
||||
for (int y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y)
|
||||
@@ -448,7 +473,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
float minDist = Math.Max(tunnel.MinWidth, Math.Max(siteInterval.X, siteInterval.Y)) * 2.0f;
|
||||
float minDist = Math.Max(tunnel.MinWidth * 2.0f, Math.Max(siteInterval.X, siteInterval.Y));
|
||||
if (siteX < Math.Min(tunnel.Nodes[i - 1].X, tunnel.Nodes[i].X) - minDist) { continue; }
|
||||
if (siteX > Math.Max(tunnel.Nodes[i - 1].X, tunnel.Nodes[i].X) + minDist) { continue; }
|
||||
if (siteY < Math.Min(tunnel.Nodes[i - 1].Y, tunnel.Nodes[i].Y) - minDist) { continue; }
|
||||
@@ -459,7 +484,7 @@ namespace Barotrauma
|
||||
{
|
||||
closeToTunnel = true;
|
||||
tunnelDistSqr = MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], new Point(siteX, siteY));
|
||||
if (tunnel.Type == TunnelType.Cave )
|
||||
if (tunnel.Type == TunnelType.Cave)
|
||||
{
|
||||
closeToCave = true;
|
||||
}
|
||||
@@ -474,31 +499,64 @@ namespace Barotrauma
|
||||
if (Rand.Range(0, 10, Rand.RandSync.Server) != 0) { continue; }
|
||||
}
|
||||
|
||||
if (closeToCave)
|
||||
{
|
||||
//add some more sites around caves to generate more small voronoi cells
|
||||
if (x < borders.Width - siteInterval.X)
|
||||
{
|
||||
siteCoordsX.Add(x + Rand.Range(siteInterval.X / 4, siteInterval.X / 2, Rand.RandSync.Server));
|
||||
siteCoordsY.Add(y);
|
||||
}
|
||||
if (y < borders.Height - siteInterval.Y)
|
||||
{
|
||||
siteCoordsX.Add(x);
|
||||
siteCoordsY.Add(y + Rand.Range(siteInterval.Y / 4, siteInterval.Y / 2, Rand.RandSync.Server));
|
||||
}
|
||||
if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y)
|
||||
{
|
||||
siteCoordsX.Add(x + Rand.Range(siteInterval.X / 4, siteInterval.X / 2, Rand.RandSync.Server));
|
||||
siteCoordsY.Add(y + Rand.Range(siteInterval.Y / 4, siteInterval.Y / 2, Rand.RandSync.Server));
|
||||
}
|
||||
}
|
||||
|
||||
siteCoordsX.Add(siteX);
|
||||
siteCoordsY.Add(siteY);
|
||||
|
||||
if (closeToCave)
|
||||
{
|
||||
for (int x2 = x; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
{
|
||||
for (int y2 = y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
{
|
||||
int caveSiteX = x2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.Server);
|
||||
int caveSiteY = y2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.Server);
|
||||
|
||||
bool tooClose = false;
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
if (MathUtils.DistanceSquared(caveSiteX, caveSiteY, siteCoordsX[i], siteCoordsY[i]) < 10.0f * 10.0f)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tooClose) { continue; }
|
||||
siteCoordsX.Add(caveSiteX);
|
||||
siteCoordsY.Add(caveSiteY);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*int caveSiteInterval = 500;
|
||||
foreach (Cave cave in Caves)
|
||||
{
|
||||
for (int x = cave.Area.X; x < cave.Area.Right; x += caveSiteInterval)
|
||||
{
|
||||
for (int y = cave.Area.Y; y < cave.Area.Bottom; y += caveSiteInterval)
|
||||
{
|
||||
int siteX = x + Rand.Int(caveSiteInterval / 2);
|
||||
int siteY = y + Rand.Int(caveSiteInterval / 2);
|
||||
|
||||
bool tooClose = false;
|
||||
for (int i = 0; i<siteCoordsX.Count; i++)
|
||||
{
|
||||
if (MathUtils.DistanceSquared(siteX, siteY, siteCoordsX[i],siteCoordsY[i]) < 10.0f * 10.0f)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tooClose) { continue; }
|
||||
siteCoordsX.Add(siteX);
|
||||
siteCoordsY.Add(siteY);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
@@ -530,11 +588,13 @@ namespace Barotrauma
|
||||
CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders);
|
||||
if (tunnel.Type == TunnelType.MainPath || tunnel.Type == TunnelType.SidePath)
|
||||
{
|
||||
for (int i = 2; i < tunnel.Cells.Count; i += 3)
|
||||
var distinctCells = tunnel.Cells.Distinct().ToList();
|
||||
for (int i = 2; i < distinctCells.Count; i += 3)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(
|
||||
new Point((int)tunnel.Cells[i].Site.Coord.X, (int)tunnel.Cells[i].Site.Coord.Y),
|
||||
tunnel.Type == TunnelType.MainPath ? PositionType.MainPath : PositionType.SidePath));
|
||||
new Point((int)distinctCells[i].Site.Coord.X, (int)distinctCells[i].Site.Coord.Y),
|
||||
tunnel.Type == TunnelType.MainPath ? PositionType.MainPath : PositionType.SidePath,
|
||||
Caves.Find(cave => cave.Tunnels.Contains(tunnel))));
|
||||
}
|
||||
}
|
||||
GenerateWaypoints(tunnel, parentTunnel: tunnel.ParentTunnel);
|
||||
@@ -703,7 +763,12 @@ namespace Barotrauma
|
||||
{
|
||||
PositionsOfInterest[i] = new InterestingPosition(
|
||||
new Point(borders.Width - PositionsOfInterest[i].Position.X, PositionsOfInterest[i].Position.Y),
|
||||
PositionsOfInterest[i].PositionType);
|
||||
PositionsOfInterest[i].PositionType)
|
||||
{
|
||||
Submarine = PositionsOfInterest[i].Submarine,
|
||||
Cave = PositionsOfInterest[i].Cave,
|
||||
Ruin = PositionsOfInterest[i].Ruin,
|
||||
};
|
||||
}
|
||||
|
||||
foreach (WayPoint waypoint in WayPoint.WayPointList)
|
||||
@@ -726,6 +791,7 @@ namespace Barotrauma
|
||||
cellGrid[x, y].Add(cell);
|
||||
}
|
||||
|
||||
float destructibleWallRatio = MathHelper.Lerp(0.2f, 1.0f, LevelData.Difficulty / 100.0f);
|
||||
foreach (Cave cave in Caves)
|
||||
{
|
||||
CreatePathToClosestTunnel(cave.StartPos);
|
||||
@@ -734,7 +800,7 @@ namespace Barotrauma
|
||||
caveCells.AddRange(cave.Tunnels.SelectMany(t => t.Cells));
|
||||
foreach (var caveCell in caveCells)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < cave.CaveGenerationParams.DestructibleWallRatio)
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < destructibleWallRatio * cave.CaveGenerationParams.DestructibleWallRatio)
|
||||
{
|
||||
var chunk = CreateIceChunk(caveCell.Edges, caveCell.Center, health: 50.0f);
|
||||
if (chunk != null)
|
||||
@@ -755,7 +821,7 @@ namespace Barotrauma
|
||||
Ruins = new List<Ruin>();
|
||||
for (int i = 0; i < GenerationParams.RuinCount; i++)
|
||||
{
|
||||
GenerateRuin(mainPath.Cells, mirror);
|
||||
GenerateRuin(mainPath, mirror);
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
@@ -825,25 +891,30 @@ namespace Barotrauma
|
||||
};
|
||||
foreach (Cave cave in Caves)
|
||||
{
|
||||
cellBatches.Add(new Pair<List<VoronoiCell>, Cave>(new List<VoronoiCell>(), cave));
|
||||
var newCellBatch = new Pair<List<VoronoiCell>, Cave>(new List<VoronoiCell>(), cave);
|
||||
foreach (var caveCell in cave.Tunnels.SelectMany(t => t.Cells))
|
||||
{
|
||||
foreach (var edge in caveCell.Edges)
|
||||
{
|
||||
if (!edge.NextToCave) { continue; }
|
||||
if (edge.Cell1?.CellType == CellType.Solid && !cellBatches.Last().First.Contains(edge.Cell1))
|
||||
if (edge.Cell1?.CellType == CellType.Solid && !newCellBatch.First.Contains(edge.Cell1))
|
||||
{
|
||||
cellBatches.First().First.Remove(edge.Cell1);
|
||||
cellBatches.Last().First.Add(edge.Cell1);
|
||||
cellBatches.ForEach(cb => cb.First.Remove(edge.Cell1));
|
||||
newCellBatch.First.Add(edge.Cell1);
|
||||
}
|
||||
if (edge.Cell2?.CellType == CellType.Solid && !cellBatches.Last().First.Contains(edge.Cell2))
|
||||
if (edge.Cell2?.CellType == CellType.Solid && !newCellBatch.First.Contains(edge.Cell2))
|
||||
{
|
||||
cellBatches.First().First.Remove(edge.Cell2);
|
||||
cellBatches.Last().First.Add(edge.Cell2);
|
||||
cellBatches.ForEach(cb => cb.First.Remove(edge.Cell2));
|
||||
newCellBatch.First.Add(edge.Cell2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newCellBatch.First.Any())
|
||||
{
|
||||
cellBatches.Add(newCellBatch);
|
||||
}
|
||||
}
|
||||
cellBatches.RemoveAll(cb => !cb.First.Any());
|
||||
|
||||
Debug.Assert(cellsWithBody.Count == cellBatches.Sum(cb => cb.First.Count));
|
||||
|
||||
@@ -1140,8 +1211,9 @@ namespace Barotrauma
|
||||
|
||||
private void GenerateWaypoints(Tunnel tunnel, Tunnel parentTunnel)
|
||||
{
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
if (tunnel.Cells.Count == 0) { return; }
|
||||
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
for (int i = 0; i < tunnel.Cells.Count; i++)
|
||||
{
|
||||
tunnel.Cells[i].CellType = CellType.Path;
|
||||
@@ -1228,10 +1300,8 @@ namespace Barotrauma
|
||||
private List<VoronoiCell> GetTooCloseCells(List<VoronoiCell> emptyCells, float minDistance)
|
||||
{
|
||||
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
||||
|
||||
if (minDistance <= 0.0f) { return tooCloseCells; }
|
||||
|
||||
foreach (var cell in emptyCells)
|
||||
foreach (var cell in emptyCells.Distinct())
|
||||
{
|
||||
foreach (var tooCloseCell in GetTooCloseCells(cell.Center, minDistance))
|
||||
{
|
||||
@@ -1241,25 +1311,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*minDistance *= 0.5f;
|
||||
do
|
||||
{
|
||||
tooCloseCells.AddRange(GetTooCloseCells(position, minDistance));
|
||||
|
||||
position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step;
|
||||
|
||||
if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++;
|
||||
|
||||
} while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step * 2.0f);*/
|
||||
|
||||
return tooCloseCells;
|
||||
}
|
||||
|
||||
public List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
|
||||
{
|
||||
HashSet<VoronoiCell> tooCloseCells = new HashSet<VoronoiCell>();
|
||||
var closeCells = GetCells(position, 3);
|
||||
var closeCells = GetCells(position, searchDepth: Math.Max((int)Math.Ceiling(minDistance / GridCellSize), 3));
|
||||
float minDistSqr = minDistance * minDistance;
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
@@ -1353,7 +1411,7 @@ namespace Barotrauma
|
||||
int padding = (int)(caveSize.X * 1.2f);
|
||||
Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2);
|
||||
|
||||
var cavePos = FindPosAwayFromMainPath(radius, asFarAwayAsPossible: true, allowedArea);
|
||||
var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.2f, asCloseAsPossible: true, allowedArea);
|
||||
|
||||
Point closestParentNode = parentTunnel.Nodes.First();
|
||||
double closestDist = double.PositiveInfinity;
|
||||
@@ -1407,7 +1465,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Tunnel branch in caveBranches)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(branch.Nodes.Last(), PositionType.Cave));
|
||||
PositionsOfInterest.Add(new InterestingPosition(branch.Nodes.Last(), PositionType.Cave, cave));
|
||||
cave.Tunnels.Add(branch);
|
||||
}
|
||||
|
||||
@@ -1426,7 +1484,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateRuin(List<VoronoiCell> mainPath, bool mirror)
|
||||
private void GenerateRuin(Tunnel mainPath, bool mirror)
|
||||
{
|
||||
var ruinGenerationParams = RuinGenerationParams.GetRandom();
|
||||
|
||||
@@ -1435,12 +1493,12 @@ namespace Barotrauma
|
||||
Rand.Range(ruinGenerationParams.SizeMin.Y, ruinGenerationParams.SizeMax.Y, Rand.RandSync.Server));
|
||||
int ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) / 2;
|
||||
|
||||
Point ruinPos = FindPosAwayFromMainPath(ruinRadius + Tunnels.First().MinWidth, asFarAwayAsPossible: false,
|
||||
Point ruinPos = FindPosAwayFromMainPath((ruinRadius + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
|
||||
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize));
|
||||
|
||||
VoronoiCell closestPathCell = null;
|
||||
double closestDist = 0.0f;
|
||||
foreach (VoronoiCell pathCell in mainPath)
|
||||
foreach (VoronoiCell pathCell in mainPath.Cells)
|
||||
{
|
||||
double dist = MathUtils.DistanceSquared(pathCell.Site.Coord.X, pathCell.Site.Coord.Y, ruinPos.X, ruinPos.Y);
|
||||
if (closestPathCell == null || dist < closestDist)
|
||||
@@ -1506,22 +1564,22 @@ namespace Barotrauma
|
||||
CreatePathToClosestTunnel(ruinPos);
|
||||
}
|
||||
|
||||
private Point FindPosAwayFromMainPath(double minDistance, bool asFarAwayAsPossible, Rectangle? limits = null)
|
||||
private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null)
|
||||
{
|
||||
var validPoints = distanceField.FindAll(d => d.Second >= minDistance && (limits == null || limits.Value.Contains(d.First)));
|
||||
validPoints.RemoveAll(d => d.First.Y < GetBottomPosition(d.First.X).Y + minDistance);
|
||||
if (asFarAwayAsPossible || !validPoints.Any())
|
||||
if (asCloseAsPossible || !validPoints.Any())
|
||||
{
|
||||
if (!validPoints.Any()) { validPoints = distanceField; }
|
||||
Pair<Point, double> furthestPoint = null;
|
||||
Pair<Point, double> closestPoint = null;
|
||||
foreach (var point in validPoints)
|
||||
{
|
||||
if (furthestPoint == null || point.Second > furthestPoint.Second)
|
||||
if (closestPoint == null || point.Second < closestPoint.Second)
|
||||
{
|
||||
furthestPoint = point;
|
||||
closestPoint = point;
|
||||
}
|
||||
}
|
||||
return furthestPoint.First;
|
||||
return closestPoint.First;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1581,6 +1639,7 @@ namespace Barotrauma
|
||||
vertices.Add(edge.Point2);
|
||||
}
|
||||
}
|
||||
if (vertices.Count < 3) { return null; }
|
||||
return CreateIceChunk(vertices.Select(v => v - position).ToList(), position, health);
|
||||
}
|
||||
|
||||
@@ -1635,6 +1694,8 @@ namespace Barotrauma
|
||||
|
||||
Vector2 edgeNormal = closestEdge.GetNormal(closestCell);
|
||||
float spireLength = (float)Math.Min(Math.Sqrt(closestDistSqr), maxLength);
|
||||
spireLength *= MathHelper.Lerp(0.3f, 1.5f, Difficulty / 100.0f);
|
||||
|
||||
Vector2 extrudedPoint1 = closestEdge.Point1 + edgeNormal * spireLength * Rand.Range(0.8f, 1.0f, Rand.RandSync.Server);
|
||||
Vector2 extrudedPoint2 = closestEdge.Point2 + edgeNormal * spireLength * Rand.Range(0.8f, 1.0f, Rand.RandSync.Server);
|
||||
List<Vector2> vertices = new List<Vector2>()
|
||||
@@ -2101,7 +2162,7 @@ namespace Barotrauma
|
||||
|
||||
if (resourcesInCluster < 1) { return false; }
|
||||
|
||||
PlaceResources(selectedPrefab, resourcesInCluster, location, out var placedResources, edgeLenght: edgeLength);
|
||||
PlaceResources(selectedPrefab, resourcesInCluster, location, out var placedResources, edgeLength: edgeLength);
|
||||
itemCount += resourcesInCluster;
|
||||
location.InitializeResources();
|
||||
location.Resources.AddRange(placedResources);
|
||||
@@ -2136,7 +2197,7 @@ namespace Barotrauma
|
||||
c.Equals(location) &&
|
||||
c.Resources.Any(r => r != null && !r.Removed &&
|
||||
(!(r.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))));
|
||||
if(locationHasResources)
|
||||
if (locationHasResources)
|
||||
{
|
||||
allValidLocations.RemoveAt(i);
|
||||
}
|
||||
@@ -2231,10 +2292,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private void PlaceResources(ItemPrefab resourcePrefab, int resourceCount, ClusterLocation location, out List<Item> placedResources,
|
||||
float? edgeLenght = null, float maxResourceOverlap = 0.4f)
|
||||
float? edgeLength = null, float maxResourceOverlap = 0.4f)
|
||||
{
|
||||
edgeLenght ??= Vector2.Distance(location.Edge.Point1, location.Edge.Point2);
|
||||
var minResourceOverlap = -((edgeLenght.Value - (resourceCount * resourcePrefab.Size.X)) / (resourceCount * resourcePrefab.Size.X));
|
||||
edgeLength ??= Vector2.Distance(location.Edge.Point1, location.Edge.Point2);
|
||||
var minResourceOverlap = -((edgeLength.Value - (resourceCount * resourcePrefab.Size.X)) / (resourceCount * resourcePrefab.Size.X));
|
||||
minResourceOverlap = Math.Max(minResourceOverlap, 0.0f);
|
||||
var lerpAmounts = new float[resourceCount];
|
||||
lerpAmounts[0] = 0.0f;
|
||||
@@ -2242,7 +2303,7 @@ namespace Barotrauma
|
||||
for (int i = 1; i < resourceCount; i++)
|
||||
{
|
||||
var overlap = Rand.Range(minResourceOverlap, maxResourceOverlap, sync: Rand.RandSync.Server);
|
||||
lerpAmount += ((1.0f - overlap) * resourcePrefab.Size.X) / edgeLenght.Value;
|
||||
lerpAmount += ((1.0f - overlap) * resourcePrefab.Size.X) / edgeLength.Value;
|
||||
lerpAmounts[i] = Math.Clamp(lerpAmount, 0.0f, 1.0f);
|
||||
}
|
||||
var startOffset = Rand.Range(0.0f, 1.0f - lerpAmount, sync: Rand.RandSync.Server);
|
||||
@@ -2252,7 +2313,9 @@ namespace Barotrauma
|
||||
Vector2 selectedPos = Vector2.Lerp(location.Edge.Point1, location.Edge.Point2, startOffset + lerpAmounts[i]);
|
||||
var item = new Item(resourcePrefab, selectedPos, submarine: null);
|
||||
Vector2 edgeNormal = location.Edge.GetNormal(location.Cell);
|
||||
item.Move(edgeNormal * (item.body == null ? item.Rect.Height / 2 : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent() * 0.7f)), ignoreContacts: true);
|
||||
float moveAmount = (item.body == null ? item.Rect.Height / 2 : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent() * 0.7f));
|
||||
moveAmount += (item.GetComponent<LevelResource>()?.RandomOffsetFromWall ?? 0.0f) * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server);
|
||||
item.Move(edgeNormal * moveAmount, ignoreContacts: true);
|
||||
if (item.GetComponent<Holdable>() is Holdable h)
|
||||
{
|
||||
h.AttachToWall();
|
||||
@@ -2268,7 +2331,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float minDistFromSubs, float offsetFromWall = 10.0f)
|
||||
public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float minDistFromSubs, float offsetFromWall = 10.0f, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
if (!PositionsOfInterest.Any())
|
||||
{
|
||||
@@ -2280,7 +2343,7 @@ namespace Barotrauma
|
||||
int tries = 0;
|
||||
do
|
||||
{
|
||||
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos);
|
||||
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos, filter);
|
||||
|
||||
Vector2 offset = Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.Server), Rand.RandSync.Server);
|
||||
if (!cells.Any(c => c.IsPointInside(startPos + offset)))
|
||||
@@ -2312,14 +2375,14 @@ namespace Barotrauma
|
||||
return position;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position)
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos);
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, filter);
|
||||
position = pos.ToVector2();
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position)
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
if (!PositionsOfInterest.Any())
|
||||
{
|
||||
@@ -2328,8 +2391,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<InterestingPosition> suitablePositions = PositionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType));
|
||||
if (filter != null)
|
||||
{
|
||||
suitablePositions.RemoveAll(p => !filter(p));
|
||||
}
|
||||
//avoid floating ice chunks on the main path
|
||||
if (positionType == PositionType.MainPath || positionType == PositionType.SidePath)
|
||||
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath))
|
||||
{
|
||||
suitablePositions.RemoveAll(p => ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(p.Position.ToVector2()))));
|
||||
}
|
||||
@@ -2513,26 +2580,38 @@ namespace Barotrauma
|
||||
}
|
||||
cells.Remove(cell);
|
||||
|
||||
//if the edge is very short, remove an adjacent cell to prevent making the passage too narrow
|
||||
if (Vector2.DistanceSquared(e.Point1, e.Point2) < 200.0f * 200.0f)
|
||||
//go through the edges of this cell and find the ones that are next to a removed cell
|
||||
foreach (var otherEdge in cell.Edges)
|
||||
{
|
||||
foreach (GraphEdge e2 in cell.Edges)
|
||||
var otherAdjacent = otherEdge.AdjacentCell(cell);
|
||||
if (otherAdjacent == null || otherAdjacent.CellType == CellType.Solid) { continue; }
|
||||
|
||||
//if the edge is very short, remove adjacent cells to prevent making the passage too narrow
|
||||
if (Vector2.DistanceSquared(otherEdge.Point1, otherEdge.Point2) < 500.0f * 500.0f)
|
||||
{
|
||||
if (e2 == e) { continue; }
|
||||
var adjacentCell = e2.AdjacentCell(cell);
|
||||
if (adjacentCell == null || adjacentCell.CellType == CellType.Removed) { continue; }
|
||||
adjacentCell.CellType = CellType.Removed;
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
foreach (GraphEdge e2 in cell.Edges)
|
||||
{
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
if (e2 == otherEdge || e2 == otherEdge) { continue; }
|
||||
if (!MathUtils.NearlyEqual(otherEdge.Point1, e2.Point1) && !MathUtils.NearlyEqual(otherEdge.Point2, e2.Point1) && !MathUtils.NearlyEqual(otherEdge.Point2, e2.Point2))
|
||||
{
|
||||
cellGrid[x, y].Remove(adjacentCell);
|
||||
continue;
|
||||
}
|
||||
var adjacentCell = e2.AdjacentCell(cell);
|
||||
if (adjacentCell == null || adjacentCell.CellType == CellType.Removed) { continue; }
|
||||
adjacentCell.CellType = CellType.Removed;
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
{
|
||||
cellGrid[x, y].Remove(adjacentCell);
|
||||
}
|
||||
}
|
||||
cells.Remove(adjacentCell);
|
||||
}
|
||||
cells.Remove(adjacentCell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
@@ -2639,7 +2718,7 @@ namespace Barotrauma
|
||||
{
|
||||
sub.ShowSonarMarker = false;
|
||||
sub.PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
sub.TeamID = Character.TeamType.None;
|
||||
sub.TeamID = CharacterTeamType.None;
|
||||
}
|
||||
tempSW.Stop();
|
||||
Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)");
|
||||
@@ -2833,6 +2912,12 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Caves.Any(c =>
|
||||
ToolBox.GetWorldBounds(c.Area.Center, c.Area.Size).IntersectsWorld(bounds) ||
|
||||
ToolBox.GetWorldBounds(c.StartPos, new Point(1500)).IntersectsWorld(bounds)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return cells.Any(c => c.Body != null && Vector2.DistanceSquared(pos, c.Center) <= maxDistance && c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
|
||||
}
|
||||
}
|
||||
@@ -3113,15 +3198,26 @@ namespace Barotrauma
|
||||
if (!(GameMain.NetworkMember?.IsClient ?? false))
|
||||
{
|
||||
//empty the reactor
|
||||
foreach (Item item in reactorContainer.Inventory.Items)
|
||||
foreach (Item item in reactorContainer.Inventory.AllItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (item.NonInteractable) { continue; }
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
|
||||
//remove wires
|
||||
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire.Locked) { continue; }
|
||||
if (wire.Connections[0] != null && (wire.Connections[0].Item.NonInteractable || wire.Connections[0].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (wire.Connections[1] != null && (wire.Connections[1].Item.NonInteractable || wire.Connections[1].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
@@ -3131,6 +3227,7 @@ namespace Barotrauma
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered)))
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
{
|
||||
item.Condition *= Rand.Range(0.2f, 0.6f, Rand.RandSync.Unsynced);
|
||||
@@ -3221,7 +3318,7 @@ namespace Barotrauma
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, randSync: Rand.RandSync.Server);
|
||||
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
corpse.AnimController.FindHull(worldPos, true);
|
||||
corpse.TeamID = Character.TeamType.None;
|
||||
corpse.TeamID = CharacterTeamType.None;
|
||||
corpse.EnableDespawn = false;
|
||||
selectedPrefab.GiveItems(corpse, wreck);
|
||||
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
|
||||
|
||||
@@ -35,6 +35,11 @@ namespace Barotrauma
|
||||
|
||||
public readonly int InitialDepth;
|
||||
|
||||
/// <summary>
|
||||
/// Determined during level generation based on the size of the submarine. Null if the level hasn't been generated.
|
||||
/// </summary>
|
||||
public int? MinMainPathWidth;
|
||||
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
|
||||
|
||||
@@ -52,7 +52,11 @@ namespace Barotrauma
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return spriteIndex < 0 || Prefab.Sprites.Count == 0 ? null : Prefab.Sprites[spriteIndex % Prefab.Sprites.Count]; }
|
||||
get
|
||||
{
|
||||
var prefab = ActivePrefab?.Sprites.Count > 0 ? ActivePrefab : Prefab;
|
||||
return spriteIndex < 0 || prefab.Sprites.Count == 0 ? null : prefab.Sprites[spriteIndex % prefab.Sprites.Count];
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 ISpatialEntity.Position => new Vector2(Position.X, Position.Y);
|
||||
@@ -63,6 +67,8 @@ namespace Barotrauma
|
||||
|
||||
public Submarine Submarine => null;
|
||||
|
||||
public Level.Cave ParentCave;
|
||||
|
||||
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
{
|
||||
ActivePrefab = Prefab = prefab;
|
||||
@@ -110,6 +116,19 @@ namespace Barotrauma
|
||||
Triggers.Add(newTrigger);
|
||||
}
|
||||
|
||||
if (spriteIndex == -1)
|
||||
{
|
||||
foreach (var overrideProperties in prefab.OverrideProperties)
|
||||
{
|
||||
if (overrideProperties == null) { continue; }
|
||||
if (overrideProperties.Sprites.Count > 0)
|
||||
{
|
||||
spriteIndex = Rand.Int(overrideProperties.Sprites.Count, Rand.RandSync.Server);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NeedsUpdate = NeedsNetworkSyncing || (Triggers != null && Triggers.Any()) || Prefab.PhysicsBodyTriggerIndex > -1;
|
||||
|
||||
InitProjSpecific();
|
||||
|
||||
+55
-9
@@ -171,7 +171,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs);
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: true);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
@@ -184,10 +184,10 @@ namespace Barotrauma
|
||||
}
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
PlaceObject(prefab, spawnPosition, level, cave);
|
||||
if (prefab.MaxCount < amount)
|
||||
{
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
if (objects.Count(o => o.Prefab == prefab && o.ParentCave == cave) >= prefab.MaxCount)
|
||||
{
|
||||
availablePrefabs.Remove(prefab);
|
||||
}
|
||||
@@ -196,7 +196,51 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level)
|
||||
public void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
var availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.NestWall)));
|
||||
Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
|
||||
Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
|
||||
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
|
||||
List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
|
||||
foreach (var edge in caveCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
if (!edge.NextToCave) { continue; }
|
||||
if (MathUtils.LineSegmentToPointDistanceSquared(edge.Point1.ToPoint(), edge.Point2.ToPoint(), nestPosition.ToPoint()) > nestRadius * nestRadius) { continue; }
|
||||
if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
|
||||
if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
|
||||
}
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
|
||||
|
||||
for (int i = 0; i < objectAmount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: false);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
{
|
||||
availablePrefabs.Remove(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level, Level.Cave parentCave = null)
|
||||
{
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface && spawnPosition.Normal.LengthSquared() > 0.001f && spawnPosition != null)
|
||||
@@ -228,6 +272,7 @@ namespace Barotrauma
|
||||
var newObject = new LevelObject(prefab,
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
|
||||
AddObject(newObject, level);
|
||||
newObject.ParentCave = parentCave;
|
||||
|
||||
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
|
||||
{
|
||||
@@ -237,7 +282,7 @@ namespace Barotrauma
|
||||
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
int prefabCount = matchingPrefabs.Count();
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
|
||||
if (childPrefab == null) continue;
|
||||
if (childPrefab == null) { continue; }
|
||||
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
|
||||
|
||||
@@ -247,6 +292,7 @@ namespace Barotrauma
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
|
||||
|
||||
AddObject(childObject, level);
|
||||
childObject.ParentCave = parentCave;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +448,7 @@ namespace Barotrauma
|
||||
return objectsInRange;
|
||||
}
|
||||
|
||||
private List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
|
||||
private List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType, bool checkFlags = true)
|
||||
{
|
||||
List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
@@ -498,12 +544,12 @@ namespace Barotrauma
|
||||
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs)
|
||||
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs, bool requireCaveSpecificOverride)
|
||||
{
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(caveParams)) <= 0.0f) { return null; }
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(caveParams)).ToList(), Rand.RandSync.Server);
|
||||
availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
|
||||
+8
-7
@@ -37,11 +37,12 @@ namespace Barotrauma
|
||||
MainPathWall = 1,
|
||||
SidePathWall = 2,
|
||||
CaveWall = 4,
|
||||
RuinWall = 8,
|
||||
SeaFloor = 16,
|
||||
MainPath = 32,
|
||||
LevelStart = 64,
|
||||
LevelEnd = 128,
|
||||
NestWall = 8,
|
||||
RuinWall = 16,
|
||||
SeaFloor = 32,
|
||||
MainPath = 64,
|
||||
LevelStart = 128,
|
||||
LevelEnd = 256,
|
||||
Wall = MainPathWall | SidePathWall | CaveWall,
|
||||
}
|
||||
|
||||
@@ -442,14 +443,14 @@ namespace Barotrauma
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
public float GetCommonness(CaveGenerationParams generationParams)
|
||||
public float GetCommonness(CaveGenerationParams generationParams, bool requireCaveSpecificOverride = true)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return 0.0f;
|
||||
return requireCaveSpecificOverride ? 0.0f : Commonness;
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
|
||||
@@ -34,44 +34,38 @@ namespace Barotrauma
|
||||
|
||||
public Action<LevelTrigger, Entity> OnTriggered;
|
||||
|
||||
private PhysicsBody physicsBody;
|
||||
|
||||
/// <summary>
|
||||
/// Effects applied to entities that are inside the trigger
|
||||
/// </summary>
|
||||
private List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
/// <summary>
|
||||
/// Attacks applied to entities that are inside the trigger
|
||||
/// </summary>
|
||||
private List<Attack> attacks = new List<Attack>();
|
||||
private readonly List<Attack> attacks = new List<Attack>();
|
||||
|
||||
private float cameraShake;
|
||||
private readonly float cameraShake;
|
||||
private Vector2 unrotatedForce;
|
||||
private float forceFluctuationTimer, currentForceFluctuation = 1.0f;
|
||||
|
||||
private HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
|
||||
private TriggererType triggeredBy;
|
||||
private readonly TriggererType triggeredBy;
|
||||
|
||||
private float randomTriggerInterval;
|
||||
private float randomTriggerProbability;
|
||||
private readonly float randomTriggerInterval;
|
||||
private readonly float randomTriggerProbability;
|
||||
private float randomTriggerTimer;
|
||||
|
||||
private float triggeredTimer;
|
||||
|
||||
//how far away this trigger can activate other triggers from
|
||||
private float triggerOthersDistance;
|
||||
|
||||
private HashSet<string> tags = new HashSet<string>();
|
||||
private readonly HashSet<string> tags = new HashSet<string>();
|
||||
|
||||
//other triggers have to have at least one of these tags to trigger this one
|
||||
private HashSet<string> allowedOtherTriggerTags = new HashSet<string>();
|
||||
private readonly HashSet<string> allowedOtherTriggerTags = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// How long the trigger stays in the triggered state after triggerers have left
|
||||
/// </summary>
|
||||
private float stayTriggeredDelay;
|
||||
private readonly float stayTriggeredDelay;
|
||||
|
||||
public LevelTrigger ParentTrigger;
|
||||
|
||||
@@ -88,30 +82,24 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
worldPosition = value;
|
||||
physicsBody?.SetTransform(ConvertUnits.ToSimUnits(value), physicsBody.Rotation);
|
||||
PhysicsBody?.SetTransform(ConvertUnits.ToSimUnits(value), PhysicsBody.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return physicsBody == null ? 0.0f : physicsBody.Rotation; }
|
||||
get { return PhysicsBody == null ? 0.0f : PhysicsBody.Rotation; }
|
||||
set
|
||||
{
|
||||
if (physicsBody == null) return;
|
||||
physicsBody.SetTransform(physicsBody.Position, value);
|
||||
if (PhysicsBody == null) return;
|
||||
PhysicsBody.SetTransform(PhysicsBody.Position, value);
|
||||
CalculateDirectionalForce();
|
||||
}
|
||||
}
|
||||
|
||||
public PhysicsBody PhysicsBody
|
||||
{
|
||||
get { return physicsBody; }
|
||||
}
|
||||
public PhysicsBody PhysicsBody { get; private set; }
|
||||
|
||||
public float TriggerOthersDistance
|
||||
{
|
||||
get { return triggerOthersDistance; }
|
||||
}
|
||||
public float TriggerOthersDistance { get; private set; }
|
||||
|
||||
public IEnumerable<Entity> Triggerers
|
||||
{
|
||||
@@ -153,7 +141,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private TriggerForceMode forceMode;
|
||||
private readonly TriggerForceMode forceMode;
|
||||
public TriggerForceMode ForceMode
|
||||
{
|
||||
get { return forceMode; }
|
||||
@@ -198,6 +186,9 @@ namespace Barotrauma
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool triggeredOnce;
|
||||
private readonly bool triggerOnce;
|
||||
|
||||
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
|
||||
{
|
||||
@@ -206,20 +197,20 @@ namespace Barotrauma
|
||||
worldPosition = position;
|
||||
if (element.Attributes("radius").Any() || element.Attributes("width").Any() || element.Attributes("height").Any())
|
||||
{
|
||||
physicsBody = new PhysicsBody(element, scale)
|
||||
PhysicsBody = new PhysicsBody(element, scale)
|
||||
{
|
||||
CollisionCategories = Physics.CollisionLevel,
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall
|
||||
};
|
||||
physicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
|
||||
physicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
|
||||
physicsBody.FarseerBody.SetIsSensor(true);
|
||||
physicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
physicsBody.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
|
||||
PhysicsBody.FarseerBody.SetIsSensor(true);
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
|
||||
ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
|
||||
|
||||
physicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
|
||||
PhysicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
|
||||
}
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
|
||||
@@ -227,6 +218,8 @@ namespace Barotrauma
|
||||
InfectIdentifier = element.GetAttributeString("infectidentifier", null);
|
||||
InfectionChance = element.GetAttributeFloat("infectionchance", 0.05f);
|
||||
|
||||
triggerOnce = element.GetAttributeBool("triggeronce", false);
|
||||
|
||||
stayTriggeredDelay = element.GetAttributeFloat("staytriggereddelay", 0.0f);
|
||||
randomTriggerInterval = element.GetAttributeFloat("randomtriggerinterval", 0.0f);
|
||||
randomTriggerProbability = element.GetAttributeFloat("randomtriggerprobability", 0.0f);
|
||||
@@ -256,7 +249,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type.");
|
||||
}
|
||||
UpdateCollisionCategories();
|
||||
triggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
|
||||
TriggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
|
||||
|
||||
var tagsArray = element.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in tagsArray)
|
||||
@@ -283,11 +276,14 @@ namespace Barotrauma
|
||||
case "attack":
|
||||
case "damage":
|
||||
var attack = new Attack(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in " + parentDebugName);
|
||||
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
|
||||
attack.Afflictions.Clear();
|
||||
foreach (Affliction affliction in multipliedAfflictions)
|
||||
if (!triggerOnce)
|
||||
{
|
||||
attack.Afflictions.Add(affliction, null);
|
||||
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
|
||||
attack.Afflictions.Clear();
|
||||
foreach (Affliction affliction in multipliedAfflictions)
|
||||
{
|
||||
attack.Afflictions.Add(affliction, null);
|
||||
}
|
||||
}
|
||||
attacks.Add(attack);
|
||||
break;
|
||||
@@ -300,14 +296,14 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateCollisionCategories()
|
||||
{
|
||||
if (physicsBody == null) return;
|
||||
if (PhysicsBody == null) return;
|
||||
|
||||
var collidesWith = Physics.CollisionNone;
|
||||
if (triggeredBy.HasFlag(TriggererType.Character) || triggeredBy.HasFlag(TriggererType.Creature)) collidesWith |= Physics.CollisionCharacter;
|
||||
if (triggeredBy.HasFlag(TriggererType.Item)) collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile;
|
||||
if (triggeredBy.HasFlag(TriggererType.Submarine)) collidesWith |= Physics.CollisionWall;
|
||||
if (triggeredBy.HasFlag(TriggererType.Human) || triggeredBy.HasFlag(TriggererType.Creature)) { collidesWith |= Physics.CollisionCharacter; }
|
||||
if (triggeredBy.HasFlag(TriggererType.Item)) { collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile; }
|
||||
if (triggeredBy.HasFlag(TriggererType.Submarine)) { collidesWith |= Physics.CollisionWall; }
|
||||
|
||||
physicsBody.CollidesWith = collidesWith;
|
||||
PhysicsBody.CollidesWith = collidesWith;
|
||||
}
|
||||
|
||||
private void CalculateDirectionalForce()
|
||||
@@ -362,7 +358,7 @@ namespace Barotrauma
|
||||
private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
|
||||
{
|
||||
Entity entity = GetEntity(fixtureB);
|
||||
if (entity == null) return;
|
||||
if (entity == null) { return; }
|
||||
|
||||
if (entity is Character character &&
|
||||
(!character.Enabled || character.Removed) &&
|
||||
@@ -376,22 +372,25 @@ namespace Barotrauma
|
||||
//check if there are contacts with any other fixture of the trigger
|
||||
//(the OnSeparation callback happens when two fixtures separate,
|
||||
//e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
|
||||
ContactEdge contactEdge = fixtureA.Body.ContactList;
|
||||
while (contactEdge != null)
|
||||
foreach (Fixture fixture in PhysicsBody.FarseerBody.FixtureList)
|
||||
{
|
||||
if (contactEdge.Contact != null &&
|
||||
contactEdge.Contact.Enabled &&
|
||||
contactEdge.Contact.IsTouching)
|
||||
ContactEdge contactEdge = fixture.Body.ContactList;
|
||||
while (contactEdge != null)
|
||||
{
|
||||
if (contactEdge.Contact.FixtureA != fixtureA && contactEdge.Contact.FixtureB != fixtureA)
|
||||
if (contactEdge.Contact != null &&
|
||||
contactEdge.Contact.Enabled &&
|
||||
contactEdge.Contact.IsTouching)
|
||||
{
|
||||
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
|
||||
contactEdge.Contact.FixtureB :
|
||||
contactEdge.Contact.FixtureA);
|
||||
if (otherEntity == entity) { return; }
|
||||
if (contactEdge.Contact.FixtureA != fixture && contactEdge.Contact.FixtureB != fixture)
|
||||
{
|
||||
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
|
||||
contactEdge.Contact.FixtureB :
|
||||
contactEdge.Contact.FixtureA);
|
||||
if (otherEntity == entity) { return; }
|
||||
}
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
|
||||
if (triggerers.Contains(entity))
|
||||
@@ -403,10 +402,10 @@ namespace Barotrauma
|
||||
|
||||
private Entity GetEntity(Fixture fixture)
|
||||
{
|
||||
if (fixture.Body == null || fixture.Body.UserData == null) return null;
|
||||
if (fixture.Body.UserData is Entity entity) return entity;
|
||||
if (fixture.Body.UserData is Limb limb) return limb.character;
|
||||
if (fixture.Body.UserData is SubmarineBody subBody) return subBody.Submarine;
|
||||
if (fixture.Body == null || fixture.Body.UserData == null) { return null; }
|
||||
if (fixture.Body.UserData is Entity entity) { return entity; }
|
||||
if (fixture.Body.UserData is Limb limb) { return limb.character; }
|
||||
if (fixture.Body.UserData is SubmarineBody subBody) { return subBody.Submarine; }
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -416,15 +415,15 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void OtherTriggered(LevelObject levelObject, LevelTrigger otherTrigger)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) return;
|
||||
if (!triggeredBy.HasFlag(TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) { return; }
|
||||
|
||||
//check if the other trigger has appropriate tags
|
||||
if (allowedOtherTriggerTags.Count > 0)
|
||||
{
|
||||
if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) return;
|
||||
if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) { return; }
|
||||
}
|
||||
|
||||
if (Vector2.DistanceSquared(WorldPosition, otherTrigger.WorldPosition) <= otherTrigger.triggerOthersDistance * otherTrigger.triggerOthersDistance)
|
||||
if (Vector2.DistanceSquared(WorldPosition, otherTrigger.WorldPosition) <= otherTrigger.TriggerOthersDistance * otherTrigger.TriggerOthersDistance)
|
||||
{
|
||||
bool wasAlreadyTriggered = IsTriggered;
|
||||
triggeredTimer = stayTriggeredDelay;
|
||||
@@ -441,10 +440,10 @@ namespace Barotrauma
|
||||
|
||||
triggerers.RemoveWhere(t => t.Removed);
|
||||
|
||||
if (physicsBody != null)
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
//failsafe to ensure triggerers get removed when they're far from the trigger
|
||||
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(physicsBody.GetMaxExtent() * 5), 5000.0f);
|
||||
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(PhysicsBody.GetMaxExtent() * 5), 5000.0f);
|
||||
triggerers.RemoveWhere(t =>
|
||||
{
|
||||
return Vector2.Distance(t.WorldPosition, WorldPosition) > maxExtent;
|
||||
@@ -500,17 +499,43 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerOnce)
|
||||
{
|
||||
if (triggeredOnce) { return; }
|
||||
if (triggerers.Count > 0) { triggeredOnce = true; }
|
||||
}
|
||||
|
||||
foreach (Entity triggerer in triggerers)
|
||||
{
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (triggerer is Character)
|
||||
Vector2? position = null;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; }
|
||||
if (triggerer is Character character)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, (Character)triggerer);
|
||||
effect.Apply(effect.type, deltaTime, triggerer, character, position);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
|
||||
{
|
||||
foreach (Item item in character.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.ContainedItems == null) { continue; }
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (triggerer is Item)
|
||||
else if (triggerer is Item item)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, ((Item)triggerer).AllPropertyObjects);
|
||||
effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
#if DEBUG
|
||||
using System.Xml;
|
||||
#else
|
||||
using Barotrauma.IO;
|
||||
#endif
|
||||
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -98,6 +99,8 @@ namespace Barotrauma
|
||||
|
||||
LinkedSubmarine sl = CreateDummy(mainSub, doc.Root, position);
|
||||
sl.filePath = filePath;
|
||||
sl.saveElement = doc.Root;
|
||||
sl.saveElement.Name = "LinkedSubmarine";
|
||||
|
||||
return sl;
|
||||
}
|
||||
@@ -132,7 +135,11 @@ namespace Barotrauma
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
return CreateDummy(Submarine, filePath, Position);
|
||||
XElement cloneElement = new XElement(saveElement);
|
||||
LinkedSubmarine sl = CreateDummy(Submarine, cloneElement, Position);
|
||||
sl.saveElement = cloneElement;
|
||||
sl.filePath = filePath;
|
||||
return sl;
|
||||
}
|
||||
|
||||
private void GenerateWallVertices(XElement rootElement)
|
||||
@@ -232,6 +239,7 @@ namespace Barotrauma
|
||||
|
||||
IdRemap parentRemap = new IdRemap(Submarine.Info.SubmarineElement, Submarine.IdOffset);
|
||||
sub = Submarine.Load(info, false, parentRemap);
|
||||
sub.Info.SubmarineClass = Submarine.Info.SubmarineClass;
|
||||
|
||||
IdRemap childRemap = new IdRemap(saveElement, sub.IdOffset);
|
||||
|
||||
@@ -290,6 +298,7 @@ namespace Barotrauma
|
||||
originalMyPortID = myPort.Item.ID;
|
||||
|
||||
myPort.Undock();
|
||||
myPort.DockingDir = 0;
|
||||
|
||||
//something else is already docked to the port this sub should be docked to
|
||||
//may happen if a shuttle is lost, another vehicle docked to where the shuttle used to be,
|
||||
@@ -324,7 +333,7 @@ namespace Barotrauma
|
||||
if (wall.Submarine != sub) { continue; }
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -wall.MaxHealth);
|
||||
wall.SetDamage(i, 0, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
@@ -349,15 +358,15 @@ namespace Barotrauma
|
||||
{
|
||||
var doc = SubmarineInfo.OpenFile(filePath);
|
||||
saveElement = doc.Root;
|
||||
saveElement.Name = "LinkedSubmarine";
|
||||
saveElement.Add(new XAttribute("filepath", filePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
saveElement = this.saveElement;
|
||||
}
|
||||
saveElement.Name = "LinkedSubmarine";
|
||||
|
||||
if (saveElement.Attribute("pos") != null) saveElement.Attribute("pos").Remove();
|
||||
if (saveElement.Attribute("pos") != null) { saveElement.Attribute("pos").Remove(); }
|
||||
saveElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition)));
|
||||
|
||||
var linkedPort = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent<DockingPort>() != null);
|
||||
|
||||
@@ -62,7 +62,9 @@ namespace Barotrauma
|
||||
|
||||
public bool Discovered;
|
||||
|
||||
public int TypeChangeTimer;
|
||||
public readonly Dictionary<LocationTypeChange, int> ProximityTimer = new Dictionary<LocationTypeChange, int>();
|
||||
|
||||
public Pair<LocationTypeChange, int> PendingLocationTypeChange;
|
||||
|
||||
public string BaseName { get => baseName; }
|
||||
|
||||
@@ -80,12 +82,74 @@ namespace Barotrauma
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
|
||||
#region Store
|
||||
|
||||
private const float StoreMaxReputationModifier = 0.1f;
|
||||
private const float StoreSellPriceModifier = 0.8f;
|
||||
private const float MechanicalMaxDiscountPercentage = 50.0f;
|
||||
private const float DailySpecialPriceModifier = 0.9f;
|
||||
private const float RequestGoodPriceModifier = 1.5f;
|
||||
public const int StoreInitialBalance = 5000;
|
||||
public int StoreCurrentBalance { get; set; }
|
||||
/// <summary>
|
||||
/// In percentages
|
||||
/// </summary>
|
||||
private const int StorePriceModifierRange = 5;
|
||||
/// <summary>
|
||||
/// In percentages. Larger values make buying more expensive and selling less profitable, and vice versa.
|
||||
/// </summary>
|
||||
public int StorePriceModifier { get; private set; }
|
||||
|
||||
public Color BalanceColor => ActiveStoreBalanceStatus.Color;
|
||||
public StoreBalanceStatus ActiveStoreBalanceStatus { get; private set; }
|
||||
private static StoreBalanceStatus DefaultBalanceStatus { get; } = new StoreBalanceStatus(1.0f, 1.0f, Color.White);
|
||||
private static List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>
|
||||
{
|
||||
new StoreBalanceStatus(0.5f, 0.75f, Color.Orange),
|
||||
new StoreBalanceStatus(0.25f, 0.2f, Color.Red),
|
||||
};
|
||||
|
||||
public struct StoreBalanceStatus
|
||||
{
|
||||
public float PercentageOfInitialBalance { get; }
|
||||
public float SellPriceModifier { get; }
|
||||
public Color Color { get; }
|
||||
|
||||
public StoreBalanceStatus(float percentage, float sellPriceModifier, Color color)
|
||||
{
|
||||
PercentageOfInitialBalance = percentage;
|
||||
SellPriceModifier = sellPriceModifier;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private int storeCurrentBalance;
|
||||
public int StoreCurrentBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
return storeCurrentBalance;
|
||||
}
|
||||
set
|
||||
{
|
||||
storeCurrentBalance = value;
|
||||
ActiveStoreBalanceStatus = GetStoreBalanceStatus(value);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PurchasedItem> StoreStock { get; set; }
|
||||
public List<ItemPrefab> DailySpecials { get; } = new List<ItemPrefab>();
|
||||
public List<ItemPrefab> RequestedGoods { get; } = new List<ItemPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// How many map progress steps it takes before the discounts should be updated.
|
||||
/// </summary>
|
||||
private const int SpecialsUpdateInterval = 3;
|
||||
private const int DailySpecialsCount = 3;
|
||||
private const int RequestedGoodsCount = 3;
|
||||
private int StepsSinceSpecialsUpdated { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
private const float MechanicalMaxDiscountPercentage = 50.0f;
|
||||
|
||||
private readonly List<TakenItem> takenItems = new List<TakenItem>();
|
||||
public IEnumerable<TakenItem> TakenItems
|
||||
@@ -150,6 +214,8 @@ namespace Barotrauma
|
||||
|
||||
public string LastTypeChangeMessage;
|
||||
|
||||
public int TimeSinceLastTypeChange;
|
||||
|
||||
private struct LoadedMission
|
||||
{
|
||||
public MissionPrefab MissionPrefab { get; }
|
||||
@@ -189,10 +255,23 @@ namespace Barotrauma
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
TypeChangeTimer = element.GetAttributeInt("changetimer", 0);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TimeSinceLastTypeChange = element.GetAttributeInt("timesincelasttypechange", 0);
|
||||
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
ProximityTimer.Add(Type.CanChangeTo[i], element.GetAttributeInt("proximitytimer" + i, 0));
|
||||
}
|
||||
|
||||
int locationTypeChangeIndex = element.GetAttributeInt("pendinglocationtypechange", -1);
|
||||
if (locationTypeChangeIndex > 0 && locationTypeChangeIndex < Type.CanChangeTo.Count - 1)
|
||||
{
|
||||
PendingLocationTypeChange = new Pair<LocationTypeChange, int>(
|
||||
Type.CanChangeTo[locationTypeChangeIndex],
|
||||
element.GetAttributeInt("pendinglocationtypechangetimer", 0));
|
||||
}
|
||||
|
||||
string[] takenItemStr = element.GetAttributeStringArray("takenitems", new string[0]);
|
||||
foreach (string takenItem in takenItemStr)
|
||||
@@ -232,13 +311,8 @@ namespace Barotrauma
|
||||
LevelData = new LevelData(element.Element("Level"));
|
||||
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
|
||||
if (element.GetChildElement("store") is XElement storeElement)
|
||||
{
|
||||
StoreCurrentBalance = storeElement.GetAttributeInt("balance", StoreInitialBalance);
|
||||
StoreStock = LoadStoreStock(storeElement);
|
||||
}
|
||||
|
||||
LoadStore(element);
|
||||
LoadMissions(element);
|
||||
}
|
||||
|
||||
@@ -456,6 +530,59 @@ namespace Barotrauma
|
||||
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
|
||||
}
|
||||
|
||||
public void LoadStore(XElement locationElement)
|
||||
{
|
||||
StoreStock?.Clear();
|
||||
DailySpecials.Clear();
|
||||
RequestedGoods.Clear();
|
||||
|
||||
if (locationElement.GetChildElement("store") is XElement storeElement)
|
||||
{
|
||||
StoreCurrentBalance = storeElement.GetAttributeInt("balance", StoreInitialBalance);
|
||||
StorePriceModifier = storeElement.GetAttributeInt("pricemodifier", 0);
|
||||
|
||||
StoreStock ??= new List<PurchasedItem>();
|
||||
foreach (XElement stockElement in storeElement.GetChildElements("stock"))
|
||||
{
|
||||
var id = stockElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id);
|
||||
if (prefab == null) { continue; }
|
||||
var qty = stockElement.GetAttributeInt("qty", 0);
|
||||
if (qty < 1) { continue; }
|
||||
StoreStock.Add(new PurchasedItem(prefab, qty));
|
||||
}
|
||||
|
||||
StepsSinceSpecialsUpdated = storeElement.GetAttributeInt("stepssincespecialsupdated", 0);
|
||||
|
||||
if (storeElement.GetChildElement("dailyspecials") is XElement specialsElement)
|
||||
{
|
||||
var loadedDailySpecials = LoadStoreSpecials(specialsElement);
|
||||
DailySpecials.AddRange(loadedDailySpecials);
|
||||
}
|
||||
|
||||
if (storeElement.GetChildElement("requestedgoods") is XElement goodsElement)
|
||||
{
|
||||
var loadedRequestedGoods = LoadStoreSpecials(goodsElement);
|
||||
RequestedGoods.AddRange(loadedRequestedGoods);
|
||||
}
|
||||
|
||||
static List<ItemPrefab> LoadStoreSpecials(XElement element)
|
||||
{
|
||||
List<ItemPrefab> specials = new List<ItemPrefab>();
|
||||
foreach (var childElement in element.GetChildElements("item"))
|
||||
{
|
||||
var id = childElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
var prefab = ItemPrefab.Find(null, id);
|
||||
if (prefab == null) { continue; }
|
||||
specials.Add(prefab);
|
||||
}
|
||||
return specials;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<PurchasedItem> CreateStoreStock()
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
@@ -463,31 +590,28 @@ namespace Barotrauma
|
||||
{
|
||||
if (prefab.CanBeBoughtAtLocation(this, out PriceInfo priceInfo))
|
||||
{
|
||||
var quantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount :
|
||||
(priceInfo.MaxAvailableAmount > 0 ? Math.Min(priceInfo.MaxAvailableAmount, 5) : 5);
|
||||
int quantity = PriceInfo.DefaultAmount;
|
||||
if (priceInfo.MaxAvailableAmount > 0)
|
||||
{
|
||||
if (priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount)
|
||||
{
|
||||
quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
quantity = priceInfo.MaxAvailableAmount;
|
||||
}
|
||||
}
|
||||
else if (priceInfo.MinAvailableAmount > 0)
|
||||
{
|
||||
quantity = priceInfo.MinAvailableAmount;
|
||||
}
|
||||
stock.Add(new PurchasedItem(prefab, quantity));
|
||||
}
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
public static List<PurchasedItem> LoadStoreStock(XElement storeElement)
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
if (storeElement == null) { return stock; }
|
||||
foreach (XElement stockElement in storeElement.GetChildElements("stock"))
|
||||
{
|
||||
var id = stockElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id);
|
||||
if (prefab == null) { continue; }
|
||||
var qty = stockElement.GetAttributeInt("qty", 0);
|
||||
if (qty < 1) { continue; }
|
||||
stock.Add(new PurchasedItem(prefab, qty));
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark the items that have been taken from the outpost to prevent them from spawning when re-entering the outpost
|
||||
/// </summary>
|
||||
@@ -526,48 +650,70 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int GetAdjustedItemBuyPrice(PriceInfo priceInfo)
|
||||
/// <param name="priceInfo">If null, item.GetPriceInfo() will be used to get it.</param>
|
||||
/// /// <param name="considerDailySpecials">If false, the price won't be affected by <see cref="DailySpecialPriceModifier"/></param>
|
||||
public int GetAdjustedItemBuyPrice(ItemPrefab item, PriceInfo priceInfo = null, bool considerDailySpecials = true)
|
||||
{
|
||||
// TODO: Check priceInfo.CanBeBought
|
||||
priceInfo ??= item?.GetPriceInfo(this);
|
||||
if (priceInfo == null) { return 0; }
|
||||
var price = priceInfo.Price;
|
||||
float price = priceInfo.Price;
|
||||
|
||||
// Adjust by random price modifier
|
||||
price = ((100 + StorePriceModifier) / 100.0f) * price;
|
||||
|
||||
// Adjust by daily special status
|
||||
if (considerDailySpecials && DailySpecials.Contains(item))
|
||||
{
|
||||
price = DailySpecialPriceModifier * price;
|
||||
}
|
||||
|
||||
// Adjust by current location reputation
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price);
|
||||
price = MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price;
|
||||
}
|
||||
else
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price);
|
||||
price = MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price;
|
||||
}
|
||||
// Item price should never go below 1 mk
|
||||
return Math.Max(price, 1);
|
||||
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If item.GetPriceInfo() returns null, this will return 0
|
||||
/// </summary>
|
||||
public int GetAdjustedItemBuyPrice(ItemPrefab item) => GetAdjustedItemBuyPrice(item?.GetPriceInfo(this));
|
||||
|
||||
public int GetAdjustedItemSellPrice(PriceInfo priceInfo)
|
||||
/// <param name="priceInfo">If null, item.GetPriceInfo() will be used to get it.</param>
|
||||
/// <param name="considerRequestedGoods">If false, the price won't be affected by <see cref="RequestGoodPriceModifier"/></param>
|
||||
public int GetAdjustedItemSellPrice(ItemPrefab item, PriceInfo priceInfo = null, bool considerRequestedGoods = true)
|
||||
{
|
||||
priceInfo ??= item?.GetPriceInfo(this);
|
||||
if (priceInfo == null) { return 0; }
|
||||
var price = (int)(StoreSellPriceModifier * priceInfo.Price);
|
||||
float price = StoreSellPriceModifier * priceInfo.Price;
|
||||
|
||||
// Adjust by random price modifier
|
||||
price = ((100 - StorePriceModifier) / 100.0f) * price;
|
||||
|
||||
// Adjust by current store balance
|
||||
price = ActiveStoreBalanceStatus.SellPriceModifier * price;
|
||||
|
||||
// Adjust by requested good status
|
||||
if (considerRequestedGoods && RequestedGoods.Contains(item))
|
||||
{
|
||||
price = RequestGoodPriceModifier * price;
|
||||
}
|
||||
|
||||
// Adjust by current location reputation
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price);
|
||||
price = MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price;
|
||||
}
|
||||
else
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price);
|
||||
price = MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price;
|
||||
}
|
||||
// Item price should never go below 1 mk
|
||||
return Math.Max(price, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If item.GetPriceInfo() returns null, this will return 0
|
||||
/// </summary>
|
||||
public int GetAdjustedItemSellPrice(ItemPrefab item) => GetAdjustedItemSellPrice(item?.GetPriceInfo(this));
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
|
||||
public int GetAdjustedMechanicalCost(int cost)
|
||||
{
|
||||
@@ -575,12 +721,12 @@ namespace Barotrauma
|
||||
return (int) Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'force' is true, the stock will be recreated even if it has been created previously already.
|
||||
/// This is used when (at least) when the type of the location changes.
|
||||
/// </summary>
|
||||
/// <param name="force">If true, the store will be recreated if it already exists.</param>
|
||||
public void CreateStore(bool force = false)
|
||||
{
|
||||
// In multiplayer, stores should be created by the server and loaded from save data by clients
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
if (!force && StoreStock != null) { return; }
|
||||
|
||||
if (StoreStock != null)
|
||||
@@ -604,10 +750,16 @@ namespace Barotrauma
|
||||
StoreCurrentBalance = StoreInitialBalance;
|
||||
StoreStock = CreateStoreStock();
|
||||
}
|
||||
|
||||
GenerateRandomPriceModifier();
|
||||
CreateStoreSpecials();
|
||||
}
|
||||
|
||||
public void UpdateStore()
|
||||
{
|
||||
// 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 (StoreStock == null)
|
||||
{
|
||||
CreateStore();
|
||||
@@ -619,6 +771,8 @@ namespace Barotrauma
|
||||
StoreCurrentBalance = Math.Min(StoreCurrentBalance + (int)(StoreInitialBalance / 10.0f), StoreInitialBalance);
|
||||
}
|
||||
|
||||
GenerateRandomPriceModifier();
|
||||
|
||||
var stock = StoreStock;
|
||||
var stockToRemove = new List<PurchasedItem>();
|
||||
foreach (PurchasedItem item in stock)
|
||||
@@ -642,6 +796,56 @@ namespace Barotrauma
|
||||
}
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
StoreStock = stock;
|
||||
|
||||
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval)
|
||||
{
|
||||
CreateStoreSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateRandomPriceModifier()
|
||||
{
|
||||
StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange);
|
||||
}
|
||||
|
||||
private void CreateStoreSpecials()
|
||||
{
|
||||
DailySpecials.Clear();
|
||||
var availableStock = new Dictionary<ItemPrefab, float>();
|
||||
foreach (var stockItem in StoreStock)
|
||||
{
|
||||
if (stockItem.Quantity < 1) { continue; }
|
||||
var weight = 1.0f;
|
||||
var priceInfo = stockItem.ItemPrefab.GetPriceInfo(this);
|
||||
if (priceInfo != null)
|
||||
{
|
||||
if (!priceInfo.CanBeSpecial) { continue; }
|
||||
var baseQuantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount : PriceInfo.DefaultAmount;
|
||||
weight += (float)(stockItem.Quantity - baseQuantity) / baseQuantity;
|
||||
if (weight < 0.0f) { continue; }
|
||||
}
|
||||
availableStock.Add(stockItem.ItemPrefab, weight);
|
||||
}
|
||||
for (int i = 0; i < DailySpecialsCount; i++)
|
||||
{
|
||||
if (availableStock.None()) { break; }
|
||||
var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced);
|
||||
if (item == null) { break; }
|
||||
DailySpecials.Add(item);
|
||||
availableStock.Remove(item);
|
||||
}
|
||||
|
||||
RequestedGoods.Clear();
|
||||
for (int i = 0; i < RequestedGoodsCount; i++)
|
||||
{
|
||||
var item = ItemPrefab.Prefabs.GetRandom(p =>
|
||||
p.CanBeSold && !RequestedGoods.Contains(p) &&
|
||||
p.GetPriceInfo(this) is PriceInfo pi && pi.CanBeSpecial);
|
||||
if (item == null) { break; }
|
||||
RequestedGoods.Add(item);
|
||||
}
|
||||
|
||||
StepsSinceSpecialsUpdated = 0;
|
||||
}
|
||||
|
||||
public void AddToStock(List<SoldItem> items)
|
||||
@@ -686,6 +890,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static StoreBalanceStatus GetStoreBalanceStatus(int balance)
|
||||
{
|
||||
StoreBalanceStatus nextStatus = DefaultBalanceStatus;
|
||||
foreach (var balanceStatus in StoreBalanceStatuses)
|
||||
{
|
||||
if (balanceStatus.PercentageOfInitialBalance < nextStatus.PercentageOfInitialBalance &&
|
||||
((float)balance / StoreInitialBalance) < balanceStatus.PercentageOfInitialBalance)
|
||||
{
|
||||
nextStatus = balanceStatus;
|
||||
}
|
||||
}
|
||||
return nextStatus;
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
{
|
||||
var locationElement = new XElement("location",
|
||||
@@ -695,13 +913,24 @@ namespace Barotrauma
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier));
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier),
|
||||
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange));
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
if (TypeChangeTimer > 0)
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
locationElement.Add(new XAttribute("changetimer", TypeChangeTimer));
|
||||
if (ProximityTimer.ContainsKey(Type.CanChangeTo[i]))
|
||||
{
|
||||
locationElement.Add(new XAttribute("proximitytimer" + i, ProximityTimer[Type.CanChangeTo[i]]));
|
||||
}
|
||||
}
|
||||
|
||||
if (PendingLocationTypeChange != null)
|
||||
{
|
||||
locationElement.Add(new XAttribute("pendinglocationtypechange", Type.CanChangeTo.IndexOf(PendingLocationTypeChange.First)));
|
||||
locationElement.Add(new XAttribute("pendinglocationtypechangetimer", PendingLocationTypeChange.Second));
|
||||
}
|
||||
|
||||
if (takenItems.Any())
|
||||
{
|
||||
locationElement.Add(new XAttribute(
|
||||
@@ -715,7 +944,11 @@ namespace Barotrauma
|
||||
|
||||
if (StoreStock != null)
|
||||
{
|
||||
var storeElement = new XElement("store", new XAttribute("balance", StoreCurrentBalance));
|
||||
var storeElement = new XElement("store",
|
||||
new XAttribute("balance", StoreCurrentBalance),
|
||||
new XAttribute("pricemodifier", StorePriceModifier),
|
||||
new XAttribute("stepssincespecialsupdated", StepsSinceSpecialsUpdated));
|
||||
|
||||
foreach (PurchasedItem item in StoreStock)
|
||||
{
|
||||
if (item?.ItemPrefab == null) { continue; }
|
||||
@@ -723,6 +956,29 @@ namespace Barotrauma
|
||||
new XAttribute("id", item.ItemPrefab.Identifier),
|
||||
new XAttribute("qty", item.Quantity)));
|
||||
}
|
||||
|
||||
if (DailySpecials.Any())
|
||||
{
|
||||
var dailySpecialElement = new XElement("dailyspecials");
|
||||
foreach (var item in DailySpecials)
|
||||
{
|
||||
dailySpecialElement.Add(new XElement("item",
|
||||
new XAttribute("id", item.Identifier)));
|
||||
}
|
||||
storeElement.Add(dailySpecialElement);
|
||||
}
|
||||
|
||||
if (RequestedGoods.Any())
|
||||
{
|
||||
var requestedGoodsElement = new XElement("requestedgoods");
|
||||
foreach (var item in RequestedGoods)
|
||||
{
|
||||
requestedGoodsElement.Add(new XElement("item",
|
||||
new XAttribute("id", item.Identifier)));
|
||||
}
|
||||
storeElement.Add(requestedGoodsElement);
|
||||
}
|
||||
|
||||
locationElement.Add(storeElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -33,6 +34,19 @@ namespace Barotrauma
|
||||
|
||||
public LocationConnection(Location location1, Location location2)
|
||||
{
|
||||
if (location1 == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid location connection: location1 was null");
|
||||
}
|
||||
if (location2 == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid location connection: location2 was null");
|
||||
}
|
||||
if (location1 == location2)
|
||||
{
|
||||
throw new ArgumentException("Invalid location connection: location1 was the same as location2");
|
||||
}
|
||||
|
||||
Locations = new Location[] { location1, location2 };
|
||||
Length = Vector2.Distance(location1.MapPosition, location2.MapPosition);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -9,35 +10,85 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly string ChangeToType;
|
||||
|
||||
public readonly float Probability;
|
||||
public readonly int RequiredDuration;
|
||||
|
||||
public readonly float ProximityProbabilityIncrease;
|
||||
public readonly int RequiredProximityForProbabilityIncrease;
|
||||
|
||||
public readonly bool RequireDiscovered;
|
||||
|
||||
public List<string> Messages = new List<string>();
|
||||
|
||||
//the change can't happen if there's a location of the given type next to this one
|
||||
/// <summary>
|
||||
/// The change can only happen if there's at least one of the given types of locations near this one
|
||||
/// </summary>
|
||||
public readonly List<string> RequiredLocations;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the change to occur
|
||||
/// </summary>
|
||||
public readonly int RequiredProximity;
|
||||
|
||||
/// <summary>
|
||||
/// Base probability per turn for the location to change if near one of the RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float Probability;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the probability to increase
|
||||
/// </summary>
|
||||
public readonly int RequiredProximityForProbabilityIncrease;
|
||||
|
||||
/// <summary>
|
||||
/// How much the probability increases per turn if within RequiredProximityForProbabilityIncrease steps of RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float ProximityProbabilityIncrease;
|
||||
|
||||
/// <summary>
|
||||
/// The change can't happen if there's one or more of the given types of locations near this one
|
||||
/// </summary>
|
||||
public readonly List<string> DisallowedAdjacentLocations;
|
||||
|
||||
//the change can only happen if there's at least one of the given types of locations next to this one
|
||||
public readonly List<string> RequiredAdjacentLocations;
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the DisallowedAdjacentLocations for the change to be disabled
|
||||
/// </summary>
|
||||
public readonly int DisallowedProximity;
|
||||
|
||||
public readonly Point RequiredDurationRange;
|
||||
|
||||
public LocationTypeChange(string currentType, XElement element)
|
||||
{
|
||||
ChangeToType = element.GetAttributeString("type", "");
|
||||
Probability = element.GetAttributeFloat("probability", 1.0f);
|
||||
RequiredDuration = element.GetAttributeInt("requiredduration", 0);
|
||||
|
||||
RequireDiscovered = element.GetAttributeBool("requirediscovered", false);
|
||||
|
||||
RequiredLocations = element.GetAttributeStringArray("requiredlocations", element.GetAttributeStringArray("requiredadjacentlocations", new string[0])).ToList();
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 1);
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
|
||||
|
||||
|
||||
if (RequiredProximityForProbabilityIncrease > 0 || ProximityProbabilityIncrease > 0.0f)
|
||||
{
|
||||
if (!RequiredLocations.Any())
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{currentType}\". "+
|
||||
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.");
|
||||
}
|
||||
if (Probability >= 1.0f)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{currentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the base probability is already 100%");
|
||||
}
|
||||
}
|
||||
|
||||
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
|
||||
RequiredAdjacentLocations = element.GetAttributeStringArray("requiredadjacentlocations", new string[0]).ToList();
|
||||
DisallowedProximity = Math.Max(element.GetAttributeInt("disallowedproximity", 1), 1);
|
||||
|
||||
RequiredDurationRange = element.GetAttributePoint("requireddurationrange", Point.Zero);
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredduration") != null)
|
||||
{
|
||||
RequiredDurationRange = new Point(element.GetAttributeInt("requiredduration", 0));
|
||||
}
|
||||
|
||||
string messageTag = element.GetAttributeString("messagetag", "LocationChange." + currentType + ".ChangeTo." + ChangeToType);
|
||||
|
||||
@@ -50,15 +101,31 @@ namespace Barotrauma
|
||||
|
||||
public float DetermineProbability(Location location)
|
||||
{
|
||||
float totalProbability = Probability;
|
||||
if (AnyWithinRequiredProximity(location)) { totalProbability += ProximityProbabilityIncrease; }
|
||||
return totalProbability;
|
||||
if (RequireDiscovered && !location.Discovered) { return 0.0f; }
|
||||
|
||||
if (RequiredLocations.Any() && !AnyWithinDistance(location, RequiredProximity, (otherLocation) => { return RequiredLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
if (DisallowedAdjacentLocations.Any() && AnyWithinDistance(location, DisallowedProximity, (otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
float probability = Probability;
|
||||
if (location.ProximityTimer.ContainsKey(this))
|
||||
{
|
||||
if (AnyWithinDistance(location, RequiredProximityForProbabilityIncrease, (otherLocation) => { return RequiredLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return probability += ProximityProbabilityIncrease * location.ProximityTimer[this];
|
||||
}
|
||||
}
|
||||
return probability;
|
||||
}
|
||||
|
||||
private bool AnyWithinRequiredProximity(Location location, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
public bool AnyWithinDistance(Location location, int maxDistance, Func<Location, bool> predicate, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > RequiredProximityForProbabilityIncrease) { return false; }
|
||||
if (currentDistance > 0 && RequiredAdjacentLocations.Contains(location.Type.Identifier)) { return true; }
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && predicate(location)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
@@ -68,7 +135,7 @@ namespace Barotrauma
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinRequiredProximity(otherLocation, currentDistance + 1, checkedLocations)) { return true; }
|
||||
if (AnyWithinDistance(otherLocation, maxDistance, predicate, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +145,7 @@ namespace Barotrauma
|
||||
private int CountWithinRequiredProximity(Location location, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > RequiredProximityForProbabilityIncrease) { return 0; }
|
||||
int count = currentDistance > 0 && RequiredAdjacentLocations.Contains(location.Type.Identifier) ? 1 : 0;
|
||||
int count = currentDistance > 0 && RequiredLocations.Contains(location.Type.Identifier) ? 1 : 0;
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace Barotrauma
|
||||
{
|
||||
case "connection":
|
||||
Point locationIndices = subElement.GetAttributePoint("locations", new Point(0, 1));
|
||||
if (locationIndices.X == locationIndices.Y) { continue; }
|
||||
var connection = new LocationConnection(Locations[locationIndices.X], Locations[locationIndices.Y])
|
||||
{
|
||||
Passed = subElement.GetAttributeBool("passed", false),
|
||||
@@ -185,8 +186,8 @@ namespace Barotrauma
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
CurrentLocation.Discovered = true;
|
||||
CurrentLocation.CreateStore();
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
@@ -247,7 +248,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (newLocations[i] != null) continue;
|
||||
if (newLocations[i] != null) { continue; }
|
||||
|
||||
Vector2[] points = new Vector2[] { edge.Point1, edge.Point2 };
|
||||
|
||||
@@ -320,7 +321,15 @@ namespace Barotrauma
|
||||
{
|
||||
connection.Locations[1] = Locations[i];
|
||||
}
|
||||
Locations[i].Connections.Add(connection);
|
||||
|
||||
if (connection.Locations[0] != connection.Locations[1])
|
||||
{
|
||||
Locations[i].Connections.Add(connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
}
|
||||
Locations[i].Connections.RemoveAll(c => c.OtherLocation(Locations[i]) == Locations[j]);
|
||||
Locations.RemoveAt(j);
|
||||
@@ -548,6 +557,12 @@ namespace Barotrauma
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.CampaignMetadata is { } metadata)
|
||||
{
|
||||
metadata.SetValue("campaign.location.id", CurrentLocationIndex);
|
||||
metadata.SetValue("campaign.location.name", CurrentLocation.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLocation(int index)
|
||||
@@ -678,92 +693,105 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (furthestDiscoveredLocation == null ||
|
||||
location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
if (location.Discovered)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
if (furthestDiscoveredLocation == null ||
|
||||
location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.MapPosition.X < furthestDiscoveredLocation.MapPosition.X)
|
||||
if (location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (location == CurrentLocation || location == SelectedLocation) { continue; }
|
||||
|
||||
//find which types of locations this one can change to
|
||||
var cct = location.Type.CanChangeTo;
|
||||
List<LocationTypeChange> allowedTypeChanges = new List<LocationTypeChange>();
|
||||
List<int> readyTypeChanges = new List<int>();
|
||||
for (int i = 0; i < cct.Count; i++)
|
||||
ProgressLocationTypeChanges(location);
|
||||
|
||||
if (location.Discovered)
|
||||
{
|
||||
LocationTypeChange typeChange = cct[i];
|
||||
if (typeChange.RequireDiscovered && !location.Discovered) { continue; }
|
||||
//check if there are any adjacent locations that would prevent the change
|
||||
bool disallowedFound = false;
|
||||
foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations)
|
||||
{
|
||||
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(disallowedLocationName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
disallowedFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (disallowedFound) { continue; }
|
||||
|
||||
//check that there's a required adjacent location present
|
||||
bool requiredFound = false;
|
||||
foreach (string requiredLocationName in typeChange.RequiredAdjacentLocations)
|
||||
{
|
||||
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(requiredLocationName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
requiredFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) { continue; }
|
||||
|
||||
allowedTypeChanges.Add(typeChange);
|
||||
|
||||
if (location.TypeChangeTimer >= typeChange.RequiredDuration)
|
||||
{
|
||||
readyTypeChanges.Add(i);
|
||||
}
|
||||
location.UpdateStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<float> readyTypeProbabilities = readyTypeChanges.Select(i => cct[i].DetermineProbability(location)).ToList();
|
||||
//select a random type change
|
||||
if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(i => readyTypeProbabilities[i]))
|
||||
private void ProgressLocationTypeChanges(Location location)
|
||||
{
|
||||
location.TimeSinceLastTypeChange++;
|
||||
|
||||
if (location.PendingLocationTypeChange != null)
|
||||
{
|
||||
if (location.PendingLocationTypeChange.First.DetermineProbability(location) <= 0.0f)
|
||||
{
|
||||
var selectedTypeChangeIndex =
|
||||
ToolBox.SelectWeightedRandom(
|
||||
readyTypeChanges,
|
||||
readyTypeChanges.Select(i => readyTypeProbabilities[i]).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
var selectedTypeChange = cct[selectedTypeChangeIndex];
|
||||
if (selectedTypeChange != null)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(selectedTypeChange.ChangeToType, StringComparison.OrdinalIgnoreCase)));
|
||||
ChangeLocationType(location, prevName, selectedTypeChange);
|
||||
location.TypeChangeTimer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowedTypeChanges.Count > 0)
|
||||
{
|
||||
location.TypeChangeTimer++;
|
||||
//remove pending type change if it's no longer allowed
|
||||
location.PendingLocationTypeChange = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.TypeChangeTimer = 0;
|
||||
location.PendingLocationTypeChange.Second--;
|
||||
if (location.PendingLocationTypeChange.Second <= 0)
|
||||
{
|
||||
ChangeLocationType(location, location.PendingLocationTypeChange.First);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
location.UpdateStore();
|
||||
}
|
||||
|
||||
//find which types of locations this one can change to
|
||||
Dictionary<LocationTypeChange, float> allowedTypeChanges = new Dictionary<LocationTypeChange, float>();
|
||||
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
|
||||
{
|
||||
float probability = typeChange.DetermineProbability(location);
|
||||
if (probability <= 0.0f) { continue; }
|
||||
allowedTypeChanges.Add(typeChange, probability);
|
||||
}
|
||||
|
||||
//select a random type change
|
||||
if (Rand.Range(0.0f, 1.0f) < allowedTypeChanges.Sum(change => change.Value))
|
||||
{
|
||||
var selectedTypeChange =
|
||||
ToolBox.SelectWeightedRandom(
|
||||
allowedTypeChanges.Keys.ToList(),
|
||||
allowedTypeChanges.Values.ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
if (selectedTypeChange != null)
|
||||
{
|
||||
if (selectedTypeChange.RequiredDurationRange.X > 0)
|
||||
{
|
||||
location.PendingLocationTypeChange = new Pair<LocationTypeChange, int>(
|
||||
selectedTypeChange,
|
||||
Rand.Range(selectedTypeChange.RequiredDurationRange.X, selectedTypeChange.RequiredDurationRange.Y));
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLocationType(location, selectedTypeChange);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
|
||||
{
|
||||
if (typeChange.AnyWithinDistance(
|
||||
location,
|
||||
typeChange.RequiredProximityForProbabilityIncrease,
|
||||
(otherLocation) => { return typeChange.RequiredLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
if (!location.ProximityTimer.ContainsKey(typeChange)) { location.ProximityTimer[typeChange] = 0; }
|
||||
location.ProximityTimer[typeChange] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ProximityTimer.Remove(typeChange);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int DistanceToClosestLocationWithOutpost(Location startingLocation, out Location endingLocation)
|
||||
@@ -811,7 +839,18 @@ namespace Barotrauma
|
||||
return distance;
|
||||
}
|
||||
|
||||
partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change);
|
||||
private void ChangeLocationType(Location location, LocationTypeChange change)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase)));
|
||||
ChangeLocationTypeProjSpecific(location, prevName, change);
|
||||
location.ProximityTimer.Remove(change);
|
||||
location.TimeSinceLastTypeChange = 0;
|
||||
location.PendingLocationTypeChange = null;
|
||||
}
|
||||
|
||||
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change);
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
/// <summary>
|
||||
@@ -847,8 +886,19 @@ namespace Barotrauma
|
||||
{
|
||||
case "location":
|
||||
Location location = Locations[subElement.GetAttributeInt("i", 0)];
|
||||
|
||||
location.TypeChangeTimer = subElement.GetAttributeInt("changetimer", 0);
|
||||
location.ProximityTimer.Clear();
|
||||
for (int i = 0; i < location.Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
location.ProximityTimer.Add(location.Type.CanChangeTo[i], subElement.GetAttributeInt("changetimer" + i, 0));
|
||||
}
|
||||
int locationTypeChangeIndex = subElement.GetAttributeInt("pendinglocationtypechange", -1);
|
||||
if (locationTypeChangeIndex > 0 && locationTypeChangeIndex < location.Type.CanChangeTo.Count - 1)
|
||||
{
|
||||
location.PendingLocationTypeChange = new Pair<LocationTypeChange, int>(
|
||||
location.Type.CanChangeTo[locationTypeChangeIndex],
|
||||
subElement.GetAttributeInt("pendinglocationtypechangetimer", 0));
|
||||
}
|
||||
location.TimeSinceLastTypeChange = subElement.GetAttributeInt("timesincelasttypechange", 0);
|
||||
location.Discovered = subElement.GetAttributeBool("discovered", false);
|
||||
if (location.Discovered)
|
||||
{
|
||||
@@ -861,7 +911,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string locationType = subElement.GetAttributeString("type", "");
|
||||
string prevLocationName = location.Name;
|
||||
LocationType prevLocationType = location.Type;
|
||||
@@ -872,10 +921,14 @@ namespace Barotrauma
|
||||
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (change != null)
|
||||
{
|
||||
ChangeLocationType(location, prevLocationName, change);
|
||||
ChangeLocationTypeProjSpecific(location, prevLocationName, change);
|
||||
location.TimeSinceLastTypeChange = 0;
|
||||
}
|
||||
}
|
||||
|
||||
location.LoadStore(subElement);
|
||||
location.LoadMissions(subElement);
|
||||
|
||||
break;
|
||||
case "connection":
|
||||
int connectionIndex = subElement.GetAttributeInt("i", 0);
|
||||
|
||||
@@ -249,10 +249,6 @@ namespace Barotrauma
|
||||
{
|
||||
get { return ""; }
|
||||
}
|
||||
|
||||
private bool ignoreByAI;
|
||||
public bool IgnoreByAI => ignoreByAI;
|
||||
public void SetIgnoreByAI(bool ignore) => ignoreByAI = ignore;
|
||||
|
||||
public MapEntity(MapEntityPrefab prefab, Submarine submarine, ushort id) : base(submarine, id)
|
||||
{
|
||||
@@ -624,6 +620,21 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
if (t == typeof(Structure))
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
StructurePrefab structurePrefab = Structure.FindPrefab(name, identifier);
|
||||
if (structurePrefab == null)
|
||||
{
|
||||
ItemPrefab itemPrefab = ItemPrefab.Find(name, identifier);
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
t = typeof(Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo loadMethod = t.GetMethod("Load", new[] { typeof(XElement), typeof(Submarine), typeof(IdRemap) });
|
||||
|
||||
@@ -1386,10 +1386,10 @@ namespace Barotrauma
|
||||
{
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom();
|
||||
}
|
||||
characterInfo.TeamID = Character.TeamType.FriendlyNPC;
|
||||
characterInfo.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
npc.AnimController.FindHull(gotoTarget.WorldPosition, true);
|
||||
npc.TeamID = Character.TeamType.FriendlyNPC;
|
||||
npc.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
|
||||
{
|
||||
outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List<Character>());
|
||||
@@ -1404,9 +1404,9 @@ namespace Barotrauma
|
||||
npc.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
|
||||
}
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server);
|
||||
foreach (Item item in npc.Inventory.Items)
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
{
|
||||
if (item != null) { item.SpawnedInOutpost = true; }
|
||||
item.SpawnedInOutpost = true;
|
||||
}
|
||||
npc.GiveIdCardTags(gotoTarget as WayPoint);
|
||||
if (npc.AIController is HumanAIController humanAI)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -13,6 +12,14 @@ namespace Barotrauma
|
||||
public readonly int MinAvailableAmount;
|
||||
//maximum number of items available at a given store
|
||||
public readonly int MaxAvailableAmount;
|
||||
/// <summary>
|
||||
/// Used when both <see cref="MinAvailableAmount"/> and <see cref="MaxAvailableAmount"/> are set to 0.
|
||||
/// </summary>
|
||||
public const int DefaultAmount = 5;
|
||||
/// <summary>
|
||||
/// Can the item be a Daily Special or a Requested Good
|
||||
/// </summary>
|
||||
public readonly bool CanBeSpecial;
|
||||
|
||||
/// <summary>
|
||||
/// Support for the old style of determining item prices
|
||||
@@ -23,16 +30,21 @@ namespace Barotrauma
|
||||
{
|
||||
Price = element.GetAttributeInt("buyprice", 0);
|
||||
CanBeBought = true;
|
||||
MinAvailableAmount = GetMinAmount(element);
|
||||
MaxAvailableAmount = GetMaxAmount(element);
|
||||
var minAmount = GetMinAmount(element);
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
var maxAmount = GetMaxAmount(element);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0)
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
MinAvailableAmount = minAmount;
|
||||
MaxAvailableAmount = maxAmount;
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, minAmount);
|
||||
CanBeSpecial = canBeSpecial;
|
||||
}
|
||||
|
||||
public static List<Tuple<string, PriceInfo>> CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
|
||||
@@ -42,6 +54,7 @@ namespace Barotrauma
|
||||
var soldByDefault = element.GetAttributeBool("soldbydefault", true);
|
||||
var minAmount = GetMinAmount(element);
|
||||
var maxAmount = GetMaxAmount(element);
|
||||
var canBeSpecial = element.GetAttributeBool("canbespecial", true);
|
||||
var priceInfos = new List<Tuple<string, PriceInfo>>();
|
||||
|
||||
foreach (XElement childElement in element.GetChildElements("price"))
|
||||
@@ -51,13 +64,15 @@ namespace Barotrauma
|
||||
priceInfos.Add(new Tuple<string, PriceInfo>(childElement.GetAttributeString("locationtype", "").ToLowerInvariant(),
|
||||
new PriceInfo(price: (int)(priceMultiplier * basePrice), canBeBought: sold,
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0)));
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial: canBeSpecial)));
|
||||
}
|
||||
|
||||
var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true);
|
||||
defaultPrice = new PriceInfo(basePrice, canBeBoughtAtOtherLocations,
|
||||
minAmount: canBeBoughtAtOtherLocations ? minAmount : 0,
|
||||
maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0);
|
||||
maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0,
|
||||
canBeSpecial: canBeSpecial);
|
||||
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,11 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class WallSection : ISpatialEntity
|
||||
partial class WallSection : IIgnorable
|
||||
{
|
||||
public Rectangle rect;
|
||||
public float damage;
|
||||
public Gap gap;
|
||||
private bool ignoreByAI;
|
||||
|
||||
public Structure Wall { get; }
|
||||
public Vector2 Position => Wall.SectionPosition(Wall.Sections.IndexOf(this));
|
||||
@@ -28,7 +27,8 @@ namespace Barotrauma
|
||||
public Submarine Submarine => Wall.Submarine;
|
||||
public Rectangle WorldRect => Submarine == null ? rect :
|
||||
new Rectangle((int)(rect.X + Submarine.Position.X), (int)(rect.Y + Submarine.Position.Y), rect.Width, rect.Height);
|
||||
public bool IgnoreByAI => ignoreByAI;
|
||||
public bool IgnoreByAI => OrderedToBeIgnored;
|
||||
public bool OrderedToBeIgnored { get; set; }
|
||||
|
||||
public WallSection(Rectangle rect, Structure wall, float damage = 0.0f)
|
||||
{
|
||||
@@ -37,8 +37,6 @@ namespace Barotrauma
|
||||
this.damage = damage;
|
||||
Wall = wall;
|
||||
}
|
||||
|
||||
public void SetIgnoreByAI(bool ignore) => ignoreByAI = ignore;
|
||||
}
|
||||
|
||||
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
|
||||
@@ -843,7 +841,7 @@ namespace Barotrauma
|
||||
|
||||
public int FindSectionIndex(Vector2 displayPos, bool world = false, bool clamp = false)
|
||||
{
|
||||
if (!Sections.Any()) return -1;
|
||||
if (Sections.None()) { return -1; }
|
||||
|
||||
if (world && Submarine != null)
|
||||
{
|
||||
@@ -857,7 +855,7 @@ namespace Barotrauma
|
||||
displayPos.X += WallSectionSize - Sections[0].rect.Width;
|
||||
}
|
||||
|
||||
int index = (IsHorizontal) ?
|
||||
int index = IsHorizontal ?
|
||||
(int)Math.Floor((displayPos.X - rect.X) / WallSectionSize) :
|
||||
(int)Math.Floor((rect.Y - displayPos.Y) / WallSectionSize);
|
||||
|
||||
@@ -1082,6 +1080,7 @@ namespace Barotrauma
|
||||
|
||||
if (attacker != null && damageDiff != 0.0f)
|
||||
{
|
||||
HumanAIController.StructureDamaged(this, damageDiff, attacker);
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -1089,7 +1088,7 @@ namespace Barotrauma
|
||||
{
|
||||
attacker.Info.IncreaseSkillLevel("mechanical",
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f),
|
||||
SectionPosition(sectionIndex, true));
|
||||
SectionPosition(sectionIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1301,6 +1300,7 @@ namespace Barotrauma
|
||||
SerializableProperty.UpgradeGameVersion(s, s.Prefab.ConfigElement, submarine.Info.GameVersion);
|
||||
}
|
||||
|
||||
bool hasDamage = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1317,7 +1317,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
s.Sections[index].damage = subElement.GetAttributeFloat("damage", 0.0f);
|
||||
float damage = subElement.GetAttributeFloat("damage", 0.0f);
|
||||
s.Sections[index].damage = damage;
|
||||
hasDamage |= damage > 0.0f;
|
||||
}
|
||||
break;
|
||||
case "upgrade":
|
||||
@@ -1339,8 +1341,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("flippedx", false)) s.FlipX(false);
|
||||
if (element.GetAttributeBool("flippedy", false)) s.FlipY(false);
|
||||
if (element.GetAttributeBool("flippedx", false)) { s.FlipX(false); }
|
||||
if (element.GetAttributeBool("flippedy", false)) { s.FlipY(false); }
|
||||
|
||||
//structures with a body drop a shadow by default
|
||||
if (element.Attribute("usedropshadow") == null)
|
||||
@@ -1353,6 +1355,11 @@ namespace Barotrauma
|
||||
s.NoAITarget = prefab.NoAITarget;
|
||||
}
|
||||
|
||||
if (hasDamage)
|
||||
{
|
||||
s.UpdateSections();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
{
|
||||
public SubmarineInfo Info { get; private set; }
|
||||
|
||||
public Character.TeamType TeamID = Character.TeamType.None;
|
||||
public CharacterTeamType TeamID = CharacterTeamType.None;
|
||||
|
||||
public static readonly Vector2 HiddenSubStartPosition = new Vector2(-50000.0f, 10000.0f);
|
||||
//position of the "actual submarine" which is rendered wherever the SubmarineBody is
|
||||
@@ -254,7 +254,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Level.Loaded == null || subBody == null) { return false; }
|
||||
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth;
|
||||
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth & RealWorldDepth > RealWorldCrushDepth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace Barotrauma
|
||||
Info.Type = SubmarineType.Wreck;
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = Character.TeamType.None;
|
||||
TeamID = CharacterTeamType.None;
|
||||
|
||||
string defaultTag = Level.Loaded.GetWreckIDTag("wreck_id", this);
|
||||
ReplaceIDCardTagRequirements("wreck_id", defaultTag);
|
||||
@@ -554,7 +554,7 @@ namespace Barotrauma
|
||||
spawnPos.X = (limits.X + limits.Y) / 2 + subDockingPortOffset;
|
||||
}
|
||||
|
||||
spawnPos.Y = MathHelper.Clamp(spawnPos.Y, dockedBorders.Height / 2 + 10, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10);
|
||||
spawnPos.Y = MathHelper.Clamp(spawnPos.Y, dockedBorders.Height / 2 + 10, Level.Loaded.Size.Y - dockedBorders.Height / 2 - padding * 2);
|
||||
return spawnPos - diffFromDockedBorders;
|
||||
}
|
||||
|
||||
@@ -1332,7 +1332,7 @@ namespace Barotrauma
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = Character.TeamType.FriendlyNPC;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
@@ -1495,7 +1495,19 @@ namespace Barotrauma
|
||||
if (e is Item item)
|
||||
{
|
||||
if (item.FindParentInventory(inv => inv is CharacterInventory) != null) { continue; }
|
||||
#if CLIENT
|
||||
if (Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
if (e.Submarine != this && item.GetRootContainer()?.Submarine != this) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Submarine = this;
|
||||
}
|
||||
#else
|
||||
if (e.Submarine != this && item.GetRootContainer()?.Submarine != this) { continue; }
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1517,6 +1529,10 @@ namespace Barotrauma
|
||||
OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null,
|
||||
Name = Path.GetFileNameWithoutExtension(filePath)
|
||||
};
|
||||
#if CLIENT
|
||||
//remove reference to the preview image from the old info, so we don't dispose it (the new info still uses the texture)
|
||||
Info.PreviewImage = null;
|
||||
#endif
|
||||
Info.Dispose(); Info = newInfo;
|
||||
return newInfo.SaveAs(filePath, previewImage);
|
||||
}
|
||||
@@ -1691,6 +1707,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
node.Waypoint.FindHull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1705,6 +1722,7 @@ namespace Barotrauma
|
||||
nodes.Clear();
|
||||
obstructedNodes.Remove(otherSub);
|
||||
}
|
||||
OutdoorNodes.ForEach(n => n.Waypoint.FindHull());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,16 +450,19 @@ namespace Barotrauma
|
||||
#endif
|
||||
if (Level.Loaded == null) { return; }
|
||||
float submarineDepth = submarine.RealWorldDepth;
|
||||
if (submarineDepth < Level.Loaded.RealWorldCrushDepth) { return; }
|
||||
if (!Submarine.AtDamageDepth) { return; }
|
||||
|
||||
depthDamageTimer -= deltaTime;
|
||||
if (depthDamageTimer > 0.0f) { return; }
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != submarine || wall.CrushDepth > submarineDepth) { continue; }
|
||||
if (wall.Submarine != submarine) { continue; }
|
||||
|
||||
float pastCrushDepth = submarineDepth - wall.CrushDepth;
|
||||
float wallCrushDepth = wall.CrushDepth;
|
||||
if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; }
|
||||
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
|
||||
if (pastCrushDepth < 0) { return; }
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
|
||||
@@ -108,8 +108,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (hash == null)
|
||||
{
|
||||
XDocument doc = OpenFile(FilePath);
|
||||
StartHashDocTask(doc);
|
||||
if (hashTask == null)
|
||||
{
|
||||
XDocument doc = OpenFile(FilePath);
|
||||
StartHashDocTask(doc);
|
||||
}
|
||||
hashTask.Wait();
|
||||
hashTask = null;
|
||||
}
|
||||
@@ -118,6 +121,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool CalculatingHash
|
||||
{
|
||||
get { return hashTask != null && !hashTask.IsCompleted; }
|
||||
}
|
||||
|
||||
public Vector2 Dimensions
|
||||
{
|
||||
get;
|
||||
@@ -373,6 +381,10 @@ namespace Barotrauma
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if CLIENT
|
||||
PreviewImage?.Remove();
|
||||
PreviewImage = null;
|
||||
#endif
|
||||
if (savedSubmarines.Contains(this)) { savedSubmarines.Remove(this); }
|
||||
}
|
||||
|
||||
@@ -522,12 +534,13 @@ namespace Barotrauma
|
||||
|
||||
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (File.Exists(savedSubmarines[i].FilePath) &&
|
||||
savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) &&
|
||||
(Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath) ||
|
||||
contentPackageSubs.Any(fp => Path.GetFullPath(fp.Path).CleanUpPath() == Path.GetFullPath(savedSubmarines[i].FilePath).CleanUpPath())))
|
||||
if (File.Exists(savedSubmarines[i].FilePath))
|
||||
{
|
||||
continue;
|
||||
bool isDownloadedSub = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
|
||||
bool isInSubmarinesFolder = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath);
|
||||
bool isInContentPackage = contentPackageSubs.Any(fp => Path.GetFullPath(fp.Path).CleanUpPath() == Path.GetFullPath(savedSubmarines[i].FilePath).CleanUpPath());
|
||||
if (isDownloadedSub) { continue; }
|
||||
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && (isInSubmarinesFolder || isInContentPackage)) { continue; }
|
||||
}
|
||||
savedSubmarines[i].Dispose();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
|
||||
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
|
||||
|
||||
public const float LadderWaypointInterval = 100.0f;
|
||||
public const float LadderWaypointInterval = 70.0f;
|
||||
|
||||
protected SpawnType spawnType;
|
||||
private string[] idCardTags;
|
||||
@@ -99,6 +99,13 @@ namespace Barotrauma
|
||||
{
|
||||
SpawnType = SpawnType.Path;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { this }, false));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -179,42 +186,55 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float diffFromHullEdge = 50;
|
||||
float minDist = 150.0f;
|
||||
float minDist = 100.0f;
|
||||
float heightFromFloor = 110.0f;
|
||||
float hullMinHeight = 100;
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Rect.Height < 150) { continue; }
|
||||
|
||||
WayPoint prevWaypoint = null;
|
||||
|
||||
// Ignore hulls that a human couldn't fit in.
|
||||
// Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually.
|
||||
if (hull.Rect.Height < hullMinHeight) { continue; }
|
||||
// Don't create waypoints if there's no floor.
|
||||
Vector2 floorPos = new Vector2(hull.SimPosition.X, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
|
||||
Body floor = Submarine.PickBody(hull.SimPosition, floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
if (floor == null) { continue; }
|
||||
// Make sure that the waypoints don't go higher than the halfway of the room.
|
||||
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
|
||||
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
|
||||
{
|
||||
new WayPoint(
|
||||
new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
|
||||
continue;
|
||||
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
}
|
||||
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
else
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
|
||||
if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); }
|
||||
prevWaypoint = wayPoint;
|
||||
WayPoint prevWaypoint = null;
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); }
|
||||
prevWaypoint = wayPoint;
|
||||
}
|
||||
if (prevWaypoint == null)
|
||||
{
|
||||
// Ensure that we always create at least one waypoint per hull.
|
||||
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float outSideWaypointInterval = 200.0f;
|
||||
float outSideWaypointInterval = 100.0f;
|
||||
if (submarine.Info.Type != SubmarineType.OutpostModule)
|
||||
{
|
||||
int outsideWaypointDist = 100;
|
||||
List<WayPoint> outsideWaypoints = new List<WayPoint>();
|
||||
|
||||
Rectangle borders = Hull.GetBorders();
|
||||
borders.X -= outsideWaypointDist;
|
||||
borders.Y += outsideWaypointDist;
|
||||
borders.Width += outsideWaypointDist * 2;
|
||||
borders.Height += outsideWaypointDist * 2;
|
||||
int originalWidth = borders.Width;
|
||||
int originalHeight = borders.Height;
|
||||
borders.X -= Math.Min(500, originalWidth / 4);
|
||||
borders.Y += Math.Min(500, originalHeight / 4);
|
||||
borders.Width += Math.Min(1500, originalWidth / 2);
|
||||
borders.Height += Math.Min(1000, originalHeight / 2);
|
||||
borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
|
||||
|
||||
if (borders.Width <= outSideWaypointInterval * 2)
|
||||
@@ -238,6 +258,8 @@ namespace Barotrauma
|
||||
new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition,
|
||||
SpawnType.Path, submarine);
|
||||
|
||||
outsideWaypoints.Add(wayPoint);
|
||||
|
||||
if (x == borders.X + outSideWaypointInterval)
|
||||
{
|
||||
cornerWaypoint[i, 0] = wayPoint;
|
||||
@@ -260,18 +282,107 @@ namespace Barotrauma
|
||||
new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition,
|
||||
SpawnType.Path, submarine);
|
||||
|
||||
outsideWaypoints.Add(wayPoint);
|
||||
|
||||
if (y == borders.Y - borders.Height)
|
||||
{
|
||||
wayPoint.ConnectTo(cornerWaypoint[1, i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]);
|
||||
wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]);
|
||||
}
|
||||
}
|
||||
|
||||
wayPoint.ConnectTo(cornerWaypoint[0, i]);
|
||||
}
|
||||
|
||||
Vector2 center = ConvertUnits.ToSimUnits(submarine.HiddenSubPosition);
|
||||
float halfHeight = ConvertUnits.ToSimUnits(borders.Height / 2);
|
||||
// Try to move the waypoints so that they are near the walls, roughly following the shape of the sub.
|
||||
foreach (WayPoint wp in outsideWaypoints)
|
||||
{
|
||||
float xDiff = center.X - wp.SimPosition.X;
|
||||
Vector2 targetPos = new Vector2(center.X - xDiff * 0.5f, center.Y);
|
||||
Body wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
if (wall == null)
|
||||
{
|
||||
// Try again, and shoot to the center now. It happens with some subs that the first, offset raycast don't hit the walls.
|
||||
targetPos = new Vector2(center.X - xDiff, center.Y);
|
||||
wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
}
|
||||
if (wall != null)
|
||||
{
|
||||
float distanceFromWall = 1;
|
||||
if (xDiff > 0 && !submarine.Info.HasTag(SubmarineTag.Shuttle))
|
||||
{
|
||||
// We don't want to move the waypoints near the tail too close to the engine.
|
||||
float yDist = Math.Abs(center.Y - wp.SimPosition.Y);
|
||||
distanceFromWall = MathHelper.Lerp(1, 3, MathUtils.InverseLerp(halfHeight, 0, yDist));
|
||||
}
|
||||
Vector2 newPos = Submarine.LastPickedPosition + Submarine.LastPickedNormal * distanceFromWall;
|
||||
wp.rect = new Rectangle(ConvertUnits.ToDisplayUnits(newPos).ToPoint(), wp.rect.Size);
|
||||
wp.FindHull();
|
||||
}
|
||||
}
|
||||
// Remove unwanted points
|
||||
var removals = new List<WayPoint>();
|
||||
WayPoint previous = null;
|
||||
float tooClose = outSideWaypointInterval / 2;
|
||||
foreach (WayPoint wp in outsideWaypoints)
|
||||
{
|
||||
if (wp.CurrentHull != null ||
|
||||
Submarine.PickBody(wp.SimPosition, wp.SimPosition + Vector2.Normalize(center - wp.SimPosition) * 0.1f, collisionCategory: Physics.CollisionWall | Physics.CollisionItem, customPredicate: f => !(f.Body.UserData is Submarine), allowInsideFixture: true) != null)
|
||||
{
|
||||
// Remove waypoints that got inside/too near the sub.
|
||||
removals.Add(wp);
|
||||
previous = wp;
|
||||
continue;
|
||||
}
|
||||
foreach (WayPoint otherWp in outsideWaypoints)
|
||||
{
|
||||
if (otherWp == wp) { continue; }
|
||||
if (removals.Contains(otherWp)) { continue; }
|
||||
float sqrDist = Vector2.DistanceSquared(wp.Position, otherWp.Position);
|
||||
// Remove waypoints that are too close to each other.
|
||||
if (!removals.Contains(previous) && sqrDist < tooClose * tooClose)
|
||||
{
|
||||
removals.Add(wp);
|
||||
}
|
||||
}
|
||||
previous = wp;
|
||||
}
|
||||
foreach (WayPoint wp in removals)
|
||||
{
|
||||
outsideWaypoints.Remove(wp);
|
||||
wp.Remove();
|
||||
}
|
||||
// Connect loose ends (TODO: this sometimes fails, creating the connection to a wrong node)
|
||||
for (int i = 0; i < outsideWaypoints.Count; i++)
|
||||
{
|
||||
WayPoint current = outsideWaypoints[i];
|
||||
if (current.linkedTo.Count > 1) { continue; }
|
||||
WayPoint next = null;
|
||||
int maxConnections = 2;
|
||||
float tooFar = outSideWaypointInterval * 5;
|
||||
for (int j = 0; j < maxConnections; j++)
|
||||
{
|
||||
if (current.linkedTo.Count >= maxConnections) { break; }
|
||||
tooFar /= current.linkedTo.Count;
|
||||
// First try to find a loose end
|
||||
next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp != next && wp.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.linkedTo.Count < 2);
|
||||
// Then accept any connection that not connected to the existing connection
|
||||
next ??= current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp != next && wp.linkedTo.None(e => current.linkedTo.Contains(e)));
|
||||
if (next != null)
|
||||
{
|
||||
current.ConnectTo(next);
|
||||
}
|
||||
}
|
||||
if (current.linkedTo.Count == 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't automatically link waypoint {current.ID}. You should do it manually.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Structure> stairList = new List<Structure>();
|
||||
@@ -298,13 +409,13 @@ namespace Barotrauma
|
||||
{
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f, 30f));
|
||||
if (closest == null) continue;
|
||||
WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(100, 70));
|
||||
if (closest == null) { continue; }
|
||||
stairPoints[i].ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
|
||||
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position)/2, SpawnType.Path, submarine);
|
||||
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine);
|
||||
stairPoints[0].ConnectTo(stairPoints[2]);
|
||||
stairPoints[2].ConnectTo(stairPoints[1]);
|
||||
}
|
||||
@@ -314,21 +425,44 @@ namespace Barotrauma
|
||||
var ladders = item.GetComponent<Ladder>();
|
||||
if (ladders == null) { continue; }
|
||||
|
||||
Vector2 bottomPoint = new Vector2(item.Rect.Center.X, item.Rect.Top - item.Rect.Height + 10);
|
||||
List<WayPoint> ladderPoints = new List<WayPoint>
|
||||
{
|
||||
new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine)
|
||||
new WayPoint(bottomPoint, SpawnType.Path, submarine),
|
||||
};
|
||||
|
||||
WayPoint prevPoint = ladderPoints[0];
|
||||
Vector2 prevPos = prevPoint.SimPosition;
|
||||
List<Body> ignoredBodies = new List<Body>();
|
||||
|
||||
for (float y = ladderPoints[0].Position.Y + LadderWaypointInterval; y < item.Rect.Y - 1.0f; y += LadderWaypointInterval)
|
||||
// Lowest point is only meaningful for hanging ladders inside the sub, but it shouldn't matter in other cases either.
|
||||
// Start point is where the bots normally grasp the ladder when they stand on ground.
|
||||
WayPoint lowestPoint = ladderPoints[0];
|
||||
WayPoint prevPoint = lowestPoint;
|
||||
Vector2 prevPos = prevPoint.SimPosition;
|
||||
Body ground = Submarine.PickBody(lowestPoint.SimPosition, lowestPoint.SimPosition - Vector2.UnitY, ignoredBodies,
|
||||
collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform | Physics.CollisionStairs,
|
||||
customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
float startHeight = ground != null ? ConvertUnits.ToDisplayUnits(ground.Position.Y) : bottomPoint.Y;
|
||||
startHeight += heightFromFloor;
|
||||
WayPoint startPoint = lowestPoint;
|
||||
Vector2 nextPos = new Vector2(item.Rect.Center.X, startHeight);
|
||||
// Don't create the start point if it's too close to the lowest point or if it's outside of the sub.
|
||||
// If we skip creating the start point, the lowest point is used instead.
|
||||
if (lowestPoint == null || Math.Abs(startPoint.Position.Y - startHeight) > 40 && Hull.FindHull(nextPos) != null)
|
||||
{
|
||||
startPoint = new WayPoint(nextPos, SpawnType.Path, submarine);
|
||||
ladderPoints.Add(startPoint);
|
||||
if (lowestPoint != null)
|
||||
{
|
||||
startPoint.ConnectTo(lowestPoint);
|
||||
}
|
||||
prevPoint = startPoint;
|
||||
prevPos = prevPoint.SimPosition;
|
||||
}
|
||||
for (float y = startPoint.Position.Y + LadderWaypointInterval; y < item.Rect.Y - 1.0f; y += LadderWaypointInterval)
|
||||
{
|
||||
//first check if there's a door in the way
|
||||
//(we need to create a waypoint linked to the door for NPCs to open it)
|
||||
Body pickedBody = Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)),
|
||||
prevPos, ignoredBodies, Physics.CollisionWall, false,
|
||||
(Fixture f) => f.Body.UserData is Item && ((Item)f.Body.UserData).GetComponent<Door>() != null);
|
||||
|
||||
@@ -341,7 +475,7 @@ namespace Barotrauma
|
||||
{
|
||||
//no door, check for walls
|
||||
pickedBody = Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos, ignoredBodies, null, false,
|
||||
ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, null, false,
|
||||
(Fixture f) => f.Body.UserData is Structure);
|
||||
}
|
||||
|
||||
@@ -374,75 +508,94 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (prevPoint.rect.Y < item.Rect.Y - 10.0f)
|
||||
// Cap
|
||||
if (prevPoint.rect.Y < item.Rect.Y - 40)
|
||||
{
|
||||
WayPoint newPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine);
|
||||
ladderPoints.Add(newPoint);
|
||||
newPoint.ConnectTo(prevPoint);
|
||||
WayPoint wayPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine);
|
||||
ladderPoints.Add(wayPoint);
|
||||
wayPoint.ConnectTo(prevPoint);
|
||||
}
|
||||
|
||||
//connect ladder waypoints to hull points at the right and left side
|
||||
|
||||
// Connect ladder waypoints to hull points at the right and left side
|
||||
foreach (WayPoint ladderPoint in ladderPoints)
|
||||
{
|
||||
ladderPoint.Ladders = ladders;
|
||||
//don't connect if the waypoint is at a gap (= at the boundary of hulls and/or at a hatch)
|
||||
if (ladderPoint.ConnectedGap != null) continue;
|
||||
|
||||
bool isHatch = ladderPoint.ConnectedGap != null && !ladderPoint.ConnectedGap.IsRoomToRoom;
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = ladderPoint.FindClosest(dir, true, new Vector2(-150.0f, 50f));
|
||||
if (closest == null) continue;
|
||||
WayPoint closest = null;
|
||||
if (isHatch)
|
||||
{
|
||||
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderPoints);
|
||||
}
|
||||
else
|
||||
{
|
||||
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 70), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
|
||||
}
|
||||
if (closest == null) { continue; }
|
||||
ladderPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
|
||||
// Another pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders)
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!gap.IsHorizontal) continue;
|
||||
|
||||
//too small to walk through
|
||||
if (gap.Rect.Height < 150.0f) continue;
|
||||
|
||||
var wayPoint = new WayPoint(
|
||||
new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor), SpawnType.Path, submarine, gap);
|
||||
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
float tolerance = gap.IsRoomToRoom ? 50.0f : outSideWaypointInterval / 2.0f;
|
||||
|
||||
WayPoint closest = wayPoint.FindClosest(
|
||||
dir, true, new Vector2(-tolerance, tolerance),
|
||||
gap.ConnectedDoor?.Body.FarseerBody);
|
||||
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
var ladders = item.GetComponent<Ladder>();
|
||||
if (ladders == null) { continue; }
|
||||
var wps = WayPointList.Where(wp => wp.Ladders == ladders).OrderByDescending(wp => wp.Rect.Y);
|
||||
WayPoint cap = wps.First();
|
||||
WayPoint above = cap.FindClosest(1, horizontalSearch: false, tolerance: new Vector2(25, 50), filter: wp => wp.Ladders != null && wp.Ladders != ladders);
|
||||
above?.ConnectTo(cap);
|
||||
WayPoint bottom = wps.Last();
|
||||
WayPoint below = bottom.FindClosest(-1, horizontalSearch: false, tolerance: new Vector2(25, 50), filter: wp => wp.Ladders != null && wp.Ladders != ladders);
|
||||
below?.ConnectTo(bottom);
|
||||
}
|
||||
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.IsHorizontal || gap.IsRoomToRoom || !gap.linkedTo.Any(l => l is Hull)) { continue; }
|
||||
|
||||
//too small to walk through
|
||||
if (gap.Rect.Width < 100.0f) { continue; }
|
||||
|
||||
var wayPoint = new WayPoint(
|
||||
new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2), SpawnType.Path, submarine, gap);
|
||||
|
||||
float tolerance = outSideWaypointInterval / 2.0f;
|
||||
Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull);
|
||||
int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y);
|
||||
|
||||
WayPoint closest = wayPoint.FindClosest(
|
||||
dir, false, new Vector2(-tolerance, tolerance),
|
||||
gap.ConnectedDoor?.Body.FarseerBody);
|
||||
|
||||
if (closest != null)
|
||||
if (gap.IsHorizontal)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
// Too small to walk through
|
||||
if (gap.Rect.Height < hullMinHeight) { continue; }
|
||||
Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor);
|
||||
var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
|
||||
// The closest waypoint can be quite far if the gap is at an exterior door.
|
||||
Vector2 tolerance = gap.IsRoomToRoom ? new Vector2(150, 70) : new Vector2(1000, 1000);
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, tolerance, gap.ConnectedDoor?.Body.FarseerBody);
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create waypoints on vertical gaps on the outer walls, also hatches.
|
||||
if (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull)) { continue; }
|
||||
// Too small to swim through
|
||||
if (gap.Rect.Width < 50.0f) { continue; }
|
||||
Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2);
|
||||
// Some hatches are created in the block above where we handle the ladder waypoints. So we need to check for duplicates.
|
||||
if (WayPointList.Any(wp => wp.ConnectedGap == gap)) { continue; }
|
||||
var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
|
||||
Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull);
|
||||
int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y);
|
||||
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(50, 100));
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
for (dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), gap.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null);
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +615,36 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null)
|
||||
private WayPoint FindClosestOutside(IEnumerable<WayPoint> waypointList, float tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<WayPoint, bool> filter = null)
|
||||
{
|
||||
float closestDist = 0;
|
||||
WayPoint closest = null;
|
||||
foreach (WayPoint wp in waypointList)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Path || wp == this) { continue; }
|
||||
// Ignore if already linked
|
||||
if (linkedTo.Contains(wp)) { continue; }
|
||||
if (ignored != null && ignored.Contains(wp)) { continue; }
|
||||
if (filter != null && !filter(wp)) { continue; }
|
||||
float sqrDist = Vector2.DistanceSquared(Position, wp.Position);
|
||||
if (closest == null || sqrDist < closestDist)
|
||||
{
|
||||
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
|
||||
if (body != null && body != ignoredBody && !(body.UserData is Submarine))
|
||||
{
|
||||
if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
closestDist = sqrDist;
|
||||
closest = wp;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<WayPoint, bool> filter = null)
|
||||
{
|
||||
if (dir != -1 && dir != 1) { return null; }
|
||||
|
||||
@@ -473,33 +655,45 @@ namespace Barotrauma
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Path || wp == this) { continue; }
|
||||
|
||||
float xDiff = wp.Position.X - Position.X;
|
||||
float yDiff = wp.Position.Y - Position.Y;
|
||||
float xDist = Math.Abs(xDiff);
|
||||
float yDist = Math.Abs(yDiff);
|
||||
if (tolerance.X < xDist) { continue; }
|
||||
if (tolerance.Y < yDist) { continue; }
|
||||
|
||||
float dist = 0.0f;
|
||||
float diff = 0.0f;
|
||||
if (horizontalSearch)
|
||||
{
|
||||
if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) { continue; }
|
||||
diff = wp.Position.X - Position.X;
|
||||
dist = Math.Abs(diff) + Math.Abs(wp.Position.Y - Position.Y) / 5.0f;
|
||||
diff = xDiff;
|
||||
dist = xDist + yDist / 5.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) { continue; }
|
||||
diff = wp.Position.Y - Position.Y;
|
||||
dist = Math.Abs(diff) + Math.Abs(wp.Position.X - Position.X) / 5.0f;
|
||||
diff = yDiff;
|
||||
dist = yDist + xDist / 5.0f;
|
||||
//prefer ladder waypoints when moving vertically
|
||||
if (wp.Ladders != null) { dist *= 0.5f; }
|
||||
}
|
||||
|
||||
if (Math.Sign(diff) != dir) { continue; }
|
||||
// Ignore if already linked
|
||||
if (linkedTo.Contains(wp)) { continue; }
|
||||
if (ignored != null && ignored.Contains(wp)) { continue; }
|
||||
if (filter != null && !filter(wp)) { continue; }
|
||||
|
||||
if (closest == null || dist < closestDist)
|
||||
{
|
||||
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, true, true, false);
|
||||
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
|
||||
if (body != null && body != ignoredBody && !(body.UserData is Submarine))
|
||||
{
|
||||
if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
closestDist = dist;
|
||||
closest = wp;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user