Unstable 0.16.3.0

This commit is contained in:
Markus Isberg
2022-02-10 02:52:08 +09:00
parent 6814a11520
commit 2190fe08ef
115 changed files with 993 additions and 453 deletions

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,8 @@
namespace Barotrauma
{
partial class BeaconMission : Mission
{
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
}
}

View File

@@ -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)));

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -0,0 +1,8 @@
namespace Barotrauma
{
partial class GoToMission : Mission
{
public override bool IsAtCompletionState => false;
public override bool IsAtFailureState => false;
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)

View File

@@ -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();
}
}
}

View File

@@ -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;
}

View File

@@ -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 --------------------------------------

View File

@@ -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),

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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();

View File

@@ -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;

View File

@@ -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; }
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -251,7 +251,7 @@ namespace Barotrauma.CharacterEditor
GUI.ForceMouseOn(null);
if (isEndlessRunner)
{
Submarine.MainSub.Remove();
Submarine.MainSub?.Remove();
GameMain.World.ProcessChanges();
isEndlessRunner = false;
Reset();

View File

@@ -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())

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -108,7 +108,7 @@ namespace Barotrauma
AllowToFindDivingGear = false,
AllowDangerousPressure = true,
ConditionLevel = MIN_OXYGEN,
RemoveExisting = true
RemoveExistingWhenNecessary = true
};
},
onAbandon: () =>

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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;

View File

@@ -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; }

View File

@@ -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();

View File

@@ -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))
{

View File

@@ -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)
{

View File

@@ -1134,6 +1134,10 @@ namespace Barotrauma
{
head.HairWithHatElement = hairs[hairWithHatIndex];
}
else
{
head.HairWithHatElement = null;
}
}
if (IsValidIndex(Head.BeardIndex, beards))

View File

@@ -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);

View File

@@ -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;

View File

@@ -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))

View File

@@ -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;

View File

@@ -96,8 +96,8 @@ namespace Barotrauma
set;
}
[Serialize(500.0f, true)]
public float MaximumOlympianSkill
[Serialize(200.0f, true)]
public float MaximumSkillWithTalents
{
get;
set;

View File

@@ -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

View File

@@ -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));

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
}
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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();

View File

@@ -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)

View File

@@ -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;

View File

@@ -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

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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))

View File

@@ -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);

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