This commit is contained in:
EvilFactory
2023-06-15 12:13:50 -03:00
210 changed files with 4491 additions and 2580 deletions

View File

@@ -54,7 +54,7 @@ body:
label: Version
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options:
- v1.0.13.2
- v1.0.20.1
- Other
validations:
required: true

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -1156,6 +1157,26 @@ namespace Barotrauma
});
AssignRelayToServer("debugdraw", false);
AssignOnExecute("debugdrawlos", (string[] args) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
{
state = !GameMain.LightManager.DebugLos;
}
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) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
@@ -2824,7 +2845,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;

View File

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

View File

@@ -29,7 +29,7 @@ namespace Barotrauma
}
}
for (int i = 0; i < resourceClusters.Count; i++)
for (int i = 0; i < resourceAmounts.Count; i++)
{
var amount = msg.ReadByte();
var rotation = msg.ReadSingle();
@@ -54,7 +54,7 @@ namespace Barotrauma
CalculateMissionClusterPositions();
for(int i = 0; i < resourceClusters.Count; i++)
for(int i = 0; i < resourceAmounts.Count; i++)
{
var identifier = msg.ReadIdentifier();
var count = msg.ReadByte();

View File

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

View File

@@ -1,9 +1,9 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -199,8 +199,8 @@ namespace Barotrauma
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
size = new Point(
subElement.GetAttributeInt("width", 0),
subElement.GetAttributeInt("height", 0));
ParseSize(subElement, "width"),
ParseSize(subElement, "height"));
break;
}
}

View File

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

View File

@@ -1,14 +1,12 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -20,6 +18,26 @@ namespace Barotrauma
{
return element.NameAsIdentifier();
}
protected int ParseSize(XElement element, string attributeName)
{
string valueStr = element.GetAttributeString(attributeName, string.Empty);
bool relativeToWidth = valueStr.EndsWith("vw");
bool relativeToHeight = valueStr.EndsWith("vh");
if (relativeToWidth || relativeToHeight)
{
string floatStr = valueStr.Substring(0, valueStr.Length - 2);
if (!float.TryParse(floatStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float relativeHeight))
{
DebugConsole.ThrowError($"Error while parsing a {nameof(GUIComponentStyle)}: {valueStr} is not a valid size.");
}
return (int)(relativeHeight / 100.0f * (relativeToWidth ? GameMain.GraphicsWidth : GameMain.GraphicsHeight));
}
else
{
return element.GetAttributeInt(attributeName, 0);
}
}
}
public abstract class GUISelector<T> where T : GUIPrefab
@@ -166,7 +184,8 @@ namespace Barotrauma
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
return (uint)Math.Round(subElement.GetAttributeInt("size", 14) * GameSettings.CurrentConfig.Graphics.TextScale);
int rawSize = ParseSize(subElement, "size");
return (uint)Math.Round(rawSize * GameSettings.CurrentConfig.Graphics.TextScale);
}
}
return (uint)Math.Round(defaultSize * GameSettings.CurrentConfig.Graphics.TextScale);

View File

@@ -542,6 +542,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,

View File

@@ -159,8 +159,9 @@ namespace Barotrauma
int crewAreaY = ButtonAreaTop.Bottom + Padding;
int crewAreaHeight = ObjectiveAnchor.Top - Padding - crewAreaY;
CrewArea = new Rectangle(Padding, crewAreaY, (int)Math.Max(400 * GUI.Scale, 220), crewAreaHeight);
float crewAreaWidthMultiplier = GUI.IsUltrawide ? GUI.HorizontalAspectRatio : 1.0f;
CrewArea = new Rectangle(Padding, crewAreaY, (int)(Math.Max(400 * GUI.Scale, 220) * crewAreaWidthMultiplier), crewAreaHeight);
InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY);
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);

View File

@@ -175,6 +175,7 @@ namespace Barotrauma
{
if (relativeOffset.NearlyEquals(value)) { return; }
relativeOffset = value;
recalculateRect = true;
RecalculateChildren(false, false);
}
}

View File

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

View File

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

View File

@@ -1722,7 +1722,7 @@ namespace Barotrauma
static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel)
{
list.Content.ClearChildren();
List<Item> allItems = Character.Controlled?.Inventory?.FindAllItems(recursive: true) ?? new List<Item>();
var allItems = CargoManager.FindAllItemsOnPlayerAndSub(Character.Controlled);
var resources = prefab.GetApplicableResources(targetLevel);

View File

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

View File

@@ -672,7 +672,10 @@ namespace Barotrauma
while (Timing.Accumulator >= Timing.Step)
{
Timing.TotalTime += Timing.Step;
if (!Paused)
{
Timing.TotalTimeUnpaused += Timing.Step;
}
Stopwatch sw = new Stopwatch();
sw.Start();
@@ -956,7 +959,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)
{

View File

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

View File

@@ -376,6 +376,7 @@ namespace Barotrauma
- (0.1f * iconRelativeWidth)
// Spacing
- (7 * layoutGroup.RelativeSpacing);
nameRelativeWidth = Math.Max(nameRelativeWidth, 0.25f);
var font = layoutGroup.Rect.Width < 150 ? GUIStyle.SmallFont : GUIStyle.Font;
var nameBlock = new GUITextBlock(

View File

@@ -121,6 +121,23 @@ namespace Barotrauma
{
return AllowedToManageCampaign(ClientPermissions.ManageMoney);
}
protected GUIButton CreateEndRoundButton()
{
int buttonWidth = (int)(450 * GUI.xScale * (GUI.IsUltrawide ? 3.0f : 1.0f));
int buttonHeight = (int)(40 * GUI.yScale);
var rectT = HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y, buttonWidth, buttonHeight), GUI.Canvas);
rectT.Pivot = Pivot.Center;
return new GUIButton(rectT, TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
}
};
}
public override void Draw(SpriteBatch spriteBatch)
{

View File

@@ -125,38 +125,25 @@ namespace Barotrauma
private void CreateButtons()
{
int buttonHeight = (int) (GUI.Scale * 40),
buttonWidth = GUI.IntScale(450),
buttonCenter = buttonHeight / 2,
screenMiddle = GameMain.GraphicsWidth / 2;
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle - buttonWidth / 2, HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
endRoundButton = CreateEndRoundButton();
endRoundButton.OnClicked = (btn, userdata) =>
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
TryEndRoundWithFuelCheck(
onConfirm: () => GameMain.Client.RequestStartRound(),
onReturnToMapScreen: () =>
{
ShowCampaignUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
});
return true;
}
TryEndRoundWithFuelCheck(
onConfirm: () => GameMain.Client.RequestStartRound(),
onReturnToMapScreen: () =>
{
ShowCampaignUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
});
return true;
};
int readyButtonHeight = buttonHeight;
int readyButtonWidth = (int) (GUI.Scale * 50);
ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (buttonWidth / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, readyButtonWidth, readyButtonHeight), GUI.Canvas),
int readyButtonWidth = (int)(GUI.Scale * 50 * (GUI.IsUltrawide ? 3.0f : 1.0f));
int readyButtonHeight = (int)(GUI.Scale * 40);
int readyButtonCenter = readyButtonHeight / 2,
screenMiddle = GameMain.GraphicsWidth / 2;
ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (endRoundButton.Rect.Width / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - readyButtonCenter, readyButtonWidth, readyButtonHeight), GUI.Canvas),
style: "RepairBuyButton")
{
ToolTip = TextManager.Get("ReadyCheck.Tooltip"),
@@ -206,7 +193,7 @@ namespace Barotrauma
if (GameMain.Client == null)
{
yield return CoroutineStatus.Failure;
yield return CoroutineStatus.Success;
}
if (GameMain.Client.LateCampaignJoin)

View File

@@ -148,6 +148,9 @@ namespace Barotrauma
case "stats":
LoadStats(subElement);
break;
case "eventmanager":
GameMain.GameSession.EventManager.Load(subElement);
break;
}
}
@@ -210,28 +213,14 @@ namespace Barotrauma
{
StartRound = () => { TryEndRound(); }
};
}
private void CreateEndRoundButton()
{
int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(450);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
endRoundButton = CreateEndRoundButton();
endRoundButton.OnClicked = (btn, userdata) =>
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
TryEndRoundWithFuelCheck(
onConfirm: () => TryEndRound(),
onReturnToMapScreen: () => { ShowCampaignUI = true; CampaignUI.SelectTab(InteractionType.Map); });
return true;
}
TryEndRoundWithFuelCheck(
onConfirm: () => TryEndRound(),
onReturnToMapScreen: () => { ShowCampaignUI = true; CampaignUI.SelectTab(InteractionType.Map); });
return true;
};
}
@@ -699,6 +688,11 @@ namespace Barotrauma
modeElement.Add(Settings.Save());
modeElement.Add(SaveStats());
if (GameMain.GameSession?.EventManager != null)
{
modeElement.Add(GameMain.GameSession?.EventManager.Save());
}
//save and remove all items that are in someone's inventory so they don't get included in the sub file as well
foreach (Character c in Character.CharacterList)
{

View File

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

View File

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

View File

@@ -92,9 +92,6 @@ namespace Barotrauma.Items.Components
rect.Height = (int)(rect.Height * (1.0f - openState));
}
//only merge the door's convex hull with overlapping wall segments if it's fully open or fully closed
//it's the heaviest part of changing the convex hull, and doesn't need to be done while the door is still in motion
bool mergeOverlappingSegments = openState <= 0.0f || openState >= 1.0f;
if (Window.Height > 0 && Window.Width > 0)
{
if (IsHorizontal)
@@ -117,7 +114,7 @@ namespace Barotrauma.Items.Components
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
SetVertices(convexHull2, rect2);
}
}
}
@@ -141,7 +138,7 @@ namespace Barotrauma.Items.Components
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
SetVertices(convexHull2, rect2);
}
}
}
@@ -156,11 +153,23 @@ namespace Barotrauma.Items.Components
else
{
convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect), mergeOverlappingSegments);
SetVertices(convexHull, rect);
}
}
private void SetVertices(ConvexHull convexHull, Rectangle rect)
{
var verts = GetConvexHullCorners(rect);
Vector2 center = (verts[0] + verts[2]) / 2;
convexHull.SetVertices(
verts,
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)
{
if (shakeTimer > 0.0f)

View File

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

View File

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

View File

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

View File

@@ -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,7 +640,11 @@ namespace Barotrauma.Items.Components
}
toolTipText = $"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
if (!requiredItemPrefab.Description.IsNullOrEmpty())
if (!requiredItem.OverrideDescription.IsNullOrEmpty())
{
toolTipText += '\n' + requiredItem.OverrideDescription;
}
else if (!requiredItemPrefab.Description.IsNullOrEmpty())
{
toolTipText += '\n' + requiredItemPrefab.Description;
}
@@ -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)
{

View File

@@ -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;
}
@@ -437,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 };
@@ -445,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>();
@@ -536,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();
@@ -551,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
@@ -731,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)
{
@@ -915,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;
@@ -1305,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);
@@ -1526,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
@@ -1539,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;
@@ -1686,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)

View File

@@ -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)
@@ -1512,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)
@@ -1922,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);
}
}
@@ -1934,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);
@@ -1965,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;
@@ -1982,7 +1986,7 @@ namespace Barotrauma.Items.Components
directionalModeSwitch.Selected = useDirectionalPing;
if (mineralScannerSwitch != null)
{
mineralScannerSwitch.Selected = useMineralScanner;
mineralScannerSwitch.Selected = UseMineralScanner;
}
}
}

View File

@@ -178,8 +178,9 @@ namespace Barotrauma.Items.Components
var autoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.62f), paddedControlContainer.RectTransform, Anchor.BottomCenter), "OutlineFrame");
var paddedAutoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.92f, 0.88f), autoPilotControls.RectTransform, Anchor.Center), style: null);
int textLimit = (int)(paddedAutoPilotControls.Rect.Width * 0.75f);
maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter),
TextManager.Get("SteeringMaintainPos"), font: GUIStyle.SmallFont, style: "GUIRadioButton")
ToolBox.LimitString(TextManager.Get("SteeringMaintainPos"), GUIStyle.SmallFont, textLimit), font: GUIStyle.SmallFont, style: "GUIRadioButton")
{
UserData = UIHighlightAction.ElementId.MaintainPosTickBox,
Enabled = autoPilot,
@@ -214,7 +215,6 @@ namespace Barotrauma.Items.Components
return true;
}
};
int textLimit = (int)(paddedAutoPilotControls.Rect.Width * 0.75f);
levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center),
GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, GUIStyle.SmallFont, textLimit),
font: GUIStyle.SmallFont, style: "GUIRadioButton")

View File

@@ -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)
@@ -259,7 +299,7 @@ namespace Barotrauma.Items.Components
{
bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item);
DraggingConnected.RemoveConnection(panel.Item);
if (DraggingConnected.Connect(this, !alreadyConnected, true))
if (DraggingConnected.TryConnect(this, !alreadyConnected, true))
{
var otherConnection = DraggingConnected.OtherConnection(this);
ConnectWire(DraggingConnected);
@@ -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;

View File

@@ -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()
@@ -225,7 +232,7 @@ namespace Barotrauma.Items.Components
foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray())
{
connection.ConnectWire(wire);
wire.Connect(connection, false);
wire.TryConnect(connection, false);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -324,13 +324,13 @@ namespace Barotrauma
}
/*GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
for (int i = 0; i < waveY.Length - 1; i++)
{
GUI.DrawLine(spriteBatch,
new Vector2(drawRect.X + WaveWidth * i, -WorldSurface - waveY[i] - 10),
new Vector2(drawRect.X + WaveWidth * (i + 1), -WorldSurface - waveY[i + 1] - 10), Color.Blue * 0.5f);
}*/
}
}
foreach (MapEntity e in linkedTo)

View File

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

View File

@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -32,13 +31,13 @@ namespace Barotrauma.Lights
public bool IsHorizontal;
public bool IsAxisAligned;
public Vector2 SubmarineDrawPos;
public Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull)
{
if (start.Pos.Y > end.Pos.Y)
{
var temp = start;
start = end;
end = temp;
(end, start) = (start, end);
}
Start = start;
@@ -87,19 +86,23 @@ namespace Barotrauma.Lights
private readonly Segment[] segments = new Segment[4];
private readonly SegmentPoint[] vertices = new SegmentPoint[4];
private readonly SegmentPoint[] losVertices = new SegmentPoint[4];
private readonly VectorPair[] losOffsets = new VectorPair[4];
private readonly bool[] backFacing;
private readonly bool[] ignoreEdge;
private readonly SegmentPoint[] losVertices = new SegmentPoint[2];
private readonly Vector2[] losOffsets = new Vector2[2];
private readonly bool isHorizontal;
private readonly int thickness;
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,61 +133,59 @@ namespace Barotrauma.Lights
public Rectangle BoundingBox { get; private set; }
public ConvexHull(Vector2[] points, Color color, MapEntity parent)
public ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent)
{
if (shadowEffect == null)
{
shadowEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true
};
}
if (penumbraEffect == null)
{
penumbraEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
penumbraEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
TextureEnabled = true,
LightingEnabled = false,
Texture = TextureLoader.FromFile("Content/Lights/penumbra.png")
};
}
ParentEntity = parent;
ShadowVertices = new VertexPositionColor[6 * 4];
PenumbraVertices = new VertexPositionTexture[6 * 4];
backFacing = new bool[4];
ignoreEdge = new bool[4];
BoundingBox = rect;
float minX = points[0].X, minY = points[0].Y, maxX = points[0].X, maxY = points[0].Y;
for (int i = 1; i < vertices.Length; i++)
{
if (points[i].X < minX) minX = points[i].X;
if (points[i].Y < minY) minY = points[i].Y;
if (points[i].X > maxX) maxX = points[i].X;
if (points[i].Y > minY) maxY = points[i].Y;
}
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
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; }
}
SetVertices(points);
Vector2[] verts = new Vector2[]
{
new Vector2(rect.X, rect.Bottom),
new Vector2(rect.Right, rect.Bottom),
new Vector2(rect.Right, rect.Y),
new Vector2(rect.X, rect.Y),
};
Vector2[] losVerts;
if (this.isHorizontal)
{
thickness = rect.Height;
losVerts = new Vector2[] { new Vector2(rect.X, rect.Center.Y), new Vector2(rect.Right, rect.Center.Y) };
}
else
{
thickness = rect.Width;
losVerts = new Vector2[] { new Vector2(rect.Center.X, rect.Y), new Vector2(rect.Center.X, rect.Bottom) };
}
SetVertices(verts, losVerts);
Enabled = true;
var chList = HullLists.Find(h => h.Submarine == parent.Submarine);
@@ -196,249 +197,123 @@ namespace Barotrauma.Lights
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
ch.MergeOverlappingSegments(this);
MergeLosVertices(ch);
ch.MergeLosVertices(this);
}
chList.List.Add(this);
}
private void MergeOverlappingSegments(ConvexHull ch)
private void MergeLosVertices(ConvexHull ch, bool refreshOtherOverlappingHulls = true)
{
if (ch == this) { return; }
if (isHorizontal == ch.isHorizontal)
//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)
{
//hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces)
float mergeDist = 16;
float mergeDistSqr = mergeDist * mergeDist;
Rectangle intersection = Rectangle.Intersect(BoundingBox, ch.BoundingBox);
int intersectionArea = intersection.Width * intersection.Height;
int bboxArea = BoundingBox.Width * BoundingBox.Height;
int otherBboxArea = ch.BoundingBox.Width * ch.BoundingBox.Height;
if (Math.Abs(intersectionArea - bboxArea) < mergeDistSqr) { return; }
if (Math.Abs(intersectionArea - otherBboxArea) < mergeDistSqr) { return; }
for (int i = 0; i < segments.Length; i++)
{
for (int j = 0; j < ch.segments.Length; j++)
{
if (segments[i].IsHorizontal != ch.segments[j].IsHorizontal) { continue; }
if (ignoreEdge[i] || ch.ignoreEdge[j]) { continue; }
//the segments must be at different sides of the convex hulls to be merged
//(e.g. the right edge of a wall piece and the left edge of another one)
var segment1Center = (segments[i].Start.Pos + segments[i].End.Pos) / 2.0f;
var segment2Center = (ch.segments[j].Start.Pos + ch.segments[j].End.Pos) / 2.0f;
if (Vector2.Dot(segment1Center - BoundingBox.Center.ToVector2(), segment2Center - ch.BoundingBox.Center.ToVector2()) > 0) { continue; }
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], true);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], false);
}
}
}
}
for (int i = 0; i < segments.Length; i++)
{
if (ignoreEdge[i]) { continue; }
if (Vector2.DistanceSquared(segments[i].Start.Pos, segments[i].End.Pos) < 1.0f) { continue; }
for (int j = 0; j < ch.segments.Length; j++)
{
if (ch.ignoreEdge[j]) { continue; }
if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, ch.segments[j].End.Pos) < 1.0f) { continue; }
if (IsSegmentAInB(segments[i], ch.segments[j]))
{
ignoreEdge[i] = true;
if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, segments[i].Start.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, false, segments[i].End.Pos);
}
else if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, segments[i].End.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, false, segments[i].Start.Pos);
}
if (Vector2.DistanceSquared(ch.segments[j].End.Pos, segments[i].Start.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, true, segments[i].End.Pos);
}
else if (Vector2.DistanceSquared(ch.segments[j].End.Pos, segments[i].End.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, true, segments[i].Start.Pos);
}
}
else if (IsSegmentAInB(ch.segments[j], segments[i]))
{
ch.ignoreEdge[j] = true;
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < 4.0f)
{
ShiftSegmentPoint(i, false, ch.segments[j].End.Pos);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < 4.0f)
{
ShiftSegmentPoint(i, false, ch.segments[j].Start.Pos);
}
if (Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < 4.0f)
{
ShiftSegmentPoint(i, true, ch.segments[j].End.Pos);
}
else if (Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < 4.0f)
{
ShiftSegmentPoint(i, true, ch.segments[j].Start.Pos);
}
}
}
}
//ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++)
{
if (ch.IsPointInside(vertices[i].Pos))
{
if (ch.IsPointInside(vertices[(i + 1) % vertices.Length].Pos))
{
ignoreEdge[i] = true;
overlappingHulls.Add(ch);
}
}
}
}
private void ShiftSegmentPoint(int segmentIndex, bool end, Vector2 newPos)
{
var segment = segments[segmentIndex];
losOffsets[segmentIndex] ??= new VectorPair();
bool flipped = false;
if (Vector2.DistanceSquared(vertices[segmentIndex].Pos, segment.Start.Pos) > Vector2.DistanceSquared(vertices[segmentIndex].Pos, segment.End.Pos))
{
flipped = true;
}
if (end == !flipped)
{
losOffsets[segmentIndex].B = newPos;
mergeDistParallel = Math.Max(mergeDistParallel, MaxMergeLosVerticesDist.Value);
}
else
{
losOffsets[segmentIndex].A = newPos;
}
}
public static bool IsSegmentAInB(Segment a, Segment b)
{
if (Vector2.DistanceSquared(a.Start.Pos, a.End.Pos) > Vector2.DistanceSquared(b.Start.Pos, b.End.Pos))
{
return false;
}
Vector2 min = new Vector2(Math.Min(b.Start.Pos.X, b.End.Pos.X), Math.Min(b.Start.Pos.Y, b.End.Pos.Y));
min.X -= 1.0f; min.Y -= 1.0f;
if (a.Start.Pos.X < min.X) { return false; }
if (a.Start.Pos.Y < min.Y) { return false; }
if (a.End.Pos.X < min.X) { return false; }
if (a.End.Pos.Y < min.Y) { return false; }
Vector2 max = new Vector2(Math.Max(b.Start.Pos.X, b.End.Pos.X), Math.Max(b.Start.Pos.Y, b.End.Pos.Y));
max.X += 1.0f; max.Y += 1.0f;
if (a.Start.Pos.X > max.X) { return false; }
if (a.Start.Pos.Y > max.Y) { return false; }
if (a.End.Pos.X > max.X) { return false; }
if (a.End.Pos.Y > max.Y) { return false; }
float startDist = MathUtils.LineToPointDistanceSquared(b.Start.Pos, b.End.Pos, a.Start.Pos);
if (startDist > 1.0f) { return false; }
float endDist = MathUtils.LineToPointDistanceSquared(b.Start.Pos, b.End.Pos, a.End.Pos);
if (endDist > 1.0f) { return false; }
return true;
}
public bool IsPointInside(Vector2 point)
{
if (!BoundingBox.Contains(point)) { return false; }
Vector2 center = (vertices[0].Pos + vertices[1].Pos + vertices[2].Pos + vertices[3].Pos) * 0.25f;
for (int i = 0; i < 4; i++)
{
Vector2 segmentVector = vertices[(i + 1) % 4].Pos - vertices[i].Pos;
Vector2 centerToVertex = center - vertices[i].Pos;
Vector2 pointToVertex = point - vertices[i].Pos;
float dotCenter = Vector2.Dot(centerToVertex, segmentVector);
float dotPoint = Vector2.Dot(pointToVertex, segmentVector);
if ((dotCenter > 0f && dotPoint < 0f) || (dotCenter < 0f && dotPoint > 0f)) { return false; }
}
return true;
}
private void MergeSegments(Segment segment1, Segment segment2, bool startPointsMatch)
{
int startPointIndex = -1, endPointIndex = -1;
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.NearlyEquals(segment1.Start.Pos))
startPointIndex = i;
else if (vertices[i].Pos.NearlyEquals(segment1.End.Pos))
endPointIndex = i;
}
if (startPointIndex == -1 || endPointIndex == -1) { return; }
int startPoint2Index = -1, endPoint2Index = -1;
for (int i = 0; i < segment2.ConvexHull.vertices.Length; i++)
{
if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.Start.Pos))
startPoint2Index = i;
else if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.End.Pos))
endPoint2Index = i;
}
if (startPoint2Index == -1 || endPoint2Index == -1) { return; }
if (startPointsMatch)
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.Start.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.End.Pos) / 2.0f;
}
else
{
if (Vector2.DistanceSquared(losVertices[startPointIndex].Pos, segment1.Start.Pos) <
Vector2.DistanceSquared(losVertices[startPointIndex].Pos, segment1.End.Pos))
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))
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.End.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.Start.Pos) / 2.0f;
}
else
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.End.Pos + segment2.Start.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.Start.Pos + segment2.End.Pos) / 2.0f;
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);
overlappingHulls.Add(segment2.ConvexHull);
segment2.ConvexHull.overlappingHulls.Add(this);
Vector2 center = (losVertices[0].Pos + losVertices[1].Pos) / 2;
bool changed = false;
for (int i = 0; i < losVertices.Length; i++)
{
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 (!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,
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))
{
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)
{
foreach (var overlapping in overlappingHulls)
{
overlapping.MergeLosVertices(this, refreshOtherOverlappingHulls: false);
}
}
}
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)
@@ -447,7 +322,7 @@ namespace Barotrauma.Lights
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) *
Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix);
SetVertices(vertices.Select(v => v.Pos).ToArray(), losVertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix);
}
private void CalculateDimensions()
@@ -456,11 +331,10 @@ namespace Barotrauma.Lights
for (int i = 1; i < vertices.Length; i++)
{
if (vertices[i].Pos.X < minX) minX = vertices[i].Pos.X;
if (vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y;
if (vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X;
if (vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y;
minX = Math.Min(minX, vertices[i].Pos.X);
minY = Math.Min(minY, vertices[i].Pos.Y);
maxX = Math.Max(maxX, vertices[i].Pos.X);
maxY = Math.Max(maxY, vertices[i].Pos.Y);
}
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
@@ -471,21 +345,17 @@ namespace Barotrauma.Lights
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos += amount;
losVertices[i].Pos += amount;
losOffsets[i] = null;
segments[i].Start.Pos += amount;
segments[i].End.Pos += amount;
}
for (int i = 0; i < losVertices.Length; i++)
{
losVertices[i].Pos += amount;
}
LastVertexChangeTime = (float)Timing.TotalTime;
overlappingHulls.Clear();
for (int i = 0; i < 4; i++)
{
ignoreEdge[i] = false;
}
CalculateDimensions();
@@ -497,8 +367,8 @@ namespace Barotrauma.Lights
overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
ch.MergeOverlappingSegments(this);
MergeLosVertices(ch);
ch.MergeLosVertices(this);
}
}
}
@@ -511,23 +381,23 @@ namespace Barotrauma.Lights
foreach (ConvexHull ch in chList.List)
{
ch.overlappingHulls.Clear();
for (int i = 0; i < 4; i++)
for (int i = 0; i < ch.losOffsets.Length; i++)
{
ch.ignoreEdge[i] = false;
ch.losOffsets[i] = Vector2.Zero;
}
}
for (int i = 0; i < chList.List.Count; i++)
{
for (int j = i + 1; j < chList.List.Count; j++)
{
chList.List[i].MergeOverlappingSegments(chList.List[j]);
chList.List[j].MergeOverlappingSegments(chList.List[i]);
chList.List[i].MergeLosVertices(chList.List[j]);
chList.List[j].MergeLosVertices(chList.List[i]);
}
}
}
}
public void SetVertices(Vector2[] points, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null)
public void SetVertices(Vector2[] points, Vector2[] losPoints, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null)
{
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
@@ -535,39 +405,24 @@ namespace Barotrauma.Lights
for (int i = 0; i < 4; i++)
{
vertices[i] = new SegmentPoint(points[i], this);
losVertices[i] = new SegmentPoint(points[i], this);
losOffsets[i] = null;
vertices[i] = new SegmentPoint(points[i], this);
}
for (int i = 0; i < 4; i++)
for (int i = 0; i < 2; i++)
{
ignoreEdge[i] = false;
losVertices[i] = new SegmentPoint(losPoints[i], this);
losOffsets[i] = Vector2.Zero;
}
overlappingHulls.Clear();
int margin = 0;
if (Math.Abs(points[0].X - points[2].X) < Math.Abs(points[0].Y - points[2].Y))
{
losVertices[0].Pos = new Vector2(points[0].X + margin, points[0].Y);
losVertices[1].Pos = new Vector2(points[1].X + margin, points[1].Y);
losVertices[2].Pos = new Vector2(points[2].X - margin, points[2].Y);
losVertices[3].Pos = new Vector2(points[3].X - margin, points[3].Y);
}
else
{
losVertices[0].Pos = new Vector2(points[0].X, points[0].Y + margin);
losVertices[1].Pos = new Vector2(points[1].X, points[1].Y - margin);
losVertices[2].Pos = new Vector2(points[2].X, points[2].Y - margin);
losVertices[3].Pos = new Vector2(points[3].X, points[3].Y + margin);
}
if (rotationMatrix.HasValue)
{
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos = Vector2.Transform(vertices[i].Pos, rotationMatrix.Value);
}
for (int i = 0; i < losVertices.Length; i++)
{
losVertices[i].Pos = Vector2.Transform(losVertices[i].Pos, rotationMatrix.Value);
}
}
@@ -588,7 +443,7 @@ namespace Barotrauma.Lights
overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
MergeLosVertices(ch);
}
}
}
@@ -610,31 +465,17 @@ namespace Barotrauma.Lights
/// <summary>
/// Returns the segments that are facing towards viewPosition
/// </summary>
public void GetVisibleSegments(Vector2 viewPosition, List<Segment> visibleSegments, bool ignoreEdges)
public void GetVisibleSegments(Vector2 viewPosition, List<Segment> visibleSegments)
{
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i] && ignoreEdges) { continue; }
Vector2 pos1 = vertices[i].WorldPos;
Vector2 pos2 = vertices[(i + 1) % 4].WorldPos;
Vector2 middle = (pos1 + pos2) / 2;
Vector2 L = viewPosition - middle;
Vector2 N = new Vector2(
-(pos2.Y - pos1.Y),
pos2.X - pos1.X);
if (Vector2.Dot(N, L) > 0)
if (IsSegmentFacing(vertices[i].WorldPos, vertices[(i + 1) % 4].WorldPos, viewPosition))
{
visibleSegments.Add(segments[i]);
}
}
}
public void RefreshWorldPositions()
{
for (int i = 0; i < 4; i++)
@@ -662,34 +503,12 @@ namespace Barotrauma.Lights
ShadowVertexCount = 0;
//compute facing of each edge, using N*L
for (int i = 0; i < 4; i++)
for (int i = 0; i < losVertices.Length; i++)
{
if (ignoreEdge[i])
{
backFacing[i] = false;
continue;
}
Vector2 firstVertex = losVertices[i].Pos;
Vector2 secondVertex = losVertices[(i+1) % 4].Pos;
Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f);
Vector2 N = new Vector2(
-(secondVertex.Y - firstVertex.Y),
secondVertex.X - firstVertex.X);
backFacing[i] = (Vector2.Dot(N, L) < 0);
}
ShadowVertexCount = 0;
for (int i = 0; i < 4; i++)
{
if (!backFacing[i]) { continue; }
int currentIndex = i;
Vector3 vertexPos0 = new Vector3(losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos, 0.0f);
Vector3 vertexPos1 = new Vector3(losOffsets[currentIndex]?.B ?? losVertices[(currentIndex + 1) % 4].Pos, 0.0f);
int nextIndex = (currentIndex + 1) % 2;
Vector3 vertexPos0 = new Vector3(losVertices[currentIndex].Pos + losOffsets[currentIndex], 0.0f);
Vector3 vertexPos1 = new Vector3(losVertices[nextIndex].Pos + losOffsets[nextIndex], 0.0f);
if (Vector3.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
@@ -740,9 +559,24 @@ namespace Barotrauma.Lights
ShadowVertexCount += 6;
}
if (IsSegmentFacing(losVertices[0].Pos, losVertices[1].Pos, lightSourcePos))
{
Array.Reverse(ShadowVertices);
}
CalculateLosPenumbraVertices(lightSourcePos);
}
private static bool IsSegmentFacing(Vector2 segmentPos1, Vector2 segmentPos2, Vector2 viewPosition)
{
Vector2 segmentMid = (segmentPos1 + segmentPos2) / 2;
Vector2 segmentDiff = segmentPos2 - segmentPos1;
Vector2 segmentNormal = new Vector2(-segmentDiff.Y, segmentDiff.X);
Vector2 viewDirection = viewPosition - segmentMid;
return Vector2.Dot(segmentNormal, viewDirection) > 0;
}
private void CalculateLosPenumbraVertices(Vector2 lightSourcePos)
{
Vector3 offset = Vector3.Zero;
@@ -752,73 +586,104 @@ namespace Barotrauma.Lights
}
PenumbraVertexCount = 0;
for (int i = 0; i < 4; i++)
for (int i = 0; i < losVertices.Length; i++)
{
int currentIndex = i;
int prevIndex = (i + 3) % 4;
int nextIndex = (i + 1) % 4;
bool disjointed = losOffsets[i]?.A != null;
Vector2 vertexPos0 = losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos;
Vector2 vertexPos1 = losOffsets[currentIndex]?.B ?? losVertices[nextIndex].Pos;
int nextIndex = (i + 1) % 2;
Vector2 vertexPos0 = losVertices[currentIndex].Pos + losOffsets[currentIndex];
Vector2 vertexPos1 = losVertices[nextIndex].Pos + losOffsets[nextIndex];
if (Vector2.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
Vector3 penumbraStart = new Vector3(vertexPos0, 0.0f);
if (backFacing[currentIndex] && (disjointed || (!backFacing[prevIndex])))
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{
Vector3 penumbraStart = new Vector3(vertexPos0, 0.0f);
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + j + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + j + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + j + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + j + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
PenumbraVertices[PenumbraVertexCount + j + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
PenumbraVertexCount += 3;
PenumbraVertices[PenumbraVertexCount + j + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
disjointed = losOffsets[i]?.B != null;
if (backFacing[currentIndex] && (disjointed || (!backFacing[nextIndex])))
PenumbraVertexCount += 3;
penumbraStart = new Vector3(vertexPos1, 0.0f);
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{
Vector3 penumbraStart = new Vector3(vertexPos1, 0.0f);
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) + normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) + normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
PenumbraVertexCount += 3;
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
PenumbraVertexCount += 3;
}
}
public void DebugDraw(SpriteBatch spriteBatch)
{
//RecalculateAll(Submarine.MainSub);
//RefreshWorldPositions();
DrawLine(losVertices[0].Pos, losVertices[1].Pos, Color.Gray * 0.5f, width: 3);
DrawLine(losVertices[0].Pos + losOffsets[0], losVertices[1].Pos + losOffsets[1], Color.LightGreen, width: 2);
DrawLine(GameMain.GameScreen.Cam.Position + Vector2.One * 1000, GameMain.GameScreen.Cam.Position - Vector2.One * 1000, Color.Magenta, width: 2);
if (GameMain.LightManager.LightingEnabled)
{
for (int i = 0; i < vertices.Length; i++)
{
Vector2 start = vertices[i].Pos;
Vector2 end = vertices[(i + 1) % 4].Pos;
DrawLine(
start,
end, Color.Yellow * 0.5f,
width: 4);
}
}
void DrawLine(Vector2 vertexPos0, Vector2 vertexPos1, Color color, int width)
{
if (ParentEntity != null && ParentEntity.Submarine != null)
{
vertexPos0 += ParentEntity.Submarine.DrawPosition;
vertexPos1 += ParentEntity.Submarine.DrawPosition;
}
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);
}
}
@@ -889,16 +754,13 @@ namespace Barotrauma.Lights
{
HullLists.Remove(chList);
}
foreach (ConvexHull ch2 in overlappingHulls)
//create a new list because MergeLosVertices can edit overlappingHulls
foreach (ConvexHull ch2 in overlappingHulls.ToList())
{
for (int i = 0; i < 4; i++)
{
ch2.ignoreEdge[i] = false;
}
ch2.overlappingHulls.Remove(this);
foreach (ConvexHull ch in chList.List)
{
ch.MergeOverlappingSegments(ch2);
ch.MergeLosVertices(ch2);
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using System.Threading;
namespace Barotrauma.Lights
{
@@ -22,6 +23,9 @@ namespace Barotrauma.Lights
/// </summary>
const float ObstructLightsBehindCharactersZoomThreshold = 0.5f;
private Thread rayCastThread;
private Queue<RayCastTask> pendingRayCasts = new Queue<RayCastTask>();
public static Entity ViewTarget { get; set; }
private float currLightMapScale;
@@ -58,6 +62,8 @@ namespace Barotrauma.Lights
private readonly List<LightSource> lights;
public bool DebugLos;
public bool LosEnabled = true;
public float LosAlpha = 1f;
public LosMode LosMode = LosMode.Transparent;
@@ -68,6 +74,8 @@ namespace Barotrauma.Lights
private readonly Texture2D visionCircle;
private readonly Texture2D gapGlowTexture;
private Vector2 losOffset;
private int recalculationCount;
@@ -85,8 +93,16 @@ namespace Barotrauma.Lights
AmbientLight = new Color(20, 20, 20, 255);
rayCastThread = new Thread(UpdateRayCasts)
{
Name = "LightManager Raycast thread",
IsBackground = true //this should kill the thread if the game crashes
};
rayCastThread.Start();
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
gapGlowTexture = Sprite.LoadTexture("Content/Lights/pointlight_rays.png");
GameMain.Instance.ResolutionChanged += () =>
{
@@ -100,15 +116,12 @@ namespace Barotrauma.Lights
LosEffect = EffectLoader.Load("Effects/losshader");
SolidColorEffect = EffectLoader.Load("Effects/solidcolor");
if (lightEffect == null)
{
lightEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
lightEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true,
TextureEnabled = true,
Texture = LightSource.LightTexture
};
}
});
}
@@ -176,6 +189,51 @@ namespace Barotrauma.Lights
}
}
private sealed class RayCastTask
{
public LightSource LightSource;
public Vector2 DrawPos;
public float Rotation;
public RayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
{
LightSource = lightSource;
DrawPos = drawPos;
Rotation = rotation;
}
public void Calculate()
{
LightSource.RayCastTask(DrawPos, Rotation);
}
}
private static readonly object mutex = new object();
public void AddRayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
{
lock (mutex)
{
if (pendingRayCasts.Any(p => p.LightSource == lightSource)) { return; }
pendingRayCasts.Enqueue(new RayCastTask(lightSource, drawPos, rotation));
}
}
private void UpdateRayCasts()
{
while (true)
{
lock (mutex)
{
while (pendingRayCasts.Count > 0)
{
pendingRayCasts.Dequeue().Calculate();
}
}
Thread.Sleep(10);
}
}
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{
if (!LightingEnabled) { return; }
@@ -288,8 +346,8 @@ namespace Barotrauma.Lights
foreach (LightSource light in activeLights)
{
if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawSprite(spriteBatch, cam);
light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
light.DrawSprite(spriteBatch, cam);
}
GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
spriteBatch.End();
@@ -308,15 +366,46 @@ namespace Barotrauma.Lights
}
spriteBatch.End();
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(AmbientLight.Opaque().ToVector4());
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform, effect: SolidColorEffect);
Submarine.DrawDamageable(spriteBatch, null);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f);
Color glowColor = ToolBox.HSVToRGB(glowColorHSV.X, glowColorHSV.Y, glowColorHSV.Z);
Vector2 glowSpriteSize = new Vector2(gapGlowTexture.Width, gapGlowTexture.Height);
foreach (var gap in Gap.GapList)
{
if (gap.IsRoomToRoom || gap.Open <= 0.0f || gap.ConnectedWall == null) { continue; }
float a = MathHelper.Lerp(0.5f, 1.0f,
PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.05f, gap.GlowEffectT));
float scale = MathHelper.Lerp(0.5f, 2.0f,
PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.01f, gap.GlowEffectT));
float rot = PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.001f, gap.GlowEffectT) * MathHelper.TwoPi;
Vector2 spriteScale = new Vector2(gap.Rect.Width, gap.Rect.Height) / glowSpriteSize;
Vector2 drawPos = new Vector2(gap.DrawPosition.X, -gap.DrawPosition.Y);
spriteBatch.Draw(gapGlowTexture,
drawPos,
null,
glowColor * a,
rot,
glowSpriteSize / 2,
scale: Math.Max(spriteScale.X, spriteScale.Y) * scale,
SpriteEffects.None,
layerDepth: 0);
}
spriteBatch.End();
GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
spriteBatch.End();
graphics.BlendState = BlendState.Additive;
//draw the focused item and character to highlight them,
//and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites)
//---------------------------------------------------------------------------------------------------
@@ -389,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);
@@ -566,7 +666,7 @@ namespace Barotrauma.Lights
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{
if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) return;
if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) { return; }
if (ViewTarget == null) return;
graphics.SetRenderTarget(LosTexture);
@@ -598,23 +698,52 @@ namespace Barotrauma.Lights
if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
{
Vector2 pos = ViewTarget.DrawPosition;
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);
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>();
List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
foreach (ConvexHull convexHull in convexHulls)
{
if (!convexHull.Enabled || !convexHull.Intersects(camView)) continue;
if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
Vector2 relativeLightPos = pos;
if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position;
if (convexHull.ParentEntity?.Submarine != null) { relativeLightPos -= convexHull.ParentEntity.Submarine.Position; }
convexHull.CalculateLosVertices(relativeLightPos);
@@ -647,6 +776,21 @@ namespace Barotrauma.Lights
graphics.SetRenderTarget(null);
}
public void DebugDrawLos(SpriteBatch spriteBatch, Camera cam)
{
Vector2 pos = ViewTarget?.Position ?? cam.Position;
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform);
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();
}
public void ClearLights()
{
lights.Clear();

View File

@@ -15,6 +15,7 @@ namespace Barotrauma.Lights
public bool Persistent;
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; } = new Dictionary<Identifier, SerializableProperty>();
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, alwaysUseInstanceValues: true), Editable]
@@ -228,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; }
@@ -241,12 +243,30 @@ 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; }
private enum LightVertexState
{
UpToDate,
PendingRayCasts,
PendingVertexRecalculation,
}
private LightVertexState state;
private Vector2 calculatedDrawPos;
private readonly Dictionary<Submarine, Vector2> diffToSub;
private DynamicVertexBuffer lightVolumeBuffer;
@@ -255,7 +275,6 @@ namespace Barotrauma.Lights
private int indexCount;
private Vector2 translateVertices;
private float rotateVertices;
private readonly LightSourceParams lightSourceParams;
@@ -295,7 +314,6 @@ namespace Barotrauma.Lights
if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null)
{
rotateVertices = rotation - prevCalculatedRotation;
return;
}
@@ -647,13 +665,19 @@ namespace Barotrauma.Lights
}
}
private static readonly List<Segment> visibleSegments = new List<Segment>();
private static readonly List<SegmentPoint> points = new List<SegmentPoint>();
private static readonly List<Vector2> output = new List<Vector2>();
private static readonly SegmentPoint[] boundaryCorners = new SegmentPoint[4];
private List<Vector2> FindRaycastHits()
private static readonly object mutex = new object();
private readonly List<Segment> visibleSegments = new List<Segment>();
private readonly List<SegmentPoint> points = new List<SegmentPoint>();
private readonly List<Vector2> verts = new List<Vector2>();
private readonly SegmentPoint[] boundaryCorners = new SegmentPoint[4];
private void FindRaycastHits()
{
if (!CastShadows || Range < 1.0f || Color.A < 1) { return null; }
if (!CastShadows || Range < 1.0f || Color.A < 1)
{
state = LightVertexState.PendingVertexRecalculation;
return;
}
Vector2 drawPos = position;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
@@ -666,8 +690,18 @@ namespace Barotrauma.Lights
if (!chList.IsHidden.Contains(hull))
{
//find convexhull segments that are close enough and facing towards the light source
hull.RefreshWorldPositions();
hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false);
lock (mutex)
{
hull.RefreshWorldPositions();
hull.GetVisibleSegments(drawPos, visibleSegments);
foreach (var visibleSegment in visibleSegments)
{
if (visibleSegment.ConvexHull?.ParentEntity?.Submarine != null)
{
visibleSegment.SubmarineDrawPos = visibleSegment.ConvexHull.ParentEntity.Submarine.DrawPosition;
}
}
}
}
}
foreach (ConvexHull hull in chList.List)
@@ -676,17 +710,19 @@ namespace Barotrauma.Lights
}
}
//add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source
state = LightVertexState.PendingRayCasts;
GameMain.LightManager.AddRayCastTask(this, drawPos, rotation);
}
//(might be more effective to calculate if we actually need these extra points)
public void RayCastTask(Vector2 drawPos, float rotation)
{
Vector2 drawOffset = Vector2.Zero;
float boundsExtended = TextureRange;
if (OverrideLightTexture != null)
{
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
float cosAngle = (float)Math.Cos(rotation);
float sinAngle = -(float)Math.Sin(rotation);
var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
@@ -706,6 +742,10 @@ namespace Barotrauma.Lights
drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle;
}
//add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source
//(might be more effective to calculate if we actually need these extra points)
Vector2 boundsMin = drawPos + drawOffset + new Vector2(-boundsExtended, -boundsExtended);
Vector2 boundsMax = drawPos + drawOffset + new Vector2(boundsExtended, boundsExtended);
boundaryCorners[0] = new SegmentPoint(boundsMax, null);
@@ -719,197 +759,197 @@ namespace Barotrauma.Lights
visibleSegments.Add(s);
}
//Generate new points at the intersections between segments
//This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++)
lock (mutex)
{
Vector2 p1a = visibleSegments[i].Start.WorldPos;
Vector2 p1b = visibleSegments[i].End.WorldPos;
for (int j = i + 1; j < visibleSegments.Count; j++)
//Generate new points at the intersections between segments
//This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++)
{
//ignore intersections between parallel axis-aligned segments
if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
{
continue;
}
Vector2 p1a = visibleSegments[i].Start.WorldPos;
Vector2 p1b = visibleSegments[i].End.WorldPos;
Vector2 p2a = visibleSegments[j].Start.WorldPos;
Vector2 p2b = visibleSegments[j].End.WorldPos;
if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
Vector2.DistanceSquared(p1b, p2b) < 5.0f)
for (int j = i + 1; j < visibleSegments.Count; j++)
{
continue;
}
bool intersects;
Vector2 intersection = Vector2.Zero;
if (visibleSegments[i].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p2a, p2b, p1a, p1b, visibleSegments[i].IsHorizontal, out intersection);
}
else if (visibleSegments[j].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p1a, p1b, p2a, p2b, visibleSegments[j].IsHorizontal, out intersection);
}
else
{
intersects = MathUtils.GetLineIntersection(p1a, p1b, p2a, p2b, out intersection);
}
if (intersects)
{
SegmentPoint start = visibleSegments[i].Start;
SegmentPoint end = visibleSegments[i].End;
SegmentPoint mid = new SegmentPoint(intersection, null);
if (visibleSegments[i].ConvexHull?.ParentEntity?.Submarine != null)
{
mid.Pos -= visibleSegments[i].ConvexHull.ParentEntity.Submarine.DrawPosition;
}
if (Vector2.DistanceSquared(start.WorldPos, mid.WorldPos) < 5.0f ||
Vector2.DistanceSquared(end.WorldPos, mid.WorldPos) < 5.0f)
//ignore intersections between parallel axis-aligned segments
if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
{
continue;
}
Segment seg1 = new Segment(start, mid, visibleSegments[i].ConvexHull)
{
IsHorizontal = visibleSegments[i].IsHorizontal,
};
Vector2 p2a = visibleSegments[j].Start.WorldPos;
Vector2 p2b = visibleSegments[j].End.WorldPos;
Segment seg2 = new Segment(mid, end, visibleSegments[i].ConvexHull)
if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
Vector2.DistanceSquared(p1b, p2b) < 5.0f)
{
IsHorizontal = visibleSegments[i].IsHorizontal
};
continue;
}
visibleSegments[i] = seg1;
visibleSegments.Insert(i + 1, seg2);
bool intersects;
Vector2 intersection = Vector2.Zero;
if (visibleSegments[i].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p2a, p2b, p1a, p1b, visibleSegments[i].IsHorizontal, out intersection);
}
else if (visibleSegments[j].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p1a, p1b, p2a, p2b, visibleSegments[j].IsHorizontal, out intersection);
}
else
{
intersects = MathUtils.GetLineSegmentIntersection(p1a, p1b, p2a, p2b, out intersection);
}
if (intersects)
{
SegmentPoint start = visibleSegments[i].Start;
SegmentPoint end = visibleSegments[i].End;
SegmentPoint mid = new SegmentPoint(intersection, null);
mid.Pos -= visibleSegments[i].SubmarineDrawPos;
if (Vector2.DistanceSquared(start.WorldPos, mid.WorldPos) < 5.0f ||
Vector2.DistanceSquared(end.WorldPos, mid.WorldPos) < 5.0f)
{
continue;
}
Segment seg1 = new Segment(start, mid, visibleSegments[i].ConvexHull)
{
IsHorizontal = visibleSegments[i].IsHorizontal,
};
Segment seg2 = new Segment(mid, end, visibleSegments[i].ConvexHull)
{
IsHorizontal = visibleSegments[i].IsHorizontal
};
visibleSegments[i] = seg1;
visibleSegments.Insert(i + 1, seg2);
i--;
break;
}
}
}
points.Clear();
//remove segments that fall out of bounds
for (int i = 0; i < visibleSegments.Count; i++)
{
Segment s = visibleSegments[i];
if (Math.Abs(s.Start.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.Start.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
{
visibleSegments.RemoveAt(i);
i--;
break;
}
else
{
points.Add(s.Start);
points.Add(s.End);
}
}
}
points.Clear();
//remove segments that fall out of bounds
for (int i = 0; i < visibleSegments.Count; i++)
{
Segment s = visibleSegments[i];
if (Math.Abs(s.Start.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.Start.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
{
visibleSegments.RemoveAt(i);
i--;
for (int j = Math.Min(i + 4, points.Count - 1); j > i; j--)
{
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
{
points.RemoveAt(j);
}
}
}
else
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Add(s.Start);
points.Add(s.End);
points.Sort(compareCCW);
}
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: " + drawPos + ", Hull verts:\n");
foreach (SegmentPoint sp in points)
{
sb.AppendLine(sp.Pos.ToString());
}
DebugConsole.ThrowError(sb.ToString(), e);
}
visibleSegments.Sort((s1, s2) =>
MathUtils.LineToPointDistanceSquared(s1.Start.WorldPos, s1.End.WorldPos, drawPos)
.CompareTo(MathUtils.LineToPointDistanceSquared(s2.Start.WorldPos, s2.End.WorldPos, drawPos)));
verts.Clear();
foreach (SegmentPoint p in points)
{
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it
var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
if (intersection1.index < 0) { return; }
var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
if (intersection2.index < 0) { return; }
Segment seg1 = visibleSegments[intersection1.index];
Segment seg2 = visibleSegments[intersection2.index];
bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f;
bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f;
if (isPoint1 && isPoint2)
{
//hit at the current segmentpoint -> place the segmentpoint into the list
verts.Add(p.WorldPos);
foreach (ConvexHullList hullList in convexHullsInRange)
{
hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull);
hullList.IsHidden.Remove(seg2.ConvexHull);
}
}
else if (intersection1.index != intersection2.index)
{
//the raycasts landed on different segments
//we definitely want to generate new geometry here
verts.Add(isPoint1 ? p.WorldPos : intersection1.pos);
verts.Add(isPoint2 ? p.WorldPos : intersection2.pos);
foreach (ConvexHullList hullList in convexHullsInRange)
{
hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull);
hullList.IsHidden.Remove(seg2.ConvexHull);
}
}
//if neither of the conditions above are met, we just assume
//that the raycasts both resulted on the same segment
//and creating geometry here would be wasteful
}
}
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
for (int i = 0; i < verts.Count - 1; i++)
{
for (int j = Math.Min(i + 4, points.Count-1); j > i; j--)
for (int j = Math.Min(i + 4, verts.Count - 1); j > i; j--)
{
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
if (Math.Abs(verts[i].X - verts[j].X) < 6 &&
Math.Abs(verts[i].Y - verts[j].Y) < 6)
{
points.RemoveAt(j);
verts.RemoveAt(j);
}
}
}
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Sort(compareCCW);
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: " + drawPos + ", Hull verts:\n");
foreach (SegmentPoint sp in points)
{
sb.AppendLine(sp.Pos.ToString());
}
DebugConsole.ThrowError(sb.ToString(), e);
}
visibleSegments.Sort((s1, s2) =>
MathUtils.LineToPointDistanceSquared(s1.Start.WorldPos, s1.End.WorldPos, drawPos)
.CompareTo(MathUtils.LineToPointDistanceSquared(s2.Start.WorldPos, s2.End.WorldPos, drawPos)));
output.Clear();
foreach (SegmentPoint p in points)
{
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it
var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
if (intersection1.index < 0) { return null; }
var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
if (intersection2.index < 0) { return null; }
Segment seg1 = visibleSegments[intersection1.index];
Segment seg2 = visibleSegments[intersection2.index];
bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f;
bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f;
if (isPoint1 && isPoint2)
{
//hit at the current segmentpoint -> place the segmentpoint into the list
output.Add(p.WorldPos);
foreach (ConvexHullList hullList in convexHullsInRange)
{
hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull);
hullList.IsHidden.Remove(seg2.ConvexHull);
}
}
else if (intersection1.index != intersection2.index)
{
//the raycasts landed on different segments
//we definitely want to generate new geometry here
output.Add(isPoint1 ? p.WorldPos : intersection1.pos);
output.Add(isPoint2 ? p.WorldPos : intersection2.pos);
foreach (ConvexHullList hullList in convexHullsInRange)
{
hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull);
hullList.IsHidden.Remove(seg2.ConvexHull);
}
}
//if neither of the conditions above are met, we just assume
//that the raycasts both resulted on the same segment
//and creating geometry here would be wasteful
}
//remove points that are very close to each other
for (int i = 0; i < output.Count - 1; i++)
{
for (int j = Math.Min(i + 4, output.Count - 1); j > i; j--)
{
if (Math.Abs(output[i].X - output[j].X) < 6 &&
Math.Abs(output[i].Y - output[j].Y) < 6)
{
output.RemoveAt(j);
}
}
}
return output;
calculatedDrawPos = drawPos;
state = LightVertexState.PendingVertexRecalculation;
}
private static (int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
@@ -954,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)
@@ -987,8 +1027,7 @@ namespace Barotrauma.Lights
indices = new short[indexCount];
}
Vector2 drawPos = position;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
Vector2 drawPos = calculatedDrawPos;
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
@@ -1042,7 +1081,7 @@ namespace Barotrauma.Lights
//calculate normal of first segment
Vector2 nDiff1 = vertex - nextVertex;
float tx = nDiff1.X; nDiff1.X = -nDiff1.Y; nDiff1.Y = tx;
nDiff1 = new Vector2(-nDiff1.Y, nDiff1.X);
nDiff1 /= Math.Max(Math.Abs(nDiff1.X), Math.Abs(nDiff1.Y));
//if the normal is pointing towards the light origin
//rather than away from it, invert it
@@ -1050,21 +1089,23 @@ namespace Barotrauma.Lights
//calculate normal of second segment
Vector2 nDiff2 = prevVertex - vertex;
tx = nDiff2.X; nDiff2.X = -nDiff2.Y; nDiff2.Y = tx;
nDiff2 /= Math.Max(Math.Abs(nDiff2.X),Math.Abs(nDiff2.Y));
nDiff2 = new Vector2(-nDiff2.Y, nDiff2.X);
nDiff2 /= Math.Max(Math.Abs(nDiff2.X), Math.Abs(nDiff2.Y));
//if the normal is pointing towards the light origin
//rather than away from it, invert it
if (Vector2.DistanceSquared(nDiff2, rawDiff) > Vector2.DistanceSquared(-nDiff2, rawDiff)) nDiff2 = -nDiff2;
//add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur
Vector2 nDiff = nDiff1 * 40.0f;
if (MathUtils.GetLineIntersection(vertex + (nDiff1 * 40.0f), nextVertex + (nDiff1 * 40.0f), vertex + (nDiff2 * 40.0f), prevVertex + (nDiff2 * 40.0f), true, out Vector2 intersection))
float blurDistance = 40.0f;
Vector2 nDiff = nDiff1 * blurDistance;
if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance), true, out Vector2 intersection))
{
nDiff = intersection - vertex;
if (nDiff.LengthSquared() > 10000.0f)
if (nDiff.LengthSquared() > 100.0f * 100.0f)
{
nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y)); nDiff *= 100.0f;
nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y));
nDiff *= 100.0f;
}
}
@@ -1162,7 +1203,6 @@ namespace Barotrauma.Lights
}
translateVertices = Vector2.Zero;
rotateVertices = 0.0f;
prevCalculatedPosition = position;
prevCalculatedRotation = rotation;
}
@@ -1340,31 +1380,41 @@ namespace Barotrauma.Lights
if (NeedsRecalculation && allowRecalculation)
{
recalculationCount++;
var verts = FindRaycastHits();
if (verts == null)
if (state == LightVertexState.UpToDate)
{
#if DEBUG
DebugConsole.ThrowError($"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}");
#endif
Enabled = false;
return;
recalculationCount++;
FindRaycastHits();
}
else if (state == LightVertexState.PendingVertexRecalculation)
{
if (verts == null)
{
#if DEBUG
DebugConsole.ThrowError($"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}");
#endif
Enabled = false;
return;
}
CalculateLightVertices(verts);
CalculateLightVertices(verts);
LastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = false;
LastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = needsRecalculationWhenUpToDate;
needsRecalculationWhenUpToDate = false;
state = LightVertexState.UpToDate;
}
}
if (vertexCount == 0) { return; }
Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition;
lightEffect.World =
Matrix.CreateTranslation(-new Vector3(position, 0.0f)) *
Matrix.CreateRotationZ(rotateVertices - MathHelper.ToRadians(LightSourceParams.Rotation)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(LightSourceParams.Rotation)) *
Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) *
transform;
if (vertexCount == 0) { return; }
lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f * CurrentBrightness)) / 255.0f;
if (OverrideLightTexture != null)

View File

@@ -738,8 +738,8 @@ namespace Barotrauma
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
Vector2 topLeft = rectCenter + viewOffset;
Vector2 bottomRight = rectCenter + (viewOffset + new Vector2(Width, Height));
Vector2 topLeft = rectCenter + viewOffset - rect.Location.ToVector2();
Vector2 bottomRight = topLeft + new Vector2(Width, Height);
Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale;
int startX = (int)Math.Floor(-topLeft.X / mapTileSize.X) - 1;

View File

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

View File

@@ -55,21 +55,11 @@ namespace Barotrauma
{
if (!CastShadow) { return; }
if (convexHulls == null)
{
convexHulls = new List<ConvexHull>();
}
Vector2 halfSize = size / 2;
Vector2[] verts = new Vector2[]
{
position + new Vector2(-halfSize.X, halfSize.Y),
position + new Vector2(halfSize.X, halfSize.Y),
position + new Vector2(halfSize.X, -halfSize.Y),
position + new Vector2(-halfSize.X, -halfSize.Y),
};
var h = new ConvexHull(verts, Color.Black, this);
convexHulls ??= new List<ConvexHull>();
var h = new ConvexHull(
new Rectangle((position - size / 2).ToPoint(), size.ToPoint()),
IsHorizontal,
this);
if (Math.Abs(rotation) > 0.001f)
{
h.Rotate(position, rotation);

View File

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

View File

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

View File

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

View File

@@ -3077,8 +3077,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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ namespace Barotrauma
private RenderTarget2D renderTargetWater;
private RenderTarget2D renderTargetFinal;
private readonly Effect damageEffect;
public readonly Effect DamageEffect;
private readonly Texture2D damageStencil;
private readonly Texture2D distortTexture;
@@ -39,7 +39,7 @@ namespace Barotrauma
};
//var blurEffect = LoadEffect("Effects/blurshader");
damageEffect = EffectLoader.Load("Effects/damageshader");
DamageEffect = EffectLoader.Load("Effects/damageshader");
PostProcessEffect = EffectLoader.Load("Effects/postprocess");
GradientEffect = EffectLoader.Load("Effects/gradientshader");
GrainEffect = EffectLoader.Load("Effects/grainshader");
@@ -47,9 +47,9 @@ namespace Barotrauma
BlueprintEffect = EffectLoader.Load("Effects/blueprintshader");
damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png");
damageEffect.Parameters["xStencil"].SetValue(damageStencil);
damageEffect.Parameters["aMultiplier"].SetValue(50.0f);
damageEffect.Parameters["cMultiplier"].SetValue(200.0f);
DamageEffect.Parameters["xStencil"].SetValue(damageStencil);
DamageEffect.Parameters["aMultiplier"].SetValue(50.0f);
DamageEffect.Parameters["cMultiplier"].SetValue(200.0f);
distortTexture = TextureLoader.FromFile("Content/Effects/distortnormals.png");
PostProcessEffect.Parameters["xDistortTexture"].SetValue(distortTexture);
@@ -346,12 +346,13 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontParticles", sw.ElapsedTicks);
sw.Restart();
DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"];
spriteBatch.Begin(SpriteSortMode.Immediate,
BlendState.NonPremultiplied, SamplerState.LinearWrap,
null, null,
damageEffect,
DamageEffect,
cam.Transform);
Submarine.DrawDamageable(spriteBatch, damageEffect, false);
Submarine.DrawDamageable(spriteBatch, DamageEffect, false);
spriteBatch.End();
sw.Stop();
@@ -378,7 +379,7 @@ namespace Barotrauma
{
graphics.DepthStencilState = DepthStencilState.None;
graphics.SamplerStates[0] = SamplerState.LinearWrap;
graphics.BlendState = Lights.CustomBlendStates.Multiplicative;
graphics.BlendState = CustomBlendStates.Multiplicative;
Quad.UseBasicEffect(GameMain.LightManager.LightMap);
Quad.Render();
}
@@ -409,6 +410,7 @@ namespace Barotrauma
{
GameMain.LightManager.LosEffect.CurrentTechnique = GameMain.LightManager.LosEffect.Techniques["LosShader"];
GameMain.LightManager.LosEffect.Parameters["blurDistance"].SetValue(0.005f);
GameMain.LightManager.LosEffect.Parameters["xTexture"].SetValue(renderTargetBackground);
GameMain.LightManager.LosEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.LosTexture);
GameMain.LightManager.LosEffect.Parameters["xLosAlpha"].SetValue(GameMain.LightManager.LosAlpha);
@@ -434,8 +436,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)
@@ -519,6 +524,11 @@ namespace Barotrauma
spriteBatch.End();
}
if (GameMain.LightManager.DebugLos)
{
GameMain.LightManager.DebugDrawLos(spriteBatch, cam);
}
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:PostProcess", sw.ElapsedTicks);
sw.Restart();

View File

@@ -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;
@@ -82,6 +81,7 @@ namespace Barotrauma
{
GameMain.Instance.ResolutionChanged += () =>
{
SetMenuTabPositioning();
CreateHostServerFields();
CreateCampaignSetupUI();
SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
@@ -444,31 +444,33 @@ namespace Barotrauma
var relativeSize = new Vector2(0.6f, 0.65f);
var minSize = new Point(600, 400);
var maxSize = new Point(2000, 1500);
var anchor = Anchor.CenterRight;
var pivot = Pivot.CenterRight;
Vector2 relativeSpacing = new Vector2(0.05f, 0.0f);
menuTabs = new Dictionary<Tab, GUIFrame>();
var anchor = Anchor.Center;
var pivot = Pivot.Center;
Vector2 relativeOffset = new Vector2(0.05f, 0.0f);
menuTabs[Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing },
style: null);
menuTabs[Tab.Settings].CanBeFocused = false;
menuTabs[Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing });
menuTabs[Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing });
menuTabs = new Dictionary<Tab, GUIFrame>
{
[Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset },
style: null)
{
CanBeFocused = false
},
[Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }),
[Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
};
CreateCampaignSetupUI();
var hostServerScale = new Vector2(0.7f, 1.2f);
menuTabs[Tab.HostServer] = new GUIFrame(new RectTransform(
Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))
{ RelativeOffset = relativeSpacing });
{ RelativeOffset = relativeOffset });
CreateHostServerFields();
//----------------------------------------------------------------------
menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing });
menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset });
CreateTutorialTab();
this.game = game;
@@ -489,6 +491,20 @@ namespace Barotrauma
SelectTab(Tab.Empty);
return true;
};
SetMenuTabPositioning();
}
private void SetMenuTabPositioning()
{
foreach (GUIFrame menuTab in menuTabs.Values)
{
var anchor = GUI.IsUltrawide ? Anchor.Center : Anchor.CenterRight;
var pivot = GUI.IsUltrawide ? Pivot.Center : Pivot.CenterRight;
Vector2 relativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
menuTab.RectTransform.SetPosition(anchor, pivot);
menuTab.RectTransform.RelativeOffset = relativeOffset;
}
}
private void CreateTutorialTab()
@@ -1532,10 +1548,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);

View File

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

View File

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

View File

@@ -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;
};
@@ -2850,7 +2855,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();
@@ -5794,7 +5799,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>();
@@ -5907,6 +5915,11 @@ namespace Barotrauma
spriteBatch.End();
}
if (GameMain.LightManager.DebugLos)
{
GameMain.LightManager.DebugDrawLos(spriteBatch, cam);
}
//-------------------- HUD -----------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);

View File

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

View File

@@ -189,6 +189,7 @@ namespace Barotrauma.Steam
ModProject modProject = new ModProject(tempPkg)
{
ModVersion = modVersion,
Name = title,
ExpectedHash = tempPkg.CalculateHash(name: title, modVersion: modVersion)
};
modProject.Save(stagingFileListPath);

View File

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

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.0.13.2</Version>
<Version>1.0.20.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.0.13.2</Version>
<Version>1.0.20.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -5,6 +5,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; }
Texture2D xStencil;
sampler StencilSampler = sampler_state { Texture = <xStencil>; };
float4 solidColor;
float4 inColor;
float aCutoff;
@@ -16,7 +18,6 @@ float cMultiplier;
float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = xTexture.Sample(TextureSampler, texCoord) * inColor;
float4 stencilColor = xStencil.Sample(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;
@@ -30,6 +31,18 @@ float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord
min(aDiff * aMultiplier, c.a));
}
float4 solidColorStencil(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = xTexture.Sample(TextureSampler, texCoord) * inColor;
float4 stencilColor = xStencil.Sample(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;
clip(aDiff);
return float4(solidColor.rgb, solidColor.a * min(aDiff * aMultiplier, c.a));
}
technique StencilShader
{
pass Pass1
@@ -37,3 +50,11 @@ technique StencilShader
PixelShader = compile ps_4_0_level_9_1 main();
}
}
technique StencilShaderSolidColor
{
pass Pass1
{
PixelShader = compile ps_4_0_level_9_1 solidColorStencil();
}
}

View File

@@ -5,6 +5,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; }
Texture xStencil;
sampler StencilSampler = sampler_state { Texture = <xStencil>; };
float4 solidColor;
float4 inColor;
float aCutoff;
@@ -16,7 +18,6 @@ float cMultiplier;
float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = tex2D(TextureSampler, texCoord) * inColor;
float4 stencilColor = tex2D(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;
@@ -30,6 +31,18 @@ float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord
min(aDiff * aMultiplier, c.a));
}
float4 solidColorStencil(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = tex2D(TextureSampler, texCoord) * inColor;
float4 stencilColor = tex2D(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;
clip(aDiff);
return float4(solidColor.rgb, solidColor.a * min(aDiff * aMultiplier, c.a));
}
technique StencilShader
{
pass Pass1
@@ -37,3 +50,11 @@ technique StencilShader
PixelShader = compile ps_2_0 main();
}
}
technique StencilShaderSolidColor
{
pass Pass1
{
PixelShader = compile ps_2_0 solidColorStencil();
}
}

View File

@@ -30,11 +30,18 @@ float xLosAlpha;
float4 xColor;
float blurDistance;
float4 mainPS(VertexShaderOutput input) : COLOR0
{
float4 sampleColor = xTexture.Sample(TextureSampler, input.TexCoords);
float4 losColor = xLosTexture.Sample(LosSampler, input.TexCoords);
float4 losColor = xLosTexture.Sample(LosSampler, float2(input.TexCoords.x + blurDistance, input.TexCoords.y + blurDistance));
losColor += xLosTexture.Sample(LosSampler, float2(input.TexCoords.x - blurDistance, input.TexCoords.y - blurDistance));
losColor += xLosTexture.Sample(LosSampler, float2(input.TexCoords.x + blurDistance, input.TexCoords.y - blurDistance));
losColor += xLosTexture.Sample(LosSampler, float2(input.TexCoords.x - blurDistance, input.TexCoords.y + blurDistance));
losColor = losColor * 0.25f;
float obscureAmount = 1.0f - losColor.r;
float4 outColor = float4(
@@ -53,4 +60,4 @@ technique LosShader
VertexShader = compile vs_4_0_level_9_1 mainVS();
PixelShader = compile ps_4_0_level_9_1 mainPS();
}
}
}

View File

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

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.0.13.2</Version>
<Version>1.0.20.1</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,8 +6,8 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.0.13.2</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>

View File

@@ -6,8 +6,8 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.0.13.2</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>

View File

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

View File

@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using System.Linq;
namespace Barotrauma
{
@@ -16,11 +17,10 @@ namespace Barotrauma
foreach (var kvp in spawnedResources)
{
msg.WriteByte((byte)kvp.Value.Count);
var rotation = resourceClusters[kvp.Key].Rotation;
msg.WriteSingle(rotation);
foreach (var r in kvp.Value)
msg.WriteSingle(kvp.Value.FirstOrDefault()?.Rotation ?? 0.0f);
foreach (var item in kvp.Value)
{
r.WriteSpawnData(msg, r.ID, Entity.NullEntityID, 0, -1);
item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0, -1);
}
}
@@ -28,9 +28,9 @@ namespace Barotrauma
{
msg.WriteIdentifier(kvp.Key);
msg.WriteByte((byte)kvp.Value.Length);
foreach (var i in kvp.Value)
foreach (var item in kvp.Value)
{
msg.WriteUInt16(i.ID);
msg.WriteUInt16(item.ID);
}
}
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Steam;
namespace Barotrauma
{
@@ -1310,6 +1309,10 @@ namespace Barotrauma
public override bool TryPurchase(Client client, int price)
{
//disconnected clients can never purchase anything
//(can happen e.g. if someone starts a vote to buy something and then disconnects)
if (client != null && !GameMain.Server.ConnectedClients.Contains(client)) { return false; }
Wallet wallet = GetWallet(client);
if (!AllowedToManageWallets(client))
{
@@ -1359,6 +1362,12 @@ namespace Barotrauma
modeElement.Add(Settings.Save());
modeElement.Add(SaveStats());
modeElement.Add(Bank.Save());
if (GameMain.GameSession?.EventManager != null)
{
modeElement.Add(GameMain.GameSession?.EventManager.Save());
}
CampaignMetadata?.Save(modeElement);
Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement);

View File

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

View File

@@ -185,7 +185,7 @@ namespace Barotrauma.Items.Components
//already connected, no need to do anything
if (Connections[i].Wires.Contains(newWire)) { continue; }
newWire.Connect(Connections[i], true, true);
newWire.TryConnect(Connections[i], true, true);
Connections[i].TryAddLink(newWire);
var otherConnection = newWire.OtherConnection(Connections[i]);

View File

@@ -23,15 +23,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;
}
}
@@ -136,8 +134,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, listenIp);
@@ -1680,38 +1676,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)
@@ -3138,7 +3150,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)
@@ -3193,7 +3205,7 @@ namespace Barotrauma.Networking
//msg sent by the server
if (senderCharacter == null)
{
senderName = serverName;
senderName = ServerName;
}
else //msg sent by an AI character
{
@@ -3227,7 +3239,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
{
@@ -3459,33 +3471,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)

View File

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

View File

@@ -42,7 +42,11 @@ namespace Barotrauma
{
if (passed)
{
GameMain.Server?.SwitchSubmarine();
if (GameMain.Server != null && !GameMain.Server.TrySwitchSubmarine())
{
passed = false;
State = VoteState.Failed;
}
}
else
{

View File

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

View File

@@ -6,8 +6,8 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.0.13.2</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>

View File

@@ -95,10 +95,7 @@ namespace Barotrauma
{
get
{
if (visibleHulls == null)
{
visibleHulls = Character.GetVisibleHulls();
}
visibleHulls ??= Character.GetVisibleHulls();
return visibleHulls;
}
private set
@@ -425,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
@@ -473,7 +465,7 @@ 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 || IsCurrentPathUnreachable || 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();

View File

@@ -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";
}
@@ -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;
@@ -3504,10 +3523,10 @@ namespace Barotrauma
private readonly float stateResetCooldown = 10;
private float stateResetTimer;
private bool isStateChanged;
private readonly Dictionary<AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<AITrigger, CharacterParams.TargetParams>();
private readonly HashSet<AITrigger> inactiveTriggers = new HashSet<AITrigger>();
private readonly Dictionary<StatusEffect.AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<StatusEffect.AITrigger, CharacterParams.TargetParams>();
private readonly HashSet<StatusEffect.AITrigger> inactiveTriggers = new HashSet<StatusEffect.AITrigger>();
public void LaunchTrigger(AITrigger trigger)
public void LaunchTrigger(StatusEffect.AITrigger trigger)
{
if (trigger.IsTriggered) { return; }
if (activeTriggers.ContainsKey(trigger)) { return; }
@@ -3527,7 +3546,7 @@ namespace Barotrauma
{
foreach (var triggerObject in activeTriggers)
{
AITrigger trigger = triggerObject.Key;
StatusEffect.AITrigger trigger = triggerObject.Key;
if (trigger.IsPermanent) { continue; }
trigger.UpdateTimer(deltaTime);
if (!trigger.IsActive)
@@ -3537,7 +3556,7 @@ namespace Barotrauma
inactiveTriggers.Add(trigger);
}
}
foreach (AITrigger trigger in inactiveTriggers)
foreach (StatusEffect.AITrigger trigger in inactiveTriggers)
{
activeTriggers.Remove(trigger);
}
@@ -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;

View File

@@ -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;
@@ -436,6 +438,7 @@ namespace Barotrauma
foreach (Hull h in VisibleHulls)
{
PropagateHullSafety(Character, h);
dirtyHullSafetyCalculations.Remove(h);
}
}
else
@@ -443,9 +446,15 @@ 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)
@@ -615,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))
{
@@ -900,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))
{
@@ -1145,7 +1154,7 @@ namespace Barotrauma
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 && !Character.IsMedic)
if (Character.Bleeding > AfflictionPrefab.Bleeding.TreatmentThreshold && !Character.IsMedic)
{
string msgId = "DialogBleeding";
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
@@ -1658,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.
@@ -1891,7 +1900,7 @@ 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>
@@ -1900,9 +1909,13 @@ namespace Barotrauma
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);
}

View File

@@ -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,46 +126,17 @@ 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)
@@ -183,19 +144,10 @@ namespace Barotrauma
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,14 +157,14 @@ namespace Barotrauma
if (needsNewPath || findPathTimer < -1.0f)
{
IsPathDirty = true;
if (!needsNewPath && findPathTimer < -1)
if (!needsNewPath && currentPath?.CurrentNode is WayPoint wp)
{
if (character.Submarine != null && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0)
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 && currentPath?.CurrentNode is WayPoint wp && wp.CurrentHull != null)
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)
@@ -226,7 +178,7 @@ namespace Barotrauma
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;
@@ -252,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()
{
@@ -330,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; }
@@ -346,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());
@@ -371,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))
@@ -398,40 +391,28 @@ namespace Barotrauma
}
}
}
var collider = character.AnimController.Collider;
if (character.IsClimbing && !useLadders)
{
character.StopClimbing();
}
if (character.IsClimbing && useLadders)
{
if (currentLadder == null && nextLadder != null)
if (currentLadder == null && nextLadder != null && character.SelectedSecondaryItem == nextLadder.Item)
{
// Climbing a ladder but the path is still on the node next to the ladder -> Skip the node.
NextNode(!doorsChecked);
}
else
{
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
if (nextLadderSameAsCurrent || currentLadder != null && nextLadder != null && Math.Abs(currentLadder.Item.Position.X - nextLadder.Item.Position.X) < 50)
bool nextLadderSameAsCurrent = currentLadder == nextLadder;
if (currentLadder != null && nextLadder != null)
{
//climbing ladders -> don't move horizontally
diff.X = 0.0f;
}
//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)
float colliderHeight = collider.Height / 2 + collider.Radius;
float distanceMargin = ConvertUnits.ToDisplayUnits(colliderSize.X);
if (heightDiff < colliderHeight * 1.25f)
{
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))
{
character.StopClimbing();
}
else if (nextLadder != null && !nextLadderSameAsCurrent)
if (nextLadder != null && !nextLadderSameAsCurrent)
{
// Try to change the ladder (hatches between two submarines)
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
@@ -442,12 +423,36 @@ namespace Barotrauma
}
}
}
if (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10)
bool isAboveFloor;
if (diff.Y < 0)
{
NextNode(!doorsChecked);
// 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();
}
}
}
else if (nextLadder != null)
else if (currentLadder != null && currentPath.NextNode != null)
{
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
{
@@ -466,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);
@@ -485,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.
@@ -512,9 +515,12 @@ namespace Barotrauma
}
}
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow && currentLadder == null && (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)
@@ -533,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; }
@@ -631,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;

View File

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

View File

@@ -506,6 +506,8 @@ namespace Barotrauma
}
}
public virtual void SpeakAfterOrderReceived() { }
protected static bool CanEquip(Character character, Item item, bool allowWearing)
{
if (item == null) { return false; }

View File

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

View File

@@ -995,10 +995,18 @@ namespace Barotrauma
}
}
}
if (HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
//prefer using handcuffs already on the enemy's inventory
if (!HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems))
{
HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out matchingItems);
}
if (matchingItems.Any() &&
!Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy) && !Enemy.LockHands)
{
var handCuffs = matchingItems.First();
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true, wear: true))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red);

View File

@@ -198,7 +198,7 @@ namespace Barotrauma
TargetName = container.Item.Name,
AbortCondition = obj =>
container?.Item == null || container.Item.Removed || !container.Item.HasAccess(character) ||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
(container.Item.RootContainer?.OwnInventory?.Locked ?? false) ||
ItemToContain == null || ItemToContain.Removed ||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),

View File

@@ -30,7 +30,8 @@ namespace Barotrauma
public static readonly Identifier DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors".ToIdentifier();
public static readonly Identifier OXYGEN_SOURCE = "oxygensource".ToIdentifier();
protected override bool CheckObjectiveSpecific() => targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head);
protected override bool CheckObjectiveSpecific() =>
targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head);
public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{
@@ -51,7 +52,7 @@ namespace Barotrauma
TrySetTargetItem(character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true));
}
if (targetItem == null ||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) &&
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) &&
targetItem.ContainedItems.Any(it => IsSuitableContainedOxygenSource(it)))
{
TryAddSubObjective(ref getDivingGear, () =>
@@ -65,7 +66,7 @@ namespace Barotrauma
AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _),
AllowToFindDivingGear = false,
AllowDangerousPressure = true,
EquipSlotType = InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes,
EquipSlotType = InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head,
Wear = true
};
},

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