633e54b...7cc231b

commit 7cc231bc51890e7fde50bbac3413328dd7bc5189
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Wed Mar 20 14:23:06 2019 +0200

    Fixed server creating network events in Item.Load due to CreateServerEvent calls in some of the ItemComponent properties (e.g. LightComponent.IsOn). Caused syncing problems because the entity spawn events aren't created until the item has been loaded, leading to situations where clients fail to read events because the entity doesn't exist at their end yet (see #1293). TODO: get rid of console errors when attempting to create events during component initialization in the Item constructor.

commit ac1bf32edf125d95d56775e1e8ce54ac7671328c
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Wed Mar 20 13:49:55 2019 +0200

    Fix Hammerhead attack causing warping. Adjust the targeting priorities.

commit e3e032d7c3a753397269bb4236c1494ab0809004
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Wed Mar 20 13:48:47 2019 +0200

    Enemy AI fixes:
    - Fix enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters that were inside the sub when they were outside of it
    - Fix the previous target resetting too often
    - Fix the wall target resetting too often
    - Use world positions instead of sim positions where possible, because the sub positions are then taken into account

commit 847cf5ffd9212a542000dbf12332b2c08756579a
Author: Daniel Asteljoki <daniel.asteljoki@gmail.com>
Date:   Wed Mar 20 12:00:15 2019 +0200

    Remora: added power connection between sub and drone, removed non-scaling hull parts

commit a9c2e8cc124713e90dd44a9adf2fcbb3204b2c4d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Wed Mar 20 11:24:37 2019 +0200

    Server sets clients' last received campaign save/update IDs to one before the current up-to-date IDs (instead of zero) when a campaign changes. Zero would get interpreted as a more up-to-date ID if the IDs of the current campaign are close to ushort.MaxValue where the IDs wrap around.

commit 07d82b64e6990eacaf8905aed1a5d7c61224e47d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 19 18:24:01 2019 +0200

    Log level inequality error messages & client error reports handled by the server to GameAnalytics
This commit is contained in:
Joonas Rikkonen
2019-03-20 14:25:03 +02:00
parent 6a12058148
commit 81fafaf3f5
16 changed files with 139 additions and 120 deletions
@@ -24,7 +24,7 @@ namespace Barotrauma
if (wallTarget.Structure.Submarine != null) wallTargetPos += wallTarget.Structure.Submarine.Position; if (wallTarget.Structure.Submarine != null) wallTargetPos += wallTarget.Structure.Submarine.Position;
wallTargetPos.Y = -wallTargetPos.Y; wallTargetPos.Y = -wallTargetPos.Y;
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false); GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false);
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5); GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
} }
GUI.Font.DrawString(spriteBatch, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.ToString()})", pos - Vector2.UnitY * 20.0f, Color.Red); GUI.Font.DrawString(spriteBatch, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.ToString()})", pos - Vector2.UnitY * 20.0f, Color.Red);
@@ -1103,8 +1103,9 @@ namespace Barotrauma.Networking
if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
{ {
string errorMsg = " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
DebugConsole.ThrowError(errorMsg, createMessageBox: true); DebugConsole.ThrowError(errorMsg, createMessageBox: true);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch"+levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
CoroutineManager.StartCoroutine(EndGame("")); CoroutineManager.StartCoroutine(EndGame(""));
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -4,11 +4,6 @@ namespace Barotrauma
{ {
partial class CharacterInfo partial class CharacterInfo
{ {
partial void SpawnInventoryItemProjSpecific(Item item)
{
Entity.Spawner.CreateNetworkEvent(item, false);
}
public void ServerWrite(NetBuffer msg) public void ServerWrite(NetBuffer msg)
{ {
msg.Write(ID); msg.Write(ID);
@@ -335,6 +335,14 @@ namespace Barotrauma
{ {
if (GameMain.Server == null) return; if (GameMain.Server == null) return;
if (!ItemList.Contains(this))
{
string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
int index = components.IndexOf(ic); int index = components.IndexOf(ic);
if (index == -1) return; if (index == -1) return;
@@ -782,6 +782,7 @@ namespace Barotrauma.Networking
} }
Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error); Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error);
GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:LevelsDontMatch" + error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorStr);
KickClient(c, errorStr); KickClient(c, errorStr);
} }
@@ -839,8 +840,8 @@ namespace Barotrauma.Networking
//(the server started a new campaign and the client isn't aware of it yet?) //(the server started a new campaign and the client isn't aware of it yet?)
if (campaign.CampaignID != campaignID) if (campaign.CampaignID != campaignID)
{ {
c.LastRecvCampaignSave = 0; c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1);
c.LastRecvCampaignUpdate = 0; c.LastRecvCampaignUpdate = (ushort)(campaign.LastUpdateID - 1);
} }
} }
} }
@@ -74,6 +74,8 @@
<Submarine file="Submarines/Bunyip.sub" /> <Submarine file="Submarines/Bunyip.sub" />
<Submarine file="Submarines/Humpback.sub" /> <Submarine file="Submarines/Humpback.sub" />
<Submarine file="Submarines/Dugong.sub" /> <Submarine file="Submarines/Dugong.sub" />
<Submarine file="Submarines/Remora.sub" />
<Submarine file="Submarines/RemoraDrone.sub" />
<Text file="Content/Texts/EnglishVanilla.xml" /> <Text file="Content/Texts/EnglishVanilla.xml" />
<UIStyle file="Content/UI/style.xml"/> <UIStyle file="Content/UI/style.xml"/>
<Afflictions file="Content/Afflictions.xml"/> <Afflictions file="Content/Afflictions.xml"/>
@@ -3129,8 +3129,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Typhon.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Typhon.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -419,12 +419,7 @@ namespace Barotrauma
selectedTargetMemory.Priority -= deltaTime * 0.1f; selectedTargetMemory.Priority -= deltaTime * 0.1f;
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition; Vector2 attackPos = SelectedAiTarget.WorldPosition;
if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine != null && Character.Submarine != SelectedAiTarget.Entity.Submarine)
{
attackSimPosition = ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition - Character.Submarine.Position);
}
if (SelectedAiTarget.Entity is Item item) if (SelectedAiTarget.Entity is Item item)
{ {
@@ -440,35 +435,6 @@ namespace Barotrauma
} }
} }
if (wallTarget != null)
{
attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position);
if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null)
{
attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
}
}
else if (SelectedAiTarget.Entity is Character c)
{
//target the closest limb if the target is a character
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f;
foreach (Limb limb in ((Character)SelectedAiTarget.Entity).AnimController.Limbs)
{
if (limb == null) continue;
float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition) / Math.Max(limb.AttackPriority, 0.1f);
if (dist < closestDist)
{
closestDist = dist;
attackSimPosition = limb.SimPosition;
}
}
}
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left;
}
if (raycastTimer > 0.0) if (raycastTimer > 0.0)
{ {
raycastTimer -= deltaTime; raycastTimer -= deltaTime;
@@ -479,6 +445,35 @@ namespace Barotrauma
raycastTimer = RaycastInterval; raycastTimer = RaycastInterval;
} }
if (wallTarget != null)
{
attackPos = wallTarget.Position;
if (Character.Submarine == null && wallTarget.Structure.Submarine != null)
{
attackPos += wallTarget.Structure.Submarine.Position;
}
}
else if (SelectedAiTarget.Entity is Character c)
{
//target the closest limb if the target is a character
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.WorldPosition, WorldPosition) * 10.0f;
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb == null) continue;
float dist = Vector2.DistanceSquared(limb.WorldPosition, WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
if (dist < closestDist)
{
closestDist = dist;
attackPos = limb.WorldPosition;
}
}
}
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.WorldPosition.X < attackPos.X ? Direction.Right : Direction.Left;
}
if (aggressiveBoarding) if (aggressiveBoarding)
{ {
//targeting a wall section that can be passed through -> steer manually through the hole //targeting a wall section that can be passed through -> steer manually through the hole
@@ -511,9 +506,9 @@ namespace Barotrauma
return; return;
} }
} }
else if (SelectedAiTarget.Entity is Item) else if (SelectedAiTarget.Entity is Item i)
{ {
var door = ((Item)SelectedAiTarget.Entity).GetComponent<Door>(); var door = i.GetComponent<Door>();
//steer through the door manually if it's open or broken //steer through the door manually if it's open or broken
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f)) if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f))
{ {
@@ -556,7 +551,7 @@ namespace Barotrauma
} }
else else
{ {
UpdateFallBack(attackSimPosition, deltaTime); UpdateFallBack(attackPos, deltaTime);
return; return;
} }
} }
@@ -564,14 +559,14 @@ namespace Barotrauma
{ {
if (attackingLimb.attack.SecondaryCoolDownTimer <= 0) if (attackingLimb.attack.SecondaryCoolDownTimer <= 0)
{ {
// Don't allow attacking when the attack target has changed. // Don't allow attacking when the attack target has just changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget) if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
{ {
canAttack = false; canAttack = false;
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack) if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
{ {
// Fall back if cannot attack. // Fall back if cannot attack.
UpdateFallBack(attackSimPosition, deltaTime); UpdateFallBack(attackPos, deltaTime);
return; return;
} }
attackingLimb = null; attackingLimb = null;
@@ -580,7 +575,7 @@ namespace Barotrauma
{ {
// If the secondary cooldown is defined and expired, check if we can switch the attack // If the secondary cooldown is defined and expired, check if we can switch the attack
var previousLimb = attackingLimb; var previousLimb = attackingLimb;
var newLimb = GetAttackLimb(attackSimPosition, previousLimb); var newLimb = GetAttackLimb(attackPos, previousLimb);
if (newLimb != null) if (newLimb != null)
{ {
attackingLimb = newLimb; attackingLimb = newLimb;
@@ -594,7 +589,7 @@ namespace Barotrauma
} }
else else
{ {
UpdateFallBack(attackSimPosition, deltaTime); UpdateFallBack(attackPos, deltaTime);
return; return;
} }
} }
@@ -609,7 +604,7 @@ namespace Barotrauma
break; break;
case AIBehaviorAfterAttack.FallBack: case AIBehaviorAfterAttack.FallBack:
default: default:
UpdateFallBack(attackSimPosition, deltaTime); UpdateFallBack(attackPos, deltaTime);
return; return;
} }
@@ -617,7 +612,7 @@ namespace Barotrauma
if (attackingLimb == null) if (attackingLimb == null)
{ {
attackingLimb = GetAttackLimb(attackSimPosition); attackingLimb = GetAttackLimb(attackPos);
} }
if (canAttack) if (canAttack)
{ {
@@ -627,18 +622,18 @@ namespace Barotrauma
if (canAttack) if (canAttack)
{ {
// Check that we can reach the target // Check that we can reach the target
distance = ConvertUnits.ToDisplayUnits(Vector2.Distance(attackingLimb.SimPosition, attackSimPosition)); distance = Vector2.Distance(attackingLimb.WorldPosition, attackPos);
canAttack = distance < attackingLimb.attack.Range; canAttack = distance < attackingLimb.attack.Range;
} }
Limb steeringLimb = Character.AnimController.MainLimb; Limb steeringLimb = Character.AnimController.MainLimb;
if (steeringLimb != null) if (steeringLimb != null)
{ {
Vector2 steeringVector = attackSimPosition - steeringLimb.SimPosition; Vector2 toTarget = Vector2.Normalize(attackPos - steeringLimb.WorldPosition);
Vector2 targetingVector = Vector2.Normalize(steeringVector) * attackingLimb.attack.Range; Vector2 targetingVector = toTarget * attackingLimb.attack.Range;
// Offset the position a bit so that we don't overshoot the movement. // Offset the position a bit so that we don't overshoot the movement.
Vector2 steerPos = attackSimPosition + targetingVector; Vector2 steerPos = attackPos + targetingVector;
steeringManager.SteeringSeek(steerPos, 10); steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(attackPos), 10);
if (Character.CurrentHull == null) if (Character.CurrentHull == null)
{ {
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f); SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
@@ -656,7 +651,7 @@ namespace Barotrauma
} }
else if (indoorsSteering.CurrentPath.Finished) else if (indoorsSteering.CurrentPath.Finished)
{ {
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(steeringVector)); steeringManager.SteeringManual(deltaTime, toTarget);
} }
else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null) else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null)
{ {
@@ -674,11 +669,11 @@ namespace Barotrauma
if (canAttack) if (canAttack)
{ {
UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition, distance); UpdateLimbAttack(deltaTime, attackingLimb, ConvertUnits.ToSimUnits(attackPos), distance);
} }
} }
private Limb GetAttackLimb(Vector2 attackSimPosition, Limb ignoredLimb = null) private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null)
{ {
AttackContext currentContext = Character.GetAttackContext(); AttackContext currentContext = Character.GetAttackContext();
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity; var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
@@ -692,7 +687,7 @@ namespace Barotrauma
l.attack.IsValidTarget(target) && l.attack.IsValidTarget(target) &&
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character))) l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character)))
.OrderByDescending(l => l.attack.Priority) .OrderByDescending(l => l.attack.Priority)
.ThenBy(l => ConvertUnits.ToDisplayUnits(Vector2.Distance(l.SimPosition, attackSimPosition))); .ThenBy(l => Vector2.Distance(l.WorldPosition, attackWorldPos));
// TODO: priority should probably not override the distance -> use values instead of booleans // TODO: priority should probably not override the distance -> use values instead of booleans
return limbs.FirstOrDefault(); return limbs.FirstOrDefault();
} }
@@ -709,8 +704,9 @@ namespace Barotrauma
//check if there's a wall between the target and the Character //check if there's a wall between the target and the Character
Vector2 rayStart = Character.SimPosition; Vector2 rayStart = Character.SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition; Vector2 rayEnd = SelectedAiTarget.SimPosition;
bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) if (offset)
{ {
rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position); rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
} }
@@ -721,18 +717,7 @@ namespace Barotrauma
return; return;
} }
Structure wall = closestBody.UserData as Structure; if (closestBody.UserData is Structure wall && wall.Submarine != null)
if (wall?.Submarine == null)
{
return;
/*if (selectedAiTarget.Entity.Submarine != null)
{
wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition), selectedAiTarget.Entity.Submarine);
latchOntoAI?.SetAttachTarget(closestBody, selectedAiTarget.Entity.Submarine, Submarine.LastPickedPosition);
}*/
//if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null) wallAttackPos += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
else
{ {
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition)); int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
int passableHoleCount = GetMinimumPassableHoleCount(); int passableHoleCount = GetMinimumPassableHoleCount();
@@ -755,20 +740,21 @@ namespace Barotrauma
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i; if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
} }
Vector2 sectionPos = ConvertUnits.ToSimUnits(wall.SectionPosition(sectionIndex)); Vector2 sectionPos = wall.SectionPosition(sectionIndex);
Vector2 attachTargetNormal; Vector2 attachTargetNormal;
if (wall.IsHorizontal) if (wall.IsHorizontal)
{ {
attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y)); attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y));
sectionPos.Y += ConvertUnits.ToSimUnits((wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2) * attachTargetNormal.Y; sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
} }
else else
{ {
attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f); attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f);
sectionPos.X += ConvertUnits.ToSimUnits((wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2) * attachTargetNormal.X; sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
} }
wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(sectionPos), wall, sectionIndex);
latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, sectionPos, attachTargetNormal); wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
} }
} }
@@ -776,16 +762,6 @@ namespace Barotrauma
{ {
updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f); updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f);
// Reduce the cooldown so that the character can react
foreach (var limb in Character.AnimController.Limbs)
{
if (limb.attack != null)
{
limb.attack.CoolDownTimer *= 0.1f;
// secondary cooldown?
}
}
if (attackResult.Damage > 0.0f && attackWhenProvoked) if (attackResult.Damage > 0.0f && attackWhenProvoked)
{ {
if (!(attacker is AICharacter) || (((AICharacter)attacker).AIController is HumanAIController)) if (!(attacker is AICharacter) || (((AICharacter)attacker).AIController is HumanAIController))
@@ -801,18 +777,31 @@ namespace Barotrauma
if (attacker == null || attacker.AiTarget == null) return; if (attacker == null || attacker.AiTarget == null) return;
AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget); AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget);
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt; targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt;
// Reduce the cooldown so that the character can react
// Only allow to react once. Otherwise would attack the target with only a fraction of cooldown
if (SelectedAiTarget != attacker.AiTarget)
{
foreach (var limb in Character.AnimController.Limbs)
{
if (limb.attack != null)
{
limb.attack.CoolDownTimer *= 0.1f;
}
}
}
} }
// 10 dmg, 100 health -> 0.1 // 10 dmg, 100 health -> 0.1
private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f); private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f);
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition, float distance = -1) private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackSimPos, float distance = -1)
{ {
var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable; var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
if (damageTarget == null) return; if (damageTarget == null) return;
float prevHealth = damageTarget.Health; float prevHealth = damageTarget.Health;
if (limb.UpdateAttack(deltaTime, attackPosition, damageTarget, out AttackResult attackResult, distance)) if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance))
{ {
if (damageTarget.Health > 0) if (damageTarget.Health > 0)
{ {
@@ -820,24 +809,19 @@ namespace Barotrauma
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed; selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed;
} }
} }
if (!limb.attack.IsRunning)
{
wallTarget = null;
}
} }
private void UpdateFallBack(Vector2 attackPosition, float deltaTime) private void UpdateFallBack(Vector2 attackPosition, float deltaTime)
{ {
float dist = Vector2.Distance(attackPosition, Character.SimPosition); Vector2 attackVector = attackPosition - Character.WorldPosition;
float dist = attackVector.Length();
float desiredDist = colliderSize * 2.0f; float desiredDist = colliderSize * 2.0f;
if (dist < desiredDist) if (dist < desiredDist)
{ {
Vector2 attackDir = Vector2.Normalize(Character.SimPosition - attackPosition); Vector2 attackDir = Vector2.Normalize(-attackVector);
if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY; if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY;
steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f))); steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f)));
} }
steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f);
} }
@@ -888,8 +872,8 @@ namespace Barotrauma
//sight/hearing range //sight/hearing range
public void UpdateTargets(Character character, out TargetingPriority targetingPriority) public void UpdateTargets(Character character, out TargetingPriority targetingPriority)
{ {
AITarget newTarget = null;
targetingPriority = null; targetingPriority = null;
SelectedAiTarget = null;
selectedTargetMemory = null; selectedTargetMemory = null;
targetValue = 0.0f; targetValue = 0.0f;
@@ -1021,13 +1005,15 @@ namespace Barotrauma
if (valueModifier > targetValue) if (valueModifier > targetValue)
{ {
SelectedAiTarget = target; newTarget = target;
selectedTargetMemory = targetMemory; selectedTargetMemory = targetMemory;
targetingPriority = targetingPriorities[targetingTag]; targetingPriority = targetingPriorities[targetingTag];
targetValue = valueModifier; targetValue = valueModifier;
} }
} }
SelectedAiTarget = newTarget;
if (SelectedAiTarget != _previousAiTarget) if (SelectedAiTarget != _previousAiTarget)
{ {
wallTarget = null; wallTarget = null;
@@ -801,7 +801,7 @@ namespace Barotrauma
{ {
foreach (XElement itemElement in element.Elements()) foreach (XElement itemElement in element.Elements())
{ {
var newItem = Item.Load(itemElement, inventory.Owner.Submarine); var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true);
if (newItem == null) { continue; } if (newItem == null) { continue; }
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 }); int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
@@ -811,8 +811,6 @@ namespace Barotrauma
continue; continue;
} }
SpawnInventoryItemProjSpecific(newItem);
inventory.TryPutItem(newItem, slotIndices[0], false, false, null); inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
//force the item to the correct slots //force the item to the correct slots
@@ -842,8 +840,6 @@ namespace Barotrauma
} }
} }
partial void SpawnInventoryItemProjSpecific(Item item);
public void ReloadHeadAttachments() public void ReloadHeadAttachments()
{ {
ResetLoadedAttachments(); ResetLoadedAttachments();
@@ -491,10 +491,10 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated. /// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
/// </summary> /// </summary>
public bool UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget, out AttackResult attackResult, float distance = -1) public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
{ {
attackResult = default(AttackResult); attackResult = default(AttackResult);
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition)); float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos));
bool wasRunning = attack.IsRunning; bool wasRunning = attack.IsRunning;
attack.UpdateAttackTimer(deltaTime); attack.UpdateAttackTimer(deltaTime);
@@ -511,7 +511,7 @@ namespace Barotrauma
ignoredBodies.Add(character.AnimController.Collider.FarseerBody); ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
structureBody = Submarine.PickBody( structureBody = Submarine.PickBody(
SimPosition, attackPosition, SimPosition, attackSimPos,
ignoredBodies, Physics.CollisionWall); ignoredBodies, Physics.CollisionWall);
if (damageTarget is Item) if (damageTarget is Item)
@@ -607,7 +607,7 @@ namespace Barotrauma
attack.SetCoolDown(); attack.SetCoolDown();
} }
Vector2 diff = attackPosition - SimPosition; Vector2 diff = attackSimPos - SimPosition;
bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f; bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
if (applyForces) if (applyForces)
{ {
@@ -620,13 +620,13 @@ namespace Barotrauma
Limb limb = character.AnimController.Limbs[limbIndex]; Limb limb = character.AnimController.Limbs[limbIndex];
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA; Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos); limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
} }
} }
else else
{ {
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA; Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos); body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
} }
} }
return wasHit; return wasHit;
@@ -60,7 +60,6 @@ namespace Barotrauma
public readonly XElement StaticBodyConfig; public readonly XElement StaticBodyConfig;
private bool needsPositionUpdate;
private float lastSentCondition; private float lastSentCondition;
private float condition; private float condition;
@@ -1813,6 +1812,18 @@ namespace Barotrauma
partial void UpdateNetPosition(float deltaTime); partial void UpdateNetPosition(float deltaTime);
public static Item Load(XElement element, Submarine submarine) public static Item Load(XElement element, Submarine submarine)
{
return Load(element, submarine, createNetworkEvent: false);
}
/// <summary>
/// Instantiate a new item and load its data from the XML element.
/// </summary>
/// <param name="element">The element containing the data of the item</param>
/// <param name="submarine">The submarine to spawn the item in (can be null)</param>
/// <param name="createNetworkEvent">Should an EntitySpawner event be created to notify clients about the item being created.</param>
/// <returns></returns>
public static Item Load(XElement element, Submarine submarine, bool createNetworkEvent)
{ {
string name = element.Attribute("name").Value; string name = element.Attribute("name").Value;
string identifier = element.GetAttributeString("identifier", ""); string identifier = element.GetAttributeString("identifier", "");
@@ -1858,6 +1869,13 @@ namespace Barotrauma
linkedToID = new List<ushort>() linkedToID = new List<ushort>()
}; };
#if SERVER
if (createNetworkEvent)
{
Spawner.CreateNetworkEvent(item, remove: false);
}
#endif
foreach (XAttribute attribute in element.Attributes()) foreach (XAttribute attribute in element.Attributes())
{ {
if (!item.properties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue; if (!item.properties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue;
@@ -91,6 +91,16 @@ namespace Barotrauma
public override void CreateNetworkEvent() public override void CreateNetworkEvent()
{ {
if (!Item.ItemList.Contains(container.Item))
{
string errorMsg = "Attempted to create a network event for an item (" + container.Item.Name + ") that hasn't been fully initialized yet.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"ItemInventory.CreateServerEvent:EventForUninitializedItem" + container.Item.Name + container.Item.ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
int componentIndex = container.Item.GetComponentIndex(container); int componentIndex = container.Item.GetComponentIndex(container);
if (componentIndex == -1) if (componentIndex == -1)
{ {
@@ -407,7 +407,7 @@ namespace Barotrauma
try try
{ {
MethodInfo loadMethod = t.GetMethod("Load"); MethodInfo loadMethod = t.GetMethod("Load", new [] { typeof(XElement), typeof(Submarine) });
if (loadMethod == null) if (loadMethod == null)
{ {
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + "."); DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");
@@ -70,7 +70,7 @@ namespace Barotrauma
private readonly Queue<IEntitySpawnInfo> spawnQueue; private readonly Queue<IEntitySpawnInfo> spawnQueue;
private readonly Queue<Entity> removeQueue; private readonly Queue<Entity> removeQueue;
class SpawnOrRemove public class SpawnOrRemove
{ {
public readonly Entity Entity; public readonly Entity Entity;
Binary file not shown.