Unstable 0.16.3.0

This commit is contained in:
Markus Isberg
2022-02-10 02:52:08 +09:00
parent 6814a11520
commit 2190fe08ef
115 changed files with 993 additions and 453 deletions
@@ -11,7 +11,7 @@ namespace Barotrauma
{ {
if (Character.IsUnconscious || !Character.Enabled || !Enabled) { return; } if (Character.IsUnconscious || !Character.Enabled || !Enabled) { return; }
Vector2 pos = Character.WorldPosition; Vector2 pos = Character.DrawPosition;
pos.Y = -pos.Y; pos.Y = -pos.Y;
if (State == AIState.Idle && PreviousState == AIState.Attack) if (State == AIState.Idle && PreviousState == AIState.Attack)
@@ -31,7 +31,7 @@ namespace Barotrauma
} }
else if (SelectedAiTarget?.Entity != null) else if (SelectedAiTarget?.Entity != null)
{ {
Vector2 targetPos = SelectedAiTarget.WorldPosition; Vector2 targetPos = SelectedAiTarget.Entity.DrawPosition;
if (State == AIState.Attack) if (State == AIState.Attack)
{ {
targetPos = attackWorldPos; targetPos = attackWorldPos;
@@ -72,7 +72,7 @@ namespace Barotrauma
} }
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black); GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
if (LatchOntoAI != null) if (LatchOntoAI != null && (State == AIState.Idle || LatchOntoAI.IsAttachedToSub))
{ {
foreach (Joint attachJoint in LatchOntoAI.AttachJoints) foreach (Joint attachJoint in LatchOntoAI.AttachJoints)
{ {
@@ -312,11 +312,7 @@ namespace Barotrauma
} }
} }
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition); UpdateLocalCursor(cam);
if (AnimController.CurrentHull?.Submarine != null)
{
cursorPosition -= AnimController.CurrentHull.Submarine.Position;
}
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition); Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (GUI.PauseMenuOpen) if (GUI.PauseMenuOpen)
@@ -393,6 +389,15 @@ namespace Barotrauma
DisableControls = false; DisableControls = false;
} }
public void UpdateLocalCursor(Camera cam)
{
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
if (AnimController.CurrentHull?.Submarine != null)
{
cursorPosition -= AnimController.CurrentHull.Submarine.DrawPosition;
}
}
partial void UpdateControlled(float deltaTime, Camera cam) partial void UpdateControlled(float deltaTime, Camera cam)
{ {
if (controlled != this) return; if (controlled != this) return;
@@ -431,7 +431,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; } if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; }
if (Vector2.DistanceSquared(character.Position, item.Position) > 500f*500f) { continue; } if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true); var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item) { continue; } if (body != null && body.UserData as Item != item) { continue; }
GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false); GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
@@ -5,6 +5,7 @@
private InfectionState? prevDisplayedMessage; private InfectionState? prevDisplayedMessage;
partial void UpdateMessages() partial void UpdateMessages()
{ {
if (character != Character.Controlled) { return; }
if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; } if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; }
if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; } if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; }
@@ -897,12 +897,19 @@ namespace Barotrauma
if (wearable.Type == WearableType.Husk) { continue; } if (wearable.Type == WearableType.Husk) { continue; }
if (wearableTypesToHide.Contains(wearable.Type)) if (wearableTypesToHide.Contains(wearable.Type))
{ {
if (wearable.Type == WearableType.Hair && HairWithHatSprite != null) if (wearable.Type == WearableType.Hair)
{ {
DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect); if (HairWithHatSprite != null)
depthStep += step; {
DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
depthStep += step;
continue;
}
}
else
{
continue;
} }
continue;
} }
DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect); DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
//if there are multiple sprites on this limb, make the successive ones be drawn in front //if there are multiple sprites on this limb, make the successive ones be drawn in front
@@ -1,3 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
@@ -20,6 +21,9 @@ namespace Barotrauma
} }
} }
public override bool IsAtCompletionState => State > 0 && requireRescue.None();
public override bool IsAtFailureState => State == HostagesKilledState;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -4,6 +4,9 @@ namespace Barotrauma
{ {
partial class AlienRuinMission : Mission partial class AlienRuinMission : Mission
{ {
public override bool IsAtCompletionState => State > 0;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -0,0 +1,8 @@
namespace Barotrauma
{
partial class BeaconMission : Mission
{
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
}
}
@@ -5,6 +5,9 @@ namespace Barotrauma
{ {
partial class CargoMission : Mission partial class CargoMission : Mission
{ {
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
public override string GetMissionRewardText(Submarine sub) public override string GetMissionRewardText(Submarine sub)
{ {
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub)));
@@ -20,5 +20,8 @@ namespace Barotrauma
return descriptions[GameMain.Client.Character.TeamID == CharacterTeamType.Team1 ? 1 : 2]; return descriptions[GameMain.Client.Character.TeamID == CharacterTeamType.Team1 ? 1 : 2];
} }
} }
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
} }
} }
@@ -4,6 +4,9 @@ namespace Barotrauma
{ {
partial class EscortMission : Mission partial class EscortMission : Mission
{ {
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => State == 1;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -0,0 +1,8 @@
namespace Barotrauma
{
partial class GoToMission : Mission
{
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
}
}
@@ -1,15 +1,14 @@
using Barotrauma.Extensions; using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
partial class MineralMission : Mission partial class MineralMission : Mission
{ {
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -19,6 +19,15 @@ namespace Barotrauma
public virtual IEnumerable<Entity> HudIconTargets => Enumerable.Empty<Entity>(); public virtual IEnumerable<Entity> HudIconTargets => Enumerable.Empty<Entity>();
/// <summary>
/// State at which the only thing left to do is to reach the end of the level. Use for UI references.
/// </summary>
public abstract bool IsAtCompletionState { get; }
/// <summary>
/// State at which the mission cannot be completed anymore. Use for UI references.
/// </summary>
public abstract bool IsAtFailureState { get; }
public Color GetDifficultyColor() public Color GetDifficultyColor()
{ {
int v = Difficulty ?? MissionPrefab.MinDifficulty; int v = Difficulty ?? MissionPrefab.MinDifficulty;
@@ -4,6 +4,9 @@ namespace Barotrauma
{ {
partial class MonsterMission : Mission partial class MonsterMission : Mission
{ {
public override bool IsAtCompletionState => State > 0;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -6,6 +6,9 @@ namespace Barotrauma
{ {
partial class NestMission : Mission partial class NestMission : Mission
{ {
public override bool IsAtCompletionState => State > 0 && !requireDelivery;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -4,6 +4,9 @@ namespace Barotrauma
{ {
partial class PirateMission : Mission partial class PirateMission : Mission
{ {
public override bool IsAtCompletionState => State > 1;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -5,6 +5,9 @@ namespace Barotrauma
{ {
partial class SalvageMission : Mission partial class SalvageMission : Mission
{ {
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -23,6 +22,9 @@ namespace Barotrauma
} }
} }
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -1356,7 +1356,7 @@ namespace Barotrauma
#region Element drawing #region Element drawing
private static List<float> usedIndicatorAngles = new List<float>(); private static readonly List<float> usedIndicatorAngles = new List<float>();
/// <param name="createOffset">Should the indicator move based on the camera position?</param> /// <param name="createOffset">Should the indicator move based on the camera position?</param>
/// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param> /// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param>
@@ -1019,8 +1019,10 @@ namespace Barotrauma
case "gridtext": case "gridtext":
LoadGridText(element, parent); LoadGridText(element, parent);
return null; return null;
case "conditional":
break;
default: default:
throw new NotImplementedException("Loading GUI component \""+element.Name+"\" from XML is not implemented."); throw new NotImplementedException("Loading GUI component \"" + element.Name + "\" from XML is not implemented.");
} }
if (component != null) if (component != null)
@@ -1683,8 +1683,9 @@ namespace Barotrauma
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled) private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
{ {
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; } if (!(itemFrame?.UserData is PurchasedItem pi)) { return; }
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
if (!refreshFrameStatus) { return; }
if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon) if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon)
{ {
if (pi.ItemPrefab?.InventoryIcon != null) if (pi.ItemPrefab?.InventoryIcon != null)
@@ -1696,14 +1697,11 @@ namespace Barotrauma
icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f); icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f);
} }
}; };
var color = Color.White * (enabled ? 1.0f : 0.5f); var color = Color.White * (enabled ? 1.0f : 0.5f);
if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name) if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name)
{ {
name.TextColor = color; name.TextColor = color;
} }
if (itemFrame.FindChild("quantitylabel", recursive: true) is GUITextBlock qty) if (itemFrame.FindChild("quantitylabel", recursive: true) is GUITextBlock qty)
{ {
qty.TextColor = color; qty.TextColor = color;
@@ -1712,25 +1710,21 @@ namespace Barotrauma
{ {
numberInput.Enabled = enabled; numberInput.Enabled = enabled;
} }
if (itemFrame.FindChild("owned", recursive: true) is GUITextBlock ownedBlock) if (itemFrame.FindChild("owned", recursive: true) is GUITextBlock ownedBlock)
{ {
ownedBlock.TextColor = color; ownedBlock.TextColor = color;
} }
bool isDiscounted = false;
var isDiscounted = false;
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock) if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
{ {
undiscountedPriceBlock.TextColor = color; undiscountedPriceBlock.TextColor = color;
undiscountedPriceBlock.Strikethrough.Color = color; undiscountedPriceBlock.Strikethrough.Color = color;
isDiscounted = true; isDiscounted = true;
} }
if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock) if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock)
{ {
priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color; priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color;
} }
if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton) if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton)
{ {
addButton.Enabled = enabled; addButton.Enabled = enabled;
@@ -1739,6 +1733,8 @@ namespace Barotrauma
{ {
removeButton.Enabled = enabled; removeButton.Enabled = enabled;
} }
pi.IsStoreComponentEnabled = enabled;
itemFrame.UserData = pi;
} }
private void SetQuantityLabelText(StoreTab mode, GUIComponent itemFrame) private void SetQuantityLabelText(StoreTab mode, GUIComponent itemFrame)
@@ -129,7 +129,6 @@ namespace Barotrauma
public TabMenu() public TabMenu()
{ {
if (!initialized) { Initialize(); } if (!initialized) { Initialize(); }
CreateInfoFrame(SelectedTab); CreateInfoFrame(SelectedTab);
SelectInfoFrameTab(SelectedTab); SelectInfoFrameTab(SelectedTab);
} }
@@ -300,7 +299,7 @@ namespace Barotrauma
CreateMissionInfo(infoFrameHolder); CreateMissionInfo(infoFrameHolder);
break; break;
case InfoFrameTab.Reputation: case InfoFrameTab.Reputation:
if (GameMain.GameSession.RoundSummary != null && GameMain.GameSession.GameMode is CampaignMode campaignMode) if (GameMain.GameSession?.RoundSummary != null && GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{ {
infoFrameHolder.ClearChildren(); infoFrameHolder.ClearChildren();
GUIFrame reputationFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrameHolder.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame reputationFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrameHolder.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
@@ -308,8 +307,8 @@ namespace Barotrauma
} }
break; break;
case InfoFrameTab.Traitor: case InfoFrameTab.Traitor:
TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission; TraitorMissionPrefab traitorMission = GameMain.Client?.TraitorMission;
Character traitor = GameMain.Client.Character; Character traitor = GameMain.Client?.Character;
if (traitor == null || traitorMission == null) { return; } if (traitor == null || traitorMission == null) { return; }
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor); CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break; break;
@@ -753,7 +752,7 @@ namespace Barotrauma
if (character != null) if (character != null)
{ {
if (GameMain.NetworkMember == null) if (GameMain.Client == null)
{ {
GUIComponent preview = character.Info.CreateInfoFrame(background, false, null); GUIComponent preview = character.Info.CreateInfoFrame(background, false, null);
} }
@@ -1021,13 +1020,15 @@ namespace Barotrauma
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);*/ Point iconSize = new Point(iconWidth, iconHeight);*/
new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) var icon = new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true)
{ {
Color = mission.Prefab.IconColor, Color = mission.Prefab.IconColor,
HoverColor = mission.Prefab.IconColor, HoverColor = mission.Prefab.IconColor,
SelectedColor = mission.Prefab.IconColor, SelectedColor = mission.Prefab.IconColor,
CanBeFocused = false CanBeFocused = false
}; };
UpdateMissionStateIcon(mission, icon);
mission.OnMissionStateChanged += (mission) => UpdateMissionStateIcon(mission, icon);
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameRichTextData, missionNameString, font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameRichTextData, missionNameString, font: GUI.LargeFont);
GUILayoutGroup difficultyIndicatorGroup = null; GUILayoutGroup difficultyIndicatorGroup = null;
@@ -1064,6 +1065,33 @@ namespace Barotrauma
} }
} }
private void UpdateMissionStateIcon(Mission mission, GUIImage missionIcon)
{
if (mission == null || missionIcon == null) { return; }
string style = string.Empty;
if (mission.IsAtFailureState)
{
style = "MissionFailedIcon";
}
else if (mission.IsAtCompletionState)
{
style = "MissionCompletedIcon";
}
GUIImage stateIcon = missionIcon.GetChild<GUIImage>();
if (string.IsNullOrEmpty(style))
{
if (stateIcon != null)
{
stateIcon.Visible = false;
}
}
else
{
stateIcon ??= new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), style, scaleToFit: true);
stateIcon.Visible = true;
}
}
private void CreateTraitorInfo(GUIFrame infoFrame, TraitorMissionPrefab traitorMission, Character traitor) private void CreateTraitorInfo(GUIFrame infoFrame, TraitorMissionPrefab traitorMission, Character traitor)
{ {
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
@@ -1441,7 +1469,7 @@ namespace Barotrauma
selectedTalents.Remove(talentIdentifier); selectedTalents.Remove(talentIdentifier);
} }
UpdateTalentButtons(); UpdateTalentInfo();
return true; return true;
}, },
}; };
@@ -1506,7 +1534,7 @@ namespace Barotrauma
}; };
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock); GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
UpdateTalentButtons(); UpdateTalentInfo();
} }
private void CreateTalentSkillList(Character character, GUIListBox parent) private void CreateTalentSkillList(Character character, GUIListBox parent)
@@ -1541,11 +1569,13 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(skillNames); GUITextBlock.AutoScaleAndNormalize(skillNames);
} }
private void UpdateTalentButtons() private void UpdateTalentInfo()
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return; } if (controlledCharacter?.Info == null) { return; }
if (SelectedTab != InfoFrameTab.Talents) { return; }
bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents(); bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
if (unlockedAllTalents) if (unlockedAllTalents)
@@ -1626,7 +1656,7 @@ namespace Barotrauma
} }
} }
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentButtons(); UpdateTalentInfo();
} }
private bool ApplyTalentSelection(GUIButton guiButton, object userData) private bool ApplyTalentSelection(GUIButton guiButton, object userData)
@@ -1640,63 +1670,14 @@ namespace Barotrauma
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentButtons(); UpdateTalentInfo();
return true; return true;
} }
public void OnExperienceChanged(Character character) public void OnExperienceChanged(Character character)
{ {
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
UpdateTalentButtons(); UpdateTalentInfo();
}
private readonly StatTypes[] basicStats = new StatTypes[]
{
StatTypes.MaximumHealthMultiplier,
StatTypes.MovementSpeed,
StatTypes.SwimmingSpeed,
StatTypes.RepairSpeed,
};
private readonly StatTypes[] combatStats = new StatTypes[]
{
StatTypes.MeleeAttackMultiplier,
StatTypes.MeleeAttackSpeed,
StatTypes.RangedAttackSpeed,
StatTypes.TurretAttackSpeed,
};
private readonly StatTypes[] miscStats = new StatTypes[]
{
StatTypes.ReputationGainMultiplier,
StatTypes.MissionMoneyGainMultiplier,
StatTypes.ExperienceGainMultiplier,
StatTypes.MissionExperienceGainMultiplier,
};
private void CreateCharacterSheet(GUILayoutGroup characterInfoColumn)
{
Character controlledCharacter = Character.Controlled;
CreateRow(basicStats);
CreateRow(combatStats);
CreateRow(miscStats);
void CreateRow(StatTypes[] statTypes)
{
GUILayoutGroup characterInfoRow = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1.0f), characterInfoColumn.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopCenter);
foreach (StatTypes statType in statTypes)
{
ShowStat(statType, characterInfoRow);
}
}
void ShowStat(StatTypes statType, GUILayoutGroup characterInfoRow)
{
GUIFrame textInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), characterInfoRow.RectTransform, Anchor.TopCenter), style: null);
new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), statType.ToString(), font: GUI.SmallFont, textAlignment: Alignment.TopLeft);
new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), (int)(100f * (1 + controlledCharacter.GetStatValue(statType))) + "%", font: GUI.Font, textAlignment: Alignment.TopRight);
}
} }
} }
} }
@@ -12,6 +12,8 @@ namespace Barotrauma
static class HintManager static class HintManager
{ {
private const string HintManagerFile = "hintmanager.xml"; private const string HintManagerFile = "hintmanager.xml";
public static bool Enabled => GameMain.Config != null && !GameMain.Config.DisableInGameHints;
private static HashSet<string> HintIdentifiers { get; set; } private static HashSet<string> HintIdentifiers { get; set; }
private static Dictionary<string, HashSet<string>> HintTags { get; } = new Dictionary<string, HashSet<string>>(); private static Dictionary<string, HashSet<string>> HintTags { get; } = new Dictionary<string, HashSet<string>>();
private static Dictionary<string, (string identifier, string option)> HintOrders { get; } = new Dictionary<string, (string orderIdentifier, string orderOption)>(); private static Dictionary<string, (string identifier, string option)> HintOrders { get; } = new Dictionary<string, (string orderIdentifier, string orderOption)>();
@@ -666,6 +668,8 @@ namespace Barotrauma
ActiveHintMessageBox.InnerFrame.Flash(color: iconColor ?? Color.Orange, flashDuration: 0.75f); ActiveHintMessageBox.InnerFrame.Flash(color: iconColor ?? Color.Orange, flashDuration: 0.75f);
onDisplay?.Invoke(); onDisplay?.Invoke();
GameAnalyticsManager.AddDesignEvent($"HintManager:{GameMain.GameSession?.GameMode?.Preset?.Identifier ?? "none"}:HintDisplayed:{hintIdentifier}");
return true; return true;
} }
@@ -565,6 +565,13 @@ namespace Barotrauma
}); });
#endif #endif
foreach (var child in leftPanel.Children)
{
if (child is GUITextBlock textBlock)
{
textBlock.RectTransform.MinSize = new Point(textBlock.RectTransform.MinSize.X, (int)Math.Max(textBlock.RectTransform.MinSize.Y, textBlock.TextSize.Y));
}
}
// right panel -------------------------------------- // right panel --------------------------------------
@@ -1693,13 +1693,12 @@ namespace Barotrauma.Items.Components
} }
} }
if (iconIdentifier == null || !targetIcons.ContainsKey(iconIdentifier)) if (iconIdentifier == null || !targetIcons.TryGetValue(iconIdentifier, out var iconInfo) || iconInfo.Item1 == null)
{ {
GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), markerColor, thickness: 2); GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), markerColor, thickness: 2);
} }
else else
{ {
var iconInfo = targetIcons[iconIdentifier];
iconInfo.Item1.Draw(spriteBatch, markerPos, iconInfo.Item2); iconInfo.Item1.Draw(spriteBatch, markerPos, iconInfo.Item2);
} }
@@ -1712,7 +1711,10 @@ namespace Barotrauma.Items.Components
Vector2 textSize = GUI.SmallFont.MeasureString(wrappedLabel); Vector2 textSize = GUI.SmallFont.MeasureString(wrappedLabel);
//flip the text to left side when the marker is on the left side or goes outside the right edge of the interface //flip the text to left side when the marker is on the left side or goes outside the right edge of the interface
if ((dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) && labelPos.X - textSize.X > 0) labelPos.X -= textSize.X + 10; if (GuiFrame != null && (dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) && labelPos.X - textSize.X > 0)
{
labelPos.X -= textSize.X + 10;
}
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2(labelPos.X + 10, labelPos.Y), new Vector2(labelPos.X + 10, labelPos.Y),
@@ -381,17 +381,17 @@ namespace Barotrauma.Items.Components
if (deteriorationTimer > 0.0f) if (deteriorationTimer > 0.0f)
{ {
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2(item.WorldPosition.X, -item.WorldPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""), new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""),
paused ? Color.Cyan : Color.Lime, Color.Black * 0.5f); paused ? Color.Cyan : Color.Lime, Color.Black * 0.5f);
} }
else else
{ {
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2(item.WorldPosition.X, -item.WorldPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""), new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""),
paused ? Color.Cyan : GUI.Style.Red, Color.Black * 0.5f); paused ? Color.Cyan : GUI.Style.Red, Color.Black * 0.5f);
} }
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2(item.WorldPosition.X, -item.WorldPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition,
GUI.Style.Orange); GUI.Style.Orange);
} }
} }
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character)
{ {
if (DraggingConnected?.Item.Removed ?? false) if (DraggingConnected?.Item?.Removed ?? true)
{ {
DraggingConnected = null; DraggingConnected = null;
} }
@@ -580,14 +580,20 @@ namespace Barotrauma.Items.Components
private void GetAvailablePower(out float availableCharge, out float availableCapacity) private void GetAvailablePower(out float availableCharge, out float availableCapacity)
{ {
var batteries = item.GetConnectedComponents<PowerContainer>();
availableCharge = 0.0f; availableCharge = 0.0f;
availableCapacity = 0.0f; availableCapacity = 0.0f;
foreach (PowerContainer battery in batteries) if (item.Connections == null) { return; }
foreach (Connection c in item.Connections)
{ {
availableCharge += battery.Charge; var recipients = c.Recipients;
availableCapacity += battery.Capacity; foreach (Connection recipient in recipients)
{
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery == null) { continue; }
availableCharge += battery.Charge;
availableCapacity += battery.Capacity;
}
} }
} }
@@ -647,7 +653,9 @@ namespace Barotrauma.Items.Components
bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null); bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null);
if (ShowChargeIndicator && PowerConsumption > 0.0f) if (ShowChargeIndicator && PowerConsumption > 0.0f)
{ {
powerIndicator.Color = charged ? GUI.Style.Green : GUI.Style.Red; powerIndicator.Color = charged ?
(HasPowerToShoot() ? GUI.Style.Green : GUI.Style.Orange) :
GUI.Style.Red;
if (flashLowPower) if (flashLowPower)
{ {
powerIndicator.BarSize = 1; powerIndicator.BarSize = 1;
@@ -10,7 +10,13 @@ namespace Barotrauma.Items.Components
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100); int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100);
if (roundedValue == 0) { return; } if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}";
string afflictionName =
AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ??
TextManager.Get($"afflictiontype.{afflictionIdentifier}", returnNull: true) ??
afflictionIdentifier;
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}";
} }
public override void AddTooltipInfo(ref string name, ref string description) public override void AddTooltipInfo(ref string name, ref string description)
@@ -33,9 +39,9 @@ namespace Barotrauma.Items.Components
{ {
GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); GetDamageModifierText(ref description, damageModifier, afflictionIdentifier);
} }
foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionTypes) foreach (string afflictionType in damageModifier.ParsedAfflictionTypes)
{ {
GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); GetDamageModifierText(ref description, damageModifier, afflictionType);
} }
} }
} }
@@ -1181,7 +1181,7 @@ namespace Barotrauma
} }
texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false)); texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false));
if (CampaignInteractionType != CampaignMode.InteractionType.None) if (CampaignMode.BlocksInteraction(CampaignInteractionType))
{ {
texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false)); texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false));
} }
@@ -1550,6 +1550,7 @@ namespace Barotrauma
Vector2 pos = Vector2.Zero; Vector2 pos = Vector2.Zero;
Submarine sub = null; Submarine sub = null;
float rotation = 0.0f;
int itemContainerIndex = -1; int itemContainerIndex = -1;
int inventorySlotIndex = -1; int inventorySlotIndex = -1;
@@ -1561,7 +1562,7 @@ namespace Barotrauma
else else
{ {
pos = new Vector2(msg.ReadSingle(), msg.ReadSingle()); pos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
rotation = msg.ReadRangedSingle(0, MathHelper.TwoPi, 8);
ushort subID = msg.ReadUInt16(); ushort subID = msg.ReadUInt16();
if (subID > 0) if (subID > 0)
{ {
@@ -130,18 +130,31 @@ namespace Barotrauma
int tilesY = (int)Math.Ceiling(Height / tileSize.Y); int tilesY = (int)Math.Ceiling(Height / tileSize.Y);
mapTiles = new Sprite[tilesX, tilesY]; mapTiles = new Sprite[tilesX, tilesY];
tileDiscovered = new bool[tilesX, tilesY]; tileDiscovered = new bool[tilesX, tilesY];
HashSet<Biome> missingBiomes = new HashSet<Biome>();
for (int x = 0; x < tilesX; x++) for (int x = 0; x < tilesX; x++)
{ {
for (int y = 0; y < tilesY; y++) for (int y = 0; y < tilesY; y++)
{ {
var biome = GetBiome(x * tileSize.X); var biome = GetBiome(x * tileSize.X);
var tileList = generationParams.MapTiles.ContainsKey(biome.Identifier) ? List<Sprite> tileList = null;
generationParams.MapTiles[biome.Identifier] : if (generationParams.MapTiles.ContainsKey(biome.Identifier))
generationParams.MapTiles.Values.First(); {
tileList = generationParams.MapTiles[biome.Identifier];
}
else
{
tileList = generationParams.MapTiles.Values.First();
missingBiomes.Add(biome);
}
mapTiles[x, y] = tileList[x % tileList.Count]; mapTiles[x, y] = tileList[x % tileList.Count];
} }
} }
foreach (var missingBiome in missingBiomes)
{
DebugConsole.ThrowError($"Could not find campaign map sprites for the biome \"{missingBiome.Identifier}\". Using the sprites of the first biome instead...");
}
RemoveFogOfWar(StartLocation); RemoveFogOfWar(StartLocation);
GenerateLocationConnectionVisuals(); GenerateLocationConnectionVisuals();
@@ -772,7 +772,7 @@ namespace Barotrauma
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; }
if (item.flippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } if (item.flippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; }
var wire = item.GetComponent<Wire>(); var wire = item.GetComponent<Wire>();
if (wire != null && !wire.Item.body.Enabled) if (wire != null && wire.Item.body != null && !wire.Item.body.Enabled)
{ {
wire.Draw(spriteBatch, editing: false, new Vector2(moveAmount.X, -moveAmount.Y)); wire.Draw(spriteBatch, editing: false, new Vector2(moveAmount.X, -moveAmount.Y));
continue; continue;
@@ -55,9 +55,9 @@ namespace Barotrauma
if (closestSub != null && subsToMove.Contains(closestSub)) if (closestSub != null && subsToMove.Contains(closestSub))
{ {
GameMain.GameScreen.Cam.Position += moveAmount; GameMain.GameScreen.Cam.Position += moveAmount;
if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) GameMain.GameScreen.Cam.TargetPos += moveAmount; if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) { GameMain.GameScreen.Cam.TargetPos += moveAmount; }
if (Character.Controlled != null) Character.Controlled.CursorPosition += moveAmount; if (Character.Controlled != null) { Character.Controlled.CursorPosition += moveAmount; }
} }
} }
@@ -316,11 +316,11 @@ namespace Barotrauma
var srcRect = prefab.sprite.SourceRect; var srcRect = prefab.sprite.SourceRect;
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
if (flippedX) if (flippedX && ((prefab as ItemPrefab)?.CanSpriteFlipX ?? true))
{ {
spriteEffects |= SpriteEffects.FlipHorizontally; spriteEffects |= SpriteEffects.FlipHorizontally;
} }
if (flippedY) if (flippedY && ((prefab as ItemPrefab)?.CanSpriteFlipY ?? true))
{ {
spriteEffects |= SpriteEffects.FlipVertically; spriteEffects |= SpriteEffects.FlipVertically;
} }
@@ -90,14 +90,14 @@ namespace Barotrauma
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
drawPos, drawPos,
new Vector2(ConnectedGap.WorldPosition.X, -ConnectedGap.WorldPosition.Y), new Vector2(ConnectedGap.DrawPosition.X, -ConnectedGap.DrawPosition.Y),
GUI.Style.Green * 0.5f, width: 1); GUI.Style.Green * 0.5f, width: 1);
} }
if (Ladders != null) if (Ladders != null)
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
drawPos, drawPos,
new Vector2(Ladders.Item.WorldPosition.X, -Ladders.Item.WorldPosition.Y), new Vector2(Ladders.Item.DrawPosition.X, -Ladders.Item.DrawPosition.Y),
GUI.Style.Green * 0.5f, width: 1); GUI.Style.Green * 0.5f, width: 1);
} }
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Items.Components;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -15,7 +16,11 @@ namespace Barotrauma
var entity = FindEntityByID(entityId); var entity = FindEntityByID(entityId);
if (entity != null) if (entity != null)
{ {
DebugConsole.Log("Received entity removal message for \"" + entity.ToString() + "\"."); DebugConsole.Log($"Received entity removal message for \"{entity}\".");
if (entity is Item item && item.Container?.GetComponent<Deconstructor>() != null)
{
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + item.prefab.Identifier);
}
entity.Remove(); entity.Remove();
} }
else else
@@ -28,7 +33,11 @@ namespace Barotrauma
switch (message.ReadByte()) switch (message.ReadByte())
{ {
case (byte)SpawnableType.Item: case (byte)SpawnableType.Item:
Item.ReadSpawnData(message, true); var newItem = Item.ReadSpawnData(message, true);
if (newItem is Item item && item.Container?.GetComponent<Fabricator>() != null)
{
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + item.prefab.Identifier);
}
break; break;
case (byte)SpawnableType.Character: case (byte)SpawnableType.Character:
Character.ReadSpawnData(message); Character.ReadSpawnData(message);
@@ -251,7 +251,7 @@ namespace Barotrauma.CharacterEditor
GUI.ForceMouseOn(null); GUI.ForceMouseOn(null);
if (isEndlessRunner) if (isEndlessRunner)
{ {
Submarine.MainSub.Remove(); Submarine.MainSub?.Remove();
GameMain.World.ProcessChanges(); GameMain.World.ProcessChanges();
isEndlessRunner = false; isEndlessRunner = false;
Reset(); Reset();
@@ -115,7 +115,7 @@ namespace Barotrauma
#if TEST_REMOTE_CONTENT #if TEST_REMOTE_CONTENT
var doc = XMLExtensions.TryLoadXml("Content/UI/MenuTextTest.xml"); var doc = XMLExtensions.TryLoadXml("Content/UI/MenuContent.xml");
if (doc?.Root != null) if (doc?.Root != null)
{ {
foreach (XElement subElement in doc?.Root.Elements()) foreach (XElement subElement in doc?.Root.Elements())
@@ -144,7 +144,7 @@ namespace Barotrauma
private GUIDropDown linkedSubBox; private GUIDropDown linkedSubBox;
private static GUIComponent autoSaveLabel; private static GUIComponent autoSaveLabel;
private static int maxAutoSaves = GameSettings.MaximumAutoSaves; private readonly static int maxAutoSaves = GameSettings.MaximumAutoSaves;
public static readonly object ItemAddMutex = new object(), ItemRemoveMutex = new object(); public static readonly object ItemAddMutex = new object(), ItemRemoveMutex = new object();
@@ -541,7 +541,7 @@ namespace Barotrauma
//----------------------------------------------- //-----------------------------------------------
layerPanel = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUI.Canvas)) layerPanel = new GUIFrame(new RectTransform(new Vector2(0.25f, 0.4f), GUI.Canvas, minSize: new Point(300, 320)))
{ {
Visible = false Visible = false
}; };
@@ -604,11 +604,13 @@ namespace Barotrauma
RenameLayer(layer, newName); RenameLayer(layer, newName);
}); });
} }
return true; return true;
} }
}; };
GUITextBlock.AutoScaleAndNormalize(layerAddButton.TextBlock, layerDeleteButton.TextBlock, layerRenameButton.TextBlock);
Vector2 subPanelSize = new Vector2(0.925f, 0.9f); Vector2 subPanelSize = new Vector2(0.925f, 0.9f);
undoBufferPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) }) undoBufferPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) })
@@ -4433,9 +4435,9 @@ namespace Barotrauma
layerList.Deselect(); layerList.Deselect();
GUILayoutGroup buttonHeaders = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), layerList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomLeft); GUILayoutGroup buttonHeaders = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), layerList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomLeft);
new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headervisible"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true }; new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headervisible"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = true };
new GUIButton(new RectTransform(new Vector2(0.15f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headerlink"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true }; new GUIButton(new RectTransform(new Vector2(0.15f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headerlink"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = true };
new GUIButton(new RectTransform(new Vector2(0.65f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true }; new GUIButton(new RectTransform(new Vector2(0.6f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = true };
foreach (var (layer, (visibility, linkage)) in Layers) foreach (var (layer, (visibility, linkage)) in Layers)
{ {
@@ -4494,6 +4496,17 @@ namespace Barotrauma
layerList.RecalculateChildren(); layerList.RecalculateChildren();
buttonHeaders.Recalculate(); buttonHeaders.Recalculate();
foreach (var child in buttonHeaders.Children)
{
var btn = child as GUIButton;
string originalBtnText = btn.Text;
btn.Text = ToolBox.LimitString(btn.Text, btn.Font, btn.Rect.Width);
if (originalBtnText != btn.Text)
{
btn.ToolTip = originalBtnText;
}
}
} }
public void UpdateUndoHistoryPanel() public void UpdateUndoHistoryPanel()
@@ -1178,7 +1178,7 @@ namespace Barotrauma
if ((s.damageRange == Vector2.Zero || if ((s.damageRange == Vector2.Zero ||
(damage >= s.damageRange.X && damage <= s.damageRange.Y)) && (damage >= s.damageRange.X && damage <= s.damageRange.Y)) &&
string.Equals(s.damageType, damageType, StringComparison.OrdinalIgnoreCase) && string.Equals(s.damageType, damageType, StringComparison.OrdinalIgnoreCase) &&
(tags == null ? string.IsNullOrEmpty(s.requiredTag) : tags.Contains(s.requiredTag))) (string.IsNullOrEmpty(s.requiredTag) || (tags == null ? string.IsNullOrEmpty(s.requiredTag) : tags.Contains(s.requiredTag))))
{ {
tempList.Add(s); tempList.Add(s);
} }
@@ -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.16.2.0</Version> <Version>0.16.3.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.16.2.0</Version> <Version>0.16.3.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.16.2.0</Version> <Version>0.16.3.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.16.2.0</Version> <Version>0.16.3.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.16.2.0</Version> <Version>0.16.3.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>
@@ -252,6 +252,7 @@ namespace Barotrauma
msg.Write(Position.X); msg.Write(Position.X);
msg.Write(Position.Y); msg.Write(Position.Y);
msg.WriteRangedSingle(body == null ? 0.0f : MathUtils.WrapAngleTwoPi(body.Rotation), 0.0f, MathHelper.TwoPi, 8);
msg.Write(Submarine != null ? Submarine.ID : (ushort)0); msg.Write(Submarine != null ? Submarine.ID : (ushort)0);
} }
else else
@@ -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.16.2.0</Version> <Version>0.16.3.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>
@@ -207,8 +207,10 @@ namespace Barotrauma
} = new HashSet<Submarine>(); } = new HashSet<Submarine>();
public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget); public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget);
public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack); public static bool IsTargetBeingChasedBy(Character target, Character character)
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter); => character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && (enemyAI.State == AIState.Attack || enemyAI.State == AIState.Aggressive);
public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c);
private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character);
private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam; private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
@@ -1322,7 +1324,7 @@ namespace Barotrauma
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2); Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true); Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (Submarine.LastPickedFraction != 1.0f && closestBody != null && if (Submarine.LastPickedFraction != 1.0f && closestBody != null &&
(!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null)) ((!AIParams.TargetOuterWalls || !canAttackWalls) && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
{ {
// Target is unreachable, there's a door or wall ahead // Target is unreachable, there's a door or wall ahead
State = AIState.Idle; State = AIState.Idle;
@@ -2385,6 +2387,24 @@ namespace Barotrauma
} }
#region Targeting #region Targeting
public static bool IsLatchedTo(Character target, Character character)
{
if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null)
{
return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter == character;
}
return false;
}
public static bool IsLatchedToSomeoneElse(Character target, Character character)
{
if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null)
{
return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter != null && enemyAI.LatchOntoAI.TargetCharacter != character;
}
return false;
}
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub; private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
//goes through all the AItargets, evaluates how preferable it is to attack the target, //goes through all the AItargets, evaluates how preferable it is to attack the target,
@@ -2398,6 +2418,7 @@ namespace Barotrauma
targetingParams = null; targetingParams = null;
bool isAnyTargetClose = false; bool isAnyTargetClose = false;
bool isBeingChased = IsBeingChased; bool isBeingChased = IsBeingChased;
float maxModifier = 5;
foreach (AITarget aiTarget in AITarget.List) foreach (AITarget aiTarget in AITarget.List)
{ {
if (aiTarget.InDetectable) { continue; } if (aiTarget.InDetectable) { continue; }
@@ -2537,12 +2558,12 @@ namespace Barotrauma
{ {
if (CanPassThroughHole(s, i)) if (CanPassThroughHole(s, i))
{ {
valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0; valueModifier *= leadsInside ? (IsAggressiveBoarder ? maxModifier : 1) : 0;
} }
else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls) else if (IsAggressiveBoarder && leadsInside && canAttackWalls)
{ {
// Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside // Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open * 0.25f; valueModifier *= 1 + section.gap.Open;
} }
} }
else else
@@ -2580,6 +2601,7 @@ namespace Barotrauma
// We are actually interested in breaking things -> reduce the priority when the wall is already broken // We are actually interested in breaking things -> reduce the priority when the wall is already broken
// (Terminalcells) // (Terminalcells)
valueModifier *= 1 - section.gap.Open * 0.25f; valueModifier *= 1 - section.gap.Open * 0.25f;
valueModifier = Math.Max(valueModifier, 0.1f);
} }
} }
} }
@@ -2599,6 +2621,7 @@ namespace Barotrauma
valueModifier *= 1 + section.gap.Open; valueModifier *= 1 + section.gap.Open;
} }
} }
valueModifier = Math.Clamp(valueModifier, 0, maxModifier);
} }
} }
if (door != null) if (door != null)
@@ -2610,7 +2633,7 @@ namespace Barotrauma
bool isOpen = door.CanBeTraversed; bool isOpen = door.CanBeTraversed;
if (!isOpen) if (!isOpen)
{ {
if (!canAttackDoors || isOutdoor && !AIParams.TargetOuterWalls) { continue; } if (!canAttackDoors) { continue; }
} }
else if (!Character.AnimController.CanEnterSubmarine) else if (!Character.AnimController.CanEnterSubmarine)
{ {
@@ -2624,11 +2647,11 @@ namespace Barotrauma
// Increase the priority if the character is outside and the door is from outside to inside // Increase the priority if the character is outside and the door is from outside to inside
if (door.CanBeTraversed) if (door.CanBeTraversed)
{ {
valueModifier = 3; valueModifier = maxModifier;
} }
else if (door.LinkedGap != null) else if (door.LinkedGap != null)
{ {
valueModifier = 1 + door.LinkedGap.Open; valueModifier = 1 + door.LinkedGap.Open * (maxModifier - 1);
} }
} }
else else
@@ -2727,6 +2750,37 @@ namespace Barotrauma
if (SelectedAiTarget == aiTarget) if (SelectedAiTarget == aiTarget)
{ {
if (Character.Submarine == null && aiTarget.Entity is ISpatialEntity spatialEntity && spatialEntity.Submarine != null)
{
if (targetingTag == "door" || targetingTag == "wall")
{
Vector2 rayStart = Character.SimPosition;
Vector2 rayEnd = aiTarget.SimPosition + spatialEntity.Submarine.SimPosition;
Body closestBody = Submarine.PickBody(rayStart, rayEnd, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true);
if (closestBody != null && closestBody.UserData is ISpatialEntity hit)
{
Vector2 hitPos = hit.SimPosition;
if (closestBody.UserData is Submarine)
{
hitPos = Submarine.LastPickedPosition;
}
else if (hit.Submarine != null)
{
hitPos += hit.Submarine.SimPosition;
}
float subHalfWidth = spatialEntity.Submarine.Borders.Width / 2;
float subHalfHeight = spatialEntity.Submarine.Borders.Height / 2;
Vector2 diff = ConvertUnits.ToDisplayUnits(rayEnd - hitPos);
bool isOtherSideOfTheSub = Math.Abs(diff.X) > subHalfWidth || Math.Abs(diff.Y) > subHalfHeight;
if (isOtherSideOfTheSub)
{
IgnoreTarget(aiTarget);
ResetAITarget();
continue;
}
}
}
}
// Stick to the current target // Stick to the current target
valueModifier *= 1.1f; valueModifier *= 1.1f;
} }
@@ -2757,19 +2811,22 @@ namespace Barotrauma
} }
} }
if (targetParams.AttackPattern == AttackPattern.Circle) if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && targetCharacter == null)
{ {
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose) if (targetParams.AttackPattern == AttackPattern.Circle || targetParams.AttackPattern == AttackPattern.Sweep)
{ {
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine)) if (!isAnyTargetClose)
{ {
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable. if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X); {
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter)); // Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
} float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
else dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
{ }
dist *= 5; else if (targetParams.AttackPattern == AttackPattern.Circle)
{
dist *= 5;
}
} }
} }
} }
@@ -2936,9 +2993,9 @@ namespace Barotrauma
if (HasValidPath(requireNonDirty: true)) { return; } if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear(); wallHits.Clear();
Structure wall = null; Structure wall = null;
Vector2 rayStart = AttackingLimb != null ? AttackingLimb.SimPosition : SimPosition;
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target)) if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
{ {
Vector2 rayStart = SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition; Vector2 rayEnd = SelectedAiTarget.SimPosition;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{ {
@@ -2952,7 +3009,6 @@ namespace Barotrauma
} }
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading)) if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading))
{ {
Vector2 rayStart = SimPosition;
Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5); Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5);
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{ {
@@ -2968,7 +3024,6 @@ namespace Barotrauma
} }
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering)) if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering))
{ {
Vector2 rayStart = SimPosition;
Vector2 rayEnd = rayStart + Steering * 5; Vector2 rayEnd = rayStart + Steering * 5;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{ {
@@ -3021,6 +3076,7 @@ namespace Barotrauma
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces. // Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces.
if (!isTargetingDoor) if (!isTargetingDoor)
{ {
IgnoreTarget(SelectedAiTarget);
ResetAITarget(); ResetAITarget();
} }
} }
@@ -3032,6 +3088,7 @@ namespace Barotrauma
else else
{ {
// Blocked by a disabled wall. // Blocked by a disabled wall.
IgnoreTarget(SelectedAiTarget);
ResetAITarget(); ResetAITarget();
} }
} }
@@ -3082,8 +3139,17 @@ namespace Barotrauma
if (!(hit.UserData is Structure w)) { return false; } if (!(hit.UserData is Structure w)) { return false; }
if (w.Submarine == null) { return false; } if (w.Submarine == null) { return false; }
if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; } if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; }
if (Character.Submarine == null && w.prefab.Tags.Contains("inner")) { return false; } if (Character.Submarine == null)
if (!AIParams.TargetOuterWalls && !w.prefab.Tags.Contains("inner")) { return false; } {
if (w.prefab.Tags.Contains("inner"))
{
if (!Character.AnimController.CanEnterSubmarine) { return false; }
}
else if (!AIParams.TargetOuterWalls)
{
return false;
}
}
wall = w; wall = w;
return true; return true;
} }
@@ -3120,7 +3186,8 @@ namespace Barotrauma
{ {
if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth)) if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth))
{ {
return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: 100); float maxDistance = Math.Max(ConvertUnits.ToDisplayUnits(colliderLength), 100);
return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: maxDistance);
} }
} }
} }
@@ -3584,12 +3651,12 @@ namespace Barotrauma
public override bool SteerThroughGap(Gap gap, Vector2 targetWorldPos, float deltaTime, float maxDistance = -1) public override bool SteerThroughGap(Gap gap, Vector2 targetWorldPos, float deltaTime, float maxDistance = -1)
{ {
wallTarget = null;
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
Character.AnimController.ReleaseStuckLimbs();
bool success = base.SteerThroughGap(gap, targetWorldPos, deltaTime, maxDistance); bool success = base.SteerThroughGap(gap, targetWorldPos, deltaTime, maxDistance);
if (success) if (success)
{ {
wallTarget = null;
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
Character.AnimController.ReleaseStuckLimbs();
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1); SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
} }
IsSteeringThroughGap = success; IsSteeringThroughGap = success;
@@ -20,7 +20,6 @@ namespace Barotrauma
private float reactTimer; private float reactTimer;
private float unreachableClearTimer; private float unreachableClearTimer;
private bool shouldCrouch; private bool shouldCrouch;
public bool IsInsideCave { get; private set; }
/// <summary> /// <summary>
/// Resets each frame /// Resets each frame
/// </summary> /// </summary>
@@ -58,14 +57,14 @@ namespace Barotrauma
private float obstacleRaycastTimer; private float obstacleRaycastTimer;
private readonly float enemyCheckInterval = 0.2f; private readonly float enemyCheckInterval = 0.2f;
private readonly float enemySpotDistanceOutside = 1500; private readonly float enemySpotDistanceOutside = 800;
private readonly float enemySpotDistanceInside = 1000; private readonly float enemySpotDistanceInside = 1000;
private float enemycheckTimer; private float enemycheckTimer;
/// <summary> /// <summary>
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity. /// How far other characters can hear reports done by this character (e.g. reports for fires, intruders).
/// </summary> /// </summary>
public float ReportRange { get; set; } = float.PositiveInfinity; public float ReportRange { get; set; }
private float _aimSpeed = 1; private float _aimSpeed = 1;
public float AimSpeed public float AimSpeed
@@ -167,6 +166,7 @@ namespace Barotrauma
objectiveManager = new AIObjectiveManager(c); objectiveManager = new AIObjectiveManager(c);
reactTimer = GetReactionTime(); reactTimer = GetReactionTime();
SortTimer = Rand.Range(0f, sortObjectiveInterval); SortTimer = Rand.Range(0f, sortObjectiveInterval);
ReportRange = Character.IsOnPlayerTeam ? float.PositiveInfinity : 1000;
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
@@ -306,7 +306,7 @@ namespace Barotrauma
UseIndoorSteeringOutside = false; UseIndoorSteeringOutside = false;
} }
if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID))
{ {
// Spot enemies while staying outside or inside an enemy ship. // Spot enemies while staying outside or inside an enemy ship.
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior // does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
@@ -327,9 +327,13 @@ namespace Barotrauma
float dist = toTarget.LengthSquared(); float dist = toTarget.LengthSquared();
float maxDistance = Character.Submarine == null ? enemySpotDistanceOutside : enemySpotDistanceInside; float maxDistance = Character.Submarine == null ? enemySpotDistanceOutside : enemySpotDistanceInside;
if (dist > maxDistance * maxDistance) { continue; } if (dist > maxDistance * maxDistance) { continue; }
Vector2 forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation); if (EnemyAIController.IsLatchedToSomeoneElse(c, Character)) { continue; }
forward.X *= Character.AnimController.Dir; var head = Character.AnimController.GetLimb(LimbType.Head);
if (Vector2.Dot(toTarget, forward) < 0.2f) { continue; } if (head == null) { continue; }
float rotation = head.body.TransformedRotation;
Vector2 forward = VectorExtensions.Forward(rotation);
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(toTarget, forward));
if (angle > 70) { continue; }
if (!Character.CanSeeCharacter(c)) { continue; } if (!Character.CanSeeCharacter(c)) { continue; }
if (dist < closestDistance || closestEnemy == null) if (dist < closestDistance || closestEnemy == null)
{ {
@@ -345,8 +349,6 @@ namespace Barotrauma
} }
} }
IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave;
if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer)) if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer))
{ {
if (steeringManager != insideSteering) if (steeringManager != insideSteering)
@@ -1242,7 +1244,7 @@ namespace Barotrauma
{ {
//if the other character did not witness the attack, and the character is not within report range (or capable of reporting) //if the other character did not witness the attack, and the character is not within report range (or capable of reporting)
//don't react to the attack //don't react to the attack
if (Character.IsDead || Character.IsUnconscious || !CheckReportRange(Character, otherCharacter, ReportRange)) if (Character.IsDead || Character.IsUnconscious || otherCharacter.TeamID != Character.TeamID || !CheckReportRange(Character, otherCharacter, ReportRange))
{ {
continue; continue;
} }
@@ -1259,8 +1261,8 @@ namespace Barotrauma
{ {
if (Character.Submarine == null) if (Character.Submarine == null)
{ {
// Outside -> don't react. // Outside
return AIObjectiveCombat.CombatMode.None; return attacker.Submarine == null ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
} }
if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine)) if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{ {
@@ -1852,7 +1854,7 @@ namespace Barotrauma
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>(); bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character); bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character); bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.GetActiveObjectives<AIObjectiveFightIntruders>().Any(); bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull) if (isCurrentHull)
{ {
@@ -52,7 +52,7 @@ namespace Barotrauma
// The validity changes when a character picks the item up. // The validity changes when a character picks the item up.
if (!IsValidTarget(target, character, checkInventory: true)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); } if (!IsValidTarget(target, character, checkInventory: true)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
if (target.CurrentHull.FireSources.Count > 0) { return false; } if (target.CurrentHull.FireSources.Count > 0) { return false; }
// Don't repair items in rooms that have enemies inside. // Don't clean up items in rooms that have enemies inside.
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; } if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
return true; return true;
} }
@@ -117,7 +117,10 @@ namespace Barotrauma
private float AimSpeed => HumanAIController.AimSpeed; private float AimSpeed => HumanAIController.AimSpeed;
private float AimAccuracy => HumanAIController.AimAccuracy; private float AimAccuracy => HumanAIController.AimAccuracy;
private bool EnemyIsClose() => Enemy != null && Enemy.CurrentHull != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < 300; private bool IsEnemyCloserThan(float margin) =>
Enemy != null && Enemy.CurrentHull != null &&
character.InWater && Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition) < margin * margin ||
HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < margin;
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f) public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
: base(character, objectiveManager, priorityModifier) : base(character, objectiveManager, priorityModifier)
@@ -144,12 +147,19 @@ namespace Barotrauma
Mode = CombatMode.Retreat; Mode = CombatMode.Retreat;
} }
spreadTimer = Rand.Range(-10f, 10f); spreadTimer = Rand.Range(-10f, 10f);
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
HumanAIController.SortTimer = 0; HumanAIController.SortTimer = 0;
} }
protected override float GetPriority() protected override float GetPriority()
{ {
if (character.TeamID == CharacterTeamType.FriendlyNPC && Enemy != null) if (Enemy == null)
{
Priority = 0;
Abandon = true;
return Priority;
}
if (character.TeamID == CharacterTeamType.FriendlyNPC)
{ {
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine)) if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
{ {
@@ -160,6 +170,13 @@ namespace Barotrauma
} }
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f); float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100); Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
if (Priority > 0)
{
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
{
Priority = 0;
}
}
return Priority; return Priority;
} }
@@ -366,7 +383,7 @@ namespace Barotrauma
} }
} }
} }
bool isAllowedToSeekWeapons = character.CurrentHull != null && !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest; bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
if (!isAllowedToSeekWeapons) if (!isAllowedToSeekWeapons)
{ {
if (WeaponComponent == null) if (WeaponComponent == null)
@@ -418,9 +435,16 @@ namespace Barotrauma
onCompleted: () => RemoveSubObjective(ref seekWeaponObjective), onCompleted: () => RemoveSubObjective(ref seekWeaponObjective),
onAbandon: () => onAbandon: () =>
{ {
SpeakNoWeapons();
RemoveSubObjective(ref seekWeaponObjective); RemoveSubObjective(ref seekWeaponObjective);
Mode = CombatMode.Retreat; if (Weapon == null)
{
SpeakNoWeapons();
Mode = CombatMode.Retreat;
}
else
{
Mode = CombatMode.Defensive;
}
}); });
} }
} }
@@ -478,13 +502,25 @@ namespace Barotrauma
weaponComponent = null; weaponComponent = null;
float bestPriority = 0; float bestPriority = 0;
float lethalDmg = -1; float lethalDmg = -1;
bool enemyIsClose = EnemyIsClose(); bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300);
bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character);
foreach (var weapon in weaponList) foreach (var weapon in weaponList)
{ {
float priority = weapon.CombatPriority; float priority = weapon.CombatPriority;
if (prioritizeMelee)
{
if (weapon is MeleeWeapon)
{
priority *= 5;
}
else
{
priority /= 2;
}
}
if (!weapon.IsLoaded(character)) if (!weapon.IsLoaded(character))
{ {
if (weapon is RangedWeapon && enemyIsClose) if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
{ {
// Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo). // Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo).
continue; continue;
@@ -693,7 +729,7 @@ namespace Barotrauma
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s)); var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
if (character.Inventory.TryPutItem(Weapon, character, slots)) if (character.Inventory.TryPutItem(Weapon, character, slots))
{ {
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed; SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
} }
else else
{ {
@@ -1014,7 +1050,7 @@ namespace Barotrauma
} }
if (!canSeeTarget) if (!canSeeTarget)
{ {
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed; SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
return; return;
} }
if (Weapon.RequireAimToUse) if (Weapon.RequireAimToUse)
@@ -1074,7 +1110,7 @@ namespace Barotrauma
else if (!character.IsFacing(Enemy.WorldPosition)) else if (!character.IsFacing(Enemy.WorldPosition))
{ {
// Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around. // Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around.
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed; SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
} }
} }
else else
@@ -1190,5 +1226,7 @@ namespace Barotrauma
} }
} }
} }
private void SetAimTimer(float newTimer) => aimTimer = Math.Max(aimTimer, newTimer);
} }
} }
@@ -120,7 +120,7 @@ namespace Barotrauma
} }
if (character.CanInteractWith(container.Item, checkLinked: false)) if (character.CanInteractWith(container.Item, checkLinked: false))
{ {
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item))) if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(ItemToContain)))
{ {
HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax); HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax);
} }
@@ -70,6 +70,7 @@ namespace Barotrauma
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; } if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; } if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (target.IsArrested) { return false; } if (target.IsArrested) { return false; }
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
return true; return true;
} }
@@ -108,7 +108,7 @@ namespace Barotrauma
AllowToFindDivingGear = false, AllowToFindDivingGear = false,
AllowDangerousPressure = true, AllowDangerousPressure = true,
ConditionLevel = MIN_OXYGEN, ConditionLevel = MIN_OXYGEN,
RemoveExisting = true RemoveExistingWhenNecessary = true
}; };
}, },
onAbandon: () => onAbandon: () =>
@@ -76,6 +76,10 @@ namespace Barotrauma
// -> ignore find safety unless we need to find a diving gear // -> ignore find safety unless we need to find a diving gear
Priority = 0; Priority = 0;
} }
else if (objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
{
Priority = 0;
}
Priority = MathHelper.Clamp(Priority, 0, 100); Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted) if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
{ {
@@ -401,6 +401,11 @@ namespace Barotrauma
{ {
if (!ownerItem.IsInteractable(character)) { continue; } if (!ownerItem.IsInteractable(character)) { continue; }
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; } if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
if (ownerItem != item.Container)
{
itemPriority *= 0.1f;
}
} }
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition; Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y); float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
@@ -77,6 +77,9 @@ namespace Barotrauma
// TODO: Currently we never check the visibility (to the end node), which is actually unintentional. // TODO: Currently we never check the visibility (to the end node), which is actually unintentional.
// I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better. // I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better.
// However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true. // However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true.
// NOTE: This seemes to have caused an issue now Regalis11/Barotrauma#8067: namely, the bot was trying to use a waypoint that was obstructed by a shuttle
// because obstruction was only checked when checking visibility in PathFinder. Changed that so that obstructed nodes are no longer used.
public bool CheckVisibility { get; set; } public bool CheckVisibility { get; set; }
public bool IgnoreIfTargetDead { get; set; } public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; } public bool AllowGoingOutside { get; set; }
@@ -7,11 +7,11 @@ namespace Barotrauma
class AIObjectiveReturn : AIObjective class AIObjectiveReturn : AIObjective
{ {
public override string Identifier { get; set; } = "return"; public override string Identifier { get; set; } = "return";
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
private bool usingEscapeBehavior;
private bool isSteeringThroughGap;
public Submarine ReturnTarget { get; } public Submarine ReturnTarget { get; }
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
private bool usingEscapeBehavior, isSteeringThroughGap;
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
{ {
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded); ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
@@ -112,7 +112,6 @@ namespace Barotrauma
} }
if (targetHull != null) if (targetHull != null)
{ {
RemoveSubObjective(ref moveInCaveObjective);
RemoveSubObjective(ref moveOutsideObjective); RemoveSubObjective(ref moveOutsideObjective);
TryAddSubObjective(ref moveInsideObjective, TryAddSubObjective(ref moveInsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
@@ -137,91 +136,41 @@ namespace Barotrauma
IsCompleted = true; IsCompleted = true;
} }
} }
else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null) else if (!isSteeringThroughGap && moveOutsideObjective == null)
{ {
if (HumanAIController.IsInsideCave) Hull targetHull = null;
float targetDistanceSquared = float.MaxValue;
bool targetIsAirlock = false;
foreach (var hull in ReturnTarget.GetHulls(false))
{ {
WayPoint closestOutsideWaypoint = null; bool hullIsAirlock = hull.IsTaggedAirlock();
float closestDistance = float.MaxValue; if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
foreach (var w in WayPoint.WayPointList)
{ {
if (w.Tunnel != null && w.Tunnel.Type == Level.TunnelType.Cave) { continue; } float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; } if (targetHull == null || distanceSquared < targetDistanceSquared)
float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition);
if (closestOutsideWaypoint == null || distance < closestDistance)
{ {
closestOutsideWaypoint = w; targetHull = hull;
closestDistance = distance; targetDistanceSquared = distanceSquared;
targetIsAirlock = hullIsAirlock;
} }
} }
if (closestOutsideWaypoint != null) }
{ if (targetHull != null)
RemoveSubObjective(ref moveInsideObjective); {
RemoveSubObjective(ref moveOutsideObjective); RemoveSubObjective(ref moveInsideObjective);
TryAddSubObjective(ref moveInCaveObjective, TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{ {
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint, AllowGoingOutside = true
AllowGoingOutside = true },
}, onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective), onAbandon: () => Abandon = true);
onAbandon: () => Abandon = true);
}
else
{
#if DEBUG
DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'");
#endif
}
} }
else else
{ {
Hull targetHull = null;
float targetDistanceSquared = float.MaxValue;
bool targetIsAirlock = false;
foreach (var hull in ReturnTarget.GetHulls(false))
{
bool hullIsAirlock = hull.IsTaggedAirlock();
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
{
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
if (targetHull == null || distanceSquared < targetDistanceSquared)
{
targetHull = hull;
targetDistanceSquared = distanceSquared;
targetIsAirlock = hullIsAirlock;
}
}
}
if (targetHull != null)
{
RemoveSubObjective(ref moveInsideObjective);
RemoveSubObjective(ref moveInCaveObjective);
TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onAbandon: () => Abandon = true);
}
else
{
#if DEBUG #if DEBUG
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'"); DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'");
#endif #endif
}
}
}
else
{
if (HumanAIController.IsInsideCave)
{
RemoveSubObjective(ref moveOutsideObjective);
}
else
{
RemoveSubObjective(ref moveInCaveObjective);
} }
} }
usingEscapeBehavior = shouldUseEscapeBehavior; usingEscapeBehavior = shouldUseEscapeBehavior;
@@ -249,7 +198,6 @@ namespace Barotrauma
{ {
base.Reset(); base.Reset();
moveInsideObjective = null; moveInsideObjective = null;
moveInCaveObjective = null;
moveOutsideObjective = null; moveOutsideObjective = null;
usingEscapeBehavior = false; usingEscapeBehavior = false;
isSteeringThroughGap = false; isSteeringThroughGap = false;
@@ -333,7 +333,6 @@ namespace Barotrauma
//if searching for a path inside the sub, make sure the waypoint is visible //if searching for a path inside the sub, make sure the waypoint is visible
if (checkVisibility && isCharacter) if (checkVisibility && isCharacter)
{ {
if (node.Waypoint.isObstructed) { return false; }
var body = Submarine.PickBody(rayStart, node.TempPosition, var body = Submarine.PickBody(rayStart, node.TempPosition,
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
if (body != null) if (body != null)
@@ -350,6 +349,7 @@ namespace Barotrauma
{ {
if (nodeFilter != null && !nodeFilter(node)) { return false; } if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (startNodeFilter != null && !startNodeFilter(node)) { return false; } if (startNodeFilter != null && !startNodeFilter(node)) { return false; }
if (node.Waypoint.isObstructed) { return false; }
// Always check the visibility for the start node // Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { return false; } if (!IsWaypointVisible(node, start)) { return false; }
if (node.IsBlocked()) { return false; } if (node.IsBlocked()) { return false; }
@@ -364,6 +364,7 @@ namespace Barotrauma
{ {
if (nodeFilter != null && !nodeFilter(node)) { return false; } if (nodeFilter != null && !nodeFilter(node)) { return false; }
if (endNodeFilter != null && !endNodeFilter(node)) { return false; } if (endNodeFilter != null && !endNodeFilter(node)) { return false; }
if (node.Waypoint.isObstructed) { return false; }
// Only check the visibility for the end node when allowed (fix leaks) // Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; } if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; }
if (node.IsBlocked()) { return false; } if (node.IsBlocked()) { return false; }
@@ -776,8 +776,8 @@ namespace Barotrauma
if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); } if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
limbDiff = Vector2.Normalize(limbDiff); limbDiff = Vector2.Normalize(limbDiff);
float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass; float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f); limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * Math.Min(mass, limbJoint.BodyA.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f); limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * Math.Min(mass, limbJoint.BodyB.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
connectedLimbs.Clear(); connectedLimbs.Clear();
checkedJoints.Clear(); checkedJoints.Clear();
@@ -461,7 +461,7 @@ namespace Barotrauma
ReloadAfflictions(element); ReloadAfflictions(element);
} }
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null) public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
{ {
Character targetCharacter = target as Character; Character targetCharacter = target as Character;
if (OnlyHumans) if (OnlyHumans)
@@ -486,10 +486,10 @@ namespace Barotrauma
foreach (StatusEffect effect in statusEffects) foreach (StatusEffect effect in statusEffects)
{ {
effect.sourceBody = sourceBody; effect.sourceBody = sourceBody;
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
if (effect.HasTargetType(StatusEffect.TargetType.This)) if (effect.HasTargetType(StatusEffect.TargetType.This))
{ {
effect.Apply(effectType, deltaTime, attacker, attacker, worldPosition); // TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity, worldPosition);
} }
if (targetCharacter != null) if (targetCharacter != null)
{ {
@@ -526,7 +526,7 @@ namespace Barotrauma
} }
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>(); readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null) public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
{ {
if (targetLimb == null) if (targetLimb == null)
{ {
@@ -553,7 +553,7 @@ namespace Barotrauma
effect.sourceBody = sourceBody; effect.sourceBody = sourceBody;
if (effect.HasTargetType(StatusEffect.TargetType.This)) if (effect.HasTargetType(StatusEffect.TargetType.This))
{ {
effect.Apply(effectType, deltaTime, attacker, attacker); effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
} }
if (effect.HasTargetType(StatusEffect.TargetType.Character)) if (effect.HasTargetType(StatusEffect.TargetType.Character))
{ {
@@ -503,7 +503,7 @@ namespace Barotrauma
get { return cursorPosition; } get { return cursorPosition; }
set set
{ {
if (!MathUtils.IsValid(value)) return; if (!MathUtils.IsValid(value)) { return; }
cursorPosition = value; cursorPosition = value;
} }
} }
@@ -853,7 +853,7 @@ namespace Barotrauma
} }
else else
{ {
return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC; return IsKnockedDown || LockHands || IsBot && IsOnPlayerTeam;
} }
} }
set { canInventoryBeAccessed = value; } set { canInventoryBeAccessed = value; }
@@ -3596,8 +3596,12 @@ namespace Barotrauma
foreach (LimbJoint joint in AnimController.LimbJoints) foreach (LimbJoint joint in AnimController.LimbJoints)
{ {
if (!joint.CanBeSevered) { continue; } if (!joint.CanBeSevered) { 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. // Limb A is where we start creating the joint and LimbB is where the joint ends.
if (joint.LimbB != targetLimb) { continue; } // Normally the joints have been created starting from the body, in which case we'd want to use LimbB e.g. to severe a hand when it's hit.
// But heads are a different case, because many characters have been created so that the head is first and then comes the rest of the body.
// If this is the case, we'll have to use LimbA to decapitate the creature when it's hit on the head. Otherwise decapitation could happen only when we hit the body, not the head.
var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB;
if (referenceLimb != targetLimb) { continue; }
float probability = severLimbsProbability; float probability = severLimbsProbability;
if (!IsDead) if (!IsDead)
{ {
@@ -1134,6 +1134,10 @@ namespace Barotrauma
{ {
head.HairWithHatElement = hairs[hairWithHatIndex]; head.HairWithHatElement = hairs[hairWithHatIndex];
} }
else
{
head.HairWithHatElement = null;
}
} }
if (IsValidIndex(Head.BeardIndex, beards)) if (IsValidIndex(Head.BeardIndex, beards))
@@ -246,7 +246,7 @@ namespace Barotrauma
if (huskPrefab.ControlHusk) if (huskPrefab.ControlHusk)
{ {
#if SERVER #if SERVER
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.CharacterInfo.Character == character); var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
if (client != null) if (client != null)
{ {
GameMain.Server.SetClientCharacter(client, husk); GameMain.Server.SetClientCharacter(client, husk);
@@ -18,7 +18,7 @@ namespace Barotrauma
public void IncreaseSkill(float value, bool increasePastMax) public void IncreaseSkill(float value, bool increasePastMax)
{ {
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumOlympianSkill : MaximumSkill); level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumSkillWithTalents : MaximumSkill);
} }
private Sprite icon; private Sprite icon;
@@ -556,6 +556,7 @@ namespace Barotrauma
// TODO: We might need this or solve the cases where a limb is severed while holding on to an item // TODO: We might need this or solve the cases where a limb is severed while holding on to an item
//if (character.Params.CanInteract) { return false; } //if (character.Params.CanInteract) { return false; }
if (this == character.AnimController.MainLimb) { return false; } if (this == character.AnimController.MainLimb) { return false; }
bool canBeSevered = Params.CanBeSeveredAlive;
if (character.AnimController.CanWalk) if (character.AnimController.CanWalk)
{ {
switch (type) switch (type)
@@ -571,7 +572,7 @@ namespace Barotrauma
return false; return false;
} }
} }
return true; return canBeSevered;
} }
} }
@@ -1070,7 +1071,7 @@ namespace Barotrauma
#endif #endif
if (damageTarget is Character targetCharacter && targetLimb != null) if (damageTarget is Character targetCharacter && targetLimb != null)
{ {
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body); attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body, this);
} }
else else
{ {
@@ -1080,7 +1081,7 @@ namespace Barotrauma
} }
else else
{ {
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body); attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body, this);
} }
} }
/*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server)) /*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
@@ -648,6 +648,9 @@ namespace Barotrauma
[Serialize(1f, true, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)] [Serialize(1f, true, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)]
public float MinSeveranceDamage { get; set; } public float MinSeveranceDamage { get; set; }
[Serialize(true, true, description: "Disable if you don't want to allow severing this joint while the creature is alive. Note: Does nothing if the 'Severance Probability Modifier' in the joint settings is 0 (default). Also note that the setting doesn't override certain limitations, e.g. severing the main limb, or legs of a walking creature is not allowed."), Editable]
public bool CanBeSeveredAlive { get; set; }
//how long it takes for severed limbs to fade out //how long it takes for severed limbs to fade out
[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;
@@ -96,8 +96,8 @@ namespace Barotrauma
set; set;
} }
[Serialize(500.0f, true)] [Serialize(200.0f, true)]
public float MaximumOlympianSkill public float MaximumSkillWithTalents
{ {
get; get;
set; set;
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
@@ -7,9 +8,26 @@ namespace Barotrauma.Abilities
{ {
private readonly List<TargetType> targetTypes; private readonly List<TargetType> targetTypes;
private List<PropertyConditional> conditionals = new List<PropertyConditional>();
public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{ {
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true)); targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
foreach (XElement subElement in conditionElement.Elements())
{
if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase))
{
foreach (XAttribute attribute in subElement.Attributes())
{
if (PropertyConditional.IsValid(attribute))
{
conditionals.Add(new PropertyConditional(attribute));
}
}
break;
}
}
} }
protected override bool MatchesConditionSpecific(AbilityObject abilityObject) protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
@@ -18,7 +36,10 @@ namespace Barotrauma.Abilities
{ {
if (!(abilityCharacter.Character is Character character)) { return false; } if (!(abilityCharacter.Character is Character character)) { return false; }
if (!IsViableTarget(targetTypes, character)) { return false; } if (!IsViableTarget(targetTypes, character)) { return false; }
foreach (var conditional in conditionals)
{
if (!conditional.Matches(character)) { return false; }
}
return true; return true;
} }
else else
@@ -978,7 +978,10 @@ namespace Barotrauma
else else
{ {
NewMessage("Level seed: " + Level.Loaded.Seed); NewMessage("Level seed: " + Level.Loaded.Seed);
NewMessage("Level size: " + Level.Loaded.Size.X+"x"+ Level.Loaded.Size.Y); NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none") + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none"));
NewMessage("Mirrored: " + Level.Loaded.Mirrored);
NewMessage("Level size: " + Level.Loaded.Size.X + "x" + Level.Loaded.Size.Y);
NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown")); NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown"));
} }
},null)); },null));
@@ -17,7 +17,7 @@ namespace Barotrauma
public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
private List<Character> affectedNpcs = null; private IEnumerable<Character> affectedNpcs;
private AIObjectiveGoTo gotoObjective; private AIObjectiveGoTo gotoObjective;
@@ -25,7 +25,7 @@ namespace Barotrauma
{ {
if (isFinished) { return; } if (isFinished) { return; }
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character);
foreach (var npc in affectedNpcs) foreach (var npc in affectedNpcs)
{ {
@@ -62,7 +62,7 @@ namespace Barotrauma
{ {
foreach (var npc in affectedNpcs) foreach (var npc in affectedNpcs)
{ {
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; } if (npc.Removed || !(npc.AIController is HumanAIController)) { continue; }
if (gotoObjective != null) if (gotoObjective != null)
{ {
gotoObjective.Abandon = true; gotoObjective.Abandon = true;
@@ -796,8 +796,9 @@ namespace Barotrauma
monsterStrength += enemyAI.CombatStrength; monsterStrength += enemyAI.CombatStrength;
} }
if (character.CurrentHull?.Submarine != null && if (character.CurrentHull?.Submarine?.Info != null &&
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine))) (character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)) &&
character.CurrentHull.Submarine.Info.Type == SubmarineType.Player)
{ {
// Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42 // Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42
enemyDanger += enemyAI.CombatStrength / 500.0f; enemyDanger += enemyAI.CombatStrength / 500.0f;
@@ -50,7 +50,8 @@ namespace Barotrauma
return; return;
} }
int multiplier = CalculateScalingEscortedCharacterCount(); // Disabled for now, because they make balancing the missions a pain.
int multiplier = 1;//CalculateScalingEscortedCharacterCount();
calculatedReward = Prefab.Reward * multiplier; calculatedReward = Prefab.Reward * multiplier;
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(missionSub))}‖end‖"; string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(missionSub))}‖end‖";
@@ -319,31 +320,33 @@ namespace Barotrauma
} }
} }
foreach (Character character in characters) if (!IsClient)
{ {
if (character.Inventory == null) { continue; } foreach (Character character in characters)
foreach (Item item in character.Inventory.AllItemsMod)
{ {
//item didn't spawn with the characters -> drop it if (character.Inventory == null) { continue; }
if (!characterItems.Any(c => c.Value.Contains(item))) foreach (Item item in character.Inventory.AllItemsMod)
{ {
item.Drop(character); //item didn't spawn with the characters -> drop it
if (!characterItems.Any(c => c.Value.Contains(item)))
{
item.Drop(character);
}
} }
} }
}
// characters that survived will take their items with them, in case players tried to be crafty and steal them // characters that survived will take their items with them, in case players tried to be crafty and steal them
// this needs to run here in case players abort the mission by going back home // this needs to run here in case players abort the mission by going back home
// TODO: I think this might feel like a bug. foreach (var characterItem in characterItems)
foreach (var characterItem in characterItems)
{
if (Survived(characterItem.Key) || !completed)
{ {
foreach (Item item in characterItem.Value) if (Survived(characterItem.Key) || !completed)
{ {
if (!item.Removed) foreach (Item item in characterItem.Value)
{ {
item.Remove(); if (!item.Removed)
{
item.Remove();
}
} }
} }
} }
@@ -30,6 +30,7 @@ namespace Barotrauma
GameMain.Server?.UpdateMissionState(this); GameMain.Server?.UpdateMissionState(this);
#endif #endif
ShowMessage(State); ShowMessage(State);
OnMissionStateChanged?.Invoke(this);
} }
} }
} }
@@ -146,6 +147,8 @@ namespace Barotrauma
private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>(); private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
public Action<Mission> OnMissionStateChanged;
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub) public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
{ {
System.Diagnostics.Debug.Assert(locations.Length == 2); System.Diagnostics.Debug.Assert(locations.Length == 2);
@@ -96,9 +96,9 @@ namespace Barotrauma
public readonly bool RequireWreck; public readonly bool RequireWreck;
/// <summary> /// <summary>
/// The mission can only be received when travelling from Pair.First to Pair.Second /// The mission can only be received when travelling from a location of the first type to a location of the second type
/// </summary> /// </summary>
public readonly List<Pair<string, string>> AllowedConnectionTypes; public readonly List<(string from, string to)> AllowedConnectionTypes;
/// <summary> /// <summary>
/// The mission can only be received in these location types /// The mission can only be received in these location types
@@ -185,7 +185,14 @@ namespace Barotrauma
tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", ""); Name = TextManager.Get("MissionName." + TextIdentifier, true);
if (Name == null)
{
#if DEBUG
DebugConsole.ThrowError($"Error in mission \"{Identifier}\" - could not find a name in localization files. Make sure the texts are present in the loca file or that the mission is set to share texts with another mission using the TextIdentifier attribute.");
#endif
Name = element.GetAttributeString("name", "");
}
Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", ""); Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", "");
Reward = element.GetAttributeInt("reward", 1); Reward = element.GetAttributeInt("reward", 1);
AllowRetry = element.GetAttributeBool("allowretry", false); AllowRetry = element.GetAttributeBool("allowretry", false);
@@ -209,10 +216,20 @@ namespace Barotrauma
FailureMessage = element.GetAttributeString("failuremessage", ""); FailureMessage = element.GetAttributeString("failuremessage", "");
} }
SonarLabel = if (element.Attribute("sonarlabel") == null)
TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? {
TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ?? SonarLabel =
element.GetAttributeString("sonarlabel", ""); TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ??
TextManager.Get("missionsonarlabel.target");
}
else
{
SonarLabel =
TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ??
TextManager.Get(element.GetAttributeString("sonarlabel", ""), true) ??
element.GetAttributeString("sonarlabel", "");
}
SonarIconIdentifier = element.GetAttributeString("sonaricon", ""); SonarIconIdentifier = element.GetAttributeString("sonaricon", "");
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false); MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
@@ -224,7 +241,7 @@ namespace Barotrauma
Headers = new List<string>(); Headers = new List<string>();
Messages = new List<string>(); Messages = new List<string>();
AllowedConnectionTypes = new List<Pair<string, string>>(); AllowedConnectionTypes = new List<(string from, string to)>();
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
@@ -260,9 +277,7 @@ namespace Barotrauma
} }
else else
{ {
AllowedConnectionTypes.Add(new Pair<string, string>( AllowedConnectionTypes.Add((subElement.GetAttributeString("from", "").ToLowerInvariant(), subElement.GetAttributeString("to", "").ToLowerInvariant()));
subElement.GetAttributeString("from", ""),
subElement.GetAttributeString("to", "")));
} }
break; break;
case "locationtypechange": case "locationtypechange":
@@ -358,13 +373,15 @@ namespace Barotrauma
AllowedLocationTypes.Any(lt => lt.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase)); AllowedLocationTypes.Any(lt => lt.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase));
} }
foreach (Pair<string, string> allowedConnectionType in AllowedConnectionTypes) foreach ((string fromType, string toType) in AllowedConnectionTypes)
{ {
if (allowedConnectionType.First.Equals("any", StringComparison.OrdinalIgnoreCase) || if (fromType.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedConnectionType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase)) fromType.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase) ||
(fromType == "anyoutpost" && from.HasOutpost()))
{ {
if (allowedConnectionType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) || if (toType.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedConnectionType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase)) toType.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase) ||
(toType == "anyoutpost" && to.HasOutpost()))
{ {
return true; return true;
} }
@@ -345,12 +345,13 @@ namespace Barotrauma
protected override void UpdateMissionSpecific(float deltaTime) protected override void UpdateMissionSpecific(float deltaTime)
{ {
int newState = State; if (state >= 2) { return; }
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange); float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange; outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange;
if (State < 2 && CheckWinState()) if (CheckWinState())
{ {
newState = 2; State = 2;
} }
else else
{ {
@@ -366,7 +367,7 @@ namespace Barotrauma
} }
if (!outsideOfSonarRange || patrolPositions.None()) if (!outsideOfSonarRange || patrolPositions.None())
{ {
newState = 1; State = 1;
} }
break; break;
case 1: case 1:
@@ -391,14 +392,13 @@ namespace Barotrauma
break; break;
} }
} }
State = newState;
} }
private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m)); private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m));
private bool DeadOrCaptured(Character character) private bool DeadOrCaptured(Character character)
{ {
return character == null || character.Removed || character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub); return character == null || character.Removed || character.Submarine == null || (character.LockHands && character.Submarine == Submarine.MainSub) || character.IsIncapacitated;
} }
public override void End() public override void End()
@@ -149,9 +149,17 @@ namespace Barotrauma
internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions) internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions)
=> configureAvailableResourceCurrencies(customDimensions.Select(d => d.ToString()).ToArray()); => configureAvailableResourceCurrencies(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<string[]> configureAvailableResourceItemTypes;
internal void ConfigureAvailableResourceItemTypes(params string[] resourceItemTypes)
=> configureAvailableResourceItemTypes(resourceItemTypes);
private readonly Action<bool> setEnabledInfoLog; private readonly Action<bool> setEnabledInfoLog;
internal void SetEnabledInfoLog(bool enabled) internal void SetEnabledInfoLog(bool enabled)
=> setEnabledInfoLog(enabled); => setEnabledInfoLog(enabled);
private readonly Action<bool> setEnabledVerboseLog;
internal void SetEnabledVerboseLog(bool enabled)
=> setEnabledVerboseLog(enabled);
#endregion #endregion
#region Data required to fetch methods via reflection #region Data required to fetch methods via reflection
@@ -292,10 +300,14 @@ namespace Barotrauma
configureAvailableResourceCurrencies = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceCurrencies), configureAvailableResourceCurrencies = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceCurrencies),
new Type[] { typeof(string[]) })); new Type[] { typeof(string[]) }));
configureAvailableResourceItemTypes = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceItemTypes),
new Type[] { typeof(string[]) }));
addResourceEvent = Call<ResourceFlowType, string, float, string, string>(getMethod(nameof(AddResourceEvent), addResourceEvent = Call<ResourceFlowType, string, float, string, string>(getMethod(nameof(AddResourceEvent),
new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) })); new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) }));
setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog), setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog),
new Type[] { typeof(bool) })); new Type[] { typeof(bool) }));
setEnabledVerboseLog = Call<bool>(getMethod(nameof(SetEnabledVerboseLog),
new Type[] { typeof(bool) }));
onQuit = Call(getMethod("OnQuit", Array.Empty<Type>())); onQuit = Call(getMethod("OnQuit", Array.Empty<Type>()));
} }
@@ -450,6 +462,7 @@ namespace Barotrauma
try try
{ {
loadedImplementation?.SetEnabledInfoLog(true); loadedImplementation?.SetEnabledInfoLog(true);
loadedImplementation?.SetEnabledVerboseLog(true);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -489,7 +502,10 @@ namespace Barotrauma
+ AssemblyInfo.GitRevision + ":" + AssemblyInfo.GitRevision + ":"
+ buildConfiguration); + buildConfiguration);
loadedImplementation?.ConfigureAvailableCustomDimensions01(Enum.GetValues(typeof(CustomDimensions01)).Cast<CustomDimensions01>().ToArray()); loadedImplementation?.ConfigureAvailableCustomDimensions01(Enum.GetValues(typeof(CustomDimensions01)).Cast<CustomDimensions01>().ToArray());
loadedImplementation?.ConfigureAvailableCustomDimensions02(Enum.GetValues(typeof(CustomDimensions02)).Cast<CustomDimensions02>().ToArray());
loadedImplementation?.ConfigureAvailableResourceCurrencies(Enum.GetValues(typeof(ResourceCurrency)).Cast<ResourceCurrency>().ToArray()); loadedImplementation?.ConfigureAvailableResourceCurrencies(Enum.GetValues(typeof(ResourceCurrency)).Cast<ResourceCurrency>().ToArray());
loadedImplementation?.ConfigureAvailableResourceItemTypes(
Enum.GetValues(typeof(MoneySink)).Cast<MoneySink>().Select(s => s.ToString()).Union(Enum.GetValues(typeof(MoneySource)).Cast<MoneySource>().Select(s => s.ToString())).ToArray());
InitKeys(); InitKeys();
@@ -521,7 +537,7 @@ namespace Barotrauma
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName); loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
} }
packageNames.Sort(); packageNames.Sort();
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames)); loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(" ", packageNames));
} }
loadedImplementation?.AddDesignEvent("Language:" + GameMain.Config.Language); loadedImplementation?.AddDesignEvent("Language:" + GameMain.Config.Language);
} }
@@ -220,10 +220,10 @@ namespace Barotrauma
private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1] private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
{ {
(0, 0.85f), (0, 1.0f),
(1, 0.125f), (1, 0.0f),
(2, 0.0225f), (2, 0.0f),
(3, 0.0025f), (3, 0.0f),
}; };
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier) private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
@@ -16,11 +16,13 @@ namespace Barotrauma
{ {
public ItemPrefab ItemPrefab { get; } public ItemPrefab ItemPrefab { get; }
public int Quantity { get; set; } public int Quantity { get; set; }
public bool? IsStoreComponentEnabled { get; set; }
public PurchasedItem(ItemPrefab itemPrefab, int quantity) public PurchasedItem(ItemPrefab itemPrefab, int quantity)
{ {
ItemPrefab = itemPrefab; ItemPrefab = itemPrefab;
Quantity = quantity; Quantity = quantity;
IsStoreComponentEnabled = null;
} }
} }
@@ -426,10 +428,12 @@ namespace Barotrauma
var item = new Item(pi.ItemPrefab, position, wp.Submarine); var item = new Item(pi.ItemPrefab, position, wp.Submarine);
itemContainer?.Inventory.TryPutItem(item, null); itemContainer?.Inventory.TryPutItem(item, null);
itemSpawned(item); itemSpawned(item);
#if SERVER #if SERVER
Entity.Spawner?.CreateNetworkEvent(item, false); Entity.Spawner?.CreateNetworkEvent(item, false);
#endif #endif
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
static void itemSpawned(Item item) static void itemSpawned(Item item)
{ {
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine; Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
@@ -81,7 +81,12 @@ namespace Barotrauma
public double TotalPlayTime; public double TotalPlayTime;
public int TotalPassedLevels; public int TotalPassedLevels;
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic } public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic, Cargo }
public static bool BlocksInteraction(InteractionType interactionType)
{
return interactionType != InteractionType.None && interactionType != InteractionType.Cargo;
}
public readonly CargoManager CargoManager; public readonly CargoManager CargoManager;
public UpgradeManager UpgradeManager; public UpgradeManager UpgradeManager;
@@ -440,6 +440,7 @@ namespace Barotrauma
GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier); GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
} }
} }
GameAnalyticsManager.AddDesignEvent($"{eventId}HintManager:{(HintManager.Enabled ? "Enabled" : "Disabled")}");
#endif #endif
if (GameMode is CampaignMode campaignMode) if (GameMode is CampaignMode campaignMode)
{ {
@@ -118,6 +118,7 @@ namespace Barotrauma.Items.Components
if (character != null && !CharacterUsable) { return false; } if (character != null && !CharacterUsable) { return false; }
CurrPowerConsumption = powerConsumption; CurrPowerConsumption = powerConsumption;
Voltage = 0.0f;
charging = true; charging = true;
timer = Duration; timer = Duration;
IsActive = true; IsActive = true;
@@ -141,7 +142,7 @@ namespace Barotrauma.Items.Components
timer -= deltaTime; timer -= deltaTime;
if (charging) if (charging)
{ {
if (GetAvailableBatteryPower() >= powerConsumption) if (GetAvailableInstantaneousBatteryPower() >= powerConsumption)
{ {
var batteries = item.GetConnectedComponents<PowerContainer>(); var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption; float neededPower = powerConsumption;
@@ -130,6 +130,11 @@ namespace Barotrauma.Items.Components
} }
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots)) if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots))
{ {
if (item.CampaignInteractionType == CampaignMode.InteractionType.Cargo)
{
item.CampaignInteractionType = CampaignMode.InteractionType.None;
}
if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; } if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; }
this.picker = picker; this.picker = picker;
@@ -370,8 +370,22 @@ namespace Barotrauma.Items.Components
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x"); item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y"); item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
item.SendSignal(new Signal((sub.WorldPosition.X * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); Vector2 pos = new Vector2(sub.WorldPosition.X * Physics.DisplayToRealWorldRatio, sub.RealWorldDepth);
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y"); if (sonar != null && sonar.UseTransducers && sonar.CenterOnTransducers && sonar.ConnectedTransducers.Any())
{
pos = Vector2.Zero;
foreach (var connectedTransducer in sonar.ConnectedTransducers)
{
pos += connectedTransducer.Item.WorldPosition;
}
pos /= sonar.ConnectedTransducers.Count();
pos = new Vector2(
pos.X * Physics.DisplayToRealWorldRatio,
Level.Loaded?.GetRealWorldDepth(pos.Y) ?? (-pos.Y * Physics.DisplayToRealWorldRatio));
}
item.SendSignal(new Signal(pos.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(pos.Y.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
} }
// if our tactical AI pilot has left, revert back to maintaining position // if our tactical AI pilot has left, revert back to maintaining position
@@ -302,17 +302,24 @@ namespace Barotrauma.Items.Components
/// <summary> /// <summary>
/// Returns the amount of power that can be supplied by batteries directly connected to the item /// Returns the amount of power that can be supplied by batteries directly connected to the item
/// </summary> /// </summary>
protected float GetAvailableBatteryPower() protected float GetAvailableInstantaneousBatteryPower()
{ {
var batteries = item.GetConnectedComponents<PowerContainer>(); if (item.Connections == null) { return 0.0f; }
float availablePower = 0.0f; float availablePower = 0.0f;
foreach (PowerContainer battery in batteries) foreach (Connection c in item.Connections)
{ {
float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut); var recipients = c.Recipients;
availablePower += batteryPower; foreach (Connection recipient in recipients)
} {
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery == null) { continue; }
float maxOutputPerFrame = battery.MaxOutPut / 60.0f;
float framesPerMinute = 3600.0f;
availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame);
}
}
return availablePower; return availablePower;
} }
@@ -196,6 +196,15 @@ namespace Barotrauma.Items.Components
set; set;
} }
private float deactivationTimer;
[Serialize(0f, false)]
public float DeactivationTime
{
get;
set;
}
public Body StickTarget public Body StickTarget
{ {
get; get;
@@ -207,6 +216,9 @@ namespace Barotrauma.Items.Components
get { return StickTarget != null; } get { return StickTarget != null; }
} }
private Category originalCollisionCategories;
private Category originalCollisionTargets;
public Projectile(Item item, XElement element) public Projectile(Item item, XElement element)
: base (item, element) : base (item, element)
{ {
@@ -223,21 +235,26 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded() public override void OnItemLoaded()
{ {
if (Attack != null && Attack.DamageRange <= 0.0f && item.body != null) if (item.body != null)
{ {
switch (item.body.BodyShape) if (Attack != null && Attack.DamageRange <= 0.0f)
{ {
case PhysicsBody.Shape.Circle: switch (item.body.BodyShape)
Attack.DamageRange = item.body.radius; {
break; case PhysicsBody.Shape.Circle:
case PhysicsBody.Shape.Capsule: Attack.DamageRange = item.body.radius;
Attack.DamageRange = item.body.height / 2 + item.body.radius; break;
break; case PhysicsBody.Shape.Capsule:
case PhysicsBody.Shape.Rectangle: Attack.DamageRange = item.body.height / 2 + item.body.radius;
Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length(); break;
break; case PhysicsBody.Shape.Rectangle:
Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length();
break;
}
Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange);
} }
Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange); originalCollisionCategories = item.body.CollisionCategories;
originalCollisionTargets = item.body.CollidesWith;
} }
} }
@@ -259,6 +276,10 @@ namespace Barotrauma.Items.Components
launchPos = simPosition; launchPos = simPosition;
//set the rotation of the projectile again because dropping the projectile resets the rotation //set the rotation of the projectile again because dropping the projectile resets the rotation
Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians)); Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians));
if (DeactivationTime > 0)
{
deactivationTimer = DeactivationTime;
}
} }
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f) public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f)
@@ -585,7 +606,7 @@ namespace Barotrauma.Items.Components
{ {
if (dropper != null) if (dropper != null)
{ {
Deactivate(); DisableProjectileCollisions();
Unstick(); Unstick();
} }
base.Drop(dropper); base.Drop(dropper);
@@ -593,6 +614,14 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam) public override void Update(float deltaTime, Camera cam)
{ {
if (DeactivationTime > 0)
{
deactivationTimer -= deltaTime;
if (deactivationTimer < 0)
{
DisableProjectileCollisions();
}
}
while (impactQueue.Count > 0) while (impactQueue.Count > 0)
{ {
var impact = impactQueue.Dequeue(); var impact = impactQueue.Dequeue();
@@ -616,6 +645,10 @@ namespace Barotrauma.Items.Components
if (stickJoint == null && !item.body.FarseerBody.IsBullet) if (stickJoint == null && !item.body.FarseerBody.IsBullet)
{ {
IsActive = false; IsActive = false;
if (DeactivationTime > 0 && deactivationTimer > 0)
{
DisableProjectileCollisions();
}
} }
if (stickJoint == null) { return; } if (stickJoint == null) { return; }
@@ -715,7 +748,7 @@ namespace Barotrauma.Items.Components
} }
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell) if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
{ {
Deactivate(); DisableProjectileCollisions();
return true; return true;
} }
else else
@@ -864,7 +897,7 @@ namespace Barotrauma.Items.Components
if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell) if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
{ {
Deactivate(); DisableProjectileCollisions();
} }
if (attackResult.AppliedDamageModifiers != null && if (attackResult.AppliedDamageModifiers != null &&
@@ -934,18 +967,26 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
private void Deactivate() private void DisableProjectileCollisions()
{ {
item.body.FarseerBody.OnCollision -= OnProjectileCollision; item.body.FarseerBody.OnCollision -= OnProjectileCollision;
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0) if (originalCollisionCategories != Category.None && originalCollisionTargets != Category.None)
{ {
item.body.CollisionCategories = Physics.CollisionCharacter; item.body.CollisionCategories = originalCollisionCategories;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; item.body.CollidesWith = originalCollisionTargets;
} }
else else
{ {
item.body.CollisionCategories = Physics.CollisionItem; if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; {
item.body.CollisionCategories = Physics.CollisionCharacter;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
}
else
{
item.body.CollisionCategories = Physics.CollisionItem;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
}
} }
IgnoredBodies.Clear(); IgnoredBodies.Clear();
} }
@@ -996,7 +1037,14 @@ namespace Barotrauma.Items.Components
} }
stickJoint = null; stickJoint = null;
} }
if (!item.body.FarseerBody.IsBullet) { IsActive = false; } if (!item.body.FarseerBody.IsBullet)
{
IsActive = false;
if (DeactivationTime > 0 && deactivationTimer > 0)
{
DisableProjectileCollisions();
}
}
item.GetComponent<Rope>()?.Snap(); item.GetComponent<Rope>()?.Snap();
if (stickTargetCharacter != null) if (stickTargetCharacter != null)
{ {
@@ -236,6 +236,13 @@ namespace Barotrauma.Items.Components
{ {
ciElement.Connection = item.Connections?.FirstOrDefault(c => c.Name == ciElement.ConnectionName); ciElement.Connection = item.Connections?.FirstOrDefault(c => c.Name == ciElement.ConnectionName);
} }
#if SERVER
//make sure the clients know about the states of the checkboxes and text fields
if (item.Submarine == null || !item.Submarine.Loading)
{
item.CreateServerEvent(this);
}
#endif
} }
partial void UpdateLabelsProjSpecific(); partial void UpdateLabelsProjSpecific();
@@ -542,7 +542,7 @@ namespace Barotrauma.Items.Components
public bool HasPowerToShoot() public bool HasPowerToShoot()
{ {
return GetAvailableBatteryPower() >= GetPowerRequiredToShoot(); return GetAvailableInstantaneousBatteryPower() >= GetPowerRequiredToShoot();
} }
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false) private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
@@ -365,7 +365,16 @@ namespace Barotrauma.Items.Components
{ {
foreach (var allowedSlot in allowedSlots) foreach (var allowedSlot in allowedSlots)
{ {
if (allowedSlot != InvSlotType.Any && !character.Inventory.IsInLimbSlot(item, allowedSlot)) { return; } if (allowedSlot == InvSlotType.Any) { continue; }
foreach (Enum value in Enum.GetValues(typeof(InvSlotType)))
{
var slotType = (InvSlotType)value;
if (slotType == InvSlotType.Any || slotType == InvSlotType.None) { continue; }
if (allowedSlot.HasFlag(slotType) && !character.Inventory.IsInLimbSlot(item, slotType))
{
return;
}
}
} }
picker = character; picker = character;
@@ -98,6 +98,14 @@ namespace Barotrauma
private readonly ItemInventory ownInventory; private readonly ItemInventory ownInventory;
private Rectangle defaultRect; private Rectangle defaultRect;
/// <summary>
/// Unscaled rect
/// </summary>
public Rectangle DefaultRect
{
get { return defaultRect; }
set { defaultRect = value; }
}
private Dictionary<string, Connection> connections; private Dictionary<string, Connection> connections;
@@ -645,10 +653,6 @@ namespace Barotrauma
} }
} }
private float buoyancySineMagnitude;
private float buoyancySineFrequency;
private float buoyancyRandomForce;
public bool FireProof public bool FireProof
{ {
get { return Prefab.FireProof; } get { return Prefab.FireProof; }
@@ -767,7 +771,7 @@ namespace Barotrauma
get { return Position.X; } get { return Position.X; }
private set private set
{ {
Move(new Vector2((value - Position.X) * Scale, 0.0f)); Move(new Vector2(value * Scale, 0.0f));
} }
} }
/// <summary> /// <summary>
@@ -778,7 +782,7 @@ namespace Barotrauma
get { return Position.Y; } get { return Position.Y; }
private set private set
{ {
Move(new Vector2(0.0f, (value - Position.Y) * Scale)); Move(new Vector2(0.0f, value * Scale));
} }
} }
@@ -851,7 +855,16 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "body": case "body":
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale); float density = subElement.GetAttributeFloat("density", 10.0f);
float minDensity = subElement.GetAttributeFloat("mindensity", density);
float maxDensity = subElement.GetAttributeFloat("maxdensity", density);
if (minDensity < maxDensity)
{
var rand = new Random(ID);
density = MathHelper.Lerp(minDensity, maxDensity, (float)rand.NextDouble());
}
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density);
string collisionCategory = subElement.GetAttributeString("collisioncategory", null); string collisionCategory = subElement.GetAttributeString("collisioncategory", null);
if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0) if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0)
{ {
@@ -879,9 +892,6 @@ namespace Barotrauma
} }
body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f); body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f);
body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f); body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f);
buoyancySineMagnitude = subElement.GetAttributeFloat("buoyancysinemagnitude", 0f);
buoyancySineFrequency = subElement.GetAttributeFloat("buoyancysinefrequency", 0f);
buoyancyRandomForce = subElement.GetAttributeFloat("buoyancyrandom", 0f);
body.UserData = this; body.UserData = this;
break; break;
case "trigger": case "trigger":
@@ -1600,7 +1610,7 @@ namespace Barotrauma
float damageAmount = attack.GetItemDamage(deltaTime); float damageAmount = attack.GetItemDamage(deltaTime);
Condition -= damageAmount; Condition -= damageAmount;
if (damageAmount > 0) if (damageAmount >= Prefab.OnDamagedThreshold)
{ {
ApplyStatusEffects(ActionType.OnDamaged, 1.0f); ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
} }
@@ -1729,7 +1739,7 @@ namespace Barotrauma
UpdateNetPosition(deltaTime); UpdateNetPosition(deltaTime);
if (inWater) if (inWater)
{ {
ApplyWaterForces(deltaTime); ApplyWaterForces();
CurrentHull?.ApplyFlowForces(deltaTime, this); CurrentHull?.ApplyFlowForces(deltaTime, this);
} }
} }
@@ -1818,24 +1828,16 @@ namespace Barotrauma
transformDirty = false; transformDirty = false;
} }
private float sineTime;
/// <summary> /// <summary>
/// Applies buoyancy, drag and angular drag caused by water /// Applies buoyancy, drag and angular drag caused by water
/// </summary> /// </summary>
private void ApplyWaterForces(float deltaTime) private void ApplyWaterForces()
{ {
if (body.Mass <= 0.0f || body.Density <= 0.0f) if (body.Mass <= 0.0f || body.Density <= 0.0f)
{ {
return; return;
} }
if (buoyancySineFrequency > 0)
{
if (sineTime >= float.MaxValue)
{
sineTime = float.MinValue;
}
sineTime += deltaTime * buoyancySineFrequency;
}
float forceFactor = 1.0f; float forceFactor = 1.0f;
if (CurrentHull != null) if (CurrentHull != null)
{ {
@@ -1854,10 +1856,7 @@ namespace Barotrauma
Vector2 drag = body.LinearVelocity * volume; Vector2 drag = body.LinearVelocity * volume;
float sine = (float)Math.Sin(sineTime) * buoyancySineMagnitude; body.ApplyForce((uplift - drag) * 10.0f);
Vector2 sineForce = Vector2.UnitY * sine * volume;
Vector2 randomForce = Vector2.UnitY * Rand.Range(-buoyancyRandomForce, buoyancyRandomForce, Rand.RandSync.Unsynced) * volume;
body.ApplyForce((uplift - drag) * 10.0f + sineForce + randomForce);
//apply simple angular drag //apply simple angular drag
body.ApplyTorque(body.AngularVelocity * volume * -0.05f); body.ApplyTorque(body.AngularVelocity * volume * -0.05f);
@@ -1869,9 +1868,17 @@ namespace Barotrauma
if (transformDirty) { return false; } if (transformDirty) { return false; }
var projectile = GetComponent<Projectile>(); var projectile = GetComponent<Projectile>();
if (projectile?.IgnoredBodies != null) if (projectile != null)
{ {
if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; } //ignore character colliders (a projectile only hits limbs)
if (f2.CollisionCategories == Physics.CollisionCharacter && f2.Body.UserData is Character)
{
return false;
}
if (projectile.IgnoredBodies != null)
{
if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; }
}
} }
contact.GetWorldManifold(out Vector2 normal, out _); contact.GetWorldManifold(out Vector2 normal, out _);
@@ -2216,7 +2223,7 @@ namespace Barotrauma
foreach (Rectangle trigger in Prefab.Triggers) foreach (Rectangle trigger in Prefab.Triggers)
{ {
transformedTrigger = TransformTrigger(trigger, true); transformedTrigger = TransformTrigger(trigger, true);
if (Submarine.RectContains(transformedTrigger, worldPosition)) return true; if (Submarine.RectContains(transformedTrigger, worldPosition)) { return true; }
} }
transformedTrigger = Rectangle.Empty; transformedTrigger = Rectangle.Empty;
@@ -2230,7 +2237,10 @@ namespace Barotrauma
public bool TryInteract(Character user, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceUseKey = false) public bool TryInteract(Character user, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceUseKey = false)
{ {
if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; } if (CampaignMode.BlocksInteraction(CampaignInteractionType))
{
return false;
}
bool picked = false, selected = false; bool picked = false, selected = false;
#if CLIENT #if CLIENT
@@ -504,6 +504,9 @@ namespace Barotrauma
set { impactTolerance = Math.Max(value, 0.0f); } set { impactTolerance = Math.Max(value, 0.0f); }
} }
[Serialize(0.0f, false)]
public float OnDamagedThreshold { get; set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float SonarSize public float SonarSize
{ {
@@ -1723,7 +1723,7 @@ namespace Barotrauma
{ {
vertices[j] += position; vertices[j] += position;
} }
var newChunk = new LevelWall(vertices, GenerationParams.WallColor, this); var newChunk = new LevelWall(vertices, GenerationParams.WallColor, this, createBody: false);
AbyssIslands.Add(new AbyssIsland(islandArea, newChunk.Cells)); AbyssIslands.Add(new AbyssIsland(islandArea, newChunk.Cells));
continue; continue;
} }
@@ -1842,7 +1842,7 @@ namespace Barotrauma
Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2); Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2);
int radius = Math.Max(caveSize.X, caveSize.Y) / 2; int radius = Math.Max(caveSize.X, caveSize.Y) / 2;
var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.5f, asCloseAsPossible: true, allowedArea); var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.25f, asCloseAsPossible: true, allowedArea);
GenerateCave(caveParams, parentTunnel, cavePos, caveSize); GenerateCave(caveParams, parentTunnel, cavePos, caveSize);
@@ -2107,12 +2107,42 @@ namespace Barotrauma
private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null) private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null)
{ {
var validPoints = distanceField.FindAll(d => d.distance >= minDistance && (limits == null || limits.Value.Contains(d.point))); var pointsAboveBottom = distanceField.FindAll(d => d.point.Y > GetBottomPosition(d.point.X).Y + minDistance);
validPoints.RemoveAll(d => d.point.Y < GetBottomPosition(d.point.X).Y + minDistance); if (pointsAboveBottom.Count == 0)
if (asCloseAsPossible || !validPoints.Any()) {
DebugConsole.ThrowError("Error in FindPosAwayFromMainPath: no valid positions above the bottom of the sea floor. Has the position of the sea floor been set too high up?");
return distanceField[Rand.Int(distanceField.Count, Rand.RandSync.Server)].point;
}
var validPoints = pointsAboveBottom.FindAll(d => d.distance >= minDistance && (limits == null || limits.Value.Contains(d.point)));
if (!validPoints.Any())
{
DebugConsole.AddWarning("Failed to find a valid position far enough from the main path. Choosing the furthest possible position.\n" + Environment.StackTrace);
if (limits != null)
{
//try choosing something within the specified limits
validPoints = pointsAboveBottom.FindAll(d => limits.Value.Contains(d.point));
}
if (!validPoints.Any())
{
//couldn't find anything, let's just go with the furthest one
validPoints = pointsAboveBottom;
}
(Point position, double distance) furthestPoint = validPoints.First();
foreach (var point in validPoints)
{
if (point.distance > furthestPoint.distance)
{
furthestPoint = point;
}
}
return furthestPoint.position;
}
if (asCloseAsPossible)
{ {
if (!validPoints.Any()) { validPoints = distanceField; } if (!validPoints.Any()) { validPoints = distanceField; }
(Point position, double distance) closestPoint = validPoints.First(); (Point position, double distance) closestPoint = validPoints.First();
foreach (var point in validPoints) foreach (var point in validPoints)
{ {
if (point.distance < closestPoint.distance) if (point.distance < closestPoint.distance)
@@ -2172,7 +2202,7 @@ namespace Barotrauma
{ {
double xDiff = Math.Abs(point.X - ruinPos.X); double xDiff = Math.Abs(point.X - ruinPos.X);
double yDiff = Math.Abs(point.Y - ruinPos.Y); double yDiff = Math.Abs(point.Y - ruinPos.Y);
if (xDiff < ruinSize || yDiff < ruinSize) if (xDiff < ruinSize && yDiff < ruinSize)
{ {
shortestDistSqr = 0.0f; shortestDistSqr = 0.0f;
} }
@@ -57,7 +57,7 @@ namespace Barotrauma
set { moveState = MathHelper.Clamp(value, 0.0f, MathHelper.TwoPi); } set { moveState = MathHelper.Clamp(value, 0.0f, MathHelper.TwoPi); }
} }
public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false) public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false, bool createBody = true)
{ {
this.level = level; this.level = level;
this.color = color; this.color = color;
@@ -74,14 +74,17 @@ namespace Barotrauma
wallCell.Edges[i].IsSolid = true; wallCell.Edges[i].IsSolid = true;
} }
Cells = new List<VoronoiCell>() { wallCell }; Cells = new List<VoronoiCell>() { wallCell };
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles); if (createBody)
if (triangles.Count == 0)
{ {
throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString()))); Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
} if (triangles.Count == 0)
{
throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString())));
}
#if CLIENT #if CLIENT
GenerateVertices(); GenerateVertices();
#endif #endif
}
} }
public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level) public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level)
@@ -1776,7 +1776,7 @@ namespace Barotrauma
if (connectedWp.isObstructed) { continue; } if (connectedWp.isObstructed) { continue; }
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition); Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition);
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition); Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition);
var body = Submarine.PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false); var body = PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false);
if (body != null) if (body != null)
{ {
connectedWp.isObstructed = true; connectedWp.isObstructed = true;
@@ -1803,7 +1803,7 @@ namespace Barotrauma
foreach (var connection in node.connections) foreach (var connection in node.connections)
{ {
var connectedWp = connection.Waypoint; var connectedWp = connection.Waypoint;
if (connectedWp.isObstructed) { continue; } if (connectedWp.isObstructed || connectedWp.Ladders != null) { continue; }
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition; Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition; Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true); var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true);
@@ -315,14 +315,33 @@ namespace Barotrauma
set { FarseerBody.BodyType = value; } set { FarseerBody.BodyType = value; }
} }
private Category _collisionCategories;
public Category CollisionCategories public Category CollisionCategories
{ {
set { FarseerBody.CollisionCategories = value; } set
{
_collisionCategories = value;
FarseerBody.CollisionCategories = value;
}
get
{
return _collisionCategories;
}
} }
private Category _collidesWith;
public Category CollidesWith public Category CollidesWith
{ {
set { FarseerBody.CollidesWith = value; } set
{
_collidesWith = value;
FarseerBody.CollidesWith = value;
}
get
{
return _collidesWith;
}
} }
public PhysicsBody(XElement element, float scale = 1.0f) : this(element, Vector2.Zero, scale) { } public PhysicsBody(XElement element, float scale = 1.0f) : this(element, Vector2.Zero, scale) { }
@@ -383,12 +402,12 @@ namespace Barotrauma
list.Add(this); list.Add(this);
} }
public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f) public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f, float? forceDensity = null)
{ {
float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale; float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale;
float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale; float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale;
float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale; float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale;
density = Math.Max(element.GetAttributeFloat("density", 10.0f), MinDensity); density = Math.Max(forceDensity ?? element.GetAttributeFloat("density", 10.0f), MinDensity);
CreateBody(width, height, radius, density); CreateBody(width, height, radius, density);
Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType); Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType);
FarseerBody.BodyType = bodyType; FarseerBody.BodyType = bodyType;
@@ -245,6 +245,8 @@ namespace Barotrauma
} }
cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory); cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory);
Character.Controlled?.UpdateLocalCursor(cam);
#endif #endif
foreach (Submarine sub in Submarine.Loaded) foreach (Submarine sub in Submarine.Loaded)
@@ -929,6 +929,21 @@ namespace Barotrauma
(int)structure.Prefab.ScaledSize.Y); (int)structure.Prefab.ScaledSize.Y);
} }
} }
else if (entity is Item item)
{
if (!item.ResizeHorizontal)
{
item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
(int)(item.Prefab.Size.X * item.Prefab.Scale),
item.Rect.Height);
}
if (!item.ResizeVertical)
{
item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
item.Rect.Width,
(int)(item.Prefab.Size.Y * item.Prefab.Scale));
}
}
} }
if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property)) if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
@@ -133,7 +133,8 @@ namespace Barotrauma
Target, Target,
Limb, Limb,
MainLimb, MainLimb,
Collider Collider,
Random
} }
public readonly ItemPrefab ItemPrefab; public readonly ItemPrefab ItemPrefab;
@@ -1487,7 +1488,7 @@ namespace Barotrauma
if (giveTalentInfo.GiveRandom) if (giveTalentInfo.GiveRandom)
{ {
targetCharacter.GiveTalent(viableTalents.GetRandom(), true); targetCharacter.GiveTalent(viableTalents.GetRandom(Rand.RandSync.Unsynced), true);
} }
else else
{ {
@@ -1542,7 +1543,7 @@ namespace Barotrauma
var characters = new List<Character>(); var characters = new List<Character>();
for (int i = 0; i < characterSpawnInfo.Count; i++) for (int i = 0; i < characterSpawnInfo.Count; i++)
{ {
Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Server) + characterSpawnInfo.Offset, Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset,
onSpawn: newCharacter => onSpawn: newCharacter =>
{ {
if (newCharacter.AIController is EnemyAIController enemyAi && if (newCharacter.AIController is EnemyAIController enemyAi &&
@@ -1563,7 +1564,7 @@ namespace Barotrauma
if (spawnItemRandomly) if (spawnItemRandomly)
{ {
SpawnItem(spawnItems.GetRandom()); SpawnItem(spawnItems.GetRandom(Rand.RandSync.Unsynced));
} }
else else
{ {
@@ -1582,7 +1583,7 @@ namespace Barotrauma
switch (chosenItemSpawnInfo.SpawnPosition) switch (chosenItemSpawnInfo.SpawnPosition)
{ {
case ItemSpawnInfo.SpawnPositionType.This: case ItemSpawnInfo.SpawnPositionType.This:
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem => Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
{ {
Projectile projectile = newItem.GetComponent<Projectile>(); Projectile projectile = newItem.GetComponent<Projectile>();
if (projectile != null && user != null && sourceBody != null && entity != null) if (projectile != null && user != null && sourceBody != null && entity != null)
@@ -1597,7 +1598,7 @@ namespace Barotrauma
} }
float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread)); float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread));
var worldPos = sourceBody.Position; var worldPos = sourceBody.Position;
float rotation = chosenItemSpawnInfo.Rotation; float rotation = 0;
if (user.Submarine != null) if (user.Submarine != null)
{ {
worldPos += user.Submarine.Position; worldPos += user.Submarine.Position;
@@ -1614,11 +1615,14 @@ namespace Barotrauma
rotation = sourceBody.TransformedRotation; rotation = sourceBody.TransformedRotation;
break; break;
case ItemSpawnInfo.SpawnRotationType.Collider: case ItemSpawnInfo.SpawnRotationType.Collider:
rotation = user.AnimController.Collider.Rotation; rotation = user.AnimController.Collider.Rotation + MathHelper.PiOver2;
break; break;
case ItemSpawnInfo.SpawnRotationType.MainLimb: case ItemSpawnInfo.SpawnRotationType.MainLimb:
rotation = user.AnimController.MainLimb.body.TransformedRotation; rotation = user.AnimController.MainLimb.body.TransformedRotation;
break; break;
case ItemSpawnInfo.SpawnRotationType.Random:
DebugConsole.ShowError("Random rotation is not supported for Projectiles.");
break;
default: default:
throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType); throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType);
} }
@@ -1631,19 +1635,37 @@ namespace Barotrauma
if (body != null) if (body != null)
{ {
float rotation = MathHelper.ToRadians(chosenItemSpawnInfo.Rotation); float rotation = MathHelper.ToRadians(chosenItemSpawnInfo.Rotation);
if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Limb) switch (chosenItemSpawnInfo.RotationType)
{ {
if (sourceBody != null) case ItemSpawnInfo.SpawnRotationType.Fixed:
{ if (sourceBody != null)
rotation += sourceBody.Rotation; {
} rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.Rotation);
} }
else if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Collider) break;
{ case ItemSpawnInfo.SpawnRotationType.Limb:
if (entity is Character character) if (sourceBody != null)
{ {
rotation += character.AnimController.Collider.Rotation; rotation += sourceBody.Rotation;
} }
break;
case ItemSpawnInfo.SpawnRotationType.Collider:
if (entity is Character character)
{
rotation += character.AnimController.Collider.Rotation + MathHelper.PiOver2;
}
break;
case ItemSpawnInfo.SpawnRotationType.MainLimb:
if (entity is Character c)
{
rotation = c.AnimController.MainLimb.body.TransformedRotation;
}
break;
case ItemSpawnInfo.SpawnRotationType.Random:
rotation = Rand.Range(0f, MathHelper.TwoPi, Rand.RandSync.Unsynced);
break;
default:
throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType);
} }
body.SetTransform(newItem.SimPosition, rotation); body.SetTransform(newItem.SimPosition, rotation);
body.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed); body.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed);
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More