v1.0.20.1 (summer patch)
This commit is contained in:
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
var target = _selectedAiTarget ?? _lastAiTarget;
|
||||
if (target != null && target.Entity != null)
|
||||
{
|
||||
var memory = GetTargetMemory(target, false);
|
||||
var memory = GetTargetMemory(target);
|
||||
if (memory != null)
|
||||
{
|
||||
Vector2 targetPos = memory.Location;
|
||||
|
||||
@@ -214,7 +214,6 @@ namespace Barotrauma
|
||||
|
||||
double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
|
||||
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
|
||||
TransformCursorPos();
|
||||
|
||||
bool ragdollInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Ragdoll].Held = ragdollInput;
|
||||
|
||||
@@ -850,7 +850,7 @@ namespace Barotrauma
|
||||
if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover)
|
||||
{
|
||||
//highlight the slot the treatment item is in
|
||||
var rootContainer = matchingItem.GetRootContainer() ?? matchingItem;
|
||||
var rootContainer = matchingItem.RootContainer ?? matchingItem;
|
||||
var index = Character.Controlled.Inventory.FindIndex(rootContainer);
|
||||
if (Character.Controlled.Inventory.visualSlots != null && index > -1 && index < Character.Controlled.Inventory.visualSlots.Length &&
|
||||
Character.Controlled.Inventory.visualSlots[index].HighlightTimer <= 0.0f)
|
||||
|
||||
@@ -870,7 +870,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (wearable.Type == WearableType.Hair)
|
||||
{
|
||||
if (HairWithHatSprite != null)
|
||||
if (HairWithHatSprite != null && !hideLimb)
|
||||
{
|
||||
DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
|
||||
depthStep += step;
|
||||
|
||||
@@ -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;
|
||||
@@ -1146,6 +1147,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))
|
||||
@@ -2814,7 +2835,26 @@ namespace Barotrauma
|
||||
ContentPackageManager.EnabledPackages.ReloadCore();
|
||||
}));
|
||||
|
||||
#warning TODO: reimplement?
|
||||
#if WINDOWS
|
||||
commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
|
||||
{
|
||||
Process.Start("DedicatedServer.exe");
|
||||
}));
|
||||
|
||||
commands.Add(new Command("editserversettings", "", (string[] args) =>
|
||||
{
|
||||
if (Process.GetProcessesByName("DedicatedServer").Length > 0)
|
||||
{
|
||||
NewMessage("Can't be edited if DedicatedServer.exe is already running", Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start("notepad.exe", "serversettings.xml");
|
||||
}
|
||||
}));
|
||||
#endif
|
||||
|
||||
#warning TODO: reimplement?
|
||||
/*commands.Add(new Command("ingamemodswap", "", (string[] args) =>
|
||||
{
|
||||
ContentPackage.IngameModSwap = !ContentPackage.IngameModSwap;
|
||||
|
||||
@@ -662,34 +662,36 @@ namespace Barotrauma
|
||||
Identifier missionIdentifier = msg.ReadIdentifier();
|
||||
int locationIndex = msg.ReadInt32();
|
||||
int destinationIndex = msg.ReadInt32();
|
||||
|
||||
string missionName = msg.ReadString();
|
||||
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
|
||||
if (prefab != null)
|
||||
if (Screen.Selected != GameMain.NetLobbyScreen)
|
||||
{
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
|
||||
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
|
||||
if (prefab != null)
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
|
||||
{
|
||||
Location location = map.Locations[locationIndex];
|
||||
map.Discover(location, checkTalents: false);
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
|
||||
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
|
||||
{
|
||||
Location location = map.Locations[locationIndex];
|
||||
map.Discover(location, checkTalents: false);
|
||||
|
||||
LocationConnection? connection = null;
|
||||
if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
|
||||
{
|
||||
Location destination = map.Locations[destinationIndex];
|
||||
connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
|
||||
}
|
||||
if (connection != null)
|
||||
{
|
||||
location.UnlockMission(prefab, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.UnlockMission(prefab);
|
||||
LocationConnection? connection = null;
|
||||
if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
|
||||
{
|
||||
Location destination = map.Locations[destinationIndex];
|
||||
connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
|
||||
}
|
||||
if (connection != null)
|
||||
{
|
||||
location.UnlockMission(prefab, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.UnlockMission(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -532,6 +532,7 @@ namespace Barotrauma
|
||||
void drawRect(Vector2 topLeft, Vector2 bottomRight)
|
||||
{
|
||||
int minWidth = GUI.IntScale(5);
|
||||
if (OverflowClip) { topLeft.X = Math.Max(topLeft.X, 0.0f); }
|
||||
if (bottomRight.X - topLeft.X < minWidth) { bottomRight.X = topLeft.X + minWidth; }
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
Rect.Location.ToVector2() + topLeft,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -175,6 +175,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (relativeOffset.NearlyEquals(value)) { return; }
|
||||
relativeOffset = value;
|
||||
recalculateRect = true;
|
||||
RecalculateChildren(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,7 +870,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var minRep in priceInfo.MinReputation)
|
||||
{
|
||||
if (campaign.GetReputation(minRep.Key) < minRep.Value)
|
||||
if (MathF.Round(campaign.GetReputation(minRep.Key)) < minRep.Value)
|
||||
{
|
||||
return minRep;
|
||||
}
|
||||
@@ -1930,7 +1930,7 @@ namespace Barotrauma
|
||||
"campaignstore.reputationrequired",
|
||||
("[amount]", ((int)requiredReputation.Value.Value).ToString()),
|
||||
("[faction]", TextManager.Get("faction." + requiredReputation.Value.Key).Value));
|
||||
Color color = campaign.GetReputation(requiredReputation.Value.Key) < requiredReputation.Value.Value ?
|
||||
Color color = MathF.Round(campaign.GetReputation(requiredReputation.Value.Key)) < requiredReputation.Value.Value ?
|
||||
GUIStyle.Orange : GUIStyle.Green;
|
||||
toolTip += $"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖";
|
||||
}
|
||||
|
||||
@@ -807,8 +807,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
|
||||
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
|
||||
if (GameMain.GameSession.TryPurchaseSubmarine(selectedSubmarine))
|
||||
{
|
||||
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
|
||||
}
|
||||
RefreshSubmarineDisplay(true);
|
||||
}
|
||||
else
|
||||
@@ -829,7 +831,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
|
||||
GameMain.GameSession.TryPurchaseSubmarine(selectedSubmarine);
|
||||
RefreshSubmarineDisplay(true);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -23,9 +23,12 @@ namespace Barotrauma
|
||||
private float votingTime = 100f;
|
||||
private float timer;
|
||||
private VoteType currentVoteType;
|
||||
private Color SubmarineColor => GUIStyle.Orange;
|
||||
private static Color SubmarineColor => GUIStyle.Orange;
|
||||
private Point createdForResolution;
|
||||
|
||||
//timer ran out but server still hasn't notified of the result of the vote
|
||||
public bool TimedOut => VoteRunning && timer - votingTime > 10.0f;
|
||||
|
||||
public static VotingInterface CreateSubmarineVotingInterface(Client starter, SubmarineInfo info, VoteType type, bool transferItems, float votingTime)
|
||||
{
|
||||
if (starter == null || info == null) { return null; }
|
||||
|
||||
@@ -664,7 +664,10 @@ namespace Barotrauma
|
||||
while (Timing.Accumulator >= Timing.Step)
|
||||
{
|
||||
Timing.TotalTime += Timing.Step;
|
||||
|
||||
if (!Paused)
|
||||
{
|
||||
Timing.TotalTimeUnpaused += Timing.Step;
|
||||
}
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
@@ -944,7 +947,10 @@ namespace Barotrauma
|
||||
PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
|
||||
}
|
||||
|
||||
if (!Paused) { Timing.Alpha = Timing.Accumulator / Timing.Step; }
|
||||
if (!Paused)
|
||||
{
|
||||
Timing.Alpha = Timing.Accumulator / Timing.Step;
|
||||
}
|
||||
|
||||
if (performanceCounterTimer.ElapsedMilliseconds > 1000)
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
// Item must be in a non-equipment slot if possible
|
||||
if (!item.AllowedSlots.All(s => equipmentSlots.Contains(s)) && IsInEquipmentSlot(item)) { return false; }
|
||||
// Item must not be contained inside an item in an equipment slot
|
||||
if (item.GetRootContainer() is Item rootContainer && IsInEquipmentSlot(rootContainer)) { return false; }
|
||||
if (item.RootContainer is Item rootContainer && IsInEquipmentSlot(rootContainer)) { return false; }
|
||||
return true;
|
||||
}, recursive: true).Distinct();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -310,9 +310,10 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
|
||||
RichString.Rich(missionMessage), wrap: true);
|
||||
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed)
|
||||
if (selectedMissions.Contains(displayedMission))
|
||||
{
|
||||
RichString reputationText = displayedMission.GetReputationRewardText();
|
||||
if (!reputationText.IsNullOrEmpty())
|
||||
@@ -324,7 +325,7 @@ namespace Barotrauma
|
||||
if (totalReward > 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub)));
|
||||
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled)
|
||||
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled && displayedMission.Completed)
|
||||
{
|
||||
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
|
||||
if (share > 0)
|
||||
|
||||
@@ -575,7 +575,7 @@ namespace Barotrauma
|
||||
//cancel dragging if too far away from the container of the dragged item
|
||||
if (DraggingItems.Any())
|
||||
{
|
||||
var rootContainer = DraggingItems.First().GetRootContainer();
|
||||
var rootContainer = DraggingItems.First().RootContainer;
|
||||
var rootInventory = DraggingItems.First().ParentInventory;
|
||||
|
||||
if (rootContainer != null)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
|
||||
if (character == null || !character.IsKeyDown(InputType.Aim) || !character.CanAim) { return; }
|
||||
|
||||
//camera focused on some other item/device, don't draw the crosshair
|
||||
if (character.ViewTarget != null && (character.ViewTarget is Item viewTargetItem) && viewTargetItem.Prefab.FocusOnSelected) { return; }
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//whole text can fit in the textblock, no need to scroll
|
||||
needsScrolling = false;
|
||||
scrollingText = DisplayText.Value;
|
||||
TextBlock.Text = scrollingText = DisplayText.Value;
|
||||
scrollPadding = 0;
|
||||
scrollAmount = 0.0f;
|
||||
scrollIndex = 0;
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Barotrauma.Items.Components
|
||||
Step = 0.05f,
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
lastReceivedTargetForce = null;
|
||||
float newTargetForce = barScroll * 200.0f - 100.0f;
|
||||
if (Math.Abs(newTargetForce - targetForce) < 0.01) { return false; }
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma.Items.Components
|
||||
private GUIFrame selectedItemFrame;
|
||||
private GUIFrame selectedItemReqsFrame;
|
||||
|
||||
private GUITextBlock amountTextMin, amountTextMax;
|
||||
private GUITextBlock amountTextMax;
|
||||
private GUIScrollBar amountInput;
|
||||
|
||||
public GUIButton ActivateButton
|
||||
@@ -29,6 +29,9 @@ namespace Barotrauma.Items.Components
|
||||
private GUIComponent outputSlot;
|
||||
private GUIComponent inputInventoryHolder, outputInventoryHolder;
|
||||
|
||||
private readonly List<GUIButton> itemCategoryButtons = new List<GUIButton>();
|
||||
private MapEntityCategory? selectedItemCategory;
|
||||
|
||||
public FabricationRecipe SelectedItem
|
||||
{
|
||||
get { return selectedItem; }
|
||||
@@ -77,7 +80,67 @@ namespace Barotrauma.Items.Components
|
||||
AutoScaleVertical = true
|
||||
};
|
||||
|
||||
var mainFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.95f), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
|
||||
var innerArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.95f), paddedFrame.RectTransform, Anchor.Center), isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.01f,
|
||||
Stretch = true,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
List<MapEntityCategory> itemCategories = Enum.GetValues<MapEntityCategory>().ToList();
|
||||
itemCategories.Remove(MapEntityCategory.None);
|
||||
itemCategories.RemoveAll(c => fabricationRecipes.None(f => f.Value?.TargetItem is ItemPrefab ti && ti.Category.HasFlag(c)));
|
||||
itemCategoryButtons.Clear();
|
||||
|
||||
//only create category buttons if there's more than one category in addition to "All"
|
||||
if (itemCategories.Count > 2)
|
||||
{
|
||||
// === Item category buttons ===
|
||||
var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.05f, 1.0f), innerArea.RectTransform))
|
||||
{
|
||||
RelativeSpacing = 0.01f
|
||||
};
|
||||
|
||||
int buttonSize = Math.Min(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Height / itemCategories.Count);
|
||||
|
||||
var categoryButton = new GUIButton(new RectTransform(new Point(buttonSize), categoryButtonContainer.RectTransform), style: "CategoryButton.All")
|
||||
{
|
||||
ToolTip = TextManager.Get("MapEntityCategory.All"),
|
||||
OnClicked = OnClickedCategoryButton
|
||||
};
|
||||
itemCategoryButtons.Add(categoryButton);
|
||||
foreach (MapEntityCategory category in itemCategories)
|
||||
{
|
||||
categoryButton = new GUIButton(new RectTransform(new Point(buttonSize), categoryButtonContainer.RectTransform),
|
||||
style: "CategoryButton." + category)
|
||||
{
|
||||
ToolTip = TextManager.Get("MapEntityCategory." + category),
|
||||
UserData = category,
|
||||
OnClicked = OnClickedCategoryButton
|
||||
};
|
||||
itemCategoryButtons.Add(categoryButton);
|
||||
}
|
||||
bool OnClickedCategoryButton(GUIButton button, object userData)
|
||||
{
|
||||
MapEntityCategory? newCategory = !button.Selected ? (MapEntityCategory?)userData : null;
|
||||
if (newCategory.HasValue) { itemFilterBox.Text = ""; }
|
||||
selectedItemCategory = newCategory;
|
||||
FilterEntities(newCategory, itemFilterBox.Text);
|
||||
return true;
|
||||
}
|
||||
foreach (var btn in itemCategoryButtons)
|
||||
{
|
||||
btn.RectTransform.SizeChanged += () =>
|
||||
{
|
||||
if (btn.Frame.sprites == null || !btn.Frame.sprites.TryGetValue(GUIComponent.ComponentState.None, out var spriteList)) { return; }
|
||||
var sprite = spriteList?.First();
|
||||
if (sprite == null) { return; }
|
||||
btn.RectTransform.NonScaledSize = new Point(btn.Rect.Width, (int)(btn.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var mainFrame = new GUILayoutGroup(new RectTransform(Vector2.One, innerArea.RectTransform), childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
RelativeSpacing = 0.02f,
|
||||
Stretch = true,
|
||||
@@ -105,10 +168,13 @@ namespace Barotrauma.Items.Components
|
||||
Padding = Vector4.Zero,
|
||||
AutoScaleVertical = true
|
||||
};
|
||||
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true);
|
||||
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true)
|
||||
{
|
||||
OverflowClip = true
|
||||
};
|
||||
itemFilterBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
FilterEntities(text);
|
||||
FilterEntities(selectedItemCategory, text);
|
||||
return true;
|
||||
};
|
||||
filterArea.RectTransform.MaxSize = new Point(int.MaxValue, itemFilterBox.Rect.Height);
|
||||
@@ -174,7 +240,7 @@ namespace Barotrauma.Items.Components
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
amountTextMin = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||
|
||||
amountInput = new GUIScrollBar(new RectTransform(new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style: "GUISlider")
|
||||
{
|
||||
@@ -489,15 +555,37 @@ namespace Barotrauma.Items.Components
|
||||
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
|
||||
}
|
||||
|
||||
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
|
||||
var itemIcon = requiredItemPrefab.InventoryIcon ?? requiredItemPrefab.Sprite;
|
||||
Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: requiredItemPrefab.InventoryIconColor * 0.3f,
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
|
||||
|
||||
|
||||
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
|
||||
|
||||
float iconAlpha = 0.0f;
|
||||
ItemPrefab requiredItemToDisplay;
|
||||
int count = requiredItem.ItemPrefabs.Count();
|
||||
if (count > 1)
|
||||
{
|
||||
float iconCycleSpeed = 0.5f / count;
|
||||
float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed;
|
||||
int iconIndex = (int)(iconCycleT % requiredItem.ItemPrefabs.Count());
|
||||
|
||||
requiredItemToDisplay = requiredItem.ItemPrefabs.Skip(iconIndex).FirstOrDefault();
|
||||
iconAlpha = Math.Min(Math.Abs(MathF.Sin(iconCycleT * MathHelper.Pi)) * 2.0f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredItemToDisplay = requiredItem.ItemPrefabs.FirstOrDefault();
|
||||
iconAlpha = 1.0f;
|
||||
}
|
||||
if (iconAlpha > 0.0f)
|
||||
{
|
||||
var itemIcon = requiredItemToDisplay.InventoryIcon ?? requiredItemToDisplay.Sprite;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: requiredItemToDisplay.InventoryIconColor * 0.3f * iconAlpha,
|
||||
scale: Math.Min(slotRect.Width * 0.9f / itemIcon.size.X, slotRect.Height * 0.9f / itemIcon.size.Y));
|
||||
}
|
||||
|
||||
if (missingCount > 1)
|
||||
{
|
||||
Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom);
|
||||
@@ -552,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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -119,6 +118,13 @@ namespace Barotrauma.Items.Components
|
||||
get { return sectionExtents; }
|
||||
}
|
||||
|
||||
public readonly record struct VisualSignal(
|
||||
float TimeSent,
|
||||
Color Color,
|
||||
int Direction);
|
||||
|
||||
private VisualSignal lastReceivedSignal;
|
||||
|
||||
public static Wire DraggingWire
|
||||
{
|
||||
get => draggingWire;
|
||||
@@ -126,13 +132,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public static Sprite ExtractWireSprite(ContentXElement element)
|
||||
{
|
||||
if (defaultWireSprite == null)
|
||||
{
|
||||
defaultWireSprite = new Sprite("Content/Items/Electricity/signalcomp.png", new Rectangle(970, 47, 14, 16), new Vector2(0.5f, 0.5f))
|
||||
defaultWireSprite ??=
|
||||
new Sprite("Content/Items/Electricity/signalcomp.png", new Rectangle(970, 47, 14, 16), new Vector2(0.5f, 0.5f))
|
||||
{
|
||||
Depth = 0.855f
|
||||
};
|
||||
}
|
||||
|
||||
Sprite overrideSprite = null;
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -153,6 +157,35 @@ namespace Barotrauma.Items.Components
|
||||
if (wireSprite != defaultWireSprite) { overrideSprite = wireSprite; }
|
||||
}
|
||||
|
||||
public void RegisterSignal(Signal signal, Connection source)
|
||||
{
|
||||
lastReceivedSignal = new VisualSignal(
|
||||
(float)Timing.TotalTimeUnpaused,
|
||||
GetSignalColor(signal),
|
||||
Direction: source == connections[0] ? 1 : -1);
|
||||
}
|
||||
|
||||
private static readonly Color[] dataSignalColors = new Color[] { Color.White, Color.LightBlue, Color.CornflowerBlue, Color.Blue, Color.BlueViolet, Color.Violet };
|
||||
|
||||
private static Color GetSignalColor(Signal signal)
|
||||
{
|
||||
if (signal.value == "0")
|
||||
{
|
||||
return Color.Red;
|
||||
}
|
||||
else if (signal.value == "1")
|
||||
{
|
||||
return Color.LightGreen;
|
||||
}
|
||||
else if (float.TryParse(signal.value, out float floatValue))
|
||||
{
|
||||
//convert numeric values to a color (guessing the value might be somewhere in the range of 0-200)
|
||||
//so a player with a keen eye can get some info out of the color of the signal
|
||||
return ToolBox.GradientLerp(Math.Abs(floatValue / 200.0f), dataSignalColors);
|
||||
}
|
||||
return Color.LightBlue;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
Draw(spriteBatch, editing, Vector2.Zero, itemDepth);
|
||||
@@ -166,20 +199,7 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 drawOffset = Vector2.Zero;
|
||||
Submarine sub = item.Submarine;
|
||||
if (IsActive && sub == null) // currently being rewired, we need to get the sub from the connections in case the wire has been taken outside
|
||||
{
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
||||
}
|
||||
|
||||
if (sub != null)
|
||||
{
|
||||
drawOffset = sub.DrawPosition + sub.HiddenSubPosition;
|
||||
}
|
||||
|
||||
drawOffset += offset;
|
||||
Vector2 drawOffset = GetDrawOffset() + offset;
|
||||
|
||||
float baseDepth = UseSpriteDepth ? item.SpriteDepth : wireSprite.Depth;
|
||||
float depth = item.IsSelected ? 0.0f : SubEditorScreen.IsWiringMode() ? 0.02f : baseDepth + (item.ID % 100) * 0.000001f;// item.GetDrawDepth(wireSprite.Depth, wireSprite);
|
||||
@@ -188,7 +208,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, wireSprite, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f);
|
||||
section.Draw(spriteBatch, wireSprite,
|
||||
Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor,
|
||||
drawOffset, depth + 0.00001f, Width * 2.0f);
|
||||
}
|
||||
}
|
||||
else if (item.IsSelected)
|
||||
@@ -270,6 +292,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ConnectionPanel.ShouldDebugDrawWiring)
|
||||
{
|
||||
DebugDraw(spriteBatch, alpha: 0.2f);
|
||||
}
|
||||
|
||||
if (!editing || !GameMain.SubEditorScreen.WiringMode) { return; }
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
@@ -295,6 +323,102 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void DebugDraw(SpriteBatch spriteBatch, float alpha = 1.0f)
|
||||
{
|
||||
if (sections.Count == 0 || Hidden)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float PowerPulseSpeedLow = 5.0f;
|
||||
const float PowerPulseSpeedHigh = 10.0f;
|
||||
const float PowerHighlightScaleLow = 1.5f;
|
||||
const float PowerHighlightScaleHigh = 2.5f;
|
||||
|
||||
const float SignalIndicatorInterval = 15.0f;
|
||||
const float SignalIndicatorSpeed = 100.0f;
|
||||
|
||||
Vector2 drawOffset = GetDrawOffset();
|
||||
|
||||
Color currentHighlightColor = Color.Transparent;
|
||||
float highlightScale = 0.0f;
|
||||
if (connections[0] != null && connections[1] != null)
|
||||
{
|
||||
float voltage = Math.Max(GetVoltage(0), GetVoltage(1));
|
||||
float GetVoltage(int connectionIndex)
|
||||
{
|
||||
var connection1 = connections[connectionIndex];
|
||||
var connection2 = connections[1 - connectionIndex];
|
||||
if (connection1.IsOutput && connection1.Grid is { Power: > 0.01f } grid1)
|
||||
{
|
||||
if (connection2.Item.GetComponent<Powered>() is Powered powered &&
|
||||
(powered.GetCurrentPowerConsumption(connection2) > 0 || powered is PowerTransfer))
|
||||
{
|
||||
return grid1.Voltage;
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
if (voltage > 0.0f)
|
||||
{
|
||||
//pulse faster when there's overvoltage
|
||||
float pulseSpeed = voltage > 1.2f ? PowerPulseSpeedHigh : PowerPulseSpeedLow;
|
||||
float pulseAmount = (MathF.Sin((float)Timing.TotalTimeUnpaused * pulseSpeed) + 1.5f) / 2.5f;
|
||||
voltage = Math.Min(voltage, 1.0f);
|
||||
highlightScale = MathHelper.Lerp(PowerHighlightScaleLow, PowerHighlightScaleHigh, voltage);
|
||||
currentHighlightColor = Color.Red * voltage * pulseAmount;
|
||||
}
|
||||
}
|
||||
if (highlightScale > 0.0f)
|
||||
{
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, wireSprite, currentHighlightColor * alpha, drawOffset, 0.0f, Width * highlightScale);
|
||||
}
|
||||
}
|
||||
|
||||
float signalDuration = (float)Timing.TotalTimeUnpaused - lastReceivedSignal.TimeSent;
|
||||
if (ConnectionPanel.ShouldDebugDrawWiring && signalDuration < 1.0f)
|
||||
{
|
||||
//make some wires "off sync" so it's easier to differentiate signals on overlapping wires
|
||||
float offset = item.ID % 2 == 1 ? SignalIndicatorInterval / 2 : 0.0f;
|
||||
float signalProgress = ((float)(Timing.TotalTimeUnpaused * SignalIndicatorSpeed + offset) % SignalIndicatorInterval) * lastReceivedSignal.Direction;
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
for (float x = 0; x < section.Length; x += SignalIndicatorInterval)
|
||||
{
|
||||
Vector2 dir = (section.End - section.Start) / section.Length;
|
||||
float posOnSection = x + signalProgress;
|
||||
if (posOnSection < 0 || posOnSection > section.Length) { continue; }
|
||||
Vector2 signalPos = section.Start + drawOffset + dir * posOnSection;
|
||||
float a = 1.0f - Vector2.Distance(Screen.Selected.Cam.WorldViewCenter, signalPos) / 500.0f;
|
||||
if (a < 0) { continue; }
|
||||
signalPos.Y = -signalPos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, signalPos - Vector2.One * 2.5f, Vector2.One * 5, lastReceivedSignal.Color * a * (1.0f - signalDuration) * alpha, isFilled: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetDrawOffset()
|
||||
{
|
||||
Submarine sub = item.Submarine;
|
||||
if (IsActive && sub == null) // currently being rewired, we need to get the sub from the connections in case the wire has been taken outside
|
||||
{
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
||||
}
|
||||
|
||||
if (sub == null)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
return sub.DrawPosition + sub.HiddenSubPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHangingWire(SpriteBatch spriteBatch, Vector2 start, float depth)
|
||||
{
|
||||
float angle = (float)Math.Sin(GameMain.GameScreen.GameTime * 2.0f + item.ID) * 0.2f;
|
||||
|
||||
@@ -46,6 +46,13 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DebugWiring
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool ShowDeadCharacters
|
||||
{
|
||||
@@ -111,6 +118,11 @@ namespace Barotrauma.Items.Components
|
||||
refEntity = item;
|
||||
}
|
||||
|
||||
if (equipper != null && equipper == Character.Controlled && DebugWiring)
|
||||
{
|
||||
ConnectionPanel.DebugWiringEnabledUntil = Timing.TotalTimeUnpaused + 0.5;
|
||||
}
|
||||
|
||||
thermalEffectState += deltaTime;
|
||||
thermalEffectState %= 10000.0f;
|
||||
|
||||
@@ -153,6 +165,11 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
Unequip(dropper);
|
||||
}
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (character == null) { return; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Lights;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -10,6 +11,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private GUIMessageBox autodockingVerification;
|
||||
|
||||
private readonly ConvexHull[] convexHulls = new ConvexHull[2];
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
@@ -109,6 +112,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
partial void RemoveConvexHulls()
|
||||
{
|
||||
for (int i = 0; i < convexHulls.Length; i++)
|
||||
{
|
||||
convexHulls[i]?.Remove();
|
||||
convexHulls[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
bool isDocked = msg.ReadBoolean();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -116,11 +116,11 @@ namespace Barotrauma
|
||||
|
||||
foreach (MapEntity e in entitiesToRender)
|
||||
{
|
||||
if (!e.DrawOverWater) continue;
|
||||
if (!e.DrawOverWater) { continue; }
|
||||
|
||||
if (predicate != null)
|
||||
{
|
||||
if (!predicate(e)) continue;
|
||||
if (!predicate(e)) { continue; }
|
||||
}
|
||||
|
||||
e.Draw(spriteBatch, editing, false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -18,7 +19,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
List<Submarine> subsToMove = submarine.GetConnectedSubs();
|
||||
var subsToMove = submarine.GetConnectedSubs();
|
||||
foreach (Submarine dockedSub in subsToMove)
|
||||
{
|
||||
if (dockedSub == submarine) { continue; }
|
||||
@@ -51,7 +52,6 @@ namespace Barotrauma
|
||||
sub.PhysicsBody.SetTransformIgnoreContacts(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (closestSub != null && subsToMove.Contains(closestSub))
|
||||
{
|
||||
GameMain.GameScreen.Cam.Position += moveAmount;
|
||||
|
||||
@@ -67,6 +67,10 @@ namespace Barotrauma
|
||||
else if (ConnectedDoor != null)
|
||||
{
|
||||
sprite = iconSprites["Door"];
|
||||
if (ConnectedDoor.IsHorizontal && Ladders == null)
|
||||
{
|
||||
clr = Color.Yellow;
|
||||
}
|
||||
}
|
||||
else if (Ladders != null)
|
||||
{
|
||||
|
||||
@@ -3070,8 +3070,12 @@ namespace Barotrauma.Networking
|
||||
if (votingInterface != null)
|
||||
{
|
||||
votingInterface.Update(deltaTime);
|
||||
if (!votingInterface.VoteRunning)
|
||||
if (!votingInterface.VoteRunning || votingInterface.TimedOut)
|
||||
{
|
||||
if (votingInterface.TimedOut)
|
||||
{
|
||||
DebugConsole.AddWarning($"Voting interface timed out.");
|
||||
}
|
||||
votingInterface.Remove();
|
||||
votingInterface = null;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ref struct LobbyDataChangedEventHandler
|
||||
private readonly struct LobbyDataChangedEventHandler : IDisposable
|
||||
{
|
||||
private readonly Action<Lobby> action;
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Barotrauma.Networking
|
||||
int botSpawnMode = 0,
|
||||
bool? useRespawnShuttle = null)
|
||||
{
|
||||
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
|
||||
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return; }
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
|
||||
@@ -253,12 +253,9 @@ namespace Barotrauma.Networking
|
||||
//in push-to-talk mode, InputType.Voice uses the active chat mode
|
||||
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
|
||||
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown || captureTimer <= 0)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
}
|
||||
if (pttDown)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
allowEnqueue = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,17 +115,20 @@ namespace Barotrauma.Networking
|
||||
float speechImpedimentMultiplier = 1.0f - client.Character.SpeechImpediment / 100.0f;
|
||||
bool spectating = Character.Controlled == null;
|
||||
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
||||
WifiComponent radio = null;
|
||||
WifiComponent senderRadio = null;
|
||||
var messageType =
|
||||
!client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) && ChatMessage.CanUseRadio(Character.Controlled) ?
|
||||
ChatMessageType.Radio : ChatMessageType.Default;
|
||||
!client.VoipQueue.ForceLocal &&
|
||||
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
|
||||
ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) &&
|
||||
senderRadio.CanReceive(recipientRadio) ?
|
||||
ChatMessageType.Radio : ChatMessageType.Default;
|
||||
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||
|
||||
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
||||
client.RadioNoise = 0.0f;
|
||||
if (messageType == ChatMessageType.Radio)
|
||||
{
|
||||
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||
client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||
if (distanceFactor > RangeNear && !spectating)
|
||||
{
|
||||
//noise starts increasing exponentially after 40% range
|
||||
|
||||
@@ -348,11 +348,13 @@ namespace Barotrauma
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
GameMain.GameSession.PurchaseSubmarine(subInfo);
|
||||
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
|
||||
if (GameMain.GameSession.TryPurchaseSubmarine(subInfo))
|
||||
{
|
||||
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
|
||||
}
|
||||
break;
|
||||
case VoteType.PurchaseSub:
|
||||
GameMain.GameSession.PurchaseSubmarine(subInfo);
|
||||
GameMain.GameSession.TryPurchaseSubmarine(subInfo);
|
||||
break;
|
||||
case VoteType.SwitchSub:
|
||||
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
@@ -48,19 +49,23 @@ namespace Barotrauma.Particles
|
||||
|
||||
private Vector2 drawPosition;
|
||||
private float drawRotation;
|
||||
|
||||
private Vector2 colliderRadius;
|
||||
|
||||
private Hull currentHull;
|
||||
|
||||
private List<Gap> hullGaps;
|
||||
|
||||
private bool hasSubEmitters;
|
||||
private List<ParticleEmitter> subEmitters = new List<ParticleEmitter>();
|
||||
private readonly List<ParticleEmitter> subEmitters = new List<ParticleEmitter>();
|
||||
|
||||
private float animState;
|
||||
private int animFrame;
|
||||
|
||||
private float collisionUpdateTimer;
|
||||
|
||||
private bool changesSize;
|
||||
|
||||
public bool HighQualityCollisionDetection;
|
||||
|
||||
public Vector4 ColorMultiplier;
|
||||
@@ -127,7 +132,10 @@ namespace Barotrauma.Particles
|
||||
position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
|
||||
}
|
||||
|
||||
RefreshColliderSize();
|
||||
|
||||
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
|
||||
changesSize = !sizeChange.NearlyEquals(Vector2.Zero);
|
||||
|
||||
this.position = position;
|
||||
prevPosition = position;
|
||||
@@ -256,8 +264,12 @@ namespace Barotrauma.Particles
|
||||
}
|
||||
}
|
||||
|
||||
size.X += sizeChange.X * deltaTime;
|
||||
size.Y += sizeChange.Y * deltaTime;
|
||||
if (changesSize)
|
||||
{
|
||||
size.X += sizeChange.X * deltaTime;
|
||||
size.Y += sizeChange.Y * deltaTime;
|
||||
RefreshColliderSize();
|
||||
}
|
||||
|
||||
if (UseMiddleColor)
|
||||
{
|
||||
@@ -344,11 +356,11 @@ namespace Barotrauma.Particles
|
||||
{
|
||||
Rectangle hullRect = currentHull.WorldRect;
|
||||
Vector2 collisionNormal = Vector2.Zero;
|
||||
if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height)
|
||||
if (velocity.Y < 0.0f && position.Y - colliderRadius.Y < hullRect.Y - hullRect.Height)
|
||||
{
|
||||
collisionNormal = new Vector2(0.0f, 1.0f);
|
||||
}
|
||||
else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y)
|
||||
else if (velocity.Y > 0.0f && position.Y + colliderRadius.Y > hullRect.Y)
|
||||
{
|
||||
collisionNormal = new Vector2(0.0f, -1.0f);
|
||||
}
|
||||
@@ -378,11 +390,11 @@ namespace Barotrauma.Particles
|
||||
}
|
||||
|
||||
collisionNormal = Vector2.Zero;
|
||||
if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
|
||||
if (velocity.X < 0.0f && position.X - colliderRadius.X < hullRect.X)
|
||||
{
|
||||
collisionNormal = new Vector2(1.0f, 0.0f);
|
||||
}
|
||||
else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right)
|
||||
else if (velocity.X > 0.0f && position.X + colliderRadius.X > hullRect.Right)
|
||||
{
|
||||
collisionNormal = new Vector2(-1.0f, 0.0f);
|
||||
}
|
||||
@@ -431,6 +443,13 @@ namespace Barotrauma.Particles
|
||||
return UpdateResult.Normal;
|
||||
}
|
||||
|
||||
private void RefreshColliderSize()
|
||||
{
|
||||
if (!prefab.UseCollision) { return; }
|
||||
colliderRadius = new Vector2(prefab.CollisionRadius);
|
||||
if (!prefab.InvariantCollisionSize) { colliderRadius *= size; }
|
||||
}
|
||||
|
||||
private void ApplyDrag(float dragCoefficient, float deltaTime)
|
||||
{
|
||||
Vector2 relativeVel = velocity;
|
||||
@@ -475,11 +494,11 @@ namespace Barotrauma.Particles
|
||||
{
|
||||
if (collisionNormal.X > 0.0f)
|
||||
{
|
||||
position.X = Math.Max(position.X, prevHullRect.X + prefab.CollisionRadius * size.X);
|
||||
position.X = Math.Max(position.X, prevHullRect.X + colliderRadius.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.X = Math.Min(position.X, prevHullRect.Right - prefab.CollisionRadius * size.X);
|
||||
position.X = Math.Min(position.X, prevHullRect.Right - colliderRadius.X);
|
||||
}
|
||||
velocity.X = Math.Sign(collisionNormal.X) * Math.Abs(velocity.X) * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
@@ -488,11 +507,11 @@ namespace Barotrauma.Particles
|
||||
{
|
||||
if (collisionNormal.Y > 0.0f)
|
||||
{
|
||||
position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + prefab.CollisionRadius * size.Y);
|
||||
position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + colliderRadius.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.Y = Math.Min(position.Y, prevHullRect.Y - prefab.CollisionRadius * size.Y);
|
||||
position.Y = Math.Min(position.Y, prevHullRect.Y - colliderRadius.Y);
|
||||
|
||||
}
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
@@ -513,26 +532,26 @@ namespace Barotrauma.Particles
|
||||
|
||||
if (position.Y < center.Y)
|
||||
{
|
||||
position.Y = hullRect.Y - hullRect.Height - prefab.CollisionRadius;
|
||||
position.Y = hullRect.Y - hullRect.Height - colliderRadius.Y;
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = -velocity.Y * prefab.Restitution;
|
||||
}
|
||||
else if (position.Y > center.Y)
|
||||
{
|
||||
position.Y = hullRect.Y + prefab.CollisionRadius;
|
||||
position.Y = hullRect.Y + colliderRadius.Y;
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = -velocity.Y * prefab.Restitution;
|
||||
}
|
||||
|
||||
if (position.X < center.X)
|
||||
{
|
||||
position.X = hullRect.X - prefab.CollisionRadius;
|
||||
position.X = hullRect.X - colliderRadius.X;
|
||||
velocity.X = -velocity.X * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
else if (position.X > center.X)
|
||||
{
|
||||
position.X = hullRect.X + hullRect.Width + prefab.CollisionRadius;
|
||||
position.X = hullRect.X + hullRect.Width + colliderRadius.X;
|
||||
velocity.X = -velocity.X * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
@@ -559,11 +578,12 @@ namespace Barotrauma.Particles
|
||||
|
||||
Color currColor = new Color(color.ToVector4() * ColorMultiplier);
|
||||
|
||||
if (prefab.Sprites[spriteIndex] is SpriteSheet)
|
||||
Vector2 drawPos = new Vector2(drawPosition.X, -drawPosition.Y);
|
||||
if (prefab.Sprites[spriteIndex] is SpriteSheet sheet)
|
||||
{
|
||||
((SpriteSheet)prefab.Sprites[spriteIndex]).Draw(
|
||||
sheet.Draw(
|
||||
spriteBatch, animFrame,
|
||||
new Vector2(drawPosition.X, -drawPosition.Y),
|
||||
drawPos,
|
||||
currColor * (currColor.A / 255.0f),
|
||||
prefab.Sprites[spriteIndex].Origin, drawRotation,
|
||||
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
|
||||
@@ -571,11 +591,23 @@ namespace Barotrauma.Particles
|
||||
else
|
||||
{
|
||||
prefab.Sprites[spriteIndex].Draw(spriteBatch,
|
||||
new Vector2(drawPosition.X, -drawPosition.Y),
|
||||
drawPos,
|
||||
currColor * (currColor.A / 255.0f),
|
||||
prefab.Sprites[spriteIndex].Origin, drawRotation,
|
||||
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
|
||||
}
|
||||
|
||||
/*if (GameMain.DebugDraw && prefab.UseCollision)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos - Vector2.UnitX * colliderRadius.X,
|
||||
drawPos + Vector2.UnitX * colliderRadius.X,
|
||||
Color.Gray);
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos - Vector2.UnitY * colliderRadius.Y,
|
||||
drawPos + Vector2.UnitY * colliderRadius.Y,
|
||||
Color.Gray);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ namespace Barotrauma.Particles
|
||||
[Editable(0.0f, 10000.0f), Serialize(0.0f, IsPropertySaveable.No, description: "Radius of the particle's collider. Only has an effect if UseCollision is set to true.")]
|
||||
public float CollisionRadius { get; private set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "If enabled, the size (or changes in size) of the particle doesn't affect the size of the collider.")]
|
||||
public bool InvariantCollisionSize { get; private set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Does the particle collide with the walls of the submarine and the level.")]
|
||||
public bool UseCollision { get; private set; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -345,12 +345,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();
|
||||
@@ -377,7 +378,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();
|
||||
}
|
||||
@@ -408,6 +409,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);
|
||||
@@ -433,8 +435,11 @@ namespace Barotrauma
|
||||
|
||||
graphics.BlendState = BlendState.NonPremultiplied;
|
||||
graphics.SamplerStates[0] = SamplerState.PointClamp;
|
||||
graphics.SamplerStates[1] = SamplerState.PointClamp;
|
||||
GameMain.LightManager.LosEffect.CurrentTechnique.Passes[0].Apply();
|
||||
Quad.Render();
|
||||
graphics.SamplerStates[0] = SamplerState.LinearWrap;
|
||||
graphics.SamplerStates[1] = SamplerState.LinearWrap;
|
||||
}
|
||||
|
||||
if (Character.Controlled is { } character)
|
||||
@@ -518,6 +523,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();
|
||||
|
||||
@@ -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);
|
||||
@@ -426,31 +426,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;
|
||||
@@ -471,6 +473,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()
|
||||
@@ -1508,10 +1524,11 @@ namespace Barotrauma
|
||||
|
||||
private void FetchRemoteContent()
|
||||
{
|
||||
if (string.IsNullOrEmpty(RemoteContentUrl)) { return; }
|
||||
string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
|
||||
if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
|
||||
try
|
||||
{
|
||||
var client = new RestClient(RemoteContentUrl);
|
||||
var client = new RestClient(remoteContentUrl);
|
||||
var request = new RestRequest("MenuContent.xml", Method.GET);
|
||||
TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request),
|
||||
RemoteContentReceived);
|
||||
|
||||
@@ -751,12 +751,20 @@ namespace Barotrauma
|
||||
};
|
||||
ServerMessage.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
Vector2 textSize = textBox.Font.MeasureString(textBox.WrappedText);
|
||||
textBox.RectTransform.NonScaledSize = new Point(textBox.RectTransform.NonScaledSize.X, Math.Max(serverMessageContainer.Content.Rect.Height, (int)textSize.Y + 10));
|
||||
serverMessageContainer.UpdateScrollBarSize();
|
||||
serverMessageHint.Visible = !textBox.Selected && !textBox.Readonly && string.IsNullOrWhiteSpace(textBox.Text);
|
||||
RefreshServerInfoSize();
|
||||
return true;
|
||||
};
|
||||
ServerMessage.RectTransform.SizeChanged += RefreshServerInfoSize;
|
||||
|
||||
void RefreshServerInfoSize()
|
||||
{
|
||||
serverMessageHint.Visible = !ServerMessage.Selected && !ServerMessage.Readonly && string.IsNullOrWhiteSpace(ServerMessage.Text);
|
||||
Vector2 textSize = ServerMessage.Font.MeasureString(ServerMessage.WrappedText);
|
||||
ServerMessage.RectTransform.NonScaledSize = new Point(ServerMessage.RectTransform.NonScaledSize.X, Math.Max(serverMessageContainer.Content.Rect.Height, (int)textSize.Y + 10));
|
||||
serverMessageContainer.UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
ServerMessage.OnEnterPressed += (textBox, text) =>
|
||||
{
|
||||
string str = textBox.Text;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -525,6 +525,11 @@ namespace Barotrauma
|
||||
GUI.AddMessage(TextManager.Get("waypointsgeneratedsuccesfully"), GUIStyle.Green);
|
||||
}
|
||||
WayPoint.ShowWayPoints = true;
|
||||
var matchingTickBox = showEntitiesTickBoxes?.Find(tb => tb.UserData as string == "waypoint");
|
||||
if (matchingTickBox != null)
|
||||
{
|
||||
matchingTickBox.Selected = true;
|
||||
}
|
||||
generateWaypointsVerification.Close();
|
||||
return true;
|
||||
};
|
||||
@@ -2847,7 +2852,7 @@ namespace Barotrauma
|
||||
{
|
||||
OnClicked = (button, o) =>
|
||||
{
|
||||
var requiredPackages = MapEntity.mapEntityList.Select(e => e.Prefab.ContentPackage)
|
||||
var requiredPackages = MapEntity.mapEntityList.Select(e => e?.Prefab?.ContentPackage)
|
||||
.Where(cp => cp != null)
|
||||
.Distinct().OfType<ContentPackage>().Select(p => p.Name).ToHashSet();
|
||||
var tickboxes = requiredContentPackList.Content.Children.OfType<GUITickBox>().ToArray();
|
||||
@@ -5791,7 +5796,10 @@ namespace Barotrauma
|
||||
{
|
||||
item.SetTransform(dummyCharacter.SimPosition, 0.0f);
|
||||
item.UpdateTransform();
|
||||
item.SetTransform(item.body.SimPosition, 0.0f);
|
||||
if (item.body != null)
|
||||
{
|
||||
item.SetTransform(item.body.SimPosition, 0.0f);
|
||||
}
|
||||
|
||||
//wires need to be updated for the last node to follow the player during rewiring
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
@@ -5904,6 +5912,11 @@ namespace Barotrauma
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
if (GameMain.LightManager.DebugLos)
|
||||
{
|
||||
GameMain.LightManager.DebugDrawLos(spriteBatch, cam);
|
||||
}
|
||||
|
||||
//-------------------- HUD -----------------------------
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
|
||||
|
||||
@@ -343,12 +343,11 @@ namespace Barotrauma
|
||||
if (property.PropertyType == typeof(string) && value == null)
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
}
|
||||
|
||||
Identifier propertyTag = $"{property.PropertyInfo.DeclaringType.Name}.{property.PropertyInfo.Name}".ToIdentifier();
|
||||
Identifier fallbackTag = property.PropertyInfo.Name.ToIdentifier();
|
||||
LocalizedString displayName =
|
||||
TextManager.Get(propertyTag, $"sp.{propertyTag}.name".ToIdentifier());
|
||||
LocalizedString displayName = TextManager.Get(propertyTag, $"sp.{propertyTag}.name".ToIdentifier());
|
||||
if (displayName.IsNullOrEmpty())
|
||||
{
|
||||
Editable editable = property.GetAttribute<Editable>();
|
||||
@@ -380,10 +379,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
LocalizedString toolTip = TextManager.Get($"sp.{propertyTag}.description");
|
||||
if (toolTip.IsNullOrEmpty() && entity.GetType() != property.PropertyInfo.DeclaringType)
|
||||
if (entity.GetType() != property.PropertyInfo.DeclaringType)
|
||||
{
|
||||
Identifier propertyTagForDerivedClass = $"{entity.GetType().Name}.{property.PropertyInfo.Name}".ToIdentifier();
|
||||
toolTip = TextManager.Get($"{propertyTagForDerivedClass}.description", $"sp.{propertyTagForDerivedClass}.description");
|
||||
var toolTipForDerivedClass = TextManager.Get($"{propertyTagForDerivedClass}.description", $"sp.{propertyTagForDerivedClass}.description");
|
||||
if (!toolTipForDerivedClass.IsNullOrEmpty())
|
||||
{
|
||||
toolTip = toolTipForDerivedClass;
|
||||
}
|
||||
}
|
||||
if (toolTip.IsNullOrEmpty())
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,16 @@ float xLosAlpha;
|
||||
|
||||
float4 xColor;
|
||||
|
||||
float blurDistance;
|
||||
|
||||
float4 mainPS(VertexShaderOutput input) : COLOR0
|
||||
{
|
||||
float4 sampleColor = tex2D(TextureSampler, input.TexCoords);
|
||||
float4 losColor = tex2D(LosSampler, input.TexCoords);
|
||||
float4 losColor = tex2D(LosSampler, float2(input.TexCoords.x + blurDistance, input.TexCoords.y + blurDistance));
|
||||
losColor += tex2D(LosSampler, float2(input.TexCoords.x - blurDistance, input.TexCoords.y - blurDistance));
|
||||
losColor += tex2D(LosSampler, float2(input.TexCoords.x + blurDistance, input.TexCoords.y - blurDistance));
|
||||
losColor += tex2D(LosSampler, float2(input.TexCoords.x - blurDistance, input.TexCoords.y + blurDistance));
|
||||
losColor = losColor * 0.25f;
|
||||
|
||||
float obscureAmount = 1.0f - losColor.r;
|
||||
|
||||
|
||||
@@ -6,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>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.0.13.2</Version>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.0.13.2</Version>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -21,15 +21,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
public override Voting Voting { get; }
|
||||
|
||||
private string serverName;
|
||||
public string ServerName
|
||||
{
|
||||
get { return serverName; }
|
||||
get { return ServerSettings.ServerName; }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) { return; }
|
||||
|
||||
serverName = value;
|
||||
ServerSettings.ServerName = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,8 +131,6 @@ namespace Barotrauma.Networking
|
||||
name = name.Substring(0, NetConfig.ServerNameMaxLength);
|
||||
}
|
||||
|
||||
this.serverName = name;
|
||||
|
||||
LastClientListUpdateID = 0;
|
||||
|
||||
ServerSettings = new ServerSettings(this, name, port, queryPort, maxPlayers, isPublic, attemptUPnP);
|
||||
@@ -1667,38 +1663,54 @@ namespace Barotrauma.Networking
|
||||
//characters or items spawned mid-round don't necessarily exist at the client's end yet
|
||||
if (!c.NeedsMidRoundSync)
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
Character clientCharacter = c.Character;
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
if (!character.Enabled) { continue; }
|
||||
if (!otherCharacter.Enabled) { continue; }
|
||||
if (c.SpectatePos == null)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(character.WorldPosition, c.Character.WorldPosition);
|
||||
if (c.Character.ViewTarget != null)
|
||||
//not spectating ->
|
||||
// check if the client's character, or the entity they're viewing,
|
||||
// is close enough to the other character or the entity the other character is viewing
|
||||
float distSqr = GetShortestDistance(clientCharacter.WorldPosition, otherCharacter);
|
||||
if (clientCharacter.ViewTarget != null && clientCharacter.ViewTarget != clientCharacter)
|
||||
{
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(character.WorldPosition, c.Character.ViewTarget.WorldPosition));
|
||||
distSqr = Math.Min(distSqr, GetShortestDistance(clientCharacter.ViewTarget.WorldPosition, otherCharacter));
|
||||
}
|
||||
if (distSqr >= MathUtils.Pow2(character.Params.DisableDistance)) { continue; }
|
||||
if (distSqr >= MathUtils.Pow2(otherCharacter.Params.DisableDistance)) { continue; }
|
||||
}
|
||||
else
|
||||
else if (otherCharacter != clientCharacter)
|
||||
{
|
||||
if (character != c.Character && Vector2.DistanceSquared(character.WorldPosition, c.SpectatePos.Value) >= MathUtils.Pow2(character.Params.DisableDistance))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//spectating ->
|
||||
// check if the position the client is viewing
|
||||
// is close enough to the other character or the entity the other character is viewing
|
||||
if (GetShortestDistance(c.SpectatePos.Value, otherCharacter) >= MathUtils.Pow2(otherCharacter.Params.DisableDistance)) { continue; }
|
||||
}
|
||||
|
||||
float updateInterval = character.GetPositionUpdateInterval(c);
|
||||
c.PositionUpdateLastSent.TryGetValue(character, out float lastSent);
|
||||
static float GetShortestDistance(Vector2 viewPos, Character targetCharacter)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(viewPos, targetCharacter.WorldPosition);
|
||||
if (targetCharacter.ViewTarget != null && targetCharacter.ViewTarget != targetCharacter)
|
||||
{
|
||||
//if the character is viewing something (far-away turret?),
|
||||
//we might want to send updates about it to the spectating client even though they're far away from the actual character
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(viewPos, targetCharacter.ViewTarget.WorldPosition));
|
||||
}
|
||||
return distSqr;
|
||||
}
|
||||
|
||||
float updateInterval = otherCharacter.GetPositionUpdateInterval(c);
|
||||
c.PositionUpdateLastSent.TryGetValue(otherCharacter, out float lastSent);
|
||||
if (lastSent > NetTime.Now)
|
||||
{
|
||||
//sent in the future -> can't be right, remove
|
||||
c.PositionUpdateLastSent.Remove(character);
|
||||
c.PositionUpdateLastSent.Remove(otherCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastSent > NetTime.Now - updateInterval) { continue; }
|
||||
}
|
||||
if (!c.PendingPositionUpdates.Contains(character)) { c.PendingPositionUpdates.Enqueue(character); }
|
||||
if (!c.PendingPositionUpdates.Contains(otherCharacter)) { c.PendingPositionUpdates.Enqueue(otherCharacter); }
|
||||
}
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
@@ -3094,7 +3106,7 @@ namespace Barotrauma.Networking
|
||||
default:
|
||||
if (command != "")
|
||||
{
|
||||
if (command.ToLower() == serverName.ToLower())
|
||||
if (command.ToLower() == ServerName.ToLower())
|
||||
{
|
||||
//a private message to the host
|
||||
if (OwnerConnection != null)
|
||||
@@ -3149,7 +3161,7 @@ namespace Barotrauma.Networking
|
||||
//msg sent by the server
|
||||
if (senderCharacter == null)
|
||||
{
|
||||
senderName = serverName;
|
||||
senderName = ServerName;
|
||||
}
|
||||
else //msg sent by an AI character
|
||||
{
|
||||
@@ -3183,7 +3195,7 @@ namespace Barotrauma.Networking
|
||||
//msg sent by the server
|
||||
if (senderCharacter == null)
|
||||
{
|
||||
senderName = serverName;
|
||||
senderName = ServerName;
|
||||
}
|
||||
else //sent by an AI character, not allowed when the game is not running
|
||||
{
|
||||
@@ -3406,33 +3418,35 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchSubmarine()
|
||||
public bool TrySwitchSubmarine()
|
||||
{
|
||||
if (Voting.ActiveVote is not Voting.SubmarineVote subVote) { return; }
|
||||
if (Voting.ActiveVote is not Voting.SubmarineVote subVote) { return false; }
|
||||
|
||||
SubmarineInfo targetSubmarine = subVote.Sub;
|
||||
VoteType voteType = Voting.ActiveVote.VoteType;
|
||||
Client starter = Voting.ActiveVote.VoteStarter;
|
||||
|
||||
bool purchaseFailed = false;
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
case VoteType.PurchaseSub:
|
||||
// Pay for submarine
|
||||
GameMain.GameSession.PurchaseSubmarine(targetSubmarine, starter);
|
||||
purchaseFailed = !GameMain.GameSession.TryPurchaseSubmarine(targetSubmarine, starter);
|
||||
break;
|
||||
case VoteType.SwitchSub:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (voteType != VoteType.PurchaseSub)
|
||||
if (voteType != VoteType.PurchaseSub && !purchaseFailed)
|
||||
{
|
||||
GameMain.GameSession.SwitchSubmarine(targetSubmarine, subVote.TransferItems, starter);
|
||||
}
|
||||
|
||||
Voting.StopSubmarineVote(true);
|
||||
Voting.StopSubmarineVote(passed: !purchaseFailed);
|
||||
return !purchaseFailed;
|
||||
}
|
||||
|
||||
public void UpdateClientPermissions(Client client)
|
||||
|
||||
@@ -370,6 +370,8 @@ namespace Barotrauma.Networking
|
||||
"192-255",
|
||||
"384-591",
|
||||
"1024-1279",
|
||||
"4352-4607", //Hangul Jamo
|
||||
"44032-55215", //Hangul Syllables
|
||||
"19968-21327","21329-40959","13312-19903","131072-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (passed)
|
||||
{
|
||||
GameMain.Server?.SwitchSubmarine();
|
||||
if (GameMain.Server != null && !GameMain.Server.TrySwitchSubmarine())
|
||||
{
|
||||
passed = false;
|
||||
State = VoteState.Failed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -71,6 +71,15 @@ namespace Barotrauma
|
||||
StrikesResetInterval = 60,
|
||||
StrikeThreshold = 6;
|
||||
|
||||
private const int MinPacketLimitMultipler = 1;
|
||||
|
||||
private static int GetMaxPacketLimit(ServerSettings settings)
|
||||
=> (int)MathF.Ceiling(
|
||||
settings.MaxPacketAmount *
|
||||
MathF.Max(
|
||||
settings.TickRate / (float)ServerSettings.DefaultTickRate,
|
||||
MinPacketLimitMultipler)); // Prevent the rate limit multiplier from being less than 1.
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server receives a packet to start logging how much time it takes to process.
|
||||
/// </summary>
|
||||
@@ -122,11 +131,7 @@ namespace Barotrauma
|
||||
|
||||
private void StartFor(Client client)
|
||||
{
|
||||
if (!clients.ContainsKey(client))
|
||||
{
|
||||
clients.Add(client, new OffenseData());
|
||||
}
|
||||
|
||||
clients.TryAdd(client, new OffenseData());
|
||||
clients[client].Stopwatch.Start();
|
||||
}
|
||||
|
||||
@@ -149,7 +154,7 @@ namespace Barotrauma
|
||||
if (GameMain.Server?.ServerSettings is not { } settings) { return; }
|
||||
|
||||
// client is sending too many packets, kick them
|
||||
if (data.PacketCount > settings.MaxPacketAmount && settings.MaxPacketAmount > ServerSettings.PacketLimitMin)
|
||||
if (data.PacketCount > GetMaxPacketLimit(settings) && settings.MaxPacketAmount > ServerSettings.PacketLimitMin)
|
||||
{
|
||||
AttemptKickClient(client, TextManager.Get("PacketLimitKicked"));
|
||||
clients.Remove(client);
|
||||
@@ -216,7 +221,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Server?.ServerSettings is { MaxPacketAmount: > ServerSettings.PacketLimitMin } settings)
|
||||
{
|
||||
if (data.PacketCount > settings.MaxPacketAmount * 0.9f)
|
||||
if (data.PacketCount > GetMaxPacketLimit(settings) * 0.9f)
|
||||
{
|
||||
GameServer.Log($"{NetworkMember.ClientLogName(client)} is sending a lot of packets and almost got kicked! ({data.PacketCount}).", ServerLog.MessageType.DoSProtection);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.0.13.2</Version>
|
||||
<Version>1.0.20.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Voronoi2.GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection);
|
||||
float distSqr = Vector2.DistanceSquared(character.SimPosition, potentialAttachPos);
|
||||
|
||||
@@ -506,6 +506,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SpeakAfterOrderReceived() { }
|
||||
|
||||
protected static bool CanEquip(Character character, Item item, bool allowWearing)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>(),
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user