Merge remote-tracking branch 'origin/master' into dev
# Conflicts: # Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs # Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs # Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs # Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs # Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs # Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs # Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs # Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs # Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs # Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs # Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs # Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs # Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs # Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs # Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs # Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs # Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs # Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs # Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs # Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs # Barotrauma/BarotraumaClient/LinuxClient.csproj # Barotrauma/BarotraumaClient/MacClient.csproj # Barotrauma/BarotraumaClient/WindowsClient.csproj # Barotrauma/BarotraumaServer/LinuxServer.csproj # Barotrauma/BarotraumaServer/MacServer.csproj # Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs # Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs # Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs # Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs # Barotrauma/BarotraumaServer/ServerSource/Utils/DoSProtection.cs # Barotrauma/BarotraumaServer/WindowsServer.csproj # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs # Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs # Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs # Barotrauma/BarotraumaShared/SharedSource/Enums.cs # Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs # Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs # Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs # Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs # Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs # Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs # Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs # Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs # Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs # Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs # Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs # Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs # Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs # Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs # Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs # Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs # Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs # Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs # Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs # Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs # Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs # Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs # Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs # Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs # Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs # Barotrauma/BarotraumaShared/changelog.txt
This commit is contained in:
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
var target = _selectedAiTarget ?? _lastAiTarget;
|
||||
if (target != null && target.Entity != null)
|
||||
{
|
||||
var memory = GetTargetMemory(target, false);
|
||||
var memory = GetTargetMemory(target);
|
||||
if (memory != null)
|
||||
{
|
||||
Vector2 targetPos = memory.Location;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Character == Character.Controlled) { return; }
|
||||
if (!debugai) { return; }
|
||||
if (!DebugAI) { return; }
|
||||
Vector2 pos = Character.WorldPosition;
|
||||
pos.Y = -pos.Y;
|
||||
Vector2 textOffset = new Vector2(-40, -160);
|
||||
|
||||
@@ -6,11 +6,17 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Attack
|
||||
{
|
||||
[Serialize("StructureBlunt", IsPropertySaveable.Yes), Editable()]
|
||||
[Serialize("StructureBlunt", IsPropertySaveable.Yes, description: "Name of the sound effect the attack makes when it hits a structure."), Editable()]
|
||||
public string StructureSoundType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when the attack deals damage.
|
||||
/// </summary>
|
||||
private RoundSound sound;
|
||||
|
||||
/// <summary>
|
||||
/// Particle emitter to use when the attack deals damage.
|
||||
/// </summary>
|
||||
private ParticleEmitter particleEmitter;
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
|
||||
@@ -582,7 +582,7 @@ namespace Barotrauma
|
||||
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
|
||||
foreach (MapEntity entity in entityList)
|
||||
{
|
||||
if (!(entity is Item item))
|
||||
if (entity is not Item item)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,6 @@ namespace Barotrauma
|
||||
|
||||
double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
|
||||
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
|
||||
TransformCursorPos();
|
||||
|
||||
bool ragdollInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Ragdoll].Held = ragdollInput;
|
||||
|
||||
@@ -850,7 +850,7 @@ namespace Barotrauma
|
||||
if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover)
|
||||
{
|
||||
//highlight the slot the treatment item is in
|
||||
var rootContainer = matchingItem.GetRootContainer() ?? matchingItem;
|
||||
var rootContainer = matchingItem.RootContainer ?? matchingItem;
|
||||
var index = Character.Controlled.Inventory.FindIndex(rootContainer);
|
||||
if (Character.Controlled.Inventory.visualSlots != null && index > -1 && index < Character.Controlled.Inventory.visualSlots.Length &&
|
||||
Character.Controlled.Inventory.visualSlots[index].HighlightTimer <= 0.0f)
|
||||
|
||||
@@ -870,7 +870,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (wearable.Type == WearableType.Hair)
|
||||
{
|
||||
if (HairWithHatSprite != null)
|
||||
if (HairWithHatSprite != null && !hideLimb)
|
||||
{
|
||||
DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
|
||||
depthStep += step;
|
||||
|
||||
+4
-2
@@ -86,7 +86,7 @@ namespace Barotrauma
|
||||
|
||||
public string ModVersion = ContentPackage.DefaultModVersion;
|
||||
|
||||
public Md5Hash? ExpectedHash { get; private set; }
|
||||
public Md5Hash? ExpectedHash { get; set; }
|
||||
|
||||
public bool IsCore = false;
|
||||
|
||||
@@ -125,9 +125,11 @@ namespace Barotrauma
|
||||
|
||||
public static string IncrementModVersion(string modVersion)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modVersion)) { return string.Empty; }
|
||||
|
||||
//look for an integer at the end of the string and increment it
|
||||
int startIndex = modVersion.Length - 1;
|
||||
while (char.IsDigit(modVersion[startIndex])) { startIndex--; }
|
||||
while (startIndex > 0 && char.IsDigit(modVersion[startIndex])) { startIndex--; }
|
||||
startIndex++;
|
||||
|
||||
if (startIndex >= modVersion.Length
|
||||
|
||||
@@ -9,6 +9,7 @@ using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -636,15 +637,29 @@ namespace Barotrauma
|
||||
commands.Add(new Command("wikiimage_character", "Save an image of the currently controlled character with a transparent background.", (string[] args) =>
|
||||
{
|
||||
if (Character.Controlled == null) { return; }
|
||||
WikiImage.Create(Character.Controlled);
|
||||
try
|
||||
{
|
||||
WikiImage.Create(Character.Controlled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("The command 'wikiimage_character' failed.", e);
|
||||
}
|
||||
}));
|
||||
|
||||
commands.Add(new Command("wikiimage_sub", "Save an image of the main submarine with a transparent background.", (string[] args) =>
|
||||
{
|
||||
if (Submarine.MainSub == null) { return; }
|
||||
MapEntity.SelectedList.Clear();
|
||||
MapEntity.ClearHighlightedEntities();
|
||||
WikiImage.Create(Submarine.MainSub);
|
||||
try
|
||||
{
|
||||
MapEntity.SelectedList.Clear();
|
||||
MapEntity.ClearHighlightedEntities();
|
||||
WikiImage.Create(Submarine.MainSub);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("The command 'wikiimage_sub' failed.", e);
|
||||
}
|
||||
}));
|
||||
|
||||
AssignRelayToServer("kick", false);
|
||||
@@ -1141,6 +1156,15 @@ namespace Barotrauma
|
||||
GameMain.LightManager.DebugLos = state;
|
||||
NewMessage("Los debug draw mode " + (GameMain.LightManager.DebugLos ? "enabled" : "disabled"), Color.Yellow);
|
||||
});
|
||||
AssignOnExecute("debugwiring", (string[] args) =>
|
||||
{
|
||||
if (args.None() || !bool.TryParse(args[0], out bool state))
|
||||
{
|
||||
state = !ConnectionPanel.DebugWiringMode;
|
||||
}
|
||||
ConnectionPanel.DebugWiringMode = state;
|
||||
NewMessage("Wiring debug mode " + (ConnectionPanel.DebugWiringMode ? "enabled" : "disabled"), Color.Yellow);
|
||||
});
|
||||
AssignRelayToServer("debugdraw", false);
|
||||
|
||||
AssignOnExecute("devmode", (string[] args) =>
|
||||
@@ -1263,8 +1287,8 @@ namespace Barotrauma
|
||||
|
||||
AssignOnExecute("debugai", (string[] args) =>
|
||||
{
|
||||
HumanAIController.debugai = !HumanAIController.debugai;
|
||||
if (HumanAIController.debugai)
|
||||
HumanAIController.DebugAI = !HumanAIController.DebugAI;
|
||||
if (HumanAIController.DebugAI)
|
||||
{
|
||||
GameMain.DevMode = true;
|
||||
GameMain.DebugDraw = true;
|
||||
@@ -1279,7 +1303,7 @@ namespace Barotrauma
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
GameMain.LightManager.LosAlpha = 1f;
|
||||
}
|
||||
NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.Yellow);
|
||||
NewMessage(HumanAIController.DebugAI ? "AI debug info visible" : "AI debug info hidden", Color.Yellow);
|
||||
});
|
||||
AssignRelayToServer("debugai", false);
|
||||
|
||||
@@ -2338,7 +2362,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (mapEntity is Item item)
|
||||
{
|
||||
item.Rect = new Rectangle(item.Rect.X, item.Rect.Y,
|
||||
item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
|
||||
(int)(item.Prefab.Sprite.size.X * item.Prefab.Scale),
|
||||
(int)(item.Prefab.Sprite.size.Y * item.Prefab.Scale));
|
||||
}
|
||||
@@ -2811,7 +2835,26 @@ namespace Barotrauma
|
||||
ContentPackageManager.EnabledPackages.ReloadCore();
|
||||
}));
|
||||
|
||||
#warning TODO: reimplement?
|
||||
#if WINDOWS
|
||||
commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
|
||||
{
|
||||
Process.Start("DedicatedServer.exe");
|
||||
}));
|
||||
|
||||
commands.Add(new Command("editserversettings", "", (string[] args) =>
|
||||
{
|
||||
if (Process.GetProcessesByName("DedicatedServer").Length > 0)
|
||||
{
|
||||
NewMessage("Can't be edited if DedicatedServer.exe is already running", Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start("notepad.exe", "serversettings.xml");
|
||||
}
|
||||
}));
|
||||
#endif
|
||||
|
||||
#warning TODO: reimplement?
|
||||
/*commands.Add(new Command("ingamemodswap", "", (string[] args) =>
|
||||
{
|
||||
ContentPackage.IngameModSwap = !ContentPackage.IngameModSwap;
|
||||
|
||||
@@ -662,34 +662,36 @@ namespace Barotrauma
|
||||
Identifier missionIdentifier = msg.ReadIdentifier();
|
||||
int locationIndex = msg.ReadInt32();
|
||||
int destinationIndex = msg.ReadInt32();
|
||||
|
||||
string missionName = msg.ReadString();
|
||||
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
|
||||
if (prefab != null)
|
||||
if (Screen.Selected != GameMain.NetLobbyScreen)
|
||||
{
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
|
||||
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
|
||||
if (prefab != null)
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
|
||||
{
|
||||
Location location = map.Locations[locationIndex];
|
||||
map.Discover(location, checkTalents: false);
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
|
||||
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
|
||||
{
|
||||
Location location = map.Locations[locationIndex];
|
||||
map.Discover(location, checkTalents: false);
|
||||
|
||||
LocationConnection? connection = null;
|
||||
if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
|
||||
{
|
||||
Location destination = map.Locations[destinationIndex];
|
||||
connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
|
||||
}
|
||||
if (connection != null)
|
||||
{
|
||||
location.UnlockMission(prefab, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.UnlockMission(prefab);
|
||||
LocationConnection? connection = null;
|
||||
if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
|
||||
{
|
||||
Location destination = map.Locations[destinationIndex];
|
||||
connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
|
||||
}
|
||||
if (connection != null)
|
||||
{
|
||||
location.UnlockMission(prefab, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.UnlockMission(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,13 +88,14 @@ namespace Barotrauma
|
||||
public static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element)
|
||||
=> TextManager.SpeciallyHandledCharCategories
|
||||
.Where(category => element.GetAttributeBool($"is{category}", category switch {
|
||||
// CJK isn't supported by default
|
||||
// CJK and Japanese aren't supported by default
|
||||
TextManager.SpeciallyHandledCharCategory.CJK => false,
|
||||
|
||||
TextManager.SpeciallyHandledCharCategory.Japanese => false,
|
||||
|
||||
// For backwards compatibility, we assume that Cyrillic is supported by default
|
||||
TextManager.SpeciallyHandledCharCategory.Cyrillic => true,
|
||||
|
||||
_ => throw new Exception("unreachable")
|
||||
_ => throw new NotImplementedException($"nameof{category} not implemented.")
|
||||
}))
|
||||
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
|
||||
|
||||
@@ -517,6 +518,10 @@ namespace Barotrauma
|
||||
GlyphData gd = GetGlyphData(charIndex);
|
||||
if (gd.TexIndex >= 0)
|
||||
{
|
||||
if (gd.TexIndex < 0 || gd.TexIndex >= textures.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Error while rendering text. Texture index was out of range. Text: {text}, char: {charIndex} index: {gd.TexIndex}, texture count: {textures.Count}");
|
||||
}
|
||||
Texture2D tex = textures[gd.TexIndex];
|
||||
Vector2 drawOffset;
|
||||
drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
|
||||
|
||||
@@ -533,7 +533,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (characterInfo.MinReputationToHire.factionId != Identifier.Empty)
|
||||
{
|
||||
if (campaign.GetReputation(characterInfo.MinReputationToHire.factionId) < characterInfo.MinReputationToHire.reputation)
|
||||
if (MathF.Round(campaign.GetReputation(characterInfo.MinReputationToHire.factionId)) < characterInfo.MinReputationToHire.reputation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -532,6 +532,7 @@ namespace Barotrauma
|
||||
void drawRect(Vector2 topLeft, Vector2 bottomRight)
|
||||
{
|
||||
int minWidth = GUI.IntScale(5);
|
||||
if (OverflowClip) { topLeft.X = Math.Max(topLeft.X, 0.0f); }
|
||||
if (bottomRight.X - topLeft.X < minWidth) { bottomRight.X = topLeft.X + minWidth; }
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
Rect.Location.ToVector2() + topLeft,
|
||||
@@ -801,6 +802,7 @@ namespace Barotrauma
|
||||
IEnumerable<GUITextBox> GetAndSortTextBoxes(GUIComponent parent) => parent.GetAllChildren<GUITextBox>().OrderBy(t => t.Rect.Y).ThenBy(t => t.Rect.X);
|
||||
GUITextBox SelectNextTextBox(GUIListBox listBox)
|
||||
{
|
||||
if (listBox?.SelectedComponent == null) { return null; }
|
||||
var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);
|
||||
if (textBoxes.Any())
|
||||
{
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdatePending()
|
||||
{
|
||||
if (!(pendingHealList is { } healList)) { return; }
|
||||
if (pendingHealList is not { } healList) { return; }
|
||||
|
||||
ImmutableArray<MedicalClinic.NetCrewMember> pendingList = medicalClinic.PendingHeals.ToImmutableArray();
|
||||
|
||||
@@ -493,20 +493,26 @@ namespace Barotrauma
|
||||
|
||||
GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
|
||||
{
|
||||
OnClicked = (_, _) =>
|
||||
OnClicked = (button, _) =>
|
||||
{
|
||||
if (isWaitingForServer) { return true; }
|
||||
|
||||
button.Enabled = false;
|
||||
isWaitingForServer = true;
|
||||
medicalClinic.TreatAllButtonAction(OnReceived);
|
||||
|
||||
bool wasSuccessful = medicalClinic.TreatAllButtonAction(_ => ReEnableButton());
|
||||
if (!wasSuccessful) { ReEnableButton(); }
|
||||
|
||||
void ReEnableButton()
|
||||
{
|
||||
isWaitingForServer = false;
|
||||
button.Enabled = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
|
||||
|
||||
void OnReceived(MedicalClinic.CallbackOnlyRequest obj)
|
||||
{
|
||||
isWaitingForServer = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
|
||||
@@ -585,8 +591,10 @@ namespace Barotrauma
|
||||
OnClicked = (button, _) =>
|
||||
{
|
||||
button.Enabled = false;
|
||||
medicalClinic.HealAllButtonAction(request =>
|
||||
isWaitingForServer = true;
|
||||
bool wasSuccessful = medicalClinic.HealAllButtonAction(request =>
|
||||
{
|
||||
isWaitingForServer = false;
|
||||
switch (request.HealResult)
|
||||
{
|
||||
case MedicalClinic.HealRequestResult.InsufficientFunds:
|
||||
@@ -600,6 +608,12 @@ namespace Barotrauma
|
||||
button.Enabled = true;
|
||||
ClosePopup();
|
||||
});
|
||||
|
||||
if (!wasSuccessful)
|
||||
{
|
||||
isWaitingForServer = false;
|
||||
button.Enabled = true;
|
||||
}
|
||||
ClosePopup();
|
||||
return true;
|
||||
}
|
||||
@@ -610,11 +624,19 @@ namespace Barotrauma
|
||||
ClickSound = GUISoundType.Cart,
|
||||
OnClicked = (button, _) =>
|
||||
{
|
||||
if (isWaitingForServer) { return true; }
|
||||
|
||||
button.Enabled = false;
|
||||
medicalClinic.ClearAllButtonAction(_ =>
|
||||
isWaitingForServer = true;
|
||||
|
||||
bool wasSuccessful = medicalClinic.ClearAllButtonAction(_ => ReEnableButton());
|
||||
if (!wasSuccessful) { ReEnableButton(); }
|
||||
|
||||
void ReEnableButton()
|
||||
{
|
||||
isWaitingForServer = false;
|
||||
button.Enabled = true;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -701,10 +723,15 @@ namespace Barotrauma
|
||||
OnClicked = (button, _) =>
|
||||
{
|
||||
button.Enabled = false;
|
||||
medicalClinic.RemovePendingButtonAction(crewMember, affliction, _ =>
|
||||
bool wasSuccessful = medicalClinic.RemovePendingButtonAction(crewMember, affliction, _ =>
|
||||
{
|
||||
button.Enabled = true;
|
||||
});
|
||||
|
||||
if (!wasSuccessful)
|
||||
{
|
||||
button.Enabled = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -792,7 +819,13 @@ namespace Barotrauma
|
||||
selectedCrewAfflictionList = popupAfflictionList;
|
||||
|
||||
isWaitingForServer = true;
|
||||
medicalClinic.RequestAfflictions(info, OnReceived);
|
||||
bool wasSuccessful = medicalClinic.RequestAfflictions(info, OnReceived);
|
||||
|
||||
if (!wasSuccessful)
|
||||
{
|
||||
isWaitingForServer = false;
|
||||
ClosePopup();
|
||||
}
|
||||
|
||||
void OnReceived(MedicalClinic.AfflictionRequest request)
|
||||
{
|
||||
@@ -800,6 +833,16 @@ namespace Barotrauma
|
||||
|
||||
if (request.Result != MedicalClinic.RequestResult.Success)
|
||||
{
|
||||
switch (request.Result)
|
||||
{
|
||||
case MedicalClinic.RequestResult.CharacterInfoMissing:
|
||||
DebugConsole.ThrowError($"Unable to select character \"{info.Character?.DisplayName}\" in medical clini because the character health was missing.");
|
||||
break;
|
||||
case MedicalClinic.RequestResult.CharacterNotFound:
|
||||
DebugConsole.ThrowError($"Unable to select character \"{info.Character?.DisplayName} in medical clinic because the server was unable to find a character with ID {info.ID}.");
|
||||
break;
|
||||
}
|
||||
|
||||
feedbackBlock.Text = GetErrorText(request.Result);
|
||||
feedbackBlock.TextColor = GUIStyle.Red;
|
||||
return;
|
||||
@@ -953,14 +996,20 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
|
||||
|
||||
ToggleElements(ElementState.Disabled, elementsToDisable);
|
||||
medicalClinic.AddPendingButtonAction(existingMember, request =>
|
||||
bool wasSuccessful = medicalClinic.AddPendingButtonAction(existingMember, request =>
|
||||
{
|
||||
if (request.Result == MedicalClinic.RequestResult.Timeout)
|
||||
{
|
||||
ToggleElements(ElementState.Enabled, elementsToDisable);
|
||||
}
|
||||
});
|
||||
|
||||
if (!wasSuccessful)
|
||||
{
|
||||
ToggleElements(ElementState.Enabled, elementsToDisable);
|
||||
}
|
||||
}
|
||||
|
||||
#warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString
|
||||
@@ -1090,9 +1139,8 @@ namespace Barotrauma
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
MedicalClinic.RequestResult.Error => TextManager.Get("error"),
|
||||
MedicalClinic.RequestResult.Timeout => TextManager.Get("medicalclinic.requesttimeout"),
|
||||
_ => "What the hell did you just do" // this should never happen
|
||||
_ => TextManager.Get("error")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (relativeOffset.NearlyEquals(value)) { return; }
|
||||
relativeOffset = value;
|
||||
recalculateRect = true;
|
||||
RecalculateChildren(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,7 +870,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var minRep in priceInfo.MinReputation)
|
||||
{
|
||||
if (campaign.GetReputation(minRep.Key) < minRep.Value)
|
||||
if (MathF.Round(campaign.GetReputation(minRep.Key)) < minRep.Value)
|
||||
{
|
||||
return minRep;
|
||||
}
|
||||
@@ -1930,7 +1930,7 @@ namespace Barotrauma
|
||||
"campaignstore.reputationrequired",
|
||||
("[amount]", ((int)requiredReputation.Value.Value).ToString()),
|
||||
("[faction]", TextManager.Get("faction." + requiredReputation.Value.Key).Value));
|
||||
Color color = campaign.GetReputation(requiredReputation.Value.Key) < requiredReputation.Value.Value ?
|
||||
Color color = MathF.Round(campaign.GetReputation(requiredReputation.Value.Key)) < requiredReputation.Value.Value ?
|
||||
GUIStyle.Orange : GUIStyle.Green;
|
||||
toolTip += $"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖";
|
||||
}
|
||||
|
||||
@@ -807,8 +807,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
|
||||
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
|
||||
if (GameMain.GameSession.TryPurchaseSubmarine(selectedSubmarine))
|
||||
{
|
||||
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
|
||||
}
|
||||
RefreshSubmarineDisplay(true);
|
||||
}
|
||||
else
|
||||
@@ -829,7 +831,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
|
||||
GameMain.GameSession.TryPurchaseSubmarine(selectedSubmarine);
|
||||
RefreshSubmarineDisplay(true);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -23,9 +23,12 @@ namespace Barotrauma
|
||||
private float votingTime = 100f;
|
||||
private float timer;
|
||||
private VoteType currentVoteType;
|
||||
private Color SubmarineColor => GUIStyle.Orange;
|
||||
private static Color SubmarineColor => GUIStyle.Orange;
|
||||
private Point createdForResolution;
|
||||
|
||||
//timer ran out but server still hasn't notified of the result of the vote
|
||||
public bool TimedOut => VoteRunning && timer - votingTime > 10.0f;
|
||||
|
||||
public static VotingInterface CreateSubmarineVotingInterface(Client starter, SubmarineInfo info, VoteType type, bool transferItems, float votingTime)
|
||||
{
|
||||
if (starter == null || info == null) { return null; }
|
||||
|
||||
@@ -664,7 +664,10 @@ namespace Barotrauma
|
||||
while (Timing.Accumulator >= Timing.Step)
|
||||
{
|
||||
Timing.TotalTime += Timing.Step;
|
||||
|
||||
if (!Paused)
|
||||
{
|
||||
Timing.TotalTimeUnpaused += Timing.Step;
|
||||
}
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
@@ -944,7 +947,10 @@ namespace Barotrauma
|
||||
PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
|
||||
}
|
||||
|
||||
if (!Paused) { Timing.Alpha = Timing.Accumulator / Timing.Step; }
|
||||
if (!Paused)
|
||||
{
|
||||
Timing.Alpha = Timing.Accumulator / Timing.Step;
|
||||
}
|
||||
|
||||
if (performanceCounterTimer.ElapsedMilliseconds > 1000)
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
// Item must be in a non-equipment slot if possible
|
||||
if (!item.AllowedSlots.All(s => equipmentSlots.Contains(s)) && IsInEquipmentSlot(item)) { return false; }
|
||||
// Item must not be contained inside an item in an equipment slot
|
||||
if (item.GetRootContainer() is Item rootContainer && IsInEquipmentSlot(rootContainer)) { return false; }
|
||||
if (item.RootContainer is Item rootContainer && IsInEquipmentSlot(rootContainer)) { return false; }
|
||||
return true;
|
||||
}, recursive: true).Distinct();
|
||||
|
||||
|
||||
@@ -166,6 +166,9 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
|
||||
|
||||
bool allowEndingRound = false;
|
||||
endRoundButton.Color = endRoundButton.Style.Color;
|
||||
endRoundButton.HoverColor = endRoundButton.Style.HoverColor;
|
||||
RichString overrideEndRoundButtonToolTip = string.Empty;
|
||||
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
|
||||
LocalizedString buttonText = "";
|
||||
switch (availableTransition)
|
||||
@@ -194,13 +197,23 @@ namespace Barotrauma
|
||||
break;
|
||||
case TransitionType.None:
|
||||
default:
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
|
||||
!Level.Loaded.IsEndBiome &&
|
||||
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false)))
|
||||
bool inFriendlySub = Character.Controlled is { IsInFriendlySub: true };
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost && !Level.Loaded.IsEndBiome &&
|
||||
(inFriendlySub || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false)))
|
||||
{
|
||||
if (Missions.Any(m => m is SalvageMission salvageMission && salvageMission.AnyTargetNeedsToBeRetrievedToSub))
|
||||
{
|
||||
overrideEndRoundButtonToolTip = TextManager.Get("SalvageTargetNotInSub");
|
||||
endRoundButton.Color = GUIStyle.Red * 0.7f;
|
||||
endRoundButton.HoverColor = GUIStyle.Red;
|
||||
}
|
||||
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
||||
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
|
||||
}
|
||||
else
|
||||
{
|
||||
allowEndingRound = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
|
||||
@@ -227,7 +240,11 @@ namespace Barotrauma
|
||||
prevCampaignUIAutoOpenType = availableTransition;
|
||||
}
|
||||
endRoundButton.Text = ToolBox.LimitString(buttonText.Value, endRoundButton.Font, endRoundButton.Rect.Width - 5);
|
||||
if (endRoundButton.Text != buttonText)
|
||||
if (overrideEndRoundButtonToolTip != string.Empty)
|
||||
{
|
||||
endRoundButton.ToolTip = overrideEndRoundButtonToolTip;
|
||||
}
|
||||
else if (endRoundButton.Text != buttonText)
|
||||
{
|
||||
endRoundButton.ToolTip = buttonText;
|
||||
}
|
||||
|
||||
+7
-3
@@ -193,7 +193,7 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
yield return CoroutineStatus.Failure;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
if (GameMain.Client.LateCampaignJoin)
|
||||
@@ -335,7 +335,7 @@ namespace Barotrauma
|
||||
//--------------------------------------
|
||||
|
||||
//wait for the new level to be loaded
|
||||
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 60);
|
||||
DateTime timeOut = DateTime.Now + GameClient.LevelTransitionTimeOut;
|
||||
while (Level.Loaded == prevLevel || Level.Loaded == null)
|
||||
{
|
||||
if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; }
|
||||
@@ -345,7 +345,11 @@ namespace Barotrauma
|
||||
endTransition.Stop();
|
||||
overlayColor = Color.Transparent;
|
||||
|
||||
if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); }
|
||||
if (DateTime.Now > timeOut)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to start the round. Timed out while waiting for the level transition to finish.");
|
||||
GameMain.NetLobbyScreen.Select();
|
||||
}
|
||||
if (Screen.Selected is not RoundSummaryScreen)
|
||||
{
|
||||
if (continueButton != null)
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace Barotrauma
|
||||
{
|
||||
Undecided,
|
||||
Success,
|
||||
Error,
|
||||
CharacterInfoMissing,
|
||||
CharacterNotFound,
|
||||
Timeout
|
||||
}
|
||||
|
||||
@@ -34,7 +35,9 @@ namespace Barotrauma
|
||||
private readonly List<RequestAction<CallbackOnlyRequest>> addRequests = new List<RequestAction<CallbackOnlyRequest>>();
|
||||
private readonly List<RequestAction<CallbackOnlyRequest>> removeRequests = new List<RequestAction<CallbackOnlyRequest>>();
|
||||
|
||||
public void RequestAfflictions(CharacterInfo info, Action<AfflictionRequest> onReceived)
|
||||
private static readonly LeakyBucket requestBucket = new(RateLimitExpiry / (float)RateLimitMaxRequests, 10);
|
||||
|
||||
public bool RequestAfflictions(CharacterInfo info, Action<AfflictionRequest> onReceived)
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
@@ -42,23 +45,26 @@ namespace Barotrauma
|
||||
if (Screen.Selected is TestScreen)
|
||||
{
|
||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, TestAfflictions.ToImmutableArray()));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (info is not { Character.CharacterHealth: { } health })
|
||||
{
|
||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty));
|
||||
return;
|
||||
onReceived.Invoke(new AfflictionRequest(RequestResult.CharacterInfoMissing, ImmutableArray<NetAffliction>.Empty));
|
||||
return true;
|
||||
}
|
||||
|
||||
ImmutableArray<NetAffliction> pendingAfflictions = GetAllAfflictions(health).ToImmutableArray();
|
||||
ImmutableArray<NetAffliction> pendingAfflictions = GetAllAfflictions(health);
|
||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, pendingAfflictions));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
afflictionRequests.Add(new RequestAction<AfflictionRequest>(onReceived, GetTimeout()));
|
||||
SendAfflictionRequest(info);
|
||||
return requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
afflictionRequests.Add(new RequestAction<AfflictionRequest>(onReceived, GetTimeout()));
|
||||
SendAfflictionRequest(info);
|
||||
});
|
||||
}
|
||||
|
||||
public void RequestLatestPending(Action<PendingRequest> onReceived)
|
||||
@@ -66,8 +72,11 @@ namespace Barotrauma
|
||||
// no need to worry about syncing when there's only one pair of eyes capable of looking at the UI
|
||||
if (GameMain.IsSingleplayer) { return; }
|
||||
|
||||
pendingHealRequests.Add(new RequestAction<PendingRequest>(onReceived, GetTimeout()));
|
||||
SendPendingRequest();
|
||||
requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
pendingHealRequests.Add(new RequestAction<PendingRequest>(onReceived, GetTimeout()));
|
||||
SendPendingRequest();
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
@@ -79,6 +88,7 @@ namespace Barotrauma
|
||||
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||
requestBucket.Update(deltaTime);
|
||||
|
||||
static void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
|
||||
}
|
||||
@@ -146,21 +156,25 @@ namespace Barotrauma
|
||||
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void TreatAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||
public bool TreatAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
AddEverythingToPending();
|
||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||
OnUpdate?.Invoke();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(null, NetworkHeader.ADD_EVERYTHING_TO_PENDING, DeliveryMethod.Reliable);
|
||||
return requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(null, NetworkHeader.ADD_EVERYTHING_TO_PENDING, DeliveryMethod.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
public void HealAllButtonAction(Action<HealRequest> onReceived)
|
||||
|
||||
public bool HealAllButtonAction(Action<HealRequest> onReceived)
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
@@ -171,33 +185,39 @@ namespace Barotrauma
|
||||
OnUpdate?.Invoke();
|
||||
}
|
||||
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (campaign?.CampaignUI?.MedicalClinic is { } ui)
|
||||
if (campaign?.CampaignUI?.MedicalClinic is { } openedUi)
|
||||
{
|
||||
ui.ClosePopup();
|
||||
openedUi.ClosePopup();
|
||||
}
|
||||
|
||||
healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable);
|
||||
return requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||
public bool ClearAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
ClearPendingHeals();
|
||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||
OnUpdate?.Invoke();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
clearAllRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(null, NetworkHeader.CLEAR_PENDING, DeliveryMethod.Reliable);
|
||||
return requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
clearAllRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(null, NetworkHeader.CLEAR_PENDING, DeliveryMethod.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
private void ClearRequstReceived()
|
||||
private void ClearRequestReceived()
|
||||
{
|
||||
ClearPendingHeals();
|
||||
if (TryDequeue(clearAllRequests, out var callback))
|
||||
@@ -224,28 +244,31 @@ namespace Barotrauma
|
||||
OnUpdate?.Invoke();
|
||||
}
|
||||
|
||||
public void AddPendingButtonAction(NetCrewMember crewMember, Action<CallbackOnlyRequest> onReceived)
|
||||
public bool AddPendingButtonAction(NetCrewMember crewMember, Action<CallbackOnlyRequest> onReceived)
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
InsertPendingCrewMember(crewMember);
|
||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||
OnUpdate?.Invoke();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(crewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable);
|
||||
return requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(crewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
public void RemovePendingButtonAction(NetCrewMember crewMember, NetAffliction affliction, Action<CallbackOnlyRequest> onReceived)
|
||||
public bool RemovePendingButtonAction(NetCrewMember crewMember, NetAffliction affliction, Action<CallbackOnlyRequest> onReceived)
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
RemovePendingAffliction(crewMember, affliction);
|
||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||
OnUpdate?.Invoke();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
INetSerializableStruct removedAffliction = new NetRemovedAffliction
|
||||
@@ -254,11 +277,14 @@ namespace Barotrauma
|
||||
Affliction = affliction
|
||||
};
|
||||
|
||||
removeRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(removedAffliction, NetworkHeader.REMOVE_PENDING, DeliveryMethod.Reliable);
|
||||
return requestBucket.TryEnqueue(() =>
|
||||
{
|
||||
removeRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||
ClientSend(removedAffliction, NetworkHeader.REMOVE_PENDING, DeliveryMethod.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
private void NewAdditonReceived(IReadMessage inc, MessageFlag flag)
|
||||
private void NewAdditionReceived(IReadMessage inc, MessageFlag flag)
|
||||
{
|
||||
var crewMembers = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
|
||||
foreach (var crewMember in crewMembers)
|
||||
@@ -300,7 +326,7 @@ namespace Barotrauma
|
||||
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
||||
if (TryDequeue(afflictionRequests, out var callback))
|
||||
{
|
||||
RequestResult result = crewMember.CharacterInfoID is 0 ? RequestResult.Error : RequestResult.Success;
|
||||
RequestResult result = crewMember.CharacterInfoID is 0 ? RequestResult.CharacterNotFound : RequestResult.Success;
|
||||
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
|
||||
}
|
||||
}
|
||||
@@ -336,7 +362,7 @@ namespace Barotrauma
|
||||
IWriteMessage msg = StartSending();
|
||||
msg.WriteByte((byte)header);
|
||||
netStruct?.Write(msg);
|
||||
GameMain.Client.ClientPeer?.Send(msg, deliveryMethod);
|
||||
GameMain.Client?.ClientPeer?.Send(msg, deliveryMethod);
|
||||
}
|
||||
|
||||
public void ClientRead(IReadMessage inc)
|
||||
@@ -356,7 +382,7 @@ namespace Barotrauma
|
||||
PendingRequestReceived(inc);
|
||||
break;
|
||||
case NetworkHeader.ADD_PENDING:
|
||||
NewAdditonReceived(inc, flag);
|
||||
NewAdditionReceived(inc, flag);
|
||||
break;
|
||||
case NetworkHeader.REMOVE_PENDING:
|
||||
NewRemovalReceived(inc, flag);
|
||||
@@ -365,7 +391,7 @@ namespace Barotrauma
|
||||
HealRequestReceived(inc);
|
||||
break;
|
||||
case NetworkHeader.CLEAR_PENDING:
|
||||
ClearRequstReceived();
|
||||
ClearRequestReceived();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,9 +310,10 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
|
||||
RichString.Rich(missionMessage), wrap: true);
|
||||
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed)
|
||||
if (selectedMissions.Contains(displayedMission))
|
||||
{
|
||||
RichString reputationText = displayedMission.GetReputationRewardText();
|
||||
if (!reputationText.IsNullOrEmpty())
|
||||
@@ -324,7 +325,7 @@ namespace Barotrauma
|
||||
if (totalReward > 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub)));
|
||||
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled)
|
||||
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled && displayedMission.Completed)
|
||||
{
|
||||
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
|
||||
if (share > 0)
|
||||
|
||||
@@ -575,7 +575,7 @@ namespace Barotrauma
|
||||
//cancel dragging if too far away from the container of the dragged item
|
||||
if (DraggingItems.Any())
|
||||
{
|
||||
var rootContainer = DraggingItems.First().GetRootContainer();
|
||||
var rootContainer = DraggingItems.First().RootContainer;
|
||||
var rootInventory = DraggingItems.First().ParentInventory;
|
||||
|
||||
if (rootContainer != null)
|
||||
|
||||
@@ -155,8 +155,6 @@ namespace Barotrauma.Items.Components
|
||||
convexHull.Enabled = true;
|
||||
SetVertices(convexHull, rect);
|
||||
}
|
||||
convexHull.IsExteriorWall = !linkedGap.IsRoomToRoom;
|
||||
if (convexHull2 != null) { convexHull2.IsExteriorWall = convexHull.IsExteriorWall; }
|
||||
}
|
||||
|
||||
|
||||
@@ -169,12 +167,11 @@ namespace Barotrauma.Items.Components
|
||||
IsHorizontal ?
|
||||
new Vector2[] { new Vector2(verts[0].X, center.Y), new Vector2(verts[2].X, center.Y) } :
|
||||
new Vector2[] { new Vector2(center.X, verts[0].Y), new Vector2(center.X, verts[2].Y) });
|
||||
convexHull.MaxMergeLosVerticesDist = 35.0f;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
convexHull.IsExteriorWall = !linkedGap.IsRoomToRoom;
|
||||
if (convexHull2 != null) { convexHull2.IsExteriorWall = convexHull.IsExteriorWall; }
|
||||
if (shakeTimer > 0.0f)
|
||||
{
|
||||
shakeTimer -= deltaTime;
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
|
||||
if (character == null || !character.IsKeyDown(InputType.Aim) || !character.CanAim) { return; }
|
||||
|
||||
//camera focused on some other item/device, don't draw the crosshair
|
||||
if (character.ViewTarget != null && (character.ViewTarget is Item viewTargetItem) && viewTargetItem.Prefab.FocusOnSelected) { return; }
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//whole text can fit in the textblock, no need to scroll
|
||||
needsScrolling = false;
|
||||
scrollingText = DisplayText.Value;
|
||||
TextBlock.Text = scrollingText = DisplayText.Value;
|
||||
scrollPadding = 0;
|
||||
scrollAmount = 0.0f;
|
||||
scrollIndex = 0;
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Barotrauma.Items.Components
|
||||
Step = 0.05f,
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
lastReceivedTargetForce = null;
|
||||
float newTargetForce = barScroll * 200.0f - 100.0f;
|
||||
if (Math.Abs(newTargetForce - targetForce) < 0.01) { return false; }
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma.Items.Components
|
||||
private GUIFrame selectedItemFrame;
|
||||
private GUIFrame selectedItemReqsFrame;
|
||||
|
||||
private GUITextBlock amountTextMin, amountTextMax;
|
||||
private GUITextBlock amountTextMax;
|
||||
private GUIScrollBar amountInput;
|
||||
|
||||
public GUIButton ActivateButton
|
||||
@@ -29,6 +29,9 @@ namespace Barotrauma.Items.Components
|
||||
private GUIComponent outputSlot;
|
||||
private GUIComponent inputInventoryHolder, outputInventoryHolder;
|
||||
|
||||
private readonly List<GUIButton> itemCategoryButtons = new List<GUIButton>();
|
||||
private MapEntityCategory? selectedItemCategory;
|
||||
|
||||
public FabricationRecipe SelectedItem
|
||||
{
|
||||
get { return selectedItem; }
|
||||
@@ -77,7 +80,67 @@ namespace Barotrauma.Items.Components
|
||||
AutoScaleVertical = true
|
||||
};
|
||||
|
||||
var mainFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.95f), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
|
||||
var innerArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.95f), paddedFrame.RectTransform, Anchor.Center), isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.01f,
|
||||
Stretch = true,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
List<MapEntityCategory> itemCategories = Enum.GetValues<MapEntityCategory>().ToList();
|
||||
itemCategories.Remove(MapEntityCategory.None);
|
||||
itemCategories.RemoveAll(c => fabricationRecipes.None(f => f.Value?.TargetItem is ItemPrefab ti && ti.Category.HasFlag(c)));
|
||||
itemCategoryButtons.Clear();
|
||||
|
||||
//only create category buttons if there's more than one category in addition to "All"
|
||||
if (itemCategories.Count > 2)
|
||||
{
|
||||
// === Item category buttons ===
|
||||
var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.05f, 1.0f), innerArea.RectTransform))
|
||||
{
|
||||
RelativeSpacing = 0.01f
|
||||
};
|
||||
|
||||
int buttonSize = Math.Min(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Height / itemCategories.Count);
|
||||
|
||||
var categoryButton = new GUIButton(new RectTransform(new Point(buttonSize), categoryButtonContainer.RectTransform), style: "CategoryButton.All")
|
||||
{
|
||||
ToolTip = TextManager.Get("MapEntityCategory.All"),
|
||||
OnClicked = OnClickedCategoryButton
|
||||
};
|
||||
itemCategoryButtons.Add(categoryButton);
|
||||
foreach (MapEntityCategory category in itemCategories)
|
||||
{
|
||||
categoryButton = new GUIButton(new RectTransform(new Point(buttonSize), categoryButtonContainer.RectTransform),
|
||||
style: "CategoryButton." + category)
|
||||
{
|
||||
ToolTip = TextManager.Get("MapEntityCategory." + category),
|
||||
UserData = category,
|
||||
OnClicked = OnClickedCategoryButton
|
||||
};
|
||||
itemCategoryButtons.Add(categoryButton);
|
||||
}
|
||||
bool OnClickedCategoryButton(GUIButton button, object userData)
|
||||
{
|
||||
MapEntityCategory? newCategory = !button.Selected ? (MapEntityCategory?)userData : null;
|
||||
if (newCategory.HasValue) { itemFilterBox.Text = ""; }
|
||||
selectedItemCategory = newCategory;
|
||||
FilterEntities(newCategory, itemFilterBox.Text);
|
||||
return true;
|
||||
}
|
||||
foreach (var btn in itemCategoryButtons)
|
||||
{
|
||||
btn.RectTransform.SizeChanged += () =>
|
||||
{
|
||||
if (btn.Frame.sprites == null || !btn.Frame.sprites.TryGetValue(GUIComponent.ComponentState.None, out var spriteList)) { return; }
|
||||
var sprite = spriteList?.First();
|
||||
if (sprite == null) { return; }
|
||||
btn.RectTransform.NonScaledSize = new Point(btn.Rect.Width, (int)(btn.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var mainFrame = new GUILayoutGroup(new RectTransform(Vector2.One, innerArea.RectTransform), childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
RelativeSpacing = 0.02f,
|
||||
Stretch = true,
|
||||
@@ -105,10 +168,13 @@ namespace Barotrauma.Items.Components
|
||||
Padding = Vector4.Zero,
|
||||
AutoScaleVertical = true
|
||||
};
|
||||
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true);
|
||||
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true)
|
||||
{
|
||||
OverflowClip = true
|
||||
};
|
||||
itemFilterBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
FilterEntities(text);
|
||||
FilterEntities(selectedItemCategory, text);
|
||||
return true;
|
||||
};
|
||||
filterArea.RectTransform.MaxSize = new Point(int.MaxValue, itemFilterBox.Rect.Height);
|
||||
@@ -174,7 +240,7 @@ namespace Barotrauma.Items.Components
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
amountTextMin = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||
|
||||
amountInput = new GUIScrollBar(new RectTransform(new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style: "GUISlider")
|
||||
{
|
||||
@@ -489,15 +555,37 @@ namespace Barotrauma.Items.Components
|
||||
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
}
|
||||
|
||||
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
|
||||
var itemIcon = requiredItemPrefab.InventoryIcon ?? requiredItemPrefab.Sprite;
|
||||
Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: requiredItemPrefab.InventoryIconColor * 0.3f,
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
|
||||
|
||||
|
||||
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
|
||||
|
||||
float iconAlpha = 0.0f;
|
||||
ItemPrefab requiredItemToDisplay;
|
||||
int count = requiredItem.ItemPrefabs.Count();
|
||||
if (count > 1)
|
||||
{
|
||||
float iconCycleSpeed = 0.5f / count;
|
||||
float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed;
|
||||
int iconIndex = (int)(iconCycleT % requiredItem.ItemPrefabs.Count());
|
||||
|
||||
requiredItemToDisplay = requiredItem.ItemPrefabs.Skip(iconIndex).FirstOrDefault();
|
||||
iconAlpha = Math.Min(Math.Abs(MathF.Sin(iconCycleT * MathHelper.Pi)) * 2.0f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredItemToDisplay = requiredItem.ItemPrefabs.FirstOrDefault();
|
||||
iconAlpha = 1.0f;
|
||||
}
|
||||
if (iconAlpha > 0.0f)
|
||||
{
|
||||
var itemIcon = requiredItemToDisplay.InventoryIcon ?? requiredItemToDisplay.Sprite;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: requiredItemToDisplay.InventoryIconColor * 0.3f * iconAlpha,
|
||||
scale: Math.Min(slotRect.Width * 0.9f / itemIcon.size.X, slotRect.Height * 0.9f / itemIcon.size.Y));
|
||||
}
|
||||
|
||||
if (missingCount > 1)
|
||||
{
|
||||
Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom);
|
||||
@@ -552,9 +640,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
toolTipText = $"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
|
||||
if (!requiredItemPrefab.Description.IsNullOrEmpty())
|
||||
if (!requiredItem.OverrideDescription.IsNullOrEmpty())
|
||||
{
|
||||
toolTipText = '\n' + requiredItemPrefab.Description;
|
||||
toolTipText += '\n' + requiredItem.OverrideDescription;
|
||||
}
|
||||
else if (!requiredItemPrefab.Description.IsNullOrEmpty())
|
||||
{
|
||||
toolTipText += '\n' + requiredItemPrefab.Description;
|
||||
}
|
||||
tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
|
||||
}
|
||||
@@ -601,22 +693,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterEntities(string filter)
|
||||
private bool FilterEntities(MapEntityCategory? category, string filter)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
foreach (GUIComponent child in itemList.Content.Children)
|
||||
{
|
||||
itemList.Content.Children.ForEach(c => c.Visible = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (GUIComponent child in itemList.Content.Children)
|
||||
{
|
||||
FabricationRecipe recipe = child.UserData as FabricationRecipe;
|
||||
if (recipe?.DisplayName == null) { continue; }
|
||||
child.Visible = recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
FabricationRecipe recipe = child.UserData as FabricationRecipe;
|
||||
if (recipe?.DisplayName == null) { continue; }
|
||||
child.Visible =
|
||||
(string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value));
|
||||
}
|
||||
|
||||
foreach (GUIButton btn in itemCategoryButtons)
|
||||
{
|
||||
btn.Selected = (MapEntityCategory?)btn.UserData == selectedItemCategory;
|
||||
}
|
||||
HideEmptyItemListCategories();
|
||||
|
||||
return true;
|
||||
@@ -648,7 +739,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool ClearFilter()
|
||||
{
|
||||
FilterEntities("");
|
||||
FilterEntities(selectedItemCategory, "");
|
||||
itemList.UpdateScrollBarSize();
|
||||
itemList.BarScroll = 0.0f;
|
||||
itemFilterBox.Text = "";
|
||||
@@ -737,6 +828,7 @@ namespace Barotrauma.Items.Components
|
||||
TextManager.Get("FabricatorRequiredSkills"), textColor: inadequateSkills.Any() ? GUIStyle.Red : GUIStyle.Green, font: GUIStyle.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
ToolTip = TextManager.Get("fabricatorrequiredskills.tooltip")
|
||||
};
|
||||
foreach (Skill skill in selectedItem.RequiredSkills)
|
||||
{
|
||||
|
||||
@@ -125,18 +125,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public static MiniMapSettings Default = new MiniMapSettings
|
||||
(
|
||||
ignoreOutposts: false,
|
||||
createHullElements: true,
|
||||
elementColor: MiniMap.MiniMapBaseColor
|
||||
);
|
||||
|
||||
public readonly bool IgnoreOutposts;
|
||||
public readonly bool CreateHullElements;
|
||||
public readonly Color ElementColor;
|
||||
|
||||
public MiniMapSettings(bool ignoreOutposts = false, bool createHullElements = false, Color? elementColor = null)
|
||||
public MiniMapSettings(bool createHullElements = false, Color? elementColor = null)
|
||||
{
|
||||
IgnoreOutposts = ignoreOutposts;
|
||||
CreateHullElements = createHullElements;
|
||||
ElementColor = elementColor ?? MiniMap.MiniMapBaseColor;
|
||||
}
|
||||
@@ -403,7 +400,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool VisibleOnItemFinder(Item it)
|
||||
{
|
||||
if (!item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
|
||||
if (it?.Submarine == null) { return false; }
|
||||
if (item.Submarine == null || !item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
|
||||
if (it.NonInteractable || it.HiddenInGame) { return false; }
|
||||
if (it.GetComponent<Pickable>() == null) { return false; }
|
||||
|
||||
@@ -436,7 +434,11 @@ namespace Barotrauma.Items.Components
|
||||
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
submarineContainer.ClearChildren();
|
||||
|
||||
if (item.Submarine is null) { return; }
|
||||
if (item.Submarine is null)
|
||||
{
|
||||
displayedSubs.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
|
||||
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
|
||||
@@ -444,8 +446,8 @@ namespace Barotrauma.Items.Components
|
||||
ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() != null || it.GetComponent<Turret>() != null)).ToImmutableHashSet();
|
||||
miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents);
|
||||
|
||||
IEnumerable<Item> electrialPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
|
||||
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electrialPointsOfInterest, out electricalMapComponents);
|
||||
IEnumerable<Item> electricalPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
|
||||
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electricalPointsOfInterest, out electricalMapComponents);
|
||||
|
||||
Dictionary<MiniMapGUIComponent, GUIComponent> electricChildren = new Dictionary<MiniMapGUIComponent, GUIComponent>();
|
||||
|
||||
@@ -535,7 +537,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
displayedSubs.Clear();
|
||||
displayedSubs.Add(item.Submarine);
|
||||
displayedSubs.AddRange(item.Submarine.DockedTo);
|
||||
displayedSubs.AddRange(item.Submarine.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID));
|
||||
|
||||
subEntities = MapEntity.mapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList();
|
||||
|
||||
@@ -550,7 +552,7 @@ namespace Barotrauma.Items.Components
|
||||
item.Submarine is { } itemSub &&
|
||||
(
|
||||
!displayedSubs.Contains(itemSub) || // current sub not displayed
|
||||
itemSub.DockedTo.Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) || // some of the docked subs not displayed
|
||||
itemSub.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID).Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) || // some of the docked subs not displayed
|
||||
displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s)) // displaying a sub that shouldn't be displayed
|
||||
) ||
|
||||
prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || // resolution changed
|
||||
@@ -730,7 +732,7 @@ namespace Barotrauma.Items.Components
|
||||
if (sprite != null && ShowHullIntegrity)
|
||||
{
|
||||
Vector2 spriteSize = sprite.size;
|
||||
Rectangle worldBorders = item.Submarine.GetDockedBorders();
|
||||
Rectangle worldBorders = item.Submarine.GetDockedBorders(allowDifferentTeam: false);
|
||||
worldBorders.Location += item.Submarine.WorldPosition.ToPoint();
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
@@ -914,7 +916,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
|
||||
RectangleF dockedBorders = item.Submarine.GetDockedBorders();
|
||||
RectangleF dockedBorders = item.Submarine.GetDockedBorders(allowDifferentTeam: false);
|
||||
dockedBorders.Location += item.Submarine.WorldPosition;
|
||||
RectangleF parentRect = miniMapFrame.Rect;
|
||||
|
||||
@@ -1063,7 +1065,9 @@ namespace Barotrauma.Items.Components
|
||||
waterVolume += linkedHull.WaterVolume;
|
||||
totalVolume += linkedHull.Volume;
|
||||
}
|
||||
hullData.HullWaterAmount = MathHelper.Clamp((int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100);
|
||||
hullData.HullWaterAmount =
|
||||
waterVolume > 1.0f ?
|
||||
MathHelper.Clamp((int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100) : 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1302,7 +1306,7 @@ namespace Barotrauma.Items.Components
|
||||
GameMain.Instance.GraphicsDevice.SetRenderTarget(rt);
|
||||
GameMain.Instance.GraphicsDevice.Clear(Color.Transparent);
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
Rectangle worldBorders = sub.GetDockedBorders();
|
||||
Rectangle worldBorders = sub.GetDockedBorders(allowDifferentTeam: false);
|
||||
worldBorders.Location += sub.WorldPosition.ToPoint();
|
||||
|
||||
parentRect.Inflate(-inflate, -inflate);
|
||||
@@ -1523,7 +1527,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Dictionary<MapEntity, MiniMapGUIComponent> pointsOfInterestCollection = new Dictionary<MapEntity, MiniMapGUIComponent>();
|
||||
|
||||
RectangleF worldBorders = sub.GetDockedBorders();
|
||||
RectangleF worldBorders = sub.GetDockedBorders(allowDifferentTeam: false);
|
||||
worldBorders.Location += sub.WorldPosition;
|
||||
|
||||
// create a container that has the same "aspect ratio" as the sub
|
||||
@@ -1536,7 +1540,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
GUIFrame hullContainer = new GUIFrame(new RectTransform(containerScale * elementPadding, parent.RectTransform, Anchor.Center), style: null);
|
||||
|
||||
ImmutableHashSet<Submarine> connectedSubs = sub.GetConnectedSubs().ToImmutableHashSet();
|
||||
ImmutableHashSet<Submarine> connectedSubs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID).ToImmutableHashSet();
|
||||
ImmutableArray<Hull> hullList = ImmutableArray<Hull>.Empty;
|
||||
ImmutableDictionary<Hull, ImmutableArray<Hull>> combinedHulls = ImmutableDictionary<Hull, ImmutableArray<Hull>>.Empty;
|
||||
|
||||
@@ -1683,7 +1687,7 @@ namespace Barotrauma.Items.Components
|
||||
bool IsPartofSub(MapEntity entity)
|
||||
{
|
||||
if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine) || entity.HiddenInGame) { return false; }
|
||||
return !settings.IgnoreOutposts || sub.IsEntityFoundOnThisSub(entity, true);
|
||||
return sub.IsEntityFoundOnThisSub(entity, true);
|
||||
}
|
||||
|
||||
bool IsStandaloneHull(Hull hull)
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
private const float NearbyObjectUpdateInterval = 1.0f;
|
||||
float nearbyObjectUpdateTimer;
|
||||
|
||||
private List<Submarine> connectedSubs = new List<Submarine>();
|
||||
private readonly List<Submarine> connectedSubs = new List<Submarine>();
|
||||
private const float ConnectedSubUpdateInterval = 1.0f;
|
||||
float connectedSubUpdateTimer;
|
||||
|
||||
@@ -335,9 +335,11 @@ namespace Barotrauma.Items.Components
|
||||
// Setup layout for nav terminal
|
||||
if (isConnectedToSteering || RightLayout)
|
||||
{
|
||||
controlContainer.RectTransform.AbsoluteOffset = Point.Zero;
|
||||
controlContainer.RectTransform.RelativeOffset = controlBoxOffset;
|
||||
controlContainer.RectTransform.SetPosition(Anchor.TopRight);
|
||||
sonarView.RectTransform.ScaleBasis = ScaleBasis.Smallest;
|
||||
if (HasMineralScanner) { PreventMineralScannerOverlap(); }
|
||||
sonarView.RectTransform.SetPosition(Anchor.CenterLeft);
|
||||
sonarView.RectTransform.Resize(GUISizeCalculation);
|
||||
GUITextBlock.AutoScaleAndNormalize(textBlocksToScaleAndNormalize);
|
||||
@@ -431,10 +433,11 @@ namespace Barotrauma.Items.Components
|
||||
var mineralScannerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, zoomSlider.Parent.RectTransform.RelativeSize.Y), lowerAreaFrame.RectTransform, Anchor.BottomCenter), style: null);
|
||||
mineralScannerSwitch = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), mineralScannerFrame.RectTransform, Anchor.CenterLeft), string.Empty, style: "SwitchHorizontal")
|
||||
{
|
||||
Selected = UseMineralScanner,
|
||||
OnClicked = (button, data) =>
|
||||
{
|
||||
useMineralScanner = !useMineralScanner;
|
||||
button.Selected = useMineralScanner;
|
||||
UseMineralScanner = !UseMineralScanner;
|
||||
button.Selected = UseMineralScanner;
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
unsentChanges = true;
|
||||
@@ -496,12 +499,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (transducer.Transducer.Item.Submarine == null) { continue; }
|
||||
if (connectedSubs.Contains(transducer.Transducer.Item.Submarine)) { continue; }
|
||||
connectedSubs = transducer.Transducer.Item.Submarine?.GetConnectedSubs();
|
||||
connectedSubs.AddRange(transducer.Transducer.Item.Submarine.GetConnectedSubs());
|
||||
}
|
||||
}
|
||||
else if (item.Submarine != null)
|
||||
{
|
||||
connectedSubs = item.Submarine?.GetConnectedSubs();
|
||||
connectedSubs.AddRange(item.Submarine?.GetConnectedSubs());
|
||||
}
|
||||
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
|
||||
}
|
||||
@@ -1032,7 +1035,7 @@ namespace Barotrauma.Items.Components
|
||||
missionIndex++;
|
||||
}
|
||||
|
||||
if (HasMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
|
||||
if (HasMineralScanner && UseMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
|
||||
(item.CurrentHull == null || !DetectSubmarineWalls))
|
||||
{
|
||||
foreach (var c in MineralClusters)
|
||||
@@ -1311,7 +1314,6 @@ namespace Barotrauma.Items.Components
|
||||
float worldPingRadiusSqr = worldPingRadius * worldPingRadius;
|
||||
|
||||
disruptedDirections.Clear();
|
||||
if (Level.Loaded == null) { return; }
|
||||
|
||||
for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
|
||||
{
|
||||
@@ -1513,9 +1515,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
foreach (Item item in Item.SonarVisibleItems)
|
||||
{
|
||||
if (item.CurrentHull == null && item.Prefab.SonarSize > 0.0f)
|
||||
System.Diagnostics.Debug.Assert(item.Prefab.SonarSize > 0.0f);
|
||||
if (item.CurrentHull == null)
|
||||
{
|
||||
float pointDist = ((item.WorldPosition - pingSource) * displayScale).LengthSquared();
|
||||
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
|
||||
@@ -1923,7 +1926,7 @@ namespace Barotrauma.Items.Components
|
||||
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
|
||||
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
|
||||
}
|
||||
msg.WriteBoolean(useMineralScanner);
|
||||
msg.WriteBoolean(UseMineralScanner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1935,7 +1938,7 @@ namespace Barotrauma.Items.Components
|
||||
float zoomT = 1.0f;
|
||||
bool directionalPing = useDirectionalPing;
|
||||
float directionT = 0.0f;
|
||||
bool mineralScanner = useMineralScanner;
|
||||
bool mineralScanner = UseMineralScanner;
|
||||
if (isActive)
|
||||
{
|
||||
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
@@ -1966,7 +1969,7 @@ namespace Barotrauma.Items.Components
|
||||
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
|
||||
}
|
||||
useDirectionalPing = directionalModeSwitch.Selected = directionalPing;
|
||||
useMineralScanner = mineralScanner;
|
||||
UseMineralScanner = mineralScanner;
|
||||
if (mineralScannerSwitch != null)
|
||||
{
|
||||
mineralScannerSwitch.Selected = mineralScanner;
|
||||
@@ -1983,7 +1986,7 @@ namespace Barotrauma.Items.Components
|
||||
directionalModeSwitch.Selected = useDirectionalPing;
|
||||
if (mineralScannerSwitch != null)
|
||||
{
|
||||
mineralScannerSwitch.Selected = useMineralScanner;
|
||||
mineralScannerSwitch.Selected = UseMineralScanner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components
|
||||
User = Entity.FindEntityByID(userId) as Character;
|
||||
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
float rotation = msg.ReadSingle();
|
||||
SpreadCounter = msg.ReadByte();
|
||||
spreadIndex = msg.ReadByte();
|
||||
if (User != null)
|
||||
{
|
||||
Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false);
|
||||
|
||||
@@ -21,7 +21,8 @@ namespace Barotrauma.Items.Components
|
||||
public float FlashTimer { get; private set; }
|
||||
public static Wire DraggingConnected { get; private set; }
|
||||
|
||||
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character)
|
||||
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character,
|
||||
out (Vector2 tooltipPos, LocalizedString text) tooltip)
|
||||
{
|
||||
if (DraggingConnected?.Item?.Removed ?? true)
|
||||
{
|
||||
@@ -64,6 +65,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
tooltip = (Vector2.Zero, string.Empty);
|
||||
|
||||
//two passes: first the connector, then the wires to get the wires to render in front
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
@@ -97,6 +100,42 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 position = c.IsOutput ? rightPos : leftPos;
|
||||
Color highlightColor = Color.Transparent;
|
||||
if (ConnectionPanel.ShouldDebugDrawWiring)
|
||||
{
|
||||
if (c.IsPower)
|
||||
{
|
||||
highlightColor = VisualizeSignal(0.0f, highlightColor, Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
highlightColor = VisualizeSignal(c.LastReceivedSignal.TimeSinceCreated, highlightColor, Color.LightGreen);
|
||||
highlightColor = VisualizeSignal(c.LastSentSignal.TimeSinceCreated, highlightColor, Color.Orange);
|
||||
}
|
||||
bool mouseOn = Vector2.DistanceSquared(position, PlayerInput.MousePosition) < MathUtils.Pow2(35 * GUI.Scale);
|
||||
|
||||
LocalizedString toolTipText = c.GetToolTip();
|
||||
if (mouseOn) { tooltip = (position, toolTipText); }
|
||||
if (!toolTipText.IsNullOrEmpty())
|
||||
{
|
||||
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite;
|
||||
glowSprite.Draw(spriteBatch, position, highlightColor, glowSprite.size / 2,
|
||||
scale: 45.0f / glowSprite.size.X * panel.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
static Color VisualizeSignal(double timeSinceCreated, Color defaultColor, Color color)
|
||||
{
|
||||
if (timeSinceCreated < 1.0f)
|
||||
{
|
||||
float pulseAmount = (MathF.Sin((float)Timing.TotalTimeUnpaused * 10.0f) + 3.0f) / 4.0f;
|
||||
Color targetColor = Color.Lerp(defaultColor, color, pulseAmount);
|
||||
return Color.Lerp(targetColor, defaultColor, (float)timeSinceCreated);
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
//outputs are drawn at the right side of the panel, inputs at the left
|
||||
if (c.IsOutput)
|
||||
{
|
||||
@@ -127,7 +166,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (DraggingConnected != null)
|
||||
{
|
||||
if (mouseInRect)
|
||||
@@ -225,7 +263,9 @@ namespace Barotrauma.Items.Components
|
||||
GUI.DrawString(spriteBatch, labelPos, text, GUIStyle.TextColorBright, font: GUIStyle.SmallFont);
|
||||
|
||||
float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale;
|
||||
|
||||
connectionSprite.Draw(spriteBatch, position, scale: connectorSpriteScale);
|
||||
|
||||
}
|
||||
|
||||
private void DrawWires(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval)
|
||||
@@ -307,6 +347,63 @@ namespace Barotrauma.Items.Components
|
||||
FlashTimer -= deltaTime;
|
||||
}
|
||||
|
||||
private (string signal, LocalizedString tooltip) lastSignalToolTip;
|
||||
private (int powerValue, LocalizedString tooltip) lastPowerToolTip;
|
||||
|
||||
private LocalizedString GetToolTip()
|
||||
{
|
||||
if (LastReceivedSignal.TimeSinceCreated < 1.0f)
|
||||
{
|
||||
return getSignalTooltip(LastReceivedSignal, "receivedsignal");
|
||||
}
|
||||
else if (LastSentSignal.TimeSinceCreated < 1.0f)
|
||||
{
|
||||
return getSignalTooltip(LastSentSignal, "sentsignal");
|
||||
}
|
||||
|
||||
LocalizedString getSignalTooltip(Signal signal, string textTag)
|
||||
{
|
||||
if (lastSignalToolTip.signal == signal.value && !lastSignalToolTip.tooltip.IsNullOrEmpty()) { return lastSignalToolTip.tooltip; }
|
||||
lastSignalToolTip = (signal.value, TextManager.GetWithVariable(textTag, "[signal]", signal.value));
|
||||
return lastSignalToolTip.tooltip;
|
||||
}
|
||||
|
||||
if (IsPower)
|
||||
{
|
||||
if (item.GetComponent<Powered>() is Powered powered)
|
||||
{
|
||||
if (IsOutput)
|
||||
{
|
||||
if (powered.CurrPowerConsumption < 0)
|
||||
{
|
||||
return getPowerTooltip(-(int)powered.CurrPowerConsumption, "reactoroutput");
|
||||
}
|
||||
else if (powered is PowerTransfer || powered is PowerContainer)
|
||||
{
|
||||
return getPowerTooltip((int)(Grid?.Power ?? 0), "reactoroutput");
|
||||
}
|
||||
}
|
||||
else if (!IsOutput)
|
||||
{
|
||||
float powerConsumption = powered.GetCurrentPowerConsumption(this);
|
||||
if (!MathUtils.NearlyEqual((int)powerConsumption, 0.0f))
|
||||
{
|
||||
return getPowerTooltip((int)powerConsumption, "reactorload");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalizedString getPowerTooltip(int powerValue, string textTag)
|
||||
{
|
||||
if (lastPowerToolTip.powerValue == powerValue && !lastPowerToolTip.tooltip.IsNullOrEmpty()) { return lastPowerToolTip.tooltip; }
|
||||
lastPowerToolTip = (powerValue, TextManager.GetWithVariable(textTag, "[kw]", powerValue.ToString()));
|
||||
return lastPowerToolTip.tooltip;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Vector2 end, Vector2 start, Wire equippedWire, ConnectionPanel panel, LocalizedString label)
|
||||
{
|
||||
int textX = (int)start.X;
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public static bool DebugWiringMode;
|
||||
public static double DebugWiringEnabledUntil;
|
||||
public static bool ShouldDebugDrawWiring => DebugWiringMode || Timing.TotalTimeUnpaused < DebugWiringEnabledUntil;
|
||||
|
||||
//how long the rewiring sound plays after doing changes to the wiring
|
||||
const float RewireSoundDuration = 5.0f;
|
||||
|
||||
@@ -120,12 +124,15 @@ namespace Barotrauma.Items.Components
|
||||
if (user != Character.Controlled || user == null) { return; }
|
||||
|
||||
HighlightedWire = null;
|
||||
Connection.DrawConnections(spriteBatch, this, dragArea.Rect, user);
|
||||
|
||||
Connection.DrawConnections(spriteBatch, this, dragArea.Rect, user, out (Vector2 tooltipPos, LocalizedString text) tooltip);
|
||||
foreach (UISprite sprite in GUIStyle.GetComponentStyle("ConnectionPanelFront").Sprites[GUIComponent.ComponentState.None])
|
||||
{
|
||||
sprite.Draw(spriteBatch, GuiFrame.Rect, Color.White, SpriteEffects.None);
|
||||
}
|
||||
if (!tooltip.text.IsNullOrEmpty())
|
||||
{
|
||||
GUIComponent.DrawToolTip(spriteBatch, tooltip.text, tooltip.tooltipPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckForLabelOverlap()
|
||||
|
||||
@@ -250,7 +250,7 @@ namespace Barotrauma.Items.Components
|
||||
int visibleElementCount = 0;
|
||||
foreach (var uiElement in uiElements)
|
||||
{
|
||||
if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; }
|
||||
if (uiElement.UserData is not CustomInterfaceElement element) { continue; }
|
||||
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
|
||||
if (visible) { visibleElementCount++; }
|
||||
if (uiElement.Visible != visible)
|
||||
@@ -337,7 +337,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (uiElements[i] is GUITextBox tb)
|
||||
{
|
||||
tb.Text = customInterfaceElementList[i].Signal;
|
||||
tb.Text = Screen.Selected is { IsEditor: true } ?
|
||||
customInterfaceElementList[i].Signal :
|
||||
TextManager.Get(customInterfaceElementList[i].Signal).Value;
|
||||
}
|
||||
else if (uiElements[i] is GUINumberInput ni)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -119,6 +118,13 @@ namespace Barotrauma.Items.Components
|
||||
get { return sectionExtents; }
|
||||
}
|
||||
|
||||
public readonly record struct VisualSignal(
|
||||
float TimeSent,
|
||||
Color Color,
|
||||
int Direction);
|
||||
|
||||
private VisualSignal lastReceivedSignal;
|
||||
|
||||
public static Wire DraggingWire
|
||||
{
|
||||
get => draggingWire;
|
||||
@@ -126,13 +132,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public static Sprite ExtractWireSprite(ContentXElement element)
|
||||
{
|
||||
if (defaultWireSprite == null)
|
||||
{
|
||||
defaultWireSprite = new Sprite("Content/Items/Electricity/signalcomp.png", new Rectangle(970, 47, 14, 16), new Vector2(0.5f, 0.5f))
|
||||
defaultWireSprite ??=
|
||||
new Sprite("Content/Items/Electricity/signalcomp.png", new Rectangle(970, 47, 14, 16), new Vector2(0.5f, 0.5f))
|
||||
{
|
||||
Depth = 0.855f
|
||||
};
|
||||
}
|
||||
|
||||
Sprite overrideSprite = null;
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -153,6 +157,35 @@ namespace Barotrauma.Items.Components
|
||||
if (wireSprite != defaultWireSprite) { overrideSprite = wireSprite; }
|
||||
}
|
||||
|
||||
public void RegisterSignal(Signal signal, Connection source)
|
||||
{
|
||||
lastReceivedSignal = new VisualSignal(
|
||||
(float)Timing.TotalTimeUnpaused,
|
||||
GetSignalColor(signal),
|
||||
Direction: source == connections[0] ? 1 : -1);
|
||||
}
|
||||
|
||||
private static readonly Color[] dataSignalColors = new Color[] { Color.White, Color.LightBlue, Color.CornflowerBlue, Color.Blue, Color.BlueViolet, Color.Violet };
|
||||
|
||||
private static Color GetSignalColor(Signal signal)
|
||||
{
|
||||
if (signal.value == "0")
|
||||
{
|
||||
return Color.Red;
|
||||
}
|
||||
else if (signal.value == "1")
|
||||
{
|
||||
return Color.LightGreen;
|
||||
}
|
||||
else if (float.TryParse(signal.value, out float floatValue))
|
||||
{
|
||||
//convert numeric values to a color (guessing the value might be somewhere in the range of 0-200)
|
||||
//so a player with a keen eye can get some info out of the color of the signal
|
||||
return ToolBox.GradientLerp(Math.Abs(floatValue / 200.0f), dataSignalColors);
|
||||
}
|
||||
return Color.LightBlue;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
Draw(spriteBatch, editing, Vector2.Zero, itemDepth);
|
||||
@@ -166,20 +199,7 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 drawOffset = Vector2.Zero;
|
||||
Submarine sub = item.Submarine;
|
||||
if (IsActive && sub == null) // currently being rewired, we need to get the sub from the connections in case the wire has been taken outside
|
||||
{
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
||||
}
|
||||
|
||||
if (sub != null)
|
||||
{
|
||||
drawOffset = sub.DrawPosition + sub.HiddenSubPosition;
|
||||
}
|
||||
|
||||
drawOffset += offset;
|
||||
Vector2 drawOffset = GetDrawOffset() + offset;
|
||||
|
||||
float baseDepth = UseSpriteDepth ? item.SpriteDepth : wireSprite.Depth;
|
||||
float depth = item.IsSelected ? 0.0f : SubEditorScreen.IsWiringMode() ? 0.02f : baseDepth + (item.ID % 100) * 0.000001f;// item.GetDrawDepth(wireSprite.Depth, wireSprite);
|
||||
@@ -188,7 +208,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, wireSprite, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f);
|
||||
section.Draw(spriteBatch, wireSprite,
|
||||
Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor,
|
||||
drawOffset, depth + 0.00001f, Width * 2.0f);
|
||||
}
|
||||
}
|
||||
else if (item.IsSelected)
|
||||
@@ -270,6 +292,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ConnectionPanel.ShouldDebugDrawWiring)
|
||||
{
|
||||
DebugDraw(spriteBatch, alpha: 0.2f);
|
||||
}
|
||||
|
||||
if (!editing || !GameMain.SubEditorScreen.WiringMode) { return; }
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
@@ -295,6 +323,102 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void DebugDraw(SpriteBatch spriteBatch, float alpha = 1.0f)
|
||||
{
|
||||
if (sections.Count == 0 || Hidden)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float PowerPulseSpeedLow = 5.0f;
|
||||
const float PowerPulseSpeedHigh = 10.0f;
|
||||
const float PowerHighlightScaleLow = 1.5f;
|
||||
const float PowerHighlightScaleHigh = 2.5f;
|
||||
|
||||
const float SignalIndicatorInterval = 15.0f;
|
||||
const float SignalIndicatorSpeed = 100.0f;
|
||||
|
||||
Vector2 drawOffset = GetDrawOffset();
|
||||
|
||||
Color currentHighlightColor = Color.Transparent;
|
||||
float highlightScale = 0.0f;
|
||||
if (connections[0] != null && connections[1] != null)
|
||||
{
|
||||
float voltage = Math.Max(GetVoltage(0), GetVoltage(1));
|
||||
float GetVoltage(int connectionIndex)
|
||||
{
|
||||
var connection1 = connections[connectionIndex];
|
||||
var connection2 = connections[1 - connectionIndex];
|
||||
if (connection1.IsOutput && connection1.Grid is { Power: > 0.01f } grid1)
|
||||
{
|
||||
if (connection2.Item.GetComponent<Powered>() is Powered powered &&
|
||||
(powered.GetCurrentPowerConsumption(connection2) > 0 || powered is PowerTransfer))
|
||||
{
|
||||
return grid1.Voltage;
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
if (voltage > 0.0f)
|
||||
{
|
||||
//pulse faster when there's overvoltage
|
||||
float pulseSpeed = voltage > 1.2f ? PowerPulseSpeedHigh : PowerPulseSpeedLow;
|
||||
float pulseAmount = (MathF.Sin((float)Timing.TotalTimeUnpaused * pulseSpeed) + 1.5f) / 2.5f;
|
||||
voltage = Math.Min(voltage, 1.0f);
|
||||
highlightScale = MathHelper.Lerp(PowerHighlightScaleLow, PowerHighlightScaleHigh, voltage);
|
||||
currentHighlightColor = Color.Red * voltage * pulseAmount;
|
||||
}
|
||||
}
|
||||
if (highlightScale > 0.0f)
|
||||
{
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, wireSprite, currentHighlightColor * alpha, drawOffset, 0.0f, Width * highlightScale);
|
||||
}
|
||||
}
|
||||
|
||||
float signalDuration = (float)Timing.TotalTimeUnpaused - lastReceivedSignal.TimeSent;
|
||||
if (ConnectionPanel.ShouldDebugDrawWiring && signalDuration < 1.0f)
|
||||
{
|
||||
//make some wires "off sync" so it's easier to differentiate signals on overlapping wires
|
||||
float offset = item.ID % 2 == 1 ? SignalIndicatorInterval / 2 : 0.0f;
|
||||
float signalProgress = ((float)(Timing.TotalTimeUnpaused * SignalIndicatorSpeed + offset) % SignalIndicatorInterval) * lastReceivedSignal.Direction;
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
for (float x = 0; x < section.Length; x += SignalIndicatorInterval)
|
||||
{
|
||||
Vector2 dir = (section.End - section.Start) / section.Length;
|
||||
float posOnSection = x + signalProgress;
|
||||
if (posOnSection < 0 || posOnSection > section.Length) { continue; }
|
||||
Vector2 signalPos = section.Start + drawOffset + dir * posOnSection;
|
||||
float a = 1.0f - Vector2.Distance(Screen.Selected.Cam.WorldViewCenter, signalPos) / 500.0f;
|
||||
if (a < 0) { continue; }
|
||||
signalPos.Y = -signalPos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, signalPos - Vector2.One * 2.5f, Vector2.One * 5, lastReceivedSignal.Color * a * (1.0f - signalDuration) * alpha, isFilled: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetDrawOffset()
|
||||
{
|
||||
Submarine sub = item.Submarine;
|
||||
if (IsActive && sub == null) // currently being rewired, we need to get the sub from the connections in case the wire has been taken outside
|
||||
{
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
||||
}
|
||||
|
||||
if (sub == null)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
return sub.DrawPosition + sub.HiddenSubPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHangingWire(SpriteBatch spriteBatch, Vector2 start, float depth)
|
||||
{
|
||||
float angle = (float)Math.Sin(GameMain.GameScreen.GameTime * 2.0f + item.ID) * 0.2f;
|
||||
|
||||
@@ -46,6 +46,13 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DebugWiring
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool ShowDeadCharacters
|
||||
{
|
||||
@@ -111,6 +118,11 @@ namespace Barotrauma.Items.Components
|
||||
refEntity = item;
|
||||
}
|
||||
|
||||
if (equipper != null && equipper == Character.Controlled && DebugWiring)
|
||||
{
|
||||
ConnectionPanel.DebugWiringEnabledUntil = Timing.TotalTimeUnpaused + 0.5;
|
||||
}
|
||||
|
||||
thermalEffectState += deltaTime;
|
||||
thermalEffectState %= 10000.0f;
|
||||
|
||||
@@ -153,6 +165,11 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
Unequip(dropper);
|
||||
}
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (character == null) { return; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Lights;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -10,6 +11,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private GUIMessageBox autodockingVerification;
|
||||
|
||||
private readonly ConvexHull[] convexHulls = new ConvexHull[2];
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
@@ -109,6 +112,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
partial void RemoveConvexHulls()
|
||||
{
|
||||
for (int i = 0; i < convexHulls.Length; i++)
|
||||
{
|
||||
convexHulls[i]?.Remove();
|
||||
convexHulls[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
bool isDocked = msg.ReadBoolean();
|
||||
|
||||
@@ -1398,7 +1398,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Failed to read component state - " + components[componentIndex].GetType() + " is not IServerSerializable.");
|
||||
throw new Exception($"Failed to read component state - {components[componentIndex].GetType()} in item \"{Prefab.Identifier}\" is not IServerSerializable.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1411,7 +1411,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Failed to read inventory state - " + components[containerIndex].GetType() + " is not an ItemContainer.");
|
||||
throw new Exception($"Failed to read inventory state - {components[containerIndex].GetType()} in item \"{Prefab.Identifier}\" is not an ItemContainer.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1460,9 +1460,9 @@ namespace Barotrauma
|
||||
byte length = msg.ReadByte();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var statIdentifier = INetSerializableStruct.Read<ItemStatManager.TalentStatIdentifier>(msg);
|
||||
var statIdentifier = INetSerializableStruct.Read<TalentStatIdentifier>(msg);
|
||||
var statValue = msg.ReadSingle();
|
||||
StatManager.ApplyStat(statIdentifier, statValue);
|
||||
StatManager.ApplyStatDirect(statIdentifier, statValue);
|
||||
}
|
||||
break;
|
||||
case EventType.Upgrade:
|
||||
|
||||
@@ -9,15 +9,17 @@ namespace Barotrauma
|
||||
{
|
||||
private LightSource lightSource;
|
||||
|
||||
partial void UpdateProjSpecific(float growModifier)
|
||||
private float particleTimer;
|
||||
|
||||
partial void UpdateProjSpecific(float growModifier, float deltaTime)
|
||||
{
|
||||
if (this is DummyFireSource)
|
||||
{
|
||||
EmitParticles(size, WorldPosition, hull, growModifier, null);
|
||||
EmitParticles(size, WorldPosition, deltaTime, hull, growModifier, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitParticles(size, WorldPosition, hull, growModifier, OnChangeHull);
|
||||
EmitParticles(size, WorldPosition, deltaTime, hull, growModifier, OnChangeHull);
|
||||
}
|
||||
|
||||
lightSource.Color = new Color(1.0f, 0.45f, 0.3f) * Rand.Range(0.8f, 1.0f);
|
||||
@@ -25,23 +27,29 @@ namespace Barotrauma
|
||||
if (Vector2.DistanceSquared(lightSource.Position, position) > 5.0f) { lightSource.Position = position + Vector2.UnitY * 30.0f; }
|
||||
}
|
||||
|
||||
public static void EmitParticles(Vector2 size, Vector2 worldPosition, Hull hull, float growModifier, Particle.OnChangeHullHandler onChangeHull = null)
|
||||
public void EmitParticles(Vector2 size, Vector2 worldPosition, float deltaTime, Hull hull, float growModifier, Particle.OnChangeHullHandler onChangeHull = null)
|
||||
{
|
||||
float particleCount = Rand.Range(0.0f, size.X / 50.0f);
|
||||
var particlePrefab = ParticleManager.FindPrefab("flame");
|
||||
if (particlePrefab == null) { return; }
|
||||
|
||||
for (int i = 0; i < particleCount; i++)
|
||||
float particlesPerSecond = MathHelper.Clamp(size.X / 2.0f, 10.0f, 200.0f);
|
||||
|
||||
float particleInterval = 1.0f / particlesPerSecond;
|
||||
particleTimer += deltaTime;
|
||||
while (particleTimer > particleInterval)
|
||||
{
|
||||
particleTimer -= particleInterval;
|
||||
Vector2 particlePos = new Vector2(
|
||||
worldPosition.X + Rand.Range(0.0f, size.X),
|
||||
Rand.Range(worldPosition.Y - size.Y, worldPosition.Y + 20.0f));
|
||||
worldPosition.Y - size.Y + particlePrefab.CollisionRadius);
|
||||
|
||||
Vector2 particleVel = new Vector2(
|
||||
particlePos.X - (worldPosition.X + size.X / 2.0f),
|
||||
Math.Max((float)Math.Sqrt(size.X) * Rand.Range(0.0f, 15.0f) * growModifier, 0.0f));
|
||||
|
||||
particleVel.X = MathHelper.Clamp(particleVel.X, -200.0f, 200.0f);
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("flame",
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab,
|
||||
particlePos, particleVel, 0.0f, hull);
|
||||
|
||||
if (particle == null) { continue; }
|
||||
@@ -54,7 +62,7 @@ namespace Barotrauma
|
||||
if (Rand.Int(5) == 1)
|
||||
{
|
||||
var smokeParticle = GameMain.ParticleManager.CreateParticle("smoke",
|
||||
particlePos, new Vector2(particleVel.X, particleVel.Y * 0.1f), 0.0f, hull);
|
||||
particlePos, new Vector2(particleVel.X, particleVel.Y * 0.1f), 0.0f, hull);
|
||||
|
||||
if (smokeParticle != null)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection))
|
||||
{
|
||||
intersectionFound = true;
|
||||
particlePos = intersection;
|
||||
|
||||
@@ -93,13 +93,16 @@ namespace Barotrauma.Lights
|
||||
|
||||
private readonly int thickness;
|
||||
|
||||
public bool IsExteriorWall;
|
||||
|
||||
public VertexPositionColor[] ShadowVertices { get; private set; }
|
||||
public VertexPositionTexture[] PenumbraVertices { get; private set; }
|
||||
public int ShadowVertexCount { get; private set; }
|
||||
public int PenumbraVertexCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the maximum distance a LOS vertex can be moved to make it align with a nearby LOS segment
|
||||
/// </summary>
|
||||
public float? MaxMergeLosVerticesDist;
|
||||
|
||||
private readonly HashSet<ConvexHull> overlappingHulls = new HashSet<ConvexHull>();
|
||||
|
||||
public MapEntity ParentEntity { get; private set; }
|
||||
@@ -130,7 +133,7 @@ namespace Barotrauma.Lights
|
||||
|
||||
public Rectangle BoundingBox { get; private set; }
|
||||
|
||||
public ConvexHull(Rectangle rect, bool? isHorizontal, MapEntity parent)
|
||||
public ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent)
|
||||
{
|
||||
shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
|
||||
{
|
||||
@@ -150,15 +153,15 @@ namespace Barotrauma.Lights
|
||||
|
||||
BoundingBox = rect;
|
||||
|
||||
this.isHorizontal = isHorizontal ?? BoundingBox.Width > BoundingBox.Height;
|
||||
this.isHorizontal = isHorizontal;
|
||||
if (ParentEntity is Structure structure)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(!structure.Removed);
|
||||
Debug.Assert(!structure.Removed);
|
||||
isHorizontal = structure.IsHorizontal;
|
||||
}
|
||||
else if (ParentEntity is Item item)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(!item.Removed);
|
||||
Debug.Assert(!item.Removed);
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null) { isHorizontal = door.IsHorizontal; }
|
||||
}
|
||||
@@ -205,44 +208,97 @@ namespace Barotrauma.Lights
|
||||
{
|
||||
if (ch == this) { return; }
|
||||
|
||||
//hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces)
|
||||
float mergeDist = MathHelper.Clamp(ch.thickness * 0.55f, 16, 512);
|
||||
mergeDist = Math.Min(mergeDist, Vector2.Distance(losVertices[0].Pos, losVertices[1].Pos) / 2);
|
||||
//merge dist in the direction parallel to the segment
|
||||
//(e.g. how far up/down we can stretch a vertical segment)
|
||||
float mergeDistParallel = MathHelper.Clamp(ch.thickness * 0.65f, 16, 512);
|
||||
if (MaxMergeLosVerticesDist.HasValue)
|
||||
{
|
||||
mergeDistParallel = Math.Max(mergeDistParallel, MaxMergeLosVerticesDist.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Rectangle inflatedAABB = ch.BoundingBox;
|
||||
inflatedAABB.Inflate(2, 2);
|
||||
//if this los segment isn't touching the other's bounding box,
|
||||
//don't extend the segment by more than 50% of it's length
|
||||
if (!inflatedAABB.Contains(losVertices[0].Pos) &&
|
||||
!inflatedAABB.Contains(losVertices[1].Pos))
|
||||
{
|
||||
mergeDistParallel = Math.Min(mergeDistParallel, Vector2.Distance(losVertices[0].Pos, losVertices[1].Pos) * 0.5f);
|
||||
}
|
||||
}
|
||||
//merge dist in the direction perpendicular to the segment
|
||||
//(e.g. how far right/left we can stretch a vertical segment)
|
||||
//do not allow more than ~half of the thickness, because that'd make the segment go outside the convex hull
|
||||
float mergeDistPerpendicular = Math.Min(mergeDistParallel, thickness * 0.35f);
|
||||
|
||||
float mergeDistSqr = mergeDist * mergeDist;
|
||||
Vector2 center = (losVertices[0].Pos + losVertices[1].Pos) / 2;
|
||||
|
||||
bool changed = false;
|
||||
for (int i = 0; i < losVertices.Length; i++)
|
||||
{
|
||||
//find the closest point on the other convex hull segment
|
||||
Vector2 segmentDir = Vector2.Normalize(losVertices[i].Pos - center);
|
||||
//check if the closest point on the other convex hull segment is close enough, disregarding any offsets
|
||||
//otherwise we might end up moving the vertex too much if we stretch it to an already-offset segment
|
||||
if (!isCloseEnough(
|
||||
MathUtils.GetClosestPointOnLineSegment(ch.losVertices[0].Pos, ch.losVertices[1].Pos, losVertices[i].Pos),
|
||||
losVertices[i].Pos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//check the offset position of the segment next
|
||||
Vector2 closest = MathUtils.GetClosestPointOnLineSegment(
|
||||
ch.losVertices[0].Pos + ch.losOffsets[0],
|
||||
ch.losVertices[1].Pos + ch.losOffsets[1],
|
||||
losVertices[i].Pos);
|
||||
if (Vector2.DistanceSquared(closest, losVertices[i].Pos) > mergeDistSqr) { continue; }
|
||||
if (!isCloseEnough(closest, losVertices[i].Pos)) { continue; }
|
||||
|
||||
//find where the segments would intersect if they had infinite length
|
||||
// if it's close to the closest point, let's use that instead to keep
|
||||
// the direction of the segment unchanged (i.e. vertical segment stays vertical)
|
||||
if (MathUtils.GetLineIntersection(
|
||||
ch.losVertices[0].Pos + ch.losOffsets[0],
|
||||
ch.losVertices[1].Pos + ch.losOffsets[1],
|
||||
losVertices[0].Pos,
|
||||
losVertices[1].Pos,
|
||||
out Vector2 intersection))
|
||||
ch.losVertices[0].Pos + ch.losOffsets[0], ch.losVertices[1].Pos + ch.losOffsets[1],
|
||||
losVertices[0].Pos, losVertices[1].Pos,
|
||||
areLinesInfinite: true, out Vector2 intersection) &&
|
||||
//the intersection needs to be outwards from the vertex we're checking
|
||||
Vector2.Dot(segmentDir, intersection - losVertices[i].Pos) > 0 &&
|
||||
//the intersection needs to be close enough to the default position of the vertex and the closest point
|
||||
//(we don't want to merge the segments somewhere close to infinity!)
|
||||
(Vector2.DistanceSquared(intersection, losVertices[i].Pos) < mergeDistParallel * mergeDistParallel ||
|
||||
Vector2.DistanceSquared(intersection, closest) < 16.0f * 16.0f))
|
||||
{
|
||||
if (Vector2.DistanceSquared(intersection, losVertices[i].Pos) < mergeDistSqr ||
|
||||
Vector2.DistanceSquared(intersection, closest) < 16.0f * 16.0f)
|
||||
{
|
||||
closest = intersection;
|
||||
}
|
||||
closest = intersection;
|
||||
}
|
||||
|
||||
//don't move the vertices of the segment too close to each other
|
||||
if (Vector2.DistanceSquared(losVertices[1 - i].Pos + losOffsets[1 - i], closest) < mergeDistPerpendicular * mergeDistPerpendicular)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
losOffsets[i] = closest - losVertices[i].Pos;
|
||||
overlappingHulls.Add(ch);
|
||||
ch.overlappingHulls.Add(this);
|
||||
changed = true;
|
||||
|
||||
|
||||
bool isCloseEnough(Vector2 closest, Vector2 vertex)
|
||||
{
|
||||
float dist = Vector2.Distance(closest, vertex);
|
||||
if (dist < 0.001f) { return true; }
|
||||
if (dist > mergeDistParallel) { return false; }
|
||||
|
||||
Vector2 closestDir = (closest - vertex) / dist;
|
||||
|
||||
float dot = Math.Abs(Vector2.Dot(segmentDir, closestDir));
|
||||
float distAlongAxis = dist * dot;
|
||||
if (distAlongAxis > mergeDistParallel) { return false; }
|
||||
|
||||
float distPerpendicular = dist * (1.0f - dot);
|
||||
if (distPerpendicular > mergeDistPerpendicular) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (changed && refreshOtherOverlappingHulls)
|
||||
{
|
||||
@@ -253,6 +309,13 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
}
|
||||
|
||||
public bool LosIntersects(Vector2 pos1, Vector2 pos2)
|
||||
{
|
||||
return MathUtils.LineSegmentsIntersect(
|
||||
losVertices[0].Pos + losOffsets[0], losVertices[1].Pos + losOffsets[1],
|
||||
pos1, pos2);
|
||||
}
|
||||
|
||||
public void Rotate(Vector2 origin, float amount)
|
||||
{
|
||||
Matrix rotationMatrix =
|
||||
@@ -347,6 +410,7 @@ namespace Barotrauma.Lights
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
losVertices[i] = new SegmentPoint(losPoints[i], this);
|
||||
losOffsets[i] = Vector2.Zero;
|
||||
}
|
||||
|
||||
overlappingHulls.Clear();
|
||||
@@ -612,8 +676,11 @@ namespace Barotrauma.Lights
|
||||
vertexPos0 += ParentEntity.Submarine.DrawPosition;
|
||||
vertexPos1 += ParentEntity.Submarine.DrawPosition;
|
||||
}
|
||||
Vector2 viewTargetPos = LightManager.ViewTarget.WorldPosition;
|
||||
float alpha = IsSegmentFacing(vertexPos0, vertexPos1, viewTargetPos) ? 1.0f : 0.5f;
|
||||
float alpha = 1.0f;
|
||||
if (LightManager.ViewTarget != null)
|
||||
{
|
||||
alpha = IsSegmentFacing(vertexPos0, vertexPos1, LightManager.ViewTarget.WorldPosition) ? 1.0f : 0.5f;
|
||||
}
|
||||
vertexPos0.Y = -vertexPos0.Y;
|
||||
vertexPos1.Y = -vertexPos1.Y;
|
||||
GUI.DrawLine(spriteBatch, vertexPos0, vertexPos1, color * alpha, width: width);
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
}
|
||||
|
||||
private class RayCastTask
|
||||
private sealed class RayCastTask
|
||||
{
|
||||
public LightSource LightSource;
|
||||
public Vector2 DrawPos;
|
||||
@@ -298,7 +298,8 @@ namespace Barotrauma.Lights
|
||||
range *
|
||||
((Character.Controlled?.Submarine != null && light.ParentSub == Character.Controlled?.Submarine) ? 2.0f : 1.0f) *
|
||||
(light.CastShadows ? 10.0f : 1.0f) *
|
||||
(light.LightSourceParams.OverrideLightSpriteAlpha ?? (light.Color.A / 255.0f));
|
||||
(light.LightSourceParams.OverrideLightSpriteAlpha ?? (light.Color.A / 255.0f)) *
|
||||
light.PriorityMultiplier;
|
||||
}
|
||||
|
||||
//find the lights with an active light volume
|
||||
@@ -477,6 +478,17 @@ namespace Barotrauma.Lights
|
||||
light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
|
||||
}
|
||||
|
||||
if (ConnectionPanel.ShouldDebugDrawWiring)
|
||||
{
|
||||
foreach (MapEntity e in (Submarine.VisibleEntities ?? MapEntity.mapEntityList))
|
||||
{
|
||||
if (e is Item item && item.GetComponent<Wire>() is Wire wire)
|
||||
{
|
||||
wire.DebugDraw(spriteBatch, alpha: 0.4f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lightEffect.World = transform;
|
||||
|
||||
GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.Additive);
|
||||
@@ -686,19 +698,42 @@ namespace Barotrauma.Lights
|
||||
if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
|
||||
{
|
||||
Vector2 pos = ViewTarget.DrawPosition;
|
||||
if (ViewTarget is Character character &&
|
||||
bool centeredOnHead = false;
|
||||
if (ViewTarget is Character character &&
|
||||
character.AnimController?.GetLimb(LimbType.Head) is Limb head &&
|
||||
!head.IsSevered && !head.Removed)
|
||||
{
|
||||
pos = head.body.DrawPosition;
|
||||
centeredOnHead = true;
|
||||
}
|
||||
|
||||
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
|
||||
|
||||
Matrix shadowTransform = cam.ShaderTransform
|
||||
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
|
||||
|
||||
var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width * 0.75f, ViewTarget.Submarine);
|
||||
|
||||
//make sure the head isn't peeking through any LOS segments, and if it is,
|
||||
//center the LOS on the character's collider instead
|
||||
if (centeredOnHead)
|
||||
{
|
||||
foreach (var ch in convexHulls)
|
||||
{
|
||||
Vector2 currentViewPos = pos;
|
||||
Vector2 defaultViewPos = ViewTarget.DrawPosition;
|
||||
if (ch.ParentEntity?.Submarine != null)
|
||||
{
|
||||
defaultViewPos -= ch.ParentEntity.Submarine.DrawPosition;
|
||||
currentViewPos -= ch.ParentEntity.Submarine.DrawPosition;
|
||||
}
|
||||
//check if a line from the character's collider to the head intersects with the los segment (= head poking through it)
|
||||
if (ch.LosIntersects(defaultViewPos, currentViewPos))
|
||||
{
|
||||
pos = ViewTarget.DrawPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (convexHulls != null)
|
||||
{
|
||||
List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
|
||||
@@ -706,7 +741,6 @@ namespace Barotrauma.Lights
|
||||
foreach (ConvexHull convexHull in convexHulls)
|
||||
{
|
||||
if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
|
||||
if (LosMode == LosMode.BlockOutsideView && !convexHull.IsExteriorWall) { continue; };
|
||||
|
||||
Vector2 relativeLightPos = pos;
|
||||
if (convexHull.ParentEntity?.Submarine != null) { relativeLightPos -= convexHull.ParentEntity.Submarine.Position; }
|
||||
@@ -744,13 +778,14 @@ namespace Barotrauma.Lights
|
||||
|
||||
public void DebugDrawLos(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (ViewTarget == null) { return; }
|
||||
Vector2 pos = ViewTarget?.Position ?? cam.Position;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform);
|
||||
var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width * 0.75f, ViewTarget?.Submarine);
|
||||
var convexHulls = ConvexHull.GetHullsInRange(pos, cam.WorldView.Width * 0.75f, ViewTarget?.Submarine);
|
||||
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
|
||||
foreach (ConvexHull convexHull in convexHulls)
|
||||
{
|
||||
if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
|
||||
if (convexHull.ParentEntity is Structure { CastShadow: false }) { continue; }
|
||||
convexHull.DebugDraw(spriteBatch);
|
||||
}
|
||||
spriteBatch.End();
|
||||
|
||||
@@ -53,10 +53,6 @@ namespace Barotrauma.Lights
|
||||
[Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = -360, MaxValueFloat = 360, ValueStep = 1, DecimalCount = 0)]
|
||||
public float Rotation { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, "Directional lights only shine in \"one direction\", meaning no shadows are cast behind them."+
|
||||
" Note that this does not affect how the light texture is drawn: if you want something like a conical spotlight, you should use an appropriate texture for that.")]
|
||||
public bool Directional { get; set; }
|
||||
|
||||
public Vector2 GetOffset() => Vector2.Transform(Offset, Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation)));
|
||||
|
||||
private float flicker;
|
||||
@@ -233,6 +229,7 @@ namespace Barotrauma.Lights
|
||||
|
||||
//do we need to recalculate the vertices of the light volume
|
||||
private bool needsRecalculation;
|
||||
private bool needsRecalculationWhenUpToDate;
|
||||
public bool NeedsRecalculation
|
||||
{
|
||||
get { return needsRecalculation; }
|
||||
@@ -246,9 +243,15 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
}
|
||||
needsRecalculation = value;
|
||||
if (needsRecalculation && state != LightVertexState.UpToDate)
|
||||
{
|
||||
//if we're currently recalculating light vertices, mark that we need to recalculate them again after it's done
|
||||
needsRecalculationWhenUpToDate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//when were the vertices of the light volume last calculated
|
||||
public float LastRecalculationTime { get; private set; }
|
||||
|
||||
@@ -309,8 +312,6 @@ namespace Barotrauma.Lights
|
||||
if (Math.Abs(value - rotation) < 0.001f) { return; }
|
||||
rotation = value;
|
||||
|
||||
dir = new Vector2(MathF.Cos(rotation), -MathF.Sin(rotation));
|
||||
|
||||
if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null)
|
||||
{
|
||||
return;
|
||||
@@ -321,8 +322,6 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 dir = Vector2.UnitX;
|
||||
|
||||
private Vector2 _spriteScale = Vector2.One;
|
||||
|
||||
public Vector2 SpriteScale
|
||||
@@ -396,6 +395,8 @@ namespace Barotrauma.Lights
|
||||
|
||||
public float Priority;
|
||||
|
||||
public float PriorityMultiplier = 1.0f;
|
||||
|
||||
private Vector2 lightTextureTargetSize;
|
||||
|
||||
public Vector2 LightTextureTargetSize
|
||||
@@ -444,7 +445,7 @@ namespace Barotrauma.Lights
|
||||
public bool Enabled = true;
|
||||
|
||||
private readonly ISerializableEntity conditionalTarget;
|
||||
private readonly PropertyConditional.LogicalOperatorType logicalOperator;
|
||||
private readonly PropertyConditional.Comparison comparison;
|
||||
private readonly List<PropertyConditional> conditionals = new List<PropertyConditional>();
|
||||
|
||||
public LightSource(ContentXElement element, ISerializableEntity conditionalTarget = null)
|
||||
@@ -452,8 +453,11 @@ namespace Barotrauma.Lights
|
||||
{
|
||||
lightSourceParams = new LightSourceParams(element);
|
||||
CastShadows = element.GetAttributeBool("castshadows", true);
|
||||
logicalOperator = element.GetAttributeEnum(nameof(logicalOperator),
|
||||
element.GetAttributeEnum("comparison", logicalOperator));
|
||||
string comparison = element.GetAttributeString("comparison", null);
|
||||
if (comparison != null)
|
||||
{
|
||||
Enum.TryParse(comparison, ignoreCase: true, out this.comparison);
|
||||
}
|
||||
|
||||
if (lightSourceParams.DeformableLightSpriteElement != null)
|
||||
{
|
||||
@@ -466,7 +470,13 @@ namespace Barotrauma.Lights
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "conditional":
|
||||
conditionals.AddRange(PropertyConditional.FromXElement(subElement));
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
conditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -529,32 +539,11 @@ namespace Barotrauma.Lights
|
||||
var fullChList = ConvexHull.HullLists.FirstOrDefault(chList => chList.Submarine == sub);
|
||||
if (fullChList == null) { return; }
|
||||
|
||||
//used to check whether the lightsource hits the target hull if the light is directional
|
||||
Vector2 ray = new Vector2(dir.X, -dir.Y) * TextureRange;
|
||||
Vector2 normal = new Vector2(-ray.Y, ray.X);
|
||||
|
||||
chList.List.Clear();
|
||||
foreach (var convexHull in fullChList.List)
|
||||
{
|
||||
if (!convexHull.Enabled) { continue; }
|
||||
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, convexHull.BoundingBox)) { continue; }
|
||||
if (lightSourceParams.Directional && false)
|
||||
{
|
||||
Rectangle bounds = convexHull.BoundingBox;
|
||||
//invert because GetLineRectangleIntersection uses the messed up rects that start from top-left
|
||||
bounds.Y -= bounds.Height;
|
||||
|
||||
//the ray can't hit if
|
||||
// center is in the opposite direction from the ray (cheapest check first)
|
||||
if (Vector2.Dot(ray, convexHull.BoundingBox.Center.ToVector2() - lightPos) <= 0 &&
|
||||
/*ray doesn't hit the convex hull*/
|
||||
!MathUtils.GetLineRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
|
||||
/*normal vectors of the ray don't hit the convex hull */
|
||||
!MathUtils.GetLineRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
chList.List.Add(convexHull);
|
||||
}
|
||||
chList.IsHidden.RemoveWhere(ch => !chList.List.Contains(ch));
|
||||
@@ -704,7 +693,7 @@ namespace Barotrauma.Lights
|
||||
lock (mutex)
|
||||
{
|
||||
hull.RefreshWorldPositions();
|
||||
hull.GetVisibleSegments(drawPos, visibleSegments);
|
||||
hull.GetVisibleSegments(drawPos, visibleSegments);
|
||||
foreach (var visibleSegment in visibleSegments)
|
||||
{
|
||||
if (visibleSegment.ConvexHull?.ParentEntity?.Submarine != null)
|
||||
@@ -713,7 +702,6 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
foreach (ConvexHull hull in chList.List)
|
||||
@@ -812,7 +800,7 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
else
|
||||
{
|
||||
intersects = MathUtils.GetLineIntersection(p1a, p1b, p2a, p2b, out intersection);
|
||||
intersects = MathUtils.GetLineSegmentIntersection(p1a, p1b, p2a, p2b, out intersection);
|
||||
}
|
||||
|
||||
if (intersects)
|
||||
@@ -1006,7 +994,7 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
else
|
||||
{
|
||||
intersects = MathUtils.GetLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, out intersection);
|
||||
intersects = MathUtils.GetLineSegmentIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, out intersection);
|
||||
}
|
||||
|
||||
if (intersects)
|
||||
@@ -1041,9 +1029,11 @@ namespace Barotrauma.Lights
|
||||
|
||||
Vector2 drawPos = calculatedDrawPos;
|
||||
|
||||
float cosAngle = (float)Math.Cos(Rotation);
|
||||
float sinAngle = -(float)Math.Sin(Rotation);
|
||||
|
||||
Vector2 uvOffset = Vector2.Zero;
|
||||
Vector2 overrideTextureDims = Vector2.One;
|
||||
Vector2 dir = this.dir;
|
||||
if (OverrideLightTexture != null)
|
||||
{
|
||||
overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
|
||||
@@ -1052,7 +1042,8 @@ namespace Barotrauma.Lights
|
||||
if (LightSpriteEffect == SpriteEffects.FlipHorizontally)
|
||||
{
|
||||
origin.X = OverrideLightTexture.SourceRect.Width - origin.X;
|
||||
dir = -dir;
|
||||
cosAngle = -cosAngle;
|
||||
sinAngle = -sinAngle;
|
||||
}
|
||||
if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; }
|
||||
uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
|
||||
@@ -1125,8 +1116,8 @@ namespace Barotrauma.Lights
|
||||
//calculate texture coordinates based on the light's rotation
|
||||
Vector2 originDiff = diff;
|
||||
|
||||
diff.X = originDiff.X * dir.X - originDiff.Y * dir.Y;
|
||||
diff.Y = originDiff.X * dir.Y + originDiff.Y * dir.X;
|
||||
diff.X = originDiff.X * cosAngle - originDiff.Y * sinAngle;
|
||||
diff.Y = originDiff.X * sinAngle + originDiff.Y * cosAngle;
|
||||
diff *= (overrideTextureDims / OverrideLightTexture.size);// / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y)));
|
||||
diff += uvOffset;
|
||||
}
|
||||
@@ -1231,6 +1222,9 @@ namespace Barotrauma.Lights
|
||||
}
|
||||
drawPos.Y = -drawPos.Y;
|
||||
|
||||
float cosAngle = (float)Math.Cos(Rotation);
|
||||
float sinAngle = -(float)Math.Sin(Rotation);
|
||||
|
||||
float bounds = TextureRange;
|
||||
|
||||
if (OverrideLightTexture != null)
|
||||
@@ -1242,8 +1236,8 @@ namespace Barotrauma.Lights
|
||||
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
|
||||
origin *= TextureRange;
|
||||
|
||||
drawPos.X += origin.X * dir.Y + origin.Y * dir.X;
|
||||
drawPos.Y += origin.X * dir.X + origin.Y * dir.Y;
|
||||
drawPos.X += origin.X * sinAngle + origin.Y * cosAngle;
|
||||
drawPos.Y += origin.X * cosAngle + origin.Y * sinAngle;
|
||||
}
|
||||
|
||||
//add a square-shaped boundary to make sure we've got something to construct the triangles from
|
||||
@@ -1349,7 +1343,7 @@ namespace Barotrauma.Lights
|
||||
{
|
||||
if (conditionals.None()) { return; }
|
||||
if (conditionalTarget == null) { return; }
|
||||
if (logicalOperator == PropertyConditional.LogicalOperatorType.And)
|
||||
if (comparison == PropertyConditional.Comparison.And)
|
||||
{
|
||||
Enabled = conditionals.All(c => c.Matches(conditionalTarget));
|
||||
}
|
||||
@@ -1405,7 +1399,9 @@ namespace Barotrauma.Lights
|
||||
CalculateLightVertices(verts);
|
||||
|
||||
LastRecalculationTime = (float)Timing.TotalTime;
|
||||
NeedsRecalculation = false;
|
||||
NeedsRecalculation = needsRecalculationWhenUpToDate;
|
||||
needsRecalculationWhenUpToDate = false;
|
||||
|
||||
state = LightVertexState.UpToDate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private static readonly List<RoundSound> roundSounds = new List<RoundSound>();
|
||||
private static readonly Dictionary<string, RoundSound> roundSoundByPath = new Dictionary<string, RoundSound>();
|
||||
public static RoundSound? Load(ContentXElement element, bool stream = false)
|
||||
{
|
||||
if (GameMain.SoundManager?.Disabled ?? true) { return null; }
|
||||
|
||||
var filename = element.GetAttributeContentPath("file") ?? element.GetAttributeContentPath("sound");
|
||||
|
||||
if (filename is null)
|
||||
{
|
||||
string errorMsg = "Error when loading round sound (" + element + ") - file path not set";
|
||||
@@ -70,7 +70,11 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
Sound? existingSound = roundSounds.Find(s => s.Filename == filename?.FullPath && s.Stream == stream && s.Sound is { Disposed: false })?.Sound;
|
||||
Sound? existingSound = null;
|
||||
if (roundSoundByPath.TryGetValue(filename.FullPath, out RoundSound? rs) && rs.Sound is { Disposed: false })
|
||||
{
|
||||
existingSound = rs.Sound;
|
||||
}
|
||||
|
||||
if (existingSound is null)
|
||||
{
|
||||
@@ -99,7 +103,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
RoundSound newSound = new RoundSound(element, existingSound);
|
||||
|
||||
if (filename is not null && !newSound.Stream)
|
||||
{
|
||||
roundSoundByPath.TryAdd(filename.FullPath, newSound);
|
||||
}
|
||||
roundSounds.Add(newSound);
|
||||
return newSound;
|
||||
}
|
||||
@@ -124,24 +131,14 @@ namespace Barotrauma
|
||||
roundSound.Sound = existingSound;
|
||||
}
|
||||
|
||||
private static void Remove(RoundSound roundSound)
|
||||
{
|
||||
#warning TODO: what is going on here????
|
||||
roundSound.Sound?.Dispose();
|
||||
|
||||
if (roundSounds.Contains(roundSound)) { roundSounds.Remove(roundSound); }
|
||||
foreach (RoundSound otherSound in roundSounds)
|
||||
{
|
||||
if (otherSound.Sound == roundSound.Sound) { otherSound.Sound = null; }
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAllRoundSounds()
|
||||
{
|
||||
for (int i = roundSounds.Count - 1; i >= 0; i--)
|
||||
foreach (var roundSound in roundSounds)
|
||||
{
|
||||
Remove(roundSounds[i]);
|
||||
roundSound.Sound?.Dispose();
|
||||
}
|
||||
roundSounds.Clear();
|
||||
roundSoundByPath.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,11 +58,8 @@ namespace Barotrauma
|
||||
convexHulls ??= new List<ConvexHull>();
|
||||
var h = new ConvexHull(
|
||||
new Rectangle((position - size / 2).ToPoint(), size.ToPoint()),
|
||||
IsHorizontal,
|
||||
this)
|
||||
{
|
||||
IsExteriorWall = IsExteriorWall
|
||||
};
|
||||
IsHorizontal,
|
||||
this);
|
||||
if (Math.Abs(rotation) > 0.001f)
|
||||
{
|
||||
h.Rotate(position, rotation);
|
||||
@@ -501,7 +498,7 @@ namespace Barotrauma
|
||||
|
||||
private bool ConditionalMatches(PropertyConditional conditional)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(conditional.TargetItemComponent))
|
||||
if (!string.IsNullOrEmpty(conditional.TargetItemComponentName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -116,11 +116,11 @@ namespace Barotrauma
|
||||
|
||||
foreach (MapEntity e in entitiesToRender)
|
||||
{
|
||||
if (!e.DrawOverWater) continue;
|
||||
if (!e.DrawOverWater) { continue; }
|
||||
|
||||
if (predicate != null)
|
||||
{
|
||||
if (!predicate(e)) continue;
|
||||
if (!predicate(e)) { continue; }
|
||||
}
|
||||
|
||||
e.Draw(spriteBatch, editing, false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -18,7 +19,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
List<Submarine> subsToMove = submarine.GetConnectedSubs();
|
||||
var subsToMove = submarine.GetConnectedSubs();
|
||||
foreach (Submarine dockedSub in subsToMove)
|
||||
{
|
||||
if (dockedSub == submarine) { continue; }
|
||||
@@ -51,7 +52,6 @@ namespace Barotrauma
|
||||
sub.PhysicsBody.SetTransformIgnoreContacts(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (closestSub != null && subsToMove.Contains(closestSub))
|
||||
{
|
||||
GameMain.GameScreen.Cam.Position += moveAmount;
|
||||
|
||||
@@ -67,6 +67,10 @@ namespace Barotrauma
|
||||
else if (ConnectedDoor != null)
|
||||
{
|
||||
sprite = iconSprites["Door"];
|
||||
if (ConnectedDoor.IsHorizontal && Ladders == null)
|
||||
{
|
||||
clr = Color.Yellow;
|
||||
}
|
||||
}
|
||||
else if (Ladders != null)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -16,6 +15,10 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class GameClient : NetworkMember
|
||||
{
|
||||
public static readonly TimeSpan CampaignSaveTransferTimeOut = new TimeSpan(0, 0, seconds: 100);
|
||||
//this should be longer than CampaignSaveTransferTimeOut - we shouldn't give up starting the round if we're still waiting for the save file
|
||||
public static readonly TimeSpan LevelTransitionTimeOut = new TimeSpan(0, 0, seconds: 150);
|
||||
|
||||
public override bool IsClient => true;
|
||||
public override bool IsServer => false;
|
||||
|
||||
@@ -514,6 +517,7 @@ namespace Barotrauma.Networking
|
||||
DisplayInLoadingScreens = true
|
||||
};
|
||||
Quit();
|
||||
GUI.DisableHUD = false;
|
||||
GameMain.ServerListScreen.Select();
|
||||
return;
|
||||
}
|
||||
@@ -931,7 +935,7 @@ namespace Barotrauma.Networking
|
||||
", level value count: " + levelEqualityCheckValues.Count +
|
||||
", seed: " + Level.Loaded.Seed +
|
||||
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
|
||||
", mirrored: " + Level.Loaded.Mirrored + "). Round init status: {roundInitStatus}." + campaignErrorInfo;
|
||||
", mirrored: " + Level.Loaded.Mirrored + "). Round init status: " + roundInitStatus + "." + campaignErrorInfo;
|
||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
throw new Exception(errorMsg);
|
||||
}
|
||||
@@ -1487,16 +1491,17 @@ namespace Barotrauma.Networking
|
||||
NetIdUtils.IdMoreRecent(campaignSaveID, campaign.PendingSaveID))
|
||||
{
|
||||
campaign.PendingSaveID = campaignSaveID;
|
||||
DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0, 0, 60);
|
||||
DateTime saveFileTimeOut = DateTime.Now + CampaignSaveTransferTimeOut;
|
||||
while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID))
|
||||
{
|
||||
if (DateTime.Now > saveFileTimeOut)
|
||||
{
|
||||
GameStarted = true;
|
||||
DebugConsole.ThrowError("Failed to start campaign round (timed out while waiting for the up-to-date save file).");
|
||||
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("campaignsavetransfer.timeout"));
|
||||
GameMain.NetLobbyScreen.Select();
|
||||
roundInitStatus = RoundInitStatus.Interrupted;
|
||||
yield return CoroutineStatus.Failure;
|
||||
//use success status, even though this is a failure (no need to show a console error because we show it in the message box)
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
@@ -1712,7 +1717,7 @@ namespace Barotrauma.Networking
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
if (GameMain.GameSession != null) { GameMain.GameSession.EndRound(endMessage, traitorResults, transitionType); }
|
||||
GameMain.GameSession?.EndRound(endMessage, traitorResults, transitionType);
|
||||
|
||||
ServerSettings.ServerDetailsChanged = true;
|
||||
|
||||
@@ -2872,12 +2877,14 @@ namespace Barotrauma.Networking
|
||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public bool SpectateClicked(GUIButton button, object userData)
|
||||
public bool SpectateClicked(GUIButton button, object _)
|
||||
{
|
||||
MultiPlayerCampaign campaign =
|
||||
MultiPlayerCampaign campaign =
|
||||
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
|
||||
GameMain.GameSession?.GameMode as MultiPlayerCampaign : null;
|
||||
if (campaign != null && campaign.LastSaveID < campaign.PendingSaveID)
|
||||
|
||||
if (FileReceiver.ActiveTransfers.Any(t => t.FileType == FileTransferType.CampaignSave) ||
|
||||
(campaign != null && NetIdUtils.IdMoreRecent(campaign.PendingSaveID, campaign.LastSaveID)))
|
||||
{
|
||||
new GUIMessageBox("", TextManager.Get("campaignfiletransferinprogress"));
|
||||
return false;
|
||||
@@ -3063,8 +3070,12 @@ namespace Barotrauma.Networking
|
||||
if (votingInterface != null)
|
||||
{
|
||||
votingInterface.Update(deltaTime);
|
||||
if (!votingInterface.VoteRunning)
|
||||
if (!votingInterface.VoteRunning || votingInterface.TimedOut)
|
||||
{
|
||||
if (votingInterface.TimedOut)
|
||||
{
|
||||
DebugConsole.AddWarning($"Voting interface timed out.");
|
||||
}
|
||||
votingInterface.Remove();
|
||||
votingInterface = null;
|
||||
}
|
||||
|
||||
+18
-1
@@ -111,8 +111,25 @@ namespace Barotrauma.Networking
|
||||
? NetworkConnection.TimeoutThresholdInGame
|
||||
: NetworkConnection.TimeoutThreshold;
|
||||
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
||||
try
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
||||
ProcessP2PData(inc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = $"Client failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
|
||||
GameAnalyticsManager.AddErrorEventOnce($"SteamP2PClientPeer.OnP2PData:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessP2PData(IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (!packetHeader.IsServerMessage()) { return; }
|
||||
|
||||
+22
-5
@@ -141,15 +141,31 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (remotePeer.DisconnectTime != null) { return; }
|
||||
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
|
||||
try
|
||||
{
|
||||
ProcessP2PData(steamId, remotePeer, inc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = $"Server failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
|
||||
GameAnalyticsManager.AddErrorEventOnce($"SteamP2POwnerPeer.OnP2PData:OwnerReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
|
||||
private void ProcessP2PData(ulong steamId, RemotePeer remotePeer, IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, connectionInitialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (remotePeer is { Authenticated: false, Authenticating: false } && packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
remotePeer.DisconnectTime = null;
|
||||
|
||||
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
|
||||
ConnectionInitialization initialization = connectionInitialization ?? throw new Exception("Initialization step missing");
|
||||
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
|
||||
{
|
||||
remotePeer.Authenticating = true;
|
||||
@@ -181,6 +197,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ref struct LobbyDataChangedEventHandler
|
||||
private readonly struct LobbyDataChangedEventHandler : IDisposable
|
||||
{
|
||||
private readonly Action<Lobby> action;
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Barotrauma.Networking
|
||||
int botSpawnMode = 0,
|
||||
bool? useRespawnShuttle = null)
|
||||
{
|
||||
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
|
||||
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return; }
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
|
||||
@@ -253,12 +253,9 @@ namespace Barotrauma.Networking
|
||||
//in push-to-talk mode, InputType.Voice uses the active chat mode
|
||||
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
|
||||
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown || captureTimer <= 0)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
}
|
||||
if (pttDown)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
allowEnqueue = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,17 +115,20 @@ namespace Barotrauma.Networking
|
||||
float speechImpedimentMultiplier = 1.0f - client.Character.SpeechImpediment / 100.0f;
|
||||
bool spectating = Character.Controlled == null;
|
||||
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
||||
WifiComponent radio = null;
|
||||
WifiComponent senderRadio = null;
|
||||
var messageType =
|
||||
!client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) && ChatMessage.CanUseRadio(Character.Controlled) ?
|
||||
ChatMessageType.Radio : ChatMessageType.Default;
|
||||
!client.VoipQueue.ForceLocal &&
|
||||
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
|
||||
ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) &&
|
||||
senderRadio.CanReceive(recipientRadio) ?
|
||||
ChatMessageType.Radio : ChatMessageType.Default;
|
||||
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||
|
||||
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
||||
client.RadioNoise = 0.0f;
|
||||
if (messageType == ChatMessageType.Radio)
|
||||
{
|
||||
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||
client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||
if (distanceFactor > RangeNear && !spectating)
|
||||
{
|
||||
//noise starts increasing exponentially after 40% range
|
||||
|
||||
@@ -348,11 +348,13 @@ namespace Barotrauma
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
GameMain.GameSession.PurchaseSubmarine(subInfo);
|
||||
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
|
||||
if (GameMain.GameSession.TryPurchaseSubmarine(subInfo))
|
||||
{
|
||||
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
|
||||
}
|
||||
break;
|
||||
case VoteType.PurchaseSub:
|
||||
GameMain.GameSession.PurchaseSubmarine(subInfo);
|
||||
GameMain.GameSession.TryPurchaseSubmarine(subInfo);
|
||||
break;
|
||||
case VoteType.SwitchSub:
|
||||
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
@@ -48,19 +49,23 @@ namespace Barotrauma.Particles
|
||||
|
||||
private Vector2 drawPosition;
|
||||
private float drawRotation;
|
||||
|
||||
private Vector2 colliderRadius;
|
||||
|
||||
private Hull currentHull;
|
||||
|
||||
private List<Gap> hullGaps;
|
||||
|
||||
private bool hasSubEmitters;
|
||||
private List<ParticleEmitter> subEmitters = new List<ParticleEmitter>();
|
||||
private readonly List<ParticleEmitter> subEmitters = new List<ParticleEmitter>();
|
||||
|
||||
private float animState;
|
||||
private int animFrame;
|
||||
|
||||
private float collisionUpdateTimer;
|
||||
|
||||
private bool changesSize;
|
||||
|
||||
public bool HighQualityCollisionDetection;
|
||||
|
||||
public Vector4 ColorMultiplier;
|
||||
@@ -127,7 +132,10 @@ namespace Barotrauma.Particles
|
||||
position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
|
||||
}
|
||||
|
||||
RefreshColliderSize();
|
||||
|
||||
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
|
||||
changesSize = !sizeChange.NearlyEquals(Vector2.Zero);
|
||||
|
||||
this.position = position;
|
||||
prevPosition = position;
|
||||
@@ -256,8 +264,12 @@ namespace Barotrauma.Particles
|
||||
}
|
||||
}
|
||||
|
||||
size.X += sizeChange.X * deltaTime;
|
||||
size.Y += sizeChange.Y * deltaTime;
|
||||
if (changesSize)
|
||||
{
|
||||
size.X += sizeChange.X * deltaTime;
|
||||
size.Y += sizeChange.Y * deltaTime;
|
||||
RefreshColliderSize();
|
||||
}
|
||||
|
||||
if (UseMiddleColor)
|
||||
{
|
||||
@@ -344,11 +356,11 @@ namespace Barotrauma.Particles
|
||||
{
|
||||
Rectangle hullRect = currentHull.WorldRect;
|
||||
Vector2 collisionNormal = Vector2.Zero;
|
||||
if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height)
|
||||
if (velocity.Y < 0.0f && position.Y - colliderRadius.Y < hullRect.Y - hullRect.Height)
|
||||
{
|
||||
collisionNormal = new Vector2(0.0f, 1.0f);
|
||||
}
|
||||
else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y)
|
||||
else if (velocity.Y > 0.0f && position.Y + colliderRadius.Y > hullRect.Y)
|
||||
{
|
||||
collisionNormal = new Vector2(0.0f, -1.0f);
|
||||
}
|
||||
@@ -378,11 +390,11 @@ namespace Barotrauma.Particles
|
||||
}
|
||||
|
||||
collisionNormal = Vector2.Zero;
|
||||
if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
|
||||
if (velocity.X < 0.0f && position.X - colliderRadius.X < hullRect.X)
|
||||
{
|
||||
collisionNormal = new Vector2(1.0f, 0.0f);
|
||||
}
|
||||
else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right)
|
||||
else if (velocity.X > 0.0f && position.X + colliderRadius.X > hullRect.Right)
|
||||
{
|
||||
collisionNormal = new Vector2(-1.0f, 0.0f);
|
||||
}
|
||||
@@ -431,6 +443,13 @@ namespace Barotrauma.Particles
|
||||
return UpdateResult.Normal;
|
||||
}
|
||||
|
||||
private void RefreshColliderSize()
|
||||
{
|
||||
if (!prefab.UseCollision) { return; }
|
||||
colliderRadius = new Vector2(prefab.CollisionRadius);
|
||||
if (!prefab.InvariantCollisionSize) { colliderRadius *= size; }
|
||||
}
|
||||
|
||||
private void ApplyDrag(float dragCoefficient, float deltaTime)
|
||||
{
|
||||
Vector2 relativeVel = velocity;
|
||||
@@ -475,11 +494,11 @@ namespace Barotrauma.Particles
|
||||
{
|
||||
if (collisionNormal.X > 0.0f)
|
||||
{
|
||||
position.X = Math.Max(position.X, prevHullRect.X + prefab.CollisionRadius * size.X);
|
||||
position.X = Math.Max(position.X, prevHullRect.X + colliderRadius.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.X = Math.Min(position.X, prevHullRect.Right - prefab.CollisionRadius * size.X);
|
||||
position.X = Math.Min(position.X, prevHullRect.Right - colliderRadius.X);
|
||||
}
|
||||
velocity.X = Math.Sign(collisionNormal.X) * Math.Abs(velocity.X) * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
@@ -488,11 +507,11 @@ namespace Barotrauma.Particles
|
||||
{
|
||||
if (collisionNormal.Y > 0.0f)
|
||||
{
|
||||
position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + prefab.CollisionRadius * size.Y);
|
||||
position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + colliderRadius.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.Y = Math.Min(position.Y, prevHullRect.Y - prefab.CollisionRadius * size.Y);
|
||||
position.Y = Math.Min(position.Y, prevHullRect.Y - colliderRadius.Y);
|
||||
|
||||
}
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
@@ -513,26 +532,26 @@ namespace Barotrauma.Particles
|
||||
|
||||
if (position.Y < center.Y)
|
||||
{
|
||||
position.Y = hullRect.Y - hullRect.Height - prefab.CollisionRadius;
|
||||
position.Y = hullRect.Y - hullRect.Height - colliderRadius.Y;
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = -velocity.Y * prefab.Restitution;
|
||||
}
|
||||
else if (position.Y > center.Y)
|
||||
{
|
||||
position.Y = hullRect.Y + prefab.CollisionRadius;
|
||||
position.Y = hullRect.Y + colliderRadius.Y;
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = -velocity.Y * prefab.Restitution;
|
||||
}
|
||||
|
||||
if (position.X < center.X)
|
||||
{
|
||||
position.X = hullRect.X - prefab.CollisionRadius;
|
||||
position.X = hullRect.X - colliderRadius.X;
|
||||
velocity.X = -velocity.X * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
else if (position.X > center.X)
|
||||
{
|
||||
position.X = hullRect.X + hullRect.Width + prefab.CollisionRadius;
|
||||
position.X = hullRect.X + hullRect.Width + colliderRadius.X;
|
||||
velocity.X = -velocity.X * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
@@ -559,11 +578,12 @@ namespace Barotrauma.Particles
|
||||
|
||||
Color currColor = new Color(color.ToVector4() * ColorMultiplier);
|
||||
|
||||
if (prefab.Sprites[spriteIndex] is SpriteSheet)
|
||||
Vector2 drawPos = new Vector2(drawPosition.X, -drawPosition.Y);
|
||||
if (prefab.Sprites[spriteIndex] is SpriteSheet sheet)
|
||||
{
|
||||
((SpriteSheet)prefab.Sprites[spriteIndex]).Draw(
|
||||
sheet.Draw(
|
||||
spriteBatch, animFrame,
|
||||
new Vector2(drawPosition.X, -drawPosition.Y),
|
||||
drawPos,
|
||||
currColor * (currColor.A / 255.0f),
|
||||
prefab.Sprites[spriteIndex].Origin, drawRotation,
|
||||
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
|
||||
@@ -571,11 +591,23 @@ namespace Barotrauma.Particles
|
||||
else
|
||||
{
|
||||
prefab.Sprites[spriteIndex].Draw(spriteBatch,
|
||||
new Vector2(drawPosition.X, -drawPosition.Y),
|
||||
drawPos,
|
||||
currColor * (currColor.A / 255.0f),
|
||||
prefab.Sprites[spriteIndex].Origin, drawRotation,
|
||||
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
|
||||
}
|
||||
|
||||
/*if (GameMain.DebugDraw && prefab.UseCollision)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos - Vector2.UnitX * colliderRadius.X,
|
||||
drawPos + Vector2.UnitX * colliderRadius.X,
|
||||
Color.Gray);
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos - Vector2.UnitY * colliderRadius.Y,
|
||||
drawPos + Vector2.UnitY * colliderRadius.Y,
|
||||
Color.Gray);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ namespace Barotrauma.Particles
|
||||
[Editable(0.0f, 10000.0f), Serialize(0.0f, IsPropertySaveable.No, description: "Radius of the particle's collider. Only has an effect if UseCollision is set to true.")]
|
||||
public float CollisionRadius { get; private set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "If enabled, the size (or changes in size) of the particle doesn't affect the size of the collider.")]
|
||||
public bool InvariantCollisionSize { get; private set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Does the particle collide with the walls of the submarine and the level.")]
|
||||
public bool UseCollision { get; private set; }
|
||||
|
||||
|
||||
+4
-8
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; }
|
||||
|
||||
private GUIButton nextButton;
|
||||
private GUILayoutGroup characterInfoColumns;
|
||||
private GUIListBox characterInfoColumns;
|
||||
|
||||
public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
|
||||
: base(newGameContainer, loadGameContainer)
|
||||
@@ -249,11 +249,7 @@ namespace Barotrauma
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.04f), secondPageLayout.RectTransform),
|
||||
TextManager.Get("Crew"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopLeft);
|
||||
|
||||
characterInfoColumns = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f
|
||||
};
|
||||
characterInfoColumns = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true);
|
||||
|
||||
var secondPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
|
||||
secondPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
|
||||
@@ -306,8 +302,8 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < characterInfos.Count; i++)
|
||||
{
|
||||
var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f / characterInfos.Count, 1.0f),
|
||||
characterInfoColumns.RectTransform));
|
||||
var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(Math.Max(1.0f / characterInfos.Count, 0.33f), 1.0f),
|
||||
characterInfoColumns.Content.RectTransform));
|
||||
|
||||
var (characterInfo, job) = characterInfos[i];
|
||||
|
||||
|
||||
@@ -202,18 +202,21 @@ namespace Barotrauma
|
||||
case CampaignMode.InteractionType.PurchaseSub:
|
||||
submarineSelection?.Update();
|
||||
break;
|
||||
|
||||
case CampaignMode.InteractionType.Crew:
|
||||
CrewManagement?.Update();
|
||||
break;
|
||||
|
||||
case CampaignMode.InteractionType.Store:
|
||||
Store?.Update(deltaTime);
|
||||
break;
|
||||
|
||||
break;
|
||||
case CampaignMode.InteractionType.MedicalClinic:
|
||||
MedicalClinic?.Update(deltaTime);
|
||||
break;
|
||||
case CampaignMode.InteractionType.Map:
|
||||
if (StartButton != null)
|
||||
{
|
||||
StartButton.Enabled = CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap) && Character.Controlled is { IsIncapacitated: false };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,7 +571,6 @@ namespace Barotrauma
|
||||
StartButton.Visible = false;
|
||||
missionList.Enabled = false;
|
||||
}
|
||||
//locationInfoPanel?.UpdateAuto(1.0f);
|
||||
}
|
||||
|
||||
public void SelectTab(CampaignMode.InteractionType tab, Character npc = null)
|
||||
|
||||
@@ -435,8 +435,11 @@ namespace Barotrauma
|
||||
|
||||
graphics.BlendState = BlendState.NonPremultiplied;
|
||||
graphics.SamplerStates[0] = SamplerState.PointClamp;
|
||||
graphics.SamplerStates[1] = SamplerState.PointClamp;
|
||||
GameMain.LightManager.LosEffect.CurrentTechnique.Passes[0].Apply();
|
||||
Quad.Render();
|
||||
graphics.SamplerStates[0] = SamplerState.LinearWrap;
|
||||
graphics.SamplerStates[1] = SamplerState.LinearWrap;
|
||||
}
|
||||
|
||||
if (Character.Controlled is { } character)
|
||||
|
||||
@@ -59,7 +59,6 @@ namespace Barotrauma
|
||||
private GUIImage playstyleBanner;
|
||||
private GUITextBlock playstyleDescription;
|
||||
|
||||
private const string RemoteContentUrl = "http://www.barotraumagame.com/gamedata/";
|
||||
private readonly GUIComponent remoteContentContainer;
|
||||
private XDocument remoteContentDoc;
|
||||
|
||||
@@ -1525,10 +1524,11 @@ namespace Barotrauma
|
||||
|
||||
private void FetchRemoteContent()
|
||||
{
|
||||
if (string.IsNullOrEmpty(RemoteContentUrl)) { return; }
|
||||
string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
|
||||
if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
|
||||
try
|
||||
{
|
||||
var client = new RestClient(RemoteContentUrl);
|
||||
var client = new RestClient(remoteContentUrl);
|
||||
var request = new RestRequest("MenuContent.xml", Method.GET);
|
||||
TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request),
|
||||
RemoteContentReceived);
|
||||
|
||||
@@ -751,12 +751,20 @@ namespace Barotrauma
|
||||
};
|
||||
ServerMessage.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
Vector2 textSize = textBox.Font.MeasureString(textBox.WrappedText);
|
||||
textBox.RectTransform.NonScaledSize = new Point(textBox.RectTransform.NonScaledSize.X, Math.Max(serverMessageContainer.Content.Rect.Height, (int)textSize.Y + 10));
|
||||
serverMessageContainer.UpdateScrollBarSize();
|
||||
serverMessageHint.Visible = !textBox.Selected && !textBox.Readonly && string.IsNullOrWhiteSpace(textBox.Text);
|
||||
RefreshServerInfoSize();
|
||||
return true;
|
||||
};
|
||||
ServerMessage.RectTransform.SizeChanged += RefreshServerInfoSize;
|
||||
|
||||
void RefreshServerInfoSize()
|
||||
{
|
||||
serverMessageHint.Visible = !ServerMessage.Selected && !ServerMessage.Readonly && string.IsNullOrWhiteSpace(ServerMessage.Text);
|
||||
Vector2 textSize = ServerMessage.Font.MeasureString(ServerMessage.WrappedText);
|
||||
ServerMessage.RectTransform.NonScaledSize = new Point(ServerMessage.RectTransform.NonScaledSize.X, Math.Max(serverMessageContainer.Content.Rect.Height, (int)textSize.Y + 10));
|
||||
serverMessageContainer.UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
ServerMessage.OnEnterPressed += (textBox, text) =>
|
||||
{
|
||||
string str = textBox.Text;
|
||||
|
||||
+54
-15
@@ -812,7 +812,7 @@ namespace Barotrauma
|
||||
|
||||
private bool SortList(GUIButton button, object obj)
|
||||
{
|
||||
if (!(obj is ColumnLabel sortBy)) { return false; }
|
||||
if (obj is not ColumnLabel sortBy) { return false; }
|
||||
SortList(sortBy, toggle: true);
|
||||
return true;
|
||||
}
|
||||
@@ -848,8 +848,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (c1.GUIComponent.UserData is not ServerInfo s1) { return 0; }
|
||||
if (c2.GUIComponent.UserData is not ServerInfo s2) { return 0; }
|
||||
int comparison = sortedAscending ? 1 : -1;
|
||||
return CompareServer(sortBy, s1, s2) * comparison;
|
||||
return CompareServer(sortBy, s1, s2, sortedAscending);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -857,22 +856,31 @@ namespace Barotrauma
|
||||
{
|
||||
var children = serverList.Content.RectTransform.Children.Reverse().ToList();
|
||||
|
||||
int comparison = sortedAscending ? 1 : -1;
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child.GUIComponent.UserData is not ServerInfo serverInfo2 || serverInfo.Equals(serverInfo2)) { continue; }
|
||||
if (CompareServer(sortedBy, serverInfo, serverInfo2) * comparison >= 0)
|
||||
if (CompareServer(sortedBy, serverInfo, serverInfo2, sortedAscending) >= 0)
|
||||
{
|
||||
var index = serverList.Content.RectTransform.GetChildIndex(child);
|
||||
component.RectTransform.RepositionChildInHierarchy(index + 1);
|
||||
component.RectTransform.RepositionChildInHierarchy(Math.Min(index + 1, serverList.Content.CountChildren - 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
component.RectTransform.SetAsFirstChild();
|
||||
}
|
||||
|
||||
private static int CompareServer(ColumnLabel sortBy, ServerInfo s1, ServerInfo s2)
|
||||
private static int CompareServer(ColumnLabel sortBy, ServerInfo s1, ServerInfo s2, bool ascending)
|
||||
{
|
||||
//always put servers with unknown ping at the bottom (unless we're specifically sorting by ping)
|
||||
//servers without a ping are often unreachable/spam
|
||||
bool s1HasPing = s1.Ping.IsSome();
|
||||
bool s2HasPing = s2.Ping.IsSome();
|
||||
if (s1HasPing != s2HasPing)
|
||||
{
|
||||
return s1HasPing ? -1 : 1;
|
||||
}
|
||||
|
||||
int comparison = ascending ? 1 : -1;
|
||||
switch (sortBy)
|
||||
{
|
||||
case ColumnLabel.ServerListCompatible:
|
||||
@@ -880,18 +888,18 @@ namespace Barotrauma
|
||||
bool s2Compatible = NetworkMember.IsCompatible(GameMain.Version, s2.GameVersion);
|
||||
|
||||
if (s1Compatible == s2Compatible) { return 0; }
|
||||
return s1Compatible ? -1 : 1;
|
||||
return (s1Compatible ? -1 : 1) * comparison;
|
||||
case ColumnLabel.ServerListHasPassword:
|
||||
if (s1.HasPassword == s2.HasPassword) { return 0; }
|
||||
return s1.HasPassword ? 1 : -1;
|
||||
return (s1.HasPassword ? 1 : -1) * comparison;
|
||||
case ColumnLabel.ServerListName:
|
||||
// I think we actually want culture-specific sorting here?
|
||||
return string.Compare(s1.ServerName, s2.ServerName, StringComparison.CurrentCulture);
|
||||
return string.Compare(s1.ServerName, s2.ServerName, StringComparison.CurrentCulture) * comparison;
|
||||
case ColumnLabel.ServerListRoundStarted:
|
||||
if (s1.GameStarted == s2.GameStarted) { return 0; }
|
||||
return s1.GameStarted ? 1 : -1;
|
||||
return (s1.GameStarted ? 1 : -1) * comparison;
|
||||
case ColumnLabel.ServerListPlayers:
|
||||
return s2.PlayerCount.CompareTo(s1.PlayerCount);
|
||||
return s2.PlayerCount.CompareTo(s1.PlayerCount) * comparison;
|
||||
case ColumnLabel.ServerListPing:
|
||||
return (s1.Ping.TryUnwrap(out var s1Ping), s2.Ping.TryUnwrap(out var s2Ping)) switch
|
||||
{
|
||||
@@ -899,7 +907,7 @@ namespace Barotrauma
|
||||
(true, true) => s2Ping.CompareTo(s1Ping),
|
||||
(false, true) => 1,
|
||||
(true, false) => -1
|
||||
};
|
||||
} * comparison;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -1504,9 +1512,41 @@ namespace Barotrauma
|
||||
|
||||
private void AddToServerList(ServerInfo serverInfo, bool skipPing = false)
|
||||
{
|
||||
const int MaxAllowedPlayers = 1000;
|
||||
const int MaxAllowedSimilarServers = 10;
|
||||
const float MinSimilarityPercentage = 0.8f;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(serverInfo.ServerName)) { return; }
|
||||
if (serverInfo.PlayerCount > serverInfo.MaxPlayers) { return; }
|
||||
if (serverInfo.PlayerCount < 0) { return; }
|
||||
if (serverInfo.MaxPlayers <= 0) { return; }
|
||||
//no way a legit server can have this many players
|
||||
if (serverInfo.MaxPlayers > MaxAllowedPlayers) { return; }
|
||||
|
||||
int similarServerCount = 0;
|
||||
string serverInfoStr = getServerInfoStr(serverInfo);
|
||||
foreach (var serverElement in serverList.Content.Children)
|
||||
{
|
||||
if (!serverElement.Visible) { continue; }
|
||||
if (serverElement.UserData is not ServerInfo otherServer || otherServer == serverInfo) { continue; }
|
||||
if (ToolBox.LevenshteinDistance(serverInfoStr, getServerInfoStr(otherServer)) < serverInfoStr.Length * (1.0f - MinSimilarityPercentage))
|
||||
{
|
||||
similarServerCount++;
|
||||
if (similarServerCount > MaxAllowedSimilarServers)
|
||||
{
|
||||
DebugConsole.Log($"Server {serverInfo.ServerName} seems to be almost identical to {otherServer.ServerName}. Hiding as a potential spam server.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (similarServerCount > MaxAllowedSimilarServers) { return; }
|
||||
|
||||
static string getServerInfoStr(ServerInfo serverInfo)
|
||||
{
|
||||
string str = serverInfo.ServerName + serverInfo.ServerMessage + serverInfo.MaxPlayers;
|
||||
if (str.Length > 200) { return str.Substring(0, 200); }
|
||||
return str;
|
||||
}
|
||||
|
||||
RemoveMsgFromServerList(MsgUserData.RefreshingServerList);
|
||||
RemoveMsgFromServerList(MsgUserData.NoServers);
|
||||
@@ -1522,7 +1562,6 @@ namespace Barotrauma
|
||||
UpdateServerInfoUI(serverInfo);
|
||||
if (!skipPing) { PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI); }
|
||||
|
||||
InsertServer(serverInfo, serverFrame);
|
||||
}
|
||||
|
||||
private void UpdateServerInfoUI(ServerInfo serverInfo)
|
||||
@@ -1736,7 +1775,7 @@ namespace Barotrauma
|
||||
AddToFavoriteServers(serverInfo);
|
||||
}
|
||||
|
||||
SortList(sortedBy, toggle: false);
|
||||
InsertServer(serverInfo, serverFrame);
|
||||
FilterServers();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -525,6 +525,11 @@ namespace Barotrauma
|
||||
GUI.AddMessage(TextManager.Get("waypointsgeneratedsuccesfully"), GUIStyle.Green);
|
||||
}
|
||||
WayPoint.ShowWayPoints = true;
|
||||
var matchingTickBox = showEntitiesTickBoxes?.Find(tb => tb.UserData as string == "waypoint");
|
||||
if (matchingTickBox != null)
|
||||
{
|
||||
matchingTickBox.Selected = true;
|
||||
}
|
||||
generateWaypointsVerification.Close();
|
||||
return true;
|
||||
};
|
||||
@@ -2847,7 +2852,7 @@ namespace Barotrauma
|
||||
{
|
||||
OnClicked = (button, o) =>
|
||||
{
|
||||
var requiredPackages = MapEntity.mapEntityList.Select(e => e.Prefab.ContentPackage)
|
||||
var requiredPackages = MapEntity.mapEntityList.Select(e => e?.Prefab?.ContentPackage)
|
||||
.Where(cp => cp != null)
|
||||
.Distinct().OfType<ContentPackage>().Select(p => p.Name).ToHashSet();
|
||||
var tickboxes = requiredContentPackList.Content.Children.OfType<GUITickBox>().ToArray();
|
||||
@@ -3546,10 +3551,46 @@ namespace Barotrauma
|
||||
TextManager.Get("LoadingVanillaSubmarineHeader"),
|
||||
TextManager.Get("LoadingVanillaSubmarineDesc"));
|
||||
|
||||
public void LoadSub(SubmarineInfo info)
|
||||
public void LoadSub(SubmarineInfo info, bool checkIdConflicts = true)
|
||||
{
|
||||
Submarine.Unload();
|
||||
Submarine selectedSub = null;
|
||||
|
||||
if (checkIdConflicts)
|
||||
{
|
||||
Dictionary<int, Identifier> entities = new Dictionary<int, Identifier>();
|
||||
foreach (var subElement in info.SubmarineElement.Elements())
|
||||
{
|
||||
int id = subElement.GetAttributeInt("ID", -1);
|
||||
if (id == -1) { continue; }
|
||||
Identifier identifier = subElement.GetAttributeIdentifier("identifier", string.Empty);
|
||||
if (entities.TryGetValue(id, out Identifier duplicateEntity))
|
||||
{
|
||||
var errorMsg = new GUIMessageBox(
|
||||
TextManager.Get("error"),
|
||||
TextManager.GetWithVariables("subeditor.duplicateiderror",
|
||||
("[entity1]", $"{duplicateEntity} ({id})"),
|
||||
("[entity2]", $"{identifier} ({id})")),
|
||||
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
errorMsg.Buttons[0].OnClicked = (bnt, userdata) =>
|
||||
{
|
||||
subElement.Remove();
|
||||
LoadSub(info, checkIdConflicts: false);
|
||||
errorMsg.Close();
|
||||
return true;
|
||||
};
|
||||
errorMsg.Buttons[1].OnClicked = (bnt, userdata) =>
|
||||
{
|
||||
LoadSub(info, checkIdConflicts: false);
|
||||
errorMsg.Close();
|
||||
return true;
|
||||
};
|
||||
return;
|
||||
}
|
||||
entities.Add(id, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
selectedSub = new Submarine(info);
|
||||
@@ -5755,7 +5796,10 @@ namespace Barotrauma
|
||||
{
|
||||
item.SetTransform(dummyCharacter.SimPosition, 0.0f);
|
||||
item.UpdateTransform();
|
||||
item.SetTransform(item.body.SimPosition, 0.0f);
|
||||
if (item.body != null)
|
||||
{
|
||||
item.SetTransform(item.body.SimPosition, 0.0f);
|
||||
}
|
||||
|
||||
//wires need to be updated for the last node to follow the player during rewiring
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
@@ -5868,6 +5912,11 @@ namespace Barotrauma
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
if (GameMain.LightManager.DebugLos)
|
||||
{
|
||||
GameMain.LightManager.DebugDrawLos(spriteBatch, cam);
|
||||
}
|
||||
|
||||
//-------------------- HUD -----------------------------
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
|
||||
|
||||
@@ -343,12 +343,11 @@ namespace Barotrauma
|
||||
if (property.PropertyType == typeof(string) && value == null)
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
}
|
||||
|
||||
Identifier propertyTag = $"{property.PropertyInfo.DeclaringType.Name}.{property.PropertyInfo.Name}".ToIdentifier();
|
||||
Identifier fallbackTag = property.PropertyInfo.Name.ToIdentifier();
|
||||
LocalizedString displayName =
|
||||
TextManager.Get(propertyTag, $"sp.{propertyTag}.name".ToIdentifier());
|
||||
LocalizedString displayName = TextManager.Get(propertyTag, $"sp.{propertyTag}.name".ToIdentifier());
|
||||
if (displayName.IsNullOrEmpty())
|
||||
{
|
||||
Editable editable = property.GetAttribute<Editable>();
|
||||
@@ -380,10 +379,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
LocalizedString toolTip = TextManager.Get($"sp.{propertyTag}.description");
|
||||
if (toolTip.IsNullOrEmpty() && entity.GetType() != property.PropertyInfo.DeclaringType)
|
||||
if (entity.GetType() != property.PropertyInfo.DeclaringType)
|
||||
{
|
||||
Identifier propertyTagForDerivedClass = $"{entity.GetType().Name}.{property.PropertyInfo.Name}".ToIdentifier();
|
||||
toolTip = TextManager.Get($"{propertyTagForDerivedClass}.description", $"sp.{propertyTagForDerivedClass}.description");
|
||||
var toolTipForDerivedClass = TextManager.Get($"{propertyTagForDerivedClass}.description", $"sp.{propertyTagForDerivedClass}.description");
|
||||
if (!toolTipForDerivedClass.IsNullOrEmpty())
|
||||
{
|
||||
toolTip = toolTipForDerivedClass;
|
||||
}
|
||||
}
|
||||
if (toolTip.IsNullOrEmpty())
|
||||
{
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Barotrauma.Steam
|
||||
CrossThread.RequestExecutionOnMainThread(() => ContentPackageManager.LocalPackages.Refresh());
|
||||
}
|
||||
|
||||
public static async Task CreatePublishStagingCopy(string modVersion, ContentPackage contentPackage)
|
||||
public static async Task CreatePublishStagingCopy(string title, string modVersion, ContentPackage contentPackage)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
@@ -184,11 +184,13 @@ namespace Barotrauma.Steam
|
||||
throw new Exception("Staging copy could not be loaded",
|
||||
result.TryUnwrapFailure(out var exception) ? exception : null);
|
||||
}
|
||||
|
||||
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
|
||||
|
||||
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
|
||||
ModProject modProject = new ModProject(tempPkg)
|
||||
{
|
||||
ModVersion = modVersion
|
||||
ModVersion = modVersion,
|
||||
Name = title,
|
||||
ExpectedHash = tempPkg.CalculateHash(name: title, modVersion: modVersion)
|
||||
};
|
||||
modProject.Save(stagingFileListPath);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Steamworks;
|
||||
using Directory = Barotrauma.IO.Directory;
|
||||
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
|
||||
using Path = Barotrauma.IO.Path;
|
||||
@@ -157,7 +158,7 @@ namespace Barotrauma.Steam
|
||||
}
|
||||
|
||||
var selectedTitle =
|
||||
new GUITextBlock(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), workshopItem.Title ?? localPackage.Name,
|
||||
new GUITextBlock(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), localPackage.Name,
|
||||
font: GUIStyle.LargeFont);
|
||||
if (workshopItem.Id != 0)
|
||||
{
|
||||
@@ -212,7 +213,7 @@ namespace Barotrauma.Steam
|
||||
};
|
||||
|
||||
Label(rightTop, TextManager.Get("WorkshopItemTitle"), GUIStyle.SubHeadingFont);
|
||||
var titleTextBox = new GUITextBox(NewItemRectT(rightTop), workshopItem.Title ?? localPackage.Name);
|
||||
var titleTextBox = new GUITextBox(NewItemRectT(rightTop), localPackage.Name);
|
||||
|
||||
Label(rightTop, TextManager.Get("WorkshopItemDescription"), GUIStyle.SubHeadingFont);
|
||||
var descriptionTextBox
|
||||
@@ -320,7 +321,9 @@ namespace Barotrauma.Steam
|
||||
workshopItem.Id == 0
|
||||
? Steamworks.Ugc.Editor.NewCommunityFile
|
||||
: new Steamworks.Ugc.Editor(workshopItem.Id);
|
||||
ugcEditor = ugcEditor.WithTitle(titleTextBox.Text)
|
||||
ugcEditor = ugcEditor
|
||||
.InLanguage(SteamUtils.SteamUILanguage ?? string.Empty)
|
||||
.WithTitle(titleTextBox.Text)
|
||||
.WithDescription(descriptionTextBox.Text)
|
||||
.WithTags(tagButtons.Where(kvp => kvp.Value.Selected).Select(kvp => kvp.Key.Value))
|
||||
.WithChangeLog(changeNoteTextBox.Text)
|
||||
@@ -478,7 +481,7 @@ namespace Barotrauma.Steam
|
||||
bool stagingReady = false;
|
||||
Exception? stagingException = null;
|
||||
TaskPool.Add("CreatePublishStagingCopy",
|
||||
SteamManager.Workshop.CreatePublishStagingCopy(modVersion, localPackage),
|
||||
SteamManager.Workshop.CreatePublishStagingCopy(editor.Title ?? localPackage.Name, modVersion, localPackage),
|
||||
(t) =>
|
||||
{
|
||||
stagingReady = true;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class LeakyBucket
|
||||
{
|
||||
private readonly Queue<Action> queue;
|
||||
private readonly int capacity;
|
||||
private readonly float cooldownInSeconds;
|
||||
private float timer;
|
||||
|
||||
public LeakyBucket(float cooldownInSeconds, int capacity)
|
||||
{
|
||||
this.cooldownInSeconds = cooldownInSeconds;
|
||||
this.capacity = capacity;
|
||||
queue = new Queue<Action>(capacity);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (timer > 0f)
|
||||
{
|
||||
timer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (queue.Count is 0) { return; }
|
||||
|
||||
TryDequeue();
|
||||
}
|
||||
|
||||
private void TryDequeue()
|
||||
{
|
||||
timer = cooldownInSeconds;
|
||||
if (queue.TryDequeue(out var action))
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryEnqueue(Action item)
|
||||
{
|
||||
if (queue.Count >= capacity) { return false; }
|
||||
queue.Enqueue(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -6,8 +6,8 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.1.4.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.1.4.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
|
||||
@@ -30,10 +30,16 @@ float xLosAlpha;
|
||||
|
||||
float4 xColor;
|
||||
|
||||
float blurDistance;
|
||||
|
||||
float4 mainPS(VertexShaderOutput input) : COLOR0
|
||||
{
|
||||
float4 sampleColor = tex2D(TextureSampler, input.TexCoords);
|
||||
float4 losColor = tex2D(LosSampler, input.TexCoords);
|
||||
float4 losColor = tex2D(LosSampler, float2(input.TexCoords.x + blurDistance, input.TexCoords.y + blurDistance));
|
||||
losColor += tex2D(LosSampler, float2(input.TexCoords.x - blurDistance, input.TexCoords.y - blurDistance));
|
||||
losColor += tex2D(LosSampler, float2(input.TexCoords.x + blurDistance, input.TexCoords.y - blurDistance));
|
||||
losColor += tex2D(LosSampler, float2(input.TexCoords.x - blurDistance, input.TexCoords.y + blurDistance));
|
||||
losColor = losColor * 0.25f;
|
||||
|
||||
float obscureAmount = 1.0f - losColor.r;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.1.4.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.1.4.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.1.4.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
|
||||
@@ -60,6 +60,10 @@ namespace Barotrauma
|
||||
{
|
||||
distance = Math.Min(distance, Vector2.Distance(recipient.Character.ViewTarget.WorldPosition, WorldPosition));
|
||||
}
|
||||
if (ViewTarget != null && ViewTarget != this)
|
||||
{
|
||||
distance = Math.Min(distance, Vector2.Distance(comparePosition, ViewTarget.WorldPosition));
|
||||
}
|
||||
|
||||
float priority = 1.0f - MathUtils.InverseLerp(
|
||||
NetConfig.HighPrioCharacterPositionUpdateDistance,
|
||||
@@ -155,8 +159,6 @@ namespace Barotrauma
|
||||
|
||||
memInput.RemoveAt(memInput.Count - 1);
|
||||
|
||||
TransformCursorPos();
|
||||
|
||||
if ((dequeuedInput == InputNetFlags.None || dequeuedInput == InputNetFlags.FacingLeft) && Math.Abs(AnimController.Collider.LinearVelocity.X) < 0.005f && Math.Abs(AnimController.Collider.LinearVelocity.Y) < 0.2f)
|
||||
{
|
||||
while (memInput.Count > 5 && memInput[memInput.Count - 1].states == dequeuedInput)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
static partial class DebugConsole
|
||||
{
|
||||
private static readonly RateLimiter rateLimiter = new(
|
||||
maxRequests: 10,
|
||||
maxRequests: 50,
|
||||
expiryInSeconds: 5,
|
||||
punishmentRules: new[]
|
||||
{
|
||||
@@ -2526,14 +2526,14 @@ namespace Barotrauma
|
||||
GameMain.Server.CreateEntityEvent(wall);
|
||||
}
|
||||
}));
|
||||
commands.Add(new Command("stallfiletransfers", "stallfiletransfers [seconds]: A debug command that stalls each file transfer packet by the specified duration.", (string[] args) =>
|
||||
commands.Add(new Command("stallfiletransfers", "stallfiletransfers [seconds]: A debug command that makes all file transfers take at least the specified duration.", (string[] args) =>
|
||||
{
|
||||
float seconds = 0.0f;
|
||||
if (args.Length > 0)
|
||||
{
|
||||
float.TryParse(args[0], out seconds);
|
||||
}
|
||||
GameMain.Server.FileSender.StallPacketsTime = seconds;
|
||||
GameMain.Server.FileSender.ForceMinimumFileTransferDuration = seconds;
|
||||
NewMessage("Set file transfer stall time to " + seconds);
|
||||
}));
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
@@ -11,10 +12,10 @@ namespace Barotrauma
|
||||
{
|
||||
internal partial class MedicalClinic
|
||||
{
|
||||
// allow 10 requests per 5 seconds, announce to chat if the limit is reached
|
||||
// allow 20 requests per 5 seconds, announce to chat if the limit is reached
|
||||
private readonly RateLimiter rateLimiter = new(
|
||||
maxRequests: 10,
|
||||
expiryInSeconds: 5,
|
||||
maxRequests: RateLimitMaxRequests,
|
||||
expiryInSeconds: RateLimitExpiry,
|
||||
punishmentRules: (RateLimitAction.OnLimitReached, RateLimitPunishment.Announce));
|
||||
|
||||
private readonly record struct AfflictionSubscriber(Client Subscriber, CharacterInfo Target, DateTimeOffset Expiry);
|
||||
@@ -126,6 +127,17 @@ namespace Barotrauma
|
||||
ImmutableArray<NetAffliction> pendingAfflictions = ImmutableArray<NetAffliction>.Empty;
|
||||
int infoId = 0;
|
||||
|
||||
if (foundInfo is null)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
foreach (CharacterInfo character in GetCrewCharacters())
|
||||
{
|
||||
sb.AppendLine($" - {character.DisplayName} ({character.ID})");
|
||||
}
|
||||
|
||||
DebugConsole.ThrowError($"Could not find the requested crew member with ID {crewMember.CharacterInfoID}.\n{sb}");
|
||||
}
|
||||
|
||||
if (foundInfo is { Character.CharacterHealth: { } health })
|
||||
{
|
||||
pendingAfflictions = GetAllAfflictions(health);
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
lastReceivedTargetForce = null;
|
||||
if (Math.Abs(newTargetForce - targetForce) > 0.01f)
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " set the force of " + item.Name + " to " + (int)(newTargetForce) + " %", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
targetForce = newTargetForce;
|
||||
User = c.Character;
|
||||
}
|
||||
|
||||
@@ -184,6 +184,8 @@ namespace Barotrauma
|
||||
int leftHandSlot = charInv.FindLimbSlot(InvSlotType.LeftHand),
|
||||
rightHandSlot = charInv.FindLimbSlot(InvSlotType.RightHand);
|
||||
|
||||
if (IsSlotIndexOutOfBound(leftHandSlot) || IsSlotIndexOutOfBound(rightHandSlot)) { return; }
|
||||
|
||||
TryPutInOppositeHandSlot(rightHandSlot, leftHandSlot);
|
||||
TryPutInOppositeHandSlot(leftHandSlot, rightHandSlot);
|
||||
|
||||
@@ -198,6 +200,8 @@ namespace Barotrauma
|
||||
TryPutItem(it, otherHandSlot, true, true, character, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSlotIndexOutOfBound(int index) => index < 0 || index >= slots.Length;
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
|
||||
@@ -108,9 +108,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
const int MaxTransferCount = 16;
|
||||
const int MaxTransferCountPerRecipient = 5;
|
||||
|
||||
public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0);
|
||||
|
||||
|
||||
public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
|
||||
public FileTransferDelegate OnStarted;
|
||||
public FileTransferDelegate OnEnded;
|
||||
@@ -121,8 +119,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
private readonly ServerPeer peer;
|
||||
|
||||
public static DateTime StartTime;
|
||||
#if DEBUG
|
||||
public float StallPacketsTime { get; set; }
|
||||
public float ForceMinimumFileTransferDuration { get; set; }
|
||||
#endif
|
||||
|
||||
public IReadOnlyList<FileTransferOut> ActiveTransfers => activeTransfers;
|
||||
@@ -172,6 +171,8 @@ namespace Barotrauma.Networking
|
||||
return null;
|
||||
}
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
|
||||
OnStarted(transfer);
|
||||
GameMain.Server.LastClientListUpdateID++;
|
||||
|
||||
@@ -259,7 +260,18 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < Math.Floor(transfer.PacketsPerUpdate); i++)
|
||||
{
|
||||
long remaining = transfer.Data.Length - transfer.SentOffset;
|
||||
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
|
||||
#if DEBUG
|
||||
bool stalling = false;
|
||||
float elapsedTime = (float)(DateTime.Now - StartTime).TotalSeconds;
|
||||
if (elapsedTime < ForceMinimumFileTransferDuration)
|
||||
{
|
||||
int remainingChunks = (int)Math.Max(remaining / chunkLen, 1);
|
||||
transfer.WaitTimer =
|
||||
Math.Max(transfer.WaitTimer, (ForceMinimumFileTransferDuration - elapsedTime) / remainingChunks);
|
||||
if (remainingChunks <= 1) { break; }
|
||||
}
|
||||
#endif
|
||||
int sendByteCount = remaining > chunkLen ? chunkLen : (int)remaining;
|
||||
|
||||
message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
@@ -293,11 +305,10 @@ namespace Barotrauma.Networking
|
||||
//this gets reset when packet loss or disorder sets in
|
||||
transfer.PacketsPerUpdate = Math.Min(FileTransferOut.MaxPacketsPerUpdate,
|
||||
transfer.PacketsPerUpdate + 0.05f);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
transfer.WaitTimer = Math.Max(transfer.WaitTimer, StallPacketsTime);
|
||||
if (stalling) { break; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
@@ -330,7 +341,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
byte transferId = inc.ReadByte();
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
|
||||
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
|
||||
if (matchingTransfer != null) { CancelTransfer(matchingTransfer); }
|
||||
return;
|
||||
}
|
||||
else if (messageType == FileTransferMessageType.Data)
|
||||
@@ -359,6 +370,7 @@ namespace Barotrauma.Networking
|
||||
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
|
||||
{
|
||||
matchingTransfer.Status = FileTransferStatus.Finished;
|
||||
DebugConsole.Log($"Finished sending file \"{matchingTransfer.FilePath}\" to \"{client.Name}\". Took {DateTime.Now - StartTime}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -21,15 +21,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
public override Voting Voting { get; }
|
||||
|
||||
private string serverName;
|
||||
public string ServerName
|
||||
{
|
||||
get { return serverName; }
|
||||
get { return ServerSettings.ServerName; }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) { return; }
|
||||
|
||||
serverName = value;
|
||||
ServerSettings.ServerName = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,8 +131,6 @@ namespace Barotrauma.Networking
|
||||
name = name.Substring(0, NetConfig.ServerNameMaxLength);
|
||||
}
|
||||
|
||||
this.serverName = name;
|
||||
|
||||
LastClientListUpdateID = 0;
|
||||
|
||||
ServerSettings = new ServerSettings(this, name, port, queryPort, maxPlayers, isPublic, attemptUPnP);
|
||||
@@ -775,9 +771,12 @@ namespace Barotrauma.Networking
|
||||
string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName);
|
||||
if (CampaignMode.AllowedToManageCampaign(connectedClient, ClientPermissions.ManageRound))
|
||||
{
|
||||
ServerSettings.CampaignSettings = settings;
|
||||
ServerSettings.SaveSettings();
|
||||
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
|
||||
using (dosProtection.Pause(connectedClient))
|
||||
{
|
||||
ServerSettings.CampaignSettings = settings;
|
||||
ServerSettings.SaveSettings();
|
||||
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -790,8 +789,11 @@ namespace Barotrauma.Networking
|
||||
break;
|
||||
}
|
||||
if (CampaignMode.AllowedToManageCampaign(connectedClient, ClientPermissions.ManageRound))
|
||||
{
|
||||
MultiPlayerCampaign.LoadCampaign(saveName);
|
||||
{
|
||||
using (dosProtection.Pause(connectedClient))
|
||||
{
|
||||
MultiPlayerCampaign.LoadCampaign(saveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1661,38 +1663,54 @@ namespace Barotrauma.Networking
|
||||
//characters or items spawned mid-round don't necessarily exist at the client's end yet
|
||||
if (!c.NeedsMidRoundSync)
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
Character clientCharacter = c.Character;
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
if (!character.Enabled) { continue; }
|
||||
if (!otherCharacter.Enabled) { continue; }
|
||||
if (c.SpectatePos == null)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(character.WorldPosition, c.Character.WorldPosition);
|
||||
if (c.Character.ViewTarget != null)
|
||||
//not spectating ->
|
||||
// check if the client's character, or the entity they're viewing,
|
||||
// is close enough to the other character or the entity the other character is viewing
|
||||
float distSqr = GetShortestDistance(clientCharacter.WorldPosition, otherCharacter);
|
||||
if (clientCharacter.ViewTarget != null && clientCharacter.ViewTarget != clientCharacter)
|
||||
{
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(character.WorldPosition, c.Character.ViewTarget.WorldPosition));
|
||||
distSqr = Math.Min(distSqr, GetShortestDistance(clientCharacter.ViewTarget.WorldPosition, otherCharacter));
|
||||
}
|
||||
if (distSqr >= MathUtils.Pow2(character.Params.DisableDistance)) { continue; }
|
||||
if (distSqr >= MathUtils.Pow2(otherCharacter.Params.DisableDistance)) { continue; }
|
||||
}
|
||||
else
|
||||
else if (otherCharacter != clientCharacter)
|
||||
{
|
||||
if (character != c.Character && Vector2.DistanceSquared(character.WorldPosition, c.SpectatePos.Value) >= MathUtils.Pow2(character.Params.DisableDistance))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//spectating ->
|
||||
// check if the position the client is viewing
|
||||
// is close enough to the other character or the entity the other character is viewing
|
||||
if (GetShortestDistance(c.SpectatePos.Value, otherCharacter) >= MathUtils.Pow2(otherCharacter.Params.DisableDistance)) { continue; }
|
||||
}
|
||||
|
||||
float updateInterval = character.GetPositionUpdateInterval(c);
|
||||
c.PositionUpdateLastSent.TryGetValue(character, out float lastSent);
|
||||
static float GetShortestDistance(Vector2 viewPos, Character targetCharacter)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(viewPos, targetCharacter.WorldPosition);
|
||||
if (targetCharacter.ViewTarget != null && targetCharacter.ViewTarget != targetCharacter)
|
||||
{
|
||||
//if the character is viewing something (far-away turret?),
|
||||
//we might want to send updates about it to the spectating client even though they're far away from the actual character
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(viewPos, targetCharacter.ViewTarget.WorldPosition));
|
||||
}
|
||||
return distSqr;
|
||||
}
|
||||
|
||||
float updateInterval = otherCharacter.GetPositionUpdateInterval(c);
|
||||
c.PositionUpdateLastSent.TryGetValue(otherCharacter, out float lastSent);
|
||||
if (lastSent > NetTime.Now)
|
||||
{
|
||||
//sent in the future -> can't be right, remove
|
||||
c.PositionUpdateLastSent.Remove(character);
|
||||
c.PositionUpdateLastSent.Remove(otherCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastSent > NetTime.Now - updateInterval) { continue; }
|
||||
}
|
||||
if (!c.PendingPositionUpdates.Contains(character)) { c.PendingPositionUpdates.Enqueue(character); }
|
||||
if (!c.PendingPositionUpdates.Contains(otherCharacter)) { c.PendingPositionUpdates.Enqueue(otherCharacter); }
|
||||
}
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
@@ -2594,16 +2612,20 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!CampaignMode.AllowedToManageCampaign(client, ClientPermissions.ManageRound)) { return false; }
|
||||
|
||||
const int MaxSaves = 255;
|
||||
var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false);
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteByte((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||
msg.WriteByte((byte)Math.Min(saveInfos.Count, MaxSaves));
|
||||
for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++)
|
||||
using (dosProtection.Pause(client))
|
||||
{
|
||||
msg.WriteNetSerializableStruct(saveInfos[i]);
|
||||
const int MaxSaves = 255;
|
||||
var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false);
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteByte((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||
msg.WriteByte((byte)Math.Min(saveInfos.Count, MaxSaves));
|
||||
for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++)
|
||||
{
|
||||
msg.WriteNetSerializableStruct(saveInfos[i]);
|
||||
}
|
||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3084,7 +3106,7 @@ namespace Barotrauma.Networking
|
||||
default:
|
||||
if (command != "")
|
||||
{
|
||||
if (command.ToLower() == serverName.ToLower())
|
||||
if (command.ToLower() == ServerName.ToLower())
|
||||
{
|
||||
//a private message to the host
|
||||
if (OwnerConnection != null)
|
||||
@@ -3139,7 +3161,7 @@ namespace Barotrauma.Networking
|
||||
//msg sent by the server
|
||||
if (senderCharacter == null)
|
||||
{
|
||||
senderName = serverName;
|
||||
senderName = ServerName;
|
||||
}
|
||||
else //msg sent by an AI character
|
||||
{
|
||||
@@ -3173,7 +3195,7 @@ namespace Barotrauma.Networking
|
||||
//msg sent by the server
|
||||
if (senderCharacter == null)
|
||||
{
|
||||
senderName = serverName;
|
||||
senderName = ServerName;
|
||||
}
|
||||
else //sent by an AI character, not allowed when the game is not running
|
||||
{
|
||||
@@ -3396,33 +3418,35 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchSubmarine()
|
||||
public bool TrySwitchSubmarine()
|
||||
{
|
||||
if (Voting.ActiveVote is not Voting.SubmarineVote subVote) { return; }
|
||||
if (Voting.ActiveVote is not Voting.SubmarineVote subVote) { return false; }
|
||||
|
||||
SubmarineInfo targetSubmarine = subVote.Sub;
|
||||
VoteType voteType = Voting.ActiveVote.VoteType;
|
||||
Client starter = Voting.ActiveVote.VoteStarter;
|
||||
|
||||
bool purchaseFailed = false;
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
case VoteType.PurchaseSub:
|
||||
// Pay for submarine
|
||||
GameMain.GameSession.PurchaseSubmarine(targetSubmarine, starter);
|
||||
purchaseFailed = !GameMain.GameSession.TryPurchaseSubmarine(targetSubmarine, starter);
|
||||
break;
|
||||
case VoteType.SwitchSub:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (voteType != VoteType.PurchaseSub)
|
||||
if (voteType != VoteType.PurchaseSub && !purchaseFailed)
|
||||
{
|
||||
GameMain.GameSession.SwitchSubmarine(targetSubmarine, subVote.TransferItems, starter);
|
||||
}
|
||||
|
||||
Voting.StopSubmarineVote(true);
|
||||
Voting.StopSubmarineVote(passed: !purchaseFailed);
|
||||
return !purchaseFailed;
|
||||
}
|
||||
|
||||
public void UpdateClientPermissions(Client client)
|
||||
|
||||
+19
-4
@@ -24,11 +24,28 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
protected readonly Callbacks callbacks;
|
||||
private readonly ImmutableArray<ContentPackage> contentPackages;
|
||||
|
||||
protected ServerPeer(Callbacks callbacks)
|
||||
{
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
List<ContentPackage> contentPackageList = new List<ContentPackage>();
|
||||
foreach (var cp in ContentPackageManager.EnabledPackages.All)
|
||||
{
|
||||
if (!cp.Files.Any()) { continue; }
|
||||
if (!cp.HasMultiplayerSyncedContent && !cp.Files.All(f => f is SubmarineFile)) { continue; }
|
||||
if (cp.UgcId.TryUnwrap(out var id1) &&
|
||||
contentPackageList.FirstOrDefault(cp => cp.UgcId.TryUnwrap(out var id2) && id1.Equals(id2)) is ContentPackage existingPackage)
|
||||
{
|
||||
//there can be multiple enabled mods with the same UgcId if the player has e.g. created a local copy of a workshop mod
|
||||
DebugConsole.AddWarning($"The content package \"{existingPackage.Name}\" ({existingPackage.Path}) has the same id as \"{cp.Name}\" ({cp.Path}). Ignoring the latter package.");
|
||||
continue;
|
||||
}
|
||||
contentPackageList.Add(cp);
|
||||
}
|
||||
contentPackages = contentPackageList.ToImmutableArray();
|
||||
}
|
||||
|
||||
public abstract void InitializeSteamServerCallbacks();
|
||||
|
||||
@@ -250,9 +267,7 @@ namespace Barotrauma.Networking
|
||||
structToSend = new ServerPeerContentPackageOrderPacket
|
||||
{
|
||||
ServerName = GameMain.Server.ServerName,
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All
|
||||
.Where(cp => cp.Files.Any())
|
||||
.Where(cp => cp.HasMultiplayerSyncedContent || cp.Files.All(f => f is SubmarineFile))
|
||||
ContentPackages = contentPackages
|
||||
.Select(contentPackage => new ServerContentPackage(contentPackage, timeNow))
|
||||
.ToImmutableArray()
|
||||
};
|
||||
|
||||
@@ -370,6 +370,8 @@ namespace Barotrauma.Networking
|
||||
"192-255",
|
||||
"384-591",
|
||||
"1024-1279",
|
||||
"4352-4607", //Hangul Jamo
|
||||
"44032-55215", //Hangul Syllables
|
||||
"19968-21327","21329-40959","13312-19903","131072-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (passed)
|
||||
{
|
||||
GameMain.Server?.SwitchSubmarine();
|
||||
if (GameMain.Server != null && !GameMain.Server.TrySwitchSubmarine())
|
||||
{
|
||||
passed = false;
|
||||
State = VoteState.Failed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -20,19 +20,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public delegate void MessageSender(string message);
|
||||
public void Greet(GameServer server, string codeWords, string codeResponse, MessageSender messageSender)
|
||||
{
|
||||
string greetingMessage = TextManager.FormatServerMessage(Mission.StartText,
|
||||
("[codewords]", codeWords),
|
||||
("[coderesponse]", codeResponse));
|
||||
messageSender(greetingMessage);
|
||||
Client traitorClient = server.ConnectedClients.Find(c => c.Character == Character);
|
||||
Client ownerClient = server.ConnectedClients.Find(c => c.Connection == server.OwnerConnection);
|
||||
if (traitorClient != ownerClient && ownerClient != null && ownerClient.Character == null)
|
||||
{
|
||||
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText.Value, Mission.Identifier, TraitorMessageType.ServerMessageBox);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendChatMessage(string serverText, Identifier iconIdentifier)
|
||||
{
|
||||
|
||||
@@ -224,10 +224,6 @@ namespace Barotrauma
|
||||
{
|
||||
pendingMessages.Add(traitor, new List<string>());
|
||||
}
|
||||
foreach (var traitor in Traitors.Values)
|
||||
{
|
||||
traitor.Greet(server, CodeWords, CodeResponse, message => pendingMessages[traitor].Add(message));
|
||||
}
|
||||
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message, Identifier)));
|
||||
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message, Identifier)));
|
||||
|
||||
|
||||
@@ -71,6 +71,15 @@ namespace Barotrauma
|
||||
StrikesResetInterval = 60,
|
||||
StrikeThreshold = 6;
|
||||
|
||||
private const int MinPacketLimitMultipler = 1;
|
||||
|
||||
private static int GetMaxPacketLimit(ServerSettings settings)
|
||||
=> (int)MathF.Ceiling(
|
||||
settings.MaxPacketAmount *
|
||||
MathF.Max(
|
||||
settings.TickRate / (float)ServerSettings.DefaultTickRate,
|
||||
MinPacketLimitMultipler)); // Prevent the rate limit multiplier from being less than 1.
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server receives a packet to start logging how much time it takes to process.
|
||||
/// </summary>
|
||||
@@ -122,11 +131,7 @@ namespace Barotrauma
|
||||
|
||||
private void StartFor(Client client)
|
||||
{
|
||||
if (!clients.ContainsKey(client))
|
||||
{
|
||||
clients.Add(client, new OffenseData());
|
||||
}
|
||||
|
||||
clients.TryAdd(client, new OffenseData());
|
||||
clients[client].Stopwatch.Start();
|
||||
}
|
||||
|
||||
@@ -149,7 +154,7 @@ namespace Barotrauma
|
||||
if (GameMain.Server?.ServerSettings is not { } settings) { return; }
|
||||
|
||||
// client is sending too many packets, kick them
|
||||
if (data.PacketCount > settings.MaxPacketAmount && settings.MaxPacketAmount > ServerSettings.PacketLimitMin)
|
||||
if (data.PacketCount > GetMaxPacketLimit(settings) && settings.MaxPacketAmount > ServerSettings.PacketLimitMin)
|
||||
{
|
||||
AttemptKickClient(client, TextManager.Get("PacketLimitKicked"));
|
||||
clients.Remove(client);
|
||||
@@ -216,7 +221,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Server?.ServerSettings is { MaxPacketAmount: > ServerSettings.PacketLimitMin } settings)
|
||||
{
|
||||
if (data.PacketCount > settings.MaxPacketAmount * 0.9f)
|
||||
if (data.PacketCount > GetMaxPacketLimit(settings) * 0.9f)
|
||||
{
|
||||
GameServer.Log($"{NetworkMember.ClientLogName(client)} is sending a lot of packets and almost got kicked! ({data.PacketCount}).", ServerLog.MessageType.DoSProtection);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.1.4.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||
|
||||
@@ -95,10 +95,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (visibleHulls == null)
|
||||
{
|
||||
visibleHulls = Character.GetVisibleHulls();
|
||||
}
|
||||
visibleHulls ??= Character.GetVisibleHulls();
|
||||
return visibleHulls;
|
||||
}
|
||||
private set
|
||||
@@ -107,12 +104,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasValidPath(bool requireNonDirty = false, bool requireUnfinished = true) =>
|
||||
steeringManager is IndoorsSteeringManager pathSteering &&
|
||||
pathSteering.CurrentPath != null &&
|
||||
(!requireUnfinished || !pathSteering.CurrentPath.Finished) &&
|
||||
!pathSteering.CurrentPath.Unreachable &&
|
||||
(!requireNonDirty || !pathSteering.IsPathDirty);
|
||||
/// <summary>
|
||||
/// Is the current path valid, using the provided parameters.
|
||||
/// </summary>
|
||||
/// <param name="requireNonDirty"></param>
|
||||
/// <param name="requireUnfinished"></param>
|
||||
/// <param name="nodePredicate"></param>
|
||||
/// <returns>When <paramref name="nodePredicate"/> is defined, returns false if any of the nodes fails to match the predicate.</returns>
|
||||
public bool HasValidPath(bool requireNonDirty = true, bool requireUnfinished = true, Func<WayPoint, bool> nodePredicate = null)
|
||||
{
|
||||
if (SteeringManager is not IndoorsSteeringManager pathSteering) { return false; }
|
||||
if (pathSteering.CurrentPath == null) { return false; }
|
||||
if (pathSteering.CurrentPath.Unreachable) { return false; }
|
||||
if (requireUnfinished && pathSteering.CurrentPath.Finished) { return false; }
|
||||
if (requireNonDirty && pathSteering.IsPathDirty) { return false; }
|
||||
if (nodePredicate != null)
|
||||
{
|
||||
return pathSteering.CurrentPath.Nodes.All(n => nodePredicate(n));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsCurrentPathNullOrUnreachable => IsCurrentPathUnreachable || steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath == null;
|
||||
public bool IsCurrentPathUnreachable => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable;
|
||||
@@ -251,7 +262,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private readonly HashSet<Item> unequippedItems = new HashSet<Item>();
|
||||
public bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear = false, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
|
||||
public bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear = false, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false, IEnumerable<Identifier> targetTags = null)
|
||||
{
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
@@ -265,23 +276,28 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
var holdable = item.GetComponent<Holdable>();
|
||||
if (holdable != null)
|
||||
{
|
||||
pickable = holdable;
|
||||
}
|
||||
// Not allowed to wear -> don't use the Wearable component even when it's found.
|
||||
pickable = item.GetComponent<Holdable>();
|
||||
}
|
||||
if (item.ParentInventory is ItemInventory itemInventory)
|
||||
{
|
||||
if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; }
|
||||
}
|
||||
if (equip)
|
||||
if (equip && pickable != null)
|
||||
{
|
||||
int targetSlot = -1;
|
||||
//check if all the slots required by the item are free
|
||||
foreach (InvSlotType slots in pickable.AllowedSlots)
|
||||
{
|
||||
if (slots.HasFlag(InvSlotType.Any)) { continue; }
|
||||
if (!wear)
|
||||
{
|
||||
if (slots != InvSlotType.RightHand && slots != InvSlotType.LeftHand && slots != (InvSlotType.RightHand | InvSlotType.LeftHand))
|
||||
{
|
||||
// Don't allow other than hand slots if not allowed to wear.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < targetInventory.Capacity; i++)
|
||||
{
|
||||
if (targetInventory is CharacterInventory characterInventory)
|
||||
@@ -294,7 +310,7 @@ namespace Barotrauma
|
||||
var otherItem = targetInventory.GetItemAt(i);
|
||||
if (otherItem == null) { continue; }
|
||||
//try to move the existing item to LimbSlot.Any and continue if successful
|
||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.anySlot))
|
||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.AnySlot))
|
||||
{
|
||||
if (storeUnequipped && targetInventory.Owner == Character)
|
||||
{
|
||||
@@ -304,6 +320,11 @@ namespace Barotrauma
|
||||
}
|
||||
if (dropOtherIfCannotMove)
|
||||
{
|
||||
if (otherItem.Prefab.Identifier == item.Prefab.Identifier || otherItem.HasIdentifierOrTags(targetTags))
|
||||
{
|
||||
// Shouldn't try dropping identical items, because that causes infinite looping when trying to get multiple items of the same type and if can't fit them all in the inventory.
|
||||
return false;
|
||||
}
|
||||
//if everything else fails, simply drop the existing item
|
||||
otherItem.Drop(Character);
|
||||
}
|
||||
@@ -314,7 +335,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return targetInventory.TryPutItem(item, Character, CharacterInventory.anySlot);
|
||||
return targetInventory.TryPutItem(item, Character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +360,7 @@ namespace Barotrauma
|
||||
if (avoidDroppingInSea && !character.IsInFriendlySub)
|
||||
{
|
||||
// If we are not inside a friendly sub (= same team), try to put the item in the inventory instead dropping it.
|
||||
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
|
||||
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.AnySlot))
|
||||
{
|
||||
if (unequipMax.HasValue && ++removed >= unequipMax) { return; }
|
||||
continue;
|
||||
@@ -401,14 +422,9 @@ namespace Barotrauma
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door != null)
|
||||
{
|
||||
if (!door.CanBeTraversed)
|
||||
if (!pathSteering.CanAccessDoor(door))
|
||||
{
|
||||
if (!door.HasAccess(Character))
|
||||
{
|
||||
if (!canAttackDoors) { continue; }
|
||||
// Treat doors that don't have access to like they were farther, because it will take time to break them.
|
||||
multiplier = 5;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -449,9 +465,10 @@ namespace Barotrauma
|
||||
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
|
||||
float sqrDist = diff.LengthSquared();
|
||||
bool isClose = sqrDist < MathUtils.Pow2(100);
|
||||
if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathNullOrUnreachable || IsCurrentPathFinished)
|
||||
if (Character.CurrentHull == null || (isClose && !isClosedDoor) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
|
||||
{
|
||||
// Very close to the target, outside, or at the end of the path -> try to steer through the gap
|
||||
Character.ReleaseSecondaryItem();
|
||||
SteeringManager.Reset();
|
||||
pathSteering?.ResetPath();
|
||||
Vector2 dir = Vector2.Normalize(diff);
|
||||
|
||||
@@ -369,7 +369,13 @@ namespace Barotrauma
|
||||
}
|
||||
else if (targetCharacter.AIController is EnemyAIController enemy)
|
||||
{
|
||||
if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
if (enemy.PetBehavior != null && (PetBehavior != null || AIParams.HasTag("pet")))
|
||||
{
|
||||
// Pets see other pets as pets by default.
|
||||
// Monsters see them only as pet only when they have a matching ai target. Otherwise they use the other tags, specified below.
|
||||
targetingTag = "pet";
|
||||
}
|
||||
else if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
{
|
||||
targetingTag = "husk";
|
||||
}
|
||||
@@ -480,7 +486,7 @@ namespace Barotrauma
|
||||
if (SelectedAiTarget?.Entity != null || EscapeTarget != null)
|
||||
{
|
||||
Entity t = SelectedAiTarget?.Entity ?? EscapeTarget;
|
||||
float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath(requireNonDirty: true) ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X;
|
||||
float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath() ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X;
|
||||
Character.AnimController.TargetDir = Character.WorldPosition.X < referencePos ? Direction.Right : Direction.Left;
|
||||
}
|
||||
else
|
||||
@@ -695,6 +701,9 @@ namespace Barotrauma
|
||||
// Can't target characters of same species/group because that would make us hostile to all friendly characters in the same species/group.
|
||||
if (Character.IsSameSpeciesOrGroup(c)) { return false; }
|
||||
if (targetCharacter.IsSameSpeciesOrGroup(c)) { return false; }
|
||||
//don't try to attack targets in a sub that belongs to a different team
|
||||
//(for example, targets in an outpost if we're in the main sub)
|
||||
if (c.Submarine?.TeamID != Character.Submarine?.TeamID) { return false; }
|
||||
if (c.IsPlayer || Character.IsOnFriendlyTeam(c))
|
||||
{
|
||||
return a.Damage >= selectedTargetingParams.Threshold;
|
||||
@@ -894,7 +903,7 @@ namespace Barotrauma
|
||||
_previousAttackLimb?.attack is Attack previousAttack && (previousAttack.AfterAttack != AIBehaviorAfterAttack.FallBack || previousAttack.CoolDownTimer <= 0)))
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
var memory = GetTargetMemory(target);
|
||||
if (memory != null)
|
||||
{
|
||||
var location = memory.Location;
|
||||
@@ -981,7 +990,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.SetPath(path);
|
||||
PathSteering.SetPath(patrolTarget.SimPosition, path);
|
||||
patrolTimerMargin = 0;
|
||||
newPatrolTargetTimer = newPatrolTargetIntervalMax * Rand.Range(0.5f, 1.5f);
|
||||
searchingNewHull = false;
|
||||
@@ -1088,13 +1097,13 @@ namespace Barotrauma
|
||||
Character owner = GetOwner(item);
|
||||
if (owner != null)
|
||||
{
|
||||
if (Character.IsFriendly(owner))
|
||||
if (Character.IsFriendly(owner) || owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
{
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else if (!owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
else
|
||||
{
|
||||
SelectedAiTarget = owner.AiTarget;
|
||||
}
|
||||
@@ -2186,7 +2195,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true);
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true, keepAlive: true);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
@@ -2531,8 +2540,10 @@ namespace Barotrauma
|
||||
if (Math.Abs(limbDiff.X) < itemBodyExtent &&
|
||||
Math.Abs(limbDiff.Y) < Character.AnimController.Collider.GetMaxExtent() + Character.AnimController.ColliderHeightFromFloor)
|
||||
{
|
||||
Vector2 velocity = limbDiff;
|
||||
if (limbDiff.LengthSquared() > 0.01f) { velocity = Vector2.Normalize(velocity); }
|
||||
item.body.LinearVelocity *= 0.9f;
|
||||
item.body.LinearVelocity -= limbDiff * 0.25f;
|
||||
item.body.LinearVelocity -= velocity * 0.25f;
|
||||
bool wasBroken = item.Condition <= 0.0f;
|
||||
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
|
||||
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
@@ -2924,7 +2935,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetParams.State == AIState.Eat && Character.Params.Health.HealthRegenerationWhenEating > 0)
|
||||
//no need to eat if the character is already in full health (except if it's a pet - pets actually need to eat to stay alive, not just to regain health)
|
||||
if (targetParams.State == AIState.Eat && Character.Params.Health.HealthRegenerationWhenEating > 0 && !Character.IsPet)
|
||||
{
|
||||
valueModifier *= MathHelper.Lerp(1f, 0.1f, Character.HealthPercentage / 100f);
|
||||
}
|
||||
@@ -3021,7 +3033,7 @@ namespace Barotrauma
|
||||
//if the target is very close, the distance doesn't make much difference
|
||||
// -> just ignore the distance and target whatever has the highest priority
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true, keepAlive: SelectedAiTarget != aiTarget);
|
||||
if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null)
|
||||
{
|
||||
float diff = Math.Abs(toTarget.Y) - Character.CurrentHull.Size.Y;
|
||||
@@ -3090,12 +3102,20 @@ namespace Barotrauma
|
||||
if (aiTarget.Entity is Item i)
|
||||
{
|
||||
Character owner = GetOwner(i);
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (owner == Character) { continue; }
|
||||
if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
|
||||
if (owner != null)
|
||||
{
|
||||
continue;
|
||||
if (owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)) { continue; }
|
||||
if (Character.IsFriendly(owner))
|
||||
{
|
||||
// Don't target items that we own. This is a rare case, and almost entirely related to Humanhusks (in the vanilla game).
|
||||
continue;
|
||||
}
|
||||
if (owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
{
|
||||
// ignore if owner is tagged to be explicitly ignored (Feign Death)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
@@ -3418,7 +3438,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
private AITargetMemory GetTargetMemory(AITarget target, bool addIfNotFound)
|
||||
private AITargetMemory GetTargetMemory(AITarget target, bool addIfNotFound = false, bool keepAlive = false)
|
||||
{
|
||||
if (!targetMemories.TryGetValue(target, out AITargetMemory memory))
|
||||
{
|
||||
@@ -3428,9 +3448,8 @@ namespace Barotrauma
|
||||
targetMemories.Add(target, memory);
|
||||
}
|
||||
}
|
||||
if (addIfNotFound)
|
||||
if (keepAlive)
|
||||
{
|
||||
// Keep the memory alive.
|
||||
memory.Priority = Math.Max(memory.Priority, minPriority);
|
||||
}
|
||||
return memory;
|
||||
@@ -3446,7 +3465,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (CanPerceive(_selectedAiTarget, checkVisibility: false))
|
||||
{
|
||||
var memory = GetTargetMemory(_selectedAiTarget, false);
|
||||
var memory = GetTargetMemory(_selectedAiTarget);
|
||||
if (memory != null)
|
||||
{
|
||||
memory.Location = _selectedAiTarget.WorldPosition;
|
||||
@@ -3643,7 +3662,11 @@ namespace Barotrauma
|
||||
{
|
||||
isStateChanged = true;
|
||||
SetStateResetTimer();
|
||||
ChangeParams(target.SpeciesName, state, priority, ignoreAttacksIfNotInSameSub: !target.IsHuman);
|
||||
if (!Character.IsPet || !target.IsHuman)
|
||||
{
|
||||
//don't turn pets hostile to all humans when attacked by one
|
||||
ChangeParams(target.SpeciesName, state, priority, ignoreAttacksIfNotInSameSub: !target.IsHuman);
|
||||
}
|
||||
if (target.IsHuman)
|
||||
{
|
||||
priority = GetTargetParams("human")?.Priority;
|
||||
@@ -3934,7 +3957,7 @@ namespace Barotrauma
|
||||
{
|
||||
SteerAwayFromTheEnemy();
|
||||
}
|
||||
else if (canAttackDoors && HasValidPath(requireNonDirty: true, requireUnfinished: true))
|
||||
else if (canAttackDoors && HasValidPath())
|
||||
{
|
||||
var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.CanBeTraversed && !door.HasAccess(Character))
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class HumanAIController : AIController
|
||||
{
|
||||
public static bool debugai;
|
||||
public static bool DebugAI;
|
||||
public static bool DisableCrewAI;
|
||||
|
||||
private readonly AIObjectiveManager objectiveManager;
|
||||
@@ -42,6 +42,8 @@ namespace Barotrauma
|
||||
public readonly HashSet<Hull> UnsafeHulls = new HashSet<Hull>();
|
||||
public readonly List<Item> IgnoredItems = new List<Item>();
|
||||
|
||||
private readonly HashSet<Hull> dirtyHullSafetyCalculations = new HashSet<Hull>();
|
||||
|
||||
private float respondToAttackTimer;
|
||||
private const float RespondToAttackInterval = 1.0f;
|
||||
private bool wasConscious;
|
||||
@@ -55,6 +57,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly float obstacleRaycastIntervalShort = 1, obstacleRaycastIntervalLong = 5;
|
||||
private float obstacleRaycastTimer;
|
||||
private bool isBlocked;
|
||||
|
||||
private readonly float enemyCheckInterval = 0.2f;
|
||||
private readonly float enemySpotDistanceOutside = 800;
|
||||
@@ -92,7 +95,10 @@ namespace Barotrauma
|
||||
|
||||
private readonly SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public bool UseIndoorSteeringOutside { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Waypoints that are not linked to a sub (e.g. main path).
|
||||
/// </summary>
|
||||
public bool UseOutsideWaypoints { get; private set; }
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
@@ -225,14 +231,15 @@ namespace Barotrauma
|
||||
IgnoredItems.Clear();
|
||||
}
|
||||
|
||||
bool IsCloseEnoughToTarget(float threshold, bool useTargetSub = true)
|
||||
// Note: returns false when useTargetSub is 'true' and the target is outside (targetSub is 'null')
|
||||
bool IsCloseEnoughToTarget(float threshold, bool targetSub = true)
|
||||
{
|
||||
Entity target = SelectedAiTarget?.Entity;
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (useTargetSub)
|
||||
if (targetSub)
|
||||
{
|
||||
if (target.Submarine is Submarine sub)
|
||||
{
|
||||
@@ -244,62 +251,71 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow(threshold, 2);
|
||||
return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow2(threshold);
|
||||
}
|
||||
|
||||
bool hasValidPath = HasValidPath();
|
||||
|
||||
if (Character.Submarine == null)
|
||||
bool isOutside = Character.Submarine == null;
|
||||
if (isOutside)
|
||||
{
|
||||
obstacleRaycastTimer -= deltaTime;
|
||||
if (obstacleRaycastTimer <= 0)
|
||||
{
|
||||
bool hasValidPath = HasValidPath();
|
||||
isBlocked = false;
|
||||
UseOutsideWaypoints = false;
|
||||
obstacleRaycastTimer = obstacleRaycastIntervalLong;
|
||||
if (SelectedAiTarget?.Entity == null || SelectedAiTarget.Entity is ISpatialEntity target && target.Submarine == null || !IsCloseEnoughToTarget(2000, useTargetSub: false))
|
||||
ISpatialEntity spatialTarget = SelectedAiTarget?.Entity ?? ObjectiveManager.GetLastActiveObjective<AIObjectiveGoTo>()?.Target;
|
||||
if (spatialTarget != null && (spatialTarget.Submarine == null || !IsCloseEnoughToTarget(2000, targetSub: false)))
|
||||
{
|
||||
// If the target is behind a level wall, switch to the pathing to get around the obstacles.
|
||||
ISpatialEntity spatialTarget = SelectedAiTarget?.Entity;
|
||||
if (spatialTarget == null)
|
||||
IEnumerable<FarseerPhysics.Dynamics.Body> ignoredBodies = null;
|
||||
Vector2 rayEnd = spatialTarget.SimPosition;
|
||||
Submarine targetSub = spatialTarget.Submarine;
|
||||
if (targetSub != null)
|
||||
{
|
||||
var gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
|
||||
spatialTarget = gotoObjective?.Target;
|
||||
rayEnd += targetSub.SimPosition;
|
||||
ignoredBodies = targetSub.PhysicsBody.FarseerBody.ToEnumerable();
|
||||
}
|
||||
if (spatialTarget == null)
|
||||
var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
||||
isBlocked = obstacle != null;
|
||||
// Don't use outside waypoints when blocked by a sub, because we should use the waypoints linked to the sub instead.
|
||||
UseOutsideWaypoints = isBlocked && (obstacle.UserData is not Submarine sub || sub.Info.IsRuin);
|
||||
bool resetPath = false;
|
||||
if (UseOutsideWaypoints)
|
||||
{
|
||||
UseIndoorSteeringOutside = false;
|
||||
bool isUsingInsideWaypoints = hasValidPath && HasValidPath(nodePredicate: n => n.Submarine != null || n.Ruin != null);
|
||||
if (isUsingInsideWaypoints)
|
||||
{
|
||||
resetPath = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<FarseerPhysics.Dynamics.Body> ignoredBodies = null;
|
||||
Vector2 rayEnd = spatialTarget.SimPosition;
|
||||
Submarine targetSub = spatialTarget.Submarine;
|
||||
if (targetSub != null)
|
||||
bool isUsingOutsideWaypoints = hasValidPath && HasValidPath(nodePredicate: n => n.Submarine == null && n.Ruin == null);
|
||||
if (isUsingOutsideWaypoints)
|
||||
{
|
||||
rayEnd += targetSub.SimPosition;
|
||||
ignoredBodies = targetSub.PhysicsBody.FarseerBody.ToEnumerable();
|
||||
resetPath = true;
|
||||
}
|
||||
var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
||||
UseIndoorSteeringOutside = obstacle != null;
|
||||
}
|
||||
if (resetPath)
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (hasValidPath)
|
||||
{
|
||||
UseIndoorSteeringOutside = false;
|
||||
if (hasValidPath)
|
||||
obstacleRaycastTimer = obstacleRaycastIntervalShort;
|
||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||
{
|
||||
obstacleRaycastTimer = obstacleRaycastIntervalShort;
|
||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||
if (connectedSub == Submarine.MainSub) { continue; }
|
||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||
{
|
||||
if (connectedSub == Submarine.MainSub) { continue; }
|
||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||
{
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
break;
|
||||
}
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,10 +323,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
UseIndoorSteeringOutside = false;
|
||||
UseOutsideWaypoints = false;
|
||||
isBlocked = false;
|
||||
}
|
||||
|
||||
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !Character.IsOnFriendlyTeam(Character.Submarine.TeamID))
|
||||
if (isOutside || Character.IsOnPlayerTeam && !Character.IsEscorted && !Character.IsOnFriendlyTeam(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
|
||||
@@ -352,12 +369,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer))
|
||||
bool useInsideSteering = !isOutside || isBlocked || HasValidPath() || IsCloseEnoughToTarget(steeringBuffer);
|
||||
if (useInsideSteering)
|
||||
{
|
||||
if (steeringManager != insideSteering)
|
||||
{
|
||||
insideSteering.Reset();
|
||||
PathSteering.ResetPath();
|
||||
steeringManager = insideSteering;
|
||||
}
|
||||
if (IsCloseEnoughToTarget(maxSteeringBuffer))
|
||||
@@ -420,6 +438,7 @@ namespace Barotrauma
|
||||
foreach (Hull h in VisibleHulls)
|
||||
{
|
||||
PropagateHullSafety(Character, h);
|
||||
dirtyHullSafetyCalculations.Remove(h);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -427,18 +446,33 @@ namespace Barotrauma
|
||||
foreach (Hull h in VisibleHulls)
|
||||
{
|
||||
RefreshHullSafety(h);
|
||||
dirtyHullSafetyCalculations.Remove(h);
|
||||
}
|
||||
}
|
||||
foreach (Hull h in dirtyHullSafetyCalculations)
|
||||
{
|
||||
RefreshHullSafety(h);
|
||||
}
|
||||
}
|
||||
dirtyHullSafetyCalculations.Clear();
|
||||
if (reportProblemsTimer <= 0.0f)
|
||||
{
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.Submarine.TeamID == Character.OriginalTeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
{
|
||||
ReportProblems();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allows bots to heal targets autonomously while swimming outside of the sub.
|
||||
if (AIObjectiveRescueAll.IsValidTarget(Character, Character))
|
||||
{
|
||||
AddTargets<AIObjectiveRescueAll, Character>(Character, Character);
|
||||
}
|
||||
}
|
||||
reportProblemsTimer = reportProblemsInterval;
|
||||
}
|
||||
UpdateSpeaking();
|
||||
SpeakAboutIssues();
|
||||
UnequipUnnecessaryItems();
|
||||
reactTimer = GetReactionTime();
|
||||
}
|
||||
@@ -590,7 +624,7 @@ namespace Barotrauma
|
||||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10 ||
|
||||
Character.CurrentHull.IsWetRoom;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo { IsWaitOrder: true };
|
||||
bool removeDivingSuit = !shouldKeepTheGearOn && !IsOrderedToWait();
|
||||
if (shouldActOnSuffocation && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
|
||||
{
|
||||
@@ -875,7 +909,7 @@ namespace Barotrauma
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containableItem)) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer() ?? container.Item;
|
||||
var rootContainer = container.Item.RootContainer ?? container.Item;
|
||||
if (rootContainer.GetComponent<Fabricator>() != null || rootContainer.GetComponent<Deconstructor>() != null) { return 0; }
|
||||
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
@@ -906,20 +940,40 @@ namespace Barotrauma
|
||||
}
|
||||
}))
|
||||
{
|
||||
suitableContainer = targetContainer;
|
||||
return true;
|
||||
if (targetContainer != null &&
|
||||
character.AIController is HumanAIController humanAI &&
|
||||
humanAI.PathSteering.PathFinder.FindPath(character.SimPosition, targetContainer.SimPosition, character.Submarine, errorMsgStr: $"FindSuitableContainer ({character.DisplayName})", nodeFilter: node => node.Waypoint.CurrentHull != null).Unreachable)
|
||||
{
|
||||
ignoredItems.Add(targetContainer);
|
||||
itemIndex = 0;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
suitableContainer = targetContainer;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private float draggedTimer;
|
||||
private float refuseDraggingTimer;
|
||||
private const float RefuseDraggingAfter = 10.0f;
|
||||
/// <summary>
|
||||
/// The bot breaks free if being dragged by a human player from another team for longer than this
|
||||
/// </summary>
|
||||
private const float RefuseDraggingThresholdHigh = 10.0f;
|
||||
/// <summary>
|
||||
/// If the RefuseDraggingDuration is active (the bot recently broke free of being dragged), the bot breaks free much faster
|
||||
/// </summary>
|
||||
private const float RefuseDraggingThresholdLow = 0.5f;
|
||||
private const float RefuseDraggingDuration = 30.0f;
|
||||
|
||||
private void UpdateDragged(float deltaTime)
|
||||
{
|
||||
if (Character.HumanPrefab is { AllowDraggingIndefinitely: true }) { return; }
|
||||
if (Character.IsEscorted) { return; }
|
||||
if (Character.LockHands) { return; }
|
||||
|
||||
//don't allow player characters who aren't in the same team to drag us for more than x seconds
|
||||
if (Character.SelectedBy == null ||
|
||||
@@ -931,8 +985,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
draggedTimer += deltaTime;
|
||||
if (draggedTimer > RefuseDraggingAfter ||
|
||||
(draggedTimer > 0.5f && refuseDraggingTimer > 0.0f))
|
||||
if (draggedTimer > RefuseDraggingThresholdHigh ||
|
||||
(refuseDraggingTimer > 0.0f && draggedTimer > RefuseDraggingThresholdLow))
|
||||
{
|
||||
draggedTimer = 0.0f;
|
||||
refuseDraggingTimer = RefuseDraggingDuration;
|
||||
@@ -945,7 +999,8 @@ namespace Barotrauma
|
||||
{
|
||||
Order newOrder = null;
|
||||
Hull targetHull = null;
|
||||
bool speak = Character.SpeechImpediment < 100;
|
||||
// for now, escorted characters use the report system to get targets but do not speak. escort-character specific dialogue could be implemented
|
||||
bool speak = Character.SpeechImpediment < 100 && !Character.IsEscorted;
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
|
||||
@@ -1044,25 +1099,21 @@ namespace Barotrauma
|
||||
}
|
||||
if (newOrder != null && speak)
|
||||
{
|
||||
// for now, escorted characters use the report system to get targets but do not speak. escort-character specific dialogue could be implemented
|
||||
if (!Character.IsEscorted)
|
||||
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Default,
|
||||
identifier: $"{newOrder.Prefab.Identifier}{targetHull?.RoomName ?? "null"}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
else if (Character.IsOnPlayerTeam && GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||
{
|
||||
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Order);
|
||||
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Default,
|
||||
identifier: $"{newOrder.Prefab.Identifier}{targetHull?.RoomName ?? "null"}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
else if (Character.IsOnPlayerTeam && GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||
{
|
||||
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Order);
|
||||
#if SERVER
|
||||
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder
|
||||
.WithManualPriority(CharacterInfo.HighestManualOrderPriority)
|
||||
.WithTargetEntity(targetHull)
|
||||
.WithOrderGiver(Character), "", null, Character));
|
||||
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder
|
||||
.WithManualPriority(CharacterInfo.HighestManualOrderPriority)
|
||||
.WithTargetEntity(targetHull)
|
||||
.WithOrderGiver(Character), "", null, Character));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1093,21 +1144,33 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpeaking()
|
||||
private void SpeakAboutIssues()
|
||||
{
|
||||
if (!Character.IsOnPlayerTeam) { return; }
|
||||
if (Character.SpeechImpediment >= 100) { return; }
|
||||
if (Character.Oxygen < 20.0f)
|
||||
float minDelay = 0.5f, maxDelay = 2f;
|
||||
if (Character.Oxygen < CharacterHealth.InsufficientOxygenThreshold)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogLowOxygen").Value, null, Rand.Range(0.5f, 5.0f), "lowoxygen".ToIdentifier(), 30.0f);
|
||||
string msgId = "DialogLowOxygen";
|
||||
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
if (Character.Bleeding > 2.0f)
|
||||
if (Character.Bleeding > AfflictionPrefab.Bleeding.TreatmentThreshold && !Character.IsMedic)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogBleeding").Value, null, Rand.Range(0.5f, 5.0f), "bleeding".ToIdentifier(), 30.0f);
|
||||
string msgId = "DialogBleeding";
|
||||
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
if (Character.PressureTimer > 50.0f && Character.CurrentHull?.DisplayName != null)
|
||||
if ((Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0) && !Character.IsProtectedFromPressure)
|
||||
{
|
||||
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, null, Rand.Range(0.5f, 5.0f), "pressure".ToIdentifier(), 30.0f);
|
||||
if (Character.PressureProtection > 0)
|
||||
{
|
||||
string msgId = "DialogInsufficientPressureProtection";
|
||||
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (Character.CurrentHull?.DisplayName != null)
|
||||
{
|
||||
string msgId = "DialogPressure";
|
||||
Character.Speak(TextManager.GetWithVariable(msgId, "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1236,7 +1299,7 @@ namespace Barotrauma
|
||||
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && attacker.CombatAction == null;
|
||||
if (isAccidental)
|
||||
{
|
||||
if (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold)
|
||||
if (attacker.TeamID != Character.TeamID || (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold))
|
||||
{
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
|
||||
}
|
||||
@@ -1402,7 +1465,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (humanAI.ObjectiveManager.GetActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
|
||||
if (humanAI.ObjectiveManager.GetLastActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
|
||||
{
|
||||
// Already targeting the attacker -> treat as a more serious threat.
|
||||
cumulativeDamage *= 2;
|
||||
@@ -1587,7 +1650,7 @@ namespace Barotrauma
|
||||
hull.LethalPressure > 0 ||
|
||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
|
||||
{
|
||||
needsSuit = !Character.IsProtectedFromPressure;
|
||||
needsSuit = (hull == null || hull.LethalPressure > 0) && !Character.IsImmuneToPressure;
|
||||
return needsAir || needsSuit;
|
||||
}
|
||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||
@@ -1604,7 +1667,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool HasDivingSuit(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true,
|
||||
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes));
|
||||
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes | InvSlotType.InnerClothes));
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||
@@ -1837,18 +1900,22 @@ namespace Barotrauma
|
||||
private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the hull safety for all ai characters in the team. The idea is that the crew communicates (magically) via radio about the threads.
|
||||
/// Updates the hull safety for all ai characters in the team. The idea is that the crew communicates (magically) via radio about the threats.
|
||||
/// The safety levels need to be calculated for each bot individually, because the formula takes into account things like current orders.
|
||||
/// There's now a cached value per each hull, which should prevent too frequent calculations.
|
||||
/// </summary>
|
||||
public static void PropagateHullSafety(Character character, Hull hull)
|
||||
{
|
||||
DoForEachCrewMember(character, (humanAi) => humanAi.RefreshHullSafety(hull));
|
||||
DoForEachBot(character, (humanAi) => humanAi.RefreshHullSafety(hull));
|
||||
}
|
||||
|
||||
public void AskToRecalculateHullSafety(Hull hull) => dirtyHullSafetyCalculations.Add(hull);
|
||||
|
||||
private void RefreshHullSafety(Hull hull)
|
||||
{
|
||||
if (GetHullSafety(hull, Character, VisibleHulls) > HULL_SAFETY_THRESHOLD)
|
||||
var visibleHulls = dirtyHullSafetyCalculations.Contains(hull) ? hull.GetConnectedHulls(includingThis: true, searchDepth: 1) : VisibleHulls;
|
||||
float hullSafety = GetHullSafety(hull, Character, visibleHulls);
|
||||
if (hullSafety > HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
UnsafeHulls.Remove(hull);
|
||||
}
|
||||
@@ -1916,7 +1983,7 @@ namespace Barotrauma
|
||||
private static bool AddTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
||||
{
|
||||
bool targetAdded = false;
|
||||
DoForEachCrewMember(caller, humanAI =>
|
||||
DoForEachBot(caller, humanAI =>
|
||||
{
|
||||
if (caller != humanAI.Character && caller.SpeechImpediment >= 100) { return; }
|
||||
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
|
||||
@@ -1933,7 +2000,7 @@ namespace Barotrauma
|
||||
|
||||
public static void RemoveTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
||||
{
|
||||
DoForEachCrewMember(caller, humanAI =>
|
||||
DoForEachBot(caller, humanAI =>
|
||||
humanAI.ObjectiveManager.GetObjective<T1>()?.ReportedTargets.Remove(target));
|
||||
}
|
||||
|
||||
@@ -2108,7 +2175,7 @@ namespace Barotrauma
|
||||
if (other.IsPet)
|
||||
{
|
||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||
if (!sameTeam && me.TeamID == CharacterTeamType.None) { return false; }
|
||||
return sameTeam || me.TeamID != CharacterTeamType.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2120,9 +2187,11 @@ namespace Barotrauma
|
||||
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
{
|
||||
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
|
||||
|
||||
//NPCs that allow some campaign interaction are not turned hostile by low reputation
|
||||
if (npc.CampaignInteractionType != CampaignMode.InteractionType.None) { return true; }
|
||||
if (!npc.IsEscorted && npc.AIController is HumanAIController npcAI)
|
||||
|
||||
if (npc.AIController is HumanAIController npcAI)
|
||||
{
|
||||
return !npcAI.IsInHostileFaction();
|
||||
}
|
||||
@@ -2134,6 +2203,7 @@ namespace Barotrauma
|
||||
public bool IsInHostileFaction()
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is not CampaignMode campaign) { return false; }
|
||||
if (Character.IsEscorted) { return false; }
|
||||
|
||||
Identifier npcFaction = Character.Faction;
|
||||
Identifier currentLocationFaction = campaign.Map?.CurrentLocation?.Faction?.Prefab.Identifier ?? Identifier.Empty;
|
||||
@@ -2145,8 +2215,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!currentLocationFaction.IsEmpty && npcFaction == currentLocationFaction)
|
||||
{
|
||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
||||
if (campaign.CurrentLocation is { IsFactionHostile: true })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2154,71 +2223,89 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
|
||||
public static bool IsActive(Character c) => c != null && c.Enabled && !c.IsUnconscious;
|
||||
|
||||
public static bool IsTrueForAllCrewMembers(Character character, Func<HumanAIController, bool> predicate)
|
||||
public static bool IsTrueForAllBotsInTheCrew(Character character, Func<HumanAIController, bool> predicate)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (FilterCrewMember(character, c))
|
||||
if (!IsBotInTheCrew(character, c)) { continue; }
|
||||
if (!predicate(c.AIController as HumanAIController))
|
||||
{
|
||||
if (!predicate(c.AIController as HumanAIController))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsTrueForAnyCrewMember(Character character, Func<HumanAIController, bool> predicate)
|
||||
public static bool IsTrueForAnyBotInTheCrew(Character character, Func<HumanAIController, bool> predicate)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (FilterCrewMember(character, c))
|
||||
if (!IsBotInTheCrew(character, c)) { continue; }
|
||||
if (predicate(c.AIController as HumanAIController))
|
||||
{
|
||||
if (predicate(c.AIController as HumanAIController))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false)
|
||||
public static int CountBotsInTheCrew(Character character, Func<HumanAIController, bool> predicate = null)
|
||||
{
|
||||
if (character == null) { return 0; }
|
||||
int count = 0;
|
||||
foreach (var other in Character.CharacterList)
|
||||
{
|
||||
if (onlyActive && !IsActive(other))
|
||||
if (!IsBotInTheCrew(character, other)) { continue; }
|
||||
if (predicate == null || predicate(other.AIController as HumanAIController))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (onlyBots && other.IsPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (FilterCrewMember(character, other))
|
||||
{
|
||||
if (predicate == null || predicate(other.AIController as HumanAIController))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void DoForEachCrewMember(Character character, Action<HumanAIController> action, float range = float.PositiveInfinity)
|
||||
/// <summary>
|
||||
/// Including the player characters in the same team.
|
||||
/// </summary>
|
||||
public bool IsTrueForAnyCrewMember(Func<Character, bool> predicate, bool onlyActive = true, bool onlyConnectedSubs = false)
|
||||
{
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (!IsActive(c)) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (onlyActive && c.IsIncapacitated) { continue; }
|
||||
if (onlyConnectedSubs)
|
||||
{
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
if (c.Submarine != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (c.Submarine != Character.Submarine && !Character.Submarine.GetConnectedSubs().Contains(c.Submarine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (predicate(c))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void DoForEachBot(Character character, Action<HumanAIController> action, float range = float.PositiveInfinity)
|
||||
{
|
||||
if (character == null) { return; }
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (FilterCrewMember(character, c) && CheckReportRange(character, c, range))
|
||||
if (IsBotInTheCrew(character, c) && CheckReportRange(character, c, range))
|
||||
{
|
||||
action(c.AIController as HumanAIController);
|
||||
}
|
||||
@@ -2238,7 +2325,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static bool FilterCrewMember(Character self, Character other) => other != null && !other.IsDead && !other.Removed && other.AIController is HumanAIController humanAi && humanAi.IsFriendly(self);
|
||||
private static bool IsBotInTheCrew(Character self, Character other) => IsActive(other) && other.TeamID == self.TeamID && !other.IsIncapacitated && other.IsBot && other.AIController is HumanAIController;
|
||||
|
||||
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
|
||||
{
|
||||
@@ -2283,10 +2370,9 @@ namespace Barotrauma
|
||||
bool isOrder = IsOrderedToOperateThis(Character.AIController);
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!IsActive(c)) { continue; }
|
||||
if (c == Character) { continue; }
|
||||
if (c.Removed) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (c.SelectedItem == target.Item)
|
||||
@@ -2354,9 +2440,9 @@ namespace Barotrauma
|
||||
bool isOrder = IsOrderedToRepairThis(Character.AIController as HumanAIController);
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (!IsActive(c)) { continue; }
|
||||
if (c == Character) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
other = c;
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
@@ -2370,7 +2456,7 @@ namespace Barotrauma
|
||||
{
|
||||
var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
|
||||
if (repairItemsObjective == null) { continue; }
|
||||
if (!(repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is AIObjectiveRepairItem activeObjective) || activeObjective.Item != target)
|
||||
if (repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is not AIObjectiveRepairItem activeObjective || activeObjective.Item != target)
|
||||
{
|
||||
// Not targeting the same item.
|
||||
continue;
|
||||
@@ -2405,11 +2491,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#region Wrappers
|
||||
public bool IsFriendly(Character other) => IsFriendly(Character, other);
|
||||
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
|
||||
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate);
|
||||
public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate);
|
||||
public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
|
||||
public bool IsFriendly(Character other, bool onlySameTeam = false) => IsFriendly(Character, other, onlySameTeam);
|
||||
public bool IsTrueForAnyBotInTheCrew(Func<HumanAIController, bool> predicate) => IsTrueForAnyBotInTheCrew(Character, predicate);
|
||||
public bool IsTrueForAllBotsInTheCrew(Func<HumanAIController, bool> predicate) => IsTrueForAllBotsInTheCrew(Character, predicate);
|
||||
public int CountBotsInTheCrew(Func<HumanAIController, bool> predicate = null) => CountBotsInTheCrew(Character, predicate);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
+161
-127
@@ -22,7 +22,10 @@ namespace Barotrauma
|
||||
|
||||
private readonly Character character;
|
||||
|
||||
private Vector2 currentTarget;
|
||||
/// <summary>
|
||||
/// In sim units.
|
||||
/// </summary>
|
||||
private Vector2 currentTargetPos;
|
||||
|
||||
private float findPathTimer;
|
||||
|
||||
@@ -40,11 +43,6 @@ namespace Barotrauma
|
||||
get { return pathFinder; }
|
||||
}
|
||||
|
||||
public Vector2 CurrentTarget
|
||||
{
|
||||
get { return currentTarget; }
|
||||
}
|
||||
|
||||
public bool IsPathDirty
|
||||
{
|
||||
get;
|
||||
@@ -54,9 +52,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns true if any node in the path is in stairs
|
||||
/// </summary>
|
||||
public bool InStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
|
||||
public bool PathHasStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
|
||||
|
||||
public bool IsCurrentNodeLadder => currentPath?.CurrentNode?.Ladders != null && currentPath.CurrentNode.Ladders.Item.IsInteractable(character);
|
||||
public bool IsCurrentNodeLadder => GetCurrentLadder() != null;
|
||||
|
||||
public bool IsNextNodeLadder => GetNextLadder() != null;
|
||||
|
||||
@@ -64,14 +62,9 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentPath == null) { return false; }
|
||||
if (currentPath.CurrentNode == null) { return false; }
|
||||
if (currentPath.NextNode == null) { return false; }
|
||||
var currentLadder = currentPath.CurrentNode.Ladders;
|
||||
var currentLadder = GetCurrentLadder();
|
||||
if (currentLadder == null) { return false; }
|
||||
if (!currentLadder.Item.IsInteractable(character)) { return false; }
|
||||
var nextLadder = GetNextLadder();
|
||||
return nextLadder != null && nextLadder == currentLadder;
|
||||
return currentLadder == GetNextLadder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +100,10 @@ namespace Barotrauma
|
||||
findPathTimer -= step;
|
||||
}
|
||||
|
||||
public void SetPath(SteeringPath path)
|
||||
public void SetPath(Vector2 targetPos, SteeringPath path)
|
||||
{
|
||||
currentTargetPos = targetPos;
|
||||
currentPath = path;
|
||||
if (path.Nodes.Any())
|
||||
{
|
||||
currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
|
||||
}
|
||||
findPathTimer = Math.Min(findPathTimer, 1.0f);
|
||||
IsPathDirty = false;
|
||||
}
|
||||
@@ -136,66 +126,28 @@ namespace Barotrauma
|
||||
steering += addition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seeks the ladder from the next and next + 1 nodes.
|
||||
/// </summary>
|
||||
public Ladder GetNextLadder()
|
||||
{
|
||||
if (currentPath == null) { return null; }
|
||||
if (currentPath.NextNode == null) { return null; }
|
||||
if (currentPath.NextNode.Ladders != null && currentPath.NextNode.Ladders.Item.IsInteractable(character))
|
||||
{
|
||||
return currentPath.NextNode.Ladders;
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = currentPath.CurrentIndex + 2;
|
||||
if (currentPath.Nodes.Count > index)
|
||||
{
|
||||
var node = currentPath.Nodes[index];
|
||||
if (node == null) { return null; }
|
||||
if (node.Ladders != null && node.Ladders.Item.IsInteractable(character))
|
||||
{
|
||||
return node.Ladders;
|
||||
}
|
||||
//if the next node is a hatch, check if the node after that is a ladder
|
||||
else if (node.ConnectedDoor != null && node.ConnectedDoor.IsHorizontal)
|
||||
{
|
||||
index++;
|
||||
if (currentPath.Nodes.Count > index)
|
||||
{
|
||||
node = currentPath.Nodes[index];
|
||||
if (node == null) { return null; }
|
||||
if (node.Ladders != null && node.Ladders.Item.IsInteractable(character))
|
||||
{
|
||||
return node.Ladders;
|
||||
}
|
||||
}
|
||||
}
|
||||
public Ladder GetCurrentLadder() => GetLadder(currentPath?.CurrentNode);
|
||||
|
||||
}
|
||||
return null;
|
||||
public Ladder GetNextLadder() => GetLadder(currentPath?.NextNode);
|
||||
|
||||
private Ladder GetLadder(WayPoint wp)
|
||||
{
|
||||
if (wp?.Ladders?.Item is Item item && item.IsInteractable(character))
|
||||
{
|
||||
return wp.Ladders;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
{
|
||||
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished;
|
||||
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.CurrentNode == null;
|
||||
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
|
||||
{
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
|
||||
{
|
||||
//target in a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
|
||||
if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
|
||||
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
}
|
||||
}
|
||||
// If the target has moved, we need a new path.
|
||||
// Different subs are already taken into account before setting the target.
|
||||
// Triggers when either the target or we have changed subs, but only once (until the new path has been accepted).
|
||||
Vector2 targetDiff = target - currentTargetPos;
|
||||
if (targetDiff.LengthSquared() > 1)
|
||||
{
|
||||
needsNewPath = true;
|
||||
@@ -205,15 +157,33 @@ namespace Barotrauma
|
||||
if (needsNewPath || findPathTimer < -1.0f)
|
||||
{
|
||||
IsPathDirty = true;
|
||||
if (!needsNewPath && currentPath?.CurrentNode is WayPoint wp)
|
||||
{
|
||||
if (character.Submarine != null && wp.Ladders == null && wp.ConnectedDoor == null && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0)
|
||||
{
|
||||
// Not moving -> need a new path.
|
||||
needsNewPath = true;
|
||||
}
|
||||
if (character.Submarine == null && wp.CurrentHull != null)
|
||||
{
|
||||
// Current node inside, while we are outside
|
||||
// -> Check that the current node is not too far (can happen e.g. if someone controls the character in the meanwhile)
|
||||
float maxDist = 200;
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, wp.WorldPosition) > maxDist * maxDist)
|
||||
{
|
||||
needsNewPath = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (findPathTimer < 0)
|
||||
{
|
||||
SkipCurrentPathNodes();
|
||||
currentTarget = target;
|
||||
currentTargetPos = target;
|
||||
Vector2 currentPos = host.SimPosition;
|
||||
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
|
||||
bool useNewPath = needsNewPath;
|
||||
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
{
|
||||
// Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset).
|
||||
@@ -234,6 +204,14 @@ namespace Barotrauma
|
||||
useNewPath = Vector2.DistanceSquared(character.WorldPosition, currentPath.CurrentNode.WorldPosition) > Math.Pow(Vector2.Distance(character.WorldPosition, newPath.Nodes.First().WorldPosition) * 3, 2);
|
||||
}
|
||||
}
|
||||
if (!useNewPath && !character.CanSeeTarget(currentPath.CurrentNode))
|
||||
{
|
||||
// If we are set to disregard the new path, ensure that we can actually see the current node of the old path,
|
||||
// because it's possible that there's e.g. a closed door between us and the current node,
|
||||
// and in that case we'd want to use the new path instead of the old.
|
||||
// There's visibility checks in the pathfinder calls, so the new path should always be ok.
|
||||
useNewPath = true;
|
||||
}
|
||||
|
||||
bool IsIdenticalPath()
|
||||
{
|
||||
@@ -312,6 +290,7 @@ namespace Barotrauma
|
||||
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.Height / 2 + collider.Radius)
|
||||
{
|
||||
// TODO: might cause some edge cases -> do we need this?
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
if (diff == Vector2.Zero) { return Vector2.Zero; }
|
||||
@@ -328,12 +307,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (currentPath.Finished)
|
||||
{
|
||||
Vector2 pos2 = host.SimPosition;
|
||||
Vector2 hostPosition = host.SimPosition;
|
||||
if (character != null && character.Submarine == null && CurrentPath.Nodes.Count > 0 && CurrentPath.Nodes.Last().Submarine != null)
|
||||
{
|
||||
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
|
||||
hostPosition -= CurrentPath.Nodes.Last().Submarine.SimPosition;
|
||||
}
|
||||
return currentTarget - pos2;
|
||||
return currentTargetPos - hostPosition;
|
||||
}
|
||||
bool doorsChecked = false;
|
||||
checkDoorsTimer = Math.Min(checkDoorsTimer, GetDoorCheckTime());
|
||||
@@ -353,14 +332,46 @@ namespace Barotrauma
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController && !character.LockHands;
|
||||
Ladder currentLadder = currentPath.CurrentNode.Ladders;
|
||||
if (currentLadder != null && !currentLadder.Item.IsInteractable(character))
|
||||
{
|
||||
currentLadder = null;
|
||||
}
|
||||
Ladder currentLadder = GetCurrentLadder();
|
||||
Ladder nextLadder = GetNextLadder();
|
||||
var ladders = currentLadder ?? nextLadder;
|
||||
bool useLadders = canClimb && ladders != null && steering.LengthSquared() > 0.1f && (!isDiving || steering.Y > 1);
|
||||
bool useLadders = canClimb && ladders != null;
|
||||
var collider = character.AnimController.Collider;
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
if (useLadders)
|
||||
{
|
||||
if (character.IsClimbing && Math.Abs(diff.X) - ConvertUnits.ToDisplayUnits(colliderSize.X) > Math.Abs(diff.Y))
|
||||
{
|
||||
// If the current node is horizontally farther from us than vertically, we don't want to keep climbing the ladders.
|
||||
useLadders = false;
|
||||
}
|
||||
else if (!character.IsClimbing && currentPath.NextNode != null && nextLadder == null)
|
||||
{
|
||||
Vector2 diffToNextNode = currentPath.NextNode.WorldPosition - pos;
|
||||
if (Math.Abs(diffToNextNode.X) > Math.Abs(diffToNextNode.Y))
|
||||
{
|
||||
// If the next node is horizontally farther from us than vertically, we don't want to start climbing.
|
||||
useLadders = false;
|
||||
}
|
||||
}
|
||||
else if (isDiving && steering.Y < 1)
|
||||
{
|
||||
// When diving, only use ladders to get upwards (towards the surface), otherwise we can just ignore them.
|
||||
useLadders = false;
|
||||
}
|
||||
}
|
||||
if (character.IsClimbing && !useLadders)
|
||||
{
|
||||
if (currentPath.IsAtEndNode && canClimb && ladders != null)
|
||||
{
|
||||
// Don't release the ladders when ending a path in ladders.
|
||||
useLadders = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.StopClimbing();
|
||||
}
|
||||
}
|
||||
if (useLadders && character.SelectedSecondaryItem != ladders.Item)
|
||||
{
|
||||
if (character.CanInteractWith(ladders.Item))
|
||||
@@ -380,56 +391,76 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
var collider = character.AnimController.Collider;
|
||||
if (character.IsClimbing && !useLadders)
|
||||
{
|
||||
character.StopClimbing();
|
||||
}
|
||||
if (character.IsClimbing && useLadders)
|
||||
{
|
||||
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
|
||||
if (nextLadderSameAsCurrent || currentLadder != null && nextLadder != null && Math.Abs(currentLadder.Item.Position.X - nextLadder.Item.Position.X) < 50)
|
||||
if (currentLadder == null && nextLadder != null && character.SelectedSecondaryItem == nextLadder.Item)
|
||||
{
|
||||
//climbing ladders -> don't move horizontally
|
||||
diff.X = 0.0f;
|
||||
// Climbing a ladder but the path is still on the node next to the ladder -> Skip the node.
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
//at the same height as the waypoint
|
||||
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
||||
float colliderSize = (collider.Height / 2 + collider.Radius) * 1.25f;
|
||||
if (heightDiff < colliderSize)
|
||||
else
|
||||
{
|
||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
bool isAboveFloor = heightFromFloor > -0.1f;
|
||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||
if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
bool nextLadderSameAsCurrent = currentLadder == nextLadder;
|
||||
if (currentLadder != null && nextLadder != null)
|
||||
{
|
||||
character.StopClimbing();
|
||||
//climbing ladders -> don't move horizontally
|
||||
diff.X = 0.0f;
|
||||
}
|
||||
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
//at the same height as the waypoint
|
||||
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
||||
float colliderHeight = collider.Height / 2 + collider.Radius;
|
||||
float distanceMargin = ConvertUnits.ToDisplayUnits(colliderSize.X);
|
||||
if (heightDiff < colliderHeight * 1.25f)
|
||||
{
|
||||
// Try to change the ladder (hatches between two submarines)
|
||||
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
||||
if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
{
|
||||
if (nextLadder.Item.TryInteract(character, forceSelectKey: true))
|
||||
// Try to change the ladder (hatches between two submarines)
|
||||
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
||||
{
|
||||
if (nextLadder.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool isAboveFloor;
|
||||
if (diff.Y < 0)
|
||||
{
|
||||
// When climbing down, let's use the collider bottom to prevent getting stuck at the bottom of the ladders.
|
||||
float colliderBottom = character.AnimController.Collider.SimPosition.Y;
|
||||
float floorY = character.AnimController.FloorY;
|
||||
isAboveFloor = colliderBottom > floorY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When climbing up, let's use the lowest collider (feet).
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative,
|
||||
// when a foot is still below the platform.
|
||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||
isAboveFloor = heightFromFloor > -0.1f;
|
||||
}
|
||||
if (isAboveFloor)
|
||||
{
|
||||
if (Math.Abs(diff.Y) < distanceMargin)
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
else if (!currentPath.IsAtEndNode && (nextLadder == null || (currentLadder != null && Math.Abs(currentLadder.Item.WorldPosition.X - nextLadder.Item.WorldPosition.X) > distanceMargin)))
|
||||
{
|
||||
// Can't skip the node -> Release the ladders, because the next node is not on a ladder or it's horizontally too far.
|
||||
character.StopClimbing();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!currentPath.IsAtEndNode && (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10))
|
||||
else if (currentLadder != null && currentPath.NextNode != null)
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
}
|
||||
else if (nextLadder != null)
|
||||
{
|
||||
//if the current node is below the character and the next one is above (or vice versa)
|
||||
//and both are on ladders, we can skip directly to the next one
|
||||
//e.g. no point in going down to reach the starting point of a path when we could go directly to the one above
|
||||
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
|
||||
{
|
||||
//if the current node is below the character and the next one is above (or vice versa)
|
||||
//and both are on ladders, we can skip directly to the next one
|
||||
//e.g. no point in going down to reach the starting point of a path when we could go directly to the one above
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ConvertUnits.ToSimUnits(diff);
|
||||
@@ -440,7 +471,6 @@ namespace Barotrauma
|
||||
if (door == null || door.CanBeTraversed)
|
||||
{
|
||||
float margin = MathHelper.Lerp(1, 5, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
float targetDistance = Math.Max(Math.Max(colliderSize.X, colliderSize.Y) / 2 * margin, 0.5f);
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
|
||||
@@ -459,7 +489,6 @@ namespace Barotrauma
|
||||
{
|
||||
// Walking horizontally
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
Vector2 velocity = collider.LinearVelocity;
|
||||
// If the character is very short, it would fail to use the waypoint nodes because they are always too high.
|
||||
// If the character is very thin, it would often fail to reach the waypoints, because the horizontal distance is too small.
|
||||
@@ -486,9 +515,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
|
||||
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow && (door == null || door.CanBeTraversed))
|
||||
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow)
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
if (door is not { CanBeTraversed: false } && (currentLadder == null || nextLadder == null))
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentPath.CurrentNode == null)
|
||||
@@ -507,9 +539,9 @@ namespace Barotrauma
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
|
||||
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
public bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
{
|
||||
if (door.IsBroken) { return true; }
|
||||
if (door.CanBeTraversed) { return true; }
|
||||
if (door.IsClosed)
|
||||
{
|
||||
if (!door.Item.IsInteractable(character)) { return false; }
|
||||
@@ -605,10 +637,12 @@ namespace Barotrauma
|
||||
{
|
||||
//the node we're heading towards is the last one in the path, and at a door
|
||||
//the door needs to be open for the character to reach the node
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap != null)
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap is Gap linkedGap)
|
||||
{
|
||||
// Keep the airlock doors closed, but not in ruins/wrecks
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom && currentWaypoint.CurrentHull is { IsWetRoom: false } || currentWaypoint.Submarine == null || currentWaypoint.Submarine.Info.IsRuin || currentWaypoint.Submarine.Info.IsWreck)
|
||||
if (currentWaypoint.Submarine == null ||
|
||||
currentWaypoint.Submarine.Info is { IsPlayer: false } ||
|
||||
!linkedGap.IsRoomToRoom ||
|
||||
(linkedGap.IsRoomToRoom && currentWaypoint.CurrentHull is { IsWetRoom: false }))
|
||||
{
|
||||
shouldBeOpen = true;
|
||||
door = currentWaypoint.ConnectedDoor;
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Voronoi2.GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection);
|
||||
float distSqr = Vector2.DistanceSquared(character.SimPosition, potentialAttachPos);
|
||||
|
||||
@@ -506,15 +506,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected static bool CanEquip(Character character, Item item)
|
||||
public virtual void SpeakAfterOrderReceived() { }
|
||||
|
||||
protected static bool CanEquip(Character character, Item item, bool allowWearing)
|
||||
{
|
||||
bool canEquip = item != null;
|
||||
if (canEquip && !item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
if (item == null) { return false; }
|
||||
bool canEquip = false;
|
||||
if (item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
if (character.Inventory.IsAnySlotAvailable(item))
|
||||
{
|
||||
canEquip = true;
|
||||
}
|
||||
}
|
||||
if (!canEquip)
|
||||
{
|
||||
canEquip = false;
|
||||
var inv = character.Inventory;
|
||||
foreach (var allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
if (!allowWearing)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(InvSlotType.RightHand) && !allowedSlot.HasFlag(InvSlotType.LeftHand))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var slotType in inv.SlotTypes)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(slotType)) { continue; }
|
||||
@@ -530,18 +546,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet<Identifier> identifiersOrTags)
|
||||
{
|
||||
if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; }
|
||||
foreach (var identifier in identifiersOrTags)
|
||||
{
|
||||
if (item.HasTag(identifier)) { return true; }
|
||||
}
|
||||
return false;
|
||||
return canEquip && character.Inventory.CanBePut(item);
|
||||
}
|
||||
|
||||
protected bool CanEquip(Item item) => CanEquip(character, item);
|
||||
protected bool CanEquip(Item item, bool allowWearing) => CanEquip(character, item, allowWearing);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -64,7 +64,6 @@ namespace Barotrauma
|
||||
if (subObjectives.Any()) { return; }
|
||||
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
|
||||
{
|
||||
itemIndex = 0;
|
||||
if (suitableContainer != null)
|
||||
{
|
||||
bool equip = item.GetComponent<Holdable>() != null ||
|
||||
@@ -112,10 +111,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
|
||||
+7
-3
@@ -14,6 +14,10 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<Item> prioritizedItems = new List<Item>();
|
||||
|
||||
public static readonly Identifier AllowCleanupTag = "allowcleanup".ToIdentifier();
|
||||
|
||||
protected override int MaxTargets => 100;
|
||||
|
||||
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, Item prioritizedItem = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
@@ -81,8 +85,8 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidContainer(Item container, Character character, bool allowUnloading = true) =>
|
||||
allowUnloading &&
|
||||
container.HasTag(AllowCleanupTag) &&
|
||||
container.HasAccess(character) &&
|
||||
container.HasTag("allowcleanup") &&
|
||||
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
|
||||
container.GetComponent<ItemContainer>() != null &&
|
||||
IsItemInsideValidSubmarine(container, character) &&
|
||||
@@ -91,7 +95,6 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
@@ -102,6 +105,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
@@ -121,7 +125,7 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return CanEquip(character, item);
|
||||
return CanEquip(character, item, allowWearing: false);
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user