Release 1.11.4.1 (Winter Update)
This commit is contained in:
@@ -404,7 +404,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, displayRange);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -465,12 +465,12 @@ namespace Barotrauma
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
private 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, float range)
|
||||
{
|
||||
if (attack.Range <= 0.0f) { return; }
|
||||
if (range <= 0.0f) { return; }
|
||||
|
||||
//long range for the broad distance check, because large characters may still be in range even if their collider isn't
|
||||
float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f);
|
||||
float broadRange = Math.Max(range * 10.0f, 10000.0f);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
@@ -518,7 +518,7 @@ namespace Barotrauma
|
||||
float limbRadius = limb.body.GetMaxExtent();
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
|
||||
|
||||
if (dist > attack.Range) { continue; }
|
||||
if (dist > range) { continue; }
|
||||
|
||||
float distFactor =
|
||||
DistanceFalloff ?
|
||||
|
||||
@@ -371,7 +371,7 @@ namespace Barotrauma
|
||||
if (!IsInDamageRange(c, DamageRange)) { continue; }
|
||||
|
||||
//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 + FlameHeight)
|
||||
if (hull.GetApproximateDistance(Position, c.Position, c.CurrentHull, maxDistance: 10000.0f, minimumGapOpenness: Structure.LargeGapOpenness) > size.X + DamageRange + FlameHeight)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1133,54 +1133,72 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// Used in <see cref="GetApproximateDistance"/>
|
||||
/// </summary>
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor = 0)
|
||||
private static readonly Dictionary<Hull, float> cachedDistances = [];
|
||||
/// <summary>
|
||||
/// Used in <see cref="GetApproximateDistance"/>
|
||||
/// </summary>
|
||||
private static readonly PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue = new PriorityQueue<(Hull hull, Vector2 pos), float>();
|
||||
|
||||
/// <summary>
|
||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||
/// Uses a Dijkstra's algorithm to find the shortest path.
|
||||
/// </summary>
|
||||
/// <param name="minimumGapOpenness">The gap's <see cref="Gap.Open">openness</see> must be larger than or equal to this to be considered valid for the path.</param>
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor = 0, float minimumGapOpenness = 0.5f)
|
||||
{
|
||||
return GetApproximateHullDistance(startPos, endPos, new HashSet<Hull>(), targetHull, 0.0f, maxDistance, distanceMultiplierPerClosedDoor);
|
||||
}
|
||||
cachedDistances.Clear();
|
||||
priorityQueue.Clear();
|
||||
|
||||
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 (this == target)
|
||||
cachedDistances[this] = 0f;
|
||||
priorityQueue.Enqueue((this, startPos), 0f);
|
||||
|
||||
while (priorityQueue.TryDequeue(out var current, out float currentDist))
|
||||
{
|
||||
return distance + Vector2.Distance(startPos, endPos);
|
||||
}
|
||||
Hull currentHull = current.hull;
|
||||
Vector2 currentPos = current.pos;
|
||||
|
||||
connectedHulls.Add(this);
|
||||
if (currentDist > maxDistance) { return float.MaxValue; }
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
float distanceMultiplier = 1;
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
// If we've reached the target, add the final segment from hull to endPos
|
||||
if (currentHull == targetHull)
|
||||
{
|
||||
//gap blocked if the door is closed, and we haven't made any predictions of it opening client-side
|
||||
if ((g.ConnectedDoor.IsClosed && !g.ConnectedDoor.PredictedState.HasValue) ||
|
||||
//OR we've predicted that the door is closed client-side
|
||||
(g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
return currentDist + Vector2.Distance(currentPos, endPos);
|
||||
}
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
float distanceMultiplier = 1;
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
{
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
//gap blocked if the door is closed, and we haven't made any predictions of it opening client-side
|
||||
if ((g.ConnectedDoor.IsClosed && !g.ConnectedDoor.PredictedState.HasValue) ||
|
||||
//OR we've predicted that the door is closed client-side
|
||||
(g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
{
|
||||
if (distanceMultiplierFromDoors <= 0) { continue; }
|
||||
distanceMultiplier *= distanceMultiplierFromDoors;
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
{
|
||||
if (distanceMultiplierPerClosedDoor <= 0) { continue; }
|
||||
distanceMultiplier *= distanceMultiplierPerClosedDoor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g.Open <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
|
||||
{
|
||||
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
|
||||
else if (g.Open < minimumGapOpenness)
|
||||
{
|
||||
float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position) * distanceMultiplier, maxDistance);
|
||||
if (dist < float.MaxValue)
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
|
||||
{
|
||||
if (g.linkedTo[i] is Hull nextHull && nextHull != currentHull)
|
||||
{
|
||||
return dist;
|
||||
float newDist = currentDist + Vector2.Distance(currentPos, g.Position) * distanceMultiplier;
|
||||
if (!cachedDistances.TryGetValue(nextHull, out float oldDist) || newDist < oldDist)
|
||||
{
|
||||
cachedDistances[nextHull] = newDist;
|
||||
priorityQueue.Enqueue((nextHull, g.Position), newDist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1274,7 +1292,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively find all the hulls linked to the specified hull.
|
||||
/// Recursively find all the hulls linked to the specified hull, including the hull itself.
|
||||
/// </summary>
|
||||
public void GetLinkedHulls(List<Hull> linkedHulls, bool includeHiddenHulls = false)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LevelGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
internal partial class LevelGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
{
|
||||
public readonly static PrefabCollection<LevelGenerationParams> LevelParams = new PrefabCollection<LevelGenerationParams>();
|
||||
|
||||
|
||||
@@ -378,7 +378,14 @@ namespace Barotrauma
|
||||
if (characters.Any())
|
||||
{
|
||||
price *= 1f + characters.Max(static c => c.GetStatValue(StatTypes.StoreSellMultiplier, includeSaved: false));
|
||||
price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValueWithAll(StatTypes.StoreSellMultiplier, tag)));
|
||||
price *= 1f + characters.Max(c => GetMultiplierForItem(c, item));
|
||||
|
||||
float GetMultiplierForItem(Character character, ItemPrefab item)
|
||||
{
|
||||
return
|
||||
item.Tags.Sum(tag => character.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, tag)) +
|
||||
character.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, Tags.StatIdentifierTargetAll);
|
||||
}
|
||||
}
|
||||
|
||||
// Price should never go below 1 mk
|
||||
@@ -588,7 +595,7 @@ namespace Barotrauma
|
||||
public Location(Vector2 mapPosition, int? zone, Identifier? biomeId, Random rand, bool requireOutpost = false, LocationType forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
Type = OriginalType = forceLocationType ?? LocationType.Random(rand, zone, biomeId, requireOutpost);
|
||||
CreateRandomName(Type, rand, existingLocations);
|
||||
AssignRandomName(Type, rand, existingLocations);
|
||||
MapPosition = mapPosition;
|
||||
PortraitId = ToolBox.StringToInt(nameIdentifier.Value);
|
||||
Connections = new List<LocationConnection>();
|
||||
@@ -1210,7 +1217,7 @@ namespace Barotrauma
|
||||
HireManager.AvailableCharacters = hireableCharacters.ToList();
|
||||
}
|
||||
|
||||
private void CreateRandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
public void AssignRandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (!type.ForceLocationName.IsEmpty)
|
||||
{
|
||||
|
||||
@@ -735,7 +735,7 @@ namespace Barotrauma
|
||||
Location startLocation = Locations.MinBy(l => l.MapPosition.X);
|
||||
if (LocationType.Prefabs.TryGet("outpost", out LocationType startLocationType))
|
||||
{
|
||||
startLocation.ChangeType(campaign, startLocationType, createStores: false);
|
||||
mapLocationTypeGenerator.ChangeLocationTypeAndName(campaign, startLocation, startLocationType);
|
||||
mapLocationTypeGenerator.AddToFilled(startLocation);
|
||||
}
|
||||
|
||||
|
||||
@@ -155,13 +155,17 @@ namespace Barotrauma
|
||||
return filledLocations.Contains(location);
|
||||
}
|
||||
|
||||
public static void ChangeLocationTypeAndName(CampaignMode campaign, Location location, LocationType suitableLocationType)
|
||||
public void ChangeLocationTypeAndName(CampaignMode campaign, Location location, LocationType suitableLocationType)
|
||||
{
|
||||
location.ChangeType(campaign, suitableLocationType, createStores: false, unlockInitialMissions: false);
|
||||
if (!suitableLocationType.ForceLocationName.IsEmpty)
|
||||
{
|
||||
location.ForceName(suitableLocationType.ForceLocationName);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.AssignRandomName(location.Type, Rand.GetRNG(Rand.RandSync.ServerAndClient), existingLocations: map.Locations);
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignForcedBiomeGateTypes(IEnumerable<Location> gateLocations)
|
||||
|
||||
@@ -792,6 +792,7 @@ namespace Barotrauma
|
||||
ItemPrefab itemPrefab = ItemPrefab.Find(name, identifier);
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find a structure with the identifier {identifier}, but there's a matching item with the identifier. Converting to an item.");
|
||||
t = typeof(Item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ namespace Barotrauma
|
||||
const float LeakThreshold = 0.1f;
|
||||
const float BigGapThreshold = 0.7f;
|
||||
|
||||
/// <summary>
|
||||
/// How <see cref="Gap.open">open</see> the gap on a partially broken wall section is at most (when it's below <see cref="BigGapThreshold"/>, after which it lerps up to <see cref="LargeGapOpenness"/>).
|
||||
/// </summary>
|
||||
public const float SmallGapOpenness = 0.35f;
|
||||
|
||||
/// <summary>
|
||||
/// How <see cref="Gap.open">open</see> the gap on a fully broken wall section is.
|
||||
/// </summary>
|
||||
public const float LargeGapOpenness = 0.75f;
|
||||
|
||||
public override ContentPackage ContentPackage => Prefab?.ContentPackage;
|
||||
|
||||
#if CLIENT
|
||||
@@ -64,6 +74,9 @@ namespace Barotrauma
|
||||
|
||||
private static Explosion explosionOnBroken;
|
||||
|
||||
public delegate void OnHealthChangedHandler(Character attacker, float damage);
|
||||
public OnHealthChangedHandler OnHealthChanged;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody)]
|
||||
public bool Indestructible
|
||||
{
|
||||
@@ -1332,11 +1345,11 @@ namespace Barotrauma
|
||||
float gapOpen = 0;
|
||||
if (damageRatio > BigGapThreshold)
|
||||
{
|
||||
gapOpen = MathHelper.Lerp(0.35f, 0.75f, MathUtils.InverseLerp(BigGapThreshold, 1.0f, damageRatio));
|
||||
gapOpen = MathHelper.Lerp(SmallGapOpenness, LargeGapOpenness, MathUtils.InverseLerp(BigGapThreshold, 1.0f, damageRatio));
|
||||
}
|
||||
else if (damageRatio > LeakThreshold)
|
||||
{
|
||||
gapOpen = MathHelper.Lerp(0f, 0.35f, MathUtils.InverseLerp(LeakThreshold, BigGapThreshold, damageRatio));
|
||||
gapOpen = MathHelper.Lerp(0f, SmallGapOpenness, MathUtils.InverseLerp(LeakThreshold, BigGapThreshold, damageRatio));
|
||||
}
|
||||
gap.Open = gapOpen;
|
||||
|
||||
@@ -1355,16 +1368,20 @@ namespace Barotrauma
|
||||
Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, MaxHealth);
|
||||
HasDamage = Sections.Any(s => s.damage > 0.0f);
|
||||
|
||||
if (attacker != null && damageDiff != 0.0f)
|
||||
if (damageDiff != 0.0f)
|
||||
{
|
||||
HumanAIController.StructureDamaged(this, damageDiff, attacker);
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
OnHealthChanged?.Invoke(attacker, damageDiff);
|
||||
if (attacker != null)
|
||||
{
|
||||
if (damageDiff < 0.0f)
|
||||
HumanAIController.StructureDamaged(this, damageDiff, attacker);
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
attacker.Info?.ApplySkillGain(Barotrauma.Tags.MechanicalSkill,
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage);
|
||||
if (damageDiff < 0.0f)
|
||||
{
|
||||
attacker.Info?.ApplySkillGain(Barotrauma.Tags.MechanicalSkill,
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1775,9 +1792,9 @@ namespace Barotrauma
|
||||
//3. not found, attempt to find a prefab that uses the previous name as an identifier
|
||||
if (prefab == null) { prefab = MapEntityPrefab.Find(null, name) as StructurePrefab; }
|
||||
}
|
||||
else
|
||||
else if (StructurePrefab.Prefabs.TryGet(identifier, out StructurePrefab structurePrefab))
|
||||
{
|
||||
prefab = MapEntityPrefab.Find(null, identifier) as StructurePrefab;
|
||||
prefab = structurePrefab;
|
||||
}
|
||||
return prefab;
|
||||
}
|
||||
|
||||
@@ -1400,6 +1400,8 @@ namespace Barotrauma
|
||||
if (item.Submarine != this) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || item.CurrentHull == null) { continue; }
|
||||
//if the pump has no connection panel, it must be something else than a ballast pump (e.g. a weak point which uses a pump component to pump water in)
|
||||
if (item.GetComponent<ConnectionPanel>() == null) { continue; }
|
||||
if (!item.HasTag(Tags.Ballast) && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
pump.FlowPercentage = 0.0f;
|
||||
ballastHulls.Add(item.CurrentHull);
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
const float VerticalDrag = 0.05f;
|
||||
const float MaxDrag = 0.1f;
|
||||
|
||||
private const float ImpactDamageMultiplier = 10.0f;
|
||||
private const float ImpactDamageMultiplier = 3.0f;
|
||||
|
||||
//limbs with a mass smaller than this won't cause an impact when they hit the sub
|
||||
private const float MinImpactLimbMass = 10.0f;
|
||||
@@ -886,6 +886,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal);
|
||||
if (wallImpact < MinCollisionImpact) { return; }
|
||||
|
||||
//magic number to make wall impacts on par with monster impacts (the latter are affected by the mass of the monster)
|
||||
const float WallImpactMultiplier = 3.0f;
|
||||
wallImpact *= WallImpactMultiplier;
|
||||
|
||||
ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
|
||||
foreach (Submarine dockedSub in submarine.DockedTo)
|
||||
@@ -1070,17 +1075,20 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != submarine) { continue; }
|
||||
if (Timing.TotalTimeUnpaused < item.LastSubmarineImpactTime + Item.SubmarineImpactCooldown) { continue; }
|
||||
|
||||
if (item.body is not { BodyType: BodyType.Dynamic })
|
||||
{
|
||||
if (!item.Prefab.ReceiveSubmarineImpacts) { continue; }
|
||||
item.ReceiveImpact(impact, recursive: false);
|
||||
item.LastSubmarineImpactTime = Timing.TotalTimeUnpaused;
|
||||
}
|
||||
|
||||
if (!item.body.Enabled || item.CurrentHull == null || item.body.Mass > impulseMagnitude) { continue; }
|
||||
|
||||
item.body.ApplyLinearImpulse(impulse, 10.0f);
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
item.LastSubmarineImpactTime = Timing.TotalTimeUnpaused;
|
||||
}
|
||||
|
||||
float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
|
||||
|
||||
Reference in New Issue
Block a user