Unstable 0.16.3.0
This commit is contained in:
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.IsUnconscious || !Character.Enabled || !Enabled) { return; }
|
||||
|
||||
Vector2 pos = Character.WorldPosition;
|
||||
Vector2 pos = Character.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
|
||||
if (State == AIState.Idle && PreviousState == AIState.Attack)
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (SelectedAiTarget?.Entity != null)
|
||||
{
|
||||
Vector2 targetPos = SelectedAiTarget.WorldPosition;
|
||||
Vector2 targetPos = SelectedAiTarget.Entity.DrawPosition;
|
||||
if (State == AIState.Attack)
|
||||
{
|
||||
targetPos = attackWorldPos;
|
||||
@@ -72,7 +72,7 @@ namespace Barotrauma
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -312,11 +312,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (AnimController.CurrentHull?.Submarine != null)
|
||||
{
|
||||
cursorPosition -= AnimController.CurrentHull.Submarine.Position;
|
||||
}
|
||||
UpdateLocalCursor(cam);
|
||||
|
||||
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
|
||||
if (GUI.PauseMenuOpen)
|
||||
@@ -393,6 +389,15 @@ namespace Barotrauma
|
||||
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)
|
||||
{
|
||||
if (controlled != this) return;
|
||||
|
||||
@@ -431,7 +431,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
private InfectionState? prevDisplayedMessage;
|
||||
partial void UpdateMessages()
|
||||
{
|
||||
if (character != Character.Controlled) { return; }
|
||||
if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; }
|
||||
if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; }
|
||||
|
||||
|
||||
@@ -896,13 +896,20 @@ namespace Barotrauma
|
||||
{
|
||||
if (wearable.Type == WearableType.Husk) { continue; }
|
||||
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);
|
||||
depthStep += step;
|
||||
if (HairWithHatSprite != null)
|
||||
{
|
||||
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);
|
||||
//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;
|
||||
|
||||
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)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Barotrauma
|
||||
{
|
||||
partial class AlienRuinMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => State > 0;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage 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
|
||||
{
|
||||
public override bool IsAtCompletionState => false;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override string GetMissionRewardText(Submarine 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];
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsAtCompletionState => false;
|
||||
public override bool IsAtFailureState => false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Barotrauma
|
||||
{
|
||||
partial class EscortMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => false;
|
||||
public override bool IsAtFailureState => State == 1;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage 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 Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MineralMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => false;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -19,6 +19,15 @@ namespace Barotrauma
|
||||
|
||||
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()
|
||||
{
|
||||
int v = Difficulty ?? MissionPrefab.MinDifficulty;
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Barotrauma
|
||||
{
|
||||
partial class MonsterMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => State > 0;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Barotrauma
|
||||
{
|
||||
partial class NestMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => State > 0 && !requireDelivery;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Barotrauma
|
||||
{
|
||||
partial class PirateMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => State > 1;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -5,6 +5,9 @@ namespace Barotrauma
|
||||
{
|
||||
partial class SalvageMission : Mission
|
||||
{
|
||||
public override bool IsAtCompletionState => false;
|
||||
public override bool IsAtFailureState => false;
|
||||
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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)
|
||||
{
|
||||
base.ClientReadInitial(msg);
|
||||
|
||||
@@ -1356,7 +1356,7 @@ namespace Barotrauma
|
||||
|
||||
#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="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param>
|
||||
|
||||
@@ -1019,8 +1019,10 @@ namespace Barotrauma
|
||||
case "gridtext":
|
||||
LoadGridText(element, parent);
|
||||
return null;
|
||||
case "conditional":
|
||||
break;
|
||||
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)
|
||||
|
||||
@@ -1683,8 +1683,9 @@ namespace Barotrauma
|
||||
|
||||
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 (pi.ItemPrefab?.InventoryIcon != null)
|
||||
@@ -1696,14 +1697,11 @@ namespace Barotrauma
|
||||
icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f);
|
||||
}
|
||||
};
|
||||
|
||||
var color = Color.White * (enabled ? 1.0f : 0.5f);
|
||||
|
||||
if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name)
|
||||
{
|
||||
name.TextColor = color;
|
||||
}
|
||||
|
||||
if (itemFrame.FindChild("quantitylabel", recursive: true) is GUITextBlock qty)
|
||||
{
|
||||
qty.TextColor = color;
|
||||
@@ -1712,25 +1710,21 @@ namespace Barotrauma
|
||||
{
|
||||
numberInput.Enabled = enabled;
|
||||
}
|
||||
|
||||
if (itemFrame.FindChild("owned", recursive: true) is GUITextBlock ownedBlock)
|
||||
{
|
||||
ownedBlock.TextColor = color;
|
||||
}
|
||||
|
||||
var isDiscounted = false;
|
||||
bool isDiscounted = false;
|
||||
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
|
||||
{
|
||||
undiscountedPriceBlock.TextColor = color;
|
||||
undiscountedPriceBlock.Strikethrough.Color = color;
|
||||
isDiscounted = true;
|
||||
}
|
||||
|
||||
if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock)
|
||||
{
|
||||
priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color;
|
||||
}
|
||||
|
||||
if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton)
|
||||
{
|
||||
addButton.Enabled = enabled;
|
||||
@@ -1739,6 +1733,8 @@ namespace Barotrauma
|
||||
{
|
||||
removeButton.Enabled = enabled;
|
||||
}
|
||||
pi.IsStoreComponentEnabled = enabled;
|
||||
itemFrame.UserData = pi;
|
||||
}
|
||||
|
||||
private void SetQuantityLabelText(StoreTab mode, GUIComponent itemFrame)
|
||||
|
||||
@@ -129,7 +129,6 @@ namespace Barotrauma
|
||||
public TabMenu()
|
||||
{
|
||||
if (!initialized) { Initialize(); }
|
||||
|
||||
CreateInfoFrame(SelectedTab);
|
||||
SelectInfoFrameTab(SelectedTab);
|
||||
}
|
||||
@@ -300,7 +299,7 @@ namespace Barotrauma
|
||||
CreateMissionInfo(infoFrameHolder);
|
||||
break;
|
||||
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();
|
||||
GUIFrame reputationFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrameHolder.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
||||
@@ -308,8 +307,8 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case InfoFrameTab.Traitor:
|
||||
TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission;
|
||||
Character traitor = GameMain.Client.Character;
|
||||
TraitorMissionPrefab traitorMission = GameMain.Client?.TraitorMission;
|
||||
Character traitor = GameMain.Client?.Character;
|
||||
if (traitor == null || traitorMission == null) { return; }
|
||||
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
|
||||
break;
|
||||
@@ -753,7 +752,7 @@ namespace Barotrauma
|
||||
|
||||
if (character != null)
|
||||
{
|
||||
if (GameMain.NetworkMember == null)
|
||||
if (GameMain.Client == 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));
|
||||
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,
|
||||
HoverColor = mission.Prefab.IconColor,
|
||||
SelectedColor = mission.Prefab.IconColor,
|
||||
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);
|
||||
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)
|
||||
{
|
||||
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
||||
@@ -1441,7 +1469,7 @@ namespace Barotrauma
|
||||
selectedTalents.Remove(talentIdentifier);
|
||||
}
|
||||
|
||||
UpdateTalentButtons();
|
||||
UpdateTalentInfo();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
@@ -1506,7 +1534,7 @@ namespace Barotrauma
|
||||
};
|
||||
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
|
||||
|
||||
UpdateTalentButtons();
|
||||
UpdateTalentInfo();
|
||||
}
|
||||
|
||||
private void CreateTalentSkillList(Character character, GUIListBox parent)
|
||||
@@ -1541,11 +1569,13 @@ namespace Barotrauma
|
||||
GUITextBlock.AutoScaleAndNormalize(skillNames);
|
||||
}
|
||||
|
||||
private void UpdateTalentButtons()
|
||||
private void UpdateTalentInfo()
|
||||
{
|
||||
Character controlledCharacter = Character.Controlled;
|
||||
if (controlledCharacter?.Info == null) { return; }
|
||||
|
||||
if (SelectedTab != InfoFrameTab.Talents) { return; }
|
||||
|
||||
bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
|
||||
|
||||
if (unlockedAllTalents)
|
||||
@@ -1626,7 +1656,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
|
||||
UpdateTalentButtons();
|
||||
UpdateTalentInfo();
|
||||
}
|
||||
|
||||
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
|
||||
@@ -1640,63 +1670,14 @@ namespace Barotrauma
|
||||
{
|
||||
Character controlledCharacter = Character.Controlled;
|
||||
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
|
||||
UpdateTalentButtons();
|
||||
UpdateTalentInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnExperienceChanged(Character character)
|
||||
{
|
||||
if (character != Character.Controlled) { return; }
|
||||
UpdateTalentButtons();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
UpdateTalentInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma
|
||||
static class HintManager
|
||||
{
|
||||
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 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)>();
|
||||
@@ -666,6 +668,8 @@ namespace Barotrauma
|
||||
ActiveHintMessageBox.InnerFrame.Flash(color: iconColor ?? Color.Orange, flashDuration: 0.75f);
|
||||
onDisplay?.Invoke();
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent($"HintManager:{GameMain.GameSession?.GameMode?.Preset?.Identifier ?? "none"}:HintDisplayed:{hintIdentifier}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -565,6 +565,13 @@ namespace Barotrauma
|
||||
});
|
||||
#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 --------------------------------------
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
else
|
||||
{
|
||||
var iconInfo = targetIcons[iconIdentifier];
|
||||
iconInfo.Item1.Draw(spriteBatch, markerPos, iconInfo.Item2);
|
||||
}
|
||||
|
||||
@@ -1712,7 +1711,10 @@ namespace Barotrauma.Items.Components
|
||||
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
|
||||
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,
|
||||
new Vector2(labelPos.X + 10, labelPos.Y),
|
||||
|
||||
@@ -381,17 +381,17 @@ namespace Barotrauma.Items.Components
|
||||
if (deteriorationTimer > 0.0f)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character)
|
||||
{
|
||||
if (DraggingConnected?.Item.Removed ?? false)
|
||||
if (DraggingConnected?.Item?.Removed ?? true)
|
||||
{
|
||||
DraggingConnected = null;
|
||||
}
|
||||
|
||||
@@ -580,14 +580,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
availableCharge = 0.0f;
|
||||
availableCapacity = 0.0f;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
if (item.Connections == null) { return; }
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
availableCharge += battery.Charge;
|
||||
availableCapacity += battery.Capacity;
|
||||
var recipients = c.Recipients;
|
||||
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);
|
||||
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)
|
||||
{
|
||||
powerIndicator.BarSize = 1;
|
||||
|
||||
@@ -10,7 +10,13 @@ namespace Barotrauma.Items.Components
|
||||
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100);
|
||||
if (roundedValue == 0) { return; }
|
||||
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)
|
||||
@@ -33,9 +39,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
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));
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -1550,6 +1550,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 pos = Vector2.Zero;
|
||||
Submarine sub = null;
|
||||
float rotation = 0.0f;
|
||||
int itemContainerIndex = -1;
|
||||
int inventorySlotIndex = -1;
|
||||
|
||||
@@ -1561,7 +1562,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
pos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
|
||||
rotation = msg.ReadRangedSingle(0, MathHelper.TwoPi, 8);
|
||||
ushort subID = msg.ReadUInt16();
|
||||
if (subID > 0)
|
||||
{
|
||||
|
||||
@@ -130,18 +130,31 @@ namespace Barotrauma
|
||||
int tilesY = (int)Math.Ceiling(Height / tileSize.Y);
|
||||
mapTiles = new Sprite[tilesX, tilesY];
|
||||
tileDiscovered = new bool[tilesX, tilesY];
|
||||
HashSet<Biome> missingBiomes = new HashSet<Biome>();
|
||||
for (int x = 0; x < tilesX; x++)
|
||||
{
|
||||
for (int y = 0; y < tilesY; y++)
|
||||
{
|
||||
var biome = GetBiome(x * tileSize.X);
|
||||
var tileList = generationParams.MapTiles.ContainsKey(biome.Identifier) ?
|
||||
generationParams.MapTiles[biome.Identifier] :
|
||||
generationParams.MapTiles.Values.First();
|
||||
List<Sprite> tileList = null;
|
||||
if (generationParams.MapTiles.ContainsKey(biome.Identifier))
|
||||
{
|
||||
tileList = generationParams.MapTiles[biome.Identifier];
|
||||
}
|
||||
else
|
||||
{
|
||||
tileList = generationParams.MapTiles.Values.First();
|
||||
missingBiomes.Add(biome);
|
||||
}
|
||||
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);
|
||||
|
||||
GenerateLocationConnectionVisuals();
|
||||
|
||||
@@ -772,7 +772,7 @@ namespace Barotrauma
|
||||
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; }
|
||||
if (item.flippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; }
|
||||
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));
|
||||
continue;
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace Barotrauma
|
||||
if (closestSub != null && subsToMove.Contains(closestSub))
|
||||
{
|
||||
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;
|
||||
|
||||
SpriteEffects spriteEffects = SpriteEffects.None;
|
||||
if (flippedX)
|
||||
if (flippedX && ((prefab as ItemPrefab)?.CanSpriteFlipX ?? true))
|
||||
{
|
||||
spriteEffects |= SpriteEffects.FlipHorizontally;
|
||||
}
|
||||
if (flippedY)
|
||||
if (flippedY && ((prefab as ItemPrefab)?.CanSpriteFlipY ?? true))
|
||||
{
|
||||
spriteEffects |= SpriteEffects.FlipVertically;
|
||||
}
|
||||
|
||||
@@ -90,14 +90,14 @@ namespace Barotrauma
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos,
|
||||
new Vector2(ConnectedGap.WorldPosition.X, -ConnectedGap.WorldPosition.Y),
|
||||
new Vector2(ConnectedGap.DrawPosition.X, -ConnectedGap.DrawPosition.Y),
|
||||
GUI.Style.Green * 0.5f, width: 1);
|
||||
}
|
||||
if (Ladders != null)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -15,7 +16,11 @@ namespace Barotrauma
|
||||
var entity = FindEntityByID(entityId);
|
||||
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();
|
||||
}
|
||||
else
|
||||
@@ -28,7 +33,11 @@ namespace Barotrauma
|
||||
switch (message.ReadByte())
|
||||
{
|
||||
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;
|
||||
case (byte)SpawnableType.Character:
|
||||
Character.ReadSpawnData(message);
|
||||
|
||||
@@ -251,7 +251,7 @@ namespace Barotrauma.CharacterEditor
|
||||
GUI.ForceMouseOn(null);
|
||||
if (isEndlessRunner)
|
||||
{
|
||||
Submarine.MainSub.Remove();
|
||||
Submarine.MainSub?.Remove();
|
||||
GameMain.World.ProcessChanges();
|
||||
isEndlessRunner = false;
|
||||
Reset();
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma
|
||||
|
||||
#if TEST_REMOTE_CONTENT
|
||||
|
||||
var doc = XMLExtensions.TryLoadXml("Content/UI/MenuTextTest.xml");
|
||||
var doc = XMLExtensions.TryLoadXml("Content/UI/MenuContent.xml");
|
||||
if (doc?.Root != null)
|
||||
{
|
||||
foreach (XElement subElement in doc?.Root.Elements())
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Barotrauma
|
||||
private GUIDropDown linkedSubBox;
|
||||
|
||||
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();
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -604,11 +604,13 @@ namespace Barotrauma
|
||||
RenameLayer(layer, newName);
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(layerAddButton.TextBlock, layerDeleteButton.TextBlock, layerRenameButton.TextBlock);
|
||||
|
||||
|
||||
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) })
|
||||
@@ -4433,9 +4435,9 @@ namespace Barotrauma
|
||||
layerList.Deselect();
|
||||
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.15f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headerlink"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, 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.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") { 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)
|
||||
{
|
||||
@@ -4494,6 +4496,17 @@ namespace Barotrauma
|
||||
|
||||
layerList.RecalculateChildren();
|
||||
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()
|
||||
|
||||
@@ -1178,7 +1178,7 @@ namespace Barotrauma
|
||||
if ((s.damageRange == Vector2.Zero ||
|
||||
(damage >= s.damageRange.X && damage <= s.damageRange.Y)) &&
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.16.2.0</Version>
|
||||
<Version>0.16.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.16.2.0</Version>
|
||||
<Version>0.16.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.16.2.0</Version>
|
||||
<Version>0.16.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.16.2.0</Version>
|
||||
<Version>0.16.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.16.2.0</Version>
|
||||
<Version>0.16.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -252,6 +252,7 @@ namespace Barotrauma
|
||||
|
||||
msg.Write(Position.X);
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.16.2.0</Version>
|
||||
<Version>0.16.3.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -207,8 +207,10 @@ namespace Barotrauma
|
||||
} = new HashSet<Submarine>();
|
||||
|
||||
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);
|
||||
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter);
|
||||
public static bool IsTargetBeingChasedBy(Character target, Character character)
|
||||
=> 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;
|
||||
|
||||
@@ -1322,7 +1324,7 @@ namespace Barotrauma
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
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
|
||||
State = AIState.Idle;
|
||||
@@ -2385,6 +2387,24 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
//goes through all the AItargets, evaluates how preferable it is to attack the target,
|
||||
@@ -2398,6 +2418,7 @@ namespace Barotrauma
|
||||
targetingParams = null;
|
||||
bool isAnyTargetClose = false;
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float maxModifier = 5;
|
||||
foreach (AITarget aiTarget in AITarget.List)
|
||||
{
|
||||
if (aiTarget.InDetectable) { continue; }
|
||||
@@ -2537,12 +2558,12 @@ namespace Barotrauma
|
||||
{
|
||||
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
|
||||
valueModifier *= 1 + section.gap.Open * 0.25f;
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2580,6 +2601,7 @@ namespace Barotrauma
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
// (Terminalcells)
|
||||
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 = Math.Clamp(valueModifier, 0, maxModifier);
|
||||
}
|
||||
}
|
||||
if (door != null)
|
||||
@@ -2610,7 +2633,7 @@ namespace Barotrauma
|
||||
bool isOpen = door.CanBeTraversed;
|
||||
if (!isOpen)
|
||||
{
|
||||
if (!canAttackDoors || isOutdoor && !AIParams.TargetOuterWalls) { continue; }
|
||||
if (!canAttackDoors) { continue; }
|
||||
}
|
||||
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
|
||||
if (door.CanBeTraversed)
|
||||
{
|
||||
valueModifier = 3;
|
||||
valueModifier = maxModifier;
|
||||
}
|
||||
else if (door.LinkedGap != null)
|
||||
{
|
||||
valueModifier = 1 + door.LinkedGap.Open;
|
||||
valueModifier = 1 + door.LinkedGap.Open * (maxModifier - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2727,6 +2750,37 @@ namespace Barotrauma
|
||||
|
||||
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
|
||||
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.
|
||||
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else
|
||||
{
|
||||
dist *= 5;
|
||||
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
// 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);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else if (targetParams.AttackPattern == AttackPattern.Circle)
|
||||
{
|
||||
dist *= 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2936,9 +2993,9 @@ namespace Barotrauma
|
||||
if (HasValidPath(requireNonDirty: true)) { return; }
|
||||
wallHits.Clear();
|
||||
Structure wall = null;
|
||||
Vector2 rayStart = AttackingLimb != null ? AttackingLimb.SimPosition : SimPosition;
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
@@ -2952,7 +3009,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5);
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
@@ -2968,7 +3024,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = rayStart + Steering * 5;
|
||||
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.
|
||||
if (!isTargetingDoor)
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
@@ -3032,6 +3088,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Blocked by a disabled wall.
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
@@ -3082,8 +3139,17 @@ namespace Barotrauma
|
||||
if (!(hit.UserData is Structure w)) { return false; }
|
||||
if (w.Submarine == null) { return false; }
|
||||
if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; }
|
||||
if (Character.Submarine == null && w.prefab.Tags.Contains("inner")) { return false; }
|
||||
if (!AIParams.TargetOuterWalls && !w.prefab.Tags.Contains("inner")) { return false; }
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
if (w.prefab.Tags.Contains("inner"))
|
||||
{
|
||||
if (!Character.AnimController.CanEnterSubmarine) { return false; }
|
||||
}
|
||||
else if (!AIParams.TargetOuterWalls)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
wall = w;
|
||||
return true;
|
||||
}
|
||||
@@ -3120,7 +3186,8 @@ namespace Barotrauma
|
||||
{
|
||||
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)
|
||||
{
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
bool success = base.SteerThroughGap(gap, targetWorldPos, deltaTime, maxDistance);
|
||||
if (success)
|
||||
{
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
|
||||
}
|
||||
IsSteeringThroughGap = success;
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Barotrauma
|
||||
private float reactTimer;
|
||||
private float unreachableClearTimer;
|
||||
private bool shouldCrouch;
|
||||
public bool IsInsideCave { get; private set; }
|
||||
/// <summary>
|
||||
/// Resets each frame
|
||||
/// </summary>
|
||||
@@ -58,14 +57,14 @@ namespace Barotrauma
|
||||
private float obstacleRaycastTimer;
|
||||
|
||||
private readonly float enemyCheckInterval = 0.2f;
|
||||
private readonly float enemySpotDistanceOutside = 1500;
|
||||
private readonly float enemySpotDistanceOutside = 800;
|
||||
private readonly float enemySpotDistanceInside = 1000;
|
||||
private float enemycheckTimer;
|
||||
|
||||
/// <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>
|
||||
public float ReportRange { get; set; } = float.PositiveInfinity;
|
||||
public float ReportRange { get; set; }
|
||||
|
||||
private float _aimSpeed = 1;
|
||||
public float AimSpeed
|
||||
@@ -167,6 +166,7 @@ namespace Barotrauma
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
reactTimer = GetReactionTime();
|
||||
SortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
ReportRange = Character.IsOnPlayerTeam ? float.PositiveInfinity : 1000;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -306,7 +306,7 @@ namespace Barotrauma
|
||||
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.
|
||||
// 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 maxDistance = Character.Submarine == null ? enemySpotDistanceOutside : enemySpotDistanceInside;
|
||||
if (dist > maxDistance * maxDistance) { continue; }
|
||||
Vector2 forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation);
|
||||
forward.X *= Character.AnimController.Dir;
|
||||
if (Vector2.Dot(toTarget, forward) < 0.2f) { continue; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(c, Character)) { continue; }
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
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 (dist < closestDistance || closestEnemy == null)
|
||||
{
|
||||
@@ -344,8 +348,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))
|
||||
{
|
||||
@@ -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)
|
||||
//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;
|
||||
}
|
||||
@@ -1259,8 +1261,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
// Outside -> don't react.
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
// Outside
|
||||
return attacker.Submarine == null ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
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 ignoreWater = HasDivingSuit(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);
|
||||
if (isCurrentHull)
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character, checkInventory: true)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
|
||||
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; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,10 @@ namespace Barotrauma
|
||||
private float AimSpeed => HumanAIController.AimSpeed;
|
||||
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)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -144,12 +147,19 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
spreadTimer = Rand.Range(-10f, 10f);
|
||||
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
HumanAIController.SortTimer = 0;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -160,6 +170,13 @@ namespace Barotrauma
|
||||
}
|
||||
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
|
||||
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
|
||||
if (Priority > 0)
|
||||
{
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
}
|
||||
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 (WeaponComponent == null)
|
||||
@@ -418,9 +435,16 @@ namespace Barotrauma
|
||||
onCompleted: () => RemoveSubObjective(ref seekWeaponObjective),
|
||||
onAbandon: () =>
|
||||
{
|
||||
SpeakNoWeapons();
|
||||
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;
|
||||
float bestPriority = 0;
|
||||
float lethalDmg = -1;
|
||||
bool enemyIsClose = EnemyIsClose();
|
||||
bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300);
|
||||
bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character);
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (prioritizeMelee)
|
||||
{
|
||||
if (weapon is MeleeWeapon)
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
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).
|
||||
continue;
|
||||
@@ -693,7 +729,7 @@ namespace Barotrauma
|
||||
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1014,7 +1050,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
return;
|
||||
}
|
||||
if (Weapon.RequireAimToUse)
|
||||
@@ -1074,7 +1110,7 @@ namespace Barotrauma
|
||||
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.
|
||||
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
|
||||
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
}
|
||||
}
|
||||
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 (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item)))
|
||||
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(ItemToContain)))
|
||||
{
|
||||
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 (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Barotrauma
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
ConditionLevel = MIN_OXYGEN,
|
||||
RemoveExisting = true
|
||||
RemoveExistingWhenNecessary = true
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
|
||||
@@ -76,6 +76,10 @@ namespace Barotrauma
|
||||
// -> ignore find safety unless we need to find a diving gear
|
||||
Priority = 0;
|
||||
}
|
||||
else if (objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
|
||||
@@ -401,6 +401,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!ownerItem.IsInteractable(character)) { 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;
|
||||
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.
|
||||
// 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.
|
||||
|
||||
// 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 IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace Barotrauma
|
||||
class AIObjectiveReturn : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "return";
|
||||
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior;
|
||||
private bool isSteeringThroughGap;
|
||||
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)
|
||||
{
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
@@ -112,7 +112,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
TryAddSubObjective(ref moveInsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
|
||||
@@ -137,91 +136,41 @@ namespace Barotrauma
|
||||
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;
|
||||
float closestDistance = float.MaxValue;
|
||||
foreach (var w in WayPoint.WayPointList)
|
||||
bool hullIsAirlock = hull.IsTaggedAirlock();
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
{
|
||||
if (w.Tunnel != null && w.Tunnel.Type == Level.TunnelType.Cave) { continue; }
|
||||
if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; }
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition);
|
||||
if (closestOutsideWaypoint == null || distance < closestDistance)
|
||||
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
if (targetHull == null || distanceSquared < targetDistanceSquared)
|
||||
{
|
||||
closestOutsideWaypoint = w;
|
||||
closestDistance = distance;
|
||||
targetHull = hull;
|
||||
targetDistanceSquared = distanceSquared;
|
||||
targetIsAirlock = hullIsAirlock;
|
||||
}
|
||||
}
|
||||
if (closestOutsideWaypoint != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
TryAddSubObjective(ref moveInCaveObjective,
|
||||
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
|
||||
{
|
||||
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint,
|
||||
AllowGoingOutside = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective),
|
||||
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
|
||||
}
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
TryAddSubObjective(ref moveOutsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HumanAIController.IsInsideCave)
|
||||
{
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
}
|
||||
}
|
||||
usingEscapeBehavior = shouldUseEscapeBehavior;
|
||||
@@ -249,7 +198,6 @@ namespace Barotrauma
|
||||
{
|
||||
base.Reset();
|
||||
moveInsideObjective = null;
|
||||
moveInCaveObjective = null;
|
||||
moveOutsideObjective = null;
|
||||
usingEscapeBehavior = false;
|
||||
isSteeringThroughGap = false;
|
||||
|
||||
@@ -333,7 +333,6 @@ namespace Barotrauma
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (checkVisibility && isCharacter)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
var body = Submarine.PickBody(rayStart, node.TempPosition,
|
||||
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
@@ -350,6 +349,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(node)) { return false; }
|
||||
if (startNodeFilter != null && !startNodeFilter(node)) { return false; }
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
// Always check the visibility for the start node
|
||||
if (!IsWaypointVisible(node, start)) { return false; }
|
||||
if (node.IsBlocked()) { return false; }
|
||||
@@ -364,6 +364,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(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)
|
||||
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; }
|
||||
if (node.IsBlocked()) { return false; }
|
||||
|
||||
@@ -776,8 +776,8 @@ namespace Barotrauma
|
||||
if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
|
||||
limbDiff = Vector2.Normalize(limbDiff);
|
||||
float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
|
||||
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
limbJoint.LimbB.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 * Math.Min(mass, limbJoint.BodyB.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
|
||||
connectedLimbs.Clear();
|
||||
checkedJoints.Clear();
|
||||
|
||||
@@ -461,7 +461,7 @@ namespace Barotrauma
|
||||
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;
|
||||
if (OnlyHumans)
|
||||
@@ -486,10 +486,10 @@ namespace Barotrauma
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -526,7 +526,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -553,7 +553,7 @@ namespace Barotrauma
|
||||
effect.sourceBody = sourceBody;
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -503,7 +503,7 @@ namespace Barotrauma
|
||||
get { return cursorPosition; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
cursorPosition = value;
|
||||
}
|
||||
}
|
||||
@@ -853,7 +853,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC;
|
||||
return IsKnockedDown || LockHands || IsBot && IsOnPlayerTeam;
|
||||
}
|
||||
}
|
||||
set { canInventoryBeAccessed = value; }
|
||||
@@ -3596,8 +3596,12 @@ namespace Barotrauma
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
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.
|
||||
if (joint.LimbB != targetLimb) { continue; }
|
||||
// Limb A is where we start creating the joint and LimbB is where the joint ends.
|
||||
// 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;
|
||||
if (!IsDead)
|
||||
{
|
||||
|
||||
@@ -1134,6 +1134,10 @@ namespace Barotrauma
|
||||
{
|
||||
head.HairWithHatElement = hairs[hairWithHatIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
head.HairWithHatElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsValidIndex(Head.BeardIndex, beards))
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace Barotrauma
|
||||
if (huskPrefab.ControlHusk)
|
||||
{
|
||||
#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)
|
||||
{
|
||||
GameMain.Server.SetClientCharacter(client, husk);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
//if (character.Params.CanInteract) { return false; }
|
||||
if (this == character.AnimController.MainLimb) { return false; }
|
||||
bool canBeSevered = Params.CanBeSeveredAlive;
|
||||
if (character.AnimController.CanWalk)
|
||||
{
|
||||
switch (type)
|
||||
@@ -571,7 +572,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return canBeSevered;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,7 +1071,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
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
|
||||
{
|
||||
@@ -1080,7 +1081,7 @@ namespace Barotrauma
|
||||
}
|
||||
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))
|
||||
|
||||
@@ -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)]
|
||||
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
|
||||
[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;
|
||||
|
||||
@@ -96,8 +96,8 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(500.0f, true)]
|
||||
public float MaximumOlympianSkill
|
||||
[Serialize(200.0f, true)]
|
||||
public float MaximumSkillWithTalents
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
@@ -7,9 +8,26 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
private readonly List<TargetType> targetTypes;
|
||||
|
||||
private List<PropertyConditional> conditionals = new List<PropertyConditional>();
|
||||
|
||||
public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
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)
|
||||
@@ -18,7 +36,10 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (!(abilityCharacter.Character is Character character)) { return false; }
|
||||
if (!IsViableTarget(targetTypes, character)) { return false; }
|
||||
|
||||
foreach (var conditional in conditionals)
|
||||
{
|
||||
if (!conditional.Matches(character)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -978,7 +978,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
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"));
|
||||
}
|
||||
},null));
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Barotrauma
|
||||
|
||||
public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
private IEnumerable<Character> affectedNpcs;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
||||
{
|
||||
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)
|
||||
{
|
||||
gotoObjective.Abandon = true;
|
||||
|
||||
@@ -796,8 +796,9 @@ namespace Barotrauma
|
||||
monsterStrength += enemyAI.CombatStrength;
|
||||
}
|
||||
|
||||
if (character.CurrentHull?.Submarine != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
|
||||
if (character.CurrentHull?.Submarine?.Info != null &&
|
||||
(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
|
||||
enemyDanger += enemyAI.CombatStrength / 500.0f;
|
||||
|
||||
@@ -50,7 +50,8 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
// Disabled for now, because they make balancing the missions a pain.
|
||||
int multiplier = 1;//CalculateScalingEscortedCharacterCount();
|
||||
calculatedReward = Prefab.Reward * multiplier;
|
||||
|
||||
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 (Item item in character.Inventory.AllItemsMod)
|
||||
foreach (Character character in characters)
|
||||
{
|
||||
//item didn't spawn with the characters -> drop it
|
||||
if (!characterItems.Any(c => c.Value.Contains(item)))
|
||||
if (character.Inventory == null) { continue; }
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
if (Survived(characterItem.Key) || !completed)
|
||||
// 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
|
||||
foreach (var characterItem in characterItems)
|
||||
{
|
||||
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);
|
||||
#endif
|
||||
ShowMessage(State);
|
||||
OnMissionStateChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +146,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
|
||||
|
||||
|
||||
public Action<Mission> OnMissionStateChanged;
|
||||
|
||||
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(locations.Length == 2);
|
||||
|
||||
@@ -96,9 +96,9 @@ namespace Barotrauma
|
||||
public readonly bool RequireWreck;
|
||||
|
||||
/// <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>
|
||||
public readonly List<Pair<string, string>> AllowedConnectionTypes;
|
||||
public readonly List<(string from, string to)> AllowedConnectionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
|
||||
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", "");
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
AllowRetry = element.GetAttributeBool("allowretry", false);
|
||||
@@ -209,10 +216,20 @@ namespace Barotrauma
|
||||
FailureMessage = element.GetAttributeString("failuremessage", "");
|
||||
}
|
||||
|
||||
SonarLabel =
|
||||
TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ??
|
||||
TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ??
|
||||
element.GetAttributeString("sonarlabel", "");
|
||||
if (element.Attribute("sonarlabel") == null)
|
||||
{
|
||||
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", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
@@ -224,7 +241,7 @@ namespace Barotrauma
|
||||
|
||||
Headers = 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++)
|
||||
{
|
||||
@@ -260,9 +277,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowedConnectionTypes.Add(new Pair<string, string>(
|
||||
subElement.GetAttributeString("from", ""),
|
||||
subElement.GetAttributeString("to", "")));
|
||||
AllowedConnectionTypes.Add((subElement.GetAttributeString("from", "").ToLowerInvariant(), subElement.GetAttributeString("to", "").ToLowerInvariant()));
|
||||
}
|
||||
break;
|
||||
case "locationtypechange":
|
||||
@@ -358,13 +373,15 @@ namespace Barotrauma
|
||||
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) ||
|
||||
allowedConnectionType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
if (fromType.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
fromType.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase) ||
|
||||
(fromType == "anyoutpost" && from.HasOutpost()))
|
||||
{
|
||||
if (allowedConnectionType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedConnectionType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
if (toType.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
toType.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase) ||
|
||||
(toType == "anyoutpost" && to.HasOutpost()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -345,12 +345,13 @@ namespace Barotrauma
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
int newState = State;
|
||||
if (state >= 2) { return; }
|
||||
|
||||
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
|
||||
outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange;
|
||||
if (State < 2 && CheckWinState())
|
||||
if (CheckWinState())
|
||||
{
|
||||
newState = 2;
|
||||
State = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -366,7 +367,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!outsideOfSonarRange || patrolPositions.None())
|
||||
{
|
||||
newState = 1;
|
||||
State = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@@ -391,14 +392,13 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m));
|
||||
|
||||
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()
|
||||
|
||||
@@ -149,9 +149,17 @@ namespace Barotrauma
|
||||
internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions)
|
||||
=> 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;
|
||||
internal void SetEnabledInfoLog(bool enabled)
|
||||
=> setEnabledInfoLog(enabled);
|
||||
|
||||
private readonly Action<bool> setEnabledVerboseLog;
|
||||
internal void SetEnabledVerboseLog(bool enabled)
|
||||
=> setEnabledVerboseLog(enabled);
|
||||
#endregion
|
||||
|
||||
#region Data required to fetch methods via reflection
|
||||
@@ -292,10 +300,14 @@ namespace Barotrauma
|
||||
|
||||
configureAvailableResourceCurrencies = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceCurrencies),
|
||||
new Type[] { typeof(string[]) }));
|
||||
configureAvailableResourceItemTypes = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceItemTypes),
|
||||
new Type[] { typeof(string[]) }));
|
||||
addResourceEvent = Call<ResourceFlowType, string, float, string, string>(getMethod(nameof(AddResourceEvent),
|
||||
new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) }));
|
||||
setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog),
|
||||
new Type[] { typeof(bool) }));
|
||||
setEnabledVerboseLog = Call<bool>(getMethod(nameof(SetEnabledVerboseLog),
|
||||
new Type[] { typeof(bool) }));
|
||||
|
||||
onQuit = Call(getMethod("OnQuit", Array.Empty<Type>()));
|
||||
}
|
||||
@@ -450,6 +462,7 @@ namespace Barotrauma
|
||||
try
|
||||
{
|
||||
loadedImplementation?.SetEnabledInfoLog(true);
|
||||
loadedImplementation?.SetEnabledVerboseLog(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -489,7 +502,10 @@ namespace Barotrauma
|
||||
+ AssemblyInfo.GitRevision + ":"
|
||||
+ buildConfiguration);
|
||||
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?.ConfigureAvailableResourceItemTypes(
|
||||
Enum.GetValues(typeof(MoneySink)).Cast<MoneySink>().Select(s => s.ToString()).Union(Enum.GetValues(typeof(MoneySource)).Cast<MoneySource>().Select(s => s.ToString())).ToArray());
|
||||
|
||||
InitKeys();
|
||||
|
||||
@@ -521,7 +537,7 @@ namespace Barotrauma
|
||||
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
|
||||
}
|
||||
packageNames.Sort();
|
||||
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames));
|
||||
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(" ", packageNames));
|
||||
}
|
||||
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]
|
||||
{
|
||||
(0, 0.85f),
|
||||
(1, 0.125f),
|
||||
(2, 0.0225f),
|
||||
(3, 0.0025f),
|
||||
(0, 1.0f),
|
||||
(1, 0.0f),
|
||||
(2, 0.0f),
|
||||
(3, 0.0f),
|
||||
};
|
||||
|
||||
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 int Quantity { get; set; }
|
||||
public bool? IsStoreComponentEnabled { get; set; }
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,11 +427,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
itemSpawned(item);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
|
||||
itemSpawned(item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(item, false);
|
||||
#endif
|
||||
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
|
||||
static void itemSpawned(Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
|
||||
@@ -81,7 +81,12 @@ namespace Barotrauma
|
||||
public double TotalPlayTime;
|
||||
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 UpgradeManager UpgradeManager;
|
||||
|
||||
@@ -440,6 +440,7 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
|
||||
}
|
||||
}
|
||||
GameAnalyticsManager.AddDesignEvent($"{eventId}HintManager:{(HintManager.Enabled ? "Enabled" : "Disabled")}");
|
||||
#endif
|
||||
if (GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace Barotrauma.Items.Components
|
||||
if (character != null && !CharacterUsable) { return false; }
|
||||
|
||||
CurrPowerConsumption = powerConsumption;
|
||||
Voltage = 0.0f;
|
||||
charging = true;
|
||||
timer = Duration;
|
||||
IsActive = true;
|
||||
@@ -141,7 +142,7 @@ namespace Barotrauma.Items.Components
|
||||
timer -= deltaTime;
|
||||
if (charging)
|
||||
{
|
||||
if (GetAvailableBatteryPower() >= powerConsumption)
|
||||
if (GetAvailableInstantaneousBatteryPower() >= powerConsumption)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
float neededPower = powerConsumption;
|
||||
|
||||
@@ -130,6 +130,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
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; }
|
||||
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.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");
|
||||
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
|
||||
Vector2 pos = new Vector2(sub.WorldPosition.X * Physics.DisplayToRealWorldRatio, sub.RealWorldDepth);
|
||||
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
|
||||
|
||||
@@ -302,17 +302,24 @@ namespace Barotrauma.Items.Components
|
||||
/// <summary>
|
||||
/// Returns the amount of power that can be supplied by batteries directly connected to the item
|
||||
/// </summary>
|
||||
protected float GetAvailableBatteryPower()
|
||||
protected float GetAvailableInstantaneousBatteryPower()
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
if (item.Connections == null) { return 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);
|
||||
availablePower += batteryPower;
|
||||
}
|
||||
var recipients = c.Recipients;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,15 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private float deactivationTimer;
|
||||
|
||||
[Serialize(0f, false)]
|
||||
public float DeactivationTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
@@ -207,6 +216,9 @@ namespace Barotrauma.Items.Components
|
||||
get { return StickTarget != null; }
|
||||
}
|
||||
|
||||
private Category originalCollisionCategories;
|
||||
private Category originalCollisionTargets;
|
||||
|
||||
public Projectile(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
@@ -223,21 +235,26 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
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:
|
||||
Attack.DamageRange = item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Capsule:
|
||||
Attack.DamageRange = item.body.height / 2 + item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Rectangle:
|
||||
Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length();
|
||||
break;
|
||||
switch (item.body.BodyShape)
|
||||
{
|
||||
case PhysicsBody.Shape.Circle:
|
||||
Attack.DamageRange = item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Capsule:
|
||||
Attack.DamageRange = item.body.height / 2 + item.body.radius;
|
||||
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;
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
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)
|
||||
@@ -585,7 +606,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (dropper != null)
|
||||
{
|
||||
Deactivate();
|
||||
DisableProjectileCollisions();
|
||||
Unstick();
|
||||
}
|
||||
base.Drop(dropper);
|
||||
@@ -593,6 +614,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (DeactivationTime > 0)
|
||||
{
|
||||
deactivationTimer -= deltaTime;
|
||||
if (deactivationTimer < 0)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
}
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
@@ -614,8 +643,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
//projectiles with a stickjoint don't become inactive until the stickjoint is detached
|
||||
if (stickJoint == null && !item.body.FarseerBody.IsBullet)
|
||||
{
|
||||
IsActive = false;
|
||||
{
|
||||
IsActive = false;
|
||||
if (DeactivationTime > 0 && deactivationTimer > 0)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
}
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
@@ -715,7 +748,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
{
|
||||
Deactivate();
|
||||
DisableProjectileCollisions();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -864,7 +897,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
|
||||
{
|
||||
Deactivate();
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
@@ -934,18 +967,26 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
private void DisableProjectileCollisions()
|
||||
{
|
||||
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.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
item.body.CollisionCategories = originalCollisionCategories;
|
||||
item.body.CollidesWith = originalCollisionTargets;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -996,7 +1037,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
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();
|
||||
if (stickTargetCharacter != null)
|
||||
{
|
||||
|
||||
@@ -236,6 +236,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
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();
|
||||
|
||||
@@ -542,7 +542,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool HasPowerToShoot()
|
||||
{
|
||||
return GetAvailableBatteryPower() >= GetPowerRequiredToShoot();
|
||||
return GetAvailableInstantaneousBatteryPower() >= GetPowerRequiredToShoot();
|
||||
}
|
||||
|
||||
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
|
||||
|
||||
@@ -365,7 +365,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -98,6 +98,14 @@ namespace Barotrauma
|
||||
private readonly ItemInventory ownInventory;
|
||||
|
||||
private Rectangle defaultRect;
|
||||
/// <summary>
|
||||
/// Unscaled rect
|
||||
/// </summary>
|
||||
public Rectangle DefaultRect
|
||||
{
|
||||
get { return defaultRect; }
|
||||
set { defaultRect = value; }
|
||||
}
|
||||
|
||||
private Dictionary<string, Connection> connections;
|
||||
|
||||
@@ -645,10 +653,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float buoyancySineMagnitude;
|
||||
private float buoyancySineFrequency;
|
||||
private float buoyancyRandomForce;
|
||||
|
||||
public bool FireProof
|
||||
{
|
||||
get { return Prefab.FireProof; }
|
||||
@@ -767,7 +771,7 @@ namespace Barotrauma
|
||||
get { return Position.X; }
|
||||
private set
|
||||
{
|
||||
Move(new Vector2((value - Position.X) * Scale, 0.0f));
|
||||
Move(new Vector2(value * Scale, 0.0f));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -778,7 +782,7 @@ namespace Barotrauma
|
||||
get { return Position.Y; }
|
||||
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())
|
||||
{
|
||||
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);
|
||||
if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0)
|
||||
{
|
||||
@@ -879,9 +892,6 @@ namespace Barotrauma
|
||||
}
|
||||
body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f);
|
||||
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;
|
||||
break;
|
||||
case "trigger":
|
||||
@@ -1600,7 +1610,7 @@ namespace Barotrauma
|
||||
float damageAmount = attack.GetItemDamage(deltaTime);
|
||||
Condition -= damageAmount;
|
||||
|
||||
if (damageAmount > 0)
|
||||
if (damageAmount >= Prefab.OnDamagedThreshold)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
}
|
||||
@@ -1729,7 +1739,7 @@ namespace Barotrauma
|
||||
UpdateNetPosition(deltaTime);
|
||||
if (inWater)
|
||||
{
|
||||
ApplyWaterForces(deltaTime);
|
||||
ApplyWaterForces();
|
||||
CurrentHull?.ApplyFlowForces(deltaTime, this);
|
||||
}
|
||||
}
|
||||
@@ -1818,24 +1828,16 @@ namespace Barotrauma
|
||||
transformDirty = false;
|
||||
}
|
||||
|
||||
private float sineTime;
|
||||
/// <summary>
|
||||
/// Applies buoyancy, drag and angular drag caused by water
|
||||
/// </summary>
|
||||
private void ApplyWaterForces(float deltaTime)
|
||||
private void ApplyWaterForces()
|
||||
{
|
||||
if (body.Mass <= 0.0f || body.Density <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (buoyancySineFrequency > 0)
|
||||
{
|
||||
if (sineTime >= float.MaxValue)
|
||||
{
|
||||
sineTime = float.MinValue;
|
||||
}
|
||||
sineTime += deltaTime * buoyancySineFrequency;
|
||||
}
|
||||
|
||||
float forceFactor = 1.0f;
|
||||
if (CurrentHull != null)
|
||||
{
|
||||
@@ -1854,10 +1856,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 drag = body.LinearVelocity * volume;
|
||||
|
||||
float sine = (float)Math.Sin(sineTime) * buoyancySineMagnitude;
|
||||
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);
|
||||
body.ApplyForce((uplift - drag) * 10.0f);
|
||||
|
||||
//apply simple angular drag
|
||||
body.ApplyTorque(body.AngularVelocity * volume * -0.05f);
|
||||
@@ -1869,9 +1868,17 @@ namespace Barotrauma
|
||||
if (transformDirty) { return false; }
|
||||
|
||||
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 _);
|
||||
@@ -2216,7 +2223,7 @@ namespace Barotrauma
|
||||
foreach (Rectangle trigger in Prefab.Triggers)
|
||||
{
|
||||
transformedTrigger = TransformTrigger(trigger, true);
|
||||
if (Submarine.RectContains(transformedTrigger, worldPosition)) return true;
|
||||
if (Submarine.RectContains(transformedTrigger, worldPosition)) { return true; }
|
||||
}
|
||||
|
||||
transformedTrigger = Rectangle.Empty;
|
||||
@@ -2230,7 +2237,10 @@ namespace Barotrauma
|
||||
|
||||
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;
|
||||
#if CLIENT
|
||||
|
||||
@@ -504,6 +504,9 @@ namespace Barotrauma
|
||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float OnDamagedThreshold { get; set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float SonarSize
|
||||
{
|
||||
|
||||
@@ -1723,7 +1723,7 @@ namespace Barotrauma
|
||||
{
|
||||
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));
|
||||
continue;
|
||||
}
|
||||
@@ -1842,7 +1842,7 @@ namespace Barotrauma
|
||||
Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 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);
|
||||
|
||||
@@ -2107,12 +2107,42 @@ namespace Barotrauma
|
||||
|
||||
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)));
|
||||
validPoints.RemoveAll(d => d.point.Y < GetBottomPosition(d.point.X).Y + minDistance);
|
||||
if (asCloseAsPossible || !validPoints.Any())
|
||||
var pointsAboveBottom = distanceField.FindAll(d => d.point.Y > GetBottomPosition(d.point.X).Y + minDistance);
|
||||
if (pointsAboveBottom.Count == 0)
|
||||
{
|
||||
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; }
|
||||
(Point position, double distance) closestPoint = validPoints.First();
|
||||
(Point position, double distance) closestPoint = validPoints.First();
|
||||
foreach (var point in validPoints)
|
||||
{
|
||||
if (point.distance < closestPoint.distance)
|
||||
@@ -2172,7 +2202,7 @@ namespace Barotrauma
|
||||
{
|
||||
double xDiff = Math.Abs(point.X - ruinPos.X);
|
||||
double yDiff = Math.Abs(point.Y - ruinPos.Y);
|
||||
if (xDiff < ruinSize || yDiff < ruinSize)
|
||||
if (xDiff < ruinSize && yDiff < ruinSize)
|
||||
{
|
||||
shortestDistSqr = 0.0f;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
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.color = color;
|
||||
@@ -74,14 +74,17 @@ namespace Barotrauma
|
||||
wallCell.Edges[i].IsSolid = true;
|
||||
}
|
||||
Cells = new List<VoronoiCell>() { wallCell };
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
if (triangles.Count == 0)
|
||||
if (createBody)
|
||||
{
|
||||
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
|
||||
GenerateVertices();
|
||||
GenerateVertices();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level)
|
||||
|
||||
@@ -1776,7 +1776,7 @@ namespace Barotrauma
|
||||
if (connectedWp.isObstructed) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.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)
|
||||
{
|
||||
connectedWp.isObstructed = true;
|
||||
@@ -1803,7 +1803,7 @@ namespace Barotrauma
|
||||
foreach (var connection in node.connections)
|
||||
{
|
||||
var connectedWp = connection.Waypoint;
|
||||
if (connectedWp.isObstructed) { continue; }
|
||||
if (connectedWp.isObstructed || connectedWp.Ladders != null) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
|
||||
var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true);
|
||||
|
||||
@@ -315,14 +315,33 @@ namespace Barotrauma
|
||||
set { FarseerBody.BodyType = value; }
|
||||
}
|
||||
|
||||
private Category _collisionCategories;
|
||||
|
||||
public Category CollisionCategories
|
||||
{
|
||||
set { FarseerBody.CollisionCategories = value; }
|
||||
set
|
||||
{
|
||||
_collisionCategories = value;
|
||||
FarseerBody.CollisionCategories = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return _collisionCategories;
|
||||
}
|
||||
}
|
||||
|
||||
private 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) { }
|
||||
@@ -383,12 +402,12 @@ namespace Barotrauma
|
||||
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 height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 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);
|
||||
Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType);
|
||||
FarseerBody.BodyType = bodyType;
|
||||
|
||||
@@ -245,6 +245,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory);
|
||||
|
||||
Character.Controlled?.UpdateLocalCursor(cam);
|
||||
#endif
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
|
||||
@@ -929,6 +929,21 @@ namespace Barotrauma
|
||||
(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))
|
||||
|
||||
@@ -133,7 +133,8 @@ namespace Barotrauma
|
||||
Target,
|
||||
Limb,
|
||||
MainLimb,
|
||||
Collider
|
||||
Collider,
|
||||
Random
|
||||
}
|
||||
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
@@ -1487,7 +1488,7 @@ namespace Barotrauma
|
||||
|
||||
if (giveTalentInfo.GiveRandom)
|
||||
{
|
||||
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
|
||||
targetCharacter.GiveTalent(viableTalents.GetRandom(Rand.RandSync.Unsynced), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1542,7 +1543,7 @@ namespace Barotrauma
|
||||
var characters = new List<Character>();
|
||||
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 =>
|
||||
{
|
||||
if (newCharacter.AIController is EnemyAIController enemyAi &&
|
||||
@@ -1563,7 +1564,7 @@ namespace Barotrauma
|
||||
|
||||
if (spawnItemRandomly)
|
||||
{
|
||||
SpawnItem(spawnItems.GetRandom());
|
||||
SpawnItem(spawnItems.GetRandom(Rand.RandSync.Unsynced));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1582,7 +1583,7 @@ namespace Barotrauma
|
||||
switch (chosenItemSpawnInfo.SpawnPosition)
|
||||
{
|
||||
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>();
|
||||
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));
|
||||
var worldPos = sourceBody.Position;
|
||||
float rotation = chosenItemSpawnInfo.Rotation;
|
||||
float rotation = 0;
|
||||
if (user.Submarine != null)
|
||||
{
|
||||
worldPos += user.Submarine.Position;
|
||||
@@ -1614,11 +1615,14 @@ namespace Barotrauma
|
||||
rotation = sourceBody.TransformedRotation;
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Collider:
|
||||
rotation = user.AnimController.Collider.Rotation;
|
||||
rotation = user.AnimController.Collider.Rotation + MathHelper.PiOver2;
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.MainLimb:
|
||||
rotation = user.AnimController.MainLimb.body.TransformedRotation;
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Random:
|
||||
DebugConsole.ShowError("Random rotation is not supported for Projectiles.");
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType);
|
||||
}
|
||||
@@ -1631,19 +1635,37 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
float rotation = MathHelper.ToRadians(chosenItemSpawnInfo.Rotation);
|
||||
if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Limb)
|
||||
switch (chosenItemSpawnInfo.RotationType)
|
||||
{
|
||||
if (sourceBody != null)
|
||||
{
|
||||
rotation += sourceBody.Rotation;
|
||||
}
|
||||
}
|
||||
else if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Collider)
|
||||
{
|
||||
if (entity is Character character)
|
||||
{
|
||||
rotation += character.AnimController.Collider.Rotation;
|
||||
}
|
||||
case ItemSpawnInfo.SpawnRotationType.Fixed:
|
||||
if (sourceBody != null)
|
||||
{
|
||||
rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.Rotation);
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Limb:
|
||||
if (sourceBody != null)
|
||||
{
|
||||
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.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
Reference in New Issue
Block a user