Unstable v0.1300.0.0 (February 19th 2021)
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,13 +202,18 @@ 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; }
|
||||
|
||||
[Serialize("", true, "What sound to play when the ballast flora bursts thru walls")]
|
||||
public string BurstSound { get; set; } = "";
|
||||
|
||||
private float availablePower;
|
||||
|
||||
private float toxinsTimer;
|
||||
|
||||
[Serialize(0f, true, "How much power the ballast flora has stored.")]
|
||||
public float AvailablePower
|
||||
{
|
||||
get => availablePower;
|
||||
@@ -244,7 +252,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public float PowerConsumptionTimer;
|
||||
|
||||
private float defenseCooldown, toxinsCooldown, fireCheckCooldown;
|
||||
private float damageIndicatorTimer, selfDamageTimer;
|
||||
private float damageIndicatorTimer, selfDamageTimer, toxinsTimer;
|
||||
|
||||
private readonly List<BallastFloraBranch> branchesVulnerableToFire = new List<BallastFloraBranch>();
|
||||
|
||||
@@ -293,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);
|
||||
@@ -373,8 +382,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
int flowerConfig = getInt("flowerconfig");
|
||||
int leafconfig = getInt("leafconfig");
|
||||
int id = getInt("ID");
|
||||
int health = getInt("health");
|
||||
int maxhealth = getInt("maxhealth");
|
||||
float health = getFloat("health");
|
||||
float maxhealth = getFloat("maxhealth");
|
||||
int sides = getInt("sides");
|
||||
int blockedSides = getInt("blockedsides");
|
||||
int claimedId = branchElement.GetAttributeInt("claimed", -1);
|
||||
@@ -398,6 +407,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
Branches.Add(newBranch);
|
||||
|
||||
int getInt(string name) => branchElement.GetAttributeInt(name, 0);
|
||||
float getFloat(string name) => branchElement.GetAttributeFloat(name, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,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);
|
||||
@@ -466,24 +477,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selfDamageTimer <= 0)
|
||||
{
|
||||
if (!CanGrowMore())
|
||||
{
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
float maxHealth = branch.IsRoot ? StemHealth : BranchHealth;
|
||||
DamageBranch(branch, Rand.Range(1f, maxHealth), AttackType.Other);
|
||||
}
|
||||
}
|
||||
|
||||
selfDamageTimer = 1f;
|
||||
}
|
||||
|
||||
selfDamageTimer -= deltaTime;
|
||||
}
|
||||
|
||||
UpdateSelfDamage(deltaTime);
|
||||
|
||||
if (Anger > 1f)
|
||||
{
|
||||
@@ -544,6 +539,41 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelfDamage(float deltaTime)
|
||||
{
|
||||
if (selfDamageTimer <= 0)
|
||||
{
|
||||
bool hasRoot = false;
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.IsRoot)
|
||||
{
|
||||
hasRoot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRoot)
|
||||
{
|
||||
Kill();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasBrokenThrough && !CanGrowMore())
|
||||
{
|
||||
Branches.ForEachMod(branch =>
|
||||
{
|
||||
float maxHealth = branch.IsRoot ? StemHealth : BranchHealth;
|
||||
DamageBranch(branch, Rand.Range(1f, maxHealth), AttackType.Other);
|
||||
});
|
||||
}
|
||||
|
||||
selfDamageTimer = 1f;
|
||||
}
|
||||
|
||||
selfDamageTimer -= deltaTime;
|
||||
}
|
||||
|
||||
private void UpdatePowerDrain(float deltaTime)
|
||||
{
|
||||
PowerConsumptionTimer += deltaTime;
|
||||
@@ -576,7 +606,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
float batteryDrain = powerDelta * 0.1f;
|
||||
foreach (PowerContainer battery in ClaimedBatteries)
|
||||
{
|
||||
float amount = Math.Max(battery.MaxOutPut, batteryDrain);
|
||||
float amount = Math.Min(battery.MaxOutPut, batteryDrain);
|
||||
|
||||
if (battery.Charge > amount)
|
||||
{
|
||||
@@ -744,7 +774,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
#if SERVER
|
||||
if (!load)
|
||||
{
|
||||
SendNetworkMessage(this, NetworkHeader.Infect, target.ID, true);
|
||||
SendNetworkMessage(this, NetworkHeader.Infect, target.ID, true, branch);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -802,7 +832,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
Vector2 flowerPos = GetWorldPosition() + newBranch.Position;
|
||||
CreateShapnel(flowerPos);
|
||||
newBranch.GrowthStep = 2.0f;
|
||||
SoundPlayer.PlayDamageSound("ArmorBreak", 1.0f, flowerPos, range: 800);
|
||||
SoundPlayer.PlayDamageSound(BurstSound, 1.0f, flowerPos, range: 800);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -840,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; }
|
||||
|
||||
@@ -853,7 +884,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
if (IsInWater(branch))
|
||||
{
|
||||
return;
|
||||
damage *= 1f - SubmergedWaterResistance;
|
||||
}
|
||||
|
||||
if (defenseCooldown <= 0)
|
||||
@@ -861,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)
|
||||
@@ -901,6 +932,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
target.Infector = null;
|
||||
}
|
||||
|
||||
_entityList.Remove(this);
|
||||
}
|
||||
|
||||
public void RemoveBranch(BallastFloraBranch branch)
|
||||
@@ -935,18 +968,10 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
#if CLIENT
|
||||
Vector2 pos = GetWorldPosition() + branch.Position;
|
||||
|
||||
GameMain.ParticleManager.CreateParticle("bloodsplash", pos, Rand.Range(0, 360), Rand.Range(0, 100));
|
||||
GameMain.ParticleManager.CreateParticle("waterblood", pos, Rand.Range(0, 360), 0);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("gib", pos, Rand.Range(0, 360), Rand.Range(100f, 300f));
|
||||
}
|
||||
CreateDeathParticle(branch);
|
||||
#endif
|
||||
|
||||
if (isClient) { return; }
|
||||
@@ -1014,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)
|
||||
{
|
||||
@@ -1041,7 +1068,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
CreateShapnel(GetWorldPosition() + branch.Position);
|
||||
}
|
||||
|
||||
SoundPlayer.PlayDamageSound("ArmorBreak", BreakthroughPoint, GetWorldPosition(), range: 800);
|
||||
SoundPlayer.PlayDamageSound(BurstSound, BreakthroughPoint, GetWorldPosition(), range: 800);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+14
-8
@@ -51,6 +51,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
if (pump.Item.CurrentHull == targetBranch.CurrentHull)
|
||||
{
|
||||
targetPumps.Add(pump);
|
||||
SetPump(pump);
|
||||
pump.Hijacked = true;
|
||||
}
|
||||
}
|
||||
@@ -90,18 +91,23 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPump(Pump pump)
|
||||
{
|
||||
if (pump.TargetLevel != null)
|
||||
{
|
||||
pump.TargetLevel = 100f;
|
||||
}
|
||||
else
|
||||
{
|
||||
pump.FlowPercentage = 100f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (Pump pump in targetPumps)
|
||||
{
|
||||
if (pump.TargetLevel != null)
|
||||
{
|
||||
pump.TargetLevel = 100f;
|
||||
}
|
||||
else
|
||||
{
|
||||
pump.FlowPercentage = 100f;
|
||||
}
|
||||
SetPump(pump);
|
||||
}
|
||||
|
||||
if (tryDrown && !filled)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
@@ -25,6 +26,17 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
protected override void Grow()
|
||||
{
|
||||
if (TargetBranches.Any(b => b.Removed))
|
||||
{
|
||||
if (!Behavior.IgnoredTargets.ContainsKey(Target))
|
||||
{
|
||||
Behavior.IgnoredTargets.Add(Target, 10);
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Target == null || Target.Removed)
|
||||
{
|
||||
isFinished = true;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
{
|
||||
private static readonly List<Triplet<Explosion, Vector2, float>> prevExplosions = new List<Triplet<Explosion, Vector2, float>>();
|
||||
|
||||
private readonly Attack attack;
|
||||
public readonly Attack Attack;
|
||||
|
||||
private readonly float force;
|
||||
|
||||
@@ -25,7 +25,11 @@ namespace Barotrauma
|
||||
private readonly float screenColorRange, screenColorDuration;
|
||||
|
||||
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
|
||||
private bool playTinnitus;
|
||||
private bool applyFireEffects;
|
||||
private bool ignoreCover;
|
||||
private bool onlyInside;
|
||||
private bool onlyOutside;
|
||||
private readonly float flashDuration;
|
||||
private readonly float? flashRange;
|
||||
private readonly string decal;
|
||||
@@ -35,14 +39,15 @@ namespace Barotrauma
|
||||
|
||||
public float BallastFloraDamage { get; set; }
|
||||
|
||||
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f)
|
||||
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
|
||||
{
|
||||
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
|
||||
Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
|
||||
{
|
||||
SeverLimbsProbability = 1.0f
|
||||
};
|
||||
this.force = force;
|
||||
this.EmpStrength = empStrength;
|
||||
BallastFloraDamage = ballastFloraStrength;
|
||||
sparks = true;
|
||||
shockwave = true;
|
||||
smoke = true;
|
||||
@@ -52,7 +57,7 @@ namespace Barotrauma
|
||||
|
||||
public Explosion(XElement element, string parentDebugName)
|
||||
{
|
||||
attack = new Attack(element, parentDebugName + ", Explosion");
|
||||
Attack = new Attack(element, parentDebugName + ", Explosion");
|
||||
|
||||
force = element.GetAttributeFloat("force", 0.0f);
|
||||
|
||||
@@ -62,7 +67,12 @@ namespace Barotrauma
|
||||
underwaterBubble = element.GetAttributeBool("underwaterbubble", true);
|
||||
smoke = element.GetAttributeBool("smoke", true);
|
||||
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", true);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames);
|
||||
ignoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
onlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
onlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
|
||||
flash = element.GetAttributeBool("flash", true);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
@@ -74,10 +84,10 @@ namespace Barotrauma
|
||||
decal = element.GetAttributeString("decal", "");
|
||||
decalSize = element.GetAttributeFloat(1.0f, "decalSize", "decalsize");
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f);
|
||||
cameraShakeRange = element.GetAttributeFloat("camerashakerange", attack.Range);
|
||||
cameraShake = element.GetAttributeFloat("camerashake", Attack.Range * 0.1f);
|
||||
cameraShakeRange = element.GetAttributeFloat("camerashakerange", Attack.Range);
|
||||
|
||||
screenColorRange = element.GetAttributeFloat("screencolorrange", attack.Range * 0.1f);
|
||||
screenColorRange = element.GetAttributeFloat("screencolorrange", Attack.Range * 0.1f);
|
||||
screenColor = element.GetAttributeColor("screencolor", Color.Transparent);
|
||||
screenColorDuration = element.GetAttributeFloat("screencolorduration", 0.1f);
|
||||
}
|
||||
@@ -113,7 +123,7 @@ namespace Barotrauma
|
||||
hull.AddDecal(decal, worldPosition, decalSize, isNetworkEvent: false);
|
||||
}
|
||||
|
||||
float displayRange = attack.Range;
|
||||
float displayRange = Attack.Range;
|
||||
|
||||
Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
@@ -128,9 +138,9 @@ namespace Barotrauma
|
||||
|
||||
if (displayRange < 0.1f) { return; }
|
||||
|
||||
if (attack.GetStructureDamage(1.0f) > 0.0f)
|
||||
if (Attack.GetStructureDamage(1.0f) > 0.0f)
|
||||
{
|
||||
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attacker);
|
||||
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker);
|
||||
}
|
||||
|
||||
if (BallastFloraDamage > 0.0f)
|
||||
@@ -165,12 +175,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(attack.Stun, 0.0f) && MathUtils.NearlyEqual(attack.GetTotalDamage(false), 0.0f))
|
||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, attack, force, damageSource, attacker);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -180,9 +190,9 @@ namespace Barotrauma
|
||||
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
|
||||
float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
|
||||
if (dist > attack.Range) { continue; }
|
||||
if (dist > Attack.Range) { continue; }
|
||||
|
||||
if (dist < attack.Range * 0.5f && applyFireEffects && !item.FireProof)
|
||||
if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof)
|
||||
{
|
||||
//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)
|
||||
@@ -209,8 +219,8 @@ namespace Barotrauma
|
||||
|
||||
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
|
||||
{
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
float distFactor = 1.0f - dist / Attack.Range;
|
||||
float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }
|
||||
@@ -224,7 +234,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; }
|
||||
|
||||
@@ -239,6 +249,8 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (onlyInside && c.Submarine == null) { continue; }
|
||||
else if (onlyOutside && c.Submarine != null) { continue; }
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (c.Submarine != null) { explosionPos -= c.Submarine.Position; }
|
||||
@@ -253,7 +265,7 @@ namespace Barotrauma
|
||||
List<Affliction> modifiedAfflictions = new List<Affliction>();
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || limb.IgnoreCollisions) { continue; }
|
||||
if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; }
|
||||
|
||||
float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
|
||||
|
||||
@@ -267,7 +279,10 @@ namespace Barotrauma
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
|
||||
//solid obstacles between the explosion and the limb reduce the effect of the explosion
|
||||
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
|
||||
if (!ignoreCover)
|
||||
{
|
||||
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
|
||||
}
|
||||
distFactors.Add(limb, distFactor);
|
||||
|
||||
modifiedAfflictions.Clear();
|
||||
@@ -322,10 +337,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);
|
||||
}
|
||||
@@ -354,7 +369,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||
/// </summary>
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null, bool damageLevelWalls = true)
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null)
|
||||
{
|
||||
List<Structure> structureList = new List<Structure>();
|
||||
float dist = 600.0f;
|
||||
@@ -391,7 +406,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (Level.Loaded != null && damageLevelWalls)
|
||||
if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
|
||||
{
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -400,7 +415,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (cell.IsPointInside(worldPosition))
|
||||
{
|
||||
destructibleWall.AddDamage(damage, worldPosition);
|
||||
destructibleWall.AddDamage(levelWallDamage, worldPosition);
|
||||
continue;
|
||||
}
|
||||
foreach (var edge in cell.Edges)
|
||||
|
||||
@@ -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;
|
||||
@@ -934,14 +939,14 @@ namespace Barotrauma
|
||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||
/// Uses a greedy algo and may not use the most optimal path. Returns float.MaxValue if no path is found.
|
||||
/// </summary>
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance)
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor = 0)
|
||||
{
|
||||
return GetApproximateHullDistance(startPos, endPos, new HashSet<Hull>(), targetHull, 0.0f, maxDistance);
|
||||
return GetApproximateHullDistance(startPos, endPos, new HashSet<Hull>(), targetHull, 0.0f, maxDistance, distanceMultiplierPerClosedDoor);
|
||||
}
|
||||
|
||||
private float GetApproximateHullDistance(Vector2 startPos, Vector2 endPos, HashSet<Hull> connectedHulls, Hull target, float distance, float maxDistance)
|
||||
private float GetApproximateHullDistance(Vector2 startPos, Vector2 endPos, HashSet<Hull> connectedHulls, Hull target, float distance, float maxDistance, float distanceMultiplierFromDoors = 0)
|
||||
{
|
||||
if (distance >= maxDistance) return float.MaxValue;
|
||||
if (distance >= maxDistance) { return float.MaxValue; }
|
||||
if (this == target)
|
||||
{
|
||||
return distance + Vector2.Distance(startPos, endPos);
|
||||
@@ -951,12 +956,17 @@ namespace Barotrauma
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
float distanceMultiplier = 1;
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
{
|
||||
//gap blocked if the door is not open or the predicted state is not open
|
||||
if ((!g.ConnectedDoor.IsOpen && !g.ConnectedDoor.IsBroken) || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
{
|
||||
if (g.ConnectedDoor.OpenState < 0.1f) continue;
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
{
|
||||
if (distanceMultiplierFromDoors <= 0) { continue; }
|
||||
distanceMultiplier *= distanceMultiplierFromDoors;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g.Open <= 0.0f)
|
||||
@@ -968,8 +978,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
|
||||
{
|
||||
float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position), maxDistance);
|
||||
if (dist < float.MaxValue) { return dist; }
|
||||
float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position) * distanceMultiplier, maxDistance);
|
||||
if (dist < float.MaxValue)
|
||||
{
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,23 +123,30 @@ 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)
|
||||
{
|
||||
return PasteEntities(position, sub, configElement, FilePath, selectInstance);
|
||||
}
|
||||
|
||||
public static List<MapEntity> PasteEntities(Vector2 position, Submarine sub, XElement configElement, string filePath = null, bool selectInstance = false)
|
||||
{
|
||||
int idOffset = Entity.FindFreeID(1);
|
||||
if (MapEntity.mapEntityList.Any()) { idOffset = MapEntity.mapEntityList.Max(e => e.ID); }
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, FilePath, idOffset);
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, filePath, idOffset);
|
||||
if (entities.Count == 0) { return entities; }
|
||||
|
||||
Vector2 offset = sub == null ? Vector2.Zero : sub.HiddenSubPosition;
|
||||
Vector2 offset = sub?.HiddenSubPosition ?? Vector2.Zero;
|
||||
|
||||
foreach (MapEntity me in entities)
|
||||
{
|
||||
@@ -148,14 +168,13 @@ 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);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return entities;
|
||||
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -100,19 +101,19 @@ namespace Barotrauma
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, Rand.RandSync rand)
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, bool abyss, Rand.RandSync rand)
|
||||
{
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams) <= 0.0f))
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams, abyss) <= 0.0f))
|
||||
{
|
||||
return CaveParams.First();
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams)).ToList(), rand);
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams, abyss)).ToList(), rand);
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
public float GetCommonness(LevelGenerationParams generationParams, bool abyss)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
OverrideCommonness.TryGetValue(abyss ? "abyss" : generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,69 +141,36 @@ namespace Barotrauma
|
||||
return cells;
|
||||
}
|
||||
|
||||
|
||||
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
|
||||
{
|
||||
if (cell == null) { cell = edge.AdjacentCell(null); }
|
||||
if (cell == null) { return Vector2.UnitX; }
|
||||
|
||||
CompareCCW compare = new CompareCCW(cell.Center);
|
||||
if (compare.Compare(edge.Point1, edge.Point2) == -1)
|
||||
{
|
||||
var temp = edge.Point1;
|
||||
edge.Point1 = edge.Point2;
|
||||
edge.Point2 = temp;
|
||||
}
|
||||
|
||||
Vector2 normal = Vector2.Normalize(edge.Point2 - edge.Point1);
|
||||
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.Point2);
|
||||
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
if (Vector2.Dot(normal, diffToCell) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
public static void GeneratePath(Level.Tunnel tunnel, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, Rectangle limits)
|
||||
public static void GeneratePath(Level.Tunnel tunnel, Level level)
|
||||
{
|
||||
var targetCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
//a search depth of 2 is large enough to find a cell in almost all maps, but in case it fails, we increase the depth
|
||||
int searchDepth = 2;
|
||||
while (searchDepth < 5)
|
||||
var closestCell = level.GetClosestCell(tunnel.Nodes[i].ToVector2());
|
||||
if (closestCell != null && !targetCells.Contains(closestCell))
|
||||
{
|
||||
int cellIndex = FindCellIndex(tunnel.Nodes[i], cells, cellGrid, gridCellSize, searchDepth);
|
||||
if (cellIndex > -1)
|
||||
{
|
||||
targetCells.Add(cells[cellIndex]);
|
||||
break;
|
||||
}
|
||||
|
||||
searchDepth++;
|
||||
targetCells.Add(closestCell);
|
||||
}
|
||||
}
|
||||
tunnel.Cells.AddRange(GeneratePath(targetCells, cells, limits));
|
||||
tunnel.Cells.AddRange(GeneratePath(targetCells, level.GetAllCells()));
|
||||
}
|
||||
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells, Rectangle limits)
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
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
|
||||
{
|
||||
@@ -216,10 +183,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)
|
||||
{
|
||||
@@ -258,7 +229,7 @@ namespace Barotrauma
|
||||
List<GraphEdge> tempEdges = new List<GraphEdge>();
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid)
|
||||
if (!edge.IsSolid || edge.OutsideLevel)
|
||||
{
|
||||
tempEdges.Add(edge);
|
||||
continue;
|
||||
@@ -289,7 +260,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<Vector2> edgePoints = new List<Vector2>();
|
||||
Vector2 edgeNormal = GetEdgeNormal(edge, cell);
|
||||
Vector2 edgeNormal = edge.GetNormal(cell);
|
||||
|
||||
float edgeLength = Vector2.Distance(edge.Point1, edge.Point2);
|
||||
int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
|
||||
Vector2 edgeDir = edge.Point2 - edge.Point1;
|
||||
@@ -310,12 +282,39 @@ namespace Barotrauma
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.Server);
|
||||
Vector2 extrudedPoint =
|
||||
edge.Point1 +
|
||||
edgeDir * (i / (float)pointCount) -
|
||||
edgeDir * (i / (float)pointCount) +
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF;
|
||||
|
||||
//check if extruding the edge causes it to go inside another one
|
||||
var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 1);
|
||||
if (!nearbyCells.Any(c => c.CellType == CellType.Solid && c != cell && c.IsPointInside(extrudedPoint))) { edgePoints.Add(extrudedPoint); }
|
||||
var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2);
|
||||
bool isInside = false;
|
||||
foreach (var nearbyCell in nearbyCells)
|
||||
{
|
||||
if (nearbyCell == cell || nearbyCell.CellType != CellType.Solid) { continue; }
|
||||
//check if extruding the edge causes it to go inside another one
|
||||
if (nearbyCell.IsPointInside(extrudedPoint))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
}
|
||||
//check if another edge will be inside this cell after the extrusion
|
||||
Vector2 triangleCenter = (edge.Point1 + edge.Point2 + extrudedPoint) / 3;
|
||||
foreach (GraphEdge nearbyEdge in nearbyCell.Edges)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isInside) { break; }
|
||||
}
|
||||
|
||||
if (!isInside)
|
||||
{
|
||||
edgePoints.Add(extrudedPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +380,19 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, cell.Center));
|
||||
Vector2 minVert = tempVertices[0];
|
||||
Vector2 maxVert = tempVertices[0];
|
||||
foreach (var vert in tempVertices)
|
||||
{
|
||||
minVert = new Vector2(
|
||||
Math.Min(minVert.X, vert.X),
|
||||
Math.Min(minVert.Y, vert.Y));
|
||||
maxVert = new Vector2(
|
||||
Math.Max(maxVert.X, vert.X),
|
||||
Math.Max(maxVert.Y, vert.Y));
|
||||
}
|
||||
Vector2 center = (minVert + maxVert) / 2;
|
||||
renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, center));
|
||||
|
||||
if (bodyPoints.Count < 2) { continue; }
|
||||
|
||||
@@ -404,7 +415,7 @@ namespace Barotrauma
|
||||
if (cell.CellType == CellType.Empty) { continue; }
|
||||
|
||||
cellBody.UserData = cell;
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center));
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(center));
|
||||
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
@@ -435,14 +446,21 @@ namespace Barotrauma
|
||||
}
|
||||
cell.Body = cellBody;
|
||||
}
|
||||
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
cellBody.ResetMassData();
|
||||
|
||||
return cellBody;
|
||||
}
|
||||
|
||||
|
||||
public static List<Vector2> CreateRandomChunk(float radius, int vertexCount, float radiusVariance)
|
||||
{
|
||||
Debug.Assert(radiusVariance < radius);
|
||||
return CreateRandomChunk(radius * 2, radius * 2, vertexCount, radiusVariance);
|
||||
}
|
||||
|
||||
public static List<Vector2> CreateRandomChunk(float width, float height, int vertexCount, float radiusVariance)
|
||||
{
|
||||
Debug.Assert(radiusVariance < Math.Min(width, height));
|
||||
Debug.Assert(vertexCount >= 3);
|
||||
|
||||
List<Vector2> verts = new List<Vector2>();
|
||||
@@ -450,72 +468,12 @@ namespace Barotrauma
|
||||
float angle = 0.0f;
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
verts.Add(new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) *
|
||||
(radius + Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.Server)));
|
||||
Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
|
||||
verts.Add(new Vector2(dir.X * width / 2, dir.Y * height / 2) + dir * Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.Server));
|
||||
angle += angleStep;
|
||||
}
|
||||
return verts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find the index of the cell which the point is inside
|
||||
/// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram)
|
||||
/// </summary>
|
||||
public static int FindCellIndex(Vector2 position,List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1, Vector2? offset = null)
|
||||
{
|
||||
float closestDist = float.PositiveInfinity;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
Vector2 gridOffset = offset == null ? Vector2.Zero : (Vector2)offset;
|
||||
position -= gridOffset;
|
||||
|
||||
int gridPosX = (int)Math.Floor(position.X / gridCellSize);
|
||||
int gridPosY = (int)Math.Floor(position.Y / gridCellSize);
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(cellGrid[x, y][i].Center, position);
|
||||
if (dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
|
||||
public static int FindCellIndex(Point position, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1)
|
||||
{
|
||||
int closestDist = int.MaxValue;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
int gridPosX = position.X / gridCellSize;
|
||||
int gridPosY = position.Y / gridCellSize;
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
int dist = MathUtils.DistanceSquared(
|
||||
(int)cellGrid[x, y][i].Site.Coord.X, (int)cellGrid[x, y][i].Site.Coord.Y,
|
||||
position.X, position.Y);
|
||||
if (dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ 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);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -201,6 +202,10 @@ namespace Barotrauma
|
||||
if (Destroyed) { return; }
|
||||
Destroyed = true;
|
||||
level?.UnsyncedExtraWalls?.Remove(this);
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
}
|
||||
GameMain.World.Remove(Body);
|
||||
Dispose();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,12 +29,19 @@ namespace Barotrauma
|
||||
public bool HasBeaconStation;
|
||||
public bool IsBeaconActive;
|
||||
|
||||
public bool HasHuntingGrounds;
|
||||
|
||||
public OutpostGenerationParams ForceOutpostGenerationParams;
|
||||
|
||||
public readonly Point Size;
|
||||
|
||||
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>();
|
||||
|
||||
@@ -81,6 +88,8 @@ namespace Barotrauma
|
||||
HasBeaconStation = element.GetAttributeBool("hasbeaconstation", false);
|
||||
IsBeaconActive = element.GetAttributeBool("isbeaconactive", false);
|
||||
|
||||
HasHuntingGrounds = element.GetAttributeBool("hashuntinggrounds", false);
|
||||
|
||||
string generationParamsId = element.GetAttributeString("generationparams", "");
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || l.OldIdentifier == generationParamsId);
|
||||
if (GenerationParams == null)
|
||||
@@ -96,7 +105,7 @@ namespace Barotrauma
|
||||
InitialDepth = element.GetAttributeInt("initialdepth", GenerationParams.InitialDepthMin);
|
||||
|
||||
string biomeIdentifier = element.GetAttributeString("biome", "");
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier);
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier || b.OldIdentifier == biomeIdentifier);
|
||||
if (Biome == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in level data: could not find the biome \"{biomeIdentifier}\".");
|
||||
@@ -135,7 +144,10 @@ namespace Barotrauma
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
|
||||
HasBeaconStation = rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
HasHuntingGrounds = rand.NextDouble() < Difficulty / 100.0f * maxHuntingGroundsProbability;
|
||||
|
||||
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
IsBeaconActive = false;
|
||||
}
|
||||
|
||||
@@ -158,7 +170,7 @@ namespace Barotrauma
|
||||
(int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
|
||||
}
|
||||
|
||||
public static LevelData CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null)
|
||||
public static LevelData CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null, bool requireOutpost = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
@@ -167,25 +179,34 @@ namespace Barotrauma
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type;
|
||||
LevelType type = generationParams == null ?
|
||||
(requireOutpost ? LevelType.Outpost : LevelType.LocationConnection) :
|
||||
generationParams.Type;
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
var biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
|
||||
LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server);
|
||||
|
||||
float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
||||
var levelData = new LevelData(
|
||||
seed,
|
||||
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
|
||||
generationParams,
|
||||
biome)
|
||||
biome);
|
||||
if (type == LevelType.LocationConnection)
|
||||
{
|
||||
HasBeaconStation = beaconRng < 0.5f,
|
||||
IsBeaconActive = beaconRng > 0.25f
|
||||
};
|
||||
GameMain.GameSession?.GameMode?.Mission?.AdjustLevelData(levelData);
|
||||
float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
||||
levelData.HasBeaconStation = beaconRng < 0.5f;
|
||||
levelData.IsBeaconActive = beaconRng > 0.25f;
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode != null)
|
||||
{
|
||||
foreach (Mission mission in GameMain.GameSession.GameMode.Missions)
|
||||
{
|
||||
mission.AdjustLevelData(levelData);
|
||||
}
|
||||
}
|
||||
return levelData;
|
||||
}
|
||||
|
||||
@@ -207,6 +228,13 @@ namespace Barotrauma
|
||||
new XAttribute("isbeaconactive", IsBeaconActive.ToString()));
|
||||
}
|
||||
|
||||
if (HasHuntingGrounds)
|
||||
{
|
||||
newElement.Add(
|
||||
new XAttribute("hashuntinggrounds", HasHuntingGrounds.ToString()));
|
||||
|
||||
}
|
||||
|
||||
if (Type == LevelType.Outpost)
|
||||
{
|
||||
if (EventHistory.Any())
|
||||
|
||||
@@ -195,25 +195,25 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(100000, true), Editable]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = Math.Max(value, 2000); }
|
||||
set { minWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(100000, true), Editable]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = Math.Max(value, 2000); }
|
||||
set { maxWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(50000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(50000, true), Editable]
|
||||
public int Height
|
||||
{
|
||||
get { return height; }
|
||||
set { height = Math.Max(value, 2000); }
|
||||
set { height = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
@@ -404,7 +404,35 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
[Serialize(5, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int AbyssIslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("4000,7000", true), Editable]
|
||||
public Point AbyssIslandSizeMin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("8000,10000", true), Editable]
|
||||
public Point AbyssIslandSizeMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.5f, true), Editable()]
|
||||
public float AbyssIslandCaveProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
public int SeaFloorDepth
|
||||
{
|
||||
get { return seaFloorBaseDepth; }
|
||||
@@ -554,7 +582,7 @@ namespace Barotrauma
|
||||
var matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type && lp.allowedBiomes.Any());
|
||||
if (biome == null)
|
||||
{
|
||||
matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.Any(b => b.IsEndBiome));
|
||||
matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.All(b => b.IsEndBiome));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
+60
-14
@@ -151,10 +151,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
availableSpawnPositions.Clear();
|
||||
foreach (Level.Cave cave in level.Caves)
|
||||
{
|
||||
availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall)));
|
||||
availableSpawnPositions.Clear();
|
||||
suitableSpawnPositions.Clear();
|
||||
spawnPositionWeights.Clear();
|
||||
|
||||
@@ -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,19 +184,63 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +418,7 @@ namespace Barotrauma
|
||||
return objects;
|
||||
}
|
||||
|
||||
private readonly static List<LevelObject> objectsInRange = new List<LevelObject>();
|
||||
private readonly static HashSet<LevelObject> objectsInRange = new HashSet<LevelObject>();
|
||||
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
|
||||
{
|
||||
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
|
||||
@@ -391,10 +437,10 @@ namespace Barotrauma
|
||||
{
|
||||
for (int y = minIndices.Y; y <= maxIndices.Y; y++)
|
||||
{
|
||||
if (objectGrid[x, y] == null) continue;
|
||||
if (objectGrid[x, y] == null) { continue; }
|
||||
foreach (LevelObject obj in objectGrid[x, y])
|
||||
{
|
||||
if (!objectsInRange.Contains(obj)) objectsInRange.Add(obj);
|
||||
objectsInRange.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
+9
-8
@@ -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,
|
||||
}
|
||||
|
||||
@@ -319,7 +320,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (List.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Loading additional level object prefabs from file '{configPath}'");
|
||||
DebugConsole.Log($"Loading additional level object prefabs from file '{configPath}'");
|
||||
}
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +553,7 @@ namespace Barotrauma
|
||||
float structureDamage = attack.GetStructureDamage(deltaTime);
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, damageLevelWalls: false);
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,7 +643,7 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 GetWaterFlowVelocity()
|
||||
{
|
||||
if (Force == Vector2.Zero) return Vector2.Zero;
|
||||
if (Force == Vector2.Zero || ForceMode == TriggerForceMode.LimitVelocity) { return Vector2.Zero; }
|
||||
|
||||
Vector2 vel = Force;
|
||||
if (ForceMode == TriggerForceMode.Acceleration)
|
||||
|
||||
@@ -36,7 +36,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float WallDamageOnTouch;
|
||||
private float wallDamageOnTouch;
|
||||
public float WallDamageOnTouch
|
||||
{
|
||||
get { return wallDamageOnTouch; }
|
||||
set
|
||||
{
|
||||
Cells.ForEach(c => c.DoesDamage = !MathUtils.NearlyEqual(value, 0.0f));
|
||||
wallDamageOnTouch = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float MoveSpeed;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -289,7 +297,8 @@ namespace Barotrauma
|
||||
{
|
||||
originalMyPortID = myPort.Item.ID;
|
||||
|
||||
myPort.Undock();
|
||||
myPort.Undock(applyEffects: false);
|
||||
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,
|
||||
@@ -313,7 +322,7 @@ namespace Barotrauma
|
||||
sub.SetPosition((linkedPort.Item.WorldPosition - portDiff) - offset);
|
||||
|
||||
myPort.Dock(linkedPort);
|
||||
myPort.Lock(true);
|
||||
myPort.Lock(true, applyEffects: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,20 @@ 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("previewimage") != null)
|
||||
{
|
||||
saveElement.Attribute("previewimage").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);
|
||||
|
||||
@@ -33,8 +33,9 @@ namespace Barotrauma
|
||||
{
|
||||
OriginalContainerID = item.OriginalContainerID;
|
||||
}
|
||||
|
||||
OriginalID = item.ID;
|
||||
ModuleIndex = (ushort)item.OriginalModuleIndex;
|
||||
ModuleIndex = (ushort) item.OriginalModuleIndex;
|
||||
Identifier = item.prefab.Identifier;
|
||||
}
|
||||
|
||||
@@ -42,6 +43,7 @@ namespace Barotrauma
|
||||
{
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
}
|
||||
|
||||
public bool Matches(Item item)
|
||||
{
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
@@ -56,13 +58,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public readonly List<LocationConnection> Connections = new List<LocationConnection>();
|
||||
|
||||
|
||||
private string baseName;
|
||||
private int nameFormatIndex;
|
||||
|
||||
public bool Discovered;
|
||||
|
||||
public int TypeChangeTimer;
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
public int LocationTypeChangeCooldown;
|
||||
|
||||
public readonly int ZoneIndex;
|
||||
|
||||
public string BaseName { get => baseName; }
|
||||
|
||||
@@ -80,14 +86,76 @@ namespace Barotrauma
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
|
||||
public int[] ProximityTime { get; private set; }
|
||||
public int TurnsInRadiation { 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
|
||||
@@ -106,12 +174,11 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
availableMissions.RemoveAll(m => m.Completed || m.Failed);
|
||||
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
|
||||
return availableMissions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Mission SelectedMission
|
||||
{
|
||||
get;
|
||||
@@ -152,6 +219,10 @@ namespace Barotrauma
|
||||
|
||||
public string LastTypeChangeMessage;
|
||||
|
||||
public int TimeSinceLastTypeChange;
|
||||
|
||||
public bool IsGateBetweenBiomes;
|
||||
|
||||
private struct LoadedMission
|
||||
{
|
||||
public MissionPrefab MissionPrefab { get; }
|
||||
@@ -175,29 +246,49 @@ namespace Barotrauma
|
||||
return $"Location ({Name ?? "null"})";
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, IEnumerable<Location> existingLocations = null)
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, LocationType? forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
Type = LocationType.Random(rand, zone, requireOutpost);
|
||||
Type = forceLocationType ?? LocationType.Random(rand, zone, requireOutpost);
|
||||
Name = RandomName(Type, rand, existingLocations);
|
||||
MapPosition = mapPosition;
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
Connections = new List<LocationConnection>();
|
||||
ProximityTime = new int[Type.CanChangeTo.Count];
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
public Location(XElement element)
|
||||
{
|
||||
string locationType = element.GetAttributeString("type", "");
|
||||
Type = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase));
|
||||
bool typeNotFound = false;
|
||||
if (Type == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find location type \"{locationType}\". Using location type \"None\" instead.");
|
||||
Type = LocationType.List.Find(lt => lt.Identifier.Equals("None", StringComparison.OrdinalIgnoreCase));
|
||||
Type ??= LocationType.List.First();
|
||||
typeNotFound = true;
|
||||
}
|
||||
|
||||
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);
|
||||
ProximityTime = element.GetAttributeIntArray("proximitytime", new int[Type.CanChangeTo.Count]);
|
||||
if (ProximityTime.Length != Type.CanChangeTo.Count) { ProximityTime = new int[Type.CanChangeTo.Count]; }
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
|
||||
if (!typeNotFound)
|
||||
{
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < Type.CanChangeTo[i].Requirements.Count; j++)
|
||||
{
|
||||
ProximityTimer.Add(Type.CanChangeTo[i].Requirements[j], element.GetAttributeInt("proximitytimer" + i + "-" + j, 0));
|
||||
}
|
||||
}
|
||||
|
||||
LoadLocationTypeChange(element);
|
||||
}
|
||||
|
||||
string[] takenItemStr = element.GetAttributeStringArray("takenitems", new string[0]);
|
||||
foreach (string takenItem in takenItemStr)
|
||||
@@ -237,16 +328,42 @@ 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);
|
||||
}
|
||||
|
||||
public void LoadLocationTypeChange(XElement locationElement)
|
||||
{
|
||||
TimeSinceLastTypeChange = locationElement.GetAttributeInt("timesincelasttypechange", 0);
|
||||
LocationTypeChangeCooldown = locationElement.GetAttributeInt("locationtypechangecooldown", 0);
|
||||
foreach (XElement subElement in locationElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "pendinglocationtypechange":
|
||||
int timer = subElement.GetAttributeInt("timer", 0);
|
||||
if (subElement.Attribute("index") != null)
|
||||
{
|
||||
int locationTypeChangeIndex = subElement.GetAttributeInt("index", 0);
|
||||
PendingLocationTypeChange = (Type.CanChangeTo[locationTypeChangeIndex], timer, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
string missionIdentifier = subElement.GetAttributeString("missionidentifier", "");
|
||||
var mission = MissionPrefab.List.Find(mp => mp.Identifier.Equals(missionIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (mission == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change from the mission \"{missionIdentifier}\" in location \"{Name}\". Matching mission not found.");
|
||||
continue;
|
||||
}
|
||||
PendingLocationTypeChange = (mission.LocationTypeChangeOnCompleted, timer, mission);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadMissions(XElement locationElement)
|
||||
{
|
||||
if (locationElement.GetChildElement("missions") is XElement missionsElement)
|
||||
@@ -266,9 +383,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, IEnumerable<Location> existingLocations = null)
|
||||
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, LocationType? forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
return new Location(position, zone, rand, requireOutpost, existingLocations);
|
||||
return new Location(position, zone, rand, requireOutpost, forceLocationType, existingLocations);
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
@@ -278,15 +395,34 @@ namespace Barotrauma
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
ProximityTime = new int[Type.CanChangeTo.Count];
|
||||
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
Name = Type.NameFormats == null ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom());
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom());
|
||||
}
|
||||
|
||||
CreateStore(force: true);
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
var mission = InstantiateMission(missionPrefab, ref connection);
|
||||
var mission = InstantiateMission(missionPrefab, connection);
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
@@ -304,8 +440,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
LocationConnection connection = null;
|
||||
var mission = InstantiateMission(missionPrefab, ref connection);
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
{
|
||||
@@ -332,14 +467,13 @@ namespace Barotrauma
|
||||
var unusedMissions = matchingMissions.Where(m => !availableMissions.Any(mission => mission.Prefab == m));
|
||||
if (unusedMissions.Any())
|
||||
{
|
||||
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this))));
|
||||
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this)) || m.IsAllowed(this, this)));
|
||||
if (!suitableMissions.Any())
|
||||
{
|
||||
suitableMissions = unusedMissions;
|
||||
}
|
||||
LocationConnection connection = null;
|
||||
MissionPrefab missionPrefab = suitableMissions.GetRandom();
|
||||
var mission = InstantiateMission(missionPrefab, ref connection);
|
||||
MissionPrefab missionPrefab = ToolBox.SelectWeightedRandom(suitableMissions.ToList(), suitableMissions.Select(m => (float)m.Commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
{
|
||||
@@ -360,8 +494,14 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, ref LocationConnection connection)
|
||||
private Mission InstantiateMission(MissionPrefab prefab, out LocationConnection connection)
|
||||
{
|
||||
if (prefab.IsAllowed(this, this))
|
||||
{
|
||||
connection = null;
|
||||
return InstantiateMission(prefab);
|
||||
}
|
||||
|
||||
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
|
||||
if (!suitableConnections.Any())
|
||||
{
|
||||
@@ -371,21 +511,33 @@ namespace Barotrauma
|
||||
connection = ToolBox.SelectWeightedRandom(
|
||||
suitableConnections.ToList(),
|
||||
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
Rand.RandSync.Unsynced);
|
||||
|
||||
return InstantiateMission(prefab, connection);
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection)
|
||||
{
|
||||
Location destination = connection.OtherLocation(this);
|
||||
var mission = prefab.Instantiate(new Location[] { this, destination });
|
||||
mission.AdjustLevelData(connection.LevelData);
|
||||
return mission;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab)
|
||||
{
|
||||
var mission = prefab.Instantiate(new Location[] { this, this });
|
||||
mission.AdjustLevelData(LevelData);
|
||||
return mission;
|
||||
}
|
||||
|
||||
public void InstantiateLoadedMissions(Map map)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
if (loadedMissions == null || loadedMissions.None()) { return; }
|
||||
foreach (LoadedMission loadedMission in loadedMissions)
|
||||
{
|
||||
Location destination = null;
|
||||
Location destination;
|
||||
if (loadedMission.DestinationIndex >= 0 && loadedMission.DestinationIndex < map.Locations.Count)
|
||||
{
|
||||
destination = map.Locations[loadedMission.DestinationIndex];
|
||||
@@ -410,6 +562,23 @@ namespace Barotrauma
|
||||
SelectedMissionIndex = -1;
|
||||
}
|
||||
|
||||
public bool HasOutpost()
|
||||
{
|
||||
if (!Type.HasOutpost) { return false; }
|
||||
|
||||
return !IsCriticallyRadiated();
|
||||
}
|
||||
|
||||
public bool IsCriticallyRadiated()
|
||||
{
|
||||
if (GameMain.GameSession is { Campaign: { Map: { } map } })
|
||||
{
|
||||
return TurnsInRadiation > map.Radiation.Params.CriticalRadiationThreshold;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
|
||||
@@ -456,6 +625,61 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRadiated() => GameMain.GameSession is { Campaign: { Map: { Radiation: { Enabled: true } radiation } } } && radiation.Contains(this);
|
||||
|
||||
private List<PurchasedItem> CreateStoreStock()
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
@@ -463,31 +687,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 +747,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 +818,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 +847,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 +868,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 +893,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 +987,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,17 +1010,42 @@ namespace Barotrauma
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier));
|
||||
if (ProximityTime.Length > 0 && ProximityTime.Any(t => t > 0))
|
||||
{
|
||||
locationElement.Add(new XAttribute("proximitytime", string.Join(',', ProximityTime.Select(i => i.ToString()))));
|
||||
}
|
||||
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier),
|
||||
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange),
|
||||
new XAttribute(nameof(TurnsInRadiation).ToLower(), TurnsInRadiation));
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
if (TypeChangeTimer > 0)
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
locationElement.Add(new XAttribute("changetimer", TypeChangeTimer));
|
||||
for (int j = 0; j < Type.CanChangeTo[i].Requirements.Count; j++)
|
||||
{
|
||||
if (ProximityTimer.ContainsKey(Type.CanChangeTo[i].Requirements[j]))
|
||||
{
|
||||
locationElement.Add(new XAttribute("proximitytimer" + i + "-" + j, ProximityTimer[Type.CanChangeTo[i].Requirements[j]]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PendingLocationTypeChange.HasValue)
|
||||
{
|
||||
var changeElement = new XElement("pendinglocationtypechange", new XAttribute("timer", PendingLocationTypeChange.Value.delay));
|
||||
if (PendingLocationTypeChange.Value.parentMission != null)
|
||||
{
|
||||
changeElement.Add(new XAttribute("missionidentifier", PendingLocationTypeChange.Value.parentMission.Identifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
changeElement.Add(new XAttribute("index", Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange)));
|
||||
}
|
||||
locationElement.Add(changeElement);
|
||||
}
|
||||
|
||||
if (LocationTypeChangeCooldown > 0)
|
||||
{
|
||||
locationElement.Add(new XAttribute("locationtypechangecooldown", LocationTypeChangeCooldown));
|
||||
}
|
||||
|
||||
if (takenItems.Any())
|
||||
{
|
||||
locationElement.Add(new XAttribute(
|
||||
@@ -719,7 +1059,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; }
|
||||
@@ -727,6 +1071,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);
|
||||
}
|
||||
|
||||
@@ -735,7 +1102,7 @@ namespace Barotrauma
|
||||
var missionsElement = new XElement("missions");
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
var location = mission.Locations.FirstOrDefault(l => l != this);
|
||||
var location = mission.Locations.All(l => l == this) ? this : mission.Locations.FirstOrDefault(l => l != this);
|
||||
var i = map.Locations.IndexOf(location);
|
||||
missionsElement.Add(new XElement("mission",
|
||||
new XAttribute("prefabid", mission.Prefab.Identifier),
|
||||
@@ -750,40 +1117,6 @@ namespace Barotrauma
|
||||
return locationElement;
|
||||
}
|
||||
|
||||
public int Distance(Location other, int maxRecursionDepth, int currRecursionDepth = 0)
|
||||
{
|
||||
if (currRecursionDepth >= maxRecursionDepth) { return -1; }
|
||||
if (other == this) { return 0; }
|
||||
int minDist = -1;
|
||||
foreach (Location connected in Connections.Select(c => c.Locations.First(l => l != this)))
|
||||
{
|
||||
int dist = connected.Distance(other, maxRecursionDepth, currRecursionDepth+1);
|
||||
if (dist >= 0)
|
||||
{
|
||||
if (minDist < 0 || dist < minDist) { minDist = dist; }
|
||||
}
|
||||
}
|
||||
return minDist;
|
||||
}
|
||||
|
||||
public void DetermineProximityTime(Location currentLocation)
|
||||
{
|
||||
int dist = Distance(currentLocation, Type.CanChangeTo.Select(cct => cct.RequiredProximityForProbabilityIncrease).Max());
|
||||
for (int i=0;i<ProximityTime.Length;i++)
|
||||
{
|
||||
if (dist <= Type.CanChangeTo[i].RequiredProximityForProbabilityIncrease)
|
||||
{
|
||||
ProximityTime[i]++;
|
||||
if (ProximityTime[i] > 5) { ProximityTime[i] = 5; }
|
||||
}
|
||||
else
|
||||
{
|
||||
ProximityTime[i]--;
|
||||
if (ProximityTime[i] < 0) { ProximityTime[i] = 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
RemoveProjSpecific();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -31,8 +32,31 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly List<Mission> availableMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> AvailableMissions
|
||||
{
|
||||
get
|
||||
{
|
||||
availableMissions.RemoveAll(m => m.Completed || (m.Failed && m.Prefab.AllowRetry));
|
||||
return availableMissions;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,12 @@ namespace Barotrauma
|
||||
class LocationType
|
||||
{
|
||||
public static readonly List<LocationType> List = new List<LocationType>();
|
||||
|
||||
private readonly List<string> nameFormats;
|
||||
private readonly List<string> names;
|
||||
|
||||
private readonly Sprite symbolSprite;
|
||||
|
||||
private readonly List<Sprite> portraits = new List<Sprite>();
|
||||
|
||||
//<name, commonness>
|
||||
private List<Tuple<JobPrefab, float>> hireableJobs;
|
||||
private float totalHireableWeight;
|
||||
private readonly List<Tuple<JobPrefab, float>> hireableJobs;
|
||||
private readonly float totalHireableWeight;
|
||||
|
||||
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
|
||||
|
||||
@@ -34,16 +29,20 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
|
||||
|
||||
public readonly List<string> MissionIdentifiers = new List<string>();
|
||||
public readonly List<string> MissionTags = new List<string>();
|
||||
|
||||
public readonly List<string> HideEntitySubcategories = new List<string>();
|
||||
|
||||
public bool IsEnterable { get; private set; }
|
||||
|
||||
public bool UseInMainMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<string> NameFormats
|
||||
{
|
||||
get { return nameFormats; }
|
||||
}
|
||||
|
||||
public List<string> NameFormats { get; private set; }
|
||||
|
||||
public bool HasHireableCharacters
|
||||
{
|
||||
@@ -56,10 +55,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return symbolSprite; }
|
||||
}
|
||||
public Sprite Sprite { get; private set; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
@@ -79,9 +75,15 @@ namespace Barotrauma
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
NameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
|
||||
MissionIdentifiers = element.GetAttributeStringArray("missionidentifiers", new string[0]).ToList();
|
||||
MissionTags = element.GetAttributeStringArray("missiontags", new string[0]).ToList();
|
||||
|
||||
HideEntitySubcategories = element.GetAttributeStringArray("hideentitysubcategories", new string[0]).ToList();
|
||||
|
||||
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
|
||||
try
|
||||
@@ -135,11 +137,11 @@ namespace Barotrauma
|
||||
hireableJobs.Add(hireableJob);
|
||||
break;
|
||||
case "symbol":
|
||||
symbolSprite = new Sprite(subElement, lazyLoad: true);
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
SpriteColor = subElement.GetAttributeColor("color", Color.White);
|
||||
break;
|
||||
case "changeto":
|
||||
CanChangeTo.Add(new LocationTypeChange(Identifier, subElement));
|
||||
CanChangeTo.Add(new LocationTypeChange(Identifier, subElement, requireChangeMessages: true));
|
||||
break;
|
||||
case "portrait":
|
||||
var portrait = new Sprite(subElement, lazyLoad: true);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -6,41 +8,268 @@ namespace Barotrauma
|
||||
{
|
||||
class LocationTypeChange
|
||||
{
|
||||
public class Requirement
|
||||
{
|
||||
public enum FunctionType
|
||||
{
|
||||
Add,
|
||||
Multiply
|
||||
}
|
||||
|
||||
public readonly FunctionType Function;
|
||||
|
||||
/// <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>
|
||||
/// Does there need to be a beacon station within RequiredProximity
|
||||
/// </summary>
|
||||
public readonly bool RequireBeaconStation;
|
||||
|
||||
/// <summary>
|
||||
/// Does there need to be hunting grounds within RequiredProximity
|
||||
/// </summary>
|
||||
public readonly bool RequireHuntingGrounds;
|
||||
|
||||
public Requirement(XElement element, LocationTypeChange change)
|
||||
{
|
||||
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);
|
||||
RequireBeaconStation = element.GetAttributeBool("requirebeaconstation", false);
|
||||
RequireHuntingGrounds = element.GetAttributeBool("requirehuntinggrounds", false);
|
||||
|
||||
string functionStr = element.GetAttributeString("function", "Add");
|
||||
if (!Enum.TryParse(functionStr, ignoreCase: true, out Function))
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Invalid location type change in location type \"{change.CurrentType}\". " +
|
||||
$"\"{functionStr}\" is not a valid function.");
|
||||
}
|
||||
|
||||
Probability = element.GetAttributeFloat("probability", 1.0f);
|
||||
|
||||
if (RequiredProximityForProbabilityIncrease > 0 || ProximityProbabilityIncrease > 0.0f)
|
||||
{
|
||||
if (!RequiredLocations.Any() && !RequireBeaconStation && !RequireHuntingGrounds)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{change.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 \"{change.CurrentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the base probability is already 100%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesLocation(Location location)
|
||||
{
|
||||
return RequiredLocations.Contains(location.Type.Identifier) && !location.IsCriticallyRadiated();
|
||||
}
|
||||
|
||||
public bool AnyWithinDistance(Location location, int maxDistance, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && MatchesLocation(location)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
if (RequireBeaconStation && connection.LevelData.HasBeaconStation && connection.LevelData.IsBeaconActive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (RequireHuntingGrounds && connection.LevelData.HasHuntingGrounds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinDistance(otherLocation, maxDistance, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string CurrentType;
|
||||
|
||||
public readonly string ChangeToType;
|
||||
|
||||
/// <summary>
|
||||
/// Base probability per turn for the location to change if near one of the RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float Probability;
|
||||
public readonly int RequiredDuration;
|
||||
|
||||
public readonly float ProximityProbabilityIncrease;
|
||||
public readonly int RequiredProximityForProbabilityIncrease;
|
||||
public readonly bool RequireDiscovered;
|
||||
|
||||
public List<Requirement> Requirements = new List<Requirement>();
|
||||
|
||||
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'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 LocationTypeChange(string currentType, XElement element)
|
||||
/// <summary>
|
||||
/// The location can't change it's type for this many turns after this location type changes occurs
|
||||
/// </summary>
|
||||
public readonly int CooldownAfterChange;
|
||||
|
||||
public readonly Point RequiredDurationRange;
|
||||
|
||||
public LocationTypeChange(string currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
|
||||
{
|
||||
ChangeToType = element.GetAttributeString("type", "");
|
||||
Probability = element.GetAttributeFloat("probability", 1.0f);
|
||||
RequiredDuration = element.GetAttributeInt("requiredduration", 0);
|
||||
CurrentType = currentType;
|
||||
ChangeToType = element.GetAttributeString("type", element.GetAttributeString("to", ""));
|
||||
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", 0);
|
||||
RequireDiscovered = element.GetAttributeBool("requirediscovered", false);
|
||||
|
||||
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);
|
||||
|
||||
Probability = element.GetAttributeFloat("probability", defaultProbability);
|
||||
|
||||
CooldownAfterChange = Math.Max(element.GetAttributeInt("cooldownafterchange", 0), 0);
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredlocations") != null)
|
||||
{
|
||||
Requirements.Add(new Requirement(element, this));
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredduration") != null)
|
||||
{
|
||||
RequiredDurationRange = new Point(element.GetAttributeInt("requiredduration", 0));
|
||||
}
|
||||
|
||||
string messageTag = element.GetAttributeString("messagetag", "LocationChange." + currentType + ".ChangeTo." + ChangeToType);
|
||||
|
||||
Messages = TextManager.GetAll(messageTag);
|
||||
if (Messages == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
|
||||
}
|
||||
Messages = new List<string>();
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("requirement", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Requirements.Add(new Requirement(subElement, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float DetermineProbability(Location location)
|
||||
{
|
||||
if (RequireDiscovered && !location.Discovered) { return 0.0f; }
|
||||
if (location.IsCriticallyRadiated()) { return 0.0f; }
|
||||
if (location.LocationTypeChangeCooldown > 0) { return 0.0f; }
|
||||
if (location.IsGateBetweenBiomes) { return 0.0f; }
|
||||
|
||||
if (DisallowedAdjacentLocations.Any() &&
|
||||
AnyWithinDistance(location, DisallowedProximity, (otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float probability = Probability;
|
||||
foreach (Requirement requirement in Requirements)
|
||||
{
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximity))
|
||||
{
|
||||
if (requirement.Function == Requirement.FunctionType.Add)
|
||||
{
|
||||
probability += requirement.Probability;
|
||||
}
|
||||
else
|
||||
{
|
||||
probability *= requirement.Probability;
|
||||
}
|
||||
}
|
||||
|
||||
if (location.ProximityTimer.ContainsKey(requirement))
|
||||
{
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximityForProbabilityIncrease))
|
||||
{
|
||||
if (requirement.Function == Requirement.FunctionType.Add)
|
||||
{
|
||||
probability += requirement.ProximityProbabilityIncrease * location.ProximityTimer[requirement];
|
||||
}
|
||||
else
|
||||
{
|
||||
probability *= requirement.ProximityProbabilityIncrease * location.ProximityTimer[requirement];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return probability;
|
||||
}
|
||||
|
||||
private bool AnyWithinDistance(Location location, int maxDistance, Func<Location, bool> predicate, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && predicate(location)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinDistance(otherLocation, maxDistance, predicate, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,8 +16,8 @@ namespace Barotrauma
|
||||
|
||||
private Location furthestDiscoveredLocation;
|
||||
|
||||
private int Width => generationParams.Width;
|
||||
private int Height => generationParams.Height;
|
||||
public int Width => generationParams.Width;
|
||||
public int Height => generationParams.Height;
|
||||
|
||||
public Action<Location, LocationConnection> OnLocationSelected;
|
||||
/// <summary>
|
||||
@@ -56,11 +57,14 @@ namespace Barotrauma
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
public Map()
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
Locations = new List<Location>();
|
||||
Connections = new List<LocationConnection>();
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -69,6 +73,7 @@ namespace Barotrauma
|
||||
private Map(CampaignMode campaign, XElement element) : this()
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -80,11 +85,17 @@ namespace Barotrauma
|
||||
Locations.Add(null);
|
||||
}
|
||||
Locations[i] = new Location(subElement);
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -92,6 +103,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),
|
||||
@@ -181,8 +193,8 @@ namespace Barotrauma
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
CurrentLocation.Discovered = true;
|
||||
CurrentLocation.CreateStore();
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
@@ -243,21 +255,21 @@ 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 };
|
||||
|
||||
int positionIndex = Rand.Int(1, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = points[positionIndex];
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
|
||||
int zone = MathHelper.Clamp((int)Math.Floor(position.X / zoneWidth) + 1, 1, generationParams.DifficultyZones);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, Locations);
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; }
|
||||
int zone = GetZoneIndex(position.X);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, existingLocations: Locations);
|
||||
Locations.Add(newLocations[i]);
|
||||
}
|
||||
|
||||
var newConnection = new LocationConnection(newLocations[0], newLocations[1]);
|
||||
Connections.Add(newConnection);
|
||||
Connections.Add(newConnection);
|
||||
}
|
||||
|
||||
//remove connections that are too short
|
||||
@@ -316,7 +328,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);
|
||||
@@ -337,6 +357,56 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
LocationConnection[] connectionsBetweenZones = new LocationConnection[generationParams.DifficultyZones];
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X);
|
||||
int zone2 = GetZoneIndex(connection.Locations[1].MapPosition.X);
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 > zone2)
|
||||
{
|
||||
int temp = zone2;
|
||||
zone2 = zone1;
|
||||
zone1 = temp;
|
||||
}
|
||||
|
||||
if (connectionsBetweenZones[zone1] == null)
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].CenterPos.Y - Height / 2))
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
int zone1 = GetZoneIndex(Connections[i].Locations[0].MapPosition.X);
|
||||
int zone2 = GetZoneIndex(Connections[i].Locations[1].MapPosition.X);
|
||||
if (zone1 == zone2) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones.Contains(Connections[i]))
|
||||
{
|
||||
Connections.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
var leftMostLocation =
|
||||
Connections[i].Locations[0].MapPosition.X < Connections[i].Locations[1].MapPosition.X ?
|
||||
Connections[i].Locations[0] :
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost)
|
||||
{
|
||||
leftMostLocation.ChangeType(LocationType.List.First(lt => lt.HasOutpost));
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
for (int i = location.Connections.Count - 1; i >= 0; i--)
|
||||
@@ -359,6 +429,14 @@ namespace Barotrauma
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location);
|
||||
if (location.Type.MissionIdentifiers.Any())
|
||||
{
|
||||
location.UnlockMissionByIdentifier(location.Type.MissionIdentifiers.GetRandom());
|
||||
}
|
||||
if (location.Type.MissionTags.Any())
|
||||
{
|
||||
location.UnlockMissionByTag(location.Type.MissionTags.GetRandom());
|
||||
}
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
@@ -368,6 +446,12 @@ namespace Barotrauma
|
||||
|
||||
partial void GenerateLocationConnectionVisuals();
|
||||
|
||||
private int GetZoneIndex(float xPos)
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
return MathHelper.Clamp((int)Math.Floor(xPos / zoneWidth) + 1, 1, generationParams.DifficultyZones);
|
||||
}
|
||||
|
||||
public Biome GetBiome(Vector2 mapPos)
|
||||
{
|
||||
return GetBiome(mapPos.X);
|
||||
@@ -544,6 +628,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)
|
||||
@@ -618,7 +708,6 @@ namespace Barotrauma
|
||||
|
||||
public void SelectMission(int missionIndex)
|
||||
{
|
||||
if (SelectedConnection == null) { return; }
|
||||
if (CurrentLocation == null)
|
||||
{
|
||||
string errorMsg = "Failed to select a mission (current location not set).";
|
||||
@@ -628,11 +717,18 @@ namespace Barotrauma
|
||||
}
|
||||
CurrentLocation.SelectedMissionIndex = missionIndex;
|
||||
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
if (CurrentLocation.SelectedMission == null) { return; }
|
||||
|
||||
if (CurrentLocation.SelectedMission.Locations[0] != CurrentLocation ||
|
||||
CurrentLocation.SelectedMission.Locations[1] != CurrentLocation)
|
||||
{
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
|
||||
@@ -668,89 +764,116 @@ namespace Barotrauma
|
||||
{
|
||||
ProgressWorld();
|
||||
}
|
||||
|
||||
Radiation.OnStep(steps);
|
||||
}
|
||||
|
||||
private void ProgressWorld()
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Discovered) { continue; }
|
||||
|
||||
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)
|
||||
{
|
||||
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];
|
||||
//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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//select a random type change
|
||||
if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(i => cct[i].Probability + (cct[i].ProximityProbabilityIncrease * (float)location.ProximityTime[i])))
|
||||
private void ProgressLocationTypeChanges(Location location)
|
||||
{
|
||||
location.TimeSinceLastTypeChange++;
|
||||
location.LocationTypeChangeCooldown--;
|
||||
|
||||
if (location.PendingLocationTypeChange != null)
|
||||
{
|
||||
if (location.PendingLocationTypeChange.Value.typeChange.DetermineProbability(location) <= 0.0f)
|
||||
{
|
||||
var selectedTypeChangeIndex =
|
||||
ToolBox.SelectWeightedRandom(
|
||||
readyTypeChanges,
|
||||
readyTypeChanges.Select(i => cct[i].Probability + (cct[i].ProximityProbabilityIncrease * (float)location.ProximityTime[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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
(location.PendingLocationTypeChange.Value.typeChange,
|
||||
location.PendingLocationTypeChange.Value.delay - 1,
|
||||
location.PendingLocationTypeChange.Value.parentMission);
|
||||
if (location.PendingLocationTypeChange.Value.delay <= 0)
|
||||
{
|
||||
ChangeLocationType(location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
}
|
||||
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 =
|
||||
(selectedTypeChange,
|
||||
Rand.Range(selectedTypeChange.RequiredDurationRange.X, selectedTypeChange.RequiredDurationRange.Y),
|
||||
null);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLocationType(location, selectedTypeChange);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
|
||||
{
|
||||
foreach (var requirement in typeChange.Requirements)
|
||||
{
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximityForProbabilityIncrease))
|
||||
{
|
||||
if (!location.ProximityTimer.ContainsKey(requirement)) { location.ProximityTimer[requirement] = 0; }
|
||||
location.ProximityTimer[requirement] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ProximityTimer.Remove(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,7 +922,22 @@ 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);
|
||||
foreach (var requirement in change.Requirements)
|
||||
{
|
||||
location.ProximityTimer.Remove(requirement);
|
||||
}
|
||||
location.TimeSinceLastTypeChange = 0;
|
||||
location.LocationTypeChangeCooldown = change.CooldownAfterChange;
|
||||
location.PendingLocationTypeChange = null;
|
||||
}
|
||||
|
||||
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change);
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
/// <summary>
|
||||
@@ -835,8 +973,15 @@ 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++)
|
||||
{
|
||||
for (int j = 0; j < location.Type.CanChangeTo[i].Requirements.Count; j++)
|
||||
{
|
||||
location.ProximityTimer.Add(location.Type.CanChangeTo[i].Requirements[j], subElement.GetAttributeInt("changetimer" + i + "-" + j, 0));
|
||||
}
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
location.Discovered = subElement.GetAttributeBool("discovered", false);
|
||||
if (location.Discovered)
|
||||
{
|
||||
@@ -849,7 +994,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string locationType = subElement.GetAttributeString("type", "");
|
||||
string prevLocationName = location.Name;
|
||||
LocationType prevLocationType = location.Type;
|
||||
@@ -860,15 +1004,22 @@ 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);
|
||||
Connections[connectionIndex].Passed = subElement.GetAttributeBool("passed", false);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,6 +1086,8 @@ namespace Barotrauma
|
||||
mapElement.Add(connectionElement);
|
||||
}
|
||||
|
||||
mapElement.Add(Radiation.Save());
|
||||
|
||||
element.Add(mapElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace Barotrauma
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public RadiationParams RadiationParams;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
|
||||
@@ -238,6 +240,9 @@ namespace Barotrauma
|
||||
TypeChangeIcon = new Sprite(subElement);
|
||||
break;
|
||||
#endif
|
||||
case "radiationparams":
|
||||
RadiationParams = new RadiationParams(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal partial class Radiation : ISerializableEntity
|
||||
{
|
||||
public string Name => nameof(Radiation);
|
||||
|
||||
[Serialize(defaultValue: 0f, isSaveable: true)]
|
||||
public float Amount { get; set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
public readonly Map Map;
|
||||
public readonly RadiationParams Params;
|
||||
|
||||
private float radiationTimer;
|
||||
|
||||
private float increasedAmount;
|
||||
private float lastIncrease;
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
public Radiation(Map map, RadiationParams radiationParams, XElement? element = null)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
Map = map;
|
||||
Params = radiationParams;
|
||||
radiationTimer = Params.RadiationDamageDelay;
|
||||
if (element == null)
|
||||
{
|
||||
Amount = Params.StartingRadiation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the progress of the radiation.
|
||||
/// </summary>
|
||||
/// <param name="steps"></param>
|
||||
public void OnStep(float steps = 1)
|
||||
{
|
||||
if (!Enabled) { return; }
|
||||
if (steps <= 0) { return; }
|
||||
|
||||
IncreaseRadiation(Params.RadiationStep * steps);
|
||||
|
||||
int amountOfOutposts = Map.Locations.Count(location => location.Type.HasOutpost && !location.IsCriticallyRadiated());
|
||||
|
||||
foreach (Location location in Map.Locations.Where(Contains))
|
||||
{
|
||||
if (amountOfOutposts <= Params.MinimumOutpostAmount) { break; }
|
||||
|
||||
if (Map.CurrentLocation is { } currLocation)
|
||||
{
|
||||
// Don't advance on nearby locations to avoid buggy behavior
|
||||
if (currLocation == location || currLocation.Connections.Any(lc => lc.OtherLocation(currLocation) == location)) { continue; }
|
||||
}
|
||||
|
||||
bool wasCritical = location.IsCriticallyRadiated();
|
||||
|
||||
location.TurnsInRadiation++;
|
||||
|
||||
if (location.Type.HasOutpost && !wasCritical && location.IsCriticallyRadiated())
|
||||
{
|
||||
amountOfOutposts--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void IncreaseRadiation(float amount)
|
||||
{
|
||||
Amount += amount;
|
||||
increasedAmount = lastIncrease = amount;
|
||||
}
|
||||
|
||||
public void UpdateRadiation(float deltaTime)
|
||||
{
|
||||
if (!(GameMain.GameSession?.IsCurrentLocationRadiated() ?? false)) { return; }
|
||||
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
|
||||
if (radiationTimer > 0)
|
||||
{
|
||||
radiationTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
radiationTimer = Params.RadiationDamageDelay;
|
||||
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead || character.Removed || !(character.CharacterHealth is { } health)) { continue; }
|
||||
|
||||
if (IsEntityRadiated(character))
|
||||
{
|
||||
health.ApplyAffliction(null, new Affliction(AfflictionPrefab.RadiationSickness, Params.RadiationDamageAmount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(Location location)
|
||||
{
|
||||
return Contains(location.MapPosition);
|
||||
}
|
||||
|
||||
public bool Contains(Vector2 pos)
|
||||
{
|
||||
return pos.X < Amount;
|
||||
}
|
||||
|
||||
public bool IsEntityRadiated(Entity entity)
|
||||
{
|
||||
if (!Enabled) { return false; }
|
||||
if (Level.Loaded is { Type: LevelData.LevelType.LocationConnection, StartLocation: { } startLocation, EndLocation: { } endLocation } level)
|
||||
{
|
||||
if (Contains(startLocation) && Contains(endLocation)) { return true; }
|
||||
|
||||
float distance = MathHelper.Clamp((entity.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X), 0.0f, 1.0f);
|
||||
var (startX, startY) = startLocation.MapPosition;
|
||||
var (endX, endY) = endLocation.MapPosition;
|
||||
Vector2 mapPos = new Vector2(startX + (endX - startX), startY + (endY - startY)) * distance;
|
||||
|
||||
return Contains(mapPos);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement(nameof(Radiation));
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class RadiationParams: ISerializableEntity
|
||||
{
|
||||
public string Name => nameof(RadiationParams);
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
[Serialize(defaultValue: -100f, isSaveable: false, "How much radiation the world starts with.")]
|
||||
public float StartingRadiation { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 100f, isSaveable: false, "How much radiation is added on each step.")]
|
||||
public float RadiationStep { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 10, isSaveable: false, "How many turns in radiation does it take for an outpost to be removed from the map.")]
|
||||
public int CriticalRadiationThreshold { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 3, isSaveable: false, "Minimum amount of outposts in the level that cannot be removed due to radiation.")]
|
||||
public int MinimumOutpostAmount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 3f, isSaveable: false, "How fast the radiation increase animation goes.")]
|
||||
public float AnimationSpeed { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 10f, isSaveable: false, "How long it takes to apply more radiation damage while in a radiated zone.")]
|
||||
public float RadiationDamageDelay { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 1f, isSaveable: false, "How much is the radiation affliction increased by while in a radiated zone.")]
|
||||
public float RadiationDamageAmount { get; set; }
|
||||
|
||||
public RadiationParams(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) });
|
||||
|
||||
@@ -3,14 +3,23 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
enum MapEntityCategory
|
||||
{
|
||||
Structure = 1, Decorative = 2, Machine = 4, Equipment = 8, Electrical = 16, Material = 32, Misc = 64, Alien = 128, Wrecked = 256, Thalamus = 512, ItemAssembly = 1024, Legacy = 2048
|
||||
Structure = 1,
|
||||
Decorative = 2,
|
||||
Machine = 4,
|
||||
Equipment = 8,
|
||||
Electrical = 16,
|
||||
Material = 32,
|
||||
Misc = 64,
|
||||
Alien = 128,
|
||||
Wrecked = 256,
|
||||
ItemAssembly = 512,
|
||||
Legacy = 1024
|
||||
}
|
||||
|
||||
abstract partial class MapEntityPrefab : IPrefab, IDisposable
|
||||
@@ -54,6 +63,7 @@ namespace Barotrauma
|
||||
//is it possible to stretch the entity horizontally/vertically
|
||||
[Serialize(false, false)]
|
||||
public bool ResizeHorizontal { get; protected set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool ResizeVertical { get; protected set; }
|
||||
|
||||
@@ -118,6 +128,9 @@ namespace Barotrauma
|
||||
[Serialize(false, false)]
|
||||
public bool HideInMenus { get; set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Subcategory { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool Linkable
|
||||
{
|
||||
@@ -215,6 +228,11 @@ namespace Barotrauma
|
||||
return string.IsNullOrWhiteSpace(AllowedUpgrades) ? new string[0] : AllowedUpgrades.Split(",");
|
||||
}
|
||||
|
||||
public bool HasSubCategory(string subcategory)
|
||||
{
|
||||
return subcategory?.Equals(this.Subcategory, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
|
||||
protected virtual void CreateInstance(Rectangle rect)
|
||||
{
|
||||
if (constructor == null) return;
|
||||
|
||||
@@ -39,6 +39,34 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
public bool AlwaysDestructible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
public bool AlwaysRewireable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
public bool AllowStealing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
public bool SpawnCrewInsideOutpost
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, int> moduleCounts = new Dictionary<string, int>();
|
||||
|
||||
public IEnumerable<KeyValuePair<string, int>> ModuleCounts
|
||||
|
||||
@@ -169,6 +169,7 @@ namespace Barotrauma
|
||||
Type = SubmarineType.Outpost
|
||||
};
|
||||
generationFailed = false;
|
||||
outpostInfo.OutpostGenerationParams = generationParams;
|
||||
sub = new Submarine(outpostInfo, loadEntities: loadEntities);
|
||||
sub.Info.OutpostGenerationParams = generationParams;
|
||||
if (!generationFailed)
|
||||
@@ -669,10 +670,15 @@ namespace Barotrauma
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m =>
|
||||
!m.OutpostModuleInfo.AllowedLocationTypes.Any() ||
|
||||
m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
@@ -705,10 +711,15 @@ namespace Barotrauma
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m =>
|
||||
!m.OutpostModuleInfo.AllowedLocationTypes.Any() ||
|
||||
m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
@@ -1381,15 +1392,14 @@ namespace Barotrauma
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(characterInfo.Name));
|
||||
|
||||
ISpatialEntity gotoTarget = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Human, humanPrefab.GetModuleFlags(), humanPrefab.GetSpawnPointTags());
|
||||
|
||||
if (gotoTarget == null)
|
||||
{
|
||||
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,29 +1414,12 @@ 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 = !outpost.Info.OutpostGenerationParams.AllowStealing;
|
||||
}
|
||||
npc.GiveIdCardTags(gotoTarget as WayPoint);
|
||||
if (npc.AIController is HumanAIController humanAI)
|
||||
{
|
||||
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None)
|
||||
{
|
||||
idleObjective.Behavior = AIObjectiveIdle.BehaviorType.StayInHull;
|
||||
idleObjective.TargetHull = AIObjectiveGoTo.GetTargetHull(gotoTarget);
|
||||
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(npc, humanPrefab.CampaignInteractionType);
|
||||
}
|
||||
else
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.Behavior;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
humanPrefab.InitializeCharacter(npc, gotoTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -144,10 +142,16 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
return Prefab.Body && !IsPlatform;
|
||||
return Prefab.Body && !IsPlatform;// && HasDamage;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasDamage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public StructurePrefab Prefab => prefab as StructurePrefab;
|
||||
|
||||
public HashSet<string> Tags
|
||||
@@ -356,7 +360,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine, ushort id = Entity.NullEntityID, XElement element = null)
|
||||
: base(sp, submarine, id)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
|
||||
@@ -395,7 +399,6 @@ namespace Barotrauma
|
||||
|
||||
StairDirection = Prefab.StairDirection;
|
||||
NoAITarget = Prefab.NoAITarget;
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
|
||||
InitProjSpecific();
|
||||
|
||||
@@ -421,6 +424,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
|
||||
|
||||
// Only add ai targets automatically to submarine/outpost walls
|
||||
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget)
|
||||
{
|
||||
@@ -632,7 +637,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 bodyPos = WorldPosition + BodyOffset;
|
||||
|
||||
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, MathHelper.ToDegrees(BodyRotation));
|
||||
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, BodyRotation);
|
||||
|
||||
return
|
||||
Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
|
||||
@@ -836,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)
|
||||
{
|
||||
@@ -850,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);
|
||||
|
||||
@@ -944,14 +949,14 @@ namespace Barotrauma
|
||||
return new AttackResult(damageAmount, null);
|
||||
}
|
||||
|
||||
private void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true)
|
||||
public void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode || Indestructible) { return; }
|
||||
if (!Prefab.Body) { return; }
|
||||
if (!MathUtils.IsValid(damage)) { return; }
|
||||
|
||||
damage = MathHelper.Clamp(damage, 0.0f, MaxHealth - Prefab.MinHealth);
|
||||
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && createNetworkEvent && damage != Sections[sectionIndex].damage)
|
||||
{
|
||||
@@ -1065,15 +1070,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float gapOpen = (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
}
|
||||
|
||||
float damageDiff = damage - Sections[sectionIndex].damage;
|
||||
bool hadHole = SectionBodyDisabled(sectionIndex);
|
||||
Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, MaxHealth);
|
||||
|
||||
HasDamage = Sections.Any(s => s.damage > 0.0f);
|
||||
|
||||
if (attacker != null && damageDiff != 0.0f)
|
||||
{
|
||||
HumanAIController.StructureDamaged(this, damageDiff, attacker);
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -1081,14 +1088,14 @@ namespace Barotrauma
|
||||
{
|
||||
attacker.Info.IncreaseSkillLevel("mechanical",
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f),
|
||||
SectionPosition(sectionIndex, true));
|
||||
SectionPosition(sectionIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hasHole = SectionBodyDisabled(sectionIndex);
|
||||
|
||||
if (hadHole == hasHole) return;
|
||||
if (hadHole == hasHole) { return; }
|
||||
|
||||
UpdateSections();
|
||||
}
|
||||
@@ -1283,18 +1290,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
||||
Structure s = new Structure(rect, prefab, submarine, idRemap.GetOffsetId(element))
|
||||
Structure s = new Structure(rect, prefab, submarine, idRemap.GetOffsetId(element), element)
|
||||
{
|
||||
Submarine = submarine,
|
||||
};
|
||||
|
||||
SerializableProperty.DeserializeProperties(s, element);
|
||||
|
||||
if (submarine?.Info.GameVersion != null)
|
||||
{
|
||||
SerializableProperty.UpgradeGameVersion(s, s.Prefab.ConfigElement, submarine.Info.GameVersion);
|
||||
}
|
||||
|
||||
bool hasDamage = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1311,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":
|
||||
@@ -1333,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)
|
||||
@@ -1347,6 +1355,11 @@ namespace Barotrauma
|
||||
s.NoAITarget = prefab.NoAITarget;
|
||||
}
|
||||
|
||||
if (hasDamage)
|
||||
{
|
||||
s.UpdateSections();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -360,7 +360,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(element.GetAttributeString("category", "Structure"), true, out MapEntityCategory category))
|
||||
string categoryStr = element.GetAttributeString("category", "Structure");
|
||||
if (!Enum.TryParse(categoryStr, true, out MapEntityCategory category))
|
||||
{
|
||||
category = MapEntityCategory.Structure;
|
||||
}
|
||||
@@ -419,6 +420,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sp.Category = MapEntityCategory.Wrecked;
|
||||
sp.Subcategory = "Thalamus";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sp.identifier))
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,6 @@ namespace Barotrauma
|
||||
{
|
||||
if ((!anyHasTag || item.HasTag("ballast")) && item.GetComponent<Pump>() is { } pump)
|
||||
{
|
||||
if (pump.Infected) { continue; }
|
||||
pumps.Add(pump);
|
||||
}
|
||||
}
|
||||
@@ -312,11 +311,13 @@ namespace Barotrauma
|
||||
if (!pumps.Any()) { return; }
|
||||
|
||||
Pump randomPump = pumps.GetRandom(Rand.RandSync.Unsynced);
|
||||
randomPump.Infected = true;
|
||||
randomPump.InfectIdentifier = identifier;
|
||||
if (randomPump.IsOn && randomPump.HasPower && randomPump.FlowPercentage > 0 && randomPump.Item.Condition > 0.0f)
|
||||
{
|
||||
randomPump.InfectBallast(identifier);
|
||||
#if SERVER
|
||||
randomPump.Item.CreateServerEvent(randomPump);
|
||||
randomPump.Item.CreateServerEvent(randomPump);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeWreck()
|
||||
@@ -324,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);
|
||||
@@ -553,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;
|
||||
}
|
||||
|
||||
@@ -586,6 +587,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (e is Item item)
|
||||
{
|
||||
if (item.GetComponent<Turret>() != null) { return false; }
|
||||
if (item.body != null && !item.body.Enabled) { return true; }
|
||||
}
|
||||
return false;
|
||||
@@ -598,6 +600,17 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 1; i < entities.Count; i++)
|
||||
{
|
||||
if (entities[i] is Item item)
|
||||
{
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
minX = Math.Min(minX, entities[i].Rect.X + turret.TransformedBarrelPos.X * 2f);
|
||||
minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height - turret.TransformedBarrelPos.Y * 2f);
|
||||
maxX = Math.Max(maxX, entities[i].Rect.Right + turret.TransformedBarrelPos.X * 2f);
|
||||
maxY = Math.Max(maxY, entities[i].Rect.Y - turret.TransformedBarrelPos.Y * 2f);
|
||||
}
|
||||
}
|
||||
minX = Math.Min(minX, entities[i].Rect.X);
|
||||
minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height);
|
||||
maxX = Math.Max(maxX, entities[i].Rect.Right);
|
||||
@@ -1136,7 +1149,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (ConnectedDockingPorts.TryGetValue(dockedSub, out DockingPort port))
|
||||
{
|
||||
port.Undock();
|
||||
port.Undock(applyEffects: false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1277,11 +1290,7 @@ namespace Barotrauma
|
||||
HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f);
|
||||
}
|
||||
|
||||
IdOffset = 0;
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
IdOffset = Math.Max(IdOffset, me.ID);
|
||||
}
|
||||
IdOffset = IdRemap.DetermineNewOffset();
|
||||
|
||||
List<MapEntity> newEntities = new List<MapEntity>();
|
||||
if (loadEntities == null)
|
||||
@@ -1335,16 +1344,20 @@ namespace Barotrauma
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = Character.TeamType.FriendlyNPC;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
bool indestructible =
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
item.SpawnedInOutpost = true;
|
||||
if (item.GetComponent<Repairable>() != null &&
|
||||
(GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.DestructibleOutposts))
|
||||
item.SpawnedInOutpost = !info.OutpostGenerationParams.AllowStealing;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
}
|
||||
@@ -1353,7 +1366,10 @@ namespace Barotrauma
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
{
|
||||
//prevent rewiring
|
||||
connectionPanel.Locked = true;
|
||||
if (!info.OutpostGenerationParams.AlwaysRewireable)
|
||||
{
|
||||
connectionPanel.Locked = true;
|
||||
}
|
||||
}
|
||||
else if (ic is Holdable holdable && holdable.Attached && item.GetComponent<LevelResource>() == null)
|
||||
{
|
||||
@@ -1366,9 +1382,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts)
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible)
|
||||
{
|
||||
structure.Indestructible = GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.DestructibleOutposts;
|
||||
structure.Indestructible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1498,7 +1514,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
|
||||
{
|
||||
@@ -1520,6 +1548,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);
|
||||
}
|
||||
@@ -1694,6 +1726,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
node.Waypoint.FindHull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1708,6 +1741,7 @@ namespace Barotrauma
|
||||
nodes.Clear();
|
||||
obstructedNodes.Remove(otherSub);
|
||||
}
|
||||
OutdoorNodes.ForEach(n => n.Waypoint.FindHull());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
@@ -446,21 +447,24 @@ namespace Barotrauma
|
||||
private void UpdateDepthDamage(float deltaTime)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession.GameMode is TestGameMode) { return; }
|
||||
if (GameMain.GameSession?.GameMode is TestGameMode) { return; }
|
||||
#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;
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f);
|
||||
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)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(pastCrushDepth * 0.001f, 50.0f));
|
||||
@@ -555,19 +559,29 @@ namespace Barotrauma
|
||||
{
|
||||
if (limb?.body?.FarseerBody == null || limb.character == null) { return; }
|
||||
|
||||
if (limb.Mass > MinImpactLimbMass)
|
||||
float impactMass = limb.Mass;
|
||||
var enemyAI = limb.character.AIController as EnemyAIController;
|
||||
float attackMultiplier = 1.0f;
|
||||
if (enemyAI?.ActiveAttack != null)
|
||||
{
|
||||
impactMass = Math.Max(Math.Max(limb.Mass, limb.character.AnimController.MainLimb.Mass), limb.character.AnimController.Collider.Mass);
|
||||
attackMultiplier = enemyAI.ActiveAttack.SubmarineImpactMultiplier;
|
||||
}
|
||||
|
||||
if (impactMass * attackMultiplier > MinImpactLimbMass)
|
||||
{
|
||||
Vector2 normal =
|
||||
Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ?
|
||||
Vector2.UnitY :
|
||||
Vector2.Normalize(Body.SimPosition - limb.SimPosition);
|
||||
|
||||
float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(limb.Mass / 100.0f, 1);
|
||||
float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(impactMass / 300.0f, 1);
|
||||
impact *= attackMultiplier;
|
||||
|
||||
ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false);
|
||||
ApplyImpact(impact, normal, collision.ImpactPos, applyDamage: false);
|
||||
foreach (Submarine dockedSub in submarine.DockedTo)
|
||||
{
|
||||
dockedSub.SubBody.ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false);
|
||||
dockedSub.SubBody.ApplyImpact(impact, normal, collision.ImpactPos, applyDamage: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +677,7 @@ namespace Barotrauma
|
||||
dockedSub.SubBody.ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
|
||||
}
|
||||
|
||||
if (cell != null && wallImpact > 0.0f)
|
||||
if (cell != null && cell.IsDestructible && wallImpact > 0.0f)
|
||||
{
|
||||
var hitWall = Level.Loaded?.ExtraWalls.Find(w => w.Cells.Contains(cell));
|
||||
if (hitWall != null && hitWall.WallDamageOnTouch > 0.0f)
|
||||
@@ -672,7 +686,7 @@ namespace Barotrauma
|
||||
ConvertUnits.ToDisplayUnits(impact.ImpactPos),
|
||||
500.0f,
|
||||
hitWall.WallDamageOnTouch,
|
||||
damageLevelWalls: false);
|
||||
levelWallDamage: 0.0f);
|
||||
#if CLIENT
|
||||
PlayDamageSounds(damagedStructures, impact.ImpactPos, wallImpact, "StructureSlash");
|
||||
#endif
|
||||
@@ -800,7 +814,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = impact * 10.0f;
|
||||
if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player))
|
||||
{
|
||||
float angularVelocity =
|
||||
@@ -814,25 +828,32 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != submarine) { continue; }
|
||||
|
||||
if (c.KnockbackCooldownTimer > 0.0f) { continue; }
|
||||
|
||||
c.KnockbackCooldownTimer = Character.KnockbackCooldown;
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f);
|
||||
}
|
||||
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
|
||||
|
||||
bool holdingOntoSomething = false;
|
||||
if (c.SelectedConstruction != null)
|
||||
{
|
||||
var controller = c.SelectedConstruction.GetComponent<Items.Components.Controller>();
|
||||
holdingOntoSomething = controller != null && controller.LimbPositions.Any();
|
||||
holdingOntoSomething =
|
||||
c.SelectedConstruction.GetComponent<Ladder>() != null ||
|
||||
(c.SelectedConstruction.GetComponent<Controller>()?.LimbPositions.Any() ?? false);
|
||||
}
|
||||
|
||||
//stun for up to 1 second if the impact equal or higher to the maximum impact
|
||||
if (impact >= MaxCollisionImpact && !holdingOntoSomething)
|
||||
if (!holdingOntoSomething)
|
||||
{
|
||||
c.SetStun(Math.Min(impulse.Length() * 0.2f, 1.0f));
|
||||
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
|
||||
//stun for up to 2 second if the impact equal or higher to the maximum impact
|
||||
if (impact >= MaxCollisionImpact)
|
||||
{
|
||||
c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(3.0f).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,11 +864,12 @@ namespace Barotrauma
|
||||
|
||||
item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f);
|
||||
}
|
||||
|
||||
|
||||
float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
|
||||
var damagedStructures = Explosion.RangedStructureDamage(
|
||||
ConvertUnits.ToDisplayUnits(impactPos),
|
||||
impact * 50.0f,
|
||||
applyDamage ? impact * ImpactDamageMultiplier : 0.0f);
|
||||
impact * 50.0f,
|
||||
dmg, dmg);
|
||||
|
||||
#if CLIENT
|
||||
PlayDamageSounds(damagedStructures, impactPos, impact, "StructureBlunt");
|
||||
|
||||
@@ -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;
|
||||
@@ -44,6 +44,8 @@ namespace Barotrauma
|
||||
|
||||
public Hull CurrentHull { get; private set; }
|
||||
|
||||
public Level.Tunnel Tunnel;
|
||||
|
||||
public SpawnType SpawnType
|
||||
{
|
||||
get { return spawnType; }
|
||||
@@ -97,6 +99,13 @@ namespace Barotrauma
|
||||
{
|
||||
SpawnType = SpawnType.Path;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { this }, false));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -177,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)
|
||||
@@ -236,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;
|
||||
@@ -258,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>();
|
||||
@@ -296,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]);
|
||||
}
|
||||
@@ -312,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);
|
||||
|
||||
@@ -339,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);
|
||||
}
|
||||
|
||||
@@ -372,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,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; }
|
||||
|
||||
@@ -471,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