Unstable v0.10.6.0 (October 13th 2020)

This commit is contained in:
Juan Pablo Arce
2020-10-13 12:59:45 -03:00
parent 768f516e7c
commit 6b36bf809d
59 changed files with 1036 additions and 421 deletions
@@ -593,7 +593,7 @@ namespace Barotrauma
} }
} }
if (info != null || Vitality < MaxVitality * 0.98f) if (info != null || Vitality < MaxVitality * 0.98f || IsPet)
{ {
hudInfoTimer -= deltaTime; hudInfoTimer -= deltaTime;
if (hudInfoTimer <= 0.0f) if (hudInfoTimer <= 0.0f)
@@ -772,49 +772,68 @@ namespace Barotrauma
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) : MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f; 1.0f;
if (!GUI.DisableCharacterNames && hudInfoVisible && info != null && if (!GUI.DisableCharacterNames && hudInfoVisible &&
(controlled == null || this != controlled.FocusedCharacter) && cam.Zoom > 0.4f) (controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f)
{ {
string name = Info.DisplayName; if (info != null)
if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); }
Vector2 nameSize = GUI.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y;
namePos *= screenSize / viewportSize;
namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y);
namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{ {
nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red; string name = Info.DisplayName;
if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); }
Vector2 nameSize = GUI.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y;
namePos *= screenSize / viewportSize;
namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y);
namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{
nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
}
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
}
GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
}
} }
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
var petBehavior = (AIController as EnemyAIController)?.PetBehavior;
if (petBehavior != null && !IsDead && !IsUnconscious)
{ {
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType); var petStatus = petBehavior.GetCurrentStatusIndicatorType();
var iconStyle = GUI.Style.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null) if (iconStyle != null)
{ {
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f; Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos; Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y; iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First(); var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom; float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale); icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
} }
} }
GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
}
} }
if (IsDead) { return; } if (IsDead) { return; }
@@ -98,6 +98,9 @@ namespace Barotrauma
case "pendingupgrades": case "pendingupgrades":
UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true); UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true);
break; break;
case "pets":
petsElement = subElement;
break;
} }
} }
@@ -213,6 +216,10 @@ namespace Barotrauma
crewDead = false; crewDead = false;
endTimer = 5.0f; endTimer = 5.0f;
CrewManager.InitSinglePlayerRound(); CrewManager.InitSinglePlayerRound();
if (petsElement != null)
{
PetBehavior.LoadPets(petsElement);
}
} }
protected override void LoadInitialLevel() protected override void LoadInitialLevel()
@@ -705,6 +712,10 @@ namespace Barotrauma
} }
} }
XElement petsElement = new XElement("pets");
PetBehavior.SavePets(petsElement);
modeElement.Add(petsElement);
CrewManager.Save(modeElement); CrewManager.Save(modeElement);
CampaignMetadata.Save(modeElement); CampaignMetadata.Save(modeElement);
Map.Save(modeElement); Map.Save(modeElement);
@@ -186,8 +186,7 @@ namespace Barotrauma.Tutorials
// TODO: Rework order highlighting for new command UI // TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5)); // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5));
//HighlightOrderOption("jobspecific"); //HighlightOrderOption("jobspecific");
} } while (!HasOrder(captain_mechanic, "repairsystems") && !HasOrder(captain_mechanic, "repairmechanical") && !HasOrder(captain_mechanic, "repairelectrical"));
while (!HasOrder(captain_mechanic, "repairsystems"));
RemoveCompletedObjective(segments[1]); RemoveCompletedObjective(segments[1]);
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Command)); TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Command));
@@ -123,7 +123,7 @@ namespace Barotrauma.Tutorials
// Room 3 // Room 3
engineer_reactorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_reactorobjectivesensor")).GetComponent<MotionSensor>(); engineer_reactorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_reactorobjectivesensor")).GetComponent<MotionSensor>();
tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent<Powered>(); tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent<OxygenGenerator>();
engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>(); engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>();
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity; engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
engineer_reactor.FuelConsumptionRate = 0.0f; engineer_reactor.FuelConsumptionRate = 0.0f;
@@ -380,7 +380,7 @@ namespace Barotrauma.Tutorials
yield return null; yield return null;
} while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired } while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired
SetHighlight(engineer_brokenJunctionBox, false); SetHighlight(engineer_brokenJunctionBox, false);
RemoveCompletedObjective(segments[2]); RemoveCompletedObjective(segments[3]);
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
{ {
@@ -398,7 +398,7 @@ namespace Barotrauma.Tutorials
{ {
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false); SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false);
} }
RemoveCompletedObjective(segments[3]); RemoveCompletedObjective(segments[4]);
do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained
wiringActive = false; wiringActive = false;
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true); SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true);
@@ -424,7 +424,7 @@ namespace Barotrauma.Tutorials
// Remove highlights when each individual machine is repaired // Remove highlights when each individual machine is repaired
do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (engineer_submarineJunctionBox_1.Condition < repairableJunctionBoxComponent1.RepairThreshold || engineer_submarineJunctionBox_2.Condition < repairableJunctionBoxComponent2.RepairThreshold || engineer_submarineJunctionBox_3.Condition < repairableJunctionBoxComponent3.RepairThreshold); do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (engineer_submarineJunctionBox_1.Condition < repairableJunctionBoxComponent1.RepairThreshold || engineer_submarineJunctionBox_2.Condition < repairableJunctionBoxComponent2.RepairThreshold || engineer_submarineJunctionBox_3.Condition < repairableJunctionBoxComponent3.RepairThreshold);
CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3);
RemoveCompletedObjective(segments[4]); RemoveCompletedObjective(segments[5]);
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(6); // Powerup reactor TriggerTutorialSegment(6); // Powerup reactor
@@ -433,7 +433,7 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load
engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item); engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item);
SetHighlight(engineer_submarineReactor.Item, false); SetHighlight(engineer_submarineReactor.Item, false);
RemoveCompletedObjective(segments[5]); RemoveCompletedObjective(segments[6]);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(4f, false); yield return new WaitForSeconds(4f, false);
@@ -144,17 +144,13 @@ namespace Barotrauma
protected override ItemInventory GetActiveEquippedSubInventory(int slotIndex) protected override ItemInventory GetActiveEquippedSubInventory(int slotIndex)
{ {
var item = Items[slotIndex]; var item = Items[slotIndex];
if (item == null) return null; if (item == null) { return null; }
var container = item.GetComponent<ItemContainer>(); var container = item.GetComponent<ItemContainer>();
if (container == null || if (container == null || !container.KeepOpenWhenEquippedBy(character))
!character.CanAccessInventory(container.Inventory) ||
!container.KeepOpenWhenEquipped ||
!character.HasEquippedItem(container.Item))
{ {
return null; return null;
} }
return container.Inventory; return container.Inventory;
} }
@@ -626,7 +622,7 @@ namespace Barotrauma
{ {
var itemContainer = item.GetComponent<ItemContainer>(); var itemContainer = item.GetComponent<ItemContainer>();
if (itemContainer != null && if (itemContainer != null &&
itemContainer.KeepOpenWhenEquipped && itemContainer.KeepOpenWhenEquippedBy(character) &&
character.CanAccessInventory(itemContainer.Inventory) && character.CanAccessInventory(itemContainer.Inventory) &&
!highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory)) !highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory))
{ {
@@ -70,6 +70,7 @@ namespace Barotrauma.Items.Components
[Serialize(false, false, description: "Should the inventory of this item be kept open when the item is equipped by a character.")] [Serialize(false, false, description: "Should the inventory of this item be kept open when the item is equipped by a character.")]
public bool KeepOpenWhenEquipped { get; set; } public bool KeepOpenWhenEquipped { get; set; }
[Serialize(false, false, description: "Can the inventory of this item be moved around on the screen by the player.")] [Serialize(false, false, description: "Can the inventory of this item be moved around on the screen by the player.")]
public bool MovableFrame { get; set; } public bool MovableFrame { get; set; }
@@ -162,6 +163,30 @@ namespace Barotrauma.Items.Components
} }
} }
public bool KeepOpenWhenEquippedBy(Character character)
{
if (!character.CanAccessInventory(Inventory) ||
!KeepOpenWhenEquipped ||
!character.HasEquippedItem(Item))
{
return false;
}
//if holding 2 different "always open" items in different hands, don't force them to stay open
if (character.SelectedItems[0] != null &&
character.SelectedItems[1] != null &&
character.SelectedItems[0] != character.SelectedItems[1])
{
if ((character.SelectedItems[0].GetComponent<ItemContainer>()?.KeepOpenWhenEquipped ?? false) &&
(character.SelectedItems[1].GetComponent<ItemContainer>()?.KeepOpenWhenEquipped ?? false))
{
return false;
}
}
return true;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (hideItems || (item.body != null && !item.body.Enabled)) { return; } if (hideItems || (item.body != null && !item.body.Enabled)) { return; }
@@ -342,7 +342,8 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
if (Vector2.DistanceSquared(nodeWorldPos, draggingWire.nodes[(int)highlightedNodeIndex]) > Submarine.GridSize.X * Submarine.GridSize.X || PlayerInput.IsShiftDown()) if ((highlightedNodeIndex.HasValue && Vector2.DistanceSquared(nodeWorldPos, draggingWire.nodes[(int)highlightedNodeIndex]) > Submarine.GridSize.X * Submarine.GridSize.X) ||
PlayerInput.IsShiftDown())
{ {
selectedNodeIndex = highlightedNodeIndex; selectedNodeIndex = highlightedNodeIndex;
} }
@@ -286,7 +286,7 @@ namespace Barotrauma.Items.Components
rotation + MathHelper.PiOver2, item.Scale, rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
if (!editing || GUI.DisableHUD) { return; } if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
float widgetRadius = 60.0f; float widgetRadius = 60.0f;
@@ -305,8 +305,6 @@ namespace Barotrauma.Items.Components
drawPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * widgetRadius, drawPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * widgetRadius,
Color.LightGreen); Color.LightGreen);
if (!item.IsSelected) { return; }
Widget minRotationWidget = GetWidget("minrotation", spriteBatch, size: 10, initMethod: (widget) => Widget minRotationWidget = GetWidget("minrotation", spriteBatch, size: 10, initMethod: (widget) =>
{ {
widget.Selected += () => widget.Selected += () =>
@@ -404,8 +404,8 @@ namespace Barotrauma
container = (this as ItemInventory).Container; container = (this as ItemInventory).Container;
} }
if (container == null) return false; if (container == null) { return false; }
return owner.SelectedCharacter != null || !container.KeepOpenWhenEquipped || (!(owner is Character)) || !owner.HasEquippedItem(container.Item); return owner.SelectedCharacter != null|| (!(owner is Character character)) || !container.KeepOpenWhenEquippedBy(character) || !owner.HasEquippedItem(container.Item);
} }
protected virtual bool HideSlot(int i) protected virtual bool HideSlot(int i)
@@ -10,11 +10,30 @@ namespace Barotrauma
{ {
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable
{ {
private class RemoteDecal
{
public readonly UInt32 DecalId;
public readonly int SpriteIndex;
public Vector2 NormalizedPos;
public readonly float Scale;
public RemoteDecal(UInt32 decalId, int spriteIndex, Vector2 normalizedPos, float scale)
{
DecalId = decalId;
SpriteIndex = spriteIndex;
NormalizedPos = normalizedPos;
Scale = scale;
}
}
private float serverUpdateDelay; private float serverUpdateDelay;
private float remoteWaterVolume, remoteOxygenPercentage; private float remoteWaterVolume, remoteOxygenPercentage;
private List<Vector3> remoteFireSources; private List<Vector3> remoteFireSources;
private readonly List<BackgroundSection> remoteBackgroundSections = new List<BackgroundSection>(); private readonly List<BackgroundSection> remoteBackgroundSections = new List<BackgroundSection>();
private readonly List<RemoteDecal> remoteDecals = new List<RemoteDecal>();
private readonly HashSet<Decal> pendingDecalUpdates = new HashSet<Decal>();
private double lastAmbientLightEditTime; private double lastAmbientLightEditTime;
public override bool SelectableInEditor public override bool SelectableInEditor
@@ -126,10 +145,14 @@ namespace Barotrauma
networkUpdateTimer += deltaTime; networkUpdateTimer += deltaTime;
if (networkUpdateTimer > 0.2f) if (networkUpdateTimer > 0.2f)
{ {
if (!pendingSectionUpdates.Any()) if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any())
{ {
GameMain.NetworkMember?.CreateEntityEvent(this); GameMain.NetworkMember?.CreateEntityEvent(this);
} }
foreach (Decal decal in pendingDecalUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { decal });
}
foreach (int pendingSectionUpdate in pendingSectionUpdates) foreach (int pendingSectionUpdate in pendingSectionUpdates)
{ {
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate }); GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate });
@@ -538,9 +561,9 @@ namespace Barotrauma
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.Write(extraData != null);
if (extraData == null) if (extraData == null)
{ {
msg.WriteRangedInteger(0, 0, 2);
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8); msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
msg.Write(FireSources.Count > 0); msg.Write(FireSources.Count > 0);
@@ -560,8 +583,16 @@ namespace Barotrauma
} }
} }
} }
else if (extraData[0] is Decal decal)
{
msg.WriteRangedInteger(1, 0, 2);
int decalIndex = decals.IndexOf(decal);
msg.Write((byte)(decalIndex < 0 ? 255 : decalIndex));
msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8);
}
else else
{ {
msg.WriteRangedInteger(2, 0, 2);
int sectorToUpdate = (int)extraData[0]; int sectorToUpdate = (int)extraData[0];
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent; int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
@@ -622,22 +653,16 @@ namespace Barotrauma
else else
{ {
int decalCount = message.ReadRangedInteger(0, MaxDecalsPerHull); int decalCount = message.ReadRangedInteger(0, MaxDecalsPerHull);
decals.Clear(); if (decalCount == 0) { decals.Clear(); }
remoteDecals.Clear();
for (int i = 0; i < decalCount; i++) for (int i = 0; i < decalCount; i++)
{ {
UInt32 decalId = message.ReadUInt32(); UInt32 decalId = message.ReadUInt32();
int spriteIndex = message.ReadByte(); int spriteIndex = message.ReadByte();
float normalizedXPos = message.ReadRangedSingle(0.0f, 1.0f, 8); float normalizedXPos = message.ReadRangedSingle(0.0f, 1.0f, 8);
float normalizedYPos = message.ReadRangedSingle(0.0f, 1.0f, 8); float normalizedYPos = message.ReadRangedSingle(0.0f, 1.0f, 8);
float decalPosX = MathHelper.Lerp(rect.X, rect.Right, normalizedXPos);
float decalPosY = MathHelper.Lerp(rect.Y - rect.Height, rect.Y, normalizedYPos);
float decalScale = message.ReadRangedSingle(0.0f, 2.0f, 12); float decalScale = message.ReadRangedSingle(0.0f, 2.0f, 12);
if (Submarine != null) remoteDecals.Add(new RemoteDecal(decalId, spriteIndex, new Vector2(normalizedXPos, normalizedYPos), decalScale));
{
decalPosX += Submarine.Position.X;
decalPosY += Submarine.Position.Y;
}
AddDecal(decalId, new Vector2(decalPosX, decalPosY), decalScale, isNetworkEvent: true, spriteIndex: spriteIndex);
} }
} }
} }
@@ -658,6 +683,23 @@ namespace Barotrauma
} }
remoteBackgroundSections.Clear(); remoteBackgroundSections.Clear();
if (remoteDecals.Any())
{
decals.Clear();
foreach (RemoteDecal remoteDecal in remoteDecals)
{
float decalPosX = MathHelper.Lerp(rect.X, rect.Right, remoteDecal.NormalizedPos.X);
float decalPosY = MathHelper.Lerp(rect.Y - rect.Height, rect.Y, remoteDecal.NormalizedPos.Y);
if (Submarine != null)
{
decalPosX += Submarine.Position.X;
decalPosY += Submarine.Position.Y;
}
AddDecal(remoteDecal.DecalId, new Vector2(decalPosX, decalPosY), remoteDecal.Scale, isNetworkEvent: true, spriteIndex: remoteDecal.SpriteIndex);
}
remoteDecals.Clear();
}
if (remoteFireSources == null) { return; } if (remoteFireSources == null) { return; }
WaterVolume = remoteWaterVolume; WaterVolume = remoteWaterVolume;
@@ -44,6 +44,12 @@ namespace Barotrauma.Lights
} }
} }
[Serialize(1f, true), Editable(minValue: 0.01f, maxValue: 100f, ValueStep = 0.1f, DecimalCount = 2)]
public float Scale { get; set; }
[Serialize("0, 0", true), Editable(ValueStep = 1, DecimalCount = 1, MinValueFloat = -1000f, MaxValueFloat = 1000f)]
public Vector2 Offset { get; set; }
public float TextureRange public float TextureRange
{ {
get; get;
@@ -241,11 +247,13 @@ namespace Barotrauma.Lights
} }
} }
private Vector2 _spriteScale = Vector2.One;
public Vector2 SpriteScale public Vector2 SpriteScale
{ {
get; get { return _spriteScale * lightSourceParams.Scale; }
set; set { _spriteScale = value; }
} = Vector2.One; }
public float? OverrideLightSpriteAlpha public float? OverrideLightSpriteAlpha
{ {
@@ -280,7 +288,9 @@ namespace Barotrauma.Lights
{ {
get { return lightSourceParams.LightSprite; } get { return lightSourceParams.LightSprite; }
} }
private Vector2 OverrideLightTextureOrigin => OverrideLightTexture.Origin + LightSourceParams.Offset;
public Color Color public Color Color
{ {
get { return lightSourceParams.Color; } get { return lightSourceParams.Color; }
@@ -564,7 +574,7 @@ namespace Barotrauma.Lights
var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTexture.Origin; Vector2 origin = OverrideLightTextureOrigin;
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y); origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
origin -= Vector2.One * 0.5f; origin -= Vector2.One * 0.5f;
@@ -873,7 +883,7 @@ namespace Barotrauma.Lights
{ {
overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTexture.Origin; Vector2 origin = OverrideLightTextureOrigin;
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; } if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; }
if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; } if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; }
uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f); uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
@@ -1060,7 +1070,7 @@ namespace Barotrauma.Lights
{ {
var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTexture.Origin; Vector2 origin = OverrideLightTextureOrigin;
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y); origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
origin *= TextureRange; origin *= TextureRange;
@@ -1088,13 +1098,12 @@ namespace Barotrauma.Lights
if (DeformableLightSprite != null) if (DeformableLightSprite != null)
{ {
Vector2 origin = DeformableLightSprite.Origin; Vector2 origin = DeformableLightSprite.Origin + LightSourceParams.Offset;
Vector2 drawPos = position; Vector2 drawPos = position;
if (ParentSub != null) if (ParentSub != null)
{ {
drawPos += ParentSub.DrawPosition; drawPos += ParentSub.DrawPosition;
} }
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) if (LightSpriteEffect == SpriteEffects.FlipHorizontally)
{ {
origin.X = DeformableLightSprite.Sprite.SourceRect.Width - origin.X; origin.X = DeformableLightSprite.Sprite.SourceRect.Width - origin.X;
@@ -1113,7 +1122,7 @@ namespace Barotrauma.Lights
if (LightSprite != null) if (LightSprite != null)
{ {
Vector2 origin = LightSprite.Origin; Vector2 origin = LightSprite.Origin + LightSourceParams.Offset;
if ((LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) if ((LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
{ {
origin.X = LightSprite.SourceRect.Width - origin.X; origin.X = LightSprite.SourceRect.Width - origin.X;
@@ -1023,7 +1023,10 @@ namespace Barotrauma.Networking
if (Enum.TryParse(splitMsg[0], out disconnectReason)) { disconnectReasonIncluded = true; } if (Enum.TryParse(splitMsg[0], out disconnectReason)) { disconnectReasonIncluded = true; }
} }
if (disconnectMsg == Lidgren.Network.NetConnection.NoResponseMessage) if (disconnectMsg == Lidgren.Network.NetConnection.NoResponseMessage ||
disconnectReason == DisconnectReason.Banned ||
disconnectReason == DisconnectReason.Kicked ||
disconnectReason == DisconnectReason.TooManyFailedLogins)
{ {
allowReconnect = false; allowReconnect = false;
} }
@@ -2138,6 +2141,7 @@ namespace Barotrauma.Networking
return; return;
} }
entities.Add(entity);
if (entity != null && (entity is Item || entity is Character || entity is Submarine)) if (entity != null && (entity is Item || entity is Character || entity is Submarine))
{ {
entity.ClientRead(objHeader.Value, inc, sendingTime); entity.ClientRead(objHeader.Value, inc, sendingTime);
@@ -2188,7 +2192,8 @@ namespace Barotrauma.Networking
errorLines.Add(ex.StackTrace.CleanupStackTrace()); errorLines.Add(ex.StackTrace.CleanupStackTrace());
errorLines.Add(" "); errorLines.Add(" ");
if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL || if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL) objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION)
{ {
foreach (IServerSerializable ent in entities) foreach (IServerSerializable ent in entities)
{ {
@@ -373,7 +373,17 @@ namespace Barotrauma.Networking
info.GameMode = element.GetAttributeString("GameMode", ""); info.GameMode = element.GetAttributeString("GameMode", "");
info.GameVersion = element.GetAttributeString("GameVersion", ""); info.GameVersion = element.GetAttributeString("GameVersion", "");
info.MaxPlayers = element.GetAttributeInt("MaxPlayers", 0);
int maxPlayersElement = element.GetAttributeInt("MaxPlayers", 0);
if (maxPlayersElement > NetConfig.MaxPlayers)
{
DebugConsole.IsOpen = true;
DebugConsole.NewMessage($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", Color.Red);
maxPlayersElement = NetConfig.MaxPlayers;
}
info.MaxPlayers = maxPlayersElement;
if (Enum.TryParse(element.GetAttributeString("PlayStyle", ""), out PlayStyle playStyleTemp)) { info.PlayStyle = playStyleTemp; } if (Enum.TryParse(element.GetAttributeString("PlayStyle", ""), out PlayStyle playStyleTemp)) { info.PlayStyle = playStyleTemp; }
if (bool.TryParse(element.GetAttributeString("UsingWhiteList", ""), out bool whitelistTemp)) { info.UsingWhiteList = whitelistTemp; } if (bool.TryParse(element.GetAttributeString("UsingWhiteList", ""), out bool whitelistTemp)) { info.UsingWhiteList = whitelistTemp; }
@@ -306,7 +306,7 @@ namespace Barotrauma.CharacterEditor
var lastJoint = selectedJoints.LastOrDefault(); var lastJoint = selectedJoints.LastOrDefault();
if (lastJoint != null) if (lastJoint != null)
{ {
lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbA : lastJoint.LimbB; lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbB : lastJoint.LimbA;
} }
} }
if (lastLimb != null) if (lastLimb != null)
@@ -941,7 +941,7 @@ namespace Barotrauma.CharacterEditor
var lastJoint = selectedJoints.LastOrDefault(); var lastJoint = selectedJoints.LastOrDefault();
if (lastJoint != null) if (lastJoint != null)
{ {
lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbA : lastJoint.LimbB; lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbB : lastJoint.LimbA;
} }
} }
if (lastLimb != null) if (lastLimb != null)
@@ -2389,7 +2389,7 @@ namespace Barotrauma.CharacterEditor
IEnumerable<Limb> limbs = selectedLimbs; IEnumerable<Limb> limbs = selectedLimbs;
if (limbs.None()) if (limbs.None())
{ {
limbs = selectedJoints.Select(j => PlayerInput.KeyDown(Keys.LeftAlt) ? j.LimbA : j.LimbB); limbs = selectedJoints.Select(j => PlayerInput.KeyDown(Keys.LeftAlt) ? j.LimbB : j.LimbA);
} }
foreach (var limb in limbs) foreach (var limb in limbs)
{ {
@@ -4452,11 +4452,11 @@ namespace Barotrauma.CharacterEditor
} }
if (editJoints) if (editJoints)
{ {
if (!altDown && joint.BodyA == limb.body.FarseerBody) if (altDown && joint.BodyA == limb.body.FarseerBody)
{ {
continue; continue;
} }
if (altDown && joint.BodyB == limb.body.FarseerBody) if (!altDown && joint.BodyB == limb.body.FarseerBody)
{ {
continue; continue;
} }
@@ -1104,7 +1104,16 @@ namespace Barotrauma
{ {
port = settingsDoc.Root.GetAttributeInt("port", port); port = settingsDoc.Root.GetAttributeInt("port", port);
queryPort = settingsDoc.Root.GetAttributeInt("queryport", queryPort); queryPort = settingsDoc.Root.GetAttributeInt("queryport", queryPort);
maxPlayers = settingsDoc.Root.GetAttributeInt("maxplayers", maxPlayers);
int maxPlayersElement = settingsDoc.Root.GetAttributeInt("maxplayers", maxPlayers);
if (maxPlayersElement > NetConfig.MaxPlayers)
{
DebugConsole.IsOpen = true;
DebugConsole.NewMessage($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", Color.Red);
maxPlayersElement = NetConfig.MaxPlayers;
}
maxPlayers = maxPlayersElement;
karmaEnabled = settingsDoc.Root.GetAttributeBool("karmaenabled", true); karmaEnabled = settingsDoc.Root.GetAttributeBool("karmaenabled", true);
selectedKarmaPreset = settingsDoc.Root.GetAttributeString("karmapreset", "default"); selectedKarmaPreset = settingsDoc.Root.GetAttributeString("karmapreset", "default");
string playStyleStr = settingsDoc.Root.GetAttributeString("playstyle", "Casual"); string playStyleStr = settingsDoc.Root.GetAttributeString("playstyle", "Casual");
@@ -4375,9 +4375,18 @@ namespace Barotrauma
Submarine.DrawFront(spriteBatch, editing: true, e => ShowThalamus || !(e.prefab?.Category.HasFlag(MapEntityCategory.Thalamus) ?? false)); Submarine.DrawFront(spriteBatch, editing: true, e => ShowThalamus || !(e.prefab?.Category.HasFlag(MapEntityCategory.Thalamus) ?? false));
if (!WiringMode && !IsMouseOnEditorGUI()) if (!WiringMode && !IsMouseOnEditorGUI())
{ {
MapEntityPrefab.Selected?.DrawPlacing(spriteBatch, cam); MapEntityPrefab.Selected?.DrawPlacing(spriteBatch, cam);
MapEntity.DrawSelecting(spriteBatch, cam); MapEntity.DrawSelecting(spriteBatch, cam);
} }
if (dummyCharacter != null && WiringMode)
{
for (int i = 0; i < dummyCharacter.SelectedItems.Length; i++)
{
if (dummyCharacter.SelectedItems[i] == null) { continue; }
if (i > 0 && dummyCharacter.SelectedItems[0] == dummyCharacter.SelectedItems[i]) { continue; }
dummyCharacter.SelectedItems[i].Draw(spriteBatch, editing: false, back: true);
}
}
spriteBatch.End(); spriteBatch.End();
if (GameMain.LightManager.LightingEnabled && lightingEnabled) if (GameMain.LightManager.LightingEnabled && lightingEnabled)
@@ -74,7 +74,7 @@ namespace Barotrauma
//ambience //ambience
private static Sound waterAmbienceIn, waterAmbienceOut, waterAmbienceMoving; private static Sound waterAmbienceIn, waterAmbienceOut, waterAmbienceMoving;
private static SoundChannel[] waterAmbienceChannels = new SoundChannel[3]; private static readonly SoundChannel[] waterAmbienceChannels = new SoundChannel[3];
private static float ambientSoundTimer; private static float ambientSoundTimer;
private static Vector2 ambientSoundInterval = new Vector2(20.0f, 40.0f); //x = min, y = max private static Vector2 ambientSoundInterval = new Vector2(20.0f, 40.0f); //x = min, y = max
@@ -85,6 +85,7 @@ namespace Barotrauma
private static Vector2 hullSoundInterval = new Vector2(45.0f, 90.0f); //x = min, y = max private static Vector2 hullSoundInterval = new Vector2(45.0f, 90.0f); //x = min, y = max
//misc //misc
private static float[] targetFlowLeft, targetFlowRight;
public static List<Sound> FlowSounds = new List<Sound>(); public static List<Sound> FlowSounds = new List<Sound>();
public static List<Sound> SplashSounds = new List<Sound>(); public static List<Sound> SplashSounds = new List<Sound>();
private static SoundChannel[] flowSoundChannels; private static SoundChannel[] flowSoundChannels;
@@ -109,6 +110,8 @@ namespace Barotrauma
private static Dictionary<GUISoundType, List<Sound>> guiSounds; private static Dictionary<GUISoundType, List<Sound>> guiSounds;
private static bool firstTimeInMainMenu = true;
private static Sound startUpSound; private static Sound startUpSound;
public static bool Initialized; public static bool Initialized;
@@ -335,11 +338,14 @@ namespace Barotrauma
else { miscSoundList.Add(new KeyValuePair<string, Sound>(g.Key, s)); } else { miscSoundList.Add(new KeyValuePair<string, Sound>(g.Key, s)); }
})); }));
flowSoundChannels?.ForEach(ch => ch?.Dispose());
flowSoundChannels = new SoundChannel[FlowSounds.Count]; flowSoundChannels = new SoundChannel[FlowSounds.Count];
flowVolumeLeft = new float[FlowSounds.Count]; flowVolumeLeft = new float[FlowSounds.Count];
flowVolumeRight = new float[FlowSounds.Count]; flowVolumeRight = new float[FlowSounds.Count];
targetFlowLeft = new float[FlowSounds.Count];
targetFlowRight = new float[FlowSounds.Count];
fireSoundChannels?.ForEach(ch => ch?.Dispose());
fireSoundChannels = new SoundChannel[fireSizes]; fireSoundChannels = new SoundChannel[fireSizes];
fireVolumeLeft = new float[fireSizes]; fireVolumeLeft = new float[fireSizes];
fireVolumeRight = new float[fireSizes]; fireVolumeRight = new float[fireSizes];
@@ -494,9 +500,6 @@ namespace Barotrauma
{ {
if (FlowSounds.Count == 0) { return; } if (FlowSounds.Count == 0) { return; }
float[] targetFlowLeft = new float[FlowSounds.Count];
float[] targetFlowRight = new float[FlowSounds.Count];
Vector2 listenerPos = new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y); Vector2 listenerPos = new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y);
foreach (Gap gap in Gap.GapList) foreach (Gap gap in Gap.GapList)
{ {
@@ -931,7 +934,9 @@ namespace Barotrauma
return "editor"; return "editor";
} }
if (Screen.Selected != GameMain.GameScreen) { return "menu"; } if (Screen.Selected != GameMain.GameScreen) { return firstTimeInMainMenu ? "menu" : "default"; }
firstTimeInMainMenu = false;
if (Character.Controlled != null) if (Character.Controlled != null)
@@ -973,12 +978,12 @@ namespace Barotrauma
float totalArea = 0.0f; float totalArea = 0.0f;
foreach (Hull hull in Hull.hullList) foreach (Hull hull in Hull.hullList)
{ {
if (hull.Submarine != targetSubmarine) continue; if (hull.Submarine != targetSubmarine) { continue; }
floodedArea += hull.WaterVolume; floodedArea += hull.WaterVolume;
totalArea += hull.Volume; totalArea += hull.Volume;
} }
if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) return "flooded"; if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) { return "flooded"; }
} }
float enemyDistThreshold = 5000.0f; float enemyDistThreshold = 5000.0f;
@@ -991,7 +996,7 @@ namespace Barotrauma
foreach (Character character in Character.CharacterList) foreach (Character character in Character.CharacterList)
{ {
if (character.IsDead || !character.Enabled) continue; if (character.IsDead || !character.Enabled) continue;
if (!(character.AIController is EnemyAIController enemyAI) || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) continue; if (!(character.AIController is EnemyAIController enemyAI) || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) { continue; }
if (targetSubmarine != null) if (targetSubmarine != null)
{ {
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.10.601.0</Version> <Version>0.10.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.10.601.0</Version> <Version>0.10.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.10.601.0</Version> <Version>0.10.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.10.601.0</Version> <Version>0.10.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.10.601.0</Version> <Version>0.10.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -478,7 +478,7 @@ namespace Barotrauma
msg.Write(Info == null); msg.Write(Info == null);
msg.Write(entityId); msg.Write(entityId);
msg.Write(SpeciesName); msg.Write(SpeciesName);
msg.Write(seed); msg.Write(Seed);
if (Removed) if (Removed)
{ {
@@ -1119,6 +1119,12 @@ namespace Barotrauma
} }
else else
{ {
if (maxPlayers > NetConfig.MaxPlayers)
{
NewMessage($"Setting the maximum amount of players to {maxPlayers} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.");
maxPlayers = NetConfig.MaxPlayers;
}
GameMain.Server.ServerSettings.MaxPlayers = maxPlayers; GameMain.Server.ServerSettings.MaxPlayers = maxPlayers;
NewMessage("Set the maximum player count to " + maxPlayers + "."); NewMessage("Set the maximum player count to " + maxPlayers + ".");
} }
@@ -1132,6 +1138,12 @@ namespace Barotrauma
} }
else else
{ {
if (maxPlayers > NetConfig.MaxPlayers)
{
GameMain.Server.SendConsoleMessage($"Setting the maximum amount of players to {maxPlayers} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", client);
maxPlayers = NetConfig.MaxPlayers;
}
GameMain.Server.ServerSettings.MaxPlayers = maxPlayers; GameMain.Server.ServerSettings.MaxPlayers = maxPlayers;
NewMessage(client.Name + " set the maximum player count to " + maxPlayers + "."); NewMessage(client.Name + " set the maximum player count to " + maxPlayers + ".");
GameMain.Server.SendConsoleMessage("Set the maximum player count to " + maxPlayers + ".", client); GameMain.Server.SendConsoleMessage("Set the maximum player count to " + maxPlayers + ".", client);
@@ -147,6 +147,14 @@ namespace Barotrauma
c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign))); c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign)));
} }
public void LoadPets()
{
if (petsElement != null)
{
PetBehavior.LoadPets(petsElement);
}
}
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults) protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults)
{ {
lastUpdateID++; lastUpdateID++;
@@ -229,6 +237,9 @@ namespace Barotrauma
c.Inventory.DeleteAllItems(); c.Inventory.DeleteAllItems();
} }
petsElement = new XElement("pets");
PetBehavior.SavePets(petsElement);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
@@ -768,6 +779,11 @@ namespace Barotrauma
CargoManager?.SavePurchasedItems(modeElement); CargoManager?.SavePurchasedItems(modeElement);
UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades); UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades);
if (petsElement != null)
{
modeElement.Add(petsElement);
}
// save bots // save bots
CrewManager.SaveMultiplayer(modeElement); CrewManager.SaveMultiplayer(modeElement);
@@ -128,28 +128,8 @@ namespace Barotrauma
//used when clients use the water/fire console commands or section / decal updates are received //used when clients use the water/fire console commands or section / decal updates are received
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c) public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{ {
bool hasExtraData = msg.ReadBoolean(); int messageType = msg.ReadRangedInteger(0, 2);
if (hasExtraData) if (messageType == 0)
{
int sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1);
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8);
Color color = new Color(msg.ReadUInt32());
//TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches
if (c.Character != null && c.Character.AllowInput && c.Character.SelectedItems.Any(it => it?.GetComponent<Sprayer>() != null))
{
BackgroundSections[i].SetColorStrength(colorStrength);
BackgroundSections[i].SetColor(color);
}
}
//add to pending updates to notify other clients as well
pendingSectionUpdates.Add(sectorToUpdate);
}
else
{ {
float newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; float newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
@@ -205,6 +185,37 @@ namespace Barotrauma
FireSources.RemoveAt(i); FireSources.RemoveAt(i);
} }
} }
}
else if (messageType == 1)
{
byte decalIndex = msg.ReadByte();
float decalAlpha = msg.ReadRangedSingle(0.0f, 1.0f, 255);
if (decalIndex < 0 || decalIndex >= decals.Count) { return; }
if (c.Character != null && c.Character.AllowInput && c.Character.SelectedItems.Any(it => it?.GetComponent<Sprayer>() != null))
{
decals[decalIndex].BaseAlpha = decalAlpha;
}
decalUpdatePending = true;
}
else
{
int sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1);
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8);
Color color = new Color(msg.ReadUInt32());
//TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches
if (c.Character != null && c.Character.AllowInput && c.Character.SelectedItems.Any(it => it?.GetComponent<Sprayer>() != null))
{
BackgroundSections[i].SetColorStrength(colorStrength);
BackgroundSections[i].SetColor(color);
}
}
//add to pending updates to notify other clients as well
pendingSectionUpdates.Add(sectorToUpdate);
} }
} }
} }
@@ -1574,8 +1574,9 @@ namespace Barotrauma.Networking
{ {
//if docked to a sub with a smaller ID, don't send an update //if docked to a sub with a smaller ID, don't send an update
// (= update is only sent for the docked sub that has the smallest ID, doesn't matter if it's the main sub or a shuttle) // (= update is only sent for the docked sub that has the smallest ID, doesn't matter if it's the main sub or a shuttle)
if (sub.Info.IsOutpost || sub.DockedTo.Any(s => s.ID < sub.ID)) continue; if (sub.Info.IsOutpost || sub.DockedTo.Any(s => s.ID < sub.ID)) { continue; }
if (!c.PendingPositionUpdates.Contains(sub)) c.PendingPositionUpdates.Enqueue(sub); if (sub.PhysicsBody == null || sub.PhysicsBody.BodyType == FarseerPhysics.BodyType.Static) { continue; }
if (!c.PendingPositionUpdates.Contains(sub)) { c.PendingPositionUpdates.Enqueue(sub); }
} }
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
@@ -2291,6 +2292,8 @@ namespace Barotrauma.Networking
crewManager?.InitRound(); crewManager?.InitRound();
} }
campaign?.LoadPets();
foreach (Submarine sub in Submarine.MainSubs) foreach (Submarine sub in Submarine.MainSubs)
{ {
if (sub == null) continue; if (sub == null) continue;
@@ -2359,7 +2362,7 @@ namespace Barotrauma.Networking
bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn);
bool outpostAllowRespawn = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; bool outpostAllowRespawn = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost;
msg.Write(missionAllowRespawn || outpostAllowRespawn); msg.Write(serverSettings.AllowRespawn && (missionAllowRespawn || outpostAllowRespawn));
msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring); msg.Write(serverSettings.AllowRewiring);
msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.AllowRagdollButton);
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.10.601.0</Version> <Version>0.10.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -59,6 +59,7 @@
<Item file="Content/Items/Gardening/growableplants.xml" /> <Item file="Content/Items/Gardening/growableplants.xml" />
<Item file="Content/Items/Gardening/gardeningtools.xml" /> <Item file="Content/Items/Gardening/gardeningtools.xml" />
<Item file="Content/Items/Gardening/plantproducts.xml" /> <Item file="Content/Items/Gardening/plantproducts.xml" />
<Character file="Content/Characters/Balloon/Balloon.xml" />
<Character file="Content/Characters/Carrier/Carrier.xml" /> <Character file="Content/Characters/Carrier/Carrier.xml" />
<Character file="Content/Characters/Charybdis/Charybdis.xml" /> <Character file="Content/Characters/Charybdis/Charybdis.xml" />
<Character file="Content/Characters/Coelanth/Coelanth.xml" /> <Character file="Content/Characters/Coelanth/Coelanth.xml" />
@@ -92,6 +93,7 @@
<Character file="Content/Characters/Molochbaby/Molochbaby.xml" /> <Character file="Content/Characters/Molochbaby/Molochbaby.xml" />
<Character file="Content/Characters/Peanut/Peanut.xml" /> <Character file="Content/Characters/Peanut/Peanut.xml" />
<Character file="Content/Characters/Psilotoad/Psilotoad.xml" /> <Character file="Content/Characters/Psilotoad/Psilotoad.xml" />
<Character file="Content/Characters/Orangeboy/Orangeboy.xml" />
<Character file="Content/Characters/Tigerthresher/Tigerthresher.xml" /> <Character file="Content/Characters/Tigerthresher/Tigerthresher.xml" />
<Character file="Content/Characters/Bonethresher/Bonethresher.xml" /> <Character file="Content/Characters/Bonethresher/Bonethresher.xml" />
<Character file="Content/Characters/Leucocyte/Leucocyte.xml" /> <Character file="Content/Characters/Leucocyte/Leucocyte.xml" />
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze } public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze, Follow }
abstract partial class AIController : ISteerable abstract partial class AIController : ISteerable
{ {
@@ -48,13 +48,11 @@ namespace Barotrauma
private float attackLimbResetTimer; private float attackLimbResetTimer;
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0; private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0;
public float CombatStrength => Character.Params.AI.CombatStrength; public float CombatStrength => AIParams.CombatStrength;
private float Sight => Character.Params.AI.Sight; private float Sight => AIParams.Sight;
private float Hearing => Character.Params.AI.Hearing; private float Hearing => AIParams.Hearing;
private float FleeHealthThreshold => Character.Params.AI.FleeHealthThreshold; private float FleeHealthThreshold => AIParams.FleeHealthThreshold;
private float AggressionGreed => Character.Params.AI.AggressionGreed; private bool AggressiveBoarding => AIParams.AggressiveBoarding;
private float AggressionHurt => Character.Params.AI.AggressionHurt;
private bool AggressiveBoarding => Character.Params.AI.AggressiveBoarding;
private FishAnimController FishAnimController => Character.AnimController as FishAnimController; private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
@@ -90,7 +88,6 @@ namespace Barotrauma
private readonly float priorityFearIncreasement = 2; private readonly float priorityFearIncreasement = 2;
private readonly float memoryFadeTime = 0.5f; private readonly float memoryFadeTime = 0.5f;
private readonly float avoidTime = 3;
private float avoidTimer; private float avoidTimer;
private float observeTimer; private float observeTimer;
@@ -241,6 +238,10 @@ namespace Barotrauma
{ {
targetingTag = "dead"; targetingTag = "dead";
} }
else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner)
{
targetingTag = "owner";
}
else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP)) else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP))
{ {
targetingTag = tP.Tag; targetingTag = tP.Tag;
@@ -384,7 +385,7 @@ namespace Barotrauma
{ {
updateTargetsTimer -= deltaTime; updateTargetsTimer -= deltaTime;
} }
else else if (avoidTimer <= 0)
{ {
CharacterParams.TargetParams targetingParams = null; CharacterParams.TargetParams targetingParams = null;
UpdateTargets(Character, out targetingParams); UpdateTargets(Character, out targetingParams);
@@ -393,11 +394,7 @@ namespace Barotrauma
UpdateWallTarget(); UpdateWallTarget();
} }
updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f); updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f);
if (avoidTimer > 0) if (SelectedAiTarget == null)
{
State = AIState.Escape;
}
else if (SelectedAiTarget == null)
{ {
State = AIState.Idle; State = AIState.Idle;
} }
@@ -502,17 +499,21 @@ namespace Barotrauma
} }
break; break;
case AIState.Protect: case AIState.Protect:
case AIState.Follow:
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed) if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
{ {
State = AIState.Idle; State = AIState.Idle;
return; return;
} }
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead) if (State == AIState.Protect)
{ {
// Attack the character that attacked the target we are protecting if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead)
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2); {
SelectTarget(attacker.AiTarget); // Attack the character that attacked the target we are protecting
return; ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
SelectTarget(attacker.AiTarget);
return;
}
} }
float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition); float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget); float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
@@ -568,6 +569,7 @@ namespace Barotrauma
} }
else else
{ {
// TODO: doesn't work right here
FaceTarget(SelectedAiTarget.Entity); FaceTarget(SelectedAiTarget.Entity);
} }
observeTimer -= deltaTime; observeTimer -= deltaTime;
@@ -592,7 +594,6 @@ namespace Barotrauma
if (!Character.AnimController.SimplePhysicsEnabled) if (!Character.AnimController.SimplePhysicsEnabled)
{ {
LatchOntoAI?.Update(this, deltaTime); LatchOntoAI?.Update(this, deltaTime);
PetBehavior?.Update(deltaTime);
} }
IsSteeringThroughGap = false; IsSteeringThroughGap = false;
if (SwarmBehavior != null) if (SwarmBehavior != null)
@@ -1454,7 +1455,8 @@ namespace Barotrauma
bool isFriendly = IsFriendly(Character, attacker); bool isFriendly = IsFriendly(Character, attacker);
if (wasLatched) if (wasLatched)
{ {
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f); State = AIState.Escape;
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
if (!isFriendly) if (!isFriendly)
{ {
SelectTarget(attacker.AiTarget); SelectTarget(attacker.AiTarget);
@@ -1527,7 +1529,7 @@ namespace Barotrauma
} }
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true); AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true);
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt; targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown // Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
bool retaliate = !isFriendly && SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine; bool retaliate = !isFriendly && SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine;
@@ -1552,12 +1554,14 @@ namespace Barotrauma
} }
else if (avoidGunFire) else if (avoidGunFire)
{ {
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f); State = AIState.Escape;
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
SelectTarget(attacker.AiTarget); SelectTarget(attacker.AiTarget);
} }
if (Character.HealthPercentage <= FleeHealthThreshold) if (Character.HealthPercentage <= FleeHealthThreshold)
{ {
avoidTimer = Rand.Range(15, 30); State = AIState.Flee;
avoidTimer = AIParams.MinFleeTime * Rand.Range(0.75f, 1.25f);
SelectTarget(attacker.AiTarget); SelectTarget(attacker.AiTarget);
} }
} }
@@ -1587,7 +1591,7 @@ namespace Barotrauma
if (damageTarget.Health > 0) if (damageTarget.Health > 0)
{ {
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AggressionGreed; selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed;
} }
else else
{ {
@@ -1654,10 +1658,13 @@ namespace Barotrauma
if (!item.Removed && item.body != null) if (!item.Removed && item.body != null)
{ {
float itemBodyExtent = item.body.GetMaxExtent() * 2; float itemBodyExtent = item.body.GetMaxExtent() * 2;
if (limbDiff.LengthSquared() < itemBodyExtent * itemBodyExtent) if (Math.Abs(limbDiff.X) < itemBodyExtent &&
Math.Abs(limbDiff.Y) < Character.AnimController.Collider.GetMaxExtent() + Character.AnimController.ColliderHeightFromFloor)
{ {
item.body.LinearVelocity *= 0.9f;
item.body.LinearVelocity -= limbDiff * 0.25f;
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime); item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime);
item.body.ApplyForce(-limbDiff * item.body.Mass);
if (item.Condition <= 0.0f) if (item.Condition <= 0.0f)
{ {
@@ -1697,15 +1704,40 @@ namespace Barotrauma
State = AIState.Idle; State = AIState.Idle;
return; return;
} }
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition); if (Character.CurrentHull != null && PathSteering != null)
if (!MathUtils.IsValid(dir))
{ {
return; // Inside
Character targetCharacter = SelectedAiTarget.Entity as Character;
if ((Character.AnimController.InWater || !Character.AnimController.CanWalk) &&
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity)))
{
// Steer towards the target if in the same room and swimming
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
if (MathUtils.IsValid(dir))
{
SteeringManager.SteeringManual(deltaTime, dir);
}
}
else
{
// Use path finding
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 2);
if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable)
{
// Can't reach
State = AIState.Idle;
return;
}
}
} }
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5); else
if (Character.AnimController.InWater)
{ {
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15); // Outside
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5);
if (Character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
} }
} }
@@ -1748,6 +1780,10 @@ namespace Barotrauma
{ {
targetingTag = "dead"; targetingTag = "dead";
} }
else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner)
{
targetingTag = "owner";
}
else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP)) else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP))
{ {
targetingTag = tP.Tag; targetingTag = tP.Tag;
@@ -1992,9 +2028,16 @@ namespace Barotrauma
if (targetingTag == null) { continue; } if (targetingTag == null) { continue; }
var targetParams = GetTargetParams(targetingTag); var targetParams = GetTargetParams(targetingTag);
if (targetParams == null) { continue; } if (targetParams == null) { continue; }
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine && targetParams.State == AIState.Observe) if (targetParams.State == AIState.Observe || targetParams.State == AIState.Eat)
{
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine)
{
// Don't allow to target characters that are inside a different submarine / outside when we are inside.
continue;
}
}
if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null)
{ {
// Don't allow to observe characters that are inside a different submarine / outside when we are inside.
continue; continue;
} }
valueModifier *= targetParams.Priority; valueModifier *= targetParams.Priority;
@@ -2066,6 +2109,17 @@ namespace Barotrauma
} }
if (targetCharacter != null) if (targetCharacter != null)
{ {
if (Character.CurrentHull != null && targetCharacter.CurrentHull != Character.CurrentHull)
{
if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe)
{
// Ignore targets that cannot see
if (!VisibleHulls.Contains(targetCharacter.CurrentHull))
{
continue;
}
}
}
if (targetCharacter.Submarine != Character.Submarine) if (targetCharacter.Submarine != Character.Submarine)
{ {
if (targetCharacter.Submarine != null) if (targetCharacter.Submarine != null)
@@ -1111,7 +1111,10 @@ namespace Barotrauma
public bool TakeItem(Item item, Inventory targetInventory, bool equip, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false) public bool TakeItem(Item item, Inventory targetInventory, bool equip, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
{ {
var pickable = item.GetComponent<Pickable>(); var pickable = item.GetComponent<Pickable>();
if (pickable == null) { return false; } if (item.ParentInventory is ItemInventory itemInventory)
{
if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; }
}
if (equip) if (equip)
{ {
int targetSlot = -1; int targetSlot = -1;
@@ -190,6 +190,7 @@ namespace Barotrauma
} }
} }
pathFinder.InsideSubmarine = character.Submarine != null; pathFinder.InsideSubmarine = character.Submarine != null;
pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0;
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility); var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1; bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1;
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
@@ -343,7 +344,7 @@ namespace Barotrauma
} }
return diff; return diff;
} }
else if (!canClimb || character.AnimController.InWater) else if (character.AnimController.InWater)
{ {
// If the character is underwater, we don't need the ladders anymore // If the character is underwater, we don't need the ladders anymore
if (character.IsClimbing && isDiving) if (character.IsClimbing && isDiving)
@@ -370,7 +371,7 @@ namespace Barotrauma
} }
} }
} }
else if (!IsNextLadderSameAsCurrent) else if (!canClimb || !IsNextLadderSameAsCurrent)
{ {
// Walking horizontally // Walking horizontally
Vector2 colliderBottom = character.AnimController.GetColliderBottom(); Vector2 colliderBottom = character.AnimController.GetColliderBottom();
@@ -378,6 +379,8 @@ namespace Barotrauma
Vector2 velocity = collider.LinearVelocity; Vector2 velocity = collider.LinearVelocity;
// If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high. // If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high.
float minHeight = 1; float minHeight = 1;
// If the character is very thin, without a min value, it would often fail to reach the waypoints, because the horizontal distance is too small.
float minWidth = 0.17f;
// Cannot use the head position, because not all characters have head or it can be below the total height of the character // Cannot use the head position, because not all characters have head or it can be below the total height of the character
float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight); float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight);
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
@@ -386,7 +389,7 @@ namespace Barotrauma
var door = currentPath.CurrentNode.ConnectedDoor; var door = currentPath.CurrentNode.ConnectedDoor;
bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken; bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken;
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1)); float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
float targetDistance = collider.radius * margin; float targetDistance = Math.Max(collider.radius * margin, minWidth);
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && !blockedByDoor) if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && !blockedByDoor)
{ {
currentPath.SkipToNextNode(); currentPath.SkipToNextNode();
@@ -204,11 +204,15 @@ namespace Barotrauma
{ {
// Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again. // Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again.
// If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub. // If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub.
if (currentHull != null && goToObjective != null) // If the target hull is inside a submarine that is not our main sub, just ignore it normally when it cannot be reached. This happens with outposts.
if (goToObjective != null)
{ {
if (goToObjective.Target is Hull hull) if (goToObjective.Target is Hull hull)
{ {
HumanAIController.UnreachableHulls.Add(hull); if (currentHull != null || !Submarine.MainSubs.Contains(hull.Submarine))
{
HumanAIController.UnreachableHulls.Add(hull);
}
} }
} }
RemoveSubObjective(ref goToObjective); RemoveSubObjective(ref goToObjective);
@@ -257,6 +257,10 @@ namespace Barotrauma
// Don't allow going into another sub, unless it's connected and of the same team and type. // Don't allow going into another sub, unless it's connected and of the same team and type.
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; } if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
if (character.IsItemTakenBySomeoneElse(item)) { continue; } if (character.IsItemTakenBySomeoneElse(item)) { continue; }
if (item.ParentInventory is ItemInventory itemInventory)
{
if (!itemInventory.Container.HasRequiredItems(character, addMessage: false)) { continue; }
}
float itemPriority = 1; float itemPriority = 1;
if (GetItemPriority != null) if (GetItemPriority != null)
{ {
@@ -342,7 +342,7 @@ namespace Barotrauma
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition)); SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater) if (character.AnimController.InWater)
{ {
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15); SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 2);
} }
} }
} }
@@ -360,7 +360,10 @@ namespace Barotrauma
else else
{ {
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10); SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15); if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
}
} }
} }
} }
@@ -94,6 +94,7 @@ namespace Barotrauma
private readonly List<PathNode> nodes; private readonly List<PathNode> nodes;
public bool InsideSubmarine { get; set; } public bool InsideSubmarine { get; set; }
public bool ApplyPenaltyToOutsideNodes { get; set; }
public PathFinder(List<WayPoint> wayPoints, bool indoorsSteering = false) public PathFinder(List<WayPoint> wayPoints, bool indoorsSteering = false)
{ {
@@ -178,7 +179,7 @@ namespace Barotrauma
node.TempDistance = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub node.TempDistance = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub
//much higher cost to waypoints that are outside //much higher cost to waypoints that are outside
if (node.Waypoint.CurrentHull == null && InsideSubmarine) { node.TempDistance *= 10.0f; } if (node.Waypoint.CurrentHull == null && ApplyPenaltyToOutsideNodes) { node.TempDistance *= 10.0f; }
//prefer nodes that are closer to the end position //prefer nodes that are closer to the end position
node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f; node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f;
@@ -231,8 +232,11 @@ namespace Barotrauma
node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition); node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition);
if (InsideSubmarine) if (InsideSubmarine)
{ {
//much higher cost to waypoints that are outside if (ApplyPenaltyToOutsideNodes)
if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; } {
//much higher cost to waypoints that are outside
if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; }
}
//avoid stopping at a doorway //avoid stopping at a doorway
if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; } if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; }
//avoid stopping at a ladder //avoid stopping at a ladder
@@ -1,3 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
@@ -9,6 +10,14 @@ namespace Barotrauma
{ {
class PetBehavior class PetBehavior
{ {
public enum StatusIndicatorType
{
None,
Happy,
Sad,
Hungry
}
public float Hunger { get; set; } = 50.0f; public float Hunger { get; set; } = 50.0f;
public float Happiness { get; set; } = 50.0f; public float Happiness { get; set; } = 50.0f;
@@ -21,6 +30,7 @@ namespace Barotrauma
public float PlayForce { get; set; } public float PlayForce { get; set; }
public float PlayTimer { get; set; } public float PlayTimer { get; set; }
private float? unstunY { get; set; }
public EnemyAIController AiController { get; private set; } = null; public EnemyAIController AiController { get; private set; } = null;
@@ -126,6 +136,7 @@ namespace Barotrauma
public float Hunger; public float Hunger;
public float Happiness; public float Happiness;
public float Priority; public float Priority;
public bool IgnoreContained;
public CharacterParams.TargetParams TargetParams = null; public CharacterParams.TargetParams TargetParams = null;
} }
@@ -159,7 +170,11 @@ namespace Barotrauma
case "eat": case "eat":
Food food = new Food Food food = new Food
{ {
Tag = subElement.GetAttributeString("tag", "") Tag = subElement.GetAttributeString("tag", ""),
Hunger = subElement.GetAttributeFloat("hunger", -1),
Happiness = subElement.GetAttributeFloat("happiness", 1),
Priority = subElement.GetAttributeFloat("priority", 100),
IgnoreContained = subElement.GetAttributeBool("ignorecontained", true)
}; };
string[] requiredHungerStr = subElement.GetAttributeString("requiredhunger", "0-100").Split('-'); string[] requiredHungerStr = subElement.GetAttributeString("requiredhunger", "0-100").Split('-');
food.HungerRange = new Vector2(0, 100); food.HungerRange = new Vector2(0, 100);
@@ -168,15 +183,20 @@ namespace Barotrauma
if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float tempF)) { food.HungerRange.X = tempF; } if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float tempF)) { food.HungerRange.X = tempF; }
if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { food.HungerRange.Y = tempF; } if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { food.HungerRange.Y = tempF; }
} }
food.Hunger = subElement.GetAttributeFloat("hunger", -1);
food.Happiness = subElement.GetAttributeFloat("happiness", 1);
food.Priority = subElement.GetAttributeFloat("priority", 100);
foods.Add(food); foods.Add(food);
break; break;
} }
} }
} }
public StatusIndicatorType GetCurrentStatusIndicatorType()
{
if (Hunger > MaxHunger * 0.5f) { return StatusIndicatorType.Hungry; }
if (Happiness > MaxHappiness * 0.75f) { return StatusIndicatorType.Happy; }
if (Happiness < MaxHappiness * 0.25f) { return StatusIndicatorType.Sad; }
return StatusIndicatorType.None;
}
public void OnEat(IEnumerable<string> tags, float amount) public void OnEat(IEnumerable<string> tags, float amount)
{ {
for (int i = 0; i < foods.Count; i++) for (int i = 0; i < foods.Count; i++)
@@ -203,17 +223,19 @@ namespace Barotrauma
} }
} }
public void Play() public void Play(Character player)
{ {
if (PlayTimer > 0.0f) { return; } if (PlayTimer > 0.0f) { return; }
if (Owner == null) { Owner = player; }
PlayTimer = 5.0f; PlayTimer = 5.0f;
AiController.Character.Stun = 1.0f; AiController.Character.IsRagdolled = true;
Happiness += 10.0f; Happiness += 10.0f;
if (Happiness > MaxHappiness) { Happiness = MaxHappiness; } if (Happiness > MaxHappiness) { Happiness = MaxHappiness; }
AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce); AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce);
unstunY = AiController.Character.SimPosition.Y;
} }
public string GetName() public string GetTagName()
{ {
if (AiController.Character.Inventory != null) if (AiController.Character.Inventory != null)
{ {
@@ -230,14 +252,16 @@ namespace Barotrauma
} }
} }
return AiController.Character.Name; return string.Empty;
} }
public void Update(float deltaTime) public void Update(float deltaTime)
{ {
var character = AiController.Character; var character = AiController.Character;
if (character?.Removed ?? true || character.IsDead) { return; } if (character?.Removed ?? true || character.IsDead) { return; }
if (GameMain.NetworkMember?.IsClient ?? false) { return; } //TODO: syncing if (GameMain.NetworkMember?.IsClient ?? false) { return; }
if (Owner != null && (Owner.Removed || Owner.IsDead)) { Owner = null; }
Hunger += HungerIncreaseRate * deltaTime; Hunger += HungerIncreaseRate * deltaTime;
@@ -245,20 +269,45 @@ namespace Barotrauma
PlayTimer -= deltaTime; PlayTimer -= deltaTime;
for (int i = 0; i < foods.Count; i++) if (unstunY.HasValue)
{ {
if (Hunger >= foods[i].HungerRange.X && Hunger <= foods[i].HungerRange.Y) if (PlayTimer > 4.0f)
{ {
if (foods[i].TargetParams == null && float extent = character.AnimController.MainLimb.body.GetMaxExtent();
AiController.AIParams.TryAddNewTarget(foods[i].Tag, AIState.Eat, foods[i].Priority, out CharacterParams.TargetParams targetParams)) if (character.SimPosition.Y < (unstunY.Value + extent * 3.0f) &&
character.AnimController.MainLimb.body.LinearVelocity.Y < 0.0f)
{ {
foods[i].TargetParams = targetParams; character.IsRagdolled = false;
unstunY = null;
}
else
{
character.IsRagdolled = true;
} }
} }
else if (foods[i].TargetParams != null) else
{ {
AiController.AIParams.RemoveTarget(foods[i].TargetParams); character.IsRagdolled = false;
foods[i].TargetParams = null; unstunY = null;
}
}
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
if (Hunger >= food.HungerRange.X && Hunger <= food.HungerRange.Y)
{
if (food.TargetParams == null &&
AiController.AIParams.TryAddNewTarget(food.Tag, AIState.Eat, food.Priority, out CharacterParams.TargetParams targetParams))
{
targetParams.IgnoreContained = food.IgnoreContained;
food.TargetParams = targetParams;
}
}
else if (food.TargetParams != null)
{
AiController.AIParams.RemoveTarget(food.TargetParams);
food.TargetParams = null;
} }
} }
@@ -279,7 +328,8 @@ namespace Barotrauma
if (character.SelectedBy != null) if (character.SelectedBy != null)
{ {
character.Stun = 1.0f; character.IsRagdolled = true;
unstunY = character.SimPosition.Y;
} }
for (int i = 0; i < itemsToProduce.Count; i++) for (int i = 0; i < itemsToProduce.Count; i++)
@@ -287,5 +337,73 @@ namespace Barotrauma
itemsToProduce[i].Update(this, deltaTime); itemsToProduce[i].Update(this, deltaTime);
} }
} }
public static void SavePets(XElement petsElement)
{
foreach (Character c in Character.CharacterList)
{
if (!c.IsPet || c.IsDead) { continue; }
if (c.Submarine?.Info.Type != SubmarineType.Player) { continue; }
var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior;
if (petBehavior == null) { continue; }
XElement petElement = new XElement("pet",
new XAttribute("speciesname", c.SpeciesName),
new XAttribute("ownerid", petBehavior.Owner?.ID ?? Entity.NullEntityID),
new XAttribute("seed", c.Seed));
var petBehaviorElement = new XElement("petbehavior",
new XAttribute("hunger", petBehavior.Hunger.ToString("G", CultureInfo.InvariantCulture)),
new XAttribute("happiness", petBehavior.Happiness.ToString("G", CultureInfo.InvariantCulture)));
petElement.Add(petBehaviorElement);
var healthElement = new XElement("health");
c.CharacterHealth.Save(healthElement);
petElement.Add(healthElement);
if (c.Inventory != null)
{
var inventoryElement = new XElement("inventory");
c.SaveInventory(c.Inventory, inventoryElement);
petElement.Add(inventoryElement);
}
petsElement.Add(petElement);
}
}
public static void LoadPets(XElement petsElement)
{
foreach (XElement subElement in petsElement.Elements())
{
string speciesName = subElement.GetAttributeString("speciesname", "");
string seed = subElement.GetAttributeString("seed", "123");
ushort ownerID = (ushort)subElement.GetAttributeInt("ownerid", 0);
Vector2 spawnPos = Vector2.Zero;
Character owner = Entity.FindEntityByID(ownerID) as Character;
if (owner != null)
{
spawnPos = owner.WorldPosition;
}
else
{
var spawnPoint = WayPoint.WayPointList.Where(wp => wp.SpawnType == SpawnType.Human && wp.Submarine?.Info.Type == SubmarineType.Player).GetRandom();
spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition;
}
var pet = Character.Create(speciesName, spawnPos, seed);
var petBehavior = (pet.AIController as EnemyAIController)?.PetBehavior;
if (petBehavior != null)
{
petBehavior.Owner = owner;
var petBehaviorElement = subElement.Attribute("petbehavior");
if (petBehaviorElement != null)
{
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat(50.0f);
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat(50.0f);
}
}
}
}
} }
} }
@@ -47,6 +47,10 @@ namespace Barotrauma
base.Update(deltaTime, cam); base.Update(deltaTime, cam);
if (!Enabled) { return; } if (!Enabled) { return; }
if (!IsRemotePlayer && AIController is EnemyAIController enemyAi)
{
enemyAi.PetBehavior?.Update(deltaTime);
}
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated) if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated)
{ {
//don't enable simple physics on dead/incapacitated characters //don't enable simple physics on dead/incapacitated characters
@@ -429,113 +429,195 @@ namespace Barotrauma
{ {
WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5); WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
mainLimb.PullJointWorldAnchorB = Collider.SimPosition; mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
return;
}
Vector2 transformedMovement = reverse ? -movement : movement;
float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
float mainLimbAngle = 0;
if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{
mainLimbAngle = TorsoAngle.Value;
}
else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
mainLimbAngle = HeadAngle.Value;
}
mainLimbAngle *= Dir;
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
{
movementAngle += MathHelper.TwoPi;
}
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
{
movementAngle -= MathHelper.TwoPi;
}
if (CurrentSwimParams.RotateTowardsMovement)
{
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
if (TorsoAngle.HasValue)
{
Limb torso = GetLimb(LimbType.Torso);
if (torso != null)
{
SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
}
}
if (HeadAngle.HasValue)
{
Limb head = GetLimb(LimbType.Head);
if (head != null)
{
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
}
}
if (TailAngle.HasValue)
{
Limb tail = GetLimb(LimbType.Tail);
if (tail != null)
{
float? mainLimbTargetAngle = null;
if (mainLimb.type == LimbType.Torso)
{
mainLimbTargetAngle = TorsoAngle;
}
else if (mainLimb.type == LimbType.Head)
{
mainLimbTargetAngle = HeadAngle;
}
float torque = TailTorque;
float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
{
float diff = Math.Abs(mainLimb.Rotation - tail.Rotation);
float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
}
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
}
}
} }
else else
{ {
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; Vector2 transformedMovement = reverse ? -movement : movement;
if (reverse) float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
float mainLimbAngle = 0;
if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{ {
movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi); mainLimbAngle = TorsoAngle.Value;
} }
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
{ {
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); mainLimbAngle = HeadAngle.Value;
} }
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) mainLimbAngle *= Dir;
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
{ {
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); movementAngle += MathHelper.TwoPi;
} }
if (TorsoAngle.HasValue) while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
{ {
Limb torso = GetLimb(LimbType.Torso); movementAngle -= MathHelper.TwoPi;
torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
} }
if (HeadAngle.HasValue) if (CurrentSwimParams.RotateTowardsMovement)
{ {
Limb head = GetLimb(LimbType.Head); Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque); if (TorsoAngle.HasValue)
} {
if (TailAngle.HasValue) Limb torso = GetLimb(LimbType.Torso);
{ if (torso != null)
Limb tail = GetLimb(LimbType.Tail); {
tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque); SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
} }
} }
if (HeadAngle.HasValue)
{
Limb head = GetLimb(LimbType.Head);
if (head != null)
{
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
}
}
if (TailAngle.HasValue)
{
bool isAngleApplied = false;
foreach (var limb in Limbs)
{
if (limb.IsSevered) { continue; }
if (limb.type != LimbType.Tail) { continue; }
if (!limb.Params.ApplyTailAngle) { continue; }
RotateTail(limb);
isAngleApplied = true;
}
if (!isAngleApplied)
{
RotateTail(GetLimb(LimbType.Tail));
}
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale); void RotateTail(Limb tail)
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier); {
if (waveLength > 0 && waveAmplitude > 0) if (tail == null) { return; }
{ float? mainLimbTargetAngle = null;
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); if (mainLimb.type == LimbType.Torso)
WalkPos = MathUtils.WrapAngleTwoPi(WalkPos); {
mainLimbTargetAngle = TorsoAngle;
}
else if (mainLimb.type == LimbType.Head)
{
mainLimbTargetAngle = HeadAngle;
}
float torque = TailTorque;
float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
{
float diff = Math.Abs(mainLimb.Rotation - tail.Rotation);
float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
}
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
}
}
}
else
{
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
if (reverse)
{
movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
}
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
if (TorsoAngle.HasValue)
{
Limb torso = GetLimb(LimbType.Torso);
torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
}
if (HeadAngle.HasValue)
{
Limb head = GetLimb(LimbType.Head);
head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
}
if (TailAngle.HasValue)
{
bool isAngleApplied = false;
foreach (var limb in Limbs)
{
if (limb.IsSevered) { continue; }
if (limb.type != LimbType.Tail) { continue; }
if (!limb.Params.ApplyTailAngle) { continue; }
RotateTail(limb);
isAngleApplied = true;
}
if (!isAngleApplied)
{
RotateTail(GetLimb(LimbType.Tail));
}
void RotateTail(Limb tail)
{
if (tail != null)
{
tail.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
}
}
}
}
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
if (waveLength > 0 && waveAmplitude > 0)
{
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
}
foreach (var limb in Limbs)
{
if (limb.IsSevered) { continue; }
switch (limb.type)
{
case LimbType.LeftFoot:
case LimbType.RightFoot:
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
{
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
}
break;
case LimbType.Tail:
if (waveLength > 0 && waveAmplitude > 0)
{
float waveRotation = (float)Math.Sin(WalkPos * limb.Params.SineFrequencyMultiplier);
limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude * limb.Params.SineAmplitudeMultiplier);
}
break;
}
}
for (int i = 0; i < Limbs.Length; i++)
{
var limb = Limbs[i];
if (limb.IsSevered) { continue; }
if (limb.SteerForce <= 0.0f) { continue; }
if (!Collider.PhysEnabled) { continue; }
Vector2 pullPos = limb.PullJointWorldAnchorA;
limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
}
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
if (CurrentSwimParams.UseSineMovement)
{
mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
mainLimb.PullJointWorldAnchorB,
Collider.SimPosition,
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
}
else
{
//mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
mainLimb.PullJointWorldAnchorB,
Collider.SimPosition,
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
}
} }
foreach (var limb in Limbs) foreach (var limb in Limbs)
@@ -543,55 +625,15 @@ namespace Barotrauma
if (limb.IsSevered) { continue; } if (limb.IsSevered) { continue; }
if (Math.Abs(limb.Params.ConstantTorque) > 0) if (Math.Abs(limb.Params.ConstantTorque) > 0)
{ {
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true); limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
} }
switch (limb.type) if (limb.Params.BlinkFrequency > 0)
{ {
case LimbType.LeftFoot: limb.Blink(deltaTime, MainLimb.Rotation);
case LimbType.RightFoot:
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
{
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
}
break;
case LimbType.Tail:
if (waveLength > 0 && waveAmplitude > 0)
{
float waveRotation = (float)Math.Sin(WalkPos);
limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude);
}
break;
} }
} }
for (int i = 0; i < Limbs.Length; i++) floorY = Limbs[0].SimPosition.Y;
{
var limb = Limbs[i];
if (limb.IsSevered) { continue; }
if (limb.SteerForce <= 0.0f) { continue; }
if (!Collider.PhysEnabled) { continue; }
Vector2 pullPos = limb.PullJointWorldAnchorA;
limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
}
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
if (CurrentSwimParams.UseSineMovement)
{
mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
mainLimb.PullJointWorldAnchorB,
Collider.SimPosition,
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
}
else
{
//mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
mainLimb.PullJointWorldAnchorB,
Collider.SimPosition,
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
}
floorY = Limbs[0].SimPosition.Y;
} }
void UpdateWalkAnim(float deltaTime) void UpdateWalkAnim(float deltaTime)
@@ -686,10 +728,26 @@ namespace Barotrauma
if (TailAngle.HasValue) if (TailAngle.HasValue)
{ {
var tail = GetLimb(LimbType.Tail); bool isAngleApplied = false;
if (tail != null) foreach (var limb in Limbs)
{ {
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque); if (limb.IsSevered) { continue; }
if (limb.type != LimbType.Tail) { continue; }
if (!limb.Params.ApplyTailAngle) { continue; }
RotateTail(limb);
isAngleApplied = true;
}
if (!isAngleApplied)
{
RotateTail(GetLimb(LimbType.Tail));
}
void RotateTail(Limb tail)
{
if (tail != null)
{
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque);
}
} }
} }
@@ -709,7 +767,11 @@ namespace Barotrauma
if (limb.IsSevered) { continue; } if (limb.IsSevered) { continue; }
if (Math.Abs(limb.Params.ConstantTorque) > 0) if (Math.Abs(limb.Params.ConstantTorque) > 0)
{ {
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true); limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
}
if (limb.Params.BlinkFrequency > 0)
{
limb.Blink(deltaTime, MainLimb.Rotation);
} }
switch (limb.type) switch (limb.type)
{ {
@@ -145,7 +145,7 @@ namespace Barotrauma
private set; private set;
} }
private LimbJoint shoulder; private LimbJoint rightShoulder, leftShoulder;
private float upperLegLength = 0.0f, lowerLegLength = 0.0f; private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
@@ -241,12 +241,13 @@ namespace Barotrauma
Limb rightHand = GetLimb(LimbType.RightHand); Limb rightHand = GetLimb(LimbType.RightHand);
if (rightHand == null) { return; } if (rightHand == null) { return; }
shoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm); rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm);
leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm);
Vector2 localAnchorShoulder = Vector2.Zero; Vector2 localAnchorShoulder = Vector2.Zero;
Vector2 localAnchorElbow = Vector2.Zero; Vector2 localAnchorElbow = Vector2.Zero;
if (shoulder != null) if (rightShoulder != null)
{ {
localAnchorShoulder = shoulder.LimbA.type == LimbType.RightArm ? shoulder.LocalAnchorA : shoulder.LocalAnchorB; localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB;
} }
LimbJoint rightElbow = rightForearm == null ? LimbJoint rightElbow = rightForearm == null ?
GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) : GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) :
@@ -1612,7 +1613,7 @@ namespace Barotrauma
pullLimb.PullJointMaxForce = 5000.0f; pullLimb.PullJointMaxForce = 5000.0f;
targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f); targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
Vector2 shoulderPos = shoulder.WorldAnchorA; Vector2 shoulderPos = rightShoulder.WorldAnchorA;
Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY; Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY;
targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength); targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength);
@@ -1757,10 +1758,10 @@ namespace Barotrauma
} }
else else
{ {
itemAngle = (torso.body.Rotation + holdAngle * Dir); itemAngle = torso.body.Rotation + holdAngle * Dir;
} }
Vector2 transformedHoldPos = shoulder.WorldAnchorA; Vector2 transformedHoldPos = rightShoulder.WorldAnchorA;
if (itemPos == Vector2.Zero || isClimbing || usingController) if (itemPos == Vector2.Zero || isClimbing || usingController)
{ {
if (character.SelectedItems[0] == item) if (character.SelectedItems[0] == item)
@@ -1781,15 +1782,17 @@ namespace Barotrauma
if (character.SelectedItems[0] == item) if (character.SelectedItems[0] == item)
{ {
if (rightHand == null || rightHand.IsSevered) { return; } if (rightHand == null || rightHand.IsSevered) { return; }
transformedHoldPos = rightShoulder.WorldAnchorA;
rightHand.Disabled = true; rightHand.Disabled = true;
} }
if (character.SelectedItems[1] == item) if (character.SelectedItems[1] == item)
{ {
if (leftHand == null || leftHand.IsSevered) { return; } if (leftHand == null || leftHand.IsSevered) { return; }
transformedHoldPos = leftShoulder.WorldAnchorA;
leftHand.Disabled = true; leftHand.Disabled = true;
} }
itemPos.X = itemPos.X * Dir; itemPos.X *= Dir;
transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle)); transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle));
} }
@@ -1858,18 +1861,21 @@ namespace Barotrauma
private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) private void HandIK(Limb hand, Vector2 pos, float force = 1.0f)
{ {
if (shoulder == null) { return; } Vector2 shoulderPos;
Vector2 shoulderPos = shoulder.WorldAnchorA;
Limb arm, forearm; Limb arm, forearm;
if (hand.type == LimbType.LeftHand) if (hand.type == LimbType.LeftHand)
{ {
if (leftShoulder == null) { return; }
shoulderPos = leftShoulder.WorldAnchorA;
arm = GetLimb(LimbType.LeftArm); arm = GetLimb(LimbType.LeftArm);
forearm = GetLimb(LimbType.LeftForearm); forearm = GetLimb(LimbType.LeftForearm);
LeftHandIKPos = pos; LeftHandIKPos = pos;
} }
else else
{ {
if (rightShoulder == null) { return; }
shoulderPos = rightShoulder.WorldAnchorA;
arm = GetLimb(LimbType.RightArm); arm = GetLimb(LimbType.RightArm);
forearm = GetLimb(LimbType.RightForearm); forearm = GetLimb(LimbType.RightForearm);
RightHandIKPos = pos; RightHandIKPos = pos;
@@ -216,16 +216,18 @@ namespace Barotrauma
get get
{ {
Limb mainLimb = GetLimb(RagdollParams.MainLimb); Limb mainLimb = GetLimb(RagdollParams.MainLimb);
if (mainLimb == null) if (!IsValid(mainLimb))
{ {
Limb torso = GetLimb(LimbType.Torso); Limb torso = GetLimb(LimbType.Torso);
Limb head = GetLimb(LimbType.Head); Limb head = GetLimb(LimbType.Head);
mainLimb = torso ?? head; mainLimb = torso ?? head;
if (mainLimb == null) if (!IsValid(mainLimb))
{ {
mainLimb = Limbs.FirstOrDefault(l => !l.IsSevered && !l.ignoreCollisions); mainLimb = Limbs.FirstOrDefault(l => IsValid(l));
} }
} }
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.ignoreCollisions;
return mainLimb; return mainLimb;
} }
} }
@@ -753,7 +755,6 @@ namespace Barotrauma
foreach (Limb limb in Limbs) foreach (Limb limb in Limbs)
{ {
if (connectedLimbs.Contains(limb)) { continue; } if (connectedLimbs.Contains(limb)) { continue; }
if (!character.IsDead && !limb.CanBeSeveredAlive) { continue; }
limb.IsSevered = true; limb.IsSevered = true;
if (limb.type == LimbType.RightHand) if (limb.type == LimbType.RightHand)
{ {
@@ -765,7 +766,6 @@ namespace Barotrauma
} }
} }
if (!string.IsNullOrEmpty(character.BloodDecalName)) if (!string.IsNullOrEmpty(character.BloodDecalName))
{ {
character.CurrentHull?.AddDecal(character.BloodDecalName, character.CurrentHull?.AddDecal(character.BloodDecalName,
@@ -131,7 +131,7 @@ namespace Barotrauma
protected float oxygenAvailable; protected float oxygenAvailable;
//seed used to generate this character //seed used to generate this character
private readonly string seed; public readonly string Seed;
protected Item focusedItem; protected Item focusedItem;
private Character selectedCharacter, selectedBy; private Character selectedCharacter, selectedBy;
public Character LastAttacker; public Character LastAttacker;
@@ -273,7 +273,8 @@ namespace Barotrauma
{ {
if (IsPet) if (IsPet)
{ {
return (AIController as EnemyAIController).PetBehavior.GetName(); string petName = (AIController as EnemyAIController).PetBehavior.GetTagName();
if (!string.IsNullOrEmpty(petName)) { return petName; }
} }
if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; } if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; }
@@ -827,7 +828,7 @@ namespace Barotrauma
{ {
prefab = CharacterPrefab.FindBySpeciesName(speciesName); prefab = CharacterPrefab.FindBySpeciesName(speciesName);
this.seed = seed; this.Seed = seed;
MTRandom random = new MTRandom(ToolBox.StringToInt(seed)); MTRandom random = new MTRandom(ToolBox.StringToInt(seed));
selectedItems = new Item[2]; selectedItems = new Item[2];
@@ -2178,7 +2179,7 @@ namespace Barotrauma
} }
else if (FocusedCharacter != null && !FocusedCharacter.IsIncapacitated && IsKeyHit(InputType.Use) && FocusedCharacter.IsPet && CanInteract) else if (FocusedCharacter != null && !FocusedCharacter.IsIncapacitated && IsKeyHit(InputType.Use) && FocusedCharacter.IsPet && CanInteract)
{ {
(FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play(); (FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play(this);
} }
else if (FocusedCharacter != null && IsKeyHit(InputType.Health) && FocusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(FocusedCharacter, 160f, false)) else if (FocusedCharacter != null && IsKeyHit(InputType.Health) && FocusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(FocusedCharacter, 160f, false))
{ {
@@ -2918,7 +2919,7 @@ namespace Barotrauma
} }
#endif #endif
// Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head) // Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head)
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: AIController == null || AIController is HumanAIController); TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: attacker.IsHuman || attacker.IsPlayer);
return attackResult; return attackResult;
} }
@@ -2944,7 +2945,8 @@ namespace Barotrauma
foreach (LimbJoint joint in AnimController.LimbJoints) foreach (LimbJoint joint in AnimController.LimbJoints)
{ {
if (!joint.CanBeSevered) { continue; } if (!joint.CanBeSevered) { continue; }
if (joint.LimbA != targetLimb && joint.LimbB != targetLimb) { continue; } // Limb A is where we usually create the joints from. Let's not allow severing when the "parent" limb is hit, or the head can pop off when we hit the torso, for example.
if (joint.LimbB != targetLimb) { continue; }
float probability = severLimbsProbability; float probability = severLimbsProbability;
if (!IsDead) if (!IsDead)
{ {
@@ -3225,6 +3227,11 @@ namespace Barotrauma
return; return;
} }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
}
IsDead = true; IsDead = true;
ApplyStatusEffects(ActionType.OnDeath, 1.0f); ApplyStatusEffects(ActionType.OnDeath, 1.0f);
@@ -768,6 +768,18 @@ namespace Barotrauma
{ {
attack.UpdateCoolDown(deltaTime); attack.UpdateCoolDown(deltaTime);
} }
if (Params.BlinkFrequency > 0)
{
if (blinkTimer > -TotalBlinkDurationOut)
{
blinkTimer -= deltaTime;
}
else
{
blinkTimer = Params.BlinkFrequency;
}
}
} }
partial void UpdateProjSpecific(float deltaTime); partial void UpdateProjSpecific(float deltaTime);
@@ -1039,6 +1051,45 @@ namespace Barotrauma
} }
} }
private float blinkTimer;
private float blinkPhase;
private float TotalBlinkDurationOut => Params.BlinkDurationOut + Params.BlinkHoldTime;
public void Blink(float deltaTime, float referenceRotation)
{
if (blinkTimer > -TotalBlinkDurationOut)
{
blinkPhase -= deltaTime;
if (blinkPhase > 0)
{
// in
float t = ToolBox.GetEasing(Params.BlinkTransitionIn, MathUtils.InverseLerp(1, 0, blinkPhase / Params.BlinkDurationIn));
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true);
}
else
{
if (Math.Abs(blinkPhase) < Params.BlinkHoldTime)
{
// hold
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce, wrapAngle: true);
}
else
{
// out
float t = ToolBox.GetEasing(Params.BlinkTransitionOut, MathUtils.InverseLerp(0, 1, -blinkPhase / TotalBlinkDurationOut));
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true);
}
}
}
else
{
// out
blinkPhase = Params.BlinkDurationIn;
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce, wrapAngle: true);
}
}
public void Remove() public void Remove()
{ {
body?.Remove(); body?.Remove();
@@ -305,7 +305,7 @@ namespace Barotrauma
instance.Load(fullPath, speciesName); instance.Load(fullPath, speciesName);
anims.Add(fileName, instance); anims.Add(fileName, instance);
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite); DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
return instance as T; return instance;
} }
public bool Serialize() => base.Serialize(); public bool Serialize() => base.Serialize();
@@ -474,6 +474,12 @@ namespace Barotrauma
[Serialize(true, true, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable] [Serialize(true, true, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable]
public bool AvoidGunfire { get; private set; } public bool AvoidGunfire { get; private set; }
[Serialize(3f, true, description: "How long the creature avoids gunfire. Also used when the creature is unlatched."), Editable(minValue: 0f, maxValue: 100f)]
public float AvoidTime { get; private set; }
[Serialize(20f, true, description: "How long the creature flees before returning to normal state. When the creature sees the target or is being chased, it will always flee, if it's in the flee state."), Editable(minValue: 0f, maxValue: 100f)]
public float MinFleeTime { get; private set; }
[Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()] [Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()]
public bool AggressiveBoarding { get; private set; } public bool AggressiveBoarding { get; private set; }
@@ -580,6 +586,9 @@ namespace Barotrauma
[Serialize(0f, true, description: "Generic timer that can be used for different purposes depending on the state. E.g. in Observe state this defines how long the character in general keeps staring the targets (Some random is always applied)."), Editable] [Serialize(0f, true, description: "Generic timer that can be used for different purposes depending on the state. E.g. in Observe state this defines how long the character in general keeps staring the targets (Some random is always applied)."), Editable]
public float Timer { get; set; } public float Timer { get; set; }
[Serialize(false, true, description: "Should the target be ignored if it's inside a container/inventory. Only affects items."), Editable]
public bool IgnoreContained { get; set; }
public TargetParams(XElement element, CharacterParams character) : base(element, character) { } public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { } public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
@@ -77,7 +77,7 @@ namespace Barotrauma
[Serialize(LimbType.Torso, true), Editable] [Serialize(LimbType.Torso, true), Editable]
public LimbType MainLimb { get; set; } public LimbType MainLimb { get; set; }
private static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>(); private readonly static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>();
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>(); public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>(); public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
@@ -546,14 +546,17 @@ namespace Barotrauma
[Serialize(LimbType.None, true, description: "The limb type affects many things, like the animations. Torso or Head are considered as the main limbs. Every character should have at least one Torso or Head."), Editable()] [Serialize(LimbType.None, true, description: "The limb type affects many things, like the animations. Torso or Head are considered as the main limbs. Every character should have at least one Torso or Head."), Editable()]
public LimbType Type { get; set; } public LimbType Type { get; set; }
[Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360)]
public float SpriteOrientation { get; set; }
/// <summary> /// <summary>
/// The orientation of the sprite as drawn on the sprite sheet (in radians). /// The orientation of the sprite as drawn on the sprite sheet (in radians).
/// </summary> /// </summary>
public float GetSpriteOrientation() => MathHelper.ToRadians(float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation); public float GetSpriteOrientation() => MathHelper.ToRadians(float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation);
[Serialize("", true), Editable]
public string Notes { get; set; }
[Serialize(1f, true), Editable]
public float Scale { get; set; }
[Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()] [Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()]
public bool Flip { get; set; } public bool Flip { get; set; }
@@ -566,8 +569,8 @@ namespace Barotrauma
[Serialize(false, true, description: "Disable drawing for this limb."), Editable()] [Serialize(false, true, description: "Disable drawing for this limb."), Editable()]
public bool Hide { get; set; } public bool Hide { get; set; }
[Serialize(1f, true, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)] [Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
public float AttackPriority { get; set; } public float SpriteOrientation { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)] [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
public float SteerForce { get; set; } public float SteerForce { get; set; }
@@ -590,6 +593,9 @@ namespace Barotrauma
[Serialize(7f, true, description: "Increasing the damping makes the limb stop rotating more quickly."), Editable] [Serialize(7f, true, description: "Increasing the damping makes the limb stop rotating more quickly."), Editable]
public float AngularDamping { get; set; } public float AngularDamping { get; set; }
[Serialize(1f, true, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)]
public float AttackPriority { get; set; }
[Serialize("0, 0", true, description: "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot."), Editable()] [Serialize("0, 0", true, description: "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot."), Editable()]
public Vector2 PullPos { get; set; } public Vector2 PullPos { get; set; }
@@ -602,18 +608,12 @@ namespace Barotrauma
[Serialize("0, 0", true, description: "Relative offset for the mouth position (starting from the center). Only applicable for LimbType.Head. Used for eating."), Editable(DecimalCount = 2, MinValueFloat = -10f, MaxValueFloat = 10f)] [Serialize("0, 0", true, description: "Relative offset for the mouth position (starting from the center). Only applicable for LimbType.Head. Used for eating."), Editable(DecimalCount = 2, MinValueFloat = -10f, MaxValueFloat = 10f)]
public Vector2 MouthPos { get; set; } public Vector2 MouthPos { get; set; }
[Serialize("", true), Editable]
public string Notes { get; set; }
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
public float ConstantTorque { get; set; } public float ConstantTorque { get; set; }
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
public float ConstantAngle { get; set; } public float ConstantAngle { get; set; }
[Serialize(1f, true), Editable]
public float Scale { get; set; }
[Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)] [Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
public float AttackForceMultiplier { get; set; } public float AttackForceMultiplier { get; set; }
@@ -624,6 +624,42 @@ namespace Barotrauma
[Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)] [Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)]
public float SeveredFadeOutTime { get; set; } = 10.0f; public float SeveredFadeOutTime { get; set; } = 10.0f;
[Serialize(false, true, description: "Only applied when the limb is of type Tail. If none of the tails have been defined to use the angle and an angle is defined in the animation parameters, the first tail limb is used."), Editable]
public bool ApplyTailAngle { get; set; }
[Serialize(1f, true), Editable(ValueStep = 0.1f, DecimalCount = 2)]
public float SineFrequencyMultiplier { get; set; }
[Serialize(1f, true), Editable(ValueStep = 0.1f, DecimalCount = 2)]
public float SineAmplitudeMultiplier { get; set; }
[Serialize(0f, true), Editable(0, 100, ValueStep = 1, DecimalCount = 1)]
public float BlinkFrequency { get; set; }
[Serialize(0.2f, true), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
public float BlinkDurationIn { get; set; }
[Serialize(0.5f, true), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
public float BlinkDurationOut { get; set; }
[Serialize(0f, true), Editable(0, 10, ValueStep = 1, DecimalCount = 2)]
public float BlinkHoldTime { get; set; }
[Serialize(0f, true), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
public float BlinkRotationIn { get; set; }
[Serialize(45f, true), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
public float BlinkRotationOut { get; set; }
[Serialize(50f, true), Editable]
public float BlinkForce { get; set; }
[Serialize(TransitionMode.Linear, true), Editable]
public TransitionMode BlinkTransitionIn { get; private set; }
[Serialize(TransitionMode.Linear, true), Editable]
public TransitionMode BlinkTransitionOut { get; private set; }
// Non-editable -> // Non-editable ->
// TODO: make read-only // TODO: make read-only
[Serialize(0, true)] [Serialize(0, true)]
@@ -592,13 +592,13 @@ namespace Barotrauma
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine))) (character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
{ {
//crawler inside the sub adds 0.1f to enemy danger, mantis 0.25f //crawler inside the sub adds 0.1f to enemy danger, mantis 0.25f
enemyDanger += enemyAI.CombatStrength / 1000.0f; enemyDanger += enemyAI.CombatStrength / 100.0f;
} }
else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null) else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null)
{ {
//enemy outside and targeting the sub or something in it //enemy outside and targeting the sub or something in it
//moloch adds 0.24 to enemy danger, a crawler 0.02 //moloch adds 0.24 to enemy danger, a crawler 0.02
enemyDanger += enemyAI.CombatStrength / 2000.0f; enemyDanger += enemyAI.CombatStrength / 1000.0f;
} }
} }
enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f); enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f);
@@ -654,12 +654,12 @@ namespace Barotrauma
if (targetIntensity > currentIntensity) if (targetIntensity > currentIntensity)
{ {
//25 seconds for intensity to go from 0.0 to 1.0 //25 seconds for intensity to go from 0.0 to 1.0
currentIntensity = MathHelper.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity); currentIntensity = Math.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity);
} }
else else
{ {
//400 seconds for intensity to go from 1.0 to 0.0 //400 seconds for intensity to go from 1.0 to 0.0
currentIntensity = MathHelper.Max(0.0025f * IntensityUpdateInterval, targetIntensity); currentIntensity = Math.Max(currentIntensity - 0.0025f * IntensityUpdateInterval, targetIntensity);
} }
} }
@@ -30,6 +30,8 @@ namespace Barotrauma
public CampaignMetadata CampaignMetadata; public CampaignMetadata CampaignMetadata;
protected XElement petsElement;
public enum TransitionType public enum TransitionType
{ {
None, None,
@@ -282,6 +282,11 @@ namespace Barotrauma
DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted."); DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted.");
return; return;
} }
if (SubmarineInfo.SubmarineElement.Elements().Count() == 0)
{
DebugConsole.ThrowError("Couldn't start game session, saved submarine is empty. The submarine file may be corrupted.");
return;
}
LevelData = levelData; LevelData = levelData;
@@ -6,18 +6,20 @@ namespace Barotrauma.Items.Components
{ {
class Throwable : Holdable class Throwable : Holdable
{ {
private float throwForce, throwPos; private float throwPos;
private bool throwing, throwDone; private bool throwing, throwDone;
private bool midAir; private bool midAir;
[Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")] public Character CurrentThrower
public float ThrowForce
{ {
get { return throwForce; } get;
set { throwForce = value; } private set;
} }
[Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")]
public float ThrowForce { get; set; }
public Throwable(Item item, XElement element) public Throwable(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
@@ -60,6 +62,21 @@ namespace Barotrauma.Items.Components
{ {
if (item.body.LinearVelocity.LengthSquared() < 0.01f) if (item.body.LinearVelocity.LengthSquared() < 0.01f)
{ {
CurrentThrower = null;
if (statusEffectLists.ContainsKey(ActionType.OnImpact))
{
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
{
statusEffect.SetUser(null);
}
}
if (statusEffectLists.ContainsKey(ActionType.OnBroken))
{
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
{
statusEffect.SetUser(null);
}
}
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform; item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
midAir = false; midAir = false;
} }
@@ -117,9 +134,24 @@ namespace Barotrauma.Items.Components
#if SERVER #if SERVER
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction); GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif #endif
Character thrower = picker; CurrentThrower = picker;
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); if (statusEffectLists.ContainsKey(ActionType.OnImpact))
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); {
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
{
statusEffect.SetUser(CurrentThrower);
}
}
if (statusEffectLists.ContainsKey(ActionType.OnBroken))
{
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
{
statusEffect.SetUser(CurrentThrower);
}
}
item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
item.body.ApplyLinearImpulse(throwVector * ThrowForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
//disable platform collisions until the item comes back to rest again //disable platform collisions until the item comes back to rest again
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
@@ -135,12 +167,12 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{ {
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, thrower.ID }); GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, CurrentThrower.ID });
} }
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{ {
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse" //Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower, user: thrower); ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
} }
throwing = false; throwing = false;
} }
@@ -349,6 +349,14 @@ namespace Barotrauma.Items.Components
} }
} }
public override void OnItemLoaded()
{
if (item.Submarine == null || !item.Submarine.Loading)
{
SpawnAlwaysContainedItems();
}
}
public override void OnMapLoaded() public override void OnMapLoaded()
{ {
if (itemIds != null) if (itemIds != null)
@@ -361,7 +369,11 @@ namespace Barotrauma.Items.Components
} }
itemIds = null; itemIds = null;
} }
SpawnAlwaysContainedItems();
}
private void SpawnAlwaysContainedItems()
{
if (SpawnWithId.Length > 0) if (SpawnWithId.Length > 0)
{ {
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId); ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
@@ -375,6 +387,7 @@ namespace Barotrauma.Items.Components
} }
} }
protected override void ShallowRemoveComponentSpecific() protected override void ShallowRemoveComponentSpecific()
{ {
} }
@@ -1658,10 +1658,14 @@ namespace Barotrauma
public override void FlipX(bool relativeToSub) public override void FlipX(bool relativeToSub)
{ {
if (!Prefab.CanFlipX) { return; } //call the base method even if the item can't flip, to handle repositioning when flipping the whole sub
base.FlipX(relativeToSub); base.FlipX(relativeToSub);
if (!Prefab.CanFlipX)
{
flippedX = false;
return;
}
#if CLIENT #if CLIENT
if (Prefab.CanSpriteFlipX) if (Prefab.CanSpriteFlipX)
{ {
@@ -1677,10 +1681,15 @@ namespace Barotrauma
public override void FlipY(bool relativeToSub) public override void FlipY(bool relativeToSub)
{ {
if (!Prefab.CanFlipY) { return; } //call the base method even if the item can't flip, to handle repositioning when flipping the whole sub
base.FlipY(relativeToSub); base.FlipY(relativeToSub);
if (!Prefab.CanFlipY)
{
flippedY = false;
return;
}
#if CLIENT #if CLIENT
if (Prefab.CanSpriteFlipY) if (Prefab.CanSpriteFlipY)
{ {
@@ -1340,6 +1340,9 @@ namespace Barotrauma
decalsCleaned = true; decalsCleaned = true;
#if SERVER #if SERVER
decalUpdatePending = true; decalUpdatePending = true;
#elif CLIENT
pendingDecalUpdates.Add(decal);
networkUpdatePending = true;
#endif #endif
} }
} }
@@ -833,7 +833,7 @@ namespace Barotrauma
if (location.Discovered) if (location.Discovered)
{ {
#if CLIENT #if CLIENT
RemoveFogOfWar(StartLocation); RemoveFogOfWar(location);
#endif #endif
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{ {
@@ -50,7 +50,7 @@ namespace Barotrauma
//observable collection because some entities may need to be notified when the collection is modified //observable collection because some entities may need to be notified when the collection is modified
public readonly ObservableCollection<MapEntity> linkedTo = new ObservableCollection<MapEntity>(); public readonly ObservableCollection<MapEntity> linkedTo = new ObservableCollection<MapEntity>();
private bool flippedX, flippedY; protected bool flippedX, flippedY;
public bool FlippedX { get { return flippedX; } } public bool FlippedX { get { return flippedX; } }
public bool FlippedY { get { return flippedY; } } public bool FlippedY { get { return flippedY; } }
@@ -534,7 +534,7 @@ namespace Barotrauma
public virtual void FlipX(bool relativeToSub) public virtual void FlipX(bool relativeToSub)
{ {
flippedX = !flippedX; flippedX = !flippedX;
if (!relativeToSub || Submarine == null) return; if (!relativeToSub || Submarine == null) { return; }
Vector2 relative = WorldPosition - Submarine.WorldPosition; Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.Y = 0.0f; relative.Y = 0.0f;
@@ -548,7 +548,7 @@ namespace Barotrauma
public virtual void FlipY(bool relativeToSub) public virtual void FlipY(bool relativeToSub)
{ {
flippedY = !flippedY; flippedY = !flippedY;
if (!relativeToSub || Submarine == null) return; if (!relativeToSub || Submarine == null) { return; }
Vector2 relative = WorldPosition - Submarine.WorldPosition; Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.X = 0.0f; relative.X = 0.0f;
@@ -415,7 +415,7 @@ namespace Barotrauma
} }
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError("Error in SerializableProperty.TrySetValue", e); DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
return false; return false;
} }
} }
@@ -733,9 +733,10 @@ namespace Barotrauma
} }
else else
{ {
if (targets.FirstOrDefault(t => t is MapEntity) is MapEntity targetEntity && !targetEntity.Removed) var targetLimb = targets.FirstOrDefault(t => t is Limb) as Limb;
if (targetLimb != null && !targetLimb.Removed)
{ {
position = targetEntity.WorldPosition; position = targetLimb.WorldPosition;
} }
} }
} }
+32 -32
View File
@@ -1,40 +1,10 @@
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
v0.10.601.0 (Unstable) v0.10.6.0 (Unstable)
---------------------------------------------------------------------------------------------------------
- Added 2 pets: Peanut and Psilotoad. Currently only obtainable via console commands ("spawnitem peanutegg", "spawnitem psilotoadegg", "spawn peanut" or "spawn psilotoad").
- Improvements to the Watcher.
- Fixed 2 equipped storage containers from one hand to another causing one of them to get stuck mid-air.
- Fixed a crash caused by humanoid enemies (e.g. husks).
- Moved toolbelt slot to the right side of the generic slots, added inventory icon for the slot.
- Fixed EventManager crashing if there are no event sets configured for the current location type (only affected mods that add new location types without adding any events for them).
- Use player name instead of server name for the server owner when hosting a server.
- Fixed diving suit's low oxygen warning beep not following the player wearing the diving suit.
- Made large monsters immune to paralyzant (mudraptor is the largest affected monster).
- Fixed bots not reacting to player reports in multiplayer.
- Added more copper to chalcopyrite and bornite deconstruct recipe.
- More calcium for aragonite, adjusted prices.
- Fixed fires not damaging characters.
- Set terminal's maximum message length to match maximum chat message length (otherwise chat-linked terminals work differently in multiplayer).
- Fixed inability to unlink hulls in the sub editor.
- Fixed bots "cleaning up" components attached to walls.
- Fixed yet another cause for "missing entity" errors. Occasionally happened in monster missions when a monster happened to get assigned the same ID as an item in a player's inventory.
- Fixed items held in the left hand being drawn in front of the characters.
- Allow closing the splash screens with esc.
- Fixed "inventory sizes don't match" error when a human becomes a husk.
- Fixed ability to drag and drop items into outpost reactors.
- Fixed torso getting hidden when wearing a toolbelt.
- Fixed coilgun ammo only using 90% of the fabrication materials.
- Fixed paints reverting to the dirt color client-side after spraying.
- Added missing dialog for the new "Cleanup Items" order.
- Rebalanced upgrade parameters, allowing for more noticable benefits.
---------------------------------------------------------------------------------------------------------
v0.10.600.0 (Unstable)
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
Changes and additions: Changes and additions:
- Reworked Watcher. - Reworked Watcher.
- Added pets (can be obtained by buying eggs from outposts). The pets produce items that can be used for crafting if they're kept happy and well-fed.
- Added a new monster behavior: observe. - Added a new monster behavior: observe.
- Added toolbelt (a wearable container with a capacity of 12 and it's own dedicated slot) as a replacement for the toolbox. - Added toolbelt (a wearable container with a capacity of 12 and it's own dedicated slot) as a replacement for the toolbox.
- Improvements to the effects caused by psychosis: the affliction icon is not visible to the psychotic character, the fake fires and floods are a bit more convincing, the affliction plays random sounds and can cause other characters to become invisible. - Improvements to the effects caused by psychosis: the affliction icon is not visible to the psychotic character, the fake fires and floods are a bit more convincing, the affliction plays random sounds and can cause other characters to become invisible.
@@ -66,12 +36,20 @@ Changes and additions:
- A minor change to the status effect condition targeting logic: If "This" and "NearbyCharacters" are both defined as the targets of a status effect, the conditions only apply to "this" entity, even though the effects are applied on all the targets. - A minor change to the status effect condition targeting logic: If "This" and "NearbyCharacters" are both defined as the targets of a status effect, the conditions only apply to "this" entity, even though the effects are applied on all the targets.
- Implement spread, speed, and rotation for the spawn item status effects. - Implement spread, speed, and rotation for the spawn item status effects.
- Minor damage (less than 1 hp) doesn't spawn particles anymore. - Minor damage (less than 1 hp) doesn't spawn particles anymore.
- Rebalanced upgrade parameters, allowing for more noticable benefits.
- Allow closing the splash screens with esc.
- Added more copper to chalcopyrite and bornite deconstruct recipe.
- More calcium for aragonite, adjusted prices.
- Made large monsters immune to paralyzant (mudraptor is the largest affected monster).
- Use player name instead of server name for the server owner when hosting a server.
- Don't draw turret range indicators in the sub editor when the turret isn't selected.
Character Editor: Character Editor:
- Fixed a number of issues with the joint limit widgets. Also allowed to set a joint to rotate clockwise, which inverses the widget direction. Useful for heads or other limbs that extrude right from the main body. - Fixed a number of issues with the joint limit widgets. Also allowed to set a joint to rotate clockwise, which inverses the widget direction. Useful for heads or other limbs that extrude right from the main body.
- Inversed the default joint ends, because it's more usual case to edit the second limb of the joint than the first. - Inversed the default joint ends, because it's more usual case to edit the second limb of the joint than the first.
- The colliders of the hidden limbs are now hidden in the game view. - The colliders of the hidden limbs are now hidden in the game view.
- Changed the hotkey for toggling the parameter editor from "Tab" to "F1" and fix the inability to toggle the editor when a text field is selected. - Changed the hotkey for toggling the parameter editor from "Tab" to "F1" and fix the inability to toggle the editor when a text field is selected.
- Fixed load and save interfaces being broken on lower resolutions.
Sounds: Sounds:
- Added 2 new background music tracks - Added 2 new background music tracks
@@ -110,6 +88,8 @@ AI improvements and fixes:
Misc fixes: Misc fixes:
- Fixed clients always getting the generic "could not connect" error message when connecting to a server fails, even if there's a specific reason to the connection failing (e.g. disallowed symbols in the player's name, mismatching content packages or game version). - Fixed clients always getting the generic "could not connect" error message when connecting to a server fails, even if there's a specific reason to the connection failing (e.g. disallowed symbols in the player's name, mismatching content packages or game version).
- Fixed yet another cause for "missing entity" errors. Occasionally happened in monster missions when a monster happened to get assigned the same ID as an item in a player's inventory.
- Fixed clients spawning a respawn shuttle in non-campaign missions even if the server has disabled respawning, leading to "missing entity" errors.
- Fixed planters dropping removed seeds after a save is reloaded. - Fixed planters dropping removed seeds after a save is reloaded.
- Fixed plants not updating the health after being fully grown in multiplayer. - Fixed plants not updating the health after being fully grown in multiplayer.
- Fixed decal syncing working unreliably in multiplayer. - Fixed decal syncing working unreliably in multiplayer.
@@ -145,6 +125,26 @@ Misc fixes:
- OnDamaged status effects now launch only when there's any damage. Not when the damage is zero. - OnDamaged status effects now launch only when there's any damage. Not when the damage is zero.
- Fixed health bar pulsating even when no damage is done by an attack/status effect. - Fixed health bar pulsating even when no damage is done by an attack/status effect.
- Fixed affliction probability not having any effect when used in status effects. - Fixed affliction probability not having any effect when used in status effects.
- Fixed EventManager crashing if there are no event sets configured for the current location type (only affected mods that add new location types without adding any events for them).
- Fixed items held in the left hand being drawn in front of the characters.
- Fixed ability to drag and drop items into outpost reactors.
- Set terminal's maximum message length to match maximum chat message length (otherwise chat-linked terminals work differently in multiplayer).
- Fixed diving suit's low oxygen warning beep not following the player wearing the suit.
- Fixed "propaganda" and "clown outbreak" outpost events never triggering.
- Fixed "clown brutality" event getting stuck after the NPCs have been spawned.
- Fixed "impromptu engineering" event giving only one coilgun ammo box despite the text saying 2.
- Fixed "black market" event always giving the player the alien pistol.
- Fixed incendium bars exploding when pressing the Use key while holding one.
- Fixed outpost security not reacting to players throwing items that explode on impact (e.g. flash powder or nitroglycerin).
- Fixed outpost security reacting to raptor bane extract injections with lethal force.
- Fixed mantis' animation.
- Fixed multiple wire nodes occasionally getting placed with one click when rewiring.
- Fixed fire and water flow sounds staying active when returning from a multiplayer session to the main menu.
- Fixed previously discovered map tiles becoming undiscovered when saving and loading a campaign.
- Fixed 1st shot from SMG magazines spawned with console commands doing nothing.
- Fixed calyxanide not damaging huskified humans or crawlers.
- Fixed explosives exploding when combining them results in one being removed.
- Fixed items that don't flip horizontally being positioned incorrectly in mirrored subs.
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
v0.10.5.1 v0.10.5.1