Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
2
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
2
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
@@ -73,7 +73,7 @@ body:
|
||||
label: Version
|
||||
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
||||
options:
|
||||
- v1.8.8.1 (Calm Before the Storm Hotfix 2)
|
||||
- v1.9.7.0 (Summer Update 2025)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -435,6 +435,13 @@ namespace Barotrauma
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected * item.OffsetOnSelectedMultiplier;
|
||||
}
|
||||
else if (HeldItems.SelectMany(static item => item.GetComponents<Holdable>())
|
||||
.Where(static holdable => holdable.Aimable)
|
||||
.MaxOrNull(static holdable => holdable.CameraAimOffset) is float maxOffset
|
||||
&& maxOffset > 0f && IsKeyDown(InputType.Aim))
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = maxOffset;
|
||||
}
|
||||
else if (SelectedItem != null && ViewTarget == null &&
|
||||
SelectedItem.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this)))
|
||||
{
|
||||
|
||||
@@ -653,7 +653,10 @@ namespace Barotrauma
|
||||
(int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2),
|
||||
(int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)), character.Info.IsDisguisedAsAnother);
|
||||
float yOffset = (GameMain.GameSession?.Campaign is MultiPlayerCampaign ? -10 : 4) * GUI.Scale;
|
||||
character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, yOffset), targetWidth: HUDLayoutSettings.PortraitArea.Width, true, character.Info.IsDisguisedAsAnother);
|
||||
|
||||
character.Info?.DrawIcon(spriteBatch,
|
||||
new Vector2(HUDLayoutSettings.PortraitArea.Center.X - 12 * GUI.Scale, HUDLayoutSettings.PortraitArea.Center.Y), HUDLayoutSettings.PortraitArea.Size.ToVector2(),
|
||||
flip: true);
|
||||
character.Info.DrawForeground(spriteBatch);
|
||||
}
|
||||
mouseOnPortrait = MouseOnCharacterPortrait() && !character.ShouldLockHud();
|
||||
@@ -733,8 +736,9 @@ namespace Barotrauma
|
||||
|
||||
string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName;
|
||||
Vector2 textPos = startPos;
|
||||
Vector2 textSize = GUIStyle.Font.MeasureString(focusName);
|
||||
Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(focusName);
|
||||
//measure arbitrary one-line text instead of the potentially-multi-line name
|
||||
Vector2 textSize = GUIStyle.Font.MeasureString("T");
|
||||
Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString("T");
|
||||
|
||||
textPos -= new Vector2(textSize.X / 2, textSize.Y);
|
||||
|
||||
|
||||
@@ -360,64 +360,6 @@ namespace Barotrauma
|
||||
GUIStyle.Font.DrawString(spriteBatch, str, new Vector2(barRect.Right - iconXOffset - scaledTextSizeX - GUI.IntScale(4), barRect.Center.Y - scaledTextSizeY / 2), GUIStyle.TextColorNormal, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f);
|
||||
}
|
||||
|
||||
public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false, bool evaluateDisguise = false)
|
||||
{
|
||||
if (evaluateDisguise && IsDisguised) { return; }
|
||||
|
||||
Vector2? sheetIndex;
|
||||
Sprite portraitToDraw;
|
||||
List<WearableSprite> attachmentsToDraw;
|
||||
|
||||
Color hairColor;
|
||||
Color facialHairColor;
|
||||
Color skinColor;
|
||||
|
||||
if (!IsDisguisedAsAnother || !evaluateDisguise)
|
||||
{
|
||||
sheetIndex = Head.SheetIndex;
|
||||
portraitToDraw = Portrait;
|
||||
attachmentsToDraw = AttachmentSprites;
|
||||
|
||||
hairColor = Head.HairColor;
|
||||
facialHairColor = Head.FacialHairColor;
|
||||
skinColor = Head.SkinColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
sheetIndex = disguisedSheetIndex;
|
||||
portraitToDraw = disguisedPortrait;
|
||||
attachmentsToDraw = disguisedAttachmentSprites;
|
||||
|
||||
hairColor = disguisedHairColor;
|
||||
facialHairColor = disguisedFacialHairColor;
|
||||
skinColor = disguisedSkinColor;
|
||||
}
|
||||
|
||||
if (portraitToDraw != null)
|
||||
{
|
||||
var currEffect = spriteBatch.GetCurrentEffect();
|
||||
// Scale down the head sprite 10%
|
||||
float scale = targetWidth * 0.9f / Portrait.size.X;
|
||||
if (sheetIndex.HasValue)
|
||||
{
|
||||
SetHeadEffect(spriteBatch);
|
||||
portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size);
|
||||
}
|
||||
portraitToDraw.Draw(spriteBatch, screenPos + offset, skinColor, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
|
||||
if (attachmentsToDraw != null)
|
||||
{
|
||||
float depthStep = 0.000001f;
|
||||
foreach (var attachment in attachmentsToDraw)
|
||||
{
|
||||
SetAttachmentEffect(spriteBatch, attachment);
|
||||
DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment, hairColor, facialHairColor), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
|
||||
depthStep += depthStep;
|
||||
}
|
||||
}
|
||||
spriteBatch.SwapEffect(currEffect);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: I hate this so much :(
|
||||
private SpriteBatch.EffectWithParams headEffectParameters;
|
||||
private Dictionary<WearableType, SpriteBatch.EffectWithParams> attachmentEffectParameters
|
||||
@@ -466,23 +408,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize)
|
||||
public void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize, bool flip = false)
|
||||
{
|
||||
var headSprite = HeadSprite;
|
||||
if (headSprite != null)
|
||||
{
|
||||
var spriteEffects = flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
||||
|
||||
var currEffect = spriteBatch.GetCurrentEffect();
|
||||
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
|
||||
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.ToPoint()), headSprite.SourceRect.Size);
|
||||
SetHeadEffect(spriteBatch);
|
||||
headSprite.Draw(spriteBatch, screenPos, scale: scale, color: Head.SkinColor);
|
||||
headSprite.Draw(spriteBatch, screenPos, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects);
|
||||
if (AttachmentSprites != null)
|
||||
{
|
||||
float depthStep = 0.000001f;
|
||||
foreach (var attachment in AttachmentSprites)
|
||||
{
|
||||
SetAttachmentEffect(spriteBatch, attachment);
|
||||
DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment, Head.HairColor, Head.FacialHairColor));
|
||||
DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment, Head.HairColor, Head.FacialHairColor),
|
||||
spriteEffects: spriteEffects);
|
||||
depthStep += depthStep;
|
||||
}
|
||||
}
|
||||
@@ -534,9 +479,6 @@ namespace Barotrauma
|
||||
{
|
||||
origin.Y = attachment.Sprite.size.Y - origin.Y;
|
||||
}
|
||||
//the portrait's origin is forced to 0,0 (presumably for easier drawing on the UI?), see LoadHeadElement
|
||||
//we need to take that into account here and draw the attachment at where the origin of the "actual" head sprite would be
|
||||
drawPos += HeadSprite.Origin * scale;
|
||||
}
|
||||
float depth = attachment.Sprite.Depth;
|
||||
if (attachment.InheritLimbDepth)
|
||||
@@ -545,7 +487,6 @@ namespace Barotrauma
|
||||
}
|
||||
attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
|
||||
}
|
||||
|
||||
public static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc, bool requireJobPrefabFound = true)
|
||||
{
|
||||
ushort infoID = inc.ReadUInt16();
|
||||
@@ -568,7 +509,6 @@ namespace Barotrauma
|
||||
Color hairColor = inc.ReadColorR8G8B8();
|
||||
Color facialHairColor = inc.ReadColorR8G8B8();
|
||||
|
||||
|
||||
Identifier npcId = inc.ReadIdentifier();
|
||||
|
||||
Identifier factionId = inc.ReadIdentifier();
|
||||
|
||||
@@ -833,6 +833,9 @@ namespace Barotrauma
|
||||
causeOfDeathAffliction = afflictionPrefab;
|
||||
}
|
||||
}
|
||||
|
||||
Character killer = FindEntityByID(msg.ReadUInt16()) as Character;
|
||||
|
||||
bool containsAfflictionData = msg.ReadBoolean();
|
||||
if (!IsDead)
|
||||
{
|
||||
@@ -842,7 +845,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Kill(causeOfDeathType, causeOfDeathAffliction?.Instantiate(1.0f), true);
|
||||
Kill(causeOfDeathType, causeOfDeathAffliction?.Instantiate(1.0f, killer), true);
|
||||
}
|
||||
}
|
||||
if (containsAfflictionData)
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace Barotrauma
|
||||
new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform, Anchor.CenterLeft),
|
||||
onDraw: (spriteBatch, component) =>
|
||||
{
|
||||
character.Info?.DrawPortrait(spriteBatch, new Vector2(component.Rect.X, component.Rect.Center.Y - component.Rect.Width / 2), Vector2.Zero, component.Rect.Width, false, character != Character.Controlled);
|
||||
character.Info?.DrawIcon(spriteBatch, component.Rect.Center.ToVector2(), component.Rect.Size.ToVector2());
|
||||
});
|
||||
characterName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), nameContainer.RectTransform), "", textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont)
|
||||
{
|
||||
@@ -1041,8 +1041,29 @@ namespace Barotrauma
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
|
||||
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
|
||||
if (affliction.Prefab is AfflictionPrefab { AfflictionOverlay: not null } afflictionPrefab)
|
||||
{
|
||||
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
if (afflictionPrefab.AfflictionOverlay is SpriteSheet spriteSheet)
|
||||
{
|
||||
spriteSheet.Draw(spriteBatch,
|
||||
spriteIndex: spriteSheet.GetAnimatedSpriteIndex(afflictionPrefab.AfflictionOverlayAnimSpeed),
|
||||
pos: Vector2.Zero,
|
||||
color: Color.White * affliction.GetAfflictionOverlayMultiplier(),
|
||||
origin: Vector2.Zero,
|
||||
rotate: 0,
|
||||
scale: screenSize / spriteSheet.FrameSize.ToVector2());
|
||||
}
|
||||
else if (afflictionPrefab.AfflictionOverlay is Sprite sprite)
|
||||
{
|
||||
sprite.Draw(spriteBatch,
|
||||
pos: Vector2.Zero,
|
||||
color: Color.White * affliction.GetAfflictionOverlayMultiplier(),
|
||||
origin: Vector2.Zero,
|
||||
rotate: 0,
|
||||
scale: screenSize / sprite.size);
|
||||
}
|
||||
}
|
||||
|
||||
var activeEffect = affliction.GetActiveEffect();
|
||||
if (activeEffect is { ThermalOverlayRange: > 0.0f })
|
||||
@@ -1554,9 +1575,9 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
|
||||
affliction.Prefab.GetDescription(
|
||||
RichString.Rich(affliction.Prefab.GetDescription(
|
||||
affliction.Strength,
|
||||
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter),
|
||||
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter)),
|
||||
textAlignment: Alignment.TopLeft, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
|
||||
@@ -912,6 +912,14 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("The command 'wikiimage_sub' failed.", e);
|
||||
}
|
||||
}));
|
||||
|
||||
AssignOnExecute("loslightingfreecam", (string[] args) =>
|
||||
{
|
||||
ExecuteCommand("los");
|
||||
ExecuteCommand("lighting");
|
||||
ExecuteCommand("freecam");
|
||||
});
|
||||
AssignRelayToServer("loslightingfreecam", false);
|
||||
|
||||
AssignRelayToServer("kick", false);
|
||||
AssignRelayToServer("kickid", false);
|
||||
@@ -1101,6 +1109,42 @@ namespace Barotrauma
|
||||
(lightComponent.LightColor.A / 255.0f) * value.W);
|
||||
}
|
||||
}, isCheat: false));
|
||||
|
||||
commands.Add(new Command("steamtimelinetest", "steamtimelinetest: Test the Steamworks timeline feature.", (string[] args) =>
|
||||
{
|
||||
// Add an instantaneous event to the Steam timeline
|
||||
var eventHandle = Steamworks.SteamTimeline.AddInstantaneousTimelineEvent(
|
||||
"Barotrauma Test Event",
|
||||
"This is a test event created from the debug console",
|
||||
"steam_marker", // Important: icon must be specified, or it does nothing :D
|
||||
1, // Priority
|
||||
0.0f, // Current time (no offset)
|
||||
Steamworks.TimelineEventClipPriority.Standard);
|
||||
|
||||
NewMessage($"Steamworks timeline test: Added instantaneous event with handle: {eventHandle}", Color.Yellow);
|
||||
}));
|
||||
|
||||
commands.Add(new Command("setsteamtimelinegamemode", "setsteamtimelinegamemode [gamemode]: Sets the Steam timeline gamemode to the specified value.", args =>
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
NewMessage("Please specify a gamemode. Available modes: " + string.Join(", ", Enum.GetNames(typeof(SteamTimelineManager.TimelineGameMode))), Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Enum.TryParse<SteamTimelineManager.TimelineGameMode>(args[0], ignoreCase: true, out var gameMode))
|
||||
{
|
||||
SteamTimelineManager.SetTimelineGameMode(gameMode);
|
||||
NewMessage($"Timeline gamemode set to: {gameMode}", Color.Green);
|
||||
}
|
||||
else
|
||||
{
|
||||
NewMessage($"Invalid gamemode '{args[0]}'. Available modes: " + string.Join(", ", Enum.GetNames(typeof(SteamTimelineManager.TimelineGameMode))), Color.Red);
|
||||
}
|
||||
}, isCheat: true, getValidArgs: () =>
|
||||
{
|
||||
return new[] { Enum.GetNames(typeof(SteamTimelineManager.TimelineGameMode)) };
|
||||
}));
|
||||
|
||||
commands.Add(new Command("color|colour", "Change color (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (string[] args) =>
|
||||
{
|
||||
@@ -2535,6 +2579,85 @@ namespace Barotrauma
|
||||
|
||||
#if DEBUG
|
||||
|
||||
commands.Add(new Command(
|
||||
"listachievements",
|
||||
"listachievements: Lists all achievement identifiers, names, descriptions, and their current Steam status (Locked/Unlocked).",
|
||||
(string[] args) =>
|
||||
{
|
||||
NewMessage("--- Achievement Status: Name - (Identifier) - [Status] - Description ---", Color.Cyan);
|
||||
|
||||
var supportedIds = AchievementManager.SupportedAchievements;
|
||||
|
||||
if (supportedIds == null || !supportedIds.Any())
|
||||
{
|
||||
NewMessage("No achievement identifiers found in AchievementManager.SupportedAchievements.", Color.Yellow);
|
||||
NewMessage("-------------------------------------------------------------------", Color.Cyan);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SteamManager.IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
NewMessage("Steam not initialized. Cannot fetch achievement status or texts.", Color.Red);
|
||||
foreach (Identifier id in supportedIds.OrderBy(i => i.Value))
|
||||
{
|
||||
NewMessage($"- Name N/A - ({id.Value}) - [Status Unknown] - Description N/A", Color.Red);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var steamAchievements = Steamworks.SteamUserStats.Achievements
|
||||
.ToDictionary(a => a.Identifier, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (Identifier id in supportedIds.OrderBy(i => i.Value))
|
||||
{
|
||||
string statusText;
|
||||
string nameText = "Name N/A";
|
||||
string descText = "Description N/A";
|
||||
Color statusColor;
|
||||
|
||||
if (steamAchievements.TryGetValue(id.Value, out var steamAch))
|
||||
{
|
||||
nameText = steamAch.Name ?? "Name N/A";
|
||||
descText = steamAch.Description ?? "Description N/A";
|
||||
|
||||
if (steamAch.State)
|
||||
{
|
||||
statusText = "[Unlocked]";
|
||||
statusColor = Color.LimeGreen;
|
||||
}
|
||||
else
|
||||
{
|
||||
statusText = "[Locked]";
|
||||
statusColor = Color.Orange;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
statusText = "[Not Found on Steam]";
|
||||
statusColor = Color.Red;
|
||||
}
|
||||
|
||||
string output = $"- {nameText} - ({id.Value}) - {statusText} - {descText}";
|
||||
NewMessage(output, statusColor);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ThrowError("Error retrieving achievement statuses/texts from Steam.", e);
|
||||
foreach (Identifier id in supportedIds.OrderBy(i => i.Value))
|
||||
{
|
||||
NewMessage($"- Name N/A - ({id.Value}) - [Status Error] - Description N/A", Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NewMessage("-------------------------------------------------------------------", Color.Cyan);
|
||||
},
|
||||
isCheat: true
|
||||
));
|
||||
|
||||
commands.Add(new Command("unlockachievement", "unlockachievement [identifier]: Unlocks the specified achievement.", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 1)
|
||||
@@ -2545,6 +2668,60 @@ namespace Barotrauma
|
||||
NewMessage($"Unlocked \"{args[0]}\".");
|
||||
AchievementManager.UnlockAchievement(args[0].ToIdentifier());
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command(
|
||||
"resetachievement",
|
||||
"resetachievement [identifier]: Clears (locks) the specified Steam achievement for testing.",
|
||||
args =>
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
ThrowError("Please specify the achievement identifier to reset.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SteamManager.IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
ThrowError("Steam not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
string achievementId = args[0];
|
||||
bool found = false;
|
||||
bool success = false;
|
||||
|
||||
try
|
||||
{
|
||||
var achievement = Steamworks.SteamUserStats.Achievements
|
||||
.FirstOrDefault(a => a.Identifier.Equals(achievementId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (achievement.Identifier == null)
|
||||
{
|
||||
ThrowError($"Achievement with identifier \"{achievementId}\" not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
found = true;
|
||||
success = achievement.Clear();
|
||||
|
||||
if (success)
|
||||
{
|
||||
// IMPORTANT: Store the stats to make the change persistent
|
||||
SteamManager.StoreStats();
|
||||
NewMessage($"Reset achievement \"{achievementId}\".", Color.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowError($"Failed to clear achievement \"{achievementId}\" (Steamworks returned false).");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ThrowError($"An error occurred while trying to reset achievement \"{achievementId}\". Found: {found}, Success: {success}", e);
|
||||
}
|
||||
},
|
||||
isCheat: true
|
||||
));
|
||||
|
||||
commands.Add(new Command("deathprompt", "Shows the death prompt for testing purposes.", (string[] args) =>
|
||||
{
|
||||
@@ -2706,7 +2883,14 @@ namespace Barotrauma
|
||||
{
|
||||
int amount = 1;
|
||||
if (args.Length > 0) { int.TryParse(args[0], out amount); }
|
||||
GameMain.LevelEditorScreen.TestLevelGenerationForErrors(amount);
|
||||
try
|
||||
{
|
||||
GameMain.LevelEditorScreen.TestLevelGenerationForErrors(amount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ThrowError("Failed to generate levels", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -139,6 +139,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Entity> HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item);
|
||||
public override IEnumerable<Entity> HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2236,6 +2236,29 @@ namespace Barotrauma
|
||||
return textBox;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pre-built filter box.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The filter function must be set using <see cref="GUITextBox.OnTextChanged"/>.<see langword="add"/>.
|
||||
/// </remarks>
|
||||
public static GUITextBox CreateFilterBox(RectTransform rectT)
|
||||
{
|
||||
GUITextBox textBox = new(rectT, createClearButton: true)
|
||||
{
|
||||
OnEnterPressed = (tb, _) =>
|
||||
{
|
||||
tb.Deselect();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
GUITextBlock label = new(new RectTransform(Vector2.One, textBox.TextBlock.RectTransform, Anchor.CenterLeft), TextManager.Get("serverlog.filter"), GUIStyle.TextColorNormal * 0.75f);
|
||||
textBox.OnSelected += (_, _) => label.Visible = false;
|
||||
textBox.OnDeselected += (tb, _) => label.Visible = tb.Text.IsNullOrEmpty();
|
||||
textBox.OnTextChanged += (tb, text) => label.Visible = !tb.Selected && text.IsNullOrEmpty();
|
||||
return textBox;
|
||||
}
|
||||
|
||||
public static void NotifyPrompt(LocalizedString header, LocalizedString body)
|
||||
{
|
||||
GUIMessageBox msgBox = new GUIMessageBox(header, body, new[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
|
||||
@@ -2295,9 +2318,61 @@ namespace Barotrauma
|
||||
return msgBox;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#nullable enable
|
||||
#region Item UI
|
||||
/// <summary>
|
||||
/// Creates a 7-segment display.
|
||||
/// </summary>
|
||||
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/> or empty.</param>
|
||||
/// <param name="rightLabelText">Defaults to <c>TextManager.Get("kilowatt")</c>.</param>
|
||||
/// <param name="leftLabelFont">Defaults to <see cref="GUIStyle.LargeFont"/>.</param>
|
||||
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
|
||||
{
|
||||
GUILayoutGroup textArea = new(rect, isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true,
|
||||
CanBeFocused = true,
|
||||
ToolTip = tooltip!,
|
||||
AbsoluteSpacing = 5
|
||||
};
|
||||
|
||||
#region Element positioning
|
||||
leftLabel = null;
|
||||
if (!leftLabelText.IsNullOrEmpty())
|
||||
{
|
||||
leftLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), textArea.RectTransform), leftLabelText, textColor: GUIStyle.TextColorBright, font: leftLabelFont ?? GUIStyle.LargeFont, textAlignment: Alignment.CenterRight);
|
||||
}
|
||||
|
||||
GUIFrame displayBackground = new(new RectTransform(new Vector2(0.55f, 0.8f), textArea.RectTransform), style: "DigitalFrameDark");
|
||||
GUITextBlock displayText = new(new RectTransform(new Vector2(0.9f, 0.95f), displayBackground.RectTransform, Anchor.Center), "8888", font: GUIStyle.DigitalFont, textColor: GUIStyle.TextColorDark, textAlignment: Alignment.CenterRight);
|
||||
displayText.TextScale = Math.Max((displayText.Rect.Height - 10) / GUIStyle.DigitalFont.LineHeight, 0.1f);
|
||||
|
||||
rightLabel = new GUITextBlock(new RectTransform(Vector2.Zero, textArea.RectTransform), rightLabelText ?? TextManager.Get("kilowatt"), textColor: GUIStyle.TextColorNormal, font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
|
||||
{
|
||||
Padding = Vector4.Zero
|
||||
};
|
||||
rightLabel.RectTransform.MinSize = rightLabel.TextSize.ToPoint();
|
||||
|
||||
textArea.GetAllChildren().ForEach(child => child.CanBeFocused = false);
|
||||
return displayText;
|
||||
}
|
||||
|
||||
/// <param name="labelFont">Defaults to <see cref="GUIStyle.SubHeadingFont"/>.</param>
|
||||
public static GUITickBox CreateIndicatorLight(RectTransform rect, string style = "", LocalizedString? label = null, LocalizedString? tooltip = null, GUIFont? labelFont = null)
|
||||
{
|
||||
GUITickBox indicator = new(rect, label, font: labelFont ?? GUIStyle.SubHeadingFont, style: style)
|
||||
{
|
||||
Enabled = false,
|
||||
ToolTip = tooltip!
|
||||
};
|
||||
indicator.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal);
|
||||
return indicator;
|
||||
}
|
||||
#endregion
|
||||
#nullable restore
|
||||
|
||||
#endregion
|
||||
|
||||
#region Element positioning
|
||||
private static List<T> CreateElements<T>(int count, RectTransform parent, Func<RectTransform, T> constructor,
|
||||
Vector2? relativeSize = null, Point? absoluteSize = null,
|
||||
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null,
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var selfStyle = Style;
|
||||
textBlock = new GUITextBlock(new RectTransform(Vector2.One, rectT, Anchor.Center), text, textAlignment: textAlignment, style: null)
|
||||
textBlock = new GUITextBlock(new RectTransform(Vector2.One, rectT, Anchor.Center), RichString.Rich(text), textAlignment: textAlignment, style: null)
|
||||
{
|
||||
TextColor = selfStyle?.TextColor ?? Color.Black,
|
||||
HoverTextColor = selfStyle?.HoverTextColor ?? Color.Black,
|
||||
|
||||
@@ -446,19 +446,22 @@ namespace Barotrauma
|
||||
UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
public void Select(object userData, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled)
|
||||
public bool Select(object userData, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled)
|
||||
{
|
||||
var children = Content.Children;
|
||||
int i = 0;
|
||||
bool wasSelected = false;
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
if (Equals(child.UserData, userData))
|
||||
{
|
||||
wasSelected = true;
|
||||
Select(i, force, autoScroll);
|
||||
if (!SelectMultiple) { return; }
|
||||
if (!SelectMultiple) { return true; }
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return wasSelected;
|
||||
}
|
||||
|
||||
private Point CalculateFrameSize(bool isHorizontal, int scrollBarSize)
|
||||
|
||||
@@ -1021,6 +1021,14 @@ namespace Barotrauma
|
||||
{
|
||||
MaxTextLength = Client.MaxNameLength
|
||||
};
|
||||
nameBox.OnTextChanged += (GUITextBox textBox, string text) =>
|
||||
{
|
||||
if (text.Contains('\n') || text.Contains('\r'))
|
||||
{
|
||||
textBox.Text = text.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
new GUIButton(new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get("confirm"))
|
||||
{
|
||||
OnClicked = (button, userData) =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -749,7 +749,7 @@ namespace Barotrauma
|
||||
{
|
||||
new GUICustomComponent(new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: ScaleBasis.BothHeight), (spriteBatch, component) =>
|
||||
{
|
||||
info.DrawPortrait(spriteBatch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width);
|
||||
info.DrawIcon(spriteBatch, component.Rect.Center.ToVector2(), component.Rect.Size.ToVector2());
|
||||
});
|
||||
|
||||
GUILayoutGroup textGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.8f), parent.RectTransform));
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace Barotrauma
|
||||
|
||||
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
|
||||
{
|
||||
info.DrawPortrait(batch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width, false, false);
|
||||
info.DrawIcon(batch, component.Rect.Center.ToVector2(), component.Rect.Size.ToVector2());
|
||||
});
|
||||
|
||||
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
|
||||
@@ -327,7 +327,7 @@ namespace Barotrauma
|
||||
if (extraTalent.IsHiddenExtraTalent) { continue; }
|
||||
GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
|
||||
{
|
||||
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
|
||||
ToolTip = GetTalentTooltip(extraTalent, characterInfo),
|
||||
Color = GUIStyle.Green
|
||||
};
|
||||
}
|
||||
@@ -637,7 +637,7 @@ namespace Barotrauma
|
||||
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
|
||||
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
|
||||
{
|
||||
ToolTip = CreateTooltip(talent, characterInfo),
|
||||
ToolTip = GetTalentTooltip(talent, characterInfo),
|
||||
UserData = talent.Identifier,
|
||||
PressedColor = pressedColor,
|
||||
Enabled = info.Character != null,
|
||||
@@ -703,24 +703,6 @@ namespace Barotrauma
|
||||
},
|
||||
};
|
||||
|
||||
static RichString CreateTooltip(TalentPrefab talent, CharacterInfo? character)
|
||||
{
|
||||
LocalizedString progress = string.Empty;
|
||||
|
||||
if (character is not null && talent.TrackedStat.TryUnwrap(out var stat))
|
||||
{
|
||||
var statValue = character.GetSavedStatValue(StatTypes.None, stat.PermanentStatIdentifier);
|
||||
var intValue = (int)MathF.Round(statValue);
|
||||
progress = "\n\n";
|
||||
progress += statValue < stat.Max
|
||||
? TextManager.GetWithVariables("talentprogress", ("[amount]", intValue.ToString()), ("[max]", stat.Max.ToString()))
|
||||
: TextManager.Get("talentprogresscompleted");
|
||||
}
|
||||
|
||||
RichString tooltip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖\n\n{ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)}{progress}");
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
|
||||
|
||||
GUIComponent iconImage;
|
||||
@@ -814,6 +796,24 @@ namespace Barotrauma
|
||||
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
|
||||
}
|
||||
|
||||
private static RichString GetTalentTooltip(TalentPrefab talent, CharacterInfo? character)
|
||||
{
|
||||
LocalizedString progress = string.Empty;
|
||||
|
||||
if (character is not null && talent.TrackedStat.TryUnwrap(out var stat))
|
||||
{
|
||||
var statValue = character.GetSavedStatValue(StatTypes.None, stat.PermanentStatIdentifier);
|
||||
var intValue = (int)MathF.Round(statValue);
|
||||
progress = "\n\n";
|
||||
progress += statValue < stat.Max
|
||||
? TextManager.GetWithVariables("talentprogress", ("[amount]", intValue.ToString()), ("[max]", stat.Max.ToString()))
|
||||
: TextManager.Get("talentprogresscompleted");
|
||||
}
|
||||
|
||||
RichString tooltip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖\n\n{ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)}{progress}");
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
private bool ResetTalentSelection(GUIButton guiButton, object userData)
|
||||
{
|
||||
if (characterInfo is null) { return false; }
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
@@ -949,7 +950,7 @@ namespace Barotrauma
|
||||
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1f, 1f, toggleButton.Frame), isHorizontal: true);
|
||||
|
||||
LocalizedString slotText = "";
|
||||
if (linkedItems.Count() > 1)
|
||||
if (linkedItems.Count > 1)
|
||||
{
|
||||
slotText = TextManager.GetWithVariable("weaponslot", "[number]", string.Join(", ", linkedItems.Select(it => (swappableEntities.IndexOf(it) + 1).ToString())));
|
||||
}
|
||||
@@ -978,7 +979,7 @@ namespace Barotrauma
|
||||
List<GUIFrame> frames = new List<GUIFrame>();
|
||||
if (currentOrPending != null)
|
||||
{
|
||||
bool canUninstall = item.PendingItemSwap != null || !(currentOrPending.SwappableItem?.ReplacementOnUninstall.IsEmpty ?? true);
|
||||
bool canUninstall = HasPermission && (item.PendingItemSwap != null || !(currentOrPending.SwappableItem?.ReplacementOnUninstall.IsEmpty ?? true));
|
||||
|
||||
bool isUninstallPending = item.Prefab.SwappableItem != null && item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall;
|
||||
if (isUninstallPending) { canUninstall = false; }
|
||||
@@ -1030,7 +1031,8 @@ namespace Barotrauma
|
||||
buttonStyle: isPurchased ? "WeaponInstallButton" : "StoreAddToCrateButton").Frame);
|
||||
|
||||
if (!(frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton buyButton)) { continue; }
|
||||
if (PlayerBalance >= price)
|
||||
|
||||
if (HasPermission && PlayerBalance >= price)
|
||||
{
|
||||
buyButton.Enabled = true;
|
||||
buyButton.OnClicked += (button, o) =>
|
||||
@@ -1272,7 +1274,7 @@ namespace Barotrauma
|
||||
|
||||
if (!prefabFrame.BuyButton.TryUnwrap(out BuyButtonFrame buyButtonFrame)) { return; }
|
||||
|
||||
if (!HasPermission || !prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
|
||||
if (!prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
|
||||
{
|
||||
prefabFrame.Frame.Enabled = false;
|
||||
prefabFrame.Description.Enabled = false;
|
||||
@@ -1289,12 +1291,14 @@ namespace Barotrauma
|
||||
("[amount]", prefab.Price.GetBuyPrice(prefab, Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation, characterList).ToString()));
|
||||
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
|
||||
{
|
||||
if (GameMain.NetworkMember != null)
|
||||
if (Campaign.UpgradeManager.TryPurchaseUpgrade(prefab, category))
|
||||
{
|
||||
WaitForServerUpdate = true;
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
WaitForServerUpdate = true;
|
||||
}
|
||||
GameMain.Client?.SendCampaignState();
|
||||
}
|
||||
Campaign.UpgradeManager.PurchaseUpgrade(prefab, category);
|
||||
GameMain.Client?.SendCampaignState();
|
||||
return true;
|
||||
}, overrideConfirmButtonSound: GUISoundType.ConfirmTransaction);
|
||||
|
||||
@@ -1722,7 +1726,7 @@ namespace Barotrauma
|
||||
|
||||
if (buttonParent.FindChild(UpgradeStoreUserData.BuyButton, recursive: true) is GUIButton button)
|
||||
{
|
||||
bool canBuy = !WaitForServerUpdate && !isMax && campaign.GetBalance() >= price && prefab.HasResourcesToUpgrade(Character.Controlled, currentLevel + 1);
|
||||
bool canBuy = !WaitForServerUpdate && HasPermission && !isMax && campaign.GetBalance() >= price && prefab.HasResourcesToUpgrade(Character.Controlled, currentLevel + 1);
|
||||
|
||||
button.Enabled = canBuy;
|
||||
}
|
||||
@@ -1935,7 +1939,7 @@ namespace Barotrauma
|
||||
return frames.ToArray();
|
||||
}
|
||||
|
||||
private bool HasPermission => true;
|
||||
private static bool HasPermission => CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageCampaign);
|
||||
|
||||
// just a shortcut to create new RectTransforms since all the new RectTransform and new Vector2 confuses my IDE (and me)
|
||||
private static RectTransform rectT(float x, float y, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft, ScaleBasis scaleBasis = ScaleBasis.Normal)
|
||||
|
||||
@@ -686,6 +686,7 @@ namespace Barotrauma
|
||||
|
||||
private void OnInvitedToSteamGame(string connectCommand)
|
||||
{
|
||||
DebugConsole.NewMessage($"Invited to Steam game, connect command: {connectCommand}", Color.Lime);
|
||||
try
|
||||
{
|
||||
ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ToolBox.SplitCommand(connectCommand));
|
||||
@@ -825,6 +826,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (ConnectCommand.TryUnwrap(out var connectCommand))
|
||||
{
|
||||
DebugConsole.NewMessage($"Processing connect command: {connectCommand}...", Color.Lime);
|
||||
if (Client != null)
|
||||
{
|
||||
Client.Quit();
|
||||
@@ -836,6 +838,7 @@ namespace Barotrauma
|
||||
|
||||
if (connectCommand.SteamLobbyIdOption.TryUnwrap(out var lobbyId))
|
||||
{
|
||||
DebugConsole.NewMessage($"Connecting to lobby ID {lobbyId}...", Color.Lime);
|
||||
SteamManager.JoinLobby(lobbyId.Value, joinServer: true);
|
||||
}
|
||||
else if ((connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint) && nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints }))
|
||||
@@ -844,6 +847,7 @@ namespace Barotrauma
|
||||
endpoints.Cast<Endpoint>().ToImmutableArray(),
|
||||
string.IsNullOrWhiteSpace(serverName) ? endpoints.First().StringRepresentation : serverName,
|
||||
Option<int>.None());
|
||||
DebugConsole.NewMessage($"Connecting to endpoint {endpoints.First().StringRepresentation}...", Color.Lime);
|
||||
}
|
||||
else if ((connectCommand.NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint) && nameAndLidgrenEndpoint is { ServerName: var lidgrenServerName, Endpoint: var endpoint }))
|
||||
{
|
||||
@@ -853,6 +857,10 @@ namespace Barotrauma
|
||||
string.IsNullOrWhiteSpace(lidgrenServerName) ? endpoint.StringRepresentation : lidgrenServerName,
|
||||
Option<int>.None());
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Cannot connect: unrecognized connect command.", Color.Lime);
|
||||
}
|
||||
|
||||
ConnectCommand = Option<ConnectCommand>.None();
|
||||
}
|
||||
@@ -1197,8 +1205,9 @@ namespace Barotrauma
|
||||
|
||||
CoroutineManager.StopCoroutines("EndCinematic");
|
||||
|
||||
if (GameSession != null)
|
||||
if (GameSession != null && GameSession.IsRunning)
|
||||
{
|
||||
AchievementManager.OnRoundEnded(GameSession, roundInterrupted: true);
|
||||
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
|
||||
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
|
||||
GameSession.RoundDuration);
|
||||
|
||||
@@ -592,8 +592,7 @@ namespace Barotrauma
|
||||
public bool CharacterClicked(GUIComponent component, object selection)
|
||||
{
|
||||
if (!AllowCharacterSwitch) { return false; }
|
||||
if (selection is not Character character || character.IsDead || character.IsUnconscious) { return false; }
|
||||
if (!character.IsOnPlayerTeam) { return false; }
|
||||
if (selection is not Character character || !character.IsOnPlayerTeam) { return false; }
|
||||
|
||||
if (GameMain.IsMultiplayer)
|
||||
{
|
||||
@@ -605,6 +604,8 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
if (character.IsDead || character.IsUnconscious) { return false; }
|
||||
|
||||
SelectCharacter(character);
|
||||
if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; }
|
||||
return true;
|
||||
@@ -3703,7 +3704,8 @@ namespace Barotrauma
|
||||
canIssueOrders =
|
||||
ChatMessage.CanUseRadio(Character.Controlled) &&
|
||||
Character.Controlled?.CurrentHull?.Submarine?.TeamID == Character.Controlled.TeamID &&
|
||||
!Character.Controlled.CurrentHull.Submarine.Info.IsWreck;
|
||||
!Character.Controlled.CurrentHull.Submarine.Info.IsWreck &&
|
||||
GameMain.Client is not { IsBlockedBySpamFilter: true };
|
||||
}
|
||||
|
||||
if (canIssueOrders)
|
||||
|
||||
@@ -133,56 +133,15 @@ namespace Barotrauma
|
||||
case "map":
|
||||
map = Map.Load(this, subElement);
|
||||
break;
|
||||
case "cargo":
|
||||
CargoManager.LoadPurchasedItems(subElement);
|
||||
break;
|
||||
case "pendingupgrades": //backwards compatibility
|
||||
case "upgrademanager":
|
||||
UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true);
|
||||
break;
|
||||
case "pets":
|
||||
petsElement = subElement;
|
||||
break;
|
||||
case Wallet.LowerCaseSaveElementName:
|
||||
Bank = new Wallet(Option<Character>.None(), subElement);
|
||||
break;
|
||||
case "stats":
|
||||
LoadStats(subElement);
|
||||
break;
|
||||
case "eventmanager":
|
||||
GameMain.GameSession.EventManager.Load(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LoadSaveSharedSingleAndMultiplayer(element);
|
||||
|
||||
UpgradeManager ??= new UpgradeManager(this);
|
||||
|
||||
InitUI();
|
||||
|
||||
//backwards compatibility for saves made prior to the addition of personal wallets
|
||||
int oldMoney = element.GetAttributeInt("money", 0);
|
||||
if (oldMoney > 0)
|
||||
{
|
||||
Bank = new Wallet(Option<Character>.None())
|
||||
{
|
||||
Balance = oldMoney
|
||||
};
|
||||
}
|
||||
|
||||
PurchasedLostShuttlesInLatestSave = element.GetAttributeBool("purchasedlostshuttles", false);
|
||||
PurchasedHullRepairsInLatestSave = element.GetAttributeBool("purchasedhullrepairs", false);
|
||||
PurchasedItemRepairsInLatestSave = element.GetAttributeBool("purchaseditemrepairs", false);
|
||||
CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
|
||||
if (CheatsEnabled)
|
||||
{
|
||||
DebugConsole.CheatsEnabled = true;
|
||||
if (!AchievementManager.CheatsEnabled)
|
||||
{
|
||||
AchievementManager.CheatsEnabled = true;
|
||||
new GUIMessageBox("Cheats enabled", "Cheat commands have been enabled on the campaign. You will not receive Steam Achievements until you restart the game.");
|
||||
}
|
||||
}
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
throw new System.Exception("Failed to load the campaign save file (saved with an older, incompatible version of Barotrauma).");
|
||||
@@ -687,6 +646,11 @@ namespace Barotrauma
|
||||
modeElement.Add(GameMain.GameSession?.EventManager.Save());
|
||||
}
|
||||
|
||||
foreach (Identifier unlockedRecipe in GameMain.GameSession.UnlockedRecipes)
|
||||
{
|
||||
modeElement.Add(new XElement("unlockedrecipe", new XAttribute("identifier", unlockedRecipe)));
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
|
||||
@@ -346,7 +346,6 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void RefreshAnyOpenPlayerInfo()
|
||||
{
|
||||
DebugConsole.NewMessage($"Refreshing any open player info");
|
||||
if (IsTabMenuOpen && TabMenu.SelectedTab == TabMenu.InfoFrameTab.Talents)
|
||||
{
|
||||
TabMenuInstance.SelectInfoFrameTab(TabMenu.InfoFrameTab.Talents);
|
||||
|
||||
@@ -907,7 +907,7 @@ namespace Barotrauma
|
||||
void SetReputationText(GUITextBlock textBlock)
|
||||
{
|
||||
LocalizedString reputationText = Reputation.GetFormattedReputationText(reputation.NormalizedValue, reputation.Value, addColorTags: true);
|
||||
int reputationChange = (int)Math.Round(reputation.Value - initialReputation);
|
||||
int reputationChange = (int)reputation.Value - (int)initialReputation;
|
||||
if (Math.Abs(reputationChange) > 0)
|
||||
{
|
||||
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
|
||||
|
||||
@@ -771,6 +771,8 @@ namespace Barotrauma
|
||||
|
||||
private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment)
|
||||
{
|
||||
if (!item.IsInteractable(Character.Controlled)) { return QuickUseAction.None; }
|
||||
|
||||
if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null &&
|
||||
//if the item can be equipped in the health interface slot, don't use it as a treatment but try to equip it
|
||||
!item.AllowedSlots.Contains(InvSlotType.HealthInterface))
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
Health = msg.ReadRangedSingle(0, MaxHealth, 8);
|
||||
Health = msg.ReadRangedSingle(0, MaxWater, 8);
|
||||
int startOffset = msg.ReadRangedInteger(-1, MaximumVines);
|
||||
if (startOffset > -1)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -15,18 +16,33 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
|
||||
{
|
||||
if (!IsActive || picker == null || !CanBeAttached(picker) || !picker.IsKeyDown(InputType.Aim) || picker != Character.Controlled)
|
||||
if (!IsActive || picker == null || !picker.IsKeyDown(InputType.Aim) || picker != Character.Controlled || !attachable)
|
||||
{
|
||||
Drawable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 gridPos = picker.Position;
|
||||
Vector2 roundedGridPos = new Vector2(
|
||||
MathUtils.RoundTowardsClosest(picker.Position.X, Submarine.GridSize.X),
|
||||
MathUtils.RoundTowardsClosest(picker.Position.Y, Submarine.GridSize.Y));
|
||||
Color indicatorColor = Color.White;
|
||||
if (!CanBeAttached(picker, out IEnumerable<Item> overlappingItems))
|
||||
{
|
||||
foreach (var overlappingItem in overlappingItems)
|
||||
{
|
||||
overlappingItem.Draw(spriteBatch, editing: false, overrideColor: Color.Red * 0.7f, overrideDepth: 0.0f);
|
||||
}
|
||||
indicatorColor = Color.Red;
|
||||
}
|
||||
|
||||
Vector2 attachPos = GetAttachPosition(picker);
|
||||
|
||||
Vector2 gridPos = picker.Position;
|
||||
if (AttachesToFloor)
|
||||
{
|
||||
gridPos.Y = attachPos.Y - item.Rect.Height / 2;
|
||||
}
|
||||
Vector2 roundedGridPos = new Vector2(
|
||||
MathUtils.RoundTowardsClosest(gridPos.X, Submarine.GridSize.X),
|
||||
MathUtils.RoundTowardsClosest(gridPos.Y, Submarine.GridSize.Y));
|
||||
|
||||
if (item.Submarine == null)
|
||||
{
|
||||
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
|
||||
@@ -35,20 +51,20 @@ namespace Barotrauma.Items.Components
|
||||
if (attachTarget.Submarine != null)
|
||||
{
|
||||
//set to submarine-relative position
|
||||
gridPos += attachTarget.Submarine.Position;
|
||||
roundedGridPos += attachTarget.Submarine.Position;
|
||||
attachPos += attachTarget.Submarine.Position;
|
||||
gridPos += attachTarget.Submarine.DrawPosition;
|
||||
roundedGridPos += attachTarget.Submarine.DrawPosition;
|
||||
attachPos += attachTarget.Submarine.DrawPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gridPos += item.Submarine.Position;
|
||||
roundedGridPos += item.Submarine.Position;
|
||||
attachPos += item.Submarine.Position;
|
||||
gridPos += item.Submarine.DrawPosition;
|
||||
roundedGridPos += item.Submarine.DrawPosition;
|
||||
attachPos += item.Submarine.DrawPosition;
|
||||
}
|
||||
|
||||
Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.4f);
|
||||
Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.4f, color: indicatorColor);
|
||||
|
||||
Sprite sprite = item.Sprite;
|
||||
foreach (ContainedItemSprite containedSprite in item.Prefab.ContainedSprites)
|
||||
@@ -63,7 +79,7 @@ namespace Barotrauma.Items.Components
|
||||
sprite.Draw(
|
||||
spriteBatch,
|
||||
new Vector2(attachPos.X, -attachPos.Y),
|
||||
item.SpriteColor * 0.5f,
|
||||
item.SpriteColor.Multiply(indicatorColor) * 0.5f,
|
||||
item.RotationRad,
|
||||
item.Scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
@@ -75,7 +91,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
if (!attachable || body == null) { return; }
|
||||
if (!attachable || originalBody == null) { return; }
|
||||
|
||||
var eventData = ExtractEventData<AttachEventData>(extraData);
|
||||
|
||||
@@ -115,9 +131,9 @@ namespace Barotrauma.Items.Components
|
||||
if (attached)
|
||||
{
|
||||
DropConnectedWires(null);
|
||||
if (body != null)
|
||||
if (originalBody != null)
|
||||
{
|
||||
item.body = body;
|
||||
item.body = originalBody;
|
||||
item.body.Enabled = true;
|
||||
}
|
||||
IsActive = false;
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
|
||||
particleEmitterCharges.Add(new ParticleEmitter(subElement));
|
||||
break;
|
||||
case "chargesound":
|
||||
chargeSound = RoundSound.Load(subElement, false);
|
||||
chargeSound = RoundSound.Load(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public ItemComponent GetReplacementOrThis()
|
||||
{
|
||||
return ReplacedBy?.GetReplacementOrThis() ?? this;
|
||||
if (ReplacedBy != null && ReplacedBy != this)
|
||||
{
|
||||
return ReplacedBy.GetReplacementOrThis();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool NeedsSoundUpdate()
|
||||
@@ -511,7 +515,7 @@ namespace Barotrauma.Items.Components
|
||||
if (HUDOverlay is SpriteSheet spriteSheet)
|
||||
{
|
||||
spriteSheet.Draw(spriteBatch,
|
||||
spriteIndex: (int)(Math.Floor(Timing.TotalTimeUnpaused * HUDOverlayAnimSpeed) % spriteSheet.FrameCount),
|
||||
spriteIndex: spriteSheet.GetAnimatedSpriteIndex(HUDOverlayAnimSpeed),
|
||||
pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / spriteSheet.FrameSize.ToVector2());
|
||||
}
|
||||
else
|
||||
|
||||
@@ -553,12 +553,12 @@ namespace Barotrauma.Items.Components
|
||||
bool flipX = rootBody is { Dir: -1 } || flippedX;
|
||||
if (flipX)
|
||||
{
|
||||
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
|
||||
spriteEffects |= SpriteEffects.FlipHorizontally;
|
||||
}
|
||||
bool flipY = flippedY;
|
||||
if (flipY)
|
||||
{
|
||||
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
|
||||
spriteEffects |= SpriteEffects.FlipVertically;
|
||||
}
|
||||
|
||||
contained.Item.Sprite.Draw(
|
||||
|
||||
@@ -434,12 +434,19 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (FabricationRecipe fi in fabricationRecipes.Values)
|
||||
{
|
||||
RichString recipeTooltip = RichString.Rich(fi.TargetItem.Description);
|
||||
if (fi.RequiresRecipe)
|
||||
{
|
||||
recipeTooltip += "\n\n" + $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{TextManager.Get("fabricatorrequiresrecipe")}‖color:end‖";
|
||||
}
|
||||
recipeTooltip = RichString.Rich(recipeTooltip);
|
||||
|
||||
var frame = new GUIFrame(new RectTransform(new Point(itemList.Content.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null)
|
||||
{
|
||||
UserData = fi,
|
||||
HoverColor = Color.Gold * 0.2f,
|
||||
SelectedColor = Color.Gold * 0.5f,
|
||||
ToolTip = RichString.Rich(fi.TargetItem.Description)
|
||||
ToolTip = recipeTooltip
|
||||
};
|
||||
|
||||
var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform),
|
||||
@@ -451,8 +458,8 @@ namespace Barotrauma.Items.Components
|
||||
new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform),
|
||||
itemIcon, scaleToFit: true)
|
||||
{
|
||||
Color = fi.TargetItem.InventoryIconColor,
|
||||
ToolTip = RichString.Rich(fi.TargetItem.Description)
|
||||
Color = itemIcon == fi.TargetItem.Sprite ? fi.TargetItem.SpriteColor : fi.TargetItem.InventoryIconColor,
|
||||
ToolTip = recipeTooltip
|
||||
};
|
||||
}
|
||||
|
||||
@@ -461,7 +468,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
AutoScaleVertical = true,
|
||||
ToolTip = RichString.Rich(fi.TargetItem.Description)
|
||||
ToolTip = recipeTooltip
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight),
|
||||
@@ -513,7 +520,7 @@ namespace Barotrauma.Items.Components
|
||||
var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
|
||||
nonItems.ForEach(i => i.Visible = false);
|
||||
|
||||
SortItems(character: null);
|
||||
SortItems(character);
|
||||
FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
|
||||
HideEmptyItemListCategories();
|
||||
}
|
||||
@@ -1196,6 +1203,15 @@ namespace Barotrauma.Items.Components
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), TextManager.FormatCurrency(SelectedItem.RequiredMoney),
|
||||
font: GUIStyle.SmallFont);
|
||||
}
|
||||
|
||||
if (selectedRecipe.RequiresRecipe && !AnyOneHasRecipeForItem(Character.Controlled, selectedRecipe.TargetItem))
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
|
||||
TextManager.Get("fabricatorrequiresrecipe"), textColor: GUIStyle.Red, font: GUIStyle.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void HighlightRecipe(string identifier, Color color)
|
||||
|
||||
@@ -92,10 +92,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "temperatureboostsoundup":
|
||||
temperatureBoostSoundUp = RoundSound.Load(subElement, false);
|
||||
temperatureBoostSoundUp = RoundSound.Load(subElement);
|
||||
break;
|
||||
case "temperatureboostsounddown":
|
||||
temperatureBoostSoundDown = RoundSound.Load(subElement, false);
|
||||
temperatureBoostSoundDown = RoundSound.Load(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,6 +1060,7 @@ namespace Barotrauma.Items.Components
|
||||
int missionIndex = 0;
|
||||
foreach (Mission mission in GameMain.GameSession.Missions)
|
||||
{
|
||||
if (!mission.Prefab.ShowSonarLabels) { continue; }
|
||||
int i = 0;
|
||||
foreach ((LocalizedString label, Vector2 position) in mission.SonarLabels)
|
||||
{
|
||||
@@ -1714,15 +1715,15 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
if (structure.Submarine != sub) { continue; }
|
||||
CreateBlips(structure.IsHorizontal, structure.WorldPosition, structure.WorldRect);
|
||||
CreateBlips(structure.IsHorizontal, structure.WorldPosition, structure.WorldRect, -structure.RotationWithFlipping);
|
||||
}
|
||||
foreach (var door in Door.DoorList)
|
||||
{
|
||||
if (door.Item.Submarine != sub || door.IsOpen) { continue; }
|
||||
CreateBlips(door.IsHorizontal, door.Item.WorldPosition, door.Item.WorldRect, BlipType.Door);
|
||||
CreateBlips(door.IsHorizontal, door.Item.WorldPosition, door.Item.WorldRect, rotation: 0.0f, BlipType.Door);
|
||||
}
|
||||
|
||||
void CreateBlips(bool isHorizontal, Vector2 worldPos, Rectangle worldRect, BlipType blipType = BlipType.Default)
|
||||
void CreateBlips(bool isHorizontal, Vector2 worldPos, Rectangle worldRect, float rotation, BlipType blipType = BlipType.Default)
|
||||
{
|
||||
Vector2 point1, point2;
|
||||
if (isHorizontal)
|
||||
@@ -1735,6 +1736,14 @@ namespace Barotrauma.Items.Components
|
||||
point1 = new Vector2(worldPos.X, worldRect.Y);
|
||||
point2 = new Vector2(worldPos.X, worldRect.Y - worldRect.Height);
|
||||
}
|
||||
|
||||
if (!MathUtils.NearlyEqual(rotation, 0.0f))
|
||||
{
|
||||
float rotationRad = MathHelper.ToRadians(rotation);
|
||||
point1 = MathUtils.RotatePointAroundTarget(point1, worldPos, rotationRad);
|
||||
point2 = MathUtils.RotatePointAroundTarget(point2, worldPos, rotationRad);
|
||||
}
|
||||
|
||||
CreateBlipsForLine(
|
||||
point1,
|
||||
point2,
|
||||
|
||||
@@ -973,6 +973,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
PosToMaintain += nudgeAmount;
|
||||
}
|
||||
unsentChanges = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
internal partial class PowerDistributor : PowerTransfer, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private partial class PowerGroup
|
||||
{
|
||||
private GUIFrame? frame;
|
||||
private GUITextBox? nameBox;
|
||||
private GUIScrollBar? ratioSlider;
|
||||
private readonly List<GUITextBlock> powerUnitLabels = new List<GUITextBlock>();
|
||||
private GUIFrame? divider;
|
||||
|
||||
public bool IsVisible { get; private set; } = true;
|
||||
|
||||
public void CreateGUI()
|
||||
{
|
||||
frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), distributor.groupList!.Content.RectTransform, minSize: (0, 130)), style: null);
|
||||
GUIFrame groupContent = new(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
|
||||
|
||||
GUILayoutGroup nameGroup = new(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
GUIButton penIcon = new(new RectTransform(new Vector2(0.75f), nameGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "TextBoxIcon")
|
||||
{
|
||||
HoverCursor = CursorState.IBeam,
|
||||
OnClicked = (_, _) =>
|
||||
{
|
||||
nameBox!.Select();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Name, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
|
||||
{
|
||||
MaxTextLength = MaxNameLength,
|
||||
OverflowClip = true,
|
||||
TextBlock = { ForceUpperCase = ForceUpperCase.No },
|
||||
OnEnterPressed = static (textBox, _) =>
|
||||
{
|
||||
textBox.Deselect();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
nameBox.OnDeselected += (tb, _) =>
|
||||
{
|
||||
Name = tb.Text;
|
||||
if (GameMain.Client == null) { return; }
|
||||
distributor.item.CreateClientEvent(distributor, new EventData(this, EventType.NameChange));
|
||||
};
|
||||
|
||||
GUITextBlock loadDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.TopRight) { AbsoluteOffset = (5, 0) },
|
||||
out GUITextBlock? _, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
|
||||
loadDisplay.TextGetter = () => MathUtils.RoundToInt(Load).ToString();
|
||||
|
||||
ratioSlider = new GUIScrollBar(new RectTransform(new Vector2(1f, 0.33f), groupContent.RectTransform, Anchor.Center), barSize: 0.15f, style: "DeviceSlider")
|
||||
{
|
||||
Step = SupplyRatioStep,
|
||||
BarScroll = SupplyRatio,
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
if (MathUtils.NearlyEqual(barScroll, SupplyRatio)) { return false; }
|
||||
SupplyRatio = barScroll;
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
distributor.item.CreateClientEvent(distributor, new EventData(this, EventType.RatioChange));
|
||||
distributor.correctionTimer = CorrectionDelay;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
ratioSlider.Bar.RectTransform.MaxSize = new Point(ratioSlider.Bar.Rect.Height);
|
||||
|
||||
GUITextBlock ratioDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.2f, 0.33f), groupContent.RectTransform, Anchor.BottomLeft),
|
||||
out GUITextBlock? _, out GUITextBlock _,
|
||||
rightLabelText: "%");
|
||||
ratioDisplay.TextGetter = () => DisplayRatio.ToString();
|
||||
|
||||
GUITextBlock outputDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.BottomRight) { AbsoluteOffset = (5, 0) },
|
||||
out GUITextBlock? _, out GUITextBlock outputDisplayUnitLabel,
|
||||
TextManager.Get("powerdistributor.supplylabel"), tooltip: TextManager.Get("PowerTransferTipPower"), leftLabelFont: GUIStyle.Font);
|
||||
outputDisplay.TextGetter = () => distributor.IsShortCircuited(PowerOut) ? "err" : MathUtils.RoundToInt(distributor.CalculatePowerOut(this)).ToString();
|
||||
|
||||
powerUnitLabels.Add(loadDisplayUnitLabel);
|
||||
powerUnitLabels.Add(outputDisplayUnitLabel);
|
||||
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
|
||||
|
||||
divider = new GUIFrame(new RectTransform(Vector2.UnitX, distributor.groupList!.Content.RectTransform), style: "HorizontalLine");
|
||||
}
|
||||
|
||||
private void UpdateNameBox()
|
||||
{
|
||||
if (nameBox == null || nameBox.Text == DisplayName) { return; }
|
||||
nameBox.Text = DisplayName?.Value ?? string.Empty;
|
||||
}
|
||||
|
||||
private void UpdateSlider()
|
||||
{
|
||||
if (ratioSlider == null || MathUtils.NearlyEqual(ratioSlider.BarScroll, supplyRatio)) { return; }
|
||||
ratioSlider.BarScroll = supplyRatio;
|
||||
}
|
||||
|
||||
public void UpdateGUI()
|
||||
{
|
||||
IsVisible = PowerOut.Wires.Count >= 1;
|
||||
frame!.Visible = IsVisible;
|
||||
divider!.Visible = IsVisible && distributor.powerGroups.Last(group => group.frame!.Visible) != this;
|
||||
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language) { GUITextBlock.AutoScaleAndNormalize(powerUnitLabels); }
|
||||
}
|
||||
}
|
||||
|
||||
private GUIListBox? groupList;
|
||||
|
||||
private GUITextBlock? noConnectionsText;
|
||||
|
||||
protected override void CreateGUI()
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
guiContent = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset })
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
GUIFrame defaultUIContainer = new(new RectTransform(Vector2.UnitX, guiContent.RectTransform, minSize: (0, 125)), style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
CreateDefaultPowerUI(defaultUIContainer);
|
||||
|
||||
groupList = new(new RectTransform(Vector2.One, guiContent.RectTransform)) { Enabled = false };
|
||||
noConnectionsText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), groupList.Content.RectTransform, Anchor.Center), TextManager.Get("powerdistributor.noconnections"), wrap: true)
|
||||
{
|
||||
Visible = false
|
||||
};
|
||||
powerGroups.ForEach(group => group.CreateGUI());
|
||||
}
|
||||
|
||||
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
powerGroups.ForEach(group => group.UpdateGUI());
|
||||
noConnectionsText!.Visible = powerGroups.None(group => group.IsVisible);
|
||||
base.UpdateHUDComponentSpecific(character, deltaTime, cam);
|
||||
}
|
||||
|
||||
#region Networking
|
||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData? extraData = null) => SharedEventWrite(msg, extraData);
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
int msgStartPos = msg.BitPosition;
|
||||
SharedEventRead(msg, out EventType eventType, out PowerGroup powerGroup, out string newName, out float newRatio);
|
||||
|
||||
if (correctionTimer > 0f)
|
||||
{
|
||||
int msgBits = msg.BitPosition - msgStartPos;
|
||||
msg.BitPosition -= msgBits;
|
||||
StartDelayedCorrection(msg.ExtractBits(msgBits), sendingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.NameChange:
|
||||
powerGroup.Name = newName;
|
||||
break;
|
||||
case EventType.RatioChange:
|
||||
powerGroup.SupplyRatio = newRatio;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -6,125 +6,84 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class PowerTransfer : Powered
|
||||
{
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
protected GUIComponent guiContent;
|
||||
|
||||
private GUITickBox powerIndicator;
|
||||
private GUITickBox highVoltageIndicator;
|
||||
private GUITickBox lowVoltageIndicator;
|
||||
|
||||
private GUITextBlock powerLabel, loadLabel;
|
||||
protected GUITextBlock powerDisplay, loadDisplay;
|
||||
|
||||
private LanguageIdentifier prevLanguage;
|
||||
protected LanguageIdentifier prevLanguage;
|
||||
|
||||
partial void InitProjectSpecific(XElement element)
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
CreateGUI();
|
||||
prevLanguage = GameSettings.CurrentConfig.Language;
|
||||
}
|
||||
|
||||
var paddedFrame = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
style: null)
|
||||
protected override void CreateGUI()
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
guiContent = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
CreateDefaultPowerUI(guiContent);
|
||||
}
|
||||
|
||||
var lightsArea = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1), paddedFrame.RectTransform, Anchor.CenterLeft))
|
||||
protected void CreateDefaultPowerUI(GUIComponent parent)
|
||||
{
|
||||
GUILayoutGroup lightsArea = new(new RectTransform(new Vector2(0.4f, 1f), parent.RectTransform, Anchor.CenterLeft))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
powerIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
TextManager.Get("PowerTransferPowered"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightGreen")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
highVoltageIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
TextManager.Get("PowerTransferHighVoltage"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipOvervoltage"),
|
||||
Enabled = false
|
||||
};
|
||||
lowVoltageIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
TextManager.Get("PowerTransferLowVoltage"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipLowvoltage"),
|
||||
Enabled = false
|
||||
};
|
||||
powerIndicator.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal);
|
||||
highVoltageIndicator.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal);
|
||||
lowVoltageIndicator.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal);
|
||||
powerIndicator = GUI.CreateIndicatorLight(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
"IndicatorLightGreen", TextManager.Get("PowerTransferPowered"));
|
||||
highVoltageIndicator = GUI.CreateIndicatorLight(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
"IndicatorLightRed", TextManager.Get("PowerTransferHighVoltage"), TextManager.Get("PowerTransferTipOvervoltage"));
|
||||
lowVoltageIndicator = GUI.CreateIndicatorLight(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
"IndicatorLightRed", TextManager.Get("PowerTransferLowVoltage"), TextManager.Get("PowerTransferTipLowvoltage"));
|
||||
GUITextBlock.AutoScaleAndNormalize(powerIndicator.TextBlock, highVoltageIndicator.TextBlock, lowVoltageIndicator.TextBlock);
|
||||
|
||||
var textContainer = new GUIFrame(new RectTransform(new Vector2(0.58f, 1.0f), paddedFrame.RectTransform, Anchor.CenterRight), style: null);
|
||||
var upperTextArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), textContainer.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
var lowerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), textContainer.RectTransform, Anchor.BottomLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
GUIFrame textContainer = new(new RectTransform(new Vector2(0.58f, 1f), parent.RectTransform, Anchor.CenterRight), style: null);
|
||||
|
||||
powerLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), upperTextArea.RectTransform),
|
||||
TextManager.Get("PowerTransferPowerLabel"), textColor: GUIStyle.TextColorBright, font: GUIStyle.LargeFont, textAlignment: Alignment.CenterRight)
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipPower")
|
||||
};
|
||||
loadLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), lowerTextArea.RectTransform),
|
||||
TextManager.Get("PowerTransferLoadLabel"), textColor: GUIStyle.TextColorBright, font: GUIStyle.LargeFont, textAlignment: Alignment.CenterRight)
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipLoad")
|
||||
};
|
||||
powerDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(1f, 0.5f), textContainer.RectTransform, Anchor.TopLeft),
|
||||
out powerLabel, out GUITextBlock unitLabel1, TextManager.Get("PowerTransferPowerLabel"), TextManager.Get("kilowatt"), TextManager.Get("PowerTransferTipPower"));
|
||||
|
||||
var digitalBackground = new GUIFrame(new RectTransform(new Vector2(0.55f, 0.8f), upperTextArea.RectTransform), style: "DigitalFrameDark");
|
||||
var powerText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.95f), digitalBackground.RectTransform, Anchor.Center),
|
||||
"", font: GUIStyle.DigitalFont, textColor: GUIStyle.TextColorDark)
|
||||
powerDisplay.TextGetter = () =>
|
||||
{
|
||||
TextAlignment = Alignment.CenterRight,
|
||||
ToolTip = TextManager.Get("PowerTransferTipPower"),
|
||||
TextGetter = () => {
|
||||
float currPower = powerLoad < 0 ? -powerLoad: 0;
|
||||
if (this is not RelayComponent && PowerConnections != null && PowerConnections.Count > 0 && PowerConnections[0].Grid != null)
|
||||
{
|
||||
currPower = PowerConnections[0].Grid.Power;
|
||||
}
|
||||
return ((int)Math.Round(currPower)).ToString();
|
||||
}
|
||||
};
|
||||
var kw1 = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.5f), upperTextArea.RectTransform),
|
||||
TextManager.Get("kilowatt"), textColor: GUIStyle.TextColorNormal, font: GUIStyle.Font)
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
TextAlignment = Alignment.BottomCenter
|
||||
};
|
||||
|
||||
digitalBackground = new GUIFrame(new RectTransform(new Vector2(0.55f, 0.8f), lowerTextArea.RectTransform), style: "DigitalFrameDark");
|
||||
var loadText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.95f), digitalBackground.RectTransform, Anchor.Center),
|
||||
"", font: GUIStyle.DigitalFont, textColor: GUIStyle.TextColorDark)
|
||||
{
|
||||
TextAlignment = Alignment.CenterRight,
|
||||
ToolTip = TextManager.Get("PowerTransferTipLoad"),
|
||||
TextGetter = () =>
|
||||
float currPower = powerLoad < 0 ? -powerLoad : 0;
|
||||
if (this is not RelayComponent && PowerConnections != null && PowerConnections.Count > 0 && PowerConnections[0].Grid != null)
|
||||
{
|
||||
float load = PowerLoad;
|
||||
if (this is RelayComponent relay)
|
||||
{
|
||||
load = relay.DisplayLoad;
|
||||
}
|
||||
else if (load < 0)
|
||||
{
|
||||
load = 0;
|
||||
}
|
||||
return ((int)Math.Round(load)).ToString();
|
||||
currPower = PowerConnections[0].Grid.Power;
|
||||
}
|
||||
return MathUtils.RoundToInt(currPower).ToString();
|
||||
};
|
||||
var kw2 = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.5f), lowerTextArea.RectTransform),
|
||||
TextManager.Get("kilowatt"), textColor: GUIStyle.TextColorNormal, font: GUIStyle.Font)
|
||||
|
||||
loadDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(1f, 0.5f), textContainer.RectTransform, Anchor.BottomLeft),
|
||||
out loadLabel, out GUITextBlock unitLabel2, TextManager.Get("PowerTransferLoadLabel"), TextManager.Get("kilowatt"), TextManager.Get("PowerTransferTipLoad"));
|
||||
|
||||
loadDisplay.TextGetter = () =>
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
TextAlignment = Alignment.BottomCenter
|
||||
float load = PowerLoad;
|
||||
if (this is RelayComponent relay)
|
||||
{
|
||||
load = relay.DisplayLoad;
|
||||
}
|
||||
else if (load < 0)
|
||||
{
|
||||
load = 0;
|
||||
}
|
||||
return MathUtils.RoundToInt(load).ToString();
|
||||
};
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(powerLabel, loadLabel);
|
||||
GUITextBlock.AutoScaleAndNormalize(true, true, powerText, loadText);
|
||||
GUITextBlock.AutoScaleAndNormalize(kw1, kw2);
|
||||
|
||||
prevLanguage = GameSettings.CurrentConfig.Language;
|
||||
GUITextBlock.AutoScaleAndNormalize(true, true, powerDisplay, loadDisplay);
|
||||
GUITextBlock.AutoScaleAndNormalize(unitLabel1, unitLabel2);
|
||||
}
|
||||
|
||||
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "poweronsound":
|
||||
powerOnSound = RoundSound.Load(subElement, false);
|
||||
powerOnSound = RoundSound.Load(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@ using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -28,7 +24,7 @@ namespace Barotrauma.Items.Components
|
||||
private readonly List<ParticleEmitter> particleEmitterHitCharacter = new List<ParticleEmitter>();
|
||||
private readonly List<(RelatedItem relatedItem, ParticleEmitter emitter)> particleEmitterHitItem = new List<(RelatedItem relatedItem, ParticleEmitter emitter)>();
|
||||
|
||||
private float prevProgressBarState;
|
||||
private float prevProgressBarState = 1;
|
||||
private Item prevProgressBarTarget = null;
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
@@ -150,16 +150,16 @@ namespace Barotrauma.Items.Components
|
||||
crosshairPointerSprite = new Sprite(subElement, path: textureDir);
|
||||
break;
|
||||
case "startmovesound":
|
||||
startMoveSound = RoundSound.Load(subElement, false);
|
||||
startMoveSound = RoundSound.Load(subElement);
|
||||
break;
|
||||
case "endmovesound":
|
||||
endMoveSound = RoundSound.Load(subElement, false);
|
||||
endMoveSound = RoundSound.Load(subElement);
|
||||
break;
|
||||
case "movesound":
|
||||
moveSound = RoundSound.Load(subElement, false);
|
||||
moveSound = RoundSound.Load(subElement);
|
||||
break;
|
||||
case "chargesound":
|
||||
chargeSound = RoundSound.Load(subElement, false);
|
||||
chargeSound = RoundSound.Load(subElement);
|
||||
break;
|
||||
case "particleemitter":
|
||||
particleEmitters.Add(new ParticleEmitter(subElement));
|
||||
|
||||
@@ -357,6 +357,18 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
toolTip += $" ({item.Prefab.Identifier})";
|
||||
#endif
|
||||
if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession)
|
||||
{
|
||||
if (GameSession.UnlockedRecipes.Contains(item.Prefab.UnlockedRecipeInToolTip))
|
||||
{
|
||||
toolTip += TextManager.Get("unlockedrecipe.true");
|
||||
}
|
||||
else
|
||||
{
|
||||
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerInput.KeyDown(InputType.ContextualCommand))
|
||||
{
|
||||
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
|
||||
@@ -365,7 +377,8 @@ namespace Barotrauma
|
||||
{
|
||||
var colorStr = XMLExtensions.ToStringHex(Color.LightGray * 0.7f);
|
||||
toolTip += $"\n‖color:{colorStr}‖{TextManager.Get("itemmsg.morreoptionsavailable")}‖color:end‖";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return RichString.Rich(toolTip);
|
||||
}
|
||||
@@ -425,9 +438,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Inventory GetReplacementOrThiS()
|
||||
public Inventory GetReplacementOrThis()
|
||||
{
|
||||
return ReplacedBy?.GetReplacementOrThiS() ?? this;
|
||||
return ReplacedBy?.GetReplacementOrThis() ?? this;
|
||||
}
|
||||
|
||||
public virtual void CreateSlots()
|
||||
@@ -1255,7 +1268,7 @@ namespace Barotrauma
|
||||
container.AllowDragAndDrop &&
|
||||
inventory.CanBePut(DraggingItems.FirstOrDefault());
|
||||
|
||||
bool isTargetingValidCharacter = IsValidTargetForDragDropGive(Character.Controlled, Character.Controlled.FocusedCharacter);
|
||||
bool isTargetingValidCharacter = IsValidTargetForDragDropGive(Character.Controlled, Character.Controlled.FocusedCharacter, DraggingItems);
|
||||
|
||||
if (DraggingItemToWorld && (isTargetingValidContainer || isTargetingValidCharacter))
|
||||
{
|
||||
@@ -1418,11 +1431,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidTargetForDragDropGive(Character giver, Character receiver)
|
||||
private static bool IsValidTargetForDragDropGive(Character giver, Character receiver, IEnumerable<Item> draggedItems)
|
||||
{
|
||||
if (giver == null || receiver == null) { return false; }
|
||||
if (giver == null || receiver == null || draggedItems.None()) { return false; }
|
||||
if (receiver == giver) { return false; }
|
||||
return receiver.IsInventoryAccessibleTo(giver, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited);
|
||||
return
|
||||
receiver.IsInventoryAccessibleTo(giver, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited) &&
|
||||
receiver.Inventory.CanBePut(draggedItems.FirstOrDefault(), InvSlotType.Any);
|
||||
}
|
||||
|
||||
private static bool CanSelectSlot(SlotReference selectedSlot)
|
||||
@@ -1651,7 +1666,7 @@ namespace Barotrauma
|
||||
|
||||
(LocalizedString, Color) GetDragLabelTextAndColor(bool mouseOnHealthInterface)
|
||||
{
|
||||
bool useDragDropGive = IsValidTargetForDragDropGive(Character.Controlled, Character.Controlled.FocusedCharacter);
|
||||
bool useDragDropGive = IsValidTargetForDragDropGive(Character.Controlled, Character.Controlled.FocusedCharacter, DraggingItems);
|
||||
|
||||
Color toolTipColor = Color.LightGreen;
|
||||
|
||||
|
||||
@@ -316,9 +316,6 @@ namespace Barotrauma
|
||||
if (worldPosition.X + extents.X > worldView.Right || worldPosition.X + extents.Width < worldView.X) { return false; }
|
||||
if (worldPosition.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) { return false; }
|
||||
|
||||
if (extents.Width * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
|
||||
if (extents.Height * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -327,7 +324,7 @@ namespace Barotrauma
|
||||
Draw(spriteBatch, editing, back, overrideColor: null);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Color? overrideColor = null)
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Color? overrideColor = null, float? overrideDepth = null)
|
||||
{
|
||||
if (!Visible || (!editing && IsHidden) || !SubEditorScreen.IsLayerVisible(this)) { return; }
|
||||
|
||||
@@ -395,7 +392,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
float depth = GetDrawDepth();
|
||||
float depth = overrideDepth ?? GetDrawDepth();
|
||||
if (isWiringMode && isLogic && !PlayerInput.IsShiftDown()) { depth = 0.01f; }
|
||||
if (activeSprite != null)
|
||||
{
|
||||
@@ -427,7 +424,7 @@ namespace Barotrauma
|
||||
textureScale: Vector2.One * Scale,
|
||||
depth: d);
|
||||
}
|
||||
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, rotation: 0, depth, overrideColor);
|
||||
DrawDecorativeSprites(spriteBatch, DrawPosition, FlippedX && Prefab.CanSpriteFlipX, FlippedY && Prefab.CanSpriteFlipY, rotation: 0, depth, overrideColor);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -448,7 +445,7 @@ namespace Barotrauma
|
||||
Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.002f);
|
||||
}
|
||||
|
||||
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, -RotationRad, depth, overrideColor);
|
||||
DrawDecorativeSprites(spriteBatch, DrawPosition, FlippedX && Prefab.CanSpriteFlipX, FlippedY && Prefab.CanSpriteFlipY, -RotationRad, depth, overrideColor);
|
||||
}
|
||||
}
|
||||
else if (body.Enabled)
|
||||
@@ -524,8 +521,8 @@ namespace Barotrauma
|
||||
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
|
||||
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
|
||||
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
|
||||
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
|
||||
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
|
||||
if (FlippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
|
||||
if (FlippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
|
||||
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
|
||||
rotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
|
||||
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
|
||||
@@ -543,7 +540,7 @@ namespace Barotrauma
|
||||
//causing them to be removed from the list
|
||||
for (int i = drawableComponents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
drawableComponents[i].Draw(spriteBatch, editing, depth, overrideColor);
|
||||
drawableComponents[i].Draw(spriteBatch, editing && !GameMain.SubEditorScreen.TransformWidgetSelected, depth, overrideColor);
|
||||
}
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
@@ -813,6 +810,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (Screen.Selected != GameMain.SubEditorScreen) { return; }
|
||||
if (Character.Controlled == null) { activeHUDs.Clear(); }
|
||||
if (GameMain.SubEditorScreen.TransformWidgetSelected) { return; }
|
||||
|
||||
if (GetComponent<ElectricalDischarger>() is { } discharger)
|
||||
{
|
||||
@@ -826,8 +825,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.Controlled == null) { activeHUDs.Clear(); }
|
||||
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
ic.UpdateEditing(deltaTime);
|
||||
@@ -2341,6 +2338,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool onInsertedEffectsAppliedOnPreviousRound = msg.ReadBoolean();
|
||||
byte bodyType = msg.ReadByte();
|
||||
bool spawnedInOutpost = msg.ReadBoolean();
|
||||
bool allowStealing = msg.ReadBoolean();
|
||||
@@ -2453,6 +2451,10 @@ namespace Barotrauma
|
||||
AllowStealing = allowStealing,
|
||||
Quality = quality
|
||||
};
|
||||
if (onInsertedEffectsAppliedOnPreviousRound)
|
||||
{
|
||||
item.OnInsertedEffectsApplied = item.OnInsertedEffectsAppliedOnPreviousRound = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Barotrauma
|
||||
SoundTriggers = new LevelTrigger[Prefab.Sounds.Count];
|
||||
for (int i = 0; i < Prefab.Sounds.Count; i++)
|
||||
{
|
||||
Sounds[i] = RoundSound.Load(Prefab.Sounds[i].SoundElement, false);
|
||||
Sounds[i] = RoundSound.Load(Prefab.Sounds[i].SoundElement);
|
||||
SoundTriggers[i] = Prefab.Sounds[i].TriggerIndex > -1 ? Triggers[Prefab.Sounds[i].TriggerIndex] : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -714,21 +714,21 @@ namespace Barotrauma.Lights
|
||||
const float MaxOffset = 256.0f;
|
||||
//the magic numbers here are just based on experimentation
|
||||
float MinHorizontalScale = MathHelper.Lerp(3.5f, 1.5f, ObstructVisionAmount);
|
||||
float MaxHorizontalScale = MinHorizontalScale * 1.25f;
|
||||
float MaxHorizontalScale = 10.0f;
|
||||
float VerticalScale = MathHelper.Lerp(4.0f, 1.25f, ObstructVisionAmount);
|
||||
|
||||
//Starting point and scale-based modifier that moves the point of origin closer to the edge of the texture if the player moves their mouse further away, or vice versa.
|
||||
float relativeOriginStartPosition = 0.1f; //Increasing this value moves the origin further behind the character
|
||||
float originStartPosition = visionCircle.Width * relativeOriginStartPosition * MinHorizontalScale;
|
||||
float relativeOriginLookAtPosModifier = -0.055f; //Increase this value increases how much the vision changes by moving the mouse
|
||||
float originLookAtPosModifier = visionCircle.Width * relativeOriginLookAtPosModifier;
|
||||
|
||||
Vector2 scale = new Vector2(
|
||||
MathHelper.Clamp(losOffset.Length() / MaxOffset, MinHorizontalScale, MaxHorizontalScale), VerticalScale);
|
||||
|
||||
//Increasing this value moves the origin further behind the character (current value chosen by experimentation)
|
||||
float relativeOriginStartPosition = 0.2f;
|
||||
//Divide by scale to move the origin closer to the edge of the texture, meaning the visible area moves forwards.
|
||||
//Just stretching the texture without touching the origin would otherwise mean the blurry edge of visibility moves further behind the character (allowing you to see behind you better when looking far away)
|
||||
float originStartPosition = visionCircle.Width * relativeOriginStartPosition / scale.X;
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameSettings.CurrentConfig.Graphics.LightMapScale, GameSettings.CurrentConfig.Graphics.LightMapScale, 1.0f)));
|
||||
spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
|
||||
new Vector2(originStartPosition + (scale.X * originLookAtPosModifier), visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
|
||||
new Vector2(originStartPosition, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
|
||||
spriteBatch.End();
|
||||
}
|
||||
else
|
||||
@@ -788,8 +788,8 @@ namespace Barotrauma.Lights
|
||||
if (!convexHull.Intersects(camView)) { continue; }
|
||||
|
||||
Vector2 relativeViewPos = pos;
|
||||
if (convexHull.ParentEntity?.Submarine != null)
|
||||
{
|
||||
if (convexHull.ParentEntity?.Submarine != null)
|
||||
{
|
||||
relativeViewPos -= convexHull.ParentEntity.Submarine.DrawPosition;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
private GUIComponent editor;
|
||||
|
||||
private bool editorEnabled;
|
||||
|
||||
private void CreateEditor()
|
||||
{
|
||||
editor = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), GUI.Canvas, Anchor.TopRight, minSize: new Point(400, 0)));
|
||||
@@ -534,8 +536,15 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
if (editor == null) CreateEditor();
|
||||
editor.AddToGUIUpdateList(order: 1);
|
||||
if (editor == null) { CreateEditor(); }
|
||||
if (editorEnabled)
|
||||
{
|
||||
editor.AddToGUIUpdateList(order: 1);
|
||||
}
|
||||
if (PlayerInput.KeyHit(Keys.T))
|
||||
{
|
||||
editorEnabled = !editorEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerInput.KeyHit(Keys.Space))
|
||||
@@ -822,7 +831,7 @@ namespace Barotrauma
|
||||
drawRect.X = (int)pos.X - drawRect.Width / 2;
|
||||
drawRect.Y = (int)pos.Y - drawRect.Width / 2;
|
||||
|
||||
if (drawRect.X > rect.Right - GUI.IntScale(100) && generationParams.MissionIcon != null && location.AvailableMissions.Any())
|
||||
if (drawRect.X > rect.Right - GUI.IntScale(100) && generationParams.MissionIcon != null && location.AvailableAndVisibleMissions.Any(m => m.Prefab.ShowInMenus))
|
||||
{
|
||||
Vector2 offScreenMissionIconPos = new Vector2(rect.Right - GUI.IntScale(50), drawRect.Center.Y);
|
||||
generationParams.MissionIcon.Draw(spriteBatch,
|
||||
@@ -934,18 +943,19 @@ namespace Barotrauma
|
||||
}
|
||||
if (location != CurrentLocation && generationParams.MissionIcon != null)
|
||||
{
|
||||
if ((CurrentLocation == currentDisplayLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location))) ||
|
||||
location.AvailableMissions.Any(m => m.Locations[0] == m.Locations[1]))
|
||||
var currentLocationVisibleMissions = CurrentLocation.AvailableAndVisibleMissions;
|
||||
if ((CurrentLocation == currentDisplayLocation && currentLocationVisibleMissions.Any(m => m.Locations.Contains(location))) ||
|
||||
location.AvailableAndVisibleMissions.Any(m => m.Locations[0] == m.Locations[1]))
|
||||
{
|
||||
Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
|
||||
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
|
||||
if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos))
|
||||
{
|
||||
var availableMissions = CurrentLocation.AvailableMissions
|
||||
var allVisibleMissions = currentLocationVisibleMissions
|
||||
.Where(m => m.Locations.Contains(location))
|
||||
.Concat(location.AvailableMissions.Where(m => m.Locations[0] == m.Locations[1]))
|
||||
.Concat(location.AvailableAndVisibleMissions.Where(m => m.Locations[0] == m.Locations[1]))
|
||||
.Distinct();
|
||||
tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name)));
|
||||
tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', allVisibleMissions.Select(m => "- " + m.Name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,6 +1002,12 @@ namespace Barotrauma
|
||||
spriteBatch.End();
|
||||
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
#if DEBUG
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(mapContainer.Center.X, mapContainer.Rect.Y), "Press T to toggle editing map generation parameters.", Color.Magenta, font: GUIStyle.SmallFont);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void DrawNoise(SpriteBatch spriteBatch, Rectangle rect, float strength)
|
||||
|
||||
@@ -1159,15 +1159,6 @@ namespace Barotrauma
|
||||
|
||||
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
|
||||
|
||||
private float RotationRad
|
||||
=> MathHelper.ToRadians(
|
||||
this switch
|
||||
{
|
||||
Structure s => s.Rotation,
|
||||
Item it => it.Rotation,
|
||||
_ => 0.0f
|
||||
});
|
||||
|
||||
private Vector2 GetEditingHandlePos(int x, int y, Camera cam)
|
||||
{
|
||||
Vector2 handleDiff = new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f));
|
||||
|
||||
@@ -29,6 +29,15 @@ namespace Barotrauma
|
||||
Volume = element.GetAttributeFloat("volume", 1.0f);
|
||||
IgnoreMuffling = element.GetAttributeBool("dontmuffle", false);
|
||||
MuteBackgroundMusic = element.GetAttributeBool("MuteBackgroundMusic", false);
|
||||
|
||||
if (!Stream && Sound.DurationSeconds > 60.0f)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Potential issue in content package: a large audio clip \"{System.IO.Path.GetFileName(Filename)}\" is set to be loaded into memory instead of streaming it from the disk. "+
|
||||
"This can lead to excessive memory usage. Large clips should generally be streamed, while small and frequently played sounds should be loaded to memory to avoid the IO overhead of streaming. "+
|
||||
"Consider adding stream=\"true\" to the sound's XML element.",
|
||||
contentPackage: element.ContentPackage);
|
||||
}
|
||||
|
||||
FrequencyMultiplierRange = new Vector2(1.0f);
|
||||
string freqMultAttr = element.GetAttributeString("frequencymultiplier", element.GetAttributeString("frequency", "1.0"));
|
||||
@@ -61,10 +70,11 @@ 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)
|
||||
public static RoundSound? Load(ContentXElement element)
|
||||
{
|
||||
if (GameMain.SoundManager?.Disabled ?? true) { return null; }
|
||||
|
||||
bool stream = element.GetAttributeBool(nameof(Stream), false);
|
||||
var filename = element.GetAttributeContentPath("file") ?? element.GetAttributeContentPath("sound");
|
||||
if (filename is null)
|
||||
{
|
||||
|
||||
@@ -320,8 +320,8 @@ namespace Barotrauma
|
||||
{
|
||||
RectangleF worldRect = Quad2D.FromSubmarineRectangle(WorldRect).Rotated(
|
||||
FlippedX != FlippedY
|
||||
? rotationRad
|
||||
: -rotationRad).BoundingAxisAlignedRectangle;
|
||||
? RotationRad
|
||||
: -RotationRad).BoundingAxisAlignedRectangle;
|
||||
Vector2 worldPos = WorldPosition;
|
||||
|
||||
Vector2 min = new Vector2(worldRect.X, worldRect.Y);
|
||||
@@ -451,7 +451,7 @@ namespace Barotrauma
|
||||
MathUtils.PositiveModulo(-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width * TextureScale.X * Scale),
|
||||
MathUtils.PositiveModulo(-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height * TextureScale.Y * Scale));
|
||||
|
||||
float rotationRad = GetRotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
|
||||
float rotationRad = GetRotationForSprite(RotationRad, Prefab.BackgroundSprite);
|
||||
|
||||
Prefab.BackgroundSprite.DrawTiled(
|
||||
spriteBatch,
|
||||
@@ -484,7 +484,7 @@ namespace Barotrauma
|
||||
|
||||
if (back == GetRealDepth() > 0.5f)
|
||||
{
|
||||
Vector2 advanceX = MathUtils.RotatedUnitXRadians(this.rotationRad).FlipY();
|
||||
Vector2 advanceX = MathUtils.RotatedUnitXRadians(RotationRad).FlipY();
|
||||
Vector2 advanceY = advanceX.YX().FlipX();
|
||||
if (FlippedX != FlippedY)
|
||||
{
|
||||
@@ -492,7 +492,7 @@ namespace Barotrauma
|
||||
advanceY = advanceY.FlipX();
|
||||
}
|
||||
|
||||
float sectionSpriteRotationRad = GetRotationForSprite(this.rotationRad, Prefab.Sprite);
|
||||
float sectionSpriteRotationRad = GetRotationForSprite(RotationRad, Prefab.Sprite);
|
||||
|
||||
for (int i = 0; i < Sections.Length; i++)
|
||||
{
|
||||
@@ -558,9 +558,11 @@ namespace Barotrauma
|
||||
foreach (var decorativeSprite in Prefab.DecorativeSprites)
|
||||
{
|
||||
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
|
||||
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + this.rotationRad;
|
||||
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + RotationRad;
|
||||
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
|
||||
Vector2 drawPos = DrawPosition + MathUtils.RotatePoint(offset, -this.rotationRad);
|
||||
if (FlippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
|
||||
if (FlippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
|
||||
Vector2 drawPos = DrawPosition + MathUtils.RotatePoint(offset, -this.RotationRad);
|
||||
decorativeSprite.Sprite.Draw(
|
||||
spriteBatch: spriteBatch,
|
||||
pos: drawPos.FlipY(),
|
||||
|
||||
@@ -230,30 +230,28 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f)
|
||||
public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f, Color? color = null)
|
||||
{
|
||||
Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2;
|
||||
Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2;
|
||||
|
||||
for (int i = 0; i < gridCells; i++)
|
||||
{
|
||||
float distFromGridX = (MathUtils.RoundTowardsClosest(gridCenter.X, GridSize.X) - gridCenter.X) / GridSize.X;
|
||||
float distFromGridY = (MathUtils.RoundTowardsClosest(gridCenter.Y, GridSize.Y) - gridCenter.Y) / GridSize.Y;
|
||||
float middleIndex = (gridCells - 1) / 2.0f;
|
||||
float normalizedPos = Math.Abs((i - middleIndex) / middleIndex);
|
||||
float expandX = MathHelper.Lerp(30.0f, 0.0f, normalizedPos);
|
||||
float expandY = expandX;
|
||||
|
||||
float normalizedDistX = Math.Abs(i + distFromGridX - gridCells / 2) / (gridCells / 2);
|
||||
float normalizedDistY = Math.Abs(i - distFromGridY - gridCells / 2) / (gridCells / 2);
|
||||
|
||||
float expandX = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY);
|
||||
float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistX);
|
||||
Color lineColor = color ?? Color.White;
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y),
|
||||
new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y),
|
||||
Color.White * (1.0f - normalizedDistY) * alpha, depth: 0.6f, width: 3);
|
||||
lineColor * (1.0f - normalizedPos) * alpha, depth: 0.6f, width: 3);
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY),
|
||||
new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY),
|
||||
Color.White * (1.0f - normalizedDistX) * alpha, depth: 0.6f, width: 3);
|
||||
lineColor * (1.0f - normalizedPos) * alpha, depth: 0.6f, width: 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,9 @@ namespace Barotrauma.Networking
|
||||
styleSetting = msg.ReadString();
|
||||
txt = TextManager.GetServerMessage(txt).Value;
|
||||
break;
|
||||
case ChatMessageType.BlockedBySpamFilter:
|
||||
GameMain.Client.BlockedBySpamFilterTimer = BlockedBySpamFilterTime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (NetIdUtils.IdMoreRecent(id, LastID))
|
||||
|
||||
@@ -104,6 +104,10 @@ namespace Barotrauma.Networking
|
||||
private UInt16 lastQueueChatMsgID = 0; //last message added to the queue
|
||||
private readonly List<ChatMessage> chatMsgQueue = new List<ChatMessage>();
|
||||
|
||||
public float BlockedBySpamFilterTimer;
|
||||
|
||||
public bool IsBlockedBySpamFilter => BlockedBySpamFilterTimer > 0.0f;
|
||||
|
||||
public UInt16 LastSentEntityEventID;
|
||||
|
||||
#if DEBUG
|
||||
@@ -479,6 +483,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
#endif
|
||||
|
||||
BlockedBySpamFilterTimer -= deltaTime;
|
||||
|
||||
foreach (Client c in ConnectedClients)
|
||||
{
|
||||
if (c.Character != null && c.Character.Removed) { c.Character = null; }
|
||||
@@ -868,6 +874,10 @@ namespace Barotrauma.Networking
|
||||
case ServerPacketHeader.ACHIEVEMENT:
|
||||
ReadAchievement(inc);
|
||||
break;
|
||||
case ServerPacketHeader.UNLOCKRECIPE:
|
||||
Identifier identifier = inc.ReadIdentifier();
|
||||
GameMain.GameSession.UnlockRecipe(identifier, showNotifications: true);
|
||||
break;
|
||||
case ServerPacketHeader.ACHIEVEMENT_STAT:
|
||||
ReadAchievementStat(inc);
|
||||
break;
|
||||
@@ -1069,13 +1079,15 @@ namespace Barotrauma.Networking
|
||||
CloseReconnectBox();
|
||||
|
||||
GUI.ClearCursorWait();
|
||||
|
||||
string disconnectMessage = $"Client received a disconnect message. Reason: {disconnectPacket.DisconnectReason}";
|
||||
SteamTimelineManager.OnClientDisconnect(disconnectMessage);
|
||||
|
||||
if (disconnectPacket.ShouldCreateAnalyticsEvent)
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"GameClient.HandleDisconnectMessage",
|
||||
GameAnalyticsManager.ErrorSeverity.Debug,
|
||||
$"Client received a disconnect message. Reason: {disconnectPacket.DisconnectReason}");
|
||||
GameAnalyticsManager.ErrorSeverity.Debug, disconnectMessage);
|
||||
}
|
||||
|
||||
if (disconnectPacket.DisconnectReason == DisconnectReason.ServerFull)
|
||||
@@ -1235,7 +1247,16 @@ namespace Barotrauma.Networking
|
||||
|
||||
private void OnConnectionInitializationComplete()
|
||||
{
|
||||
UpdatePresence($"-connect \"{ToolBox.EscapeCharacters(ServerName)}\" {string.Join(",", serverEndpoints.Select(e => e.StringRepresentation))}");
|
||||
//don't allow connecting through the friend list if we're connected to localhost (others can't join to "localhost")
|
||||
//we could potentially find the public IP of the server (assuming it's a public server) from the Steam API, but maybe not worth the trouble?
|
||||
bool connectedToLocalHost = serverEndpoints.All(e => e is LidgrenEndpoint lidgrenEndpoint && lidgrenEndpoint.Address.IsLocalHost);
|
||||
string escapedServerName = ServerName.IsNullOrWhiteSpace() ? "Server" : ToolBox.EscapeCharacters(ServerName);
|
||||
string connectCommand =
|
||||
connectedToLocalHost ?
|
||||
string.Empty :
|
||||
$"-connect \"{escapedServerName}\" {string.Join(",", serverEndpoints.Select(e => e.StringRepresentation))}";
|
||||
|
||||
UpdatePresence(connectCommand);
|
||||
|
||||
canStart = true;
|
||||
connected = true;
|
||||
@@ -2150,11 +2171,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (lobbyUpdated)
|
||||
{
|
||||
//we don't want the client to create any network events
|
||||
//when they modify the server lobby to match the server state as a result of this message
|
||||
ServerSettings.SuppressNetworkMessages = true;
|
||||
|
||||
var prevDispatcher = GUI.KeyboardDispatcher.Subscriber;
|
||||
|
||||
UInt16 updateID = inc.ReadUInt16();
|
||||
|
||||
|
||||
UInt16 settingsLen = inc.ReadUInt16();
|
||||
byte[] settingsData = inc.ReadBytes(settingsLen);
|
||||
|
||||
@@ -2299,6 +2323,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
lastSentChatMsgID = inc.ReadUInt16();
|
||||
|
||||
ServerSettings.SuppressNetworkMessages = false;
|
||||
|
||||
break;
|
||||
case ServerNetSegment.ClientList:
|
||||
ReadClientList(inc);
|
||||
@@ -3091,6 +3118,7 @@ namespace Barotrauma.Networking
|
||||
public void RequestSelectSub(SubmarineInfo sub, SelectedSubType type)
|
||||
{
|
||||
if (!HasPermission(ClientPermissions.SelectSub) || sub == null) { return; }
|
||||
if (ServerSettings.SuppressNetworkMessages) { return; }
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||
@@ -3392,6 +3420,10 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
msgBox = GameMain.NetLobbyScreen.ChatInput;
|
||||
}
|
||||
if (msgBox != null)
|
||||
{
|
||||
msgBox.Enabled = !IsBlockedBySpamFilter;
|
||||
}
|
||||
|
||||
UpdateLogButtonVisibility();
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma.Networking
|
||||
private static readonly LocalizedString packetAmountTooltip = TextManager.Get("ServerSettingsMaxPacketAmountTooltip");
|
||||
private static readonly RichString packetAmountTooltipWarning = RichString.Rich($"{packetAmountTooltip}\n\n‖color:gui.red‖{TextManager.Get("PacketLimitWarning")}‖end‖");
|
||||
|
||||
public static bool SuppressNetworkMessages;
|
||||
|
||||
partial class NetPropertyData
|
||||
{
|
||||
public GUIComponent GUIComponent;
|
||||
@@ -94,6 +96,14 @@ namespace Barotrauma.Networking
|
||||
get
|
||||
{
|
||||
if (GUIComponent == null) { return false; }
|
||||
if (GUIComponent is GUIDropDown dropDown &&
|
||||
dropDown.SelectedIndex == -1)
|
||||
{
|
||||
//nothing selected in the dropdown
|
||||
//it's not possible to select nothing via the UI, which means the client cannot have selected anything locally
|
||||
//(so this must mean that either nothing has been selected yet or that there's nothing in the dropdown)
|
||||
return false;
|
||||
}
|
||||
return !PropEquals(TempValue, GUIComponentValue);
|
||||
}
|
||||
}
|
||||
@@ -238,6 +248,7 @@ namespace Barotrauma.Networking
|
||||
int traitorDangerLevel = 0)
|
||||
{
|
||||
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return; }
|
||||
if (SuppressNetworkMessages) { return; }
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
|
||||
@@ -11,6 +11,14 @@ namespace Barotrauma
|
||||
{
|
||||
abstract class CampaignSetupUI
|
||||
{
|
||||
protected enum SaveSortingType
|
||||
{
|
||||
LastPlayedDescending, LastPlayedAscending,
|
||||
NameDescending, NameAscending
|
||||
}
|
||||
|
||||
private const SaveSortingType DefaultSaveSortingType = SaveSortingType.LastPlayedDescending;
|
||||
|
||||
protected readonly GUIComponent newGameContainer, loadGameContainer;
|
||||
|
||||
protected GUIListBox saveList;
|
||||
@@ -111,24 +119,54 @@ namespace Barotrauma
|
||||
return saveFrame;
|
||||
}
|
||||
|
||||
protected void SortSaveList()
|
||||
protected void SortSaveList(SaveSortingType sortingType = DefaultSaveSortingType) => saveList?.Content.RectTransform.SortChildren((rect1, rect2) =>
|
||||
{
|
||||
saveList.Content.RectTransform.SortChildren((c1, c2) =>
|
||||
if (rect1.GUIComponent.UserData is not CampaignMode.SaveInfo file1 || rect2.GUIComponent.UserData is not CampaignMode.SaveInfo file2) { return 0; }
|
||||
if (!file1.SaveTime.TryUnwrap(out SerializableDateTime file1WriteTime) || !file2.SaveTime.TryUnwrap(out SerializableDateTime file2WriteTime)) { return 0; }
|
||||
return sortingType switch
|
||||
{
|
||||
if (c1.GUIComponent.UserData is not CampaignMode.SaveInfo file1
|
||||
|| c2.GUIComponent.UserData is not CampaignMode.SaveInfo file2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
SaveSortingType.LastPlayedDescending => file2WriteTime.CompareTo(file1WriteTime),
|
||||
SaveSortingType.LastPlayedAscending => file1WriteTime.CompareTo(file2WriteTime),
|
||||
SaveSortingType.NameDescending => string.Compare(Path.GetFileNameWithoutExtension(file1.FilePath), Path.GetFileNameWithoutExtension(file2.FilePath), StringComparison.OrdinalIgnoreCase),
|
||||
SaveSortingType.NameAscending => string.Compare(Path.GetFileNameWithoutExtension(file2.FilePath), Path.GetFileNameWithoutExtension(file1.FilePath), StringComparison.OrdinalIgnoreCase),
|
||||
_ => 0
|
||||
};
|
||||
});
|
||||
|
||||
if (!file1.SaveTime.TryUnwrap(out var file1WriteTime)
|
||||
|| !file2.SaveTime.TryUnwrap(out var file2WriteTime))
|
||||
protected void CreateSaveFilteringHeader(GUIComponent parent)
|
||||
{
|
||||
GUILayoutGroup container = new(new RectTransform(Vector2.UnitX, parent.RectTransform), true) { Stretch = true };
|
||||
|
||||
GUI.CreateFilterBox(new RectTransform(new Vector2(0.6f, 1f), container.RectTransform)).OnTextChanged += (_, filterText) =>
|
||||
{
|
||||
filterText = filterText.Trim();
|
||||
foreach (GUIComponent saveElement in saveList.Content.Children)
|
||||
{
|
||||
return 0;
|
||||
if (saveElement.UserData is not CampaignMode.SaveInfo saveInfo) { continue; }
|
||||
saveElement.Visible = filterText.IsNullOrEmpty()
|
||||
|| Path.GetFileNameWithoutExtension(saveInfo.FilePath).Contains(filterText, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return file2WriteTime.CompareTo(file1WriteTime);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
SaveSortingType[] sortingTypes = Enum.GetValues<SaveSortingType>();
|
||||
GUIDropDown dropDown = new(new RectTransform(new Vector2(0.4f, 1f), container.RectTransform), elementCount: sortingTypes.Length)
|
||||
{
|
||||
OnSelected = (_, data) =>
|
||||
{
|
||||
SortSaveList((SaveSortingType)data);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
foreach (SaveSortingType sortingType in sortingTypes)
|
||||
{
|
||||
dropDown.AddItem(TextManager.Get($"SaveSortingType.{sortingType}"), sortingType);
|
||||
}
|
||||
|
||||
dropDown.SelectItem(DefaultSaveSortingType);
|
||||
|
||||
container.RectTransform.MinSize = (0, container.Children.Max(child => child.Rect.Size.Y));
|
||||
}
|
||||
|
||||
public struct CampaignSettingElements
|
||||
|
||||
@@ -217,6 +217,8 @@ namespace Barotrauma
|
||||
RelativeSpacing = 0.03f
|
||||
};
|
||||
|
||||
CreateSaveFilteringHeader(leftColumn);
|
||||
|
||||
saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform))
|
||||
{
|
||||
PlaySoundOnSelect = true,
|
||||
|
||||
@@ -320,14 +320,14 @@ namespace Barotrauma
|
||||
if (string.IsNullOrWhiteSpace(sender.Text))
|
||||
{
|
||||
characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced);
|
||||
sender.Text = characterInfo.Name;
|
||||
sender.UserData = "random";
|
||||
}
|
||||
else
|
||||
{
|
||||
characterInfo.Name = sender.Text;
|
||||
characterInfo.Rename(sender.Text);
|
||||
sender.UserData = "user";
|
||||
}
|
||||
sender.Text = characterInfo.Name;
|
||||
};
|
||||
characterName.OnEnterPressed += (sender, text) =>
|
||||
{
|
||||
@@ -594,6 +594,8 @@ namespace Barotrauma
|
||||
RelativeSpacing = 0.03f
|
||||
};
|
||||
|
||||
CreateSaveFilteringHeader(leftColumn);
|
||||
|
||||
saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform))
|
||||
{
|
||||
PlaySoundOnSelect = true,
|
||||
|
||||
@@ -352,7 +352,7 @@ namespace Barotrauma
|
||||
Location currentDisplayLocation = Campaign.GetCurrentDisplayLocation();
|
||||
if (connection != null && connection.Locations.Contains(currentDisplayLocation))
|
||||
{
|
||||
List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList();
|
||||
List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).Where(m => m.Prefab.ShowInMenus || GameMain.DebugDraw).ToList();
|
||||
|
||||
if (!availableMissions.Any()) { availableMissions.Insert(0, null); }
|
||||
|
||||
@@ -389,19 +389,26 @@ namespace Barotrauma
|
||||
AbsoluteSpacing = GUI.IntScale(5)
|
||||
};
|
||||
|
||||
var missionName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), font: GUIStyle.SubHeadingFont, wrap: true);
|
||||
missionName.RectTransform.MinSize = new Point(0, GUI.IntScale(15));
|
||||
LocalizedString missionName = mission?.Name ?? TextManager.Get("NoMission");
|
||||
if (GameMain.DebugDraw && mission != null)
|
||||
{
|
||||
if (!mission.Prefab.ShowInMenus) { missionName = $"[HIDDEN] {missionName}"; }
|
||||
missionName += $" ({mission.Prefab.Identifier})";
|
||||
}
|
||||
|
||||
var missionNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), missionName, font: GUIStyle.SubHeadingFont, wrap: true);
|
||||
missionNameBlock.RectTransform.MinSize = new Point(0, GUI.IntScale(15));
|
||||
if (mission == null)
|
||||
{
|
||||
missionTextContent.RectTransform.MinSize = missionName.RectTransform.MinSize = new Point(0, GUI.IntScale(35));
|
||||
missionTextContent.RectTransform.MinSize = missionNameBlock.RectTransform.MinSize = new Point(0, GUI.IntScale(35));
|
||||
missionTextContent.ChildAnchor = Anchor.CenterLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUITickBox tickBox = null;
|
||||
if (!isMissionInNextLocation)
|
||||
if (!isMissionInNextLocation && mission.Prefab.ShowInMenus)
|
||||
{
|
||||
tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, label: string.Empty)
|
||||
tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionNameBlock.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionNameBlock.Padding.X, 0) }, label: string.Empty)
|
||||
{
|
||||
UserData = mission,
|
||||
Selected = Campaign.Map.CurrentLocation?.SelectedMissions.Contains(mission) ?? false
|
||||
@@ -443,7 +450,7 @@ namespace Barotrauma
|
||||
GUILayoutGroup difficultyIndicatorGroup = null;
|
||||
if (mission.Difficulty.HasValue)
|
||||
{
|
||||
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.9f), missionName.RectTransform, anchor: Anchor.CenterRight) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
|
||||
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.9f), missionNameBlock.RectTransform, anchor: Anchor.CenterRight) { AbsoluteOffset = new Point((int)missionNameBlock.Padding.Z, 0) },
|
||||
isHorizontal: true, childAnchor: Anchor.CenterRight)
|
||||
{
|
||||
AbsoluteSpacing = 1,
|
||||
@@ -465,11 +472,11 @@ namespace Barotrauma
|
||||
|
||||
float extraPadding = 0;// 0.8f * tickBox.Rect.Width;
|
||||
float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0;
|
||||
missionName.Padding = new Vector4(missionName.Padding.X + (tickBox?.Rect.Width ?? 0) * 1.2f + extraPadding,
|
||||
missionName.Padding.Y,
|
||||
missionName.Padding.Z + extraZPadding + extraPadding,
|
||||
missionName.Padding.W);
|
||||
missionName.CalculateHeightFromText();
|
||||
missionNameBlock.Padding = new Vector4(missionNameBlock.Padding.X + (tickBox?.Rect.Width ?? 0) * 1.2f + extraPadding,
|
||||
missionNameBlock.Padding.Y,
|
||||
missionNameBlock.Padding.Z + extraZPadding + extraPadding,
|
||||
missionNameBlock.Padding.W);
|
||||
missionNameBlock.CalculateHeightFromText();
|
||||
|
||||
//spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(10)) }, style: null);
|
||||
@@ -544,7 +551,7 @@ namespace Barotrauma
|
||||
OnClicked = (GUIButton btn, object obj) =>
|
||||
{
|
||||
if (missionList.Content.FindChild(c => c is GUITickBox tickBox && tickBox.Selected, recursive: true) == null &&
|
||||
missionList.Content.Children.Any(c => c.UserData is Mission mission && mission.Locations.Contains(Campaign?.Map?.CurrentLocation)))
|
||||
missionList.Content.Children.Any(c => c.UserData is Mission { Prefab.ShowInMenus: true } mission && mission.Locations.Contains(Campaign?.Map?.CurrentLocation)))
|
||||
{
|
||||
var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
|
||||
noMissionVerification.Buttons[0].OnClicked = (btn, userdata) =>
|
||||
|
||||
@@ -482,6 +482,11 @@ namespace Barotrauma
|
||||
|
||||
public void TestLevelGenerationForErrors(int amountOfLevelsToGenerate)
|
||||
{
|
||||
if (selectedParams == null)
|
||||
{
|
||||
throw new InvalidOperationException("No level generation parameters selected in the level editor.");
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(GenerateLevels());
|
||||
|
||||
IEnumerable<CoroutineStatus> GenerateLevels()
|
||||
@@ -520,7 +525,7 @@ namespace Barotrauma
|
||||
errorCatcher.Errors.ToList().ForEach(e => DebugConsole.ThrowError(e.Text));
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
yield return CoroutineStatus.Running;
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,6 +740,30 @@ namespace Barotrauma
|
||||
|
||||
List<Identifier> missionTypes = MissionPrefab.GetAllMultiplayerSelectableMissionTypes().ToList();
|
||||
|
||||
GUILayoutGroup buttonGroup = new(new RectTransform(Vector2.UnitX, missionTypeList.Content.RectTransform), true) { Stretch = true };
|
||||
GUIButton selectAllMissionsButton = new(new RectTransform(new Vector2(0.5f, 1f), buttonGroup.RectTransform), TextManager.Get("selectall"))
|
||||
{
|
||||
OnClicked = (_, _) =>
|
||||
{
|
||||
IEnumerable<Identifier> validMissions = GetValidMissions();
|
||||
validMissions.ForEach(missionType => GameMain.Client.ServerSettings?.ClientAdminWrite(ServerSettings.NetFlags.Misc, addedMissionType: missionType));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
GUIButton deselectAllMissionsButton = new(new RectTransform(new Vector2(0.5f, 1f), buttonGroup.RectTransform), TextManager.Get("deselectall"))
|
||||
{
|
||||
OnClicked = (_, _) =>
|
||||
{
|
||||
IEnumerable<Identifier> validMissions = GetValidMissions();
|
||||
|
||||
// The server must have at least one mission selected, so ensure the first in the list is enabled.
|
||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, addedMissionType: validMissions.First());
|
||||
validMissions.Skip(1).ForEach(missionType => GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, removedMissionType: missionType));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
buttonGroup.RectTransform.MinSize = (0, buttonGroup.Children.Max(child => child.Rect.Height));
|
||||
|
||||
missionTypeTickBoxes = new GUITickBox[missionTypes.Count];
|
||||
int index = 0;
|
||||
foreach (var missionType in missionTypes.OrderBy(t => TextManager.Get("MissionType." + t.Value).Value))
|
||||
@@ -763,6 +787,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Identifier firstValidMission = GetValidMissions().First();
|
||||
if (missionTypeTickBoxes.None(tickBox => tickBox.Selected && tickBox.Parent.Visible))
|
||||
{
|
||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, addedMissionType: firstValidMission);
|
||||
if ((Identifier)tickbox.UserData == firstValidMission) { return true; }
|
||||
}
|
||||
|
||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, removedMissionType: (Identifier)tickbox.UserData);
|
||||
}
|
||||
return true;
|
||||
@@ -771,9 +802,16 @@ namespace Barotrauma
|
||||
frame.RectTransform.MinSize = missionTypeTickBoxes[index].RectTransform.MinSize;
|
||||
index++;
|
||||
}
|
||||
|
||||
clientDisabledElements.Add(selectAllMissionsButton);
|
||||
clientDisabledElements.Add(deselectAllMissionsButton);
|
||||
clientDisabledElements.AddRange(missionTypeTickBoxes);
|
||||
|
||||
return gameModeSpecificFrame;
|
||||
|
||||
IEnumerable<Identifier> GetValidMissions() => missionTypeTickBoxes
|
||||
.Where(tickBox => tickBox.Parent.Visible)
|
||||
.Select(tickBox => (Identifier)tickBox.UserData);
|
||||
}
|
||||
|
||||
private GUIFrame gameModeSettingsContent;
|
||||
@@ -2884,6 +2922,11 @@ namespace Barotrauma
|
||||
UserData = new JobVariant(jobPrefab, variant)
|
||||
};
|
||||
jobVariantTooltip.RectTransform.AbsoluteOffset = new Point(parentSlot.Rect.Right, parentSlot.Rect.Y);
|
||||
if (jobVariantTooltip.Rect.X < 0)
|
||||
{
|
||||
jobVariantTooltip.RectTransform.SetPosition(anchor: Anchor.TopLeft, pivot: Pivot.BottomLeft);
|
||||
jobVariantTooltip.RectTransform.AbsoluteOffset = new Point(parentSlot.Rect.X, parentSlot.Rect.Y);
|
||||
}
|
||||
|
||||
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), jobVariantTooltip.RectTransform, Anchor.Center))
|
||||
{
|
||||
@@ -3049,6 +3092,8 @@ namespace Barotrauma
|
||||
|
||||
private void RefreshOutpostDropdown()
|
||||
{
|
||||
Identifier randomOutpostIdentifier = "Random".ToIdentifier();
|
||||
|
||||
outpostDropdown.Parent.Visible = MissionTypeFrame.Visible;
|
||||
if (!outpostDropdown.Parent.Visible) { return; }
|
||||
|
||||
@@ -3057,7 +3102,7 @@ namespace Barotrauma
|
||||
Identifier prevSelected = GameMain.NetworkMember?.ServerSettings.SelectedOutpostName ?? Identifier.Empty;
|
||||
|
||||
outpostDropdown.ClearChildren();
|
||||
outpostDropdown.AddItem(TextManager.Get("Random"), "Random".ToIdentifier());
|
||||
outpostDropdown.AddItem(TextManager.Get("Random"), randomOutpostIdentifier);
|
||||
HashSet<Identifier> validOutpostTagsForMissions = new HashSet<Identifier>();
|
||||
|
||||
IEnumerable<Type> suitableMissionClasses =
|
||||
@@ -3081,12 +3126,22 @@ namespace Barotrauma
|
||||
foreach (var submarineInfo in SubmarineInfo.SavedSubmarines.DistinctBy(s => s.Name))
|
||||
{
|
||||
if (submarineInfo.Type == SubmarineType.Outpost &&
|
||||
validOutpostTagsForMissions.Any(tag => submarineInfo.OutpostTags.Contains(tag)))
|
||||
validOutpostTagsForMissions.Any(submarineInfo.OutpostTags.Contains))
|
||||
{
|
||||
outpostDropdown.AddItem(submarineInfo.DisplayName, userData: submarineInfo.Name.ToIdentifier(), toolTip: submarineInfo.Description);
|
||||
}
|
||||
}
|
||||
outpostDropdown.ListBox.Select(prevSelected);
|
||||
if (!outpostDropdown.ListBox.Select(prevSelected))
|
||||
{
|
||||
//could not select the previously selected outpost (not suitable for the selected missions)
|
||||
// -> choose random instead
|
||||
if (outpostDropdown.SelectedData is Identifier selectedIdentifier &&
|
||||
selectedIdentifier != randomOutpostIdentifier)
|
||||
{
|
||||
outpostDropdown.Flash(GUIStyle.Red);
|
||||
}
|
||||
outpostDropdown.ListBox.Select(randomOutpostIdentifier);
|
||||
}
|
||||
GameMain.Client.ServerSettings.AssignGUIComponent(nameof(ServerSettings.SelectedOutpostName), outpostDropdown);
|
||||
}
|
||||
else
|
||||
@@ -4496,26 +4551,16 @@ namespace Barotrauma
|
||||
|
||||
void PositionJobSelectionFrame()
|
||||
{
|
||||
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - JobSelectionFrame.Rect.Width, characterInfoFrame.Rect.Bottom);
|
||||
if (characterInfoFrame.Rect.Bottom + JobSelectionFrame.Rect.Height > GameMain.GraphicsHeight)
|
||||
//move to the left side of the info frame
|
||||
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.X - JobSelectionFrame.Rect.Width, JobList.Rect.Y);
|
||||
if (JobSelectionFrame.Rect.X < 0)
|
||||
{
|
||||
//move to the left side of the info frame if the bottom goes below the screen
|
||||
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.X - JobSelectionFrame.Rect.Width, characterInfoFrame.Rect.Bottom - JobSelectionFrame.Rect.Height / 2);
|
||||
if (JobSelectionFrame.Rect.X < 0)
|
||||
{
|
||||
//scale if goes outside the screen horizontally
|
||||
JobSelectionFrame.RectTransform.Resize(new Point(characterInfoFrame.Rect.X, JobSelectionFrame.Rect.Height));
|
||||
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.X - JobSelectionFrame.Rect.Width, JobSelectionFrame.RectTransform.AbsoluteOffset.Y);
|
||||
}
|
||||
}
|
||||
//scale if goes outside the screen horizontally
|
||||
JobSelectionFrame.RectTransform.Resize(new Point(characterInfoFrame.Rect.X, JobSelectionFrame.Rect.Height));
|
||||
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.X - JobSelectionFrame.Rect.Width, JobSelectionFrame.RectTransform.AbsoluteOffset.Y);
|
||||
}
|
||||
}
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), JobSelectionFrame.RectTransform, anchor: Anchor.Center), style: "OuterGlow", color: Color.Black)
|
||||
{
|
||||
UserData = "outerglow",
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
var jobSelectionList = new GUIListBox(new RectTransform(Vector2.One * listBoxRelativeSize, JobSelectionFrame.RectTransform, Anchor.Center), style: "GUIFrameListBox")
|
||||
{
|
||||
Padding = Vector4.One * GUI.IntScale(10)
|
||||
|
||||
@@ -368,6 +368,7 @@ namespace Barotrauma
|
||||
"DecorativeSprite",
|
||||
"BarrelSprite",
|
||||
"RailSprite",
|
||||
"ChargeSprite",
|
||||
"SchematicSprite",
|
||||
"WeldedSprite"
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
@@ -37,6 +38,151 @@ namespace Barotrauma
|
||||
Wiring
|
||||
}
|
||||
|
||||
#region Transform Editor
|
||||
private const float TransformWidgetOffset = 300f;
|
||||
|
||||
private GUITickBox rotateToolToggle, scaleToolToggle;
|
||||
public bool TransformWidgetSelected => TransformWidget.IsSelected;
|
||||
|
||||
private static Vector2 GetSelectionCenter()
|
||||
{
|
||||
IEnumerable<MapEntity> nonWireEntities = MapEntity.FilteredSelectedList.Where(static entity => (entity as Item)?.GetComponent<Wire>() is not { Drawable: true });
|
||||
if (nonWireEntities.None()) { return Vector2.Zero; }
|
||||
|
||||
float minX = nonWireEntities.Min(static entity => entity.DrawPosition.X);
|
||||
float minY = nonWireEntities.Min(static entity => entity.DrawPosition.Y);
|
||||
float maxX = nonWireEntities.Max(static entity => entity.DrawPosition.X);
|
||||
float maxY = nonWireEntities.Max(static entity => entity.DrawPosition.Y);
|
||||
return new Vector2(minX + maxX, minY + maxY) / 2f;
|
||||
}
|
||||
|
||||
private Vector2 oldWidgetWorldPos;
|
||||
|
||||
public record struct TransformData(float Scale, float RotationRad, Vector2 Pos, Rectangle Rect, Vector2? TexOffset,
|
||||
Dictionary<Wire, (List<Vector2> Nodes, float Width)> Wires,
|
||||
Dictionary<ItemLabel, float> TextScales,
|
||||
Dictionary<LightComponent, float> LightRanges,
|
||||
Dictionary<Turret, Vector2> TurretLimits);
|
||||
private TransformToolCommand transformCommand;
|
||||
|
||||
private Widget transformWidget;
|
||||
private Widget TransformWidget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (transformWidget != null) { return transformWidget; }
|
||||
|
||||
int size = GUI.IntScale(16f);
|
||||
transformWidget = new Widget("scale", size, WidgetShape.Rectangle)
|
||||
{
|
||||
Enabled = false,
|
||||
Color = GUIStyle.Yellow,
|
||||
InputAreaMargin = 20,
|
||||
RequireMouseOn = false,
|
||||
TooltipOffset = (size / 2f, -size / 2f),
|
||||
IsFilled = true
|
||||
};
|
||||
transformWidget.PreUpdate += _ =>
|
||||
{
|
||||
transformWidget.Enabled = MapEntity.FilteredSelectedList.Any() && (rotateToolToggle.Selected || scaleToolToggle.Selected);
|
||||
if (transformWidget.IsSelected && PlayerInput.PrimaryMouseButtonReleased())
|
||||
{
|
||||
if (MapEntity.EditingHUD.GetChild<GUIListBox>() is GUIListBox listBox)
|
||||
{
|
||||
SerializableEntityEditor.LockEditing = true;
|
||||
listBox.Content.Children.OfType<SerializableEntityEditor>().ForEach(editor => editor.RefreshValues());
|
||||
SerializableEntityEditor.LockEditing = false;
|
||||
}
|
||||
Widget.SelectedWidgets.Remove(transformWidget);
|
||||
StoreCommand(transformCommand);
|
||||
transformWidget.Color = Color.Yellow;
|
||||
}
|
||||
};
|
||||
transformWidget.Selected += () =>
|
||||
{
|
||||
transformWidget.Color = GUIStyle.Blue;
|
||||
|
||||
IEnumerable<Item> containedItems = MapEntity.SelectedList.OfType<Item>().SelectManyRecursive(item => item.ContainedItems);
|
||||
IEnumerable<MapEntity> allEntities = MapEntity.SelectedList.Concat(containedItems).Distinct();
|
||||
|
||||
Dictionary<MapEntity, TransformData> oldTransformData = allEntities.ToDictionary(static entity => entity, static entity =>
|
||||
{
|
||||
Item item = entity as Item;
|
||||
Structure structure = entity as Structure;
|
||||
|
||||
float rotation = entity switch
|
||||
{
|
||||
Structure => MathHelper.ToRadians(structure.Rotation),
|
||||
Item => item.RotationRad,
|
||||
_ => 0f
|
||||
};
|
||||
return new TransformData(entity.Scale, rotation, entity.DrawPosition, entity.Rect, structure?.TextureOffset,
|
||||
GetPropertyDict<Wire, (List<Vector2>, float)>(static wire => (wire.GetNodes(), wire.Width)),
|
||||
GetPropertyDict<ItemLabel, float>(static label => label.TextScale),
|
||||
GetPropertyDict<LightComponent, float>(static light => light.Range),
|
||||
GetPropertyDict<Turret, Vector2>(static turret => turret.RotationLimits));
|
||||
|
||||
Dictionary<TComponent, TProperties> GetPropertyDict<TComponent, TProperties>(Func<TComponent, TProperties> propSelector) where TComponent : ItemComponent
|
||||
=> item?.GetComponents<TComponent>().ToDictionary(static comp => comp, propSelector);
|
||||
});
|
||||
|
||||
transformCommand = new TransformToolCommand(oldTransformData, GetSelectionCenter());
|
||||
};
|
||||
transformWidget.MouseHeld += _ =>
|
||||
{
|
||||
MapEntity.DisableSelect = true;
|
||||
|
||||
Vector2 widgetWorldPos = Cam.ScreenToWorld(transformWidget.DrawPos * 2f); // Scale position to account for camera zooming as well.
|
||||
if (MathUtils.NearlyEqual(widgetWorldPos, oldWidgetWorldPos)) { return; }
|
||||
oldWidgetWorldPos = widgetWorldPos;
|
||||
|
||||
transformCommand.RotationRad = null;
|
||||
LocalizedString rotationString = null;
|
||||
if (rotateToolToggle.Selected)
|
||||
{
|
||||
transformCommand.RotationRad = MathUtils.VectorToAngle(PlayerInput.MousePosition - Cam.WorldToScreen(transformCommand.Pivot));
|
||||
rotationString = TextManager.GetWithVariable("SubEditor.TransformWidget.Rotation", "[value]", MathHelper.ToDegrees(transformCommand.RotationRad.Value).ToString("0.000", CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
transformCommand.ScaleMult = null;
|
||||
LocalizedString scaleString = null;
|
||||
if (scaleToolToggle.Selected)
|
||||
{
|
||||
transformCommand.ScaleMult = Math.Clamp(Vector2.Distance(PlayerInput.MousePosition, Cam.WorldToScreen(transformCommand.Pivot)) / (TransformWidgetOffset * GUI.Scale), transformCommand.MinScale, transformCommand.MaxScale);
|
||||
scaleString = TextManager.GetWithVariable("SubEditor.TransformWidget.Scale", "[value]", transformCommand.ScaleMult.Value.ToString("0.000", CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
transformWidget.Tooltip = !rotationString.IsNullOrEmpty() && !scaleString.IsNullOrEmpty()
|
||||
? LocalizedString.Join("\n", rotationString, scaleString)
|
||||
: transformWidget.Tooltip = rotationString ?? scaleString;
|
||||
|
||||
transformCommand.Execute();
|
||||
};
|
||||
transformWidget.PreDraw += (sb, _) =>
|
||||
{
|
||||
Vector2 selectionCenterScreenPos = Cam.WorldToScreen(transformWidget.IsSelected ? transformCommand.Pivot : GetSelectionCenter());
|
||||
|
||||
if (!GameMain.Instance.Paused)
|
||||
{
|
||||
if (transformWidget.IsSelected && scaleToolToggle.Selected)
|
||||
{
|
||||
transformWidget.DrawPos = PlayerInput.MousePosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 dir = transformWidget.IsSelected ? Vector2.Normalize(PlayerInput.MousePosition - Cam.WorldToScreen(transformCommand.Pivot)) : Vector2.UnitX;
|
||||
transformWidget.DrawPos = selectionCenterScreenPos + dir * TransformWidgetOffset * GUI.Scale;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.DrawLine(sb, selectionCenterScreenPos, transformWidget.DrawPos, Color.Black, width: 7f);
|
||||
GUI.DrawLine(sb, selectionCenterScreenPos, transformWidget.DrawPos, Color.Red, width: 3f);
|
||||
};
|
||||
return transformWidget;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public enum WarningType
|
||||
{
|
||||
NoWaypoints,
|
||||
@@ -530,6 +676,16 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
spacing = new GUIFrame(new RectTransform(new Vector2(0.02f, 1.0f), paddedTopPanel.RectTransform), style: null);
|
||||
new GUIFrame(new RectTransform(new Vector2(0.1f, 0.9f), spacing.RectTransform, Anchor.Center), style: "VerticalLine");
|
||||
|
||||
rotateToolToggle = new GUITickBox(new RectTransform(new Vector2(0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "SubEditorRotateToggle")
|
||||
{
|
||||
ToolTip = TextManager.Get("SubEditor.RotateToggleToolTip")
|
||||
};
|
||||
scaleToolToggle = new GUITickBox(new RectTransform(new Vector2(0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "SubEditorScaleToggle")
|
||||
{
|
||||
ToolTip = TextManager.Get("SubEditor.ScaleToggleToolTip")
|
||||
};
|
||||
|
||||
var selectedLayerText = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), paddedTopPanel.RectTransform),
|
||||
string.Empty, textAlignment: Alignment.Center);
|
||||
@@ -1099,6 +1255,7 @@ namespace Barotrauma
|
||||
{
|
||||
Submarine.Unload();
|
||||
GameMain.SubEditorScreen.Select();
|
||||
GameMain.GameSession = null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1451,6 +1608,9 @@ namespace Barotrauma
|
||||
GUI.ForceMouseOn(null);
|
||||
SetMode(Mode.Default);
|
||||
|
||||
rotateToolToggle.Selected = false;
|
||||
scaleToolToggle.Selected = false;
|
||||
|
||||
if (backedUpSubInfo != null)
|
||||
{
|
||||
MainSub = new Submarine(backedUpSubInfo);
|
||||
@@ -1660,6 +1820,9 @@ namespace Barotrauma
|
||||
});
|
||||
|
||||
ClearFilter();
|
||||
|
||||
Widget.SelectedWidgets.Remove(TransformWidget);
|
||||
TransformWidget.Color = Color.Yellow;
|
||||
}
|
||||
|
||||
private void CreateDummyCharacter()
|
||||
@@ -4636,7 +4799,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (itemPrefab.Name.IsNullOrEmpty() || itemPrefab.HideInMenus || itemPrefab.HideInEditors) { continue; }
|
||||
if (!itemPrefab.Tags.Contains(Tags.WireItem)) { continue; }
|
||||
if (CircuitBox.IsInGame() && itemPrefab.Tags.Contains(Tags.Thalamus)) { continue; }
|
||||
if (CircuitBox.IsInGame() && (itemPrefab.Tags.Contains(Tags.Thalamus) || itemPrefab.Tags.Contains("alien")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
wirePrefabs.Add(itemPrefab);
|
||||
}
|
||||
@@ -6221,6 +6387,8 @@ namespace Barotrauma
|
||||
|
||||
CharacterHUD.Update((float)deltaTime, dummyCharacter, cam);
|
||||
}
|
||||
|
||||
TransformWidget.Update((float)deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -6350,6 +6518,11 @@ namespace Barotrauma
|
||||
}
|
||||
MapEntity.DrawEditor(spriteBatch, cam);
|
||||
|
||||
if (TransformWidget.Enabled)
|
||||
{
|
||||
TransformWidget.Draw(spriteBatch, (float)deltaTime);
|
||||
}
|
||||
|
||||
GUI.Draw(Cam, spriteBatch);
|
||||
|
||||
if (MeasurePositionStart != Vector2.Zero)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
Steamworks.FriendState.Offline => FriendStatus.Offline,
|
||||
Steamworks.FriendState.Invisible => FriendStatus.Offline,
|
||||
_ when steamFriend.IsPlayingThisGame => FriendStatus.PlayingBarotrauma,
|
||||
_ when steamFriend.GameInfo is { GameID: > 0 } => FriendStatus.PlayingAnotherGame,
|
||||
_ when steamFriend.GameInfo is { GameID.Value: > 0 } => FriendStatus.PlayingAnotherGame,
|
||||
_ => FriendStatus.NotPlaying
|
||||
},
|
||||
serverName: steamFriend.GetRichPresence("servername") ?? "",
|
||||
|
||||
@@ -17,11 +17,15 @@ namespace Barotrauma.Sounds
|
||||
|
||||
private short[] sampleBuffer = Array.Empty<short>();
|
||||
private short[] muffleBuffer = Array.Empty<short>();
|
||||
|
||||
private readonly double durationSeconds;
|
||||
public override double? DurationSeconds => durationSeconds;
|
||||
|
||||
public OggSound(SoundManager owner, string filename, bool stream, ContentXElement xElement) : base(owner, filename,
|
||||
stream, true, xElement)
|
||||
{
|
||||
var reader = new VorbisReader(Filename);
|
||||
|
||||
durationSeconds = reader.TotalTime.TotalSeconds;
|
||||
ALFormat = reader.Channels == 1 ? Al.FormatMono16 : Al.FormatStereo16;
|
||||
SampleRate = reader.SampleRate;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using OpenAL;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.IO;
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Sounds
|
||||
{
|
||||
@@ -24,6 +22,11 @@ namespace Barotrauma.Sounds
|
||||
|
||||
public readonly bool StreamsReliably;
|
||||
|
||||
/// <summary>
|
||||
/// Length of the audio in seconds. Null if the length is unknown (e.g. a streaming audio source).
|
||||
/// </summary>
|
||||
public abstract double? DurationSeconds { get; }
|
||||
|
||||
public bool Loading { get; protected set; }
|
||||
|
||||
private readonly SoundManager.SourcePoolIndex sourcePoolIndex = SoundManager.SourcePoolIndex.Default;
|
||||
|
||||
@@ -778,34 +778,36 @@ namespace Barotrauma.Sounds
|
||||
while (!killThread)
|
||||
{
|
||||
killThread = true;
|
||||
for (int i = 0; i < playingChannels.Length; i++)
|
||||
for (int sourcePoolIndex = 0; sourcePoolIndex < playingChannels.Length; sourcePoolIndex++)
|
||||
{
|
||||
lock (playingChannels[i])
|
||||
lock (playingChannels[sourcePoolIndex])
|
||||
{
|
||||
for (int j = 0; j < playingChannels[i].Length; j++)
|
||||
for (int channelIndex = 0; channelIndex < playingChannels[sourcePoolIndex].Length; channelIndex++)
|
||||
{
|
||||
if (playingChannels[i][j] == null) { continue; }
|
||||
if (playingChannels[i][j].IsStream)
|
||||
var channel = playingChannels[sourcePoolIndex][channelIndex];
|
||||
|
||||
if (channel == null) { continue; }
|
||||
if (channel.FadingOutAndDisposing)
|
||||
{
|
||||
if (playingChannels[i][j].IsPlaying)
|
||||
killThread = false;
|
||||
channel.Gain -= 0.1f;
|
||||
if (channel.Gain <= 0.0f)
|
||||
{
|
||||
channel.Dispose();
|
||||
playingChannels[sourcePoolIndex][channelIndex] = null;
|
||||
}
|
||||
}
|
||||
else if (channel.IsStream)
|
||||
{
|
||||
if (channel.IsPlaying)
|
||||
{
|
||||
killThread = false;
|
||||
playingChannels[i][j].UpdateStream();
|
||||
channel.UpdateStream();
|
||||
}
|
||||
else
|
||||
{
|
||||
playingChannels[i][j].Dispose();
|
||||
playingChannels[i][j] = null;
|
||||
}
|
||||
}
|
||||
else if (playingChannels[i][j].FadingOutAndDisposing)
|
||||
{
|
||||
killThread = false;
|
||||
playingChannels[i][j].Gain -= 0.1f;
|
||||
if (playingChannels[i][j].Gain <= 0.0f)
|
||||
{
|
||||
playingChannels[i][j].Dispose();
|
||||
playingChannels[i][j] = null;
|
||||
channel.Dispose();
|
||||
playingChannels[sourcePoolIndex][channelIndex] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,10 +868,28 @@ namespace Barotrauma
|
||||
if (Level.IsLoadedOutpost)
|
||||
{
|
||||
// Only return music type for location types which have music tracks defined
|
||||
var locationType = Level.Loaded.StartLocation?.Type?.Identifier;
|
||||
if (locationType.HasValue && locationType != Identifier.Empty && musicClips.Any(c => c.Type == locationType))
|
||||
var locationType = Level.Loaded?.StartLocation?.Type?.Identifier;
|
||||
var backgroundMusicIdentifier = Level.Loaded?.StartLocation?.Type?.BackgroundMusicLocationType;
|
||||
|
||||
if (MatchesTrack(backgroundMusicIdentifier, out Identifier id) ||
|
||||
MatchesTrack(locationType, out id))
|
||||
{
|
||||
return locationType.Value;
|
||||
return id;
|
||||
}
|
||||
|
||||
bool MatchesTrack(Identifier? identifier, out Identifier idValue)
|
||||
{
|
||||
if (identifier.HasValue && identifier.Value != Identifier.Empty)
|
||||
{
|
||||
if (musicClips.Any(clip => clip.Type == identifier.Value))
|
||||
{
|
||||
idValue = identifier.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
idValue = Identifier.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenAL;
|
||||
using Barotrauma.Media;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Barotrauma.Media;
|
||||
using OpenAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Sounds
|
||||
{
|
||||
class VideoSound : Sound
|
||||
{
|
||||
private readonly object mutex;
|
||||
private Queue<short[]> sampleQueue;
|
||||
private readonly Queue<short[]> sampleQueue;
|
||||
|
||||
private SoundChannel soundChannel;
|
||||
private Video video;
|
||||
private readonly Video video;
|
||||
|
||||
public override double? DurationSeconds => null;
|
||||
|
||||
public VideoSound(SoundManager owner, string filename, int sampleRate, int channelCount, Video vid) : base(owner, filename, true, false)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Barotrauma.Sounds
|
||||
{
|
||||
class VoipSound : Sound
|
||||
{
|
||||
public override double? DurationSeconds => null;
|
||||
|
||||
public override SoundManager.SourcePoolIndex SourcePoolIndex
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -18,5 +19,15 @@ namespace Barotrauma
|
||||
|
||||
spriteBatch.Draw(texture, pos + offset, sourceRects[MathHelper.Clamp(spriteIndex, 0, sourceRects.Length - 1)], color, rotation + rotate, origin, scale, spriteEffect, depth == null ? this.depth : (float)depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When this spritesheet is used for an animation, returns the current spriteIndex based on the given animation speed.
|
||||
/// </summary>
|
||||
/// <param name="animationSpeed">Animation speed in frames per second</param>
|
||||
/// <param name="animatePaused">Should the animation run when paused? Defaults to false.</param>
|
||||
public int GetAnimatedSpriteIndex(float animationSpeed, bool animatePaused = false)
|
||||
{
|
||||
return (int)(Math.Floor((animatePaused ? Timing.TotalTime : Timing.TotalTimeUnpaused) * animationSpeed) % FrameCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace Barotrauma
|
||||
private readonly static HashSet<StatusEffect> ActiveLoopingSounds = new HashSet<StatusEffect>();
|
||||
private static double LastMuffleCheckTime;
|
||||
private readonly List<RoundSound> sounds = new List<RoundSound>();
|
||||
public IEnumerable<RoundSound> Sounds { get { return sounds; } }
|
||||
public IEnumerable<RoundSound> Sounds => sounds;
|
||||
|
||||
private SoundSelectionMode soundSelectionMode;
|
||||
private SoundChannel soundChannel;
|
||||
private Entity soundEmitter;
|
||||
@@ -64,6 +65,16 @@ namespace Barotrauma
|
||||
|
||||
partial void ApplyProjSpecific(float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Hull hull, Vector2 worldPosition, bool playSound)
|
||||
{
|
||||
if (steamTimeLineEventToTrigger != default)
|
||||
{
|
||||
SteamTimelineManager.AddTimelineEvent(
|
||||
steamTimeLineEventToTrigger.title,
|
||||
steamTimeLineEventToTrigger.description,
|
||||
steamTimeLineEventToTrigger.icon,
|
||||
priority: 1,
|
||||
submarine: entity?.Submarine);
|
||||
}
|
||||
|
||||
if (playSound)
|
||||
{
|
||||
PlaySound(entity, hull, worldPosition);
|
||||
@@ -222,6 +233,7 @@ namespace Barotrauma
|
||||
{
|
||||
PlaySound(selectedSound);
|
||||
}
|
||||
playSoundAfterLoadedCoroutine = null;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
|
||||
87
Barotrauma/BarotraumaClient/ClientSource/Steam/SteamIcons.cs
Normal file
87
Barotrauma/BarotraumaClient/ClientSource/Steam/SteamIcons.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all available Steam timeline event icons as constants and helper methods for number icons.
|
||||
/// </summary>
|
||||
public static class SteamIcons
|
||||
{
|
||||
// Standard icons
|
||||
public const string Marker = "steam_marker";
|
||||
public const string Achievement = "steam_achievement";
|
||||
public const string Attack = "steam_attack";
|
||||
public const string Bolt = "steam_bolt";
|
||||
public const string Bookmark = "steam_bookmark";
|
||||
public const string Bug = "steam_bug";
|
||||
public const string Cart = "steam_cart";
|
||||
public const string Caution = "steam_caution";
|
||||
public const string Chat = "steam_chat";
|
||||
public const string Checkmark = "steam_checkmark";
|
||||
public const string Chest = "steam_chest";
|
||||
public const string Circle = "steam_circle";
|
||||
public const string Combat = "steam_combat";
|
||||
public const string Completed = "steam_completed";
|
||||
public const string Crown = "steam_crown";
|
||||
public const string Death = "steam_death";
|
||||
public const string Defend = "steam_defend";
|
||||
public const string Diamond = "steam_diamond";
|
||||
public const string Edit = "steam_edit";
|
||||
public const string Effect = "steam_effect";
|
||||
public const string Explosion = "steam_explosion";
|
||||
public const string Fix = "steam_fix";
|
||||
public const string Flag = "steam_flag";
|
||||
public const string Gem = "steam_gem";
|
||||
public const string Group = "steam_group";
|
||||
public const string Heart = "steam_heart";
|
||||
public const string Info = "steam_info";
|
||||
public const string Invalid = "steam_invalid";
|
||||
public const string Minus = "steam_minus";
|
||||
public const string Pair = "steam_pair";
|
||||
public const string Person = "steam_person";
|
||||
public const string Plus = "steam_plus";
|
||||
public const string Purchase = "steam_purchase";
|
||||
public const string Question = "steam_question";
|
||||
public const string Ribbon = "steam_ribbon";
|
||||
public const string Screenshot = "steam_screenshot";
|
||||
public const string Scroll = "steam_scroll";
|
||||
public const string Square = "steam_square";
|
||||
public const string Star = "steam_star";
|
||||
public const string Starburst = "steam_starburst";
|
||||
public const string Timer = "steam_timer";
|
||||
public const string Transfer = "steam_transfer";
|
||||
public const string Triangle = "steam_triangle";
|
||||
public const string Trophy = "steam_trophy";
|
||||
public const string View = "steam_view";
|
||||
public const string X = "steam_x";
|
||||
|
||||
// Common number icons
|
||||
public const string Zero = "steam_0";
|
||||
public const string One = "steam_1";
|
||||
public const string Two = "steam_2";
|
||||
public const string Three = "steam_3";
|
||||
public const string Four = "steam_4";
|
||||
public const string Five = "steam_5";
|
||||
public const string Six = "steam_6";
|
||||
public const string Seven = "steam_7";
|
||||
public const string Eight = "steam_8";
|
||||
public const string Nine = "steam_9";
|
||||
public const string Ten = "steam_10";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Steam icon name for a number between 0 and 99.
|
||||
/// </summary>
|
||||
/// <param name="number">The number to get the icon for (0-99)</param>
|
||||
/// <returns>The Steam icon name in the format "steam_X"</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the number is less than 0 or greater than 99</exception>
|
||||
public static string GetNumberIcon(int number)
|
||||
{
|
||||
if (number is < 0 or > 99)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(number), "Number must be between 0 and 99");
|
||||
}
|
||||
return $"steam_{number}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,8 @@ namespace Barotrauma.Steam
|
||||
|
||||
//Maybe I'm completely wrong! All I know is that we need to handle both!
|
||||
}
|
||||
|
||||
SteamTimelineManager.Initialize();
|
||||
}
|
||||
|
||||
public static bool NetworkingDebugLog { get; private set; } = false;
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Steamworks.Data;
|
||||
using Steamworks;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal static class SteamTimelineManager
|
||||
{
|
||||
private static Screen? prevScreen;
|
||||
private static TimelineGameMode gameMode = TimelineGameMode.LoadingScreen;
|
||||
/// <summary>
|
||||
/// The current submarine that the controlled character is in (and has been for at least the delay amount).
|
||||
/// </summary>
|
||||
private static Submarine? currentSubmarine = null;
|
||||
/// <summary>
|
||||
/// For tracking the instantaneous switch of submarines, to reset the delay timer
|
||||
/// </summary>
|
||||
private static Submarine? previousTrackedSubmarine;
|
||||
private static Character? trackedCharacter = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delay in seconds before the submarine state change is considered valid, triggering events.
|
||||
/// </summary>
|
||||
private const float SubmarineStateChangeDelay = 2.0f;
|
||||
private static float submarineStateChangeTimer = 0.0f;
|
||||
|
||||
public enum TimelineGameMode
|
||||
{
|
||||
Playing,
|
||||
Staging,
|
||||
Menus,
|
||||
LoadingScreen
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
SetTimelineGameMode(TimelineGameMode.LoadingScreen);
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
{
|
||||
PollScreenChange();
|
||||
PollCharacterChange(deltaTime);
|
||||
PollSubmarineChange(deltaTime);
|
||||
}
|
||||
|
||||
private static void PollScreenChange()
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
if (Screen.Selected == prevScreen) { return; }
|
||||
|
||||
TimelineGameMode newMode = Screen.Selected switch
|
||||
{
|
||||
GameScreen _ => TimelineGameMode.Playing,
|
||||
NetLobbyScreen _ => TimelineGameMode.Staging,
|
||||
EditorScreen _ => TimelineGameMode.Playing,
|
||||
MainMenuScreen _ => TimelineGameMode.Menus,
|
||||
_ => TimelineGameMode.LoadingScreen // Default to Menus for other screens for now
|
||||
};
|
||||
|
||||
if (GameMain.Instance != null && GameMain.Instance.LoadingScreenOpen)
|
||||
{
|
||||
newMode = TimelineGameMode.LoadingScreen;
|
||||
}
|
||||
|
||||
if (newMode == gameMode) { return; }
|
||||
|
||||
SetTimelineGameMode(newMode);
|
||||
gameMode = newMode;
|
||||
|
||||
DebugConsole.NewMessage($"Timeline game mode set to {newMode}");
|
||||
|
||||
prevScreen = Screen.Selected;
|
||||
}
|
||||
|
||||
private static void PollCharacterChange(float deltaTime)
|
||||
{
|
||||
Character? controlledCharacter = Character.Controlled;
|
||||
|
||||
// reset current sub state if character changes
|
||||
if (controlledCharacter != trackedCharacter)
|
||||
{
|
||||
InstantlySetCurrentSubmarine(controlledCharacter?.Submarine ?? null);
|
||||
trackedCharacter = controlledCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PollSubmarineChange(float deltaTime)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
if (trackedCharacter == null) { return; }
|
||||
if (Screen.Selected is not GameScreen) { return; }
|
||||
|
||||
Submarine? trackedCharacterSubmarine = trackedCharacter.Submarine;
|
||||
|
||||
// timer makes sure only time-stable state changes are registered
|
||||
if (submarineStateChangeTimer > 0f)
|
||||
{
|
||||
submarineStateChangeTimer -= deltaTime;
|
||||
|
||||
if (submarineStateChangeTimer <= 0f)
|
||||
{
|
||||
// actually register our pending state change
|
||||
CharacterSubChanged(trackedCharacter, trackedCharacterSubmarine);
|
||||
}
|
||||
}
|
||||
|
||||
// detect instantaneous submarine change and start the delay timer
|
||||
if (previousTrackedSubmarine != trackedCharacterSubmarine)
|
||||
{
|
||||
submarineStateChangeTimer = SubmarineStateChangeDelay;
|
||||
}
|
||||
previousTrackedSubmarine = trackedCharacterSubmarine;
|
||||
}
|
||||
|
||||
private static void InstantlySetCurrentSubmarine(Submarine? submarine)
|
||||
{
|
||||
currentSubmarine = submarine;
|
||||
previousTrackedSubmarine = submarine;
|
||||
submarineStateChangeTimer = 0f;
|
||||
}
|
||||
|
||||
private static void CharacterSubChanged(Character character, Submarine newSubmarine)
|
||||
{
|
||||
if (newSubmarine == currentSubmarine) { return; }
|
||||
|
||||
// currentSub to none
|
||||
if (currentSubmarine != null && newSubmarine == null)
|
||||
{
|
||||
OnCharacterLeftSubmarine(character, currentSubmarine);
|
||||
}
|
||||
// currentSub to newSub
|
||||
else if (currentSubmarine != null && newSubmarine != null)
|
||||
{
|
||||
OnCharacterMovedBetweenSubmarines(character, currentSubmarine, newSubmarine);
|
||||
}
|
||||
//none to newSub
|
||||
else if (currentSubmarine == null && newSubmarine != null)
|
||||
{
|
||||
OnCharacterEnteredSubmarine(character, newSubmarine);
|
||||
}
|
||||
|
||||
currentSubmarine = newSubmarine;
|
||||
}
|
||||
|
||||
public static void SetTimelineGameMode(TimelineGameMode mode)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
|
||||
Steamworks.TimelineGameMode steamMode = mode switch
|
||||
{
|
||||
TimelineGameMode.Playing => Steamworks.TimelineGameMode.Playing,
|
||||
TimelineGameMode.Staging => Steamworks.TimelineGameMode.Staging,
|
||||
TimelineGameMode.Menus => Steamworks.TimelineGameMode.Menus,
|
||||
TimelineGameMode.LoadingScreen => Steamworks.TimelineGameMode.LoadingScreen,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, message: null)
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
SteamTimeline.SetTimelineGameMode(steamMode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to set timeline game mode to {mode}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnPlayerDied(Character victim, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
if (victim == null || causeOfDeath == null) { return; }
|
||||
|
||||
string eventTitle = $"{victim.DisplayName} died";
|
||||
string causeOfDeathText = causeOfDeath.Affliction != null ?
|
||||
causeOfDeath.Affliction.CauseOfDeathDescription.Value :
|
||||
causeOfDeath.Type.ToString();
|
||||
string eventDescription = $"{victim.DisplayName} died: {causeOfDeathText}";
|
||||
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Death, 1);
|
||||
}
|
||||
|
||||
public static void OnSignificantEnemyDied(Character victim, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
string eventTitle = $"{victim.DisplayName} has died!";
|
||||
string causeOfDeathText = causeOfDeath.Affliction != null ?
|
||||
causeOfDeath.Affliction.CauseOfDeathDescription.Value :
|
||||
causeOfDeath.Type.ToString();
|
||||
string eventDescription = $"{victim.DisplayName} died: {causeOfDeathText}";
|
||||
if (causeOfDeath.Killer != null)
|
||||
{
|
||||
eventDescription = $"{victim.DisplayName} was killed by {causeOfDeath.Killer.DisplayName}";
|
||||
}
|
||||
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Attack, 2);
|
||||
}
|
||||
|
||||
public static void OnRoundStarted()
|
||||
{
|
||||
string eventTitle = "Round Started";
|
||||
string eventDescription = "The round has started";
|
||||
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Marker, 0);
|
||||
}
|
||||
|
||||
public static void OnRoundEnded()
|
||||
{
|
||||
string eventTitle = "Round Ended";
|
||||
string eventDescription = "The round has ended";
|
||||
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Completed, 0);
|
||||
}
|
||||
|
||||
public static void OnCharacterLeftSubmarine(Character character, Submarine submarine)
|
||||
{
|
||||
string eventTitle = $"{character.Name} Went Diving Outside";
|
||||
string eventDescription = $"{character.Name} left {submarine.Info.Name}";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Transfer, 1);
|
||||
}
|
||||
|
||||
public static void OnCharacterMovedBetweenSubmarines(Character character, Submarine oldSubmarine, Submarine newSubmarine)
|
||||
{
|
||||
string eventTitle = $"{character.Name} Moved Between Locations";
|
||||
string eventDescription = $"{character.Name} moved from {oldSubmarine.Info.Name} to {newSubmarine.Info.Name}";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Transfer, 1);
|
||||
}
|
||||
|
||||
public static void OnCharacterEnteredSubmarine(Character character, Submarine submarine)
|
||||
{
|
||||
string eventTitle = $"{character.Name} Entered Hull";
|
||||
string eventDescription = $"{character.Name} has entered {submarine.Info.Name}";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Transfer, 1);
|
||||
}
|
||||
|
||||
public static void OnError(string errorMessage, Exception? e = null)
|
||||
{
|
||||
// these don't have localization support yet, use hardcoded strings
|
||||
string eventTitle = "Error Occurred";
|
||||
string eventDescription = $"An error was logged: {errorMessage}";
|
||||
if (e != null) { eventDescription += $"\n{e.GetType().Name}"; }
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Bug, 3); // Higher priority for errors
|
||||
}
|
||||
|
||||
public static void OnClientDisconnect(string disconnectInfo)
|
||||
{
|
||||
// these don't have localization support yet, use hardcoded strings
|
||||
string eventTitle = $"Client Disconnected";
|
||||
string eventDescription = $"{disconnectInfo}";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Bug, 2); // Maybe slightly lower priority than code errors
|
||||
}
|
||||
|
||||
public static void OnMonsterMissionTargetsKilled(MonsterMission mission)
|
||||
{
|
||||
// these don't have localization support yet, use hardcoded strings
|
||||
string eventTitle = $"Monsters Dispatched";
|
||||
string eventDescription = $"{mission.Name}: All targets were eliminated.";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Attack, 2);
|
||||
}
|
||||
|
||||
public static void OnScanSuccessful(ScanMission mission)
|
||||
{
|
||||
// these don't have localization support yet, use hardcoded strings
|
||||
string eventTitle = "Scan Successful";
|
||||
string eventDescription = $"{mission.Name}: A scanner has successfully scanned a target.";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Marker, 1);
|
||||
}
|
||||
|
||||
public static void OnOutpostTargetEliminated(AbandonedOutpostMission mission)
|
||||
{
|
||||
// these don't have localization support yet, use hardcoded strings
|
||||
string eventTitle = $"Target Character Eliminated";
|
||||
string eventDescription = $"{mission.Name}: A target was eliminated.";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Attack, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How often can hull breach events be created? There's often multiple breaches very close to each other, not necessary to track all of them.
|
||||
/// </summary>
|
||||
const float HullBreachEventInterval = 10.0f;
|
||||
private static double LastHullBreachTime;
|
||||
|
||||
public static void OnHullBreached(Structure structure)
|
||||
{
|
||||
if (LastHullBreachTime > Timing.TotalTime - HullBreachEventInterval) { return; }
|
||||
// only trigger this event for player subs, since beacon stations can fill the requirements at level start
|
||||
if (structure.Submarine?.Info is not { IsPlayer: true }) { return; }
|
||||
|
||||
string eventTitle = "Major Hull Breach";
|
||||
string eventDescription = $"The hull of {structure.Submarine?.Info.Name ?? "Unknown Submarine"} suffered a major breach.";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Caution, 2);
|
||||
LastHullBreachTime = Timing.TotalTime;
|
||||
}
|
||||
|
||||
public static void OnMissionTargetRetrieved(Item item, Mission mission)
|
||||
{
|
||||
string eventTitle = $"Target Retrieved: {item.Name}";
|
||||
string eventDescription = $"{mission.Name}: A target item {item.Name} was retrieved.";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Checkmark, 1);
|
||||
}
|
||||
|
||||
public static void OnMissionTargetPickedUp(Item item, Mission mission)
|
||||
{
|
||||
string eventTitle = $"Target Picked Up: {item.Name}";
|
||||
string eventDescription = $"{mission.Name}: A target item {item.Name} was picked up.";
|
||||
AddTimelineEvent(eventTitle, eventDescription, SteamIcons.Checkmark, 1);
|
||||
}
|
||||
|
||||
public static void AddTimelineEvent(string title, string description, string icon, uint priority = 1, Submarine? submarine = null)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
|
||||
// exit early if title, description or icon is empty
|
||||
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(description) || string.IsNullOrWhiteSpace(icon))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to add timeline event: title, description or icon is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (submarine != null)
|
||||
{
|
||||
string submarineName = submarine.Info?.DisplayName.Value ?? "Unknown Submarine";
|
||||
title = title.Replace("[sub]", submarineName);
|
||||
description = description.Replace("[sub]", submarineName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var eventHandle = Steamworks.SteamTimeline.AddInstantaneousTimelineEvent(
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
priority,
|
||||
0.0f,
|
||||
Steamworks.TimelineEventClipPriority.Standard);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to add timeline event", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -727,7 +727,9 @@ namespace Barotrauma.Steam
|
||||
}
|
||||
if (selectedMods.All(ContentPackageManager.WorkshopPackages.Contains))
|
||||
{
|
||||
if (parentList.AllSelected.All(c => c.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last()?.Style?.Identifier == "WorkshopMenu.DownloadedIcon") && selectedMods.Length > 0 && SteamManager.IsInitialized)
|
||||
static Identifier? GetButtonIconStyle(GUIComponent c) => c.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last()?.Style?.Identifier;
|
||||
if (parentList.AllSelected.All(c => GetButtonIconStyle(c) is { } style && (style == "WorkshopMenu.DownloadedIcon" || style == "WorkshopMenu.InfoButtonUpdate")) &&
|
||||
selectedMods.Length > 0 && SteamManager.IsInitialized)
|
||||
{
|
||||
contextMenuOptions.Add(new((selectedMods.Length > 1 ? "UnsubscribeFromAllSelected" : "WorkshopItemUnsubscribe").ToIdentifier(), true, () =>
|
||||
{
|
||||
@@ -774,8 +776,7 @@ namespace Barotrauma.Steam
|
||||
return true;
|
||||
}
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[1].OnClicked += (_, _) =>
|
||||
msgBox.Buttons[0].OnClicked += (_, _) =>
|
||||
{
|
||||
if (textBox.Text == mod.Name)
|
||||
{
|
||||
@@ -794,6 +795,7 @@ namespace Barotrauma.Steam
|
||||
return false;
|
||||
}
|
||||
};
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
}
|
||||
void CopyToLocal()
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Steamworks;
|
||||
using Steamworks.Ugc;
|
||||
using Directory = Barotrauma.IO.Directory;
|
||||
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
|
||||
using Path = Barotrauma.IO.Path;
|
||||
@@ -335,9 +336,10 @@ namespace Barotrauma.Steam
|
||||
.WithTags(tagButtons.Where(kvp => kvp.Value.Selected).Select(kvp => kvp.Key.Value))
|
||||
.WithChangeLog(changeNoteTextBox.Text)
|
||||
.WithMetaData($"gameversion={localPackage.GameVersion};modversion={versionTextBox.Text}")
|
||||
.WithVisibility(visibility)
|
||||
.WithPreviewFile(thumbnailPath);
|
||||
|
||||
ugcEditor.Visibility = visibility;
|
||||
|
||||
CoroutineManager.StartCoroutine(
|
||||
MessageBoxCoroutine((currentStepText, messageBox)
|
||||
=> PublishItem(currentStepText, messageBox, versionTextBox.Text, ugcEditor, localPackage)));
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
@@ -298,7 +299,7 @@ namespace Barotrauma
|
||||
case null:
|
||||
continue;
|
||||
case ItemContainer { Inventory: not null } newContainer when ic is ItemContainer { Inventory: not null } itemContainer:
|
||||
itemContainer.Inventory.GetReplacementOrThiS().ReplacedBy = newContainer.Inventory;
|
||||
itemContainer.Inventory.GetReplacementOrThis().ReplacedBy = newContainer.Inventory;
|
||||
goto default;
|
||||
default:
|
||||
ic.GetReplacementOrThis().ReplacedBy = component;
|
||||
@@ -321,7 +322,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (slotRef.Item == receiver)
|
||||
{
|
||||
inventory.GetReplacementOrThiS().TryPutItem(it, slotRef.Slot, false, false, null, createNetworkEvent: false);
|
||||
inventory.GetReplacementOrThis().TryPutItem(it, slotRef.Slot, false, false, null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +417,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Inventory.GetReplacementOrThiS().TryPutItem(item, slot, false, false, null, createNetworkEvent: false);
|
||||
Inventory.GetReplacementOrThis().TryPutItem(item, slot, false, false, null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,7 +610,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (targetItem.GetReplacementOrThis() is Item item)
|
||||
{
|
||||
newInventory?.GetReplacementOrThiS().TryPutItem(item, newSlot, true, false, null, createNetworkEvent: false);
|
||||
newInventory?.GetReplacementOrThis().TryPutItem(item, newSlot, true, false, null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,7 +618,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (targetItem.GetReplacementOrThis() is Item item)
|
||||
{
|
||||
oldInventory?.GetReplacementOrThiS().TryPutItem(item, oldSlot, true, false, null, createNetworkEvent: false);
|
||||
oldInventory?.GetReplacementOrThis().TryPutItem(item, oldSlot, true, false, null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,4 +629,86 @@ namespace Barotrauma
|
||||
return TextManager.GetWithVariable("Undo.MovedItem", "[item]", targetItem.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A command for applying changes from the <see cref="SubEditorScreen.TransformWidget"/>.
|
||||
/// </summary>
|
||||
internal class TransformToolCommand : Command
|
||||
{
|
||||
private readonly Dictionary<MapEntity, SubEditorScreen.TransformData> originalData;
|
||||
public float? ScaleMult, RotationRad;
|
||||
public readonly Vector2 Pivot;
|
||||
private readonly Vector2 wirePivot;
|
||||
public float MinScale, MaxScale;
|
||||
|
||||
public TransformToolCommand(Dictionary<MapEntity, SubEditorScreen.TransformData> data, Vector2 pivot)
|
||||
{
|
||||
originalData = data;
|
||||
Pivot = pivot;
|
||||
wirePivot = Pivot - Submarine.MainSub.HiddenSubPosition;
|
||||
|
||||
MinScale = 0.01f / Math.Max(data.Values.Min(data => data.Scale), 0.01f);
|
||||
MaxScale = 10f / Math.Min(data.Values.Max(data => data.Scale), 10f);
|
||||
}
|
||||
|
||||
public override void Execute() => UpdateTransforms(RotationRad ?? 0f, ScaleMult ?? 1f);
|
||||
public override void UnExecute() => UpdateTransforms(0f, 1f);
|
||||
|
||||
public override void Cleanup() => originalData.Clear();
|
||||
|
||||
private void UpdateTransforms(float rotationRad, float scaleMult)
|
||||
{
|
||||
foreach ((MapEntity receiver, SubEditorScreen.TransformData data) in originalData)
|
||||
{
|
||||
if (RotationRad.HasValue && receiver is Item { Prefab.AllowRotatingInEditor: true } or Structure { Prefab.AllowRotatingInEditor: true })
|
||||
{
|
||||
int rotationDir = receiver is Structure && receiver.FlippedX ^ receiver.FlippedY ? -1 : 1;
|
||||
float newRotation = MathHelper.ToDegrees(data.RotationRad + rotationRad * rotationDir);
|
||||
switch (receiver)
|
||||
{
|
||||
case Item item:
|
||||
item.Rotation = newRotation;
|
||||
break;
|
||||
case Structure structure:
|
||||
structure.Rotation = newRotation;
|
||||
break;
|
||||
}
|
||||
data.TurretLimits?.ForEach(pair => pair.Key.RotationLimits = pair.Value + new Vector2(MathHelper.ToDegrees(rotationRad)));
|
||||
}
|
||||
|
||||
if (ScaleMult.HasValue)
|
||||
{
|
||||
receiver.Scale = data.Scale * scaleMult;
|
||||
if (receiver.ResizeVertical || receiver.ResizeHorizontal)
|
||||
{
|
||||
if (receiver.ResizeVertical)
|
||||
{
|
||||
receiver.RectHeight = (int)(data.Rect.Height * scaleMult);
|
||||
}
|
||||
if (receiver.ResizeHorizontal)
|
||||
{
|
||||
receiver.RectWidth = (int)(data.Rect.Width * scaleMult);
|
||||
}
|
||||
if (receiver is Structure structure && data.TexOffset.HasValue)
|
||||
{
|
||||
structure.TextureOffset = data.TexOffset.Value * scaleMult;
|
||||
}
|
||||
}
|
||||
data.TextScales?.ForEach(pair => pair.Key.TextScale = pair.Value * scaleMult);
|
||||
data.LightRanges?.ForEach(pair => pair.Key.Range = pair.Value * scaleMult);
|
||||
data.Wires?.ForEach(pair => pair.Key.Width = pair.Value.Width * scaleMult);
|
||||
}
|
||||
|
||||
Vector2 newEntityPos = MathUtils.RotatePoint((data.Pos - Pivot) * scaleMult, -rotationRad) + Pivot;
|
||||
receiver.Move(newEntityPos - receiver.DrawPosition);
|
||||
|
||||
data.Wires?.ForEach(pair => pair.Key.SetNodes(pair.Value.Nodes.Select(TransformWireNode)));
|
||||
Vector2 TransformWireNode(Vector2 node) => MathUtils.RotatePoint((node - wirePivot) * scaleMult, -rotationRad) + wirePivot;
|
||||
}
|
||||
}
|
||||
|
||||
public override LocalizedString GetDescription() => originalData.Count > 1
|
||||
? TextManager.GetWithVariable("Undo.ChangedTransformMultiple", "[amount]", originalData.Count.ToString())
|
||||
: TextManager.GetWithVariable("Undo.ChangedTransform", "[item]", originalData.First().Key.Name);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.8.8.1</Version>
|
||||
<Version>1.9.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.8.8.1</Version>
|
||||
<Version>1.9.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.8.8.1</Version>
|
||||
<Version>1.9.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.8.8.1</Version>
|
||||
<Version>1.9.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.8.8.1</Version>
|
||||
<Version>1.9.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -677,6 +677,7 @@ namespace Barotrauma
|
||||
{
|
||||
msg.WriteUInt32(CauseOfDeath.Affliction.UintIdentifier);
|
||||
}
|
||||
msg.WriteUInt16(CauseOfDeath.Killer?.ID ?? NullEntityID);
|
||||
msg.WriteBoolean(forceAfflictionData);
|
||||
if (forceAfflictionData)
|
||||
{
|
||||
|
||||
@@ -2143,9 +2143,33 @@ namespace Barotrauma
|
||||
"freecam",
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
client.UsingFreeCam = true;
|
||||
GameMain.Server.SetClientCharacter(client, null);
|
||||
client.SpectateOnly = true;
|
||||
if (client.UsingFreeCam)
|
||||
{
|
||||
// Exiting freecam - try to return to previous character
|
||||
Character prevCharacter = null;
|
||||
if (client.PreviousCharacter != null && client.PreviousCharacter.TryGetTarget(out prevCharacter) &&
|
||||
prevCharacter != null && !prevCharacter.IsDead && !prevCharacter.Removed)
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage($"{client.Name}: Exiting freecam mode", client, Color.Yellow);
|
||||
client.UsingFreeCam = false;
|
||||
GameMain.Server.SetClientCharacter(client, prevCharacter);
|
||||
client.SpectateOnly = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage($"{client.Name}: Could not regain control of the previous character (dead or removed).", client, Color.Red);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Entering freecam - store current character ID
|
||||
GameMain.Server.SendConsoleMessage($"{client.Name}: Entering freecam mode", client, Color.Yellow);
|
||||
Character currentCharacter = client.Character;
|
||||
client.PreviousCharacter = new WeakReference<Character>(currentCharacter);
|
||||
client.UsingFreeCam = true;
|
||||
client.SpectateOnly = true;
|
||||
GameMain.Server.SetClientCharacter(client, null);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ namespace Barotrauma
|
||||
purchasedHullRepairs = value;
|
||||
PurchasedHullRepairsInLatestSave |= value;
|
||||
IncrementLastUpdateIdForFlag(NetFlags.Misc);
|
||||
DebugConsole.NewMessage("Set PurchasedHullRepairs to " + PurchasedHullRepairs, Color.Cyan);
|
||||
}
|
||||
}
|
||||
public override bool PurchasedLostShuttles
|
||||
@@ -861,6 +862,13 @@ namespace Barotrauma
|
||||
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
|
||||
}
|
||||
|
||||
if (purchasedUpgradeCount > 0 || purchasedItemSwapCount > 0)
|
||||
{
|
||||
//if the client attempted to purchase something, increment flag regardless of whether the upgrades were actually purchased or not
|
||||
//so we can sync the correct state in case the client incorrectly assumed they can buy something (e.g. lost permissions just as they were purchasing)
|
||||
IncrementLastUpdateIdForFlag(NetFlags.UpgradeManager);
|
||||
}
|
||||
|
||||
int hullRepairCost = GetHullRepairCost();
|
||||
int itemRepairCost = GetItemRepairCost();
|
||||
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
|
||||
@@ -1100,7 +1108,7 @@ namespace Barotrauma
|
||||
var characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
foreach (var (prefab, category, _) in purchasedUpgrades)
|
||||
{
|
||||
UpgradeManager.PurchaseUpgrade(prefab, category, client: sender);
|
||||
UpgradeManager.TryPurchaseUpgrade(prefab, category, client: sender);
|
||||
|
||||
// unstable logging
|
||||
int price = prefab.Price.GetBuyPrice(prefab, UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList);
|
||||
@@ -1111,7 +1119,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (purchasedItemSwap.ItemToInstall == null)
|
||||
{
|
||||
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove);
|
||||
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, client: sender);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1502,11 +1510,13 @@ namespace Barotrauma
|
||||
{
|
||||
element.Add(new XAttribute("campaignid", CampaignID));
|
||||
XElement modeElement = new XElement("MultiPlayerCampaign",
|
||||
new XAttribute("purchasedlostshuttles", PurchasedLostShuttles),
|
||||
new XAttribute("purchasedhullrepairs", PurchasedHullRepairs),
|
||||
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
|
||||
new XAttribute("purchasedlostshuttles", PurchasedLostShuttlesInLatestSave),
|
||||
new XAttribute("purchasedhullrepairs", PurchasedHullRepairsInLatestSave),
|
||||
new XAttribute("purchaseditemrepairs", PurchasedItemRepairsInLatestSave),
|
||||
new XAttribute("cheatsenabled", CheatsEnabled));
|
||||
|
||||
DebugConsole.NewMessage("Saved PurchasedHullRepairs: "+ PurchasedHullRepairs+" (in last save "+PurchasedHullRepairsInLatestSave+")", Color.Magenta);
|
||||
|
||||
modeElement.Add(Settings.Save());
|
||||
modeElement.Add(SaveStats());
|
||||
if (GameMain.Server?.TraitorManager is TraitorManager traitorManager)
|
||||
@@ -1520,6 +1530,11 @@ namespace Barotrauma
|
||||
modeElement.Add(GameMain.GameSession?.EventManager.Save());
|
||||
}
|
||||
|
||||
foreach (Identifier unlockedRecipe in GameMain.GameSession.UnlockedRecipes)
|
||||
{
|
||||
modeElement.Add(new XElement("unlockedrecipe", new XAttribute("identifier", unlockedRecipe)));
|
||||
}
|
||||
|
||||
CampaignMetadata?.Save(modeElement);
|
||||
Map.Save(modeElement);
|
||||
CargoManager?.SavePurchasedItems(modeElement);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.WriteRangedSingle(Health, 0f, (float)MaxHealth, 8);
|
||||
msg.WriteRangedSingle(Health, 0f, (float)MaxWater, 8);
|
||||
if (TryExtractEventData(extraData, out EventData eventData))
|
||||
{
|
||||
int offset = eventData.Offset;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.ServerEventWrite(msg, c, extraData);
|
||||
|
||||
bool writeAttachData = attachable && body != null;
|
||||
bool writeAttachData = attachable && originalBody != null;
|
||||
msg.WriteBoolean(writeAttachData);
|
||||
if (!writeAttachData) { return; }
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
msg.WriteBoolean(Attached);
|
||||
msg.WriteSingle(body.SimPosition.X);
|
||||
msg.WriteSingle(body.SimPosition.Y);
|
||||
msg.WriteSingle(originalBody.SimPosition.X);
|
||||
msg.WriteSingle(originalBody.SimPosition.Y);
|
||||
msg.WriteUInt16(item.Submarine?.ID ?? Entity.NullEntityID);
|
||||
msg.WriteUInt16(attacherId);
|
||||
}
|
||||
@@ -40,6 +40,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Drop(false, null);
|
||||
item.SetTransform(simPosition, 0.0f, findNewHull: false);
|
||||
//don't find the new hull in SetTransform, because that'd also potentially change the submarine (teleport the item outside if it's attached outside)
|
||||
//instead just find the hull, so the item is considered to be in the right hull
|
||||
item.CurrentHull = Hull.FindHull(item.WorldPosition, item.CurrentHull);
|
||||
AttachToWall();
|
||||
OnUsed.Invoke(new ItemUseInfo(item, c.Character));
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
internal partial class PowerDistributor : PowerTransfer, IServerSerializable, IClientSerializable
|
||||
{
|
||||
#region Networking
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
{
|
||||
SharedEventRead(msg, out EventType eventType, out PowerGroup powerGroup, out string newName, out float newRatio);
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.NameChange:
|
||||
powerGroup.Name = newName;
|
||||
break;
|
||||
case EventType.RatioChange:
|
||||
powerGroup.SupplyRatio = newRatio;
|
||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} changed supply ratio of power group \"{powerGroup.Name}\" to \"{powerGroup.SupplyRatio}\"", ServerLog.MessageType.ItemInteraction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
item.CreateServerEvent(this, new EventData(powerGroup, eventType));
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData? extraData = null) => SharedEventWrite(msg, extraData);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -274,6 +274,7 @@ namespace Barotrauma
|
||||
msg.WriteByte(originalSlotIndex < 0 ? (byte)255 : (byte)originalSlotIndex);
|
||||
}
|
||||
|
||||
msg.WriteBoolean(OnInsertedEffectsAppliedOnPreviousRound);
|
||||
msg.WriteByte(body == null ? (byte)0 : (byte)body.BodyType);
|
||||
msg.WriteBoolean(SpawnedInCurrentOutpost);
|
||||
msg.WriteBoolean(AllowStealing);
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace Barotrauma
|
||||
//don't create updates if all clients are very far from the hull
|
||||
float hullUpdateDistanceSqr = NetConfig.HullUpdateDistance * NetConfig.HullUpdateDistance;
|
||||
if (!GameMain.Server.ConnectedClients.Any(c =>
|
||||
c.Character != null &&
|
||||
Vector2.DistanceSquared(c.Character.WorldPosition, WorldPosition) < hullUpdateDistanceSqr))
|
||||
(c.Character != null && Vector2.DistanceSquared(c.Character.WorldPosition, WorldPosition) < hullUpdateDistanceSqr) ||
|
||||
(c.SpectatePos != null && Vector2.DistanceSquared(c.SpectatePos.Value, WorldPosition) < hullUpdateDistanceSqr)) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -76,6 +76,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceStatusUpdate()
|
||||
{
|
||||
statusUpdateTimer = NetConfig.SparseHullUpdateInterval;
|
||||
}
|
||||
|
||||
|
||||
public void CreateStatusEvent()
|
||||
{
|
||||
@@ -137,6 +142,12 @@ namespace Barotrauma
|
||||
|
||||
WaterVolume = newWaterVolume;
|
||||
|
||||
if (newFireSources.Length != FireSources.Count)
|
||||
{
|
||||
//number of fire sources has changed, force a network update
|
||||
ForceStatusUpdate();
|
||||
}
|
||||
|
||||
for (int i = 0; i < newFireSources.Length; i++)
|
||||
{
|
||||
Vector2 pos = newFireSources[i].Position;
|
||||
|
||||
@@ -176,9 +176,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
|
||||
c.ChatSpamTimer = 10.0f;
|
||||
GameMain.Server.SendDirectChatMessage(denyMsg, c);
|
||||
BlockBySpamFilter();
|
||||
}
|
||||
flaggedAsSpam = true;
|
||||
return;
|
||||
@@ -188,14 +186,20 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (c.ChatSpamTimer > 0.0f && !isSpamExempt)
|
||||
{
|
||||
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
|
||||
c.ChatSpamTimer = 10.0f;
|
||||
GameMain.Server.SendDirectChatMessage(denyMsg, c);
|
||||
BlockBySpamFilter();
|
||||
flaggedAsSpam = true;
|
||||
return;
|
||||
}
|
||||
|
||||
flaggedAsSpam = false;
|
||||
|
||||
void BlockBySpamFilter()
|
||||
{
|
||||
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.BlockedBySpamFilter, null);
|
||||
c.ChatSpamTimer = BlockedBySpamFilterTime;
|
||||
GameMain.Server.SendDirectChatMessage(denyMsg, c);
|
||||
GameServer.Log(c.Name + " blocked by spam filter", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public int EstimateLengthBytesServer(Client c)
|
||||
|
||||
@@ -138,6 +138,8 @@ namespace Barotrauma.Networking
|
||||
get { return kickVoters.Count; }
|
||||
}
|
||||
|
||||
public WeakReference<Character> PreviousCharacter;
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
kickVoters = new List<Client>();
|
||||
|
||||
@@ -4279,6 +4279,9 @@ namespace Barotrauma.Networking
|
||||
public void GiveAchievement(Client client, Identifier achievementIdentifier)
|
||||
{
|
||||
if (client.GivenAchievements.Contains(achievementIdentifier)) { return; }
|
||||
|
||||
DebugConsole.NewMessage($"Attempting to give the achievement {achievementIdentifier} to {client.Name}...");
|
||||
|
||||
client.GivenAchievements.Add(achievementIdentifier);
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
@@ -4288,6 +4291,17 @@ namespace Barotrauma.Networking
|
||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public void UnlockRecipe(Identifier identifier)
|
||||
{
|
||||
foreach (var client in connectedClients)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteByte((byte)ServerPacketHeader.UNLOCKRECIPE);
|
||||
msg.WriteIdentifier(identifier);
|
||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
public void IncrementStat(Client client, AchievementStat stat, int amount)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.8.8.1</Version>
|
||||
<Version>1.9.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<contentpackage name="[DebugOnlyTest]PowerTestSub" modversion="1.0.9" corepackage="False" gameversion="1.8.0.0">
|
||||
<Submarine file="%ModDir%/PowerTestSub.sub" />
|
||||
</contentpackage>
|
||||
Binary file not shown.
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
|
||||
static class AchievementManager
|
||||
{
|
||||
private static readonly ImmutableHashSet<Identifier> SupportedAchievements = ImmutableHashSet.Create(
|
||||
public static readonly ImmutableHashSet<Identifier> SupportedAchievements = ImmutableHashSet.Create(
|
||||
"killmoloch".ToIdentifier(),
|
||||
"killhammerhead".ToIdentifier(),
|
||||
"killendworm".ToIdentifier(),
|
||||
@@ -146,6 +146,9 @@ namespace Barotrauma
|
||||
|
||||
public static void OnStartRound(Biome biome = null)
|
||||
{
|
||||
#if CLIENT
|
||||
SteamTimelineManager.OnRoundStarted();
|
||||
#endif
|
||||
roundData = new RoundData();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
@@ -161,6 +164,7 @@ namespace Barotrauma
|
||||
if (GameMain.Client != null) { return; }
|
||||
#endif
|
||||
|
||||
|
||||
if (biome != null && GameMain.GameSession?.GameMode is CampaignMode)
|
||||
{
|
||||
string shortBiomeIdentifier = biome.Identifier.Value.Replace(" ", "");
|
||||
@@ -233,8 +237,8 @@ namespace Barotrauma
|
||||
|
||||
//convert submarine velocity to km/h
|
||||
Vector2 submarineVel = Physics.DisplayToRealWorldRatio * ConvertUnits.ToDisplayUnits(sub.Velocity) * 3.6f;
|
||||
//achievement for going > 100 km/h
|
||||
if (Math.Abs(submarineVel.X) > 100.0f)
|
||||
//achievement for going > 50 km/h
|
||||
if (Math.Abs(submarineVel.X) > 50.0f)
|
||||
{
|
||||
//all conscious characters inside the sub get an achievement
|
||||
UnlockAchievement("subhighvelocity".ToIdentifier(), true, c => c != null && c.Submarine == sub && !c.IsDead && !c.IsUnconscious);
|
||||
@@ -279,7 +283,7 @@ namespace Barotrauma
|
||||
if (c == null || c.Removed) { return; }
|
||||
|
||||
if (c.HasEquippedItem("clownmask".ToIdentifier()) &&
|
||||
c.HasEquippedItem("clowncostume".ToIdentifier()))
|
||||
c.HasEquippedItem("clowngear".ToIdentifier()))
|
||||
{
|
||||
UnlockAchievement(c, "clowncostume".ToIdentifier());
|
||||
}
|
||||
@@ -407,9 +411,33 @@ namespace Barotrauma
|
||||
UnlockAchievement(reviver, "healcrit".ToIdentifier());
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
private static void CheckSteamTimelineEvents(Character killedCharacter, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
if (killedCharacter == Character.Controlled)
|
||||
{
|
||||
SteamTimelineManager.OnPlayerDied(killedCharacter, causeOfDeath);
|
||||
return;
|
||||
}
|
||||
|
||||
bool pvpkill = killedCharacter.IsHuman && GameMain.GameSession?.GameMode is PvPMode;
|
||||
|
||||
float combatStrength = killedCharacter.Params.AI?.CombatStrength ?? 0;
|
||||
bool significantCombatStrength = combatStrength >= 300;
|
||||
|
||||
if (pvpkill || significantCombatStrength)
|
||||
{
|
||||
// note: sometimes the causeOfDeath.Killer is null in multiplayer
|
||||
SteamTimelineManager.OnSignificantEnemyDied(killedCharacter, causeOfDeath);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void OnCharacterKilled(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
#if CLIENT
|
||||
CheckSteamTimelineEvents(character, causeOfDeath);
|
||||
|
||||
// If this is a multiplayer game, the client should let the server handle achievements
|
||||
if (GameMain.Client != null || GameMain.GameSession == null) { return; }
|
||||
|
||||
@@ -417,40 +445,41 @@ namespace Barotrauma
|
||||
causeOfDeath.Killer != null &&
|
||||
causeOfDeath.Killer == Character.Controlled)
|
||||
{
|
||||
IncrementStat(causeOfDeath.Killer, character.IsHuman ? AchievementStat.HumansKilled : AchievementStat.MonstersKilled , 1);
|
||||
IncrementStat(causeOfDeath.Killer, character.IsHuman ? AchievementStat.HumansKilled : AchievementStat.MonstersKilled, 1);
|
||||
}
|
||||
|
||||
#elif SERVER
|
||||
if (character != causeOfDeath.Killer && causeOfDeath.Killer != null)
|
||||
{
|
||||
IncrementStat(causeOfDeath.Killer, character.IsHuman ? AchievementStat.HumansKilled : AchievementStat.MonstersKilled , 1);
|
||||
IncrementStat(causeOfDeath.Killer, character.IsHuman ? AchievementStat.HumansKilled : AchievementStat.MonstersKilled, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
UnlockAchievement(causeOfDeath.Killer, $"kill{character.SpeciesName}".ToIdentifier());
|
||||
UnlockKillAchievement(causeOfDeath.Killer, character, $"kill{character.SpeciesName}".ToIdentifier());
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, $"kill{character.SpeciesName}indoors".ToIdentifier());
|
||||
UnlockKillAchievement(causeOfDeath.Killer, character, $"kill{character.SpeciesName}indoors".ToIdentifier());
|
||||
}
|
||||
if (character.SpeciesName.EndsWith("boss"))
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, $"kill{character.SpeciesName.Replace("boss", "")}".ToIdentifier());
|
||||
UnlockKillAchievement(causeOfDeath.Killer, character, $"kill{character.SpeciesName.Replace("boss", "")}".ToIdentifier());
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, $"kill{character.SpeciesName.Replace("boss", "")}indoors".ToIdentifier());
|
||||
UnlockKillAchievement(causeOfDeath.Killer, character, $"kill{character.SpeciesName.Replace("boss", "")}indoors".ToIdentifier());
|
||||
}
|
||||
}
|
||||
if (character.SpeciesName.EndsWith("_m"))
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, $"kill{character.SpeciesName.Replace("_m", "")}".ToIdentifier());
|
||||
UnlockKillAchievement(causeOfDeath.Killer, character, $"kill{character.SpeciesName.Replace("_m", "")}".ToIdentifier());
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, $"kill{character.SpeciesName.Replace("_m", "")}indoors".ToIdentifier());
|
||||
UnlockKillAchievement(causeOfDeath.Killer, character, $"kill{character.SpeciesName.Replace("_m", "")}indoors".ToIdentifier());
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (character.SpeciesName == "Jove" &&
|
||||
GameMain.GameSession.Campaign is MultiPlayerCampaign &&
|
||||
GameMain.Server?.ServerSettings is { IronmanModeActive: true })
|
||||
(GameMain.Server?.ServerSettings is { IronmanModeActive: true } or { RespawnMode: RespawnMode.Permadeath }))
|
||||
{
|
||||
UnlockAchievement(
|
||||
identifier: "europasfinest".ToIdentifier(),
|
||||
@@ -464,8 +493,12 @@ namespace Barotrauma
|
||||
causeOfDeath.Killer != character)
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, "killclown".ToIdentifier());
|
||||
if (character.CharacterHealth?.GetAffliction("psychosis") != null)
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, "whatsmirksbelow".ToIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (character.CharacterHealth?.GetAffliction("psychoclown") != null &&
|
||||
character.CurrentHull?.Submarine.Info is { Type: SubmarineType.BeaconStation })
|
||||
{
|
||||
@@ -516,6 +549,20 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void UnlockKillAchievement(Character killer, Character target, Identifier identifier)
|
||||
{
|
||||
if (killer != null &&
|
||||
target.Params.UnlockKillAchievementForWholeCrew &&
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Player).Contains(killer))
|
||||
{
|
||||
UnlockAchievement(identifier, unlockClients: true, characterConditions: c => c != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnlockAchievement(killer, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnTraitorWin(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -525,9 +572,15 @@ namespace Barotrauma
|
||||
UnlockAchievement(character, "traitorwin".ToIdentifier());
|
||||
}
|
||||
|
||||
public static void OnRoundEnded(GameSession gameSession)
|
||||
public static void OnRoundEnded(GameSession gameSession, bool roundInterrupted = false)
|
||||
{
|
||||
#if CLIENT
|
||||
SteamTimelineManager.OnRoundEnded();
|
||||
#endif
|
||||
if (CheatsEnabled) { return; }
|
||||
|
||||
// no processing for achievements if player quit to menu or such.
|
||||
if (roundInterrupted) { return; }
|
||||
|
||||
//made it to the destination
|
||||
if (gameSession?.Submarine != null && Level.Loaded != null && gameSession.Submarine.AtEndExit)
|
||||
@@ -713,7 +766,9 @@ namespace Barotrauma
|
||||
private static void UnlockAchievementsOnPlatforms(Identifier identifier)
|
||||
{
|
||||
if (unlockedAchievements.Contains(identifier)) { return; }
|
||||
|
||||
|
||||
DebugConsole.NewMessage($"Attempting to unlock achievement {identifier}...");
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
if (SteamManager.UnlockAchievement(identifier))
|
||||
|
||||
@@ -4396,11 +4396,11 @@ namespace Barotrauma
|
||||
else if (canAttackDoors && HasValidPath())
|
||||
{
|
||||
var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door is { CanBeTraversed: false } && !door.HasAccess(Character))
|
||||
if (door is { CanBeTraversed: false } && !door.HasAccess(Character) && door.Item.AiTarget is { } doorAiTarget)
|
||||
{
|
||||
if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack)
|
||||
if (SelectedAiTarget != doorAiTarget || State != AIState.Attack)
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, CurrentTargetMemory.Priority);
|
||||
SelectTarget(doorAiTarget, CurrentTargetMemory.Priority);
|
||||
State = AIState.Attack;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -787,6 +787,13 @@ namespace Barotrauma
|
||||
foreach (Item item in Character.HeldItems)
|
||||
{
|
||||
if (item == null || !item.IsInteractable(Character)) { continue; }
|
||||
if (!item.UnequipAutomatically) { continue; }
|
||||
//NPC set to operate the item they're holding, don't put it away
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveOperateItem operateItem &&
|
||||
(operateItem.OperateTarget == item || operateItem.Component?.Item == item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Character.TryPutItemInAnySlot(item)) { continue; }
|
||||
if (Character.TryPutItemInBag(item)) { continue; }
|
||||
if (item.HasTag(Tags.Weapon))
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace Barotrauma
|
||||
}
|
||||
var getItemObjective = new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
|
||||
{
|
||||
IsFindDivingGearSubObjective = true,
|
||||
AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _),
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
|
||||
@@ -49,6 +49,13 @@ namespace Barotrauma
|
||||
public const float DefaultReach = 100;
|
||||
public const float MaxReach = 150;
|
||||
|
||||
/// <summary>
|
||||
/// Is the goal of this objective to get diving gear (i.e. has it been created by <see cref="AIObjectiveFindDivingGear"/>)?
|
||||
/// If so, the objective won't attempt to create another objective if the path requires diving gear
|
||||
/// (wouldn't make sense to start looking for diving gear so the bot can get to a room they're trying to get diving gear from!)
|
||||
/// </summary>
|
||||
public bool IsFindDivingGearSubObjective;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool MustBeSpecificItem { get; set; }
|
||||
|
||||
@@ -378,6 +385,7 @@ namespace Barotrauma
|
||||
{
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
|
||||
{
|
||||
IsFindDivingGearSubObjective = IsFindDivingGearSubObjective,
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
AbortCondition = obj => targetItem == null || (targetItem.GetRootInventoryOwner() is Entity owner && owner != moveToTarget && owner != character),
|
||||
SpeakIfFails = false,
|
||||
|
||||
@@ -14,6 +14,13 @@ namespace Barotrauma
|
||||
|
||||
public override bool KeepDivingGearOn => GetTargetHull() == null;
|
||||
|
||||
/// <summary>
|
||||
/// Is the goal of this objective to get diving gear (i.e. has it been created by <see cref="AIObjectiveFindDivingGear"/>)?
|
||||
/// If so, the objective won't attempt to create another objective if the path requires diving gear
|
||||
/// (wouldn't make sense to start looking for diving gear so the bot can get to a room they're trying to get diving gear from!)
|
||||
/// </summary>
|
||||
public bool IsFindDivingGearSubObjective;
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
private readonly bool repeat;
|
||||
//how long until the path to the target is declared unreachable
|
||||
@@ -379,85 +386,89 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (Abandon) { return; }
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
|
||||
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool tryToGetDivingSuit = needsDivingSuit;
|
||||
Character followTarget = Target as Character;
|
||||
if (Mimic && !character.IsImmuneToPressure)
|
||||
|
||||
if (!IsFindDivingGearSubObjective)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
|
||||
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool tryToGetDivingSuit = needsDivingSuit;
|
||||
Character followTarget = Target as Character;
|
||||
if (Mimic && !character.IsImmuneToPressure)
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
tryToGetDivingSuit = true;
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
tryToGetDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
|
||||
if (tryToGetDivingSuit)
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen, requireSuitablePressureProtection: !objectiveManager.FailedToFindDivingGearForDepth);
|
||||
}
|
||||
}
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
|
||||
if (tryToGetDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen, requireSuitablePressureProtection: !objectiveManager.FailedToFindDivingGearForDepth);
|
||||
}
|
||||
else if (tryToGetDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
if (!getDivingGearIfNeeded)
|
||||
{
|
||||
if (needsEquipment)
|
||||
else if (tryToGetDivingGear)
|
||||
{
|
||||
// Don't try to reach the target without proper equipment.
|
||||
Abandon = true;
|
||||
return;
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.LockHands)
|
||||
if (!getDivingGearIfNeeded)
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
if (needsEquipment)
|
||||
{
|
||||
// Don't try to reach the target without proper equipment.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cantFindDivingGear && needsDivingSuit)
|
||||
else
|
||||
{
|
||||
// Don't try to reach the target without a suit because it's lethal.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (needsEquipment && !cantFindDivingGear)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
if (needsDivingSuit)
|
||||
if (character.LockHands)
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
}
|
||||
if (cantFindDivingGear && needsDivingSuit)
|
||||
{
|
||||
// Don't try to reach the target without a suit because it's lethal.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (needsEquipment && !cantFindDivingGear)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
// Shouldn't try to reach the target without a suit, because it's lethal.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without requiring the diving suit (or mask)
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: !tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
});
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
cantFindDivingGear = true;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
// Shouldn't try to reach the target without a suit, because it's lethal.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without requiring the diving suit (or mask)
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: !tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
});
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsDoneFollowing())
|
||||
{
|
||||
OnCompleted();
|
||||
|
||||
@@ -347,7 +347,10 @@ namespace Barotrauma
|
||||
useItemTimer = 0.05f;
|
||||
StartUsingItem();
|
||||
|
||||
if (!allowMovement)
|
||||
//make the character move towards the item they're using...
|
||||
if (!allowMovement &&
|
||||
//...except if they've selected an item that controls the character's direction (e.g. a periscope)
|
||||
character.SelectedSecondaryItem?.GetComponent<Controller>() is not { Direction: Direction.Left or Direction.Right })
|
||||
{
|
||||
TargetMovement = Vector2.Zero;
|
||||
TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
@@ -4,7 +4,11 @@ namespace Barotrauma
|
||||
{
|
||||
enum CauseOfDeathType
|
||||
{
|
||||
Unknown, Pressure, Suffocation, Drowning, Affliction, Disconnected
|
||||
Unknown, Pressure, Suffocation, Drowning, Affliction, Disconnected,
|
||||
/// <summary>
|
||||
/// Special cause of death type returned by <see cref="Character.CauseOfDeathType"/> when the character is not dead.
|
||||
/// </summary>
|
||||
None
|
||||
}
|
||||
|
||||
class CauseOfDeath
|
||||
|
||||
@@ -592,6 +592,9 @@ namespace Barotrauma
|
||||
|
||||
public Identifier VariantOf => Prefab.VariantOf;
|
||||
|
||||
/// <summary>
|
||||
/// Non-localized name of the character (for characters with info, their name, for monsters, their species). E.g. "Mudraptor_veteran", "John Smith".
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
@@ -600,6 +603,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localized display name of the character (e.g. "Mudraptor Veteran", "John Smith") - this should generally be used in any the player sees.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
@@ -1210,6 +1216,11 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by mods to check the cause of death of the character using conditionals (e.g. if some <see cref="OnDeath"/> effects should or should not be triggered by certain causes of death).
|
||||
/// </summary>
|
||||
public CauseOfDeathType CauseOfDeathType => CauseOfDeath?.Type ?? CauseOfDeathType.None;
|
||||
|
||||
//can other characters select (= grab) this character
|
||||
public bool CanBeSelected
|
||||
{
|
||||
@@ -2413,7 +2424,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!CanInteractWith(item)) { continue; }
|
||||
|
||||
if (SelectedItem?.OwnInventory != null && SelectedItem.OwnInventory.CanBePut(item))
|
||||
if (SelectedItem?.OwnInventory != null && !SelectedItem.OwnInventory.Locked && SelectedItem.OwnInventory.CanBePut(item))
|
||||
{
|
||||
SelectedItem.OwnInventory.TryPutItem(item, this);
|
||||
}
|
||||
@@ -5729,6 +5740,7 @@ namespace Barotrauma
|
||||
|
||||
public bool HasRecipeForItem(Identifier recipeIdentifier)
|
||||
{
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.UnlockedRecipes.Contains(recipeIdentifier)) { return true; }
|
||||
return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user