Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2023-01-31 13:17:15 -03:00
232 changed files with 4481 additions and 2283 deletions

View File

@@ -16,6 +16,9 @@ csharp_prefer_braces = when_multiline:warning
csharp_indent_case_contents_when_block = false csharp_indent_case_contents_when_block = false
# CS1591: Missing XML comment for publicly visible type or member # CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none dotnet_diagnostic.CS1591.severity = none
# IDE0090: Use 'new(...)'
csharp_style_implicit_object_creation_when_type_is_apparent = false
dotnet_diagnostic.CA1806.severity = silent
[*.{html,xml,csproj}] [*.{html,xml,csproj}]
indent_style = space indent_style = space

3
.gitattributes vendored
View File

@@ -1,4 +1,5 @@
# Declare files that will always have CRLF line endings on checkout. # Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf *.sln text eol=crlf
*.cs text eol=crlf *.cs text eol=crlf
*.xml text eol=crlf *.xml text eol=crlf
Barotrauma\BarotraumaServer\DedicatedServer.exe text eol=lf

View File

@@ -52,8 +52,8 @@ body:
label: Version 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. 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: options:
- 0.20.16.1 - 0.21.6.0
- 0.21.5.0 (Unstable) - 0.21.6.0 (Unstable)
- Faction/endgame test branch - Faction/endgame test branch
- Other - Other
validations: validations:

View File

@@ -154,9 +154,12 @@ namespace Barotrauma
} }
if (LosFadeIn && clampedTimer / PanDuration > 0.8f) if (LosFadeIn && clampedTimer / PanDuration > 0.8f)
{ {
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f; if (!GameMain.DevMode)
{
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f;
}
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity); Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
GameMain.LightManager.LosEnabled = true;
} }
#endif #endif
timer += CoroutineManager.DeltaTime; timer += CoroutineManager.DeltaTime;
@@ -170,8 +173,11 @@ namespace Barotrauma
#if CLIENT #if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack; GUI.ScreenOverlayColor = Color.TransparentBlack;
GameMain.LightManager.LosEnabled = true; if (!GameMain.DevMode)
GameMain.LightManager.LosAlpha = 1f; {
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
}
#endif #endif
if (prevControlled != null && !prevControlled.Removed) if (prevControlled != null && !prevControlled.Removed)

View File

@@ -107,7 +107,7 @@ namespace Barotrauma
Collider.AngularVelocity = newAngularVelocity; Collider.AngularVelocity = newAngularVelocity;
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove ? 0.01f : 0.2f; float errorTolerance = character.CanMove && !character.IsRagdolled ? 0.01f : 0.2f;
if (distSqrd > errorTolerance) if (distSqrd > errorTolerance)
{ {
if (distSqrd > 10.0f || !character.CanMove) if (distSqrd > 10.0f || !character.CanMove)
@@ -145,6 +145,7 @@ namespace Barotrauma
{ {
MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
MainLimb.PullJointEnabled = true; MainLimb.PullJointEnabled = true;
MainLimb.body.LinearVelocity = newVelocity;
} }
} }
} }
@@ -442,10 +443,20 @@ namespace Barotrauma
{ {
foreach (Limb limb in Limbs) foreach (Limb limb in Limbs)
{ {
if (limb == null || limb.IsSevered || limb.ActiveSprite == null || !limb.DoesFlip) { continue; } if (limb == null || limb.IsSevered || !limb.DoesMirror) { continue; }
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X; FlipSprite(limb.DeformSprite?.Sprite ?? limb.Sprite);
limb.ActiveSprite.Origin = spriteOrigin; foreach (var conditionalSprite in limb.ConditionalSprites)
{
FlipSprite(conditionalSprite.DeformableSprite?.Sprite ?? conditionalSprite.Sprite);
}
}
static void FlipSprite(Sprite sprite)
{
if (sprite == null) { return; }
Vector2 spriteOrigin = sprite.Origin;
spriteOrigin.X = sprite.SourceRect.Width - spriteOrigin.X;
sprite.Origin = spriteOrigin;
} }
} }

View File

@@ -616,7 +616,7 @@ namespace Barotrauma
return closestItem; return closestItem;
} }
private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f) private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = MaxHighlightDistance)
{ {
Character closestCharacter = null; Character closestCharacter = null;
@@ -626,7 +626,7 @@ namespace Barotrauma
{ {
if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; } if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; }
float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition); float dist = c.GetDistanceToClosestLimb(mouseSimPos);
if (dist < closestDist || if (dist < closestDist ||
(c.CampaignInteractionType != CampaignMode.InteractionType.None && closestCharacter?.CampaignInteractionType == CampaignMode.InteractionType.None && dist * 0.9f < closestDist)) (c.CampaignInteractionType != CampaignMode.InteractionType.None && closestCharacter?.CampaignInteractionType == CampaignMode.InteractionType.None && dist * 0.9f < closestDist))
{ {

View File

@@ -608,7 +608,8 @@ namespace Barotrauma
} }
} }
Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) * 0.7f; float dist = Vector2.Distance(character.FocusedCharacter.DrawPosition, character.DrawPosition);
Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) / dist * Math.Min(dist, Character.MaxDragDistance);
startPos = cam.WorldToScreen(startPos); startPos = cam.WorldToScreen(startPos);
string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName; string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName;
@@ -723,6 +724,8 @@ namespace Barotrauma
} }
bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = health; bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = health;
Color color = bossHealthBar.Character.CharacterHealth.GetAfflictionStrength("poison") > 0 || bossHealthBar.Character.CharacterHealth.GetAfflictionStrength("paralysis") > 0 ? GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;
bossHealthBar.TopHealthBar.Color = bossHealthBar.SideHealthBar.Color = color;
if (bossHealthBar.Character.Removed || !bossHealthBar.Character.Enabled) if (bossHealthBar.Character.Removed || !bossHealthBar.Character.Enabled)
{ {

View File

@@ -938,7 +938,7 @@ namespace Barotrauma
var headPreset = obj as HeadPreset; var headPreset = obj as HeadPreset;
if (info.Head.Preset != headPreset) if (info.Head.Preset != headPreset)
{ {
info.Head = new HeadInfo(info, headPreset) info.Head = new HeadInfo(info, headPreset, info.Head.HairIndex, info.Head.BeardIndex, info.Head.MoustacheIndex, info.Head.FaceAttachmentIndex)
{ {
SkinColor = info.Head.SkinColor, SkinColor = info.Head.SkinColor,
HairColor = info.Head.HairColor, HairColor = info.Head.HairColor,

View File

@@ -640,8 +640,7 @@ namespace Barotrauma
else else
{ {
forceAfflictionContainerUpdate = true; forceAfflictionContainerUpdate = true;
currentDisplayedAfflictions = GetAllAfflictions(mergeSameAfflictions: true) currentDisplayedAfflictions = GetAllAfflictions(mergeSameAfflictions: true, predicate: a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null);
.FindAll(a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null);
currentDisplayedAfflictions.Sort((a1, a2) => currentDisplayedAfflictions.Sort((a1, a2) =>
{ {
int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond); int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond);
@@ -1275,7 +1274,7 @@ namespace Barotrauma
//displaying an affliction we no longer have -> dirty //displaying an affliction we no longer have -> dirty
foreach ((Affliction affliction, float strength) in displayedAfflictions) foreach ((Affliction affliction, float strength) in displayedAfflictions)
{ {
if (!afflictions.Any(a => a.Key == affliction)) { return true; } if (afflictions.None(a => a.Key == affliction && a.Key.ShouldShowIcon(Character))) { return true; }
} }
return false; return false;
} }
@@ -2072,6 +2071,8 @@ namespace Barotrauma
foreach (var periodicEffect in newPeriodicEffects) foreach (var periodicEffect in newPeriodicEffects)
{ {
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
if (existingAffliction.Strength < periodicEffect.effect.MinStrength) { continue; }
if (periodicEffect.effect.MaxStrength > 0 && existingAffliction.Strength > periodicEffect.effect.MaxStrength) { continue; }
//timer has wrapped around, apply the effect //timer has wrapped around, apply the effect
if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {

View File

@@ -0,0 +1,22 @@
#nullable enable
using System;
namespace Barotrauma
{
internal static class HealingCooldown
{
public static float NormalizedCooldown => MathF.Min((float) (DateTimeOffset.UtcNow - OnCooldownUntil).TotalSeconds / CooldownDuration, 0f);
public static bool IsOnCooldown => DateTimeOffset.UtcNow < OnCooldownUntil;
private static DateTimeOffset OnCooldownUntil = DateTimeOffset.MinValue;
private const float CooldownDuration = 0.5f;
public static readonly Identifier MedicalItemTag = new Identifier("medical");
public static void PutOnCooldown()
{
OnCooldownUntil = DateTimeOffset.UtcNow.AddSeconds(CooldownDuration);
}
}
}

View File

@@ -8,6 +8,7 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using Barotrauma.Utils;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using SpriteParams = Barotrauma.RagdollParams.SpriteParams; using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
@@ -260,9 +261,7 @@ namespace Barotrauma
{ {
if (enableHuskSprite) if (enableHuskSprite)
{ {
List<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite }; OtherWearables.Insert(0, HuskSprite);
otherWearablesWithHusk.AddRange(OtherWearables);
OtherWearables = otherWearablesWithHusk;
UpdateWearableTypesToHide(); UpdateWearableTypesToHide();
} }
else else
@@ -546,7 +545,7 @@ namespace Barotrauma
{ {
foreach (var affliction in result.Afflictions) foreach (var affliction in result.Afflictions)
{ {
if (affliction is AfflictionBleeding) if (affliction is AfflictionBleeding bleeding && bleeding.Prefab.DamageParticles)
{ {
bleedingDamage += affliction.GetVitalityDecrease(null); bleedingDamage += affliction.GetVitalityDecrease(null);
} }
@@ -555,7 +554,7 @@ namespace Barotrauma
float damage = 0; float damage = 0;
foreach (var affliction in result.Afflictions) foreach (var affliction in result.Afflictions)
{ {
if (affliction.Prefab.AfflictionType == "damage") if (affliction.Prefab.DamageParticles && affliction.Prefab.AfflictionType == "damage")
{ {
damage += affliction.GetVitalityDecrease(null); damage += affliction.GetVitalityDecrease(null);
} }
@@ -732,7 +731,7 @@ namespace Barotrauma
bool hideLimb = Hide || bool hideLimb = Hide ||
OtherWearables.Any(w => w.HideLimb) || OtherWearables.Any(w => w.HideLimb) ||
wearingItems.Any(w => w != null && w.HideLimb); WearingItems.Any(w => w.HideLimb);
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk); bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
@@ -828,7 +827,7 @@ namespace Barotrauma
LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically; LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
} }
float step = depthStep; float step = depthStep;
WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables); WearableSprite onlyDrawable = WearingItems.Find(w => w.HideOtherWearables);
if (Params.MirrorHorizontally) if (Params.MirrorHorizontally)
{ {
spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None; spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
@@ -965,31 +964,28 @@ namespace Barotrauma
public void UpdateWearableTypesToHide() public void UpdateWearableTypesToHide()
{ {
alphaClipEffectParams?.Clear();
wearableTypeHidingSprites.Clear(); wearableTypeHidingSprites.Clear();
if (WearingItems != null && WearingItems.Count > 0)
void addWearablesFrom(IReadOnlyList<WearableSprite> wearableSprites)
{ {
if (wearableSprites.Count <= 0) { return; }
wearableTypeHidingSprites.AddRange( wearableTypeHidingSprites.AddRange(
WearingItems.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0)); wearableSprites.Where(w => w.HideWearablesOfType.Count > 0));
}
if (OtherWearables != null && OtherWearables.Count > 0)
{
wearableTypeHidingSprites.AddRange(
OtherWearables.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0));
} }
addWearablesFrom(WearingItems);
addWearablesFrom(OtherWearables);
wearableTypesToHide.Clear(); wearableTypesToHide.Clear();
if (wearableTypeHidingSprites.Count > 0)
if (wearableTypeHidingSprites.Count <= 0) { return; }
foreach (WearableSprite sprite in wearableTypeHidingSprites)
{ {
foreach (WearableSprite sprite in wearableTypeHidingSprites) wearableTypesToHide.UnionWith(sprite.HideWearablesOfType);
{
foreach (WearableType type in sprite.HideWearablesOfType)
{
if (!wearableTypesToHide.Contains(type))
{
wearableTypesToHide.Add(type);
}
}
}
} }
} }
@@ -1071,7 +1067,13 @@ namespace Barotrauma
} }
} }
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect) private (
Color FinalColor,
Vector2 Origin,
float Rotation,
float Scale,
float Depth)
CalculateDrawParameters(WearableSprite wearable, float depthStep, Color color, float alpha)
{ {
var sprite = ActiveSprite; var sprite = ActiveSprite;
if (wearable.InheritSourceRect) if (wearable.InheritSourceRect)
@@ -1163,27 +1165,118 @@ namespace Barotrauma
float finalAlpha = alpha * wearableColor.A; float finalAlpha = alpha * wearableColor.A;
Color finalColor = color.Multiply(wearableColor); Color finalColor = color.Multiply(wearableColor);
finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha); finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha);
wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
return (finalColor, origin, rotation, scale, depth);
} }
private WearableSprite GetWearableSprite(WearableType type)//, bool random = false) private static Effect alphaClipEffect;
private Dictionary<WearableSprite, Dictionary<string, object>> alphaClipEffectParams;
private void ApplyAlphaClip(SpriteBatch spriteBatch, WearableSprite wearable, WearableSprite alphaClipper, SpriteEffects spriteEffect)
{
SpriteRecorder.Command makeCommand(WearableSprite w)
{
var (_, origin, rotation, scale, _)
= CalculateDrawParameters(w, 0f, Color.White, 0f);
var command = SpriteRecorder.Command.FromTransform(
texture: w.Sprite.Texture,
pos: new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
srcRect: w.Sprite.SourceRect,
color: Color.White,
rotation: rotation,
origin: origin,
scale: new Vector2(scale, scale),
effects: spriteEffect,
depth: 0f,
index: 0);
return command;
}
void spacesFromCommand(WearableSprite w, SpriteRecorder.Command command, out CoordinateSpace2D textureSpace, out CoordinateSpace2D worldSpace)
{
var (topLeft, bottomLeft, topRight) = spriteEffect switch
{
SpriteEffects.None
=> (command.VertexTL, command.VertexBL, command.VertexTR),
SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically
=> (command.VertexBR, command.VertexTR, command.VertexBL),
SpriteEffects.FlipHorizontally
=> (command.VertexTR, command.VertexBR, command.VertexTL),
SpriteEffects.FlipVertically
=> (command.VertexBL, command.VertexTL, command.VertexBR)
};
textureSpace = new CoordinateSpace2D
{
Origin = topLeft.TextureCoordinate,
I = topRight.TextureCoordinate - topLeft.TextureCoordinate,
J = bottomLeft.TextureCoordinate - topLeft.TextureCoordinate
};
worldSpace = new CoordinateSpace2D
{
Origin = topLeft.Position.DiscardZ(),
I = topRight.Position.DiscardZ() - topLeft.Position.DiscardZ(),
J = bottomLeft.Position.DiscardZ() - topLeft.Position.DiscardZ()
};
}
var wearableCommand = makeCommand(wearable);
var clipperCommand = makeCommand(alphaClipper);
spacesFromCommand(wearable, wearableCommand, out var wearableTextureSpace, out var wearableWorldSpace);
spacesFromCommand(alphaClipper, clipperCommand, out var clipperTextureSpace, out var clipperWorldSpace);
var wearableUvToClipperUv =
wearableTextureSpace.CanonicalToLocal
* wearableWorldSpace.LocalToCanonical
* clipperWorldSpace.CanonicalToLocal
* clipperTextureSpace.LocalToCanonical;
alphaClipEffect ??= EffectLoader.Load("Effects/wearableclip");
alphaClipEffectParams ??= new Dictionary<WearableSprite, Dictionary<string, object>>();
if (!alphaClipEffectParams.ContainsKey(wearable)) { alphaClipEffectParams.Add(wearable, new Dictionary<string, object>()); }
var paramsToPass = new SpriteBatch.EffectWithParams
{
Effect = alphaClipEffect,
Params = alphaClipEffectParams[wearable]
};
paramsToPass.Params["wearableUvToClipperUv"] = wearableUvToClipperUv;
paramsToPass.Params["clipperTexelSize"] = 2f / alphaClipper.Sprite.Texture.Width;
paramsToPass.Params["aCutoff"] = 2f / 255f;
paramsToPass.Params["xTexture"] = wearable.Sprite.Texture;
paramsToPass.Params["xStencil"] = alphaClipper.Sprite.Texture;
spriteBatch.SwapEffect(paramsToPass);
}
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
{
var (finalColor, origin, rotation, scale, depth)
= CalculateDrawParameters(wearable, depthStep, color, alpha);
var prevEffect = spriteBatch.GetCurrentEffect();
var alphaClipper = WearingItems.Find(w => w.AlphaClipOtherWearables);
bool shouldApplyAlphaClip = alphaClipper != null && wearable != alphaClipper;
if (shouldApplyAlphaClip)
{
ApplyAlphaClip(spriteBatch, wearable, alphaClipper, spriteEffect);
}
wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
if (shouldApplyAlphaClip)
{
spriteBatch.SwapEffect(effect: prevEffect);
}
}
private WearableSprite GetWearableSprite(WearableType type)
{ {
var info = character.Info; var info = character.Info;
if (info == null) { return null; } if (info == null) { return null; }
ContentXElement element; ContentXElement element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet, type)?.FirstOrDefault();
/*if (random) return element != null ? new WearableSprite(element.GetChildElement("sprite"), type) : null;
{
element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet)?.GetRandom(Rand.RandSync.ClientOnly);
}
else
{*/
element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet, type)?.FirstOrDefault();
//}
if (element != null)
{
return new WearableSprite(element.GetChildElement("sprite"), type);
}
return null;
} }
partial void RemoveProjSpecific() partial void RemoveProjSpecific()
@@ -1206,8 +1299,8 @@ namespace Barotrauma
LightSource?.Remove(); LightSource?.Remove();
LightSource = null; LightSource = null;
OtherWearables?.ForEach(w => w.Sprite.Remove()); OtherWearables.ForEach(w => w.Sprite.Remove());
OtherWearables = null; OtherWearables.Clear();
HuskSprite?.Sprite.Remove(); HuskSprite?.Sprite.Remove();
HuskSprite = null; HuskSprite = null;

View File

@@ -92,7 +92,7 @@ namespace Barotrauma
public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None(); public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None();
public Option<DateTime> InstallTime = Option<DateTime>.None(); public Option<SerializableDateTime> InstallTime = Option<SerializableDateTime>.None();
public bool HasFile(File file) public bool HasFile(File file)
=> Files.Any(f => => Files.Any(f =>
@@ -120,7 +120,7 @@ namespace Barotrauma
public void DiscardHashAndInstallTime() public void DiscardHashAndInstallTime()
{ {
ExpectedHash = null; ExpectedHash = null;
InstallTime = Option<DateTime>.None(); InstallTime = Option<SerializableDateTime>.None();
} }
public static string IncrementModVersion(string modVersion) public static string IncrementModVersion(string modVersion)
@@ -159,8 +159,8 @@ namespace Barotrauma
addRootAttribute("gameversion", GameMain.Version); addRootAttribute("gameversion", GameMain.Version);
if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); } if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); }
if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); } if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); }
if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", ToolBox.Epoch.FromDateTime(installTime)); } if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", installTime); }
files.ForEach(f => rootElement.Add(f.ToXElement())); files.ForEach(f => rootElement.Add(f.ToXElement()));
doc.Add(rootElement); doc.Add(rootElement);

View File

@@ -49,7 +49,7 @@ namespace Barotrauma
&& ugcId is SteamWorkshopId workshopId && ugcId is SteamWorkshopId workshopId
&& item.Id == workshopId.Value && item.Id == workshopId.Value
&& p.InstallTime.TryUnwrap(out var installTime) && p.InstallTime.TryUnwrap(out var installTime)
&& item.LatestUpdateTime <= installTime)) && item.LatestUpdateTime <= installTime.ToUtcValue()))
.ToArray(); .ToArray();
if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); } if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); }

View File

@@ -1130,6 +1130,28 @@ namespace Barotrauma
}); });
AssignRelayToServer("debugdraw", false); AssignRelayToServer("debugdraw", false);
AssignOnExecute("devmode", (string[] args) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
{
state = !GameMain.DevMode;
}
GameMain.DevMode = state;
if (GameMain.DevMode)
{
GameMain.LightManager.LightingEnabled = false;
GameMain.LightManager.LosEnabled = false;
}
else
{
GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
}
NewMessage("Dev mode " + (GameMain.DevMode ? "enabled" : "disabled"), Color.White);
});
AssignRelayToServer("devmode", false);
AssignOnExecute("debugdrawlocalization", (string[] args) => AssignOnExecute("debugdrawlocalization", (string[] args) =>
{ {
if (args.None() || !bool.TryParse(args[0], out bool state)) if (args.None() || !bool.TryParse(args[0], out bool state))
@@ -1231,12 +1253,14 @@ namespace Barotrauma
HumanAIController.debugai = !HumanAIController.debugai; HumanAIController.debugai = !HumanAIController.debugai;
if (HumanAIController.debugai) if (HumanAIController.debugai)
{ {
GameMain.DevMode = true;
GameMain.DebugDraw = true; GameMain.DebugDraw = true;
GameMain.LightManager.LightingEnabled = false; GameMain.LightManager.LightingEnabled = false;
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
} }
else else
{ {
GameMain.DevMode = false;
GameMain.DebugDraw = false; GameMain.DebugDraw = false;
GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;

View File

@@ -8,7 +8,8 @@ partial class CheckObjectiveAction : BinaryOptionAction
public enum CheckType public enum CheckType
{ {
Added, Added,
Completed Completed,
Incomplete
} }
[Serialize(CheckType.Completed, IsPropertySaveable.Yes)] [Serialize(CheckType.Completed, IsPropertySaveable.Yes)]
@@ -30,8 +31,13 @@ partial class CheckObjectiveAction : BinaryOptionAction
{ {
CheckType.Added => true, CheckType.Added => true,
CheckType.Completed => segment.IsCompleted, CheckType.Completed => segment.IsCompleted,
CheckType.Incomplete => !segment.IsCompleted,
_ => false _ => false
}; };
} }
else if (Type == CheckType.Incomplete)
{
success = true;
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Barotrauma; namespace Barotrauma;
@@ -13,11 +14,22 @@ partial class UIHighlightAction : EventAction
bool useCircularFlash = false; bool useCircularFlash = false;
if (Id != ElementId.None) if (Id != ElementId.None)
{ {
FindAndFlashComponents(c => Equals(Id, c.UserData)); var predicate = (GUIComponent c) => c is not null && Equals(Id, c.UserData);
if (!FindAndFlashAddedComponents(predicate))
{
if (predicate(GUIMessageBox.VisibleBox))
{
Flash(GUIMessageBox.VisibleBox);
}
else
{
FindAndFlashMessageBoxComponents(predicate);
}
}
} }
else if (!EntityIdentifier.IsEmpty) else if (!EntityIdentifier.IsEmpty)
{ {
FindAndFlashComponents(c => FindAndFlashAddedComponents(c =>
c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier); c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier);
} }
else if (!OrderIdentifier.IsEmpty) else if (!OrderIdentifier.IsEmpty)
@@ -26,26 +38,26 @@ partial class UIHighlightAction : EventAction
bool foundMinimapNode = false; bool foundMinimapNode = false;
if (!OrderTargetTag.IsEmpty) if (!OrderTargetTag.IsEmpty)
{ {
foundMinimapNode = FindAndFlashComponents(c => foundMinimapNode = FindAndFlashAddedComponents(c =>
c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order && c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order &&
order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag)); order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag));
} }
if (!foundMinimapNode) if (!foundMinimapNode)
{ {
FindAndFlashComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption, FindAndFlashAddedComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption,
c => c.UserData is Order order && order.Identifier == OrderIdentifier, c => c.UserData is Order order && order.Identifier == OrderIdentifier,
c => Equals(OrderCategory, c.UserData)); c => Equals(OrderCategory, c.UserData));
} }
} }
bool FindAndFlashComponents(params Func<GUIComponent, bool>[] predicates) bool FindAndFlashComponents(IEnumerable<GUIComponent> components, params Func<GUIComponent, bool>[] predicates)
{ {
foreach (var predicate in predicates) foreach (var predicate in predicates)
{ {
if (HighlightMultiple) if (HighlightMultiple)
{ {
bool found = false; bool found = false;
foreach (var component in GUI.GetAdditions()) foreach (var component in components)
{ {
if (predicate(component)) if (predicate(component))
{ {
@@ -55,7 +67,7 @@ partial class UIHighlightAction : EventAction
}; };
return found; return found;
} }
else if (GUI.GetAdditions().FirstOrDefault(predicate) is GUIComponent component) else if (components.FirstOrDefault(predicate) is GUIComponent component)
{ {
Flash(component); Flash(component);
return true; return true;
@@ -64,6 +76,10 @@ partial class UIHighlightAction : EventAction
return false; return false;
} }
bool FindAndFlashAddedComponents(params Func<GUIComponent, bool>[] predicates) => FindAndFlashComponents(GUI.GetAdditions(), predicates);
bool FindAndFlashMessageBoxComponents(params Func<GUIComponent, bool>[] predicates) => FindAndFlashComponents(GUIMessageBox.VisibleBox?.GetAllChildren() ?? Enumerable.Empty<GUIComponent>(), predicates);
void Flash(GUIComponent component) void Flash(GUIComponent component)
{ {
if (component.FlashTimer <= 0.0f) if (component.FlashTimer <= 0.0f)

View File

@@ -1143,14 +1143,13 @@ namespace Barotrauma
bool wrap = element.GetAttributeBool("wrap", true); bool wrap = element.GetAttributeBool("wrap", true);
Alignment alignment = Alignment alignment =
element.GetAttributeEnum("alignment", text.Contains('\n') ? Alignment.Left : Alignment.Center); element.GetAttributeEnum("alignment", text.Contains('\n') ? Alignment.Left : Alignment.Center);
GUIFont font; if (!GUIStyle.Fonts.TryGetValue(element.GetAttributeIdentifier("font", "Font"), out GUIFont font))
if (!GUIStyle.Fonts.TryGetValue(element.GetAttributeIdentifier("font", "Font"), out font))
{ {
font = GUIStyle.Font; font = GUIStyle.Font;
} }
var textBlock = new GUITextBlock(RectTransform.Load(element, parent), var textBlock = new GUITextBlock(RectTransform.Load(element, parent),
text, color, font, alignment, wrap: wrap, style: style) RichString.Rich(text), color, font, alignment, wrap: wrap, style: style)
{ {
TextScale = scale TextScale = scale
}; };

View File

@@ -236,7 +236,8 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center), new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
style: "UIToggleButton") style: "UIToggleButton")
{ {
OnClicked = Close OnClicked = Close,
UserData = UIHighlightAction.ElementId.MessageBoxCloseButton
} }
}; };
InputType? closeInput = null; InputType? closeInput = null;

View File

@@ -140,6 +140,7 @@ namespace Barotrauma
public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow"); public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow");
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium"); public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium");
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned");
public static Point ItemFrameMargin public static Point ItemFrameMargin
{ {

View File

@@ -615,7 +615,7 @@ namespace Barotrauma
listBackground.SetCrop(true); listBackground.SetCrop(true);
GUIFont font = GUIStyle.Font; GUIFont font = GUIStyle.Font;
info.CreateSpecsWindow(specsFrame, font); info.CreateSpecsWindow(specsFrame, font, includeCrushDepth: true);
descriptionTextBlock.Text = info.Description; descriptionTextBlock.Text = info.Description;
descriptionTextBlock.CalculateHeightFromText(); descriptionTextBlock.CalculateHeightFromText();
} }

View File

@@ -1766,7 +1766,7 @@ namespace Barotrauma
{ {
foreach (UpgradePrefab prefab in categoryData.Prefabs) foreach (UpgradePrefab prefab in categoryData.Prefabs)
{ {
var frame = UpgradeStore.CreateUpgradeFrame(prefab, categoryData.Category, campaign, new RectTransform(new Vector2(1f, 0.3f), upgradePanel.Content.RectTransform), addBuyButton: false); var frame = UpgradeStore.CreateUpgradeFrame(prefab, categoryData.Category, campaign, new RectTransform(new Vector2(1f, 0.3f), upgradePanel.Content.RectTransform), addBuyButton: false).Frame;
UpgradeStore.UpdateUpgradeEntry(frame, prefab, categoryData.Category, campaign); UpgradeStore.UpdateUpgradeEntry(frame, prefab, categoryData.Category, campaign);
} }
} }
@@ -1779,7 +1779,10 @@ namespace Barotrauma
{ {
CurrentSelectMode = GUIListBox.SelectMode.None CurrentSelectMode = GUIListBox.SelectMode.None
}; };
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true); sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font,
includeTitle: false,
includeClass: false,
includeDescription: true);
} }
} }

View File

@@ -796,7 +796,7 @@ namespace Barotrauma
CharacterInfo? ownCharacterInfo = Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo; CharacterInfo? ownCharacterInfo = Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo;
if (ownCharacterInfo is null) { return false; } if (ownCharacterInfo is null) { return false; }
return info == ownCharacterInfo; return info.GetIdentifierUsingOriginalName() == ownCharacterInfo.GetIdentifierUsingOriginalName();
} }
public static bool CanManageTalents(CharacterInfo targetInfo) public static bool CanManageTalents(CharacterInfo targetInfo)

View File

@@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.Linq; using System.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
@@ -90,6 +89,16 @@ namespace Barotrauma
Repairs Repairs
} }
private enum UpgradeStoreUserData
{
BuyButton,
BuyButtonLayout,
ProgressBarLayout,
IncreaseLabel,
PriceLabel,
MaterialCostList
}
public UpgradeStore(CampaignUI campaignUI, GUIComponent parent) public UpgradeStore(CampaignUI campaignUI, GUIComponent parent)
{ {
WaitForServerUpdate = false; WaitForServerUpdate = false;
@@ -600,7 +609,7 @@ namespace Barotrauma
GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - repairIcon.RectTransform.RelativeSize.X, 1, contentLayout)) { Stretch = true }; GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - repairIcon.RectTransform.RelativeSize.X, 1, contentLayout)) { Stretch = true };
new GUITextBlock(rectT(1, 0, textLayout), title, font: GUIStyle.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true }; new GUITextBlock(rectT(1, 0, textLayout), title, font: GUIStyle.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true };
new GUITextBlock(rectT(1, 0, textLayout), TextManager.FormatCurrency(price)); new GUITextBlock(rectT(1, 0, textLayout), TextManager.FormatCurrency(price));
GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" }; GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = UpgradeStoreUserData.BuyButtonLayout };
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { Enabled = PlayerBalance >= price && !isDisabled, OnClicked = onPressed }; new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { Enabled = PlayerBalance >= price && !isDisabled, OnClicked = onPressed };
contentLayout.Recalculate(); contentLayout.Recalculate();
buyButtonLayout.Recalculate(); buyButtonLayout.Recalculate();
@@ -950,7 +959,7 @@ namespace Barotrauma
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite, frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite,
item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity), item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity),
currentOrPending.Description, currentOrPending.Description,
0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton")); 0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton").Frame);
if (canUninstall && frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton refundButton) if (canUninstall && frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton refundButton)
{ {
@@ -987,11 +996,11 @@ namespace Barotrauma
int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count(); int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count();
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description,
price, replacement, price, replacement,
addBuyButton: true, addBuyButton: true,
addProgressBar: false, addProgressBar: false,
buttonStyle: isPurchased ? "WeaponInstallButton" : "StoreAddToCrateButton")); buttonStyle: isPurchased ? "WeaponInstallButton" : "StoreAddToCrateButton").Frame);
if (!(frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton buyButton)) { continue; } if (!(frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton buyButton)) { continue; }
if (PlayerBalance >= price) if (PlayerBalance >= price)
@@ -1081,13 +1090,23 @@ namespace Barotrauma
}; };
} }
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) public readonly record struct BuyButtonFrame(GUILayoutGroup Layout, GUIListBox MaterialCostList, GUIButton BuyButton, GUITextBlock PriceText);
public readonly record struct ProgressBarFrame(GUITextBlock ProgressText, GUIProgressBar ProgressBar);
public readonly record struct UpgradeFrame(GUIFrame Frame,
GUIImage Icon,
GUITextBlock Name,
GUITextBlock Description,
Option<BuyButtonFrame> BuyButton,
Option<ProgressBarFrame> ProgressBar);
public static UpgradeFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{ {
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category)); return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
} }
public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, LocalizedString title, LocalizedString body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton", UpgradePrefab? upgradePrefab = null, int currentLevel = 0) public static UpgradeFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, LocalizedString title, LocalizedString body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton", UpgradePrefab? upgradePrefab = null, int currentLevel = 0)
{ {
float progressBarHeight = 0.25f; float progressBarHeight = 0.25f;
@@ -1105,21 +1124,26 @@ namespace Barotrauma
* |------------------------------------------------------------------| * |------------------------------------------------------------------|
*/ */
GUIFrame prefabFrame = new GUIFrame(parent, style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = userData }; GUIFrame prefabFrame = new GUIFrame(parent, style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = userData };
GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(0.98f, 0.95f, prefabFrame, Anchor.Center), isHorizontal: true) { Stretch = true }; GUILayoutGroup mainLayout = new GUILayoutGroup(rectT(0.98f, 0.95f, prefabFrame, Anchor.Center), isHorizontal: false);
GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center); GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(1f, addBuyButton ? 0.65f : 1f, mainLayout, Anchor.Center), isHorizontal: true) { Stretch = true };
var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false }; GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center);
GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout)); var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false };
var name = new GUITextBlock(rectT(1, 0.25f, textLayout), RichString.Rich(title), font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero }; GUILayoutGroup textLayout = new GUILayoutGroup(rectT(1f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout));
GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout)); var name = new GUITextBlock(rectT(1, 0.25f, textLayout), RichString.Rich(title), font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero };
var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero }; GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout));
GUILayoutGroup? progressLayout = null; var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
GUILayoutGroup? progressLayout = null;
GUILayoutGroup? buyButtonLayout = null; GUILayoutGroup? buyButtonLayout = null;
Option<BuyButtonFrame> buyButtonOption = Option<BuyButtonFrame>.None();
Option<ProgressBarFrame> progressBarOption = Option<ProgressBarFrame>.None();
if (addProgressBar) if (addProgressBar)
{ {
progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" }; progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = UpgradeStoreUserData.ProgressBarLayout };
new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUIStyle.Orange); GUITextBlock progressText = new GUITextBlock(rectT(0.15f, 1, progressLayout), string.Empty, font: GUIStyle.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero };
new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUIStyle.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero }; GUIProgressBar progressBar = new GUIProgressBar(rectT(0.85f, 0.75f, progressLayout), 0.0f, GUIStyle.Orange);
progressBarOption = Option.Some(new ProgressBarFrame(progressText, progressBar));
} }
if (addBuyButton) if (addBuyButton)
@@ -1127,12 +1151,33 @@ namespace Barotrauma
var formattedPrice = TextManager.FormatCurrency(Math.Abs(price)); var formattedPrice = TextManager.FormatCurrency(Math.Abs(price));
//negative price = refund //negative price = refund
if (price < 0) { formattedPrice = "+" + formattedPrice; } if (price < 0) { formattedPrice = "+" + formattedPrice; }
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; buyButtonLayout = new GUILayoutGroup(rectT(1f, 0.35f, mainLayout), isHorizontal: true) { UserData = UpgradeStoreUserData.BuyButtonLayout };;
var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center)
GUIListBox materialCostList;
if (upgradePrefab is not null)
{ {
var increaseText = new GUITextBlock(rectT(imageLayout.RectTransform.RelativeSize.X, 1f, buyButtonLayout), "", textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
{
UserData = UpgradeStoreUserData.IncreaseLabel
};
UpdateUpgradePercentageText(increaseText, upgradePrefab, currentLevel);
materialCostList = new GUIListBox(rectT(0.65f - imageLayout.RectTransform.RelativeSize.X, 1f, buyButtonLayout), isHorizontal: true, style: null);
}
else
{
materialCostList = new GUIListBox(rectT(0.65f, 1f, buyButtonLayout), isHorizontal: true, style: null);
}
materialCostList.Visible = false;
materialCostList.UserData = UpgradeStoreUserData.MaterialCostList;
var priceText = new GUITextBlock(rectT(0.2f, 1f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Right)
{
UserData = UpgradeStoreUserData.PriceLabel,
//prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades //prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades
Visible = userData is ItemPrefab Visible = userData is ItemPrefab
}; };
if (price < 0) if (price < 0)
{ {
priceText.TextColor = GUIStyle.Green; priceText.TextColor = GUIStyle.Green;
@@ -1141,15 +1186,13 @@ namespace Barotrauma
{ {
priceText.Text = string.Empty; priceText.Text = string.Empty;
} }
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) GUIButton buyButton = new GUIButton(rectT(0.15f, 1f, buyButtonLayout), string.Empty, style: buttonStyle)
{ {
UserData = UpgradeStoreUserData.BuyButton,
Enabled = false Enabled = false
}; };
if (upgradePrefab != null)
{ buyButtonOption = Option.Some(new BuyButtonFrame(buyButtonLayout, materialCostList, buyButton, priceText));
var increaseText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), "", textAlignment: Alignment.Center);
UpdateUpgradePercentageText(increaseText, upgradePrefab, currentLevel);
}
} }
description.CalculateHeightFromText(); description.CalculateHeightFromText();
@@ -1175,7 +1218,7 @@ namespace Barotrauma
progressLayout?.Recalculate(); progressLayout?.Recalculate();
buyButtonLayout?.Recalculate(); buyButtonLayout?.Recalculate();
return prefabFrame; return new UpgradeFrame(prefabFrame, icon, name, description, buyButtonOption, progressBarOption);
} }
private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel) private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel)
@@ -1197,31 +1240,21 @@ namespace Barotrauma
Submarine? sub = GameMain.GameSession?.Submarine ?? Submarine.MainSub; Submarine? sub = GameMain.GameSession?.Submarine ?? Submarine.MainSub;
if (Campaign is null || sub is null) { return; } if (Campaign is null || sub is null) { return; }
GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent)); UpgradeFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.4f, parent));
var prefabLayout = prefabFrame.GetChild<GUILayoutGroup>();
GUILayoutGroup[] childLayouts = prefabLayout.GetAllChildren<GUILayoutGroup>().ToArray(); if (!prefabFrame.BuyButton.TryUnwrap(out BuyButtonFrame buyButtonFrame)) { return; }
var imageLayout = childLayouts[0];
var icon = imageLayout.GetChild<GUIImage>();
var textLayout = childLayouts[1];
var name = textLayout.GetChild<GUITextBlock>();
GUILayoutGroup[] textChildLayouts = textLayout.GetAllChildren<GUILayoutGroup>().ToArray();
var descriptionLayout = textChildLayouts[0];
var description = descriptionLayout.GetChild<GUITextBlock>();
var progressLayout = textChildLayouts[1];
var buyButtonLayout = childLayouts[2];
var buyButton = buyButtonLayout.GetChild<GUIButton>();
if (!HasPermission || !prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab)))) if (!HasPermission || !prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
{ {
prefabFrame.Enabled = false; prefabFrame.Frame.Enabled = false;
description.Enabled = false; prefabFrame.Description.Enabled = false;
name.Enabled = false; prefabFrame.Name.Enabled = false;
icon.Color = Color.Gray; prefabFrame.Icon.Color = Color.Gray;
buyButton.Enabled = false; buyButtonFrame.BuyButton.Enabled = false;
buyButtonLayout.UserData = null; // prevent UpdateUpgradeEntry() from enabling the button buyButtonFrame.Layout.UserData = null; // prevent UpdateUpgradeEntry() from enabling the button
} }
buyButton.OnClicked += (button, o) => buyButtonFrame.BuyButton.OnClicked += (button, o) =>
{ {
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody", LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
("[upgradename]", prefab.Name), ("[upgradename]", prefab.Name),
@@ -1240,7 +1273,7 @@ namespace Barotrauma
return true; return true;
}; };
UpdateUpgradeEntry(prefabFrame, prefab, category, Campaign); UpdateUpgradeEntry(prefabFrame.Frame, prefab, category, Campaign);
} }
private void CreateItemTooltip(MapEntity entity) private void CreateItemTooltip(MapEntity entity)
@@ -1623,7 +1656,7 @@ namespace Barotrauma
int maxLevel = prefab.GetMaxLevelForCurrentSub(); int maxLevel = prefab.GetMaxLevelForCurrentSub();
LocalizedString progressText = TextManager.GetWithVariables("upgrades.progressformat", ("[level]", currentLevel.ToString()), ("[maxlevel]", maxLevel.ToString())); LocalizedString progressText = TextManager.GetWithVariables("upgrades.progressformat", ("[level]", currentLevel.ToString()), ("[maxlevel]", maxLevel.ToString()));
if (prefabFrame.FindChild("progressbar", true) is { } progressParent) if (prefabFrame.FindChild(UpgradeStoreUserData.ProgressBarLayout, true) is { } progressParent)
{ {
GUIProgressBar bar = progressParent.GetChild<GUIProgressBar>(); GUIProgressBar bar = progressParent.GetChild<GUIProgressBar>();
if (bar != null) if (bar != null)
@@ -1636,36 +1669,111 @@ namespace Barotrauma
if (block != null) { block.Text = progressText; } if (block != null) { block.Text = progressText; }
} }
if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) if (prefabFrame.FindChild(UpgradeStoreUserData.BuyButtonLayout, true) is not { } buttonParent) { return; }
GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: true);
priceLabel.Visible = true;
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (!WaitForServerUpdate)
{ {
List<GUITextBlock> textBlocks = buttonParent.GetAllChildren<GUITextBlock>().ToList(); priceLabel.Text = TextManager.FormatCurrency(price);
if (currentLevel >= maxLevel)
GUITextBlock priceLabel = textBlocks[0];
priceLabel.Visible = true;
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate)
{ {
priceLabel.Text = TextManager.FormatCurrency(price); priceLabel.Text = TextManager.Get("Upgrade.MaxedUpgrade");
if (currentLevel >= maxLevel)
{
priceLabel.Text = TextManager.Get("Upgrade.MaxedUpgrade");
}
} }
}
GUIButton button = buttonParent.GetChild<GUIButton>(); if (buttonParent.FindChild(UpgradeStoreUserData.IncreaseLabel, recursive: true) is GUITextBlock increaseLabel && !WaitForServerUpdate)
if (button != null) {
UpdateUpgradePercentageText(increaseLabel, prefab, currentLevel);
}
bool isMax = currentLevel >= maxLevel;
if (buttonParent.FindChild(UpgradeStoreUserData.BuyButton, recursive: true) is GUIButton button)
{
bool canBuy = !WaitForServerUpdate && !isMax && campaign.GetBalance() >= price && prefab.HasResourcesToUpgrade(Character.Controlled, currentLevel + 1);
button.Enabled = canBuy;
}
if (prefabFrame.FindChild(UpgradeStoreUserData.MaterialCostList, true) is GUIListBox itemList)
{
if (isMax)
{ {
button.Enabled = currentLevel < maxLevel; itemList.Visible = false;
if (WaitForServerUpdate || campaign.GetBalance() < price)
{
button.Enabled = false;
}
} }
GUITextBlock increaseLabel = textBlocks[1]; else
if (increaseLabel != null && !WaitForServerUpdate)
{ {
UpdateUpgradePercentageText(increaseLabel, prefab, currentLevel); CreateMaterialCosts(itemList, prefab, currentLevel + 1);
}
}
static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel)
{
list.Content.ClearChildren();
List<Item> allItems = Character.Controlled?.Inventory?.FindAllItems(recursive: true) ?? new List<Item>();
var resources = prefab.GetApplicableResources(targetLevel);
foreach (ApplicableResourceCollection collection in resources)
{
list.Visible = true;
int length = collection.MatchingItems.Length;
if (length is 0) { continue; }
ItemPrefab defaultItemPrefab = collection.MatchingItems.First();
GUILayoutGroup wrapperLayout = new GUILayoutGroup(rectT(0.25f, 1f, list.Content));
GUIFrame itemFrame = new GUIFrame(rectT(1f, 1f, wrapperLayout), style: null)
{
ToolTip = defaultItemPrefab.Name
};
bool hasItems = collection.Cost.Amount <= allItems.Count(collection.Cost.MatchesItem);
Sprite icon = defaultItemPrefab.InventoryIcon ?? prefab.Sprite;
Color iconColor = defaultItemPrefab.InventoryIcon is null ? defaultItemPrefab.SpriteColor : defaultItemPrefab.InventoryIconColor;
GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true)
{
Color = hasItems ? iconColor : iconColor * 0.9f,
CanBeFocused = false
};
// item count text
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), itemIcon.RectTransform, anchor: Anchor.BottomRight), $"{collection.Count}", font: GUIStyle.Font, textAlignment: Alignment.BottomRight)
{
Shadow = true,
CanBeFocused = false,
Padding = Vector4.Zero,
TextColor = hasItems ? Color.White : GUIStyle.Red,
};
if (length is 1) { continue; }
// we have more than 1 item, show a "slideshow" of the items
float index = 0f;
GUICustomComponent customComponent = new GUICustomComponent(rectT(1f, 1f, itemFrame), null, (deltaTime, component) =>
{
index += deltaTime / 3f;
if (index > length) { index = 0; }
ItemPrefab currentPrefab = collection.MatchingItems[(int)MathF.Floor(index)];
Sprite icon = currentPrefab.InventoryIcon ?? prefab.Sprite;
Color iconColor = currentPrefab.InventoryIcon is null ? currentPrefab.SpriteColor : currentPrefab.InventoryIconColor;
itemIcon.Sprite = icon;
itemIcon.Color = hasItems ? iconColor : iconColor * 0.9f;
itemFrame.ToolTip = currentPrefab.Name;
})
{
CanBeFocused = false
};
} }
} }
} }

View File

@@ -17,16 +17,20 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using System.Collections.Immutable;
namespace Barotrauma namespace Barotrauma
{ {
class GameMain : Game class GameMain : Game
{ {
internal static LuaCsSetup LuaCs; public static LuaCsSetup LuaCs;
public static bool ShowFPS;
public static bool ShowFPS = false; public static bool ShowPerf;
public static bool ShowPerf = false;
public static bool DebugDraw; public static bool DebugDraw;
/// <summary>
/// Doesn't automatically enable los or bot AI or do anything like that. Probably not fully implemented.
/// </summary>
public static bool DevMode;
public static bool IsSingleplayer => NetworkMember == null; public static bool IsSingleplayer => NetworkMember == null;
public static bool IsMultiplayer => NetworkMember != null; public static bool IsMultiplayer => NetworkMember != null;
@@ -401,7 +405,7 @@ namespace Barotrauma
TextureLoader.Init(GraphicsDevice); TextureLoader.Init(GraphicsDevice);
//do this here because we need it for the loading screen //do this here because we need it for the loading screen
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content); WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice);
Quad.Init(GraphicsDevice); Quad.Init(GraphicsDevice);
@@ -479,6 +483,19 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
var corePackage = ContentPackageManager.EnabledPackages.Core;
if (corePackage.EnableError.TryUnwrap(out var error))
{
if (error.ErrorsOrException.TryGet(out ImmutableArray<string> errorMessages))
{
throw new Exception($"Error while loading the core content package \"{corePackage.Name}\": {errorMessages.First()}");
}
else if (error.ErrorsOrException.TryGet(out Exception exception))
{
throw new Exception($"Error while loading the core content package \"{corePackage.Name}\": {exception.Message}", exception);
}
}
TextManager.VerifyLanguageAvailable(); TextManager.VerifyLanguageAvailable();
DebugConsole.Init(); DebugConsole.Init();
@@ -502,10 +519,10 @@ namespace Barotrauma
TitleScreen.LoadState = 75.0f; TitleScreen.LoadState = 75.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice, Content); GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice);
ParticleManager = new ParticleManager(GameScreen.Cam); ParticleManager = new ParticleManager(GameScreen.Cam);
LightManager = new Lights.LightManager(base.GraphicsDevice, Content); LightManager = new Lights.LightManager(base.GraphicsDevice);
TitleScreen.LoadState = 80.0f; TitleScreen.LoadState = 80.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -744,8 +761,8 @@ namespace Barotrauma
{ {
Client.Quit(); Client.Quit();
Client = null; Client = null;
MainMenuScreen.Select();
} }
MainMenuScreen.Select();
if (connectCommand.EndpointOrLobby.TryGet(out ulong lobbyId)) if (connectCommand.EndpointOrLobby.TryGet(out ulong lobbyId))
{ {
@@ -1114,37 +1131,6 @@ namespace Barotrauma
GameMain.LuaCs.Stop(); GameMain.LuaCs.Stop();
} }
public void ShowEditorDisclaimer()
{
var msgBox = new GUIMessageBox(TextManager.Get("EditorDisclaimerTitle"), TextManager.Get("EditorDisclaimerText"));
var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f };
linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height);
List<(LocalizedString Caption, string Url)> links = new List<(LocalizedString, string)>()
{
(TextManager.Get("EditorDisclaimerWikiLink"), TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value),
(TextManager.Get("EditorDisclaimerDiscordLink"), TextManager.Get("EditorDisclaimerDiscordUrl").Fallback("https://discordapp.com/invite/undertow").Value),
};
foreach (var link in links)
{
new GUIButton(new RectTransform(new Vector2(1.0f, 0.2f), linkHolder.RectTransform), link.Caption, style: "MainMenuGUIButton", textAlignment: Alignment.Left)
{
UserData = link.Url,
OnClicked = (btn, userdata) =>
{
ShowOpenUrlInWebBrowserPrompt(userdata as string);
return true;
}
};
}
msgBox.InnerFrame.RectTransform.MinSize = new Point(0,
msgBox.InnerFrame.Rect.Height + linkHolder.Rect.Height + msgBox.Content.AbsoluteSpacing * 2 + 10);
var config = GameSettings.CurrentConfig;
config.EditorDisclaimerShown = true;
GameSettings.SetCurrentConfig(config);
GameSettings.SaveCurrentConfig();
}
public void ShowBugReporter() public void ShowBugReporter()
{ {
if (GUIMessageBox.VisibleBox != null && GUIMessageBox.VisibleBox.UserData as string == "bugreporter") if (GUIMessageBox.VisibleBox != null && GUIMessageBox.VisibleBox.UserData as string == "bugreporter")

View File

@@ -788,7 +788,6 @@ namespace Barotrauma
{ {
return; return;
} }
if (ws != null) if (ws != null)
{ {
hull = Hull.FindHull(ws.WorldPosition); hull = Hull.FindHull(ws.WorldPosition);
@@ -802,7 +801,6 @@ namespace Barotrauma
hull = Hull.FindHull(se.WorldPosition); hull = Hull.FindHull(se.WorldPosition);
} }
} }
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
order.OrderGiver?.Speak(order.GetChatMessage("", hull?.DisplayName?.Value, givingOrderToSelf: character == order.OrderGiver, isNewOrder: isNewOrder), ChatMessageType.Order); order.OrderGiver?.Speak(order.GetChatMessage("", hull?.DisplayName?.Value, givingOrderToSelf: character == order.OrderGiver, isNewOrder: isNewOrder), ChatMessageType.Order);
@@ -817,13 +815,13 @@ namespace Barotrauma
{ {
//can't issue an order if no characters are available //can't issue an order if no characters are available
if (character == null) { return; } if (character == null) { return; }
var orderGiver = order?.OrderGiver; var orderGiver = order?.OrderGiver;
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
character.SetOrder(order, isNewOrder, speak: orderGiver != character); bool isGivingOrderToSelf = orderGiver == character;
string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName?.Value, givingOrderToSelf: character == orderGiver, orderOption: order?.Option ?? Identifier.Empty, isNewOrder: isNewOrder); character.SetOrder(order, isNewOrder, speak: !isGivingOrderToSelf);
orderGiver?.Speak(message); string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName?.Value, isGivingOrderToSelf, orderOption: order?.Option ?? Identifier.Empty, isNewOrder: isNewOrder);
orderGiver?.Speak(message);
} }
else if (orderGiver != null) else if (orderGiver != null)
{ {

View File

@@ -106,12 +106,7 @@ namespace Barotrauma
public static bool AllowedToManageWallets() public static bool AllowedToManageWallets()
{ {
if (GameMain.Client == null) { return true; } return AllowedToManageCampaign(ClientPermissions.ManageMoney);
return
GameMain.Client.HasPermission(ClientPermissions.ManageMoney) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.IsServerOwner;
} }
public override void Draw(SpriteBatch spriteBatch) public override void Draw(SpriteBatch spriteBatch)

View File

@@ -965,7 +965,7 @@ namespace Barotrauma
break; break;
case QuickUseAction.PutToEquippedItem: case QuickUseAction.PutToEquippedItem:
//order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank //order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank
foreach (Item heldItem in character.HeldItems.OrderBy(it => it.ContainedItems.FirstOrDefault()?.Condition ?? 0.0f)) foreach (Item heldItem in character.HeldItems.OrderByDescending(heldItem => GetContainPriority(item, heldItem)))
{ {
if (heldItem.OwnInventory == null) { continue; } if (heldItem.OwnInventory == null) { continue; }
//don't allow swapping if we're moving items into an item with 1 slot holding a stack of items //don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
@@ -986,6 +986,22 @@ namespace Barotrauma
} }
} }
break; break;
static float GetContainPriority(Item item, Item containerItem)
{
var container = containerItem.GetComponent<ItemContainer>();
if (container == null) { return 0.0f; }
for (int i = 0; i < container.Inventory.Capacity; i++)
{
var containedItems = container.Inventory.GetItemsAt(i);
if (containedItems.Any() && container.Inventory.CanBePutInSlot(item, i))
{
//if there's a stack in the contained item that we can add the item to, prefer that
return 10.0f;
}
}
return -container.GetContainedIndicatorState();
}
} }
if (success) if (success)

View File

@@ -216,16 +216,19 @@ namespace Barotrauma.Items.Components
if (brokenSprite == null || !IsBroken) if (brokenSprite == null || !IsBroken)
{ {
spriteBatch.Draw(doorSprite.Texture, pos, if (doorSprite?.Texture != null)
getSourceRect(doorSprite, openState, IsHorizontal), {
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth); spriteBatch.Draw(doorSprite.Texture, pos,
getSourceRect(doorSprite, openState, IsHorizontal),
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth);
}
} }
float maxCondition = item.Repairables.Any() ? float maxCondition = item.Repairables.Any() ?
item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition : item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition :
item.MaxCondition; item.MaxCondition;
float healthRatio = item.Health / maxCondition; float healthRatio = item.Health / maxCondition;
if (brokenSprite != null && healthRatio < 1.0f) if (brokenSprite?.Texture != null && healthRatio < 1.0f)
{ {
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One; Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One;
if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; } if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; }
@@ -285,34 +288,45 @@ namespace Barotrauma.Items.Components
//sent by the server, or reverting it back to its old state if no msg from server was received //sent by the server, or reverting it back to its old state if no msg from server was received
PredictedState = open; PredictedState = open;
resetPredictionTimer = CorrectionDelay; resetPredictionTimer = CorrectionDelay;
if (stateChanged) PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse); if (stateChanged && !IsBroken)
{
PlayInteractionSound();
}
} }
else else
{ {
isOpen = open; isOpen = open;
if (!isNetworkMessage || open != PredictedState) if (!isNetworkMessage || open != PredictedState)
{ {
StopPicking(null); StopPicking(null);
ActionType actionType = ActionType.OnUse; if (!IsBroken)
if (forcedOpen)
{ {
actionType = ActionType.OnPicked; PlayInteractionSound();
} }
else
{
if (open && HasSoundsOfType[(int)ActionType.OnOpen])
{
actionType = ActionType.OnOpen;
}
else if (!open && HasSoundsOfType[(int)ActionType.OnClose])
{
actionType = ActionType.OnClose;
}
}
PlaySound(actionType);
if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); } if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); }
} }
} }
void PlayInteractionSound()
{
ActionType actionType = ActionType.OnUse;
if (forcedOpen)
{
actionType = ActionType.OnPicked;
}
else
{
if (open && HasSoundsOfType[(int)ActionType.OnOpen])
{
actionType = ActionType.OnOpen;
}
else if (!open && HasSoundsOfType[(int)ActionType.OnClose])
{
actionType = ActionType.OnClose;
}
}
PlaySound(actionType);
}
} }
public override void ClientEventRead(IReadMessage msg, float sendingTime) public override void ClientEventRead(IReadMessage msg, float sendingTime)

View File

@@ -55,7 +55,7 @@ namespace Barotrauma.Items.Components
public void DrawElectricity(SpriteBatch spriteBatch) public void DrawElectricity(SpriteBatch spriteBatch)
{ {
if (timer <= 0.0f) { return; } if (timer <= 0.0f && Screen.Selected is { IsEditor: false }) { return; }
for (int i = 0; i < nodes.Count; i++) for (int i = 0; i < nodes.Count; i++)
{ {
if (nodes[i].Length <= 1.0f) { continue; } if (nodes[i].Length <= 1.0f) { continue; }

View File

@@ -1,7 +1,9 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections;
using System.Linq; using System.Linq;
using static Barotrauma.Inventory;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -250,6 +252,83 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
public float GetContainedIndicatorState()
{
if (ShowConditionInContainedStateIndicator)
{
return item.Condition / item.MaxCondition;
}
int targetSlot = Math.Max(ContainedStateIndicatorSlot, 0);
if (targetSlot >= Inventory.Capacity) { return 0.0f; }
var containedItems = Inventory.GetItemsAt(targetSlot);
if (containedItems == null) { return 0.0f; }
Item containedItem = containedItems.FirstOrDefault();
if (ShowTotalStackCapacityInContainedStateIndicator)
{
// No item on the defined slot, check if the items on other slots can be used.
containedItem ??=
containedItems.FirstOrDefault() ??
Inventory.AllItems.FirstOrDefault(it => CanBeContained(it, targetSlot));
if (containedItem == null) { return 0.0f; }
int ignoredItemCount = 0;
var subContainableItems = AllSubContainableItems;
float capacity = GetMaxStackSize(targetSlot);
if (subContainableItems != null)
{
bool useMainContainerCapacity = true;
foreach (Item it in Inventory.AllItems)
{
// Ignore all items in the sub containers.
foreach (RelatedItem ri in subContainableItems)
{
if (ri.MatchesItem(containedItem))
{
// The target item is in a subcontainer -> inverse the logic.
useMainContainerCapacity = false;
break;
}
if (ri.MatchesItem(it))
{
ignoredItemCount++;
}
}
if (!useMainContainerCapacity) { break; }
}
if (useMainContainerCapacity)
{
capacity *= MainContainerCapacity;
}
else
{
// Ignore all items in the main container.
ignoredItemCount = Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
capacity *= Capacity - MainContainerCapacity;
}
}
int itemCount = Inventory.AllItems.Count() - ignoredItemCount;
return Math.Min(itemCount / Math.Max(capacity, 1), 1);
}
else
{
if (containedItem != null && (Inventory.Capacity == 1 || HasSubContainers))
{
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, GetMaxStackSize(targetSlot));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{
return containedItems.Count() / (float)maxStackSize;
}
}
return Inventory.Capacity == 1 || ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
Inventory.EmptySlotCount / (float)Inventory.Capacity;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (hideItems || (item.body != null && !item.body.Enabled)) { return; } if (hideItems || (item.body != null && !item.body.Enabled)) { return; }

View File

@@ -14,8 +14,6 @@ namespace Barotrauma.Items.Components
private CoroutineHandle resetPredictionCoroutine; private CoroutineHandle resetPredictionCoroutine;
private float resetPredictionTimer; private float resetPredictionTimer;
private float currentBrightness;
public Vector2 DrawSize public Vector2 DrawSize
{ {
get { return new Vector2(Light.Range * 2, Light.Range * 2); } get { return new Vector2(Light.Range * 2, Light.Range * 2); }
@@ -29,14 +27,21 @@ namespace Barotrauma.Items.Components
Light.Position = ParentBody != null ? ParentBody.Position : item.Position; Light.Position = ParentBody != null ? ParentBody.Position : item.Position;
} }
partial void SetLightSourceState(bool enabled, float brightness) partial void SetLightSourceState(bool enabled, float? brightness)
{ {
if (Light == null) { return; } if (Light == null) { return; }
Light.Enabled = enabled; Light.Enabled = enabled;
currentBrightness = brightness; if (brightness.HasValue)
{
lightBrightness = brightness.Value;
}
else
{
lightBrightness = enabled ? 1.0f : 0.0f;
}
if (enabled) if (enabled)
{ {
Light.Color = LightColor.Multiply(brightness); Light.Color = LightColor.Multiply(lightBrightness);
} }
} }
@@ -73,14 +78,21 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) if (Light?.LightSprite == null) { return; }
if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
{ {
Vector2 origin = Light.LightSprite.Origin; Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; } if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; }
Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition; Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition;
Light.LightSprite.Draw(spriteBatch, new Vector2(drawPos.X, -drawPos.Y), lightColor * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f);
Color color = lightColor;
if (Light.OverrideLightSpriteAlpha.HasValue)
{
color = new Color(lightColor, Light.OverrideLightSpriteAlpha.Value);
}
Light.LightSprite.Draw(spriteBatch, new Vector2(drawPos.X, -drawPos.Y), color * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f);
} }
} }

View File

@@ -413,7 +413,7 @@ namespace Barotrauma.Items.Components
var wire = it.GetComponent<Wire>(); var wire = it.GetComponent<Wire>();
if (wire != null && wire.Connections.Any(c => c != null)) { return false; } if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false }) { return false; } if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
if (it.HasTag("traitormissionitem")) { return false; } if (it.HasTag("traitormissionitem")) { return false; }
@@ -519,7 +519,10 @@ namespace Barotrauma.Items.Components
Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green; Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green;
weaponSprite.Draw(batch, center, color, origin, rotation, scale, SpriteEffects.None); weaponSprite.Draw(batch, center, color, origin, rotation, scale, SpriteEffects.None);
} }
}); })
{
CanBeFocused = false
};
weaponChilds.Add(component, frame); weaponChilds.Add(component, frame);
} }

View File

@@ -72,7 +72,9 @@ namespace Barotrauma.Items.Components
public override bool RecreateGUIOnResolutionChange => true; public override bool RecreateGUIOnResolutionChange => true;
public bool TriggerInfographic { get; set; } public bool TriggerInfographic { get; set; }
public bool IsInfographicVisible => infographic != null && infographic.Visible;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
CreateGUI(); CreateGUI();
@@ -108,6 +110,9 @@ namespace Barotrauma.Items.Components
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, { AbsoluteOffset = GUIStyle.ItemFrameOffset },
isHorizontal: true) isHorizontal: true)
{ {
CanBeFocused = true,
HoverCursor = CursorState.Default,
AlwaysOverrideCursor = true,
RelativeSpacing = 0.012f, RelativeSpacing = 0.012f,
Stretch = true Stretch = true
}; };
@@ -675,7 +680,7 @@ namespace Barotrauma.Items.Components
} }
} }
if (TriggerInfographic) if (GuiFrame is not null && GuiFrame.Visible && TriggerInfographic)
{ {
CreateInfrographic(); CreateInfrographic();
TriggerInfographic = false; TriggerInfographic = false;
@@ -851,8 +856,9 @@ namespace Barotrauma.Items.Components
{ {
AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale) AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale)
}; };
new GUIButton(closeButtonRt, TextManager.Get("close")) new GUIButton(closeButtonRt, TextManager.Get("closeinfographic"))
{ {
UserData = UIHighlightAction.ElementId.CloseButton,
OnClicked = (_, _) => OnClicked = (_, _) =>
{ {
CloseInfographic(Character.Controlled); CloseInfographic(Character.Controlled);
@@ -871,6 +877,7 @@ namespace Barotrauma.Items.Components
string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved"; string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved";
return new GUIImage(rt, style) return new GUIImage(rt, style)
{ {
CanBeFocused = false,
Rotation = MathHelper.ToRadians(rotationDegrees), Rotation = MathHelper.ToRadians(rotationDegrees),
SpriteEffects = spriteEffects SpriteEffects = spriteEffects
}; };

View File

@@ -329,6 +329,7 @@ namespace Barotrauma.Items.Components
partial void UpdateSignalsProjSpecific() partial void UpdateSignalsProjSpecific()
{ {
if (signals == null) { return; }
for (int i = 0; i < signals.Length && i < uiElements.Count; i++) for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
{ {
if (uiElements[i] is GUITextBox tb) if (uiElements[i] is GUITextBox tb)

View File

@@ -25,14 +25,14 @@ namespace Barotrauma.Items.Components
public static Color editorHighlightColor = Color.Yellow; public static Color editorHighlightColor = Color.Yellow;
public static Color editorSelectedColor = Color.Red; public static Color editorSelectedColor = Color.Red;
partial class WireSection public partial class WireSection
{ {
public VertexPositionColorTexture[] vertices; public VertexPositionColorTexture[] vertices;
public VertexPositionColorTexture[] shiftedVertices; public VertexPositionColorTexture[] shiftedVertices;
private float cachedWidth = 0f; private float cachedWidth = 0f;
private void RecalculateVertices(Wire wire, float width) private void RecalculateVertices(Sprite wireSprite, float width)
{ {
if (MathUtils.NearlyEqual(cachedWidth, width)) { return; } if (MathUtils.NearlyEqual(cachedWidth, width)) { return; }
cachedWidth = width; cachedWidth = width;
@@ -45,13 +45,13 @@ namespace Barotrauma.Items.Components
expandDir.X = -expandDir.Y; expandDir.X = -expandDir.Y;
expandDir.Y = -temp; expandDir.Y = -temp;
Rectangle srcRect = wire.wireSprite.SourceRect; Rectangle srcRect = wireSprite.SourceRect;
expandDir *= width * srcRect.Height * 0.5f; expandDir *= width * srcRect.Height * 0.5f;
Vector2 rectLocation = srcRect.Location.ToVector2(); Vector2 rectLocation = srcRect.Location.ToVector2();
Vector2 rectSize = srcRect.Size.ToVector2(); Vector2 rectSize = srcRect.Size.ToVector2();
Vector2 textureSize = new Vector2(wire.wireSprite.Texture.Width, wire.wireSprite.Texture.Height); Vector2 textureSize = new Vector2(wireSprite.Texture.Width, wireSprite.Texture.Height);
Vector2 topLeftUv = rectLocation / textureSize; Vector2 topLeftUv = rectLocation / textureSize;
Vector2 bottomRightUv = (rectLocation + rectSize) / textureSize; Vector2 bottomRightUv = (rectLocation + rectSize) / textureSize;
@@ -67,10 +67,10 @@ namespace Barotrauma.Items.Components
shiftedVertices = (VertexPositionColorTexture[])vertices.Clone(); shiftedVertices = (VertexPositionColorTexture[])vertices.Clone();
} }
public void Draw(SpriteBatch spriteBatch, Wire wire, Color color, Vector2 offset, float depth, float width = 0.3f) public void Draw(ISpriteBatch spriteBatch, Sprite wireSprite, Color color, Vector2 offset, float depth, float width = 0.3f)
{ {
if (width <= 0f) { return; } if (width <= 0f) { return; }
RecalculateVertices(wire, width); RecalculateVertices(wireSprite, width);
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
@@ -79,21 +79,22 @@ namespace Barotrauma.Items.Components
shiftedVertices[i].Position.X += offset.X; shiftedVertices[i].Position.X += offset.X;
shiftedVertices[i].Position.Y -= offset.Y; shiftedVertices[i].Position.Y -= offset.Y;
} }
spriteBatch.Draw(wire.wireSprite.Texture, spriteBatch.Draw(
wireSprite.Texture,
shiftedVertices, shiftedVertices,
depth); depth);
} }
public static void Draw(SpriteBatch spriteBatch, Wire wire, Vector2 start, Vector2 end, Color color, float depth, float width = 0.3f) public static void Draw(ISpriteBatch spriteBatch, Sprite wireSprite, Vector2 start, Vector2 end, Color color, float depth, float width = 0.3f)
{ {
start.Y = -start.Y; start.Y = -start.Y;
end.Y = -end.Y; end.Y = -end.Y;
spriteBatch.Draw(wire.wireSprite.Texture, spriteBatch.Draw(wireSprite.Texture,
start, wire.wireSprite.SourceRect, color, start, wireSprite.SourceRect, color,
MathUtils.VectorToAngle(end - start), MathUtils.VectorToAngle(end - start),
new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f), new Vector2(0.0f, wireSprite.size.Y / 2.0f),
new Vector2((Vector2.Distance(start, end)) / wire.wireSprite.size.X, width), new Vector2((Vector2.Distance(start, end)) / wireSprite.size.X, width),
SpriteEffects.None, SpriteEffects.None,
depth); depth);
} }
@@ -123,7 +124,7 @@ namespace Barotrauma.Items.Components
get => draggingWire; get => draggingWire;
} }
partial void InitProjSpecific(ContentXElement element) public static Sprite ExtractWireSprite(ContentXElement element)
{ {
if (defaultWireSprite == null) if (defaultWireSprite == null)
{ {
@@ -133,6 +134,7 @@ namespace Barotrauma.Items.Components
}; };
} }
Sprite overrideSprite = null;
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
if (subElement.Name.ToString().Equals("wiresprite", StringComparison.OrdinalIgnoreCase)) if (subElement.Name.ToString().Equals("wiresprite", StringComparison.OrdinalIgnoreCase))
@@ -142,9 +144,14 @@ namespace Barotrauma.Items.Components
} }
} }
wireSprite = overrideSprite ?? defaultWireSprite; return overrideSprite ?? defaultWireSprite;
}
partial void InitProjSpecific(ContentXElement element)
{
wireSprite = ExtractWireSprite(element);
if (wireSprite != defaultWireSprite) { overrideSprite = wireSprite; }
} }
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
@@ -181,20 +188,20 @@ namespace Barotrauma.Items.Components
{ {
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f); section.Draw(spriteBatch, wireSprite, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f);
} }
} }
else if (item.IsSelected) else if (item.IsSelected)
{ {
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, editorSelectedColor, drawOffset, depth + 0.00001f, Width * 2.0f); section.Draw(spriteBatch, wireSprite, editorSelectedColor, drawOffset, depth + 0.00001f, Width * 2.0f);
} }
} }
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, item.Color, drawOffset, depth, Width); section.Draw(spriteBatch, wireSprite, item.Color, drawOffset, depth, Width);
} }
if (nodes.Count > 0) if (nodes.Count > 0)
@@ -239,13 +246,13 @@ namespace Barotrauma.Items.Components
} }
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, nodes[^1] + drawOffset,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.Color, 0.0f, Width); item.Color, 0.0f, Width);
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, itemDepth, Width); item.Color, itemDepth, Width);
@@ -255,8 +262,8 @@ namespace Barotrauma.Items.Components
else else
{ {
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, nodes[^1] + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, 0.0f, Width); item.Color, 0.0f, Width);
} }
@@ -294,12 +301,12 @@ namespace Barotrauma.Items.Components
Vector2 endPos = start + new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)) * 50.0f; Vector2 endPos = start + new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)) * 50.0f;
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
start, endPos, start, endPos,
GUIStyle.Orange, depth + 0.00001f, 0.2f); GUIStyle.Orange, depth + 0.00001f, 0.2f);
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
start, start + (endPos - start) * 0.7f, start, start + (endPos - start) * 0.7f,
item.Color, depth, 0.3f); item.Color, depth, 0.3f);
} }

View File

@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
{ {
private static void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier) private static void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier)
{ {
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100); int roundedValue = (int)Math.Round((1 - Math.Min(damageModifier.DamageMultiplier, damageModifier.ProbabilityMultiplier)) * 100);
if (roundedValue == 0) { return; } if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green); string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green);
@@ -18,7 +18,7 @@ namespace Barotrauma.Items.Components
TextManager.Get($"afflictiontype.{afflictionIdentifier}").Fallback(afflictionIdentifier.Value); TextManager.Get($"afflictiontype.{afflictionIdentifier}").Fallback(afflictionIdentifier.Value);
if (!description.IsNullOrWhiteSpace()) { description += '\n'; } if (!description.IsNullOrWhiteSpace()) { description += '\n'; }
description += $" ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}"; description += $" ‖color:{colorStr}‖{roundedValue:-0;+#}%‖color:end‖ {afflictionName}";
} }
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
@@ -36,7 +36,6 @@ namespace Barotrauma.Items.Components
{ {
continue; continue;
} }
foreach (Identifier afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers) foreach (Identifier afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers)
{ {
GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); GetDamageModifierText(ref description, damageModifier, afflictionIdentifier);

View File

@@ -1603,88 +1603,7 @@ namespace Barotrauma
if (itemContainer != null && itemContainer.ShowContainedStateIndicator && itemContainer.Capacity > 0) if (itemContainer != null && itemContainer.ShowContainedStateIndicator && itemContainer.Capacity > 0)
{ {
float containedState = 0.0f; float containedState = itemContainer.GetContainedIndicatorState();
if (itemContainer.ShowConditionInContainedStateIndicator)
{
containedState = item.Condition / item.MaxCondition;
}
else
{
int targetSlot = Math.Max(itemContainer.ContainedStateIndicatorSlot, 0);
ItemSlot containedItemSlot = null;
if (targetSlot < itemContainer.Inventory.slots.Length)
{
containedItemSlot = itemContainer.Inventory.slots[targetSlot];
}
if (containedItemSlot != null)
{
Item containedItem = containedItemSlot.FirstOrDefault();
if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
{
if (containedItem == null)
{
// No item on the defined slot, check if the items on other slots can be used.
containedItem = containedItemSlot.FirstOrDefault() ?? itemContainer.Inventory.AllItems.FirstOrDefault(it => itemContainer.CanBeContained(it, targetSlot));
}
if (containedItem != null)
{
int ignoredItemCount = 0;
var subContainableItems = itemContainer.AllSubContainableItems;
float capacity = itemContainer.GetMaxStackSize(targetSlot);
if (subContainableItems != null)
{
bool useMainContainerCapacity = true;
foreach (Item it in itemContainer.Inventory.AllItems)
{
// Ignore all items in the sub containers.
foreach (RelatedItem ri in subContainableItems)
{
if (ri.MatchesItem(containedItem))
{
// The target item is in a subcontainer -> inverse the logic.
useMainContainerCapacity = false;
break;
}
if (ri.MatchesItem(it))
{
ignoredItemCount++;
}
}
if (!useMainContainerCapacity) { break; }
}
if (useMainContainerCapacity)
{
capacity *= itemContainer.MainContainerCapacity;
}
else
{
// Ignore all items in the main container.
ignoredItemCount = itemContainer.Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
capacity *= itemContainer.Capacity - itemContainer.MainContainerCapacity;
}
}
int itemCount = itemContainer.Inventory.AllItems.Count() - ignoredItemCount;
containedState = Math.Min(itemCount / Math.Max(capacity, 1), 1);
}
}
else
{
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && (itemContainer.Inventory.Capacity == 1 || itemContainer.HasSubContainers))
{
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(targetSlot));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{
containedState = containedItemSlot.Items.Count / (float)maxStackSize;
}
}
}
}
}
int dir = slot.SubInventoryDir; int dir = slot.SubInventoryDir;
Rectangle containedIndicatorArea = new Rectangle(rect.X, Rectangle containedIndicatorArea = new Rectangle(rect.X,
dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - ContainedIndicatorHeight, rect.Width, ContainedIndicatorHeight); dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - ContainedIndicatorHeight, rect.Width, ContainedIndicatorHeight);
@@ -1794,6 +1713,15 @@ namespace Barotrauma
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White); GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
} }
} }
if (HealingCooldown.IsOnCooldown && item.HasTag(HealingCooldown.MedicalItemTag))
{
RectangleF cdRect = rect;
// shrink the rect from top to bottom depending on HealingCooldown.NormalizedCooldown
cdRect.Height *= HealingCooldown.NormalizedCooldown;
cdRect.Y += rect.Height;
GUI.DrawFilledRectangle(spriteBatch, cdRect, Color.White * 0.5f);
}
} }
if (inventory != null && if (inventory != null &&
@@ -1807,6 +1735,7 @@ namespace Barotrauma
} }
} }
private static void DrawItemStateIndicator( private static void DrawItemStateIndicator(
SpriteBatch spriteBatch, Inventory inventory, SpriteBatch spriteBatch, Inventory inventory,
Sprite indicatorSprite, Sprite emptyIndicatorSprite, Rectangle containedIndicatorArea, float containedState, Sprite indicatorSprite, Sprite emptyIndicatorSprite, Rectangle containedIndicatorArea, float containedState,

View File

@@ -203,7 +203,7 @@ namespace Barotrauma
} }
} }
partial void InitProjSpecific() public void InitSpriteStates()
{ {
Prefab.Sprite?.EnsureLazyLoaded(); Prefab.Sprite?.EnsureLazyLoaded();
Prefab.InventoryIcon?.EnsureLazyLoaded(); Prefab.InventoryIcon?.EnsureLazyLoaded();
@@ -211,7 +211,6 @@ namespace Barotrauma
{ {
brokenSprite.Sprite.EnsureLazyLoaded(); brokenSprite.Sprite.EnsureLazyLoaded();
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
decorativeSprite.Sprite.EnsureLazyLoaded(); decorativeSprite.Sprite.EnsureLazyLoaded();
@@ -221,6 +220,11 @@ namespace Barotrauma
UpdateSpriteStates(0.0f); UpdateSpriteStates(0.0f);
} }
partial void InitProjSpecific()
{
InitSpriteStates();
}
private Rectangle? cachedVisibleExtents; private Rectangle? cachedVisibleExtents;
public void ResetCachedVisibleSize() public void ResetCachedVisibleSize()
@@ -1409,7 +1413,7 @@ namespace Barotrauma
if (targetComponent == null) if (targetComponent == null)
{ {
ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, true, worldPosition: worldPosition); ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, isNetworkEvent: true, worldPosition: worldPosition);
} }
else else
{ {

View File

@@ -236,6 +236,16 @@ namespace Barotrauma
DecorativeSprites = decorativeSprites.ToImmutableArray(); DecorativeSprites = decorativeSprites.ToImmutableArray();
ContainedSprites = containedSprites.ToImmutableArray(); ContainedSprites = containedSprites.ToImmutableArray();
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary(); DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
#if CLIENT
foreach (Item item in Item.ItemList)
{
if (item.Prefab == this)
{
item.InitSpriteStates();
}
}
#endif
} }
public bool CanCharacterBuy() public bool CanCharacterBuy()
@@ -260,16 +270,16 @@ namespace Barotrauma
public override void UpdatePlacing(Camera cam) public override void UpdatePlacing(Camera cam)
{ {
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (PlayerInput.SecondaryMouseButtonClicked()) if (PlayerInput.SecondaryMouseButtonClicked())
{ {
Selected = null; Selected = null;
return; return;
} }
var potentialContainer = MapEntity.GetPotentialContainer(cam.ScreenToWorld(PlayerInput.MousePosition));
var potentialContainer = MapEntity.GetPotentialContainer(position); Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (!ResizeHorizontal && !ResizeVertical) if (!ResizeHorizontal && !ResizeVertical)
{ {
if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null) if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)

View File

@@ -293,11 +293,11 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y), new Vector2(drawRect.X, -drawRect.Y),
new Vector2(rect.Width, rect.Height), new Vector2(rect.Width, rect.Height),
Color.Blue * alpha, false, (ID % 255) * 0.000001f, (int)Math.Max(1.5f / Screen.Selected.Cam.Zoom, 1.0f)); Color.Blue * alpha, false, (ID % 255) * 0.000001f, (int)Math.Max(MathF.Ceiling(1.5f / Screen.Selected.Cam.Zoom), 1.0f));
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height), new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height),
GUIStyle.Red * ((100.0f - OxygenPercentage) / 400.0f) * alpha, true, 0, (int)Math.Max(1.5f / Screen.Selected.Cam.Zoom, 1.0f)); GUIStyle.Red * ((100.0f - OxygenPercentage) / 400.0f) * alpha, true, 0, (int)Math.Max(MathF.Ceiling(1.5f / Screen.Selected.Cam.Zoom), 1.0f));
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {

View File

@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -65,15 +64,9 @@ namespace Barotrauma
public Texture2D WaterTexture { get; } public Texture2D WaterTexture { get; }
public WaterRenderer(GraphicsDevice graphicsDevice, ContentManager content) public WaterRenderer(GraphicsDevice graphicsDevice)
{ {
#if WINDOWS WaterEffect = EffectLoader.Load("Effects/watershader");
WaterEffect = content.Load<Effect>("Effects/watershader");
#endif
#if LINUX || OSX
WaterEffect = content.Load<Effect>("Effects/watershader_opengl");
#endif
WaterTexture = TextureLoader.FromFile("Content/Effects/waterbump.png"); WaterTexture = TextureLoader.FromFile("Content/Effects/waterbump.png");
WaterEffect.Parameters["xWaterBumpMap"].SetValue(WaterTexture); WaterEffect.Parameters["xWaterBumpMap"].SetValue(WaterTexture);

View File

@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System; using System;
@@ -73,12 +72,14 @@ namespace Barotrauma.Lights
private int recalculationCount; private int recalculationCount;
private float time;
public IEnumerable<LightSource> Lights public IEnumerable<LightSource> Lights
{ {
get { return lights; } get { return lights; }
} }
public LightManager(GraphicsDevice graphics, ContentManager content) public LightManager(GraphicsDevice graphics)
{ {
lights = new List<LightSource>(100); lights = new List<LightSource>(100);
@@ -96,13 +97,8 @@ namespace Barotrauma.Lights
{ {
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
#if WINDOWS LosEffect = EffectLoader.Load("Effects/losshader");
LosEffect = content.Load<Effect>("Effects/losshader"); SolidColorEffect = EffectLoader.Load("Effects/solidcolor");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor");
#else
LosEffect = content.Load<Effect>("Effects/losshader_opengl");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor_opengl");
#endif
if (lightEffect == null) if (lightEffect == null)
{ {
@@ -171,10 +167,12 @@ namespace Barotrauma.Lights
public void Update(float deltaTime) public void Update(float deltaTime)
{ {
//wrap around if the timer gets very large, otherwise we'd start running into floating point accuracy issues
time = (time + deltaTime) % 100000.0f;
foreach (LightSource light in activeLights) foreach (LightSource light in activeLights)
{ {
if (!light.Enabled) { continue; } if (!light.Enabled) { continue; }
light.Update(deltaTime); light.Update(time);
} }
} }

View File

@@ -200,8 +200,6 @@ namespace Barotrauma.Lights
private static Texture2D lightTexture; private static Texture2D lightTexture;
private float blinkTimer, flickerState, pulseState;
private VertexPositionColorTexture[] vertices; private VertexPositionColorTexture[] vertices;
private short[] indices; private short[] indices;
@@ -486,12 +484,12 @@ namespace Barotrauma.Lights
if (addLight) { GameMain.LightManager.AddLight(this); } if (addLight) { GameMain.LightManager.AddLight(this); }
} }
public void Update(float deltaTime) public void Update(float time)
{ {
float brightness = 1.0f; float brightness = 1.0f;
if (lightSourceParams.BlinkFrequency > 0.0f) if (lightSourceParams.BlinkFrequency > 0.0f)
{ {
blinkTimer = (blinkTimer + deltaTime * lightSourceParams.BlinkFrequency) % 1.0f; float blinkTimer = (time * lightSourceParams.BlinkFrequency) % 1.0f;
if (blinkTimer > 0.5f) if (blinkTimer > 0.5f)
{ {
CurrentBrightness = 0.0f; CurrentBrightness = 0.0f;
@@ -500,14 +498,13 @@ namespace Barotrauma.Lights
} }
if (lightSourceParams.PulseFrequency > 0.0f && lightSourceParams.PulseAmount > 0.0f) if (lightSourceParams.PulseFrequency > 0.0f && lightSourceParams.PulseAmount > 0.0f)
{ {
pulseState = (pulseState + deltaTime * lightSourceParams.PulseFrequency) % 1.0f; float pulseState = (time * lightSourceParams.PulseFrequency) % 1.0f;
//oscillate between 0-1 //oscillate between 0-1
brightness *= 1.0f - (float)(Math.Sin(pulseState * MathHelper.TwoPi) + 1.0f) / 2.0f * lightSourceParams.PulseAmount; brightness *= 1.0f - (float)(Math.Sin(pulseState * MathHelper.TwoPi) + 1.0f) / 2.0f * lightSourceParams.PulseAmount;
} }
if (lightSourceParams.Flicker > 0.0f) if (lightSourceParams.Flicker > 0.0f && lightSourceParams.FlickerSpeed > 0.0f)
{ {
flickerState += deltaTime * lightSourceParams.FlickerSpeed; float flickerState = (time * lightSourceParams.FlickerSpeed) % 255;
flickerState %= 255;
brightness *= 1.0f - PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * lightSourceParams.Flicker; brightness *= 1.0f - PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * lightSourceParams.Flicker;
} }
CurrentBrightness = brightness; CurrentBrightness = brightness;

View File

@@ -68,7 +68,7 @@ namespace Barotrauma
private (Rectangle targetArea, RichString tip)? tooltip; private (Rectangle targetArea, RichString tip)? tooltip;
private (SubmarineInfo pendingSub, float realWorldCrushDepth) pendingSubInfo; private SubmarineInfo.PendingSubInfo pendingSubInfo;
private RichString beaconStationActiveText, beaconStationInactiveText; private RichString beaconStationActiveText, beaconStationInactiveText;
@@ -936,39 +936,8 @@ namespace Barotrauma
if (connection.LevelData.HasHuntingGrounds) { iconCount++; } if (connection.LevelData.HasHuntingGrounds) { iconCount++; }
if (connection.Locked) { iconCount++; } if (connection.Locked) { iconCount++; }
string tooltip = null; string tooltip = null;
float subCrushDepth = Level.DefaultRealWorldCrushDepth;
var currentOrPendingSub = SubmarineSelection.CurrentOrPendingSubmarine();
if (Submarine.MainSub != null && Submarine.MainSub.Info == currentOrPendingSub)
{
subCrushDepth = Submarine.MainSub.RealWorldCrushDepth;
}
else if (currentOrPendingSub != null)
{
if (pendingSubInfo.pendingSub != currentOrPendingSub)
{
// Store the real world crush depth for the pending sub so that we don't have to calculate it again every time
pendingSubInfo = (currentOrPendingSub, currentOrPendingSub.GetRealWorldCrushDepth());
}
subCrushDepth = pendingSubInfo.realWorldCrushDepth;
}
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
{
var hullUpgradePrefab = UpgradePrefab.Find("increasewallhealth".ToIdentifier());
if (hullUpgradePrefab != null)
{
int pendingLevel = GameMain.GameSession.Campaign.UpgradeManager.GetUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First());
int currentLevel = GameMain.GameSession.Campaign.UpgradeManager.GetRealUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First());
if (pendingLevel > currentLevel)
{
string updateValueStr = hullUpgradePrefab.SourceElement?.GetChildElement("Structure")?.GetAttributeString("crushdepth", null);
if (!string.IsNullOrEmpty(updateValueStr))
{
subCrushDepth = PropertyReference.CalculateUpgrade(subCrushDepth, pendingLevel - currentLevel, updateValueStr);
}
}
}
}
float subCrushDepth = SubmarineInfo.GetSubCrushDepth(SubmarineSelection.CurrentOrPendingSubmarine(), ref pendingSubInfo);
string crushDepthWarningIconStyle = null; string crushDepthWarningIconStyle = null;
if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth) if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth)
{ {
@@ -1125,6 +1094,14 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Resets <see cref="pendingSubInfo"/> and forces crush depth to be calculated again for icon displaying purposes
/// </summary>
public void ResetPendingSub()
{
pendingSubInfo = new SubmarineInfo.PendingSubInfo();
}
partial void RemoveProjSpecific() partial void RemoveProjSpecific()
{ {
noiseOverlay?.Remove(); noiseOverlay?.Remove();

View File

@@ -515,11 +515,11 @@ namespace Barotrauma
Item targetContainer = null; Item targetContainer = null;
bool isShiftDown = PlayerInput.IsShiftDown(); bool isShiftDown = PlayerInput.IsShiftDown();
if (!isShiftDown) return null; if (!isShiftDown) { return null; }
foreach (MapEntity e in mapEntityList) foreach (MapEntity e in mapEntityList)
{ {
if (!e.SelectableInEditor ||!(e is Item potentialContainer)) { continue; } if (!e.SelectableInEditor || e is not Item potentialContainer) { continue; }
if (e.IsMouseOn(position)) if (e.IsMouseOn(position))
{ {

View File

@@ -83,7 +83,10 @@ namespace Barotrauma
{ {
string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found)."; string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found).";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); if (!ContentPackageManager.ModsEnabled)
{
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
}
return null; return null;
} }
catch (System.IO.InvalidDataException e) catch (System.IO.InvalidDataException e)

View File

@@ -89,7 +89,11 @@ namespace Barotrauma
CreateSpecsWindow(descriptionBox, font, includeDescription: true); CreateSpecsWindow(descriptionBox, font, includeDescription: true);
} }
public void CreateSpecsWindow(GUIListBox parent, GUIFont font, bool includeTitle = true, bool includeClass = true, bool includeDescription = false) public void CreateSpecsWindow(GUIListBox parent, GUIFont font,
bool includeTitle = true,
bool includeClass = true,
bool includeDescription = false,
bool includeCrushDepth = false)
{ {
float leftPanelWidth = 0.6f; float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth; float rightPanelWidth = 0.4f / leftPanelWidth;
@@ -155,6 +159,22 @@ namespace Barotrauma
{ CanBeFocused = false }; { CanBeFocused = false };
cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height); cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height);
if (includeCrushDepth)
{
var crushDepthText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("CrushDepth"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{
CanBeFocused = false
};
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crushDepthText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.GetWithVariable("meterformat", "[meters]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetSubCrushDepth())),
textAlignment: Alignment.TopLeft, font: font, wrap: true)
{
CanBeFocused = false
};
crushDepthText.RectTransform.MinSize = new Point(0, crushDepthText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0) if (RecommendedCrewSizeMax > 0)
{ {
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
@@ -227,5 +247,57 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock)); GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock));
parent.ForceLayoutRecalculation(); parent.ForceLayoutRecalculation();
} }
public readonly record struct PendingSubInfo(SubmarineInfo PendingSub = null, bool StructuresDefineRealWorldCrushDepth = false, float RealWorldCrushDepth = Level.DefaultRealWorldCrushDepth);
private float GetSubCrushDepth()
{
var pendingSubInfo = new PendingSubInfo();
return GetSubCrushDepth(this, ref pendingSubInfo);
}
public static float GetSubCrushDepth(SubmarineInfo subInfo, ref PendingSubInfo pendingSubInfo)
{
float subCrushDepth = Level.DefaultRealWorldCrushDepth;
if (Submarine.MainSub != null && Submarine.MainSub.Info == subInfo)
{
subCrushDepth = Submarine.MainSub.RealWorldCrushDepth;
}
else if (subInfo != null)
{
if (pendingSubInfo.PendingSub != subInfo)
{
// Store the real world crush depth for the pending sub so that we don't have to calculate it again every time
pendingSubInfo = new PendingSubInfo(subInfo, subInfo.IsCrushDepthDefinedInStructures(out float realWorldCrushDepth), realWorldCrushDepth);
}
subCrushDepth = pendingSubInfo.RealWorldCrushDepth;
}
if (GameMain.GameSession?.Campaign?.UpgradeManager != null && UpgradePrefab.Find("increasewallhealth".ToIdentifier()) is UpgradePrefab hullUpgradePrefab)
{
int pendingLevel = GameMain.GameSession.Campaign.UpgradeManager.GetUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First(), info: subInfo);
// If there is a sub switch pending, unless its structures have crush depth defined in their elements,
// calculate the value based on the default crush depth and pending upgrade level
int currentLevel = 0;
if (pendingSubInfo.PendingSub is null || pendingSubInfo.StructuresDefineRealWorldCrushDepth)
{
currentLevel = GameMain.GameSession.Campaign.UpgradeManager.GetRealUpgradeLevelForSub(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First(), subInfo);
}
if (pendingLevel > currentLevel)
{
string updateValueStr = hullUpgradePrefab.SourceElement?.GetChildElement("Structure")?.GetAttributeString("crushdepth", null);
if (!string.IsNullOrEmpty(updateValueStr))
{
if (currentLevel > 0)
{
// If the current level is greater than 0, reset the crush depth value back to the base value before calculating the upgrade
int upgradePercentage = UpgradePrefab.ParsePercentage(updateValueStr, Identifier.Empty, suppressWarnings: true);
subCrushDepth /= (1f + (upgradePercentage / 100f * currentLevel));
}
subCrushDepth = PropertyReference.CalculateUpgrade(subCrushDepth, pendingLevel, updateValueStr);
}
}
}
return subCrushDepth;
}
} }
} }

View File

@@ -4,26 +4,29 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
class SubmarinePreview : IDisposable sealed class SubmarinePreview : IDisposable
{ {
private SpriteRecorder spriteRecorder;
private readonly SubmarineInfo submarineInfo; private readonly SubmarineInfo submarineInfo;
private SpriteRecorder spriteRecorder;
private Camera camera; private Camera camera;
private Task loadTask; private Task loadTask;
private (Vector2 Min, Vector2 Max) bounds;
private volatile bool isDisposed; private volatile bool isDisposed;
private GUIFrame previewFrame; private GUIFrame previewFrame;
private class HullCollection private sealed class HullCollection
{ {
public readonly List<Rectangle> Rects; public readonly List<Rectangle> Rects;
public readonly LocalizedString Name; public readonly LocalizedString Name;
@@ -42,7 +45,7 @@ namespace Barotrauma
} }
} }
private struct Door private readonly struct Door
{ {
public readonly Rectangle Rect; public readonly Rectangle Rect;
@@ -150,7 +153,9 @@ namespace Barotrauma
ScrollBarVisible = false, ScrollBarVisible = false,
Spacing = GUI.IntScale(5) Spacing = GUI.IntScale(5)
}; };
subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font, includeTitle: false, includeDescription: true); subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font,
includeTitle: false,
includeDescription: true);
int width = specsContainer.Rect.Width; int width = specsContainer.Rect.Width;
void recalculateSpecsContainerHeight() void recalculateSpecsContainerHeight()
{ {
@@ -186,7 +191,22 @@ namespace Barotrauma
}); });
recalculateSpecsContainerHeight(); recalculateSpecsContainerHeight();
GeneratePreviewMeshes(); TaskPool.Add(nameof(GeneratePreviewMeshes), GeneratePreviewMeshes(), _ =>
{
if (isDisposed) { return; }
// Reset the camera's position on the main thread,
// because the Camera class is not thread-safe and
// it's possible for its state to not get updated
// properly if done within a task
camera.Position = (bounds.Min + bounds.Max) * (0.5f, -0.5f);
Vector2 span2d = bounds.Max - bounds.Min;
Vector2 scaledSpan2d = span2d / camera.Resolution.ToVector2();
float scaledSpan = Math.Max(scaledSpan2d.X, scaledSpan2d.Y);
camera.MinZoom = Math.Min(0.1f, 0.4f / scaledSpan);
camera.Zoom = 0.7f / scaledSpan;
camera.StopMovement();
camera.UpdateTransform(interpolate: false, updateListener: false);
});
} }
public static void AddToGUIUpdateList() public static void AddToGUIUpdateList()
@@ -207,6 +227,7 @@ namespace Barotrauma
spriteRecorder.Begin(SpriteSortMode.BackToFront); spriteRecorder.Begin(SpriteSortMode.BackToFront);
HashSet<int> toIgnore = new HashSet<int>(); HashSet<int> toIgnore = new HashSet<int>();
HashSet<int> wires = new HashSet<int>();
foreach (var subElement in submarineInfo.SubmarineElement.Elements()) foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{ {
@@ -221,7 +242,7 @@ namespace Barotrauma
ExtractItemContainerIds(component, toIgnore); ExtractItemContainerIds(component, toIgnore);
break; break;
case "connectionpanel": case "connectionpanel":
ExtractConnectionPanelLinks(component, toIgnore); ExtractConnectionPanelLinks(component, wires);
break; break;
} }
} }
@@ -231,20 +252,25 @@ namespace Barotrauma
await Task.Yield(); await Task.Yield();
} }
var wireNodes = new List<XElement>();
foreach (var subElement in submarineInfo.SubmarineElement.Elements()) foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{ {
if (subElement.GetAttributeBool("hiddeningame", false)) { continue; } if (subElement.GetAttributeBool("hiddeningame", false)) { continue; }
switch (subElement.Name.LocalName.ToLowerInvariant()) switch (subElement.Name.LocalName.ToLowerInvariant())
{ {
case "structure":
case "item": case "item":
if (!toIgnore.Contains(subElement.GetAttributeInt("ID", 0))) var id = subElement.GetAttributeInt("ID", 0);
if (wires.Contains(id))
{
wireNodes.Add(subElement);
}
else if (!toIgnore.Contains(id))
{ {
BakeMapEntity(subElement); BakeMapEntity(subElement);
} }
break; break;
case "structure":
BakeMapEntity(subElement);
break;
case "hull": case "hull":
Identifier identifier = subElement.GetAttributeIdentifier("roomname", ""); Identifier identifier = subElement.GetAttributeIdentifier("roomname", "");
if (!identifier.IsEmpty) if (!identifier.IsEmpty)
@@ -261,15 +287,14 @@ namespace Barotrauma
if (isDisposed) { return; } if (isDisposed) { return; }
await Task.Yield(); await Task.Yield();
} }
spriteRecorder.End();
camera.Position = (spriteRecorder.Min + spriteRecorder.Max) * 0.5f; bounds = (spriteRecorder.Min, spriteRecorder.Max);
float scaledSpan = (spriteRecorder.Max - spriteRecorder.Min).X / camera.Resolution.X; wireNodes.ForEach(BakeWireNodes);
camera.Zoom = 0.8f / scaledSpan;
camera.StopMovement(); spriteRecorder.End();
} }
private void ExtractItemContainerIds(XElement component, HashSet<int> ids) private static void ExtractItemContainerIds(XElement component, HashSet<int> ids)
{ {
string containedString = component.GetAttributeString("contained", ""); string containedString = component.GetAttributeString("contained", "");
string[] itemIdStrings = containedString.Split(','); string[] itemIdStrings = containedString.Split(',');
@@ -283,7 +308,7 @@ namespace Barotrauma
} }
} }
private void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids) private static void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids)
{ {
var pins = component.Elements("input").Concat(component.Elements("output")); var pins = component.Elements("input").Concat(component.Elements("output"));
foreach (var pin in pins) foreach (var pin in pins)
@@ -297,6 +322,39 @@ namespace Barotrauma
} }
} }
private void BakeWireNodes(XElement element)
{
var prefabIdentifier = element.GetAttributeIdentifier("identifier", "");
if (prefabIdentifier.IsEmpty) { return; }
if (!ItemPrefab.Prefabs.TryGet(prefabIdentifier, out var prefab)) { return; }
var prefabWireComponentElement = prefab.ConfigElement.GetChildElement("wire");
if (prefabWireComponentElement is null) { return; }
var wireComponent = element.GetChildElement("wire");
if (wireComponent is null) { return; }
var color = element.GetAttributeColor("spritecolor") ?? Color.White;
var nodes = Wire.ExtractNodes(wireComponent).ToImmutableArray();
var wireSprite = Wire.ExtractWireSprite(prefab.ConfigElement);
var useSpriteDepth = element.GetAttributeBool("usespritedepth", false);
var depth =
useSpriteDepth
? element.GetAttributeFloat("spritedepth", 1.0f)
: wireSprite.Depth;
var width = prefabWireComponentElement.GetAttributeFloat("width", 0.3f);
for (int i = 0; i < nodes.Length - 1; i++)
{
var line = (Start: nodes[i], End: nodes[i + 1]);
var wireSegment = new Wire.WireSection(line.Start, line.End);
wireSegment.Draw(spriteRecorder, wireSprite, color, Vector2.Zero, depth, width);
}
}
private void BakeMapEntity(XElement element) private void BakeMapEntity(XElement element)
{ {
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty); Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
@@ -313,27 +371,27 @@ namespace Barotrauma
float rotation = element.GetAttributeFloat("rotation", 0f); float rotation = element.GetAttributeFloat("rotation", 0f);
MapEntityPrefab prefab = null; MapEntityPrefab prefab;
if (element.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase) && if (element.NameAsIdentifier() == "item"
ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip)) && ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip))
{ {
prefab = ip; prefab = ip;
} }
else else
{ {
prefab = MapEntityPrefab.List.FirstOrDefault(p => p.Identifier == identifier); prefab = MapEntityPrefab.FindByIdentifier(identifier);
} }
if (prefab == null) { return; } if (prefab == null) { return; }
var texture = prefab.Sprite.Texture; flippedX &= prefab.CanSpriteFlipX;
var srcRect = prefab.Sprite.SourceRect; flippedY &= prefab.CanSpriteFlipY;
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
if (flippedX && ((prefab as ItemPrefab)?.CanSpriteFlipX ?? true)) if (flippedX)
{ {
spriteEffects |= SpriteEffects.FlipHorizontally; spriteEffects |= SpriteEffects.FlipHorizontally;
} }
if (flippedY && ((prefab as ItemPrefab)?.CanSpriteFlipY ?? true)) if (flippedY)
{ {
spriteEffects |= SpriteEffects.FlipVertically; spriteEffects |= SpriteEffects.FlipVertically;
} }
@@ -419,8 +477,8 @@ namespace Barotrauma
{ {
float offsetState = 0f; float offsetState = 0f;
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale; Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteRecorder, decorativeSprite.Sprite.DrawTiled(spriteRecorder,
new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)), new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)),
rect.Size.ToVector2(), color: color, rect.Size.ToVector2(), color: color,
@@ -451,8 +509,8 @@ namespace Barotrauma
float rotationState = 0f; float offsetState = 0f; float rotationState = 0f; float offsetState = 0f;
float rot = decorativeSprite.GetRotation(ref rotationState, 0f); float rot = decorativeSprite.GetRotation(ref rotationState, 0f);
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale; Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects, MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
@@ -472,6 +530,7 @@ namespace Barotrauma
{ {
overrideSprite = false; overrideSprite = false;
float relativeScale = scale / prefab.Scale;
foreach (var subElement in prefab.ConfigElement.Elements()) foreach (var subElement in prefab.ConfigElement.Elements())
{ {
switch (subElement.Name.LocalName.ToLowerInvariant()) switch (subElement.Name.LocalName.ToLowerInvariant())
@@ -498,7 +557,6 @@ namespace Barotrauma
relativeBarrelPos, relativeBarrelPos,
MathHelper.ToRadians(rotation)); MathHelper.ToRadians(rotation));
float relativeScale = scale / prefab.Scale;
Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale); Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale);
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
@@ -516,20 +574,22 @@ namespace Barotrauma
break; break;
case "door": case "door":
doors.Add(new Door(rect)); var scaledRect = rect with { Size = (rect.Size.ToVector2() * relativeScale).ToPoint() };
doors.Add(new Door(scaledRect));
var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase)); var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase));
if (doorSpriteElem != null) if (doorSpriteElem != null)
{ {
string texturePath = doorSpriteElem.GetAttributeString("texture", ""); string texturePath = doorSpriteElem.GetAttributeStringUnrestricted("texture", "");
Vector2 pos = rect.Location.ToVector2() * new Vector2(1f, -1f); Vector2 pos = scaledRect.Location.ToVector2() * new Vector2(1f, -1f);
if (subElement.GetAttributeBool("horizontal", false)) if (subElement.GetAttributeBool("horizontal", false))
{ {
pos.Y += (float)rect.Height * 0.5f; pos.Y += (float)scaledRect.Height * 0.5f;
} }
else else
{ {
pos.X += (float)rect.Width * 0.5f; pos.X += (float)scaledRect.Width * 0.5f;
} }
Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath)); Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath));
spriteRecorder.Draw(doorSprite.Texture, pos, spriteRecorder.Draw(doorSprite.Texture, pos,
@@ -555,7 +615,7 @@ namespace Barotrauma
} }
} }
public void ParseUpgrades(XElement prefabConfigElement, ref float scale) private void ParseUpgrades(XElement prefabConfigElement, ref float scale)
{ {
foreach (var upgrade in prefabConfigElement.Elements("Upgrade")) foreach (var upgrade in prefabConfigElement.Elements("Upgrade"))
{ {

View File

@@ -12,7 +12,7 @@ namespace Barotrauma.Networking
string name, string name,
Either<Address, AccountId> addressOrAccountId, Either<Address, AccountId> addressOrAccountId,
string reason, string reason,
DateTime? expiration) Option<SerializableDateTime> expiration)
{ {
this.Name = name; this.Name = name;
this.AddressOrAccountId = addressOrAccountId; this.AddressOrAccountId = addressOrAccountId;
@@ -66,9 +66,20 @@ namespace Barotrauma.Networking
}; };
var addressOrAccountId = bannedPlayer.AddressOrAccountId; var addressOrAccountId = bannedPlayer.AddressOrAccountId;
GUITextBlock textBlock = new GUITextBlock(
new RectTransform(new Vector2(0.5f, 1.0f), topArea.RectTransform), string nameText = bannedPlayer.Name;
bannedPlayer.Name + " (" + addressOrAccountId + ")") { CanBeFocused = true }; if (addressOrAccountId.TryCast(out Address address))
{
nameText += $" ({address.StringRepresentation})";
}
else if (addressOrAccountId.TryCast(out AccountId accountId))
{
nameText += $" ({accountId.StringRepresentation})";
}
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), topArea.RectTransform), nameText)
{
CanBeFocused = true
};
textBlock.RectTransform.MinSize = new Point( textBlock.RectTransform.MinSize = new Point(
(int)textBlock.Font.MeasureString(textBlock.Text.SanitizedValue).X, 0); (int)textBlock.Font.MeasureString(textBlock.Text.SanitizedValue).X, 0);
@@ -83,8 +94,9 @@ namespace Barotrauma.Networking
topArea.ForceLayoutRecalculation(); topArea.ForceLayoutRecalculation();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform),
bannedPlayer.ExpirationTime == null ? bannedPlayer.ExpirationTime.TryUnwrap(out var expirationTime)
TextManager.Get("BanPermanent") : TextManager.GetWithVariable("BanExpires", "[time]", bannedPlayer.ExpirationTime.Value.ToString()), ? TextManager.GetWithVariable("BanExpires", "[time]", expirationTime.ToLocalUserString())
: TextManager.Get("BanPermanent"),
font: GUIStyle.SmallFont); font: GUIStyle.SmallFont);
LocalizedString reason = TextManager.GetServerMessage(bannedPlayer.Reason).Fallback(bannedPlayer.Reason); LocalizedString reason = TextManager.GetServerMessage(bannedPlayer.Reason).Fallback(bannedPlayer.Reason);
@@ -106,7 +118,7 @@ namespace Barotrauma.Networking
private bool RemoveBan(GUIButton button, object obj) private bool RemoveBan(GUIButton button, object obj)
{ {
if (!(obj is BannedPlayer banned)) { return false; } if (obj is not BannedPlayer banned) { return false; }
localRemovedBans.Add(banned.UniqueIdentifier); localRemovedBans.Add(banned.UniqueIdentifier);
RecreateBanFrame(); RecreateBanFrame();
@@ -138,11 +150,11 @@ namespace Barotrauma.Networking
bool includesExpiration = incMsg.ReadBoolean(); bool includesExpiration = incMsg.ReadBoolean();
incMsg.ReadPadBits(); incMsg.ReadPadBits();
DateTime? expiration = null; Option<SerializableDateTime> expiration = Option<SerializableDateTime>.None();
if (includesExpiration) if (includesExpiration)
{ {
double hoursFromNow = incMsg.ReadDouble(); double hoursFromNow = incMsg.ReadDouble();
expiration = DateTime.Now + TimeSpan.FromHours(hoursFromNow); expiration = Option<SerializableDateTime>.Some(SerializableDateTime.LocalNow + TimeSpan.FromHours(hoursFromNow));
} }
string reason = incMsg.ReadString(); string reason = incMsg.ReadString();

View File

@@ -26,8 +26,8 @@ namespace Barotrauma.Networking
if (type != ChatMessageType.Order) if (type != ChatMessageType.Order)
{ {
changeType = (PlayerConnectionChangeType)msg.ReadByte(); changeType = (PlayerConnectionChangeType)msg.ReadByte();
txt = msg.ReadString();
} }
txt = msg.ReadString();
string senderName = msg.ReadString(); string senderName = msg.ReadString();
Character senderCharacter = null; Character senderCharacter = null;
@@ -87,11 +87,6 @@ namespace Barotrauma.Networking
targetRoom = senderCharacter?.CurrentHull?.DisplayName?.Value; targetRoom = senderCharacter?.CurrentHull?.DisplayName?.Value;
} }
txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, targetRoom,
givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter,
orderOption: orderOption,
isNewOrder: orderMessageInfo.IsNewOrder);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{ {
Order order = null; Order order = null;

View File

@@ -34,6 +34,7 @@ namespace Barotrauma.Networking
catch catch
{ {
DebugConsole.ThrowError($"Failed to start ChildServerRelay Process. File: {processInfo.FileName}, arguments: {processInfo.Arguments}"); DebugConsole.ThrowError($"Failed to start ChildServerRelay Process. File: {processInfo.FileName}, arguments: {processInfo.Arguments}");
ForceShutDown();
throw; throw;
} }

View File

@@ -522,7 +522,7 @@ namespace Barotrauma.Networking
if (GameStarted && Screen.Selected == GameMain.GameScreen) if (GameStarted && Screen.Selected == GameMain.GameScreen)
{ {
EndVoteTickBox.Visible = ServerSettings.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode); EndVoteTickBox.Visible = ServerSettings.AllowEndVoting && HasSpawned;
RespawnManager?.Update(deltaTime); RespawnManager?.Update(deltaTime);
@@ -1104,11 +1104,7 @@ namespace Barotrauma.Networking
VoipClient = new VoipClient(this, ClientPeer); VoipClient = new VoipClient(this, ClientPeer);
//if we're still in the game, roundsummary or lobby screen, we don't need to redownload the mods //if we're still in the game, roundsummary or lobby screen, we don't need to redownload the mods
if (!(Screen.Selected is GameScreen) && !(Screen.Selected is RoundSummaryScreen) && !(Screen.Selected is NetLobbyScreen)) if (Screen.Selected is GameScreen or RoundSummaryScreen or NetLobbyScreen)
{
GameMain.ModDownloadScreen.Select();
}
else
{ {
EntityEventManager.ClearSelf(); EntityEventManager.ClearSelf();
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
@@ -1116,6 +1112,10 @@ namespace Barotrauma.Networking
c.ResetNetState(); c.ResetNetState();
} }
} }
else
{
GameMain.ModDownloadScreen.Select();
}
chatBox.InputBox.Enabled = true; chatBox.InputBox.Enabled = true;
if (GameMain.NetLobbyScreen?.ChatInput != null) if (GameMain.NetLobbyScreen?.ChatInput != null)
@@ -1537,8 +1537,9 @@ namespace Barotrauma.Networking
roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize; roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
DateTime? timeOut = null; //wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message
TimeSpan timeOutDuration = new TimeSpan(0, 0, seconds: 30); TimeSpan timeOutDuration = new TimeSpan(0, 0, seconds: 30);
DateTime timeOut = DateTime.Now + timeOutDuration;
DateTime requestFinalizeTime = DateTime.Now; DateTime requestFinalizeTime = DateTime.Now;
TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2); TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
@@ -1547,11 +1548,15 @@ namespace Barotrauma.Networking
GUIMessageBox interruptPrompt = null; GUIMessageBox interruptPrompt = null;
while (true) if (includesFinalize)
{ {
try ReadStartGameFinalize(inc);
}
else
{
while (true)
{ {
if (timeOut.HasValue) try
{ {
if (DateTime.Now > requestFinalizeTime) if (DateTime.Now > requestFinalizeTime)
{ {
@@ -1585,41 +1590,30 @@ namespace Barotrauma.Networking
return true; return true;
}; };
} }
}
else if (!connected)
{
if (includesFinalize)
{ {
ReadStartGameFinalize(inc); roundInitStatus = RoundInitStatus.Interrupted;
break; break;
} }
//wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
timeOut = DateTime.Now + timeOutDuration;
} }
catch (Exception e)
if (!connected)
{ {
roundInitStatus = RoundInitStatus.Interrupted; DebugConsole.ThrowError("There was an error initializing the round.", e, true);
roundInitStatus = RoundInitStatus.Error;
break; break;
} }
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; } //waiting for a STARTGAMEFINALIZE message
yield return CoroutineStatus.Running;
} }
catch (Exception e)
{
DebugConsole.ThrowError("There was an error initializing the round.", e, true);
roundInitStatus = RoundInitStatus.Error;
break;
}
//waiting for a STARTGAMEFINALIZE message
yield return CoroutineStatus.Running;
} }
interruptPrompt?.Close(); interruptPrompt?.Close();
interruptPrompt = null; interruptPrompt = null;
if (roundInitStatus != RoundInitStatus.Started) if (roundInitStatus != RoundInitStatus.Started)
{ {
if (roundInitStatus != RoundInitStatus.Interrupted) if (roundInitStatus != RoundInitStatus.Interrupted)
@@ -1769,7 +1763,7 @@ namespace Barotrauma.Networking
{ {
string subName = inc.ReadString(); string subName = inc.ReadString();
string subHash = inc.ReadString(); string subHash = inc.ReadString();
byte subClass = inc.ReadByte(); SubmarineClass subClass = (SubmarineClass)inc.ReadByte();
bool isShuttle = inc.ReadBoolean(); bool isShuttle = inc.ReadBoolean();
bool requiredContentPackagesInstalled = inc.ReadBoolean(); bool requiredContentPackagesInstalled = inc.ReadBoolean();
@@ -1778,7 +1772,7 @@ namespace Barotrauma.Networking
{ {
matchingSub = new SubmarineInfo(Path.Combine(SaveUtil.SubmarineDownloadFolder, subName) + ".sub", subHash, tryLoad: false) matchingSub = new SubmarineInfo(Path.Combine(SaveUtil.SubmarineDownloadFolder, subName) + ".sub", subHash, tryLoad: false)
{ {
SubmarineClass = (SubmarineClass)subClass SubmarineClass = subClass
}; };
if (isShuttle) { matchingSub.AddTag(SubmarineTag.Shuttle); } if (isShuttle) { matchingSub.AddTag(SubmarineTag.Shuttle); }
} }
@@ -2011,10 +2005,10 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
GameMain.NetLobbyScreen.SetMissionType(missionType); GameMain.NetLobbyScreen.SetMissionType(missionType);
if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex); GameMain.NetLobbyScreen.SelectMode(modeIndex);
if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
{ {
if (GameMain.Client.IsServerOwner) RequestSelectMode(modeIndex); if (GameMain.Client.IsServerOwner) { RequestSelectMode(modeIndex); }
} }
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
@@ -2103,13 +2097,12 @@ namespace Barotrauma.Networking
case ServerNetSegment.EntityPosition: case ServerNetSegment.EntityPosition:
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
UInt32 incomingUintIdentifier = inc.ReadUInt32();
UInt16 id = inc.ReadUInt16();
uint msgLength = inc.ReadVariableUInt32(); uint msgLength = inc.ReadVariableUInt32();
int msgEndPos = (int)(inc.BitPosition + msgLength * 8); int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
var entity = Entity.FindEntityByID(id) as IServerPositionSync; var header = INetSerializableStruct.Read<EntityPositionHeader>(inc);
var entity = Entity.FindEntityByID(header.EntityId) as IServerPositionSync;
if (msgEndPos > inc.LengthBits) if (msgEndPos > inc.LengthBits)
{ {
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer."); DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
@@ -2119,15 +2112,15 @@ namespace Barotrauma.Networking
debugEntityList.Add(entity); debugEntityList.Add(entity);
if (entity != null) if (entity != null)
{ {
if (entity is Item != isItem) if (entity is Item != header.IsItem)
{ {
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message..."); DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(header.IsItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
} }
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me && else if (entity is MapEntity { Prefab.UintIdentifier: var uintIdentifier } me &&
uintIdentifier != incomingUintIdentifier) uintIdentifier != header.PrefabUintIdentifier)
{ {
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message." DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, " +$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == header.PrefabUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
+$"client entity is {me.Prefab.Identifier}). Ignoring the message..."); +$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
} }
else else
@@ -2135,7 +2128,6 @@ namespace Barotrauma.Networking
entity.ClientReadPosition(inc, sendingTime); entity.ClientReadPosition(inc, sendingTime);
} }
} }
//force to the correct position in case the entity doesn't exist //force to the correct position in case the entity doesn't exist
//or the message wasn't read correctly for whatever reason //or the message wasn't read correctly for whatever reason
inc.BitPosition = msgEndPos; inc.BitPosition = msgEndPos;
@@ -2146,7 +2138,7 @@ namespace Barotrauma.Networking
break; break;
case ServerNetSegment.EntityEvent: case ServerNetSegment.EntityEvent:
case ServerNetSegment.EntityEventInitial: case ServerNetSegment.EntityEventInitial:
if (!EntityEventManager.Read(segment, inc, sendingTime, debugEntityList)) if (!EntityEventManager.Read(segment, inc, sendingTime))
{ {
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes; return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
} }
@@ -2415,7 +2407,9 @@ namespace Barotrauma.Networking
var newSub = new SubmarineInfo(transfer.FilePath); var newSub = new SubmarineInfo(transfer.FilePath);
if (newSub.IsFileCorrupted) { return; } if (newSub.IsFileCorrupted) { return; }
var existingSubs = SubmarineInfo.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.StringRepresentation == newSub.MD5Hash.StringRepresentation).ToList(); var existingSubs = SubmarineInfo.SavedSubmarines
.Where(s => s.Name == newSub.Name && s.MD5Hash == newSub.MD5Hash)
.ToList();
foreach (SubmarineInfo existingSub in existingSubs) foreach (SubmarineInfo existingSub in existingSubs)
{ {
existingSub.Dispose(); existingSub.Dispose();
@@ -2474,12 +2468,13 @@ namespace Barotrauma.Networking
} }
// Replace a submarine dud with the downloaded version // Replace a submarine dud with the downloaded version
SubmarineInfo existingServerSub = ServerSubmarines.Find(s => s.Name == newSub.Name && s.MD5Hash?.StringRepresentation == newSub.MD5Hash?.StringRepresentation); SubmarineInfo existingServerSub = ServerSubmarines.Find(s =>
s.Name == newSub.Name
&& s.MD5Hash == newSub.MD5Hash);
if (existingServerSub != null) if (existingServerSub != null)
{ {
int existingIndex = ServerSubmarines.IndexOf(existingServerSub); int existingIndex = ServerSubmarines.IndexOf(existingServerSub);
ServerSubmarines.RemoveAt(existingIndex); ServerSubmarines[existingIndex] = newSub;
ServerSubmarines.Insert(existingIndex, newSub);
existingServerSub.Dispose(); existingServerSub.Dispose();
} }
@@ -2805,7 +2800,6 @@ namespace Barotrauma.Networking
/// </summary> /// </summary>
public void RequestSelectMode(int modeIndex) public void RequestSelectMode(int modeIndex)
{ {
if (!HasPermission(ClientPermissions.SelectMode)) return;
if (modeIndex < 0 || modeIndex >= GameMain.NetLobbyScreen.ModeList.Content.CountChildren) if (modeIndex < 0 || modeIndex >= GameMain.NetLobbyScreen.ModeList.Content.CountChildren)
{ {
DebugConsole.ThrowError("Gamemode index out of bounds (" + modeIndex + ")\n" + Environment.StackTrace.CleanupStackTrace()); DebugConsole.ThrowError("Gamemode index out of bounds (" + modeIndex + ")\n" + Environment.StackTrace.CleanupStackTrace());
@@ -2859,13 +2853,14 @@ namespace Barotrauma.Networking
/// <summary> /// <summary>
/// Tell the server to end the round (permission required) /// Tell the server to end the round (permission required)
/// </summary> /// </summary>
public void RequestRoundEnd(bool save) public void RequestRoundEnd(bool save, bool quitCampaign = false)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageRound); msg.WriteUInt16((UInt16)ClientPermissions.ManageRound);
msg.WriteBoolean(true); //indicates round end msg.WriteBoolean(true); //indicates round end
msg.WriteBoolean(save); msg.WriteBoolean(save);
msg.WriteBoolean(quitCampaign);
ClientPeer.Send(msg, DeliveryMethod.Reliable); ClientPeer.Send(msg, DeliveryMethod.Reliable);
} }

View File

@@ -109,16 +109,15 @@ namespace Barotrauma.Networking
private UInt16? firstNewID; private UInt16? firstNewID;
private readonly List<IServerSerializable> tempEntityList = new List<IServerSerializable>();
/// <summary> /// <summary>
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails. /// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
/// </summary> /// </summary>
public bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities) public bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime)
{ {
UInt16 unreceivedEntityEventCount = 0;
if (type == ServerNetSegment.EntityEventInitial) if (type == ServerNetSegment.EntityEventInitial)
{ {
unreceivedEntityEventCount = msg.ReadUInt16(); UInt16 unreceivedEntityEventCount = msg.ReadUInt16();
firstNewID = msg.ReadUInt16(); firstNewID = msg.ReadUInt16();
if (GameSettings.CurrentConfig.VerboseLogging) if (GameSettings.CurrentConfig.VerboseLogging)
@@ -143,7 +142,7 @@ namespace Barotrauma.Networking
} }
} }
entities.Clear(); tempEntityList.Clear();
msg.ReadPadBits(); msg.ReadPadBits();
UInt16 firstEventID = msg.ReadUInt16(); UInt16 firstEventID = msg.ReadUInt16();
@@ -156,9 +155,9 @@ namespace Barotrauma.Networking
{ {
string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits})."; string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
errorMsg += "\nPrevious entities:"; errorMsg += "\nPrevious entities:";
for (int j = entities.Count - 1; j >= 0; j--) for (int j = tempEntityList.Count - 1; j >= 0; j--)
{ {
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); errorMsg += "\n" + (tempEntityList[j] == null ? "NULL" : tempEntityList[j].ToString());
} }
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
return false; return false;
@@ -174,7 +173,7 @@ namespace Barotrauma.Networking
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)", DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
Microsoft.Xna.Framework.Color.Orange); Microsoft.Xna.Framework.Color.Orange);
} }
entities.Add(null); tempEntityList.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; } if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
continue; continue;
} }
@@ -182,7 +181,7 @@ namespace Barotrauma.Networking
int msgLength = (int)msg.ReadVariableUInt32(); int msgLength = (int)msg.ReadVariableUInt32();
IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable; IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable;
entities.Add(entity); tempEntityList.Add(entity);
//skip the event if we've already received it or if the entity isn't found //skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null) if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
@@ -223,7 +222,7 @@ namespace Barotrauma.Networking
if (msg.BitPosition != msgPosition + msgLength * 8) if (msg.BitPosition != msgPosition + msgLength * 8)
{ {
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null; var prevEntity = tempEntityList.Count >= 2 ? tempEntityList[tempEntityList.Count - 2] : null;
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0; ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). " string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) " +$"The previous entity was \"{prevEntity}\" (ID {prevId}) "

View File

@@ -30,6 +30,8 @@ namespace Barotrauma.Networking
protected readonly bool isOwner; protected readonly bool isOwner;
protected readonly Option<int> ownerKey; protected readonly Option<int> ownerKey;
public bool IsActive => isActive;
protected bool isActive; protected bool isActive;
public ClientPeer(Endpoint serverEndpoint, Callbacks callbacks, Option<int> ownerKey) public ClientPeer(Endpoint serverEndpoint, Callbacks callbacks, Option<int> ownerKey)
@@ -80,6 +82,11 @@ namespace Barotrauma.Networking
Initialization = ConnectionInitialization.SteamTicketAndVersion Initialization = ConnectionInitialization.SteamTicketAndVersion
}; };
if (steamAuthTicket is { Canceled: true })
{
throw new InvalidOperationException("ReadConnectionInitializationStep failed: Steam auth ticket has been cancelled.");
}
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
{ {
Name = GameMain.Client.Name, Name = GameMain.Client.Name,

View File

@@ -120,6 +120,7 @@ namespace Barotrauma.Networking
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters; client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
client.RadioNoise = 0.0f;
if (messageType == ChatMessageType.Radio) if (messageType == ChatMessageType.Radio)
{ {
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier); client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
@@ -131,7 +132,6 @@ namespace Barotrauma.Networking
} }
else else
{ {
client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier); client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier);
} }
client.VoipSound.UseMuffleFilter = client.VoipSound.UseMuffleFilter =

View File

@@ -98,16 +98,15 @@ namespace Barotrauma
foreach (GUIComponent comp in listBox.Content.Children) foreach (GUIComponent comp in listBox.Content.Children)
{ {
if (comp.UserData != userData) { continue; } if (comp.UserData != userData) { continue; }
if (!(comp.FindChild("votes") is GUITextBlock voteText)) if (comp.FindChild("votes") is not GUITextBlock voteText)
{ {
voteText = new GUITextBlock(new RectTransform(new Point(30, comp.Rect.Height), comp.RectTransform, Anchor.CenterRight), voteText = new GUITextBlock(new RectTransform(new Point(GUI.IntScale(30), comp.Rect.Height), comp.RectTransform, Anchor.CenterRight),
"", textAlignment: Alignment.CenterRight) "", textAlignment: Alignment.Center)
{ {
Padding = Vector4.Zero, Padding = Vector4.Zero,
UserData = "votes" UserData = "votes"
}; };
} }
voteText.Text = votes == 0 ? "" : votes.ToString(); voteText.Text = votes == 0 ? "" : votes.ToString();
} }
} }

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -58,7 +59,7 @@ namespace Barotrauma
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement") var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement")
{ {
UserData = saveInfo.FilePath UserData = saveInfo
}; };
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath), var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath),
@@ -87,10 +88,9 @@ namespace Barotrauma
}; };
string saveTimeStr = string.Empty; string saveTimeStr = string.Empty;
if (saveInfo.SaveTime > 0) if (saveInfo.SaveTime.TryUnwrap(out var time))
{ {
DateTime time = ToolBox.Epoch.ToDateTime(saveInfo.SaveTime); saveTimeStr = time.ToLocalUserString();
saveTimeStr = time.ToString();
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTimeStr, textAlignment: Alignment.Right, font: GUIStyle.SmallFont) text: saveTimeStr, textAlignment: Alignment.Right, font: GUIStyle.SmallFont)
@@ -102,6 +102,26 @@ namespace Barotrauma
return saveFrame; return saveFrame;
} }
protected void SortSaveList()
{
saveList.Content.RectTransform.SortChildren((c1, c2) =>
{
if (c1.GUIComponent.UserData is not CampaignMode.SaveInfo file1
|| c2.GUIComponent.UserData is not CampaignMode.SaveInfo file2)
{
return 0;
}
if (!file1.SaveTime.TryUnwrap(out var file1WriteTime)
|| !file2.SaveTime.TryUnwrap(out var file2WriteTime))
{
return 0;
}
return file2WriteTime.CompareTo(file1WriteTime);
});
}
public struct CampaignSettingElements public struct CampaignSettingElements
{ {
public SettingValue<bool> TutorialEnabled; public SettingValue<bool> TutorialEnabled;
@@ -303,7 +323,7 @@ namespace Barotrauma
bool ChangeValue(GUIButton btn, object userData) bool ChangeValue(GUIButton btn, object userData)
{ {
if (!(userData is int change)) { return false; } if (userData is not int change) { return false; }
int hiddenOptions = 0; int hiddenOptions = 0;
@@ -367,5 +387,25 @@ namespace Barotrauma
return inputContainer; return inputContainer;
} }
} }
public abstract void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null);
protected bool DeleteSave(GUIButton button, object obj)
{
if (obj is not CampaignMode.SaveInfo saveInfo) { return false; }
var header = TextManager.Get("deletedialoglabel");
var body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveInfo.FilePath));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveInfo.FilePath);
prevSaveFiles?.RemoveAll(s => s.FilePath == saveInfo.FilePath);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
} }
} }

View File

@@ -192,7 +192,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
public void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null) public override void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
{ {
prevSaveFiles?.Clear(); prevSaveFiles?.Clear();
prevSaveFiles = null; prevSaveFiles = null;
@@ -220,37 +220,16 @@ namespace Barotrauma
CreateSaveElement(saveInfo); CreateSaveElement(saveInfo);
} }
saveList.Content.RectTransform.SortChildren((c1, c2) => SortSaveList();
{
string file1 = c1.GUIComponent.UserData as string;
string file2 = c2.GUIComponent.UserData as string;
DateTime file1WriteTime = DateTime.MinValue;
DateTime file2WriteTime = DateTime.MinValue;
try
{
file1WriteTime = File.GetLastWriteTime(file1);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
try
{
file2WriteTime = File.GetLastWriteTime(file2);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
return file2WriteTime.CompareTo(file1WriteTime);
});
loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton")) loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"))
{ {
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string); if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
return true; return true;
}, },
@@ -264,37 +243,20 @@ namespace Barotrauma
}; };
} }
private bool SelectSaveFile(GUIComponent component, object obj) private bool SelectSaveFile(GUIComponent component, object obj)
{ {
string fileName = (string)obj; if (obj is not CampaignMode.SaveInfo saveInfo) { return true; }
string fileName = saveInfo.FilePath;
loadGameButton.Enabled = true; loadGameButton.Enabled = true;
deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner; deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner;
deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName; deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName;
if (deleteMpSaveButton.Visible) if (deleteMpSaveButton.Visible)
{ {
deleteMpSaveButton.UserData = obj as string; deleteMpSaveButton.UserData = saveInfo;
} }
return true; return true;
} }
private bool DeleteSave(GUIButton button, object obj)
{
string saveFile = obj as string;
if (obj == null) { return false; }
var header = TextManager.Get("deletedialoglabel");
var body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
} }
} }

View File

@@ -365,7 +365,7 @@ namespace Barotrauma
private void CreateCustomizeWindow(CampaignSettings prevSettings, Action<CampaignSettings> onClosed = null) private void CreateCustomizeWindow(CampaignSettings prevSettings, Action<CampaignSettings> onClosed = null)
{ {
CampaignCustomizeSettings = new GUIMessageBox("", "", new[] { TextManager.Get("OK") }, new Vector2(0.25f, 0.3f), minSize: new Point(450, 350)); CampaignCustomizeSettings = new GUIMessageBox("", "", new[] { TextManager.Get("OK") }, new Vector2(0.25f, 0.5f), minSize: new Point(450, 350));
GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter)); GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter));
@@ -581,7 +581,7 @@ namespace Barotrauma
} }
} }
public void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null) public override void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
{ {
prevSaveFiles?.Clear(); prevSaveFiles?.Clear();
prevSaveFiles = null; prevSaveFiles = null;
@@ -649,46 +649,27 @@ namespace Barotrauma
} }
} }
saveList.Content.RectTransform.SortChildren((c1, c2) => SortSaveList();
{
string file1 = c1.GUIComponent.UserData as string;
string file2 = c2.GUIComponent.UserData as string;
DateTime file1WriteTime = DateTime.MinValue;
DateTime file2WriteTime = DateTime.MinValue;
try
{
file1WriteTime = File.GetLastWriteTime(file1);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
try
{
file2WriteTime = File.GetLastWriteTime(file2);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
return file2WriteTime.CompareTo(file1WriteTime);
});
loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton")) loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"))
{ {
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string); if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath);
return true; return true;
}, },
Enabled = false Enabled = false
}; };
} }
private bool SelectSaveFile(GUIComponent component, object obj) private bool SelectSaveFile(GUIComponent component, object obj)
{ {
string fileName = (string)obj; if (obj is not CampaignMode.SaveInfo saveInfo) { return true; }
string fileName = saveInfo.FilePath;
XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); XDocument doc = SaveUtil.LoadGameSessionDoc(fileName);
if (doc?.Root == null) if (doc?.Root == null)
@@ -701,72 +682,55 @@ namespace Barotrauma
RemoveSaveFrame(); RemoveSaveFrame();
string subName = doc.Root.GetAttributeString("submarine", ""); string subName = saveInfo.SubmarineName;
string saveTime = doc.Root.GetAttributeString("savetime", "unknown"); LocalizedString saveTime = saveInfo.SaveTime
DateTime? time = null; .Select(t => (LocalizedString)t.ToLocalUserString())
if (long.TryParse(saveTime, out long unixTime)) .Fallback(TextManager.Get("Unknown"));
{
time = ToolBox.Epoch.ToDateTime(unixTime);
saveTime = time.ToString();
}
string mapseed = doc.Root.GetAttributeString("mapseed", "unknown"); string mapseed = doc.Root.GetAttributeString("mapseed", "unknown");
var saveFileFrame = new GUIFrame(new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight) var saveFileFrame = new GUIFrame(
{ new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight)
RelativeOffset = new Vector2(0.0f, 0.1f) {
}, style: "InnerFrame") RelativeOffset = new Vector2(0.0f, 0.1f)
}, style: "InnerFrame")
{ {
UserData = "savefileframe" UserData = "savefileframe"
}; };
var titleText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) var titleText = new GUITextBlock(
{ new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter)
RelativeOffset = new Vector2(0, 0.05f) {
}, RelativeOffset = new Vector2(0, 0.05f)
},
Path.GetFileNameWithoutExtension(fileName), font: GUIStyle.LargeFont, textAlignment: Alignment.Center); Path.GetFileNameWithoutExtension(fileName), font: GUIStyle.LargeFont, textAlignment: Alignment.Center);
titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width); titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width);
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center) var layoutGroup = new GUILayoutGroup(
{ new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center)
RelativeOffset = new Vector2(0, 0.1f) {
}); RelativeOffset = new Vector2(0, 0.1f)
});
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("Submarine")} : {subName}", font: GUIStyle.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("LastSaved")} : {saveTime}", font: GUIStyle.SmallFont); $"{TextManager.Get("Submarine")} : {subName}", font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("MapSeed")} : {mapseed}", font: GUIStyle.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
$"{TextManager.Get("LastSaved")} : {saveTime}", font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
$"{TextManager.Get("MapSeed")} : {mapseed}", font: GUIStyle.SmallFont);
new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter) new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter)
{ {
RelativeOffset = new Vector2(0, 0.1f) RelativeOffset = new Vector2(0, 0.1f)
}, TextManager.Get("Delete"), style: "GUIButtonSmall") }, TextManager.Get("Delete"), style: "GUIButtonSmall")
{ {
UserData = fileName, UserData = saveInfo,
OnClicked = DeleteSave OnClicked = DeleteSave
}; };
return true; return true;
} }
private bool DeleteSave(GUIButton button, object obj)
{
string saveFile = obj as string;
if (obj == null) { return false; }
LocalizedString header = TextManager.Get("deletedialoglabel");
LocalizedString body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
private void RemoveSaveFrame() private void RemoveSaveFrame()
{ {
GUIComponent prevFrame = null; GUIComponent prevFrame = null;

View File

@@ -551,6 +551,7 @@ namespace Barotrauma
submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true); submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true);
break; break;
case CampaignMode.InteractionType.Map: case CampaignMode.InteractionType.Map:
GameMain.GameSession?.Map?.ResetPendingSub();
//refresh mission rewards (may have been changed by e.g. a pending submarine switch) //refresh mission rewards (may have been changed by e.g. a pending submarine switch)
foreach (GUITextBlock rewardText in missionRewardTexts) foreach (GUITextBlock rewardText in missionRewardTexts)
{ {

View File

@@ -25,14 +25,11 @@ namespace Barotrauma.CharacterEditor
{ {
get get
{ {
if (cam == null) cam ??= new Camera()
{ {
cam = new Camera() MinZoom = 0.1f,
{ MaxZoom = 5.0f
MinZoom = 0.1f, };
MaxZoom = 5.0f
};
}
return cam; return cam;
} }
} }
@@ -125,7 +122,7 @@ namespace Barotrauma.CharacterEditor
{ {
ResetVariables(); ResetVariables();
var subInfo = new SubmarineInfo("Content/AnimEditor.sub"); var subInfo = new SubmarineInfo("Content/AnimEditor.sub");
Submarine.MainSub = new Submarine(subInfo); Submarine.MainSub = new Submarine(subInfo, showErrorMessages: false);
if (Submarine.MainSub.PhysicsBody != null) if (Submarine.MainSub.PhysicsBody != null)
{ {
Submarine.MainSub.PhysicsBody.Enabled = false; Submarine.MainSub.PhysicsBody.Enabled = false;
@@ -162,11 +159,6 @@ namespace Barotrauma.CharacterEditor
OpenDoors(); OpenDoors();
GameMain.Instance.ResolutionChanged += OnResolutionChanged; GameMain.Instance.ResolutionChanged += OnResolutionChanged;
Instance = this; Instance = this;
if (!GameSettings.CurrentConfig.EditorDisclaimerShown)
{
GameMain.Instance.ShowEditorDisclaimer();
}
} }
private void ResetVariables() private void ResetVariables()
@@ -267,7 +259,10 @@ namespace Barotrauma.CharacterEditor
#endif #endif
} }
GameMain.Instance.ResolutionChanged -= OnResolutionChanged; GameMain.Instance.ResolutionChanged -= OnResolutionChanged;
GameMain.LightManager.LightingEnabled = true; if (!GameMain.DevMode)
{
GameMain.LightManager.LightingEnabled = true;
}
ClearWidgets(); ClearWidgets();
ClearSelection(); ClearSelection();
} }
@@ -285,6 +280,7 @@ namespace Barotrauma.CharacterEditor
#region Main methods #region Main methods
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
{ {
if (rightArea == null || leftArea == null) { return; }
rightArea.AddToGUIUpdateList(); rightArea.AddToGUIUpdateList();
leftArea.AddToGUIUpdateList(); leftArea.AddToGUIUpdateList();
@@ -783,7 +779,7 @@ namespace Barotrauma.CharacterEditor
scaledMouseSpeed = PlayerInput.MouseSpeedPerSecond * (float)deltaTime; scaledMouseSpeed = PlayerInput.MouseSpeedPerSecond * (float)deltaTime;
Cam.UpdateTransform(true); Cam.UpdateTransform(true);
Submarine.CullEntities(Cam); Submarine.CullEntities(Cam);
Submarine.MainSub.UpdateTransform(); Submarine.MainSub?.UpdateTransform();
// Lightmaps // Lightmaps
if (GameMain.LightManager.LightingEnabled) if (GameMain.LightManager.LightingEnabled)
@@ -1575,10 +1571,7 @@ namespace Barotrauma.CharacterEditor
{ {
wayPoint = WayPoint.GetRandom(spawnType: SpawnType.Human, sub: Submarine.MainSub); wayPoint = WayPoint.GetRandom(spawnType: SpawnType.Human, sub: Submarine.MainSub);
} }
if (wayPoint == null) wayPoint ??= WayPoint.GetRandom(sub: Submarine.MainSub);
{
wayPoint = WayPoint.GetRandom(sub: Submarine.MainSub);
}
spawnPosition = wayPoint.WorldPosition; spawnPosition = wayPoint.WorldPosition;
} }
@@ -2688,10 +2681,6 @@ namespace Barotrauma.CharacterEditor
// Character selection // Character selection
var characterLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), GetCharacterEditorTranslation("CharacterPanel"), font: GUIStyle.LargeFont); var characterLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), GetCharacterEditorTranslation("CharacterPanel"), font: GUIStyle.LargeFont);
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(0.2f, 0.7f), characterLabel.RectTransform, Anchor.CenterRight), style: "GUINotificationButton")
{
OnClicked = (btn, userdata) => { GameMain.Instance.ShowEditorDisclaimer(); return true; }
};
var characterDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.2f), content.RectTransform) var characterDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.2f), content.RectTransform)
{ {
@@ -4007,7 +3996,7 @@ namespace Barotrauma.CharacterEditor
}; };
}).Draw(spriteBatch, deltaTime); }).Draw(spriteBatch, deltaTime);
} }
else else if (groundedParams != null)
{ {
GetAnimationWidget("HeadPosition", color, Color.Black, initMethod: w => GetAnimationWidget("HeadPosition", color, Color.Black, initMethod: w =>
{ {
@@ -4116,7 +4105,7 @@ namespace Barotrauma.CharacterEditor
}; };
}).Draw(spriteBatch, deltaTime); }).Draw(spriteBatch, deltaTime);
} }
else else if (groundedParams != null)
{ {
GetAnimationWidget("TorsoPosition", color, Color.Black, initMethod: w => GetAnimationWidget("TorsoPosition", color, Color.Black, initMethod: w =>
{ {

View File

@@ -820,6 +820,15 @@ namespace Barotrauma
}; };
valueInput.Text = newValue?.ToString() ?? "<type here>"; valueInput.Text = newValue?.ToString() ?? "<type here>";
} }
else if (type == typeof(Identifier))
{
GUITextBox valueInput = new GUITextBox(new RectTransform(Vector2.One, layout.RectTransform), newValue?.ToString() ?? string.Empty);
valueInput.OnTextChanged += (component, o) =>
{
newValue = new Identifier(o);
return true;
};
}
else if (type == typeof(float) || type == typeof(int)) else if (type == typeof(float) || type == typeof(int))
{ {
GUINumberInput valueInput = new GUINumberInput(new RectTransform(Vector2.One, layout.RectTransform), NumberType.Float) { FloatValue = (float) (newValue ?? 0.0f) }; GUINumberInput valueInput = new GUINumberInput(new RectTransform(Vector2.One, layout.RectTransform), NumberType.Float) { FloatValue = (float) (newValue ?? 0.0f) };

View File

@@ -1,6 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
@@ -27,7 +26,7 @@ namespace Barotrauma
public Effect ThresholdTintEffect { get; private set; } public Effect ThresholdTintEffect { get; private set; }
public Effect BlueprintEffect { get; set; } public Effect BlueprintEffect { get; set; }
public GameScreen(GraphicsDevice graphics, ContentManager content) public GameScreen(GraphicsDevice graphics)
{ {
cam = new Camera(); cam = new Camera();
cam.Translate(new Vector2(-10.0f, 50.0f)); cam.Translate(new Vector2(-10.0f, 50.0f));
@@ -38,20 +37,13 @@ namespace Barotrauma
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
}; };
Effect LoadEffect(string path)
=> content.Load<Effect>(path
#if LINUX || OSX
+"_opengl"
#endif
);
//var blurEffect = LoadEffect("Effects/blurshader"); //var blurEffect = LoadEffect("Effects/blurshader");
damageEffect = LoadEffect("Effects/damageshader"); damageEffect = EffectLoader.Load("Effects/damageshader");
PostProcessEffect = LoadEffect("Effects/postprocess"); PostProcessEffect = EffectLoader.Load("Effects/postprocess");
GradientEffect = LoadEffect("Effects/gradientshader"); GradientEffect = EffectLoader.Load("Effects/gradientshader");
GrainEffect = LoadEffect("Effects/grainshader"); GrainEffect = EffectLoader.Load("Effects/grainshader");
ThresholdTintEffect = LoadEffect("Effects/thresholdtint"); ThresholdTintEffect = EffectLoader.Load("Effects/thresholdtint");
BlueprintEffect = LoadEffect("Effects/blueprintshader"); BlueprintEffect = EffectLoader.Load("Effects/blueprintshader");
damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png"); damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png");
damageEffect.Parameters["xStencil"].SetValue(damageStencil); damageEffect.Parameters["xStencil"].SetValue(damageStencil);

View File

@@ -893,7 +893,25 @@ namespace Barotrauma
GameMain.ResetNetLobbyScreen(); GameMain.ResetNetLobbyScreen();
try try
{ {
string exeName = serverExecutableDropdown.SelectedComponent?.UserData is ServerExecutableFile f ? f.Path.Value : "DedicatedServer"; string fileName;
if (serverExecutableDropdown.SelectedComponent?.UserData is ServerExecutableFile f &&
f.ContentPackage != GameMain.VanillaContent)
{
fileName = Path.Combine(
Path.GetDirectoryName(f.Path.Value),
Path.GetFileNameWithoutExtension(f.Path.Value));
#if WINDOWS
fileName += ".exe";
#endif
}
else
{
#if WINDOWS
fileName = "DedicatedServer.exe";
#else
fileName = "./DedicatedServer";
#endif
}
string arguments = "-name \"" + ToolBox.EscapeCharacters(name) + "\"" + string arguments = "-name \"" + ToolBox.EscapeCharacters(name) + "\"" +
" -public " + isPublicBox.Selected.ToString() + " -public " + isPublicBox.Selected.ToString() +
@@ -917,19 +935,10 @@ namespace Barotrauma
} }
int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1); int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
arguments += " -ownerkey " + ownerKey; arguments += " -ownerkey " + ownerKey;
string filename = Path.Combine(
Path.GetDirectoryName(exeName),
Path.GetFileNameWithoutExtension(exeName));
#if WINDOWS
filename += ".exe";
#else
filename = "./" + exeName;
#endif
var processInfo = new ProcessStartInfo var processInfo = new ProcessStartInfo
{ {
FileName = filename, FileName = fileName,
Arguments = arguments, Arguments = arguments,
WorkingDirectory = Directory.GetCurrentDirectory(), WorkingDirectory = Directory.GetCurrentDirectory(),
#if !DEBUG #if !DEBUG
@@ -1005,7 +1014,7 @@ namespace Barotrauma
|| item.IsDownloadPending || item.IsDownloadPending
|| (item.InstallTime.TryGetValue(out var workshopInstallTime) || (item.InstallTime.TryGetValue(out var workshopInstallTime)
&& pkg.InstallTime.TryUnwrap(out var localInstallTime) && pkg.InstallTime.TryUnwrap(out var localInstallTime)
&& localInstallTime < workshopInstallTime))); && localInstallTime.ToUtcValue() < workshopInstallTime)));
modUpdateStatus = (DateTime.Now + ModUpdateInterval, count); modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
} }

View File

@@ -189,7 +189,8 @@ namespace Barotrauma
} }
public IReadOnlyList<SubmarineInfo> GetSubList() public IReadOnlyList<SubmarineInfo> GetSubList()
=> SubList.Content.Children.Select(c => c.UserData as SubmarineInfo).ToArray(); => (IReadOnlyList<SubmarineInfo>)GameMain.Client?.ServerSubmarines
?? Array.Empty<SubmarineInfo>();
public readonly GUIListBox PlayerList; public readonly GUIListBox PlayerList;
@@ -929,6 +930,8 @@ namespace Barotrauma
var modeTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Name, font: GUIStyle.SubHeadingFont); var modeTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Name, font: GUIStyle.SubHeadingFont);
var modeDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Description, font: GUIStyle.SmallFont, wrap: true); var modeDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Description, font: GUIStyle.SmallFont, wrap: true);
//leave some padding for the vote count text
modeDescription.Padding = new Vector4(modeDescription.Padding.X, modeDescription.Padding.Y, GUI.IntScale(30), modeDescription.Padding.W);
modeTitle.HoverColor = modeDescription.HoverColor = modeTitle.SelectedColor = modeDescription.SelectedColor = Color.Transparent; modeTitle.HoverColor = modeDescription.HoverColor = modeTitle.SelectedColor = modeDescription.SelectedColor = Color.Transparent;
modeTitle.HoverTextColor = modeDescription.HoverTextColor = modeTitle.TextColor; modeTitle.HoverTextColor = modeDescription.HoverTextColor = modeTitle.TextColor;
modeTitle.TextColor = modeDescription.TextColor = modeTitle.TextColor * 0.5f; modeTitle.TextColor = modeDescription.TextColor = modeTitle.TextColor * 0.5f;
@@ -981,7 +984,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Client.RequestSelectMode(ModeList.Content.GetChildIndex(ModeList.Content.GetChildByUserData(GameModePreset.Sandbox))); GameMain.Client.RequestRoundEnd(save: false, quitCampaign: true);
} }
return true; return true;
} }
@@ -3291,16 +3294,15 @@ namespace Barotrauma
{ {
//campaign running //campaign running
settingsBlocker.Visible = true; settingsBlocker.Visible = true;
CampaignFrame.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign); CampaignFrame.Visible = QuitCampaignButton.Enabled = CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageRound);
ContinueCampaignButton.Enabled = !GameMain.Client.GameStarted && (GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || GameMain.Client.HasPermission(ClientPermissions.ManageRound)); ContinueCampaignButton.Enabled = !GameMain.Client.GameStarted && CampaignFrame.Visible;
QuitCampaignButton.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign);
CampaignSetupFrame.Visible = false; CampaignSetupFrame.Visible = false;
} }
else else
{ {
CampaignFrame.Visible = false; CampaignFrame.Visible = false;
CampaignSetupFrame.Visible = true; CampaignSetupFrame.Visible = true;
if (!GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)) if (!CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageRound))
{ {
CampaignSetupFrame.ClearChildren(); CampaignSetupFrame.ClearChildren();
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.5f), CampaignSetupFrame.RectTransform, Anchor.Center), new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.5f), CampaignSetupFrame.RectTransform, Anchor.Center),
@@ -3364,7 +3366,7 @@ namespace Barotrauma
CampaignFrame.Visible = CampaignSetupFrame.Visible = false; CampaignFrame.Visible = CampaignSetupFrame.Visible = false;
} }
RefreshEnabledElements(); RefreshEnabledElements();
if (enabled) if (enabled && SelectedMode != GameModePreset.MultiPlayerCampaign)
{ {
ModeList.Select(GameModePreset.MultiPlayerCampaign, GUIListBox.Force.Yes); ModeList.Select(GameModePreset.MultiPlayerCampaign, GUIListBox.Force.Yes);
} }

View File

@@ -543,13 +543,6 @@ namespace Barotrauma
} }
}; };
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), paddedTopPanel.RectTransform, Anchor.CenterRight), style: "GUINotificationButton")
{
IgnoreLayoutGroups = true,
OnClicked = (btn, userdata) => { GameMain.Instance.ShowEditorDisclaimer(); return true; }
};
disclaimerBtn.RectTransform.MaxSize = new Point(disclaimerBtn.Rect.Height);
TopPanel.RectTransform.MinSize = new Point(0, (int)(paddedTopPanel.RectTransform.Children.Max(c => c.MinSize.Y) / paddedTopPanel.RectTransform.RelativeSize.Y)); TopPanel.RectTransform.MinSize = new Point(0, (int)(paddedTopPanel.RectTransform.Children.Max(c => c.MinSize.Y) / paddedTopPanel.RectTransform.RelativeSize.Y));
paddedTopPanel.Recalculate(); paddedTopPanel.Recalculate();
@@ -1428,7 +1421,7 @@ namespace Barotrauma
else if (MainSub == null) else if (MainSub == null)
{ {
var subInfo = new SubmarineInfo(); var subInfo = new SubmarineInfo();
MainSub = new Submarine(subInfo); MainSub = new Submarine(subInfo, showErrorMessages: false);
} }
MainSub.UpdateTransform(interpolate: false); MainSub.UpdateTransform(interpolate: false);
@@ -1465,11 +1458,6 @@ namespace Barotrauma
ImageManager.OnEditorSelected(); ImageManager.OnEditorSelected();
ReconstructLayers(); ReconstructLayers();
if (!GameSettings.CurrentConfig.EditorDisclaimerShown)
{
GameMain.Instance.ShowEditorDisclaimer();
}
} }
public override void OnFileDropped(string filePath, string extension) public override void OnFileDropped(string filePath, string extension)
@@ -2729,11 +2717,13 @@ namespace Barotrauma
previewImageButtonHolder.RectTransform.MinSize = new Point(0, previewImageButtonHolder.RectTransform.Children.Max(c => c.MinSize.Y)); previewImageButtonHolder.RectTransform.MinSize = new Point(0, previewImageButtonHolder.RectTransform.Children.Max(c => c.MinSize.Y));
var contentPackageTabber = new GUILayoutGroup(new RectTransform((1.0f, 0.06f), rightColumn.RectTransform), isHorizontal: true); var contentPackageTabber = new GUILayoutGroup(new RectTransform((1.0f, 0.075f), rightColumn.RectTransform), isHorizontal: true);
GUIButton createTabberBtn(string labelTag) GUIButton createTabberBtn(string labelTag)
{ {
var btn = new GUIButton(new RectTransform((0.5f, 1.0f), contentPackageTabber.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter), TextManager.Get(labelTag), style: "GUITabButton"); var btn = new GUIButton(new RectTransform((0.5f, 1.0f), contentPackageTabber.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter), TextManager.Get(labelTag), style: "GUITabButton");
btn.TextBlock.Wrap = true;
btn.TextBlock.SetTextPos();
btn.RectTransform.MaxSize = RectTransform.MaxPoint; btn.RectTransform.MaxSize = RectTransform.MaxPoint;
btn.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); btn.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
btn.Font = GUIStyle.SmallFont; btn.Font = GUIStyle.SmallFont;
@@ -5638,8 +5628,7 @@ namespace Barotrauma
MouseDragStart = Vector2.Zero; MouseDragStart = Vector2.Zero;
} }
if (!saveAssemblyFrame.Rect.Contains(PlayerInput.MousePosition) if ((GUI.MouseOn == null || !GUI.MouseOn.IsChildOf(TopPanel))
&& !snapToGridFrame.Rect.Contains(PlayerInput.MousePosition)
&& dummyCharacter?.SelectedItem == null && !WiringMode && dummyCharacter?.SelectedItem == null && !WiringMode
&& (GUI.MouseOn == null || MapEntity.SelectedAny || MapEntity.SelectionPos != Vector2.Zero)) && (GUI.MouseOn == null || MapEntity.SelectedAny || MapEntity.SelectionPos != Vector2.Zero))
{ {

View File

@@ -1,6 +1,5 @@
#nullable enable #nullable enable
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -34,7 +33,7 @@ namespace Barotrauma
{ {
BlueprintEffect.Dispose(); BlueprintEffect.Dispose();
GameMain.Instance.Content.Unload(); GameMain.Instance.Content.Unload();
BlueprintEffect = GameMain.Instance.Content.Load<Effect>("Effects/blueprintshader_opengl"); BlueprintEffect = EffectLoader.Load("Effects/blueprintshader");
GameMain.GameScreen.BlueprintEffect = BlueprintEffect; GameMain.GameScreen.BlueprintEffect = BlueprintEffect;
return true; return true;
} }

View File

@@ -9,7 +9,7 @@ using System.Diagnostics;
namespace Barotrauma namespace Barotrauma
{ {
class SerializableEntityEditor : GUIComponent sealed class SerializableEntityEditor : GUIComponent
{ {
private readonly int elementHeight; private readonly int elementHeight;
private readonly GUILayoutGroup layoutGroup; private readonly GUILayoutGroup layoutGroup;
@@ -399,10 +399,6 @@ namespace Barotrauma
{ {
propertyField = CreateBoolField(entity, property, boolVal, displayName, toolTip); propertyField = CreateBoolField(entity, property, boolVal, displayName, toolTip);
} }
else if (value is string stringVal)
{
propertyField = CreateStringField(entity, property, stringVal, displayName, toolTip);
}
else if (value.GetType().IsEnum) else if (value.GetType().IsEnum)
{ {
if (value.GetType().IsDefined(typeof(FlagsAttribute), inherit: false)) if (value.GetType().IsDefined(typeof(FlagsAttribute), inherit: false))
@@ -450,6 +446,10 @@ namespace Barotrauma
{ {
propertyField = CreateStringArrayField(entity, property, a, displayName, toolTip); propertyField = CreateStringArrayField(entity, property, a, displayName, toolTip);
} }
else if (value is string or Identifier)
{
propertyField = CreateStringField(entity, property, value.ToString(), displayName, toolTip);
}
return propertyField; return propertyField;
} }
@@ -696,7 +696,7 @@ namespace Barotrauma
propertyBox.OnEnterPressed += (box, text) => OnApply(box); propertyBox.OnEnterPressed += (box, text) => OnApply(box);
refresh += () => refresh += () =>
{ {
if (!propertyBox.Selected) { propertyBox.Text = (string)property.GetValue(entity); } if (!propertyBox.Selected) { propertyBox.Text = property.GetValue(entity).ToString(); }
}; };
bool OnApply(GUITextBox textBox) bool OnApply(GUITextBox textBox)
@@ -714,7 +714,7 @@ namespace Barotrauma
if (SetPropertyValue(property, entity, textBox.Text)) if (SetPropertyValue(property, entity, textBox.Text))
{ {
TrySendNetworkUpdate(entity, property); TrySendNetworkUpdate(entity, property);
textBox.Text = (string) property.GetValue(entity); textBox.Text = property.GetValue(entity).ToString();
textBox.Flash(GUIStyle.Green, flashDuration: 1f); textBox.Flash(GUIStyle.Green, flashDuration: 1f);
} }
//restore the entities that were selected before applying //restore the entities that were selected before applying

View File

@@ -648,6 +648,12 @@ namespace Barotrauma.Sounds
if (isConnected == 0) if (isConnected == 0)
{ {
if (!GameMain.Instance.HasLoaded)
{
//wait for loading to finish so we don't start releasing and reloading sounds when they're being loaded,
//or throw an error mid-loading that'd prevent the content package from being enabled
return;
}
DebugConsole.ThrowError("Playback device has been disconnected. You can select another available device in the settings."); DebugConsole.ThrowError("Playback device has been disconnected. You can select another available device in the settings.");
SetAudioOutputDevice("<disconnected>"); SetAudioOutputDevice("<disconnected>");
Disconnected = true; Disconnected = true;

View File

@@ -865,17 +865,18 @@ namespace Barotrauma
public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, IEnumerable<Identifier> tags = null) public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, IEnumerable<Identifier> tags = null)
{ {
var suitableSounds = damageSounds.Where(s =>
s.DamageType == damageType &&
(s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag))));
//if the damage is too low for any sound, don't play anything //if the damage is too low for any sound, don't play anything
if (damageSounds.All(d => damage < d.DamageRange.X)) { return; } if (suitableSounds.All(d => damage < d.DamageRange.X)) { return; }
//allow the damage to differ by 10 from the configured damage range, //allow the damage to differ by 10 from the configured damage range,
//so the same amount of damage doesn't always play the same sound //so the same amount of damage doesn't always play the same sound
float randomizedDamage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f); float randomizedDamage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
suitableSounds = suitableSounds.Where(s =>
var suitableSounds = damageSounds.Where(s => s.DamageRange == Vector2.Zero || (randomizedDamage >= s.DamageRange.X && randomizedDamage <= s.DamageRange.Y));
s.DamageType == damageType &&
(s.DamageRange == Vector2.Zero || (randomizedDamage >= s.DamageRange.X && randomizedDamage <= s.DamageRange.Y)) &&
(s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag))));
var damageSound = suitableSounds.GetRandomUnsynced(); var damageSound = suitableSounds.GetRandomUnsynced();
damageSound?.Sound?.Play(1.0f, range, position, muffle: !damageSound.IgnoreMuffling && ShouldMuffleSound(Character.Controlled, position, range, null)); damageSound?.Sound?.Play(1.0f, range, position, muffle: !damageSound.IgnoreMuffling && ShouldMuffleSound(Character.Controlled, position, range, null));

View File

@@ -42,12 +42,7 @@ namespace Barotrauma
{ {
if (effect == null) if (effect == null)
{ {
#if WINDOWS effect = EffectLoader.Load("Effects/deformshader");
effect = GameMain.Instance.Content.Load<Effect>("Effects/deformshader");
#endif
#if LINUX || OSX
effect = GameMain.Instance.Content.Load<Effect>("Effects/deformshader_opengl");
#endif
} }
Invert = invert; Invert = invert;

View File

@@ -202,6 +202,7 @@ namespace Barotrauma.Steam
ContentPackageManager.EnabledPackages.Core!, ContentPackageManager.EnabledPackages.Core!,
(p) => { }, (p) => { },
heightScale: 1.0f / 13.0f); heightScale: 1.0f / 13.0f);
enabledCoreDropdown.AllowNonText = true;
Label(topLeft, "", GUIStyle.SubHeadingFont, heightScale: 1.0f); Label(topLeft, "", GUIStyle.SubHeadingFont, heightScale: 1.0f);
topRight.ChildAnchor = Anchor.CenterLeft; topRight.ChildAnchor = Anchor.CenterLeft;
@@ -535,34 +536,119 @@ namespace Barotrauma.Steam
bulkUpdateButton.Enabled = false; bulkUpdateButton.Enabled = false;
bulkUpdateButton.ToolTip = ""; bulkUpdateButton.ToolTip = "";
ContentPackageManager.UpdateContentPackageList(); ContentPackageManager.UpdateContentPackageList();
var corePackages = ContentPackageManager.CorePackages.ToArray();
var currentCore = ContentPackageManager.EnabledPackages.Core!;
SwapDropdownValues<CorePackage>(enabledCoreDropdown, SwapDropdownValues<CorePackage>(enabledCoreDropdown,
(p) => p.Name, (p) => p.Name,
ContentPackageManager.CorePackages.ToArray(), corePackages,
ContentPackageManager.EnabledPackages.Core!, currentCore,
(p) => (p) =>
{ {
// Manually set dropdown text because
// adding buttons to the elements breaks
// this part of the dropdown code
enabledCoreDropdown.Text = p.Name;
enabledCoreDropdown.ButtonTextColor = enabledCoreDropdown.ButtonTextColor =
p.HasAnyErrors p.HasAnyErrors
? GUIStyle.Red ? GUIStyle.Red
: GUIStyle.TextColorNormal; : GUIStyle.TextColorNormal;
}); });
enabledCoreDropdown.ListBox.Content.Children
.OfType<GUITextBlock>() void addButtonForMod(ContentPackage mod, GUILayoutGroup parent)
.ForEach(tb =>
CreateModErrorInfo(
(tb.UserData as ContentPackage)!,
tb,
tb));
void addRegularModToList(RegularPackage mod, GUIListBox list)
{ {
var modFrame = new GUIFrame(new RectTransform((1.0f, 0.08f), list.Content.RectTransform), if (ContentPackageManager.LocalPackages.Contains(mod))
{
var editButton = new GUIButton(new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: "WorkshopMenu.EditButton")
{
OnClicked = (button, o) =>
{
ToolBox.OpenFileWithShell(mod.Dir);
return false;
},
ToolTip = TextManager.Get("OpenLocalModInExplorer")
};
}
else if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
var infoButton = new GUIButton(
new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: null)
{
CanBeSelected = false,
OnClicked = (button, o) =>
{
PrepareToShowModInfo(mod);
return false;
}
};
if (!SteamManager.IsInitialized)
{
infoButton.Enabled = false;
}
TaskPool.AddIfNotFound(
$"DetermineUpdateRequired{mod.UgcId}",
mod.IsUpToDate(),
t =>
{
if (!t.TryGetResult(out bool isUpToDate)) { return; }
if (isUpToDate) { return; }
infoButton.CanBeSelected = true;
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
bulkUpdateButton.Enabled = true;
bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
});
}
}
GUILayoutGroup createBaseModListUi(ContentPackage mod, GUIListBox listBox, float height)
{
var modFrame = new GUIFrame(new RectTransform((1.0f, height), listBox.Content.RectTransform),
style: "ListBoxElement") style: "ListBoxElement")
{ {
UserData = mod UserData = mod
}; };
var frameContent = new GUILayoutGroup(new RectTransform((0.95f, 0.9f), modFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var modNameScissor = new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform))
{
CanBeFocused = false
};
var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform),
text: mod.Name)
{
CanBeFocused = false
};
CreateModErrorInfo(mod, modFrame, modName);
addButtonForMod(mod, frameContent);
return frameContent;
}
foreach (var element in enabledCoreDropdown.ListBox.Content.Children.ToArray())
{
enabledCoreDropdown.ListBox.RemoveChild(element);
if (element.UserData is not ContentPackage mod) { continue; }
createBaseModListUi(mod, enabledCoreDropdown.ListBox, 0.24f);
}
enabledCoreDropdown.Select(corePackages.IndexOf(currentCore));
void addRegularModToList(RegularPackage mod, GUIListBox list)
{
var frameContent = createBaseModListUi(mod, list, 0.08f);
var modFrame = frameContent.Parent;
var contextMenuHandler = new GUICustomComponent(new RectTransform(Vector2.Zero, modFrame.RectTransform), var contextMenuHandler = new GUICustomComponent(new RectTransform(Vector2.Zero, modFrame.RectTransform),
onUpdate: (f, component) => onUpdate: (f, component) =>
{ {
@@ -639,76 +725,13 @@ namespace Barotrauma.Steam
contextMenuOptions.ToArray()); contextMenuOptions.ToArray());
} }
}); });
var frameContent = new GUILayoutGroup(new RectTransform((0.95f, 0.9f), modFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var dragIndicator = new GUIButton(new RectTransform((0.5f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight), var dragIndicator = new GUIButton(new RectTransform((0.5f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "GUIDragIndicator") style: "GUIDragIndicator")
{ {
CanBeFocused = false CanBeFocused = false
}; };
dragIndicator.RectTransform.SetAsFirstChild();
var modNameScissor = new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform))
{
CanBeFocused = false
};
var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform),
text: mod.Name)
{
CanBeFocused = false
};
CreateModErrorInfo(mod, modFrame, modName);
if (ContentPackageManager.LocalPackages.Contains(mod))
{
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: "WorkshopMenu.EditButton")
{
OnClicked = (button, o) =>
{
ToolBox.OpenFileWithShell(mod.Dir);
return false;
},
ToolTip = TextManager.Get("OpenLocalModInExplorer")
};
}
else if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
var infoButton = new GUIButton(
new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: null)
{
CanBeSelected = false,
OnClicked = (button, o) =>
{
PrepareToShowModInfo(mod);
return false;
}
};
if (!SteamManager.IsInitialized)
{
infoButton.Enabled = false;
}
TaskPool.AddIfNotFound(
$"DetermineUpdateRequired{mod.UgcId}",
mod.IsUpToDate(),
t =>
{
if (!t.TryGetResult(out bool isUpToDate)) { return; }
if (!isUpToDate)
{
infoButton.CanBeSelected = true;
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
bulkUpdateButton.Enabled = true;
bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
}
});
}
} }
void addRegularModsToList(IEnumerable<RegularPackage> mods, GUIListBox list) void addRegularModsToList(IEnumerable<RegularPackage> mods, GUIListBox list)
@@ -729,7 +752,7 @@ namespace Barotrauma.Steam
.Where(p => ContentPackageManager.RegularPackages.Contains(p))) .Where(p => ContentPackageManager.RegularPackages.Contains(p)))
.ToArray(); .ToArray();
var disabledMods = ContentPackageManager.RegularPackages.Where(p => !enabledMods.Contains(p)); var disabledMods = ContentPackageManager.RegularPackages.Where(p => !enabledMods.Contains(p));
addRegularModsToList(enabledMods, enabledRegularModsList); addRegularModsToList(enabledMods, enabledRegularModsList);
if (refreshDisabled) { addRegularModsToList(disabledMods, disabledRegularModsList); } if (refreshDisabled) { addRegularModsToList(disabledMods, disabledRegularModsList); }
@@ -747,7 +770,7 @@ namespace Barotrauma.Steam
var mod = child.UserData as RegularPackage; var mod = child.UserData as RegularPackage;
if (mod is null || !ContentPackageManager.WorkshopPackages.Contains(mod)) { continue; } if (mod is null || !ContentPackageManager.WorkshopPackages.Contains(mod)) { continue; }
if (!mod.UgcId.TryUnwrap(out var ugcId)) { continue; } if (!mod.UgcId.TryUnwrap(out var ugcId)) { continue; }
if (!(ugcId is SteamWorkshopId workshopId)) { continue; } if (ugcId is not SteamWorkshopId workshopId) { continue; }
var btn = child.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last(); var btn = child.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
if (btn is null) { continue; } if (btn is null) { continue; }

View File

@@ -218,6 +218,20 @@ namespace Barotrauma.Steam
var descriptionTextBox var descriptionTextBox
= ScrollableTextBox(rightTop, 6.0f, workshopItem.Description ?? string.Empty); = ScrollableTextBox(rightTop, 6.0f, workshopItem.Description ?? string.Empty);
if (workshopItem.Id != 0)
{
TaskPool.Add(
$"GetFullDescription{workshopItem.Id}",
SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? itemWithDescription)) { return; }
descriptionTextBox.Text = itemWithDescription?.Description ?? descriptionTextBox.Text;
descriptionTextBox.Deselect();
});
}
var (leftBottom, _, rightBottom) var (leftBottom, _, rightBottom)
= CreateSidebars(mainLayout, leftWidth: 0.49f, centerWidth: 0.01f, rightWidth: 0.5f, height: 0.5f); = CreateSidebars(mainLayout, leftWidth: 0.49f, centerWidth: 0.01f, rightWidth: 0.5f, height: 0.5f);
leftBottom.Stretch = true; leftBottom.Stretch = true;

View File

@@ -68,10 +68,13 @@ namespace Barotrauma.Steam
} }
protected static void SwapDropdownValues<T>( protected static void SwapDropdownValues<T>(
GUIDropDown dropdown, Func<T, LocalizedString> textFunc, IReadOnlyList<T> values, T currentValue, GUIDropDown dropdown,
Func<T, LocalizedString> textFunc,
IReadOnlyList<T> values,
T currentValue,
Action<T> setter) Action<T> setter)
{ {
if (dropdown.ListBox.Content.Children.Any(c => !(c.UserData is T))) if (dropdown.ListBox.Content.Children.Any(c => c.UserData is not T))
{ {
throw new Exception("SwapValues must preserve the type of the dropdown's userdata"); throw new Exception("SwapValues must preserve the type of the dropdown's userdata");
} }

View File

@@ -2,7 +2,7 @@
namespace Barotrauma namespace Barotrauma
{ {
partial class UpgradePrefab sealed partial class UpgradePrefab
{ {
public readonly ImmutableArray<DecorativeSprite> DecorativeSprites = new ImmutableArray<DecorativeSprite>(); public readonly ImmutableArray<DecorativeSprite> DecorativeSprites = new ImmutableArray<DecorativeSprite>();
public Sprite Sprite { get; private set; } public Sprite Sprite { get; private set; }

View File

@@ -0,0 +1,12 @@
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma;
static class EffectLoader
{
public static Effect Load(string path)
=> GameMain.Instance.Content.Load<Effect>(path
#if LINUX || OSX
+"_opengl"
#endif
);
}

View File

@@ -23,36 +23,55 @@ namespace Barotrauma
public static void ConvertMasterLocalizationKit(string outputTextsDirectory, string outputConversationsDirectory, bool convertConversations) public static void ConvertMasterLocalizationKit(string outputTextsDirectory, string outputConversationsDirectory, bool convertConversations)
{ {
string textFilePath = Path.Combine(infoTextPath, "Texts.csv"); List<string> languages = new List<string>();
string conversationFilePath = Path.Combine(infoTextPath, "NPCConversations.csv"); for (int i = 0; i < 2; i++)
{
string textFilePath;
string outputFileName;
switch (i)
{
case 0:
textFilePath = Path.Combine(infoTextPath, "Texts.csv");
outputFileName = "Vanilla.xml";
break;
case 1:
textFilePath = Path.Combine(infoTextPath, "EditorTexts.csv");
outputFileName = "VanillaEditorTexts.xml";
break;
default:
throw new NotImplementedException();
}
Dictionary<string, List<string>> xmlContent; Dictionary<string, List<string>> xmlContent;
try try
{ {
xmlContent = ConvertInfoTextToXML(File.ReadAllLines(textFilePath, Encoding.UTF8)); xmlContent = ConvertInfoTextToXML(File.ReadAllLines(textFilePath, Encoding.UTF8));
} }
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath, e); DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath, e);
return; return;
} }
if (xmlContent == null) if (xmlContent == null)
{ {
DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath); DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath);
return; return;
} }
foreach (string language in xmlContent.Keys) foreach (string language in xmlContent.Keys)
{ {
string languageNoWhitespace = language.Replace(" ", ""); languages.Add(language);
string xmlFileFullPath = Path.Combine(outputTextsDirectory, $"{languageNoWhitespace}/{languageNoWhitespace}Vanilla.xml"); string languageNoWhitespace = language.Replace(" ", "");
File.WriteAllLines(xmlFileFullPath, xmlContent[language], Encoding.UTF8); string xmlFileFullPath = Path.Combine(outputTextsDirectory, $"{languageNoWhitespace}/{languageNoWhitespace}{outputFileName}");
DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath); File.WriteAllLines(xmlFileFullPath, xmlContent[language], Encoding.UTF8);
DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath);
}
} }
if (convertConversations) if (convertConversations)
{ {
string conversationFilePath = Path.Combine(infoTextPath, "NPCConversations.csv");
var conversationLinesAll = File.ReadAllLines(conversationFilePath, Encoding.UTF8); var conversationLinesAll = File.ReadAllLines(conversationFilePath, Encoding.UTF8);
foreach (string language in xmlContent.Keys) foreach (string language in languages)
{ {
List<string> convXmlContent = ConvertConversationsToXML(conversationLinesAll, language); List<string> convXmlContent = ConvertConversationsToXML(conversationLinesAll, language);
if (convXmlContent == null) if (convXmlContent == null)
@@ -61,7 +80,7 @@ namespace Barotrauma
continue; continue;
} }
string languageNoWhitespace = language.Replace(" ", ""); string languageNoWhitespace = language.Replace(" ", "");
string xmlFileFullPath = Path.Combine(outputTextsDirectory, $"NpcConversations_{languageNoWhitespace}.xml"); string xmlFileFullPath = Path.Combine(outputConversationsDirectory, languageNoWhitespace, $"NpcConversations_{languageNoWhitespace}.xml");
File.WriteAllLines(xmlFileFullPath, convXmlContent, Encoding.UTF8); File.WriteAllLines(xmlFileFullPath, convXmlContent, Encoding.UTF8);
DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath); DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath);
} }
@@ -339,7 +358,8 @@ namespace Barotrauma
string[] headerSplit = csvContent[0].Split(separator); string[] headerSplit = csvContent[0].Split(separator);
for (int i = 0; i < headerSplit.Length; i++) for (int i = 0; i < headerSplit.Length; i++)
{ {
if (headerSplit[i] == language) if (headerSplit[i] == language ||
(language == "English" && headerSplit[i]== "Line (Original)"))
{ {
languageColumn = i; languageColumn = i;
break; break;
@@ -348,6 +368,7 @@ namespace Barotrauma
xmlContent.Add($"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\" nowhitespace=\"{nowhitespace}\">"); xmlContent.Add($"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\" nowhitespace=\"{nowhitespace}\">");
conversationClosingIndent.Clear();
int conversationStart = 1; int conversationStart = 1;
xmlContent.Add(string.Empty); xmlContent.Add(string.Empty);
@@ -399,9 +420,9 @@ namespace Barotrauma
{ {
string[] nextConversationElement = csvContent[i + 1].Split(separator); string[] nextConversationElement = csvContent[i + 1].Split(separator);
if (nextConversationElement[1] != string.Empty) if (nextConversationElement[3] != string.Empty)
{ {
nextDepth = int.Parse(nextConversationElement[2]); nextDepth = int.Parse(nextConversationElement[3]);
nextIsSubConvo = nextDepth > depthIndex; nextIsSubConvo = nextDepth > depthIndex;
} }
@@ -421,7 +442,12 @@ namespace Barotrauma
} }
else else
{ {
//end of file, close remaining xml tags
xmlContent.Add(element.TrimEnd() + "/>"); xmlContent.Add(element.TrimEnd() + "/>");
for (int j = depthIndex - 1; j >= 0; j--)
{
HandleClosingElements(xmlContent, j);
}
} }
} }
@@ -433,12 +459,12 @@ namespace Barotrauma
private static void HandleClosingElements(List<string> xmlContent, int targetDepth) private static void HandleClosingElements(List<string> xmlContent, int targetDepth)
{ {
if (conversationClosingIndent.Count == 0) return; if (conversationClosingIndent.Count == 0) { return; }
for (int k = conversationClosingIndent.Count - 1; k >= 0; k--) for (int k = conversationClosingIndent.Count - 1; k >= 0; k--)
{ {
int currentIndent = conversationClosingIndent[k]; int currentIndent = conversationClosingIndent[k];
if (currentIndent < targetDepth) break; if (currentIndent < targetDepth) { break; }
xmlContent.Add($"{GetIndenting(currentIndent)}</Conversation>"); xmlContent.Add($"{GetIndenting(currentIndent)}</Conversation>");
conversationClosingIndent.RemoveAt(k); conversationClosingIndent.RemoveAt(k);
} }

View File

@@ -7,28 +7,30 @@ using System.Text;
namespace Barotrauma namespace Barotrauma
{ {
class SpriteRecorder : ISpriteBatch, IDisposable sealed class SpriteRecorder : ISpriteBatch, IDisposable
{ {
private struct Command public readonly record struct Command(
Texture2D Texture,
VertexPositionColorTexture VertexBL,
VertexPositionColorTexture VertexBR,
VertexPositionColorTexture VertexTL,
VertexPositionColorTexture VertexTR,
float Depth,
Vector2 Min,
Vector2 Max,
int Index)
{ {
public readonly Texture2D Texture; public static Vector2 GetMinPosition(params VertexPositionColorTexture[] vertices)
public readonly VertexPositionColorTexture VertexBL; => new Vector2(
public readonly VertexPositionColorTexture VertexBR; MathUtils.Min(vertices.Select(v => v.Position.X).ToArray()),
public readonly VertexPositionColorTexture VertexTL; MathUtils.Min(vertices.Select(v => v.Position.Y).ToArray()));
public readonly VertexPositionColorTexture VertexTR;
public readonly float Depth; public static Vector2 GetMaxPosition(params VertexPositionColorTexture[] vertices)
public readonly Vector2 Min; => new Vector2(
public readonly Vector2 Max; MathUtils.Max(vertices.Select(v => v.Position.X).ToArray()),
public readonly int Index; MathUtils.Max(vertices.Select(v => v.Position.Y).ToArray()));
public bool Overlaps(Command other) public static Command FromTransform(
{
return
Min.X <= other.Max.X && Max.X >= other.Min.X &&
Min.Y <= other.Max.Y && Max.Y >= other.Min.Y;
}
public Command(
Texture2D texture, Texture2D texture,
Vector2 pos, Vector2 pos,
Rectangle srcRect, Rectangle srcRect,
@@ -46,15 +48,11 @@ namespace Barotrauma
int srcRectBottom = srcRect.Bottom; int srcRectBottom = srcRect.Bottom;
if (effects.HasFlag(SpriteEffects.FlipHorizontally)) if (effects.HasFlag(SpriteEffects.FlipHorizontally))
{ {
var temp = srcRectRight; (srcRectRight, srcRectLeft) = (srcRectLeft, srcRectRight);
srcRectRight = srcRectLeft;
srcRectLeft = temp;
} }
if (effects.HasFlag(SpriteEffects.FlipVertically)) if (effects.HasFlag(SpriteEffects.FlipVertically))
{ {
var temp = srcRectBottom; (srcRectBottom, srcRectTop) = (srcRectTop, srcRectBottom);
srcRectBottom = srcRectTop;
srcRectTop = temp;
} }
rotation = MathHelper.ToRadians(rotation); rotation = MathHelper.ToRadians(rotation);
@@ -68,59 +66,63 @@ namespace Barotrauma
pos.X -= origin.X * scale.X * cos - origin.Y * scale.Y * sin; pos.X -= origin.X * scale.X * cos - origin.Y * scale.Y * sin;
pos.Y -= origin.Y * scale.Y * cos + origin.X * scale.X * sin; pos.Y -= origin.Y * scale.Y * cos + origin.X * scale.X * sin;
Texture = texture; var vertexTl = new VertexPositionColorTexture
{
Color = color,
Position = new Vector3(pos.X, pos.Y, 0f),
TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectTop / (float)texture.Height)
};
Depth = depth; var vertexTr = new VertexPositionColorTexture
{
Color = color,
Position = new Vector3(pos.X + wAdd.X, pos.Y + wAdd.Y, 0f),
TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectTop / (float)texture.Height)
};
VertexTL.Color = color; var vertexBl = new VertexPositionColorTexture
VertexTR.Color = color; {
VertexBL.Color = color; Color = color,
VertexBR.Color = color; Position = new Vector3(pos.X + hAdd.X, pos.Y + hAdd.Y, 0f),
TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectBottom / (float)texture.Height)
};
VertexTL.Position = new Vector3(pos.X, pos.Y, 0f); var vertexBr = new VertexPositionColorTexture
VertexTR.Position = new Vector3(pos.X + wAdd.X, pos.Y + wAdd.Y, 0f); {
VertexBL.Position = new Vector3(pos.X + hAdd.X, pos.Y + hAdd.Y, 0f); Color = color,
VertexBR.Position = new Vector3(pos.X + wAdd.X + hAdd.X, pos.Y + wAdd.Y + hAdd.Y, 0f); Position = new Vector3(pos.X + wAdd.X + hAdd.X, pos.Y + wAdd.Y + hAdd.Y, 0f),
TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectBottom / (float)texture.Height)
};
Min = new Vector2( var min = GetMinPosition(
MathUtils.Min vertexTl,
( vertexTr,
VertexTL.Position.X, vertexBl,
VertexTR.Position.X, vertexBr);
VertexBL.Position.X,
VertexBR.Position.X
),
MathUtils.Min
(
VertexTL.Position.Y,
VertexTR.Position.Y,
VertexBL.Position.Y,
VertexBR.Position.Y
));
Max = new Vector2( var max = GetMaxPosition(
MathUtils.Max vertexTl,
( vertexTr,
VertexTL.Position.X, vertexBl,
VertexTR.Position.X, vertexBr);
VertexBL.Position.X,
VertexBR.Position.X
),
MathUtils.Max
(
VertexTL.Position.Y,
VertexTR.Position.Y,
VertexBL.Position.Y,
VertexBR.Position.Y
));
VertexTL.TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectTop / (float)texture.Height); return new Command(
VertexTR.TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectTop / (float)texture.Height); texture,
VertexBL.TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectBottom / (float)texture.Height); vertexBl,
VertexBR.TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectBottom / (float)texture.Height); vertexBr,
vertexTl,
Index = index; vertexTr,
depth,
min,
max,
index);
}
public bool Overlaps(Command other)
{
return
Min.X <= other.Max.X && Max.X >= other.Min.X &&
Min.Y <= other.Max.Y && Max.Y >= other.Min.Y;
} }
} }
@@ -151,8 +153,8 @@ namespace Barotrauma
public static BasicEffect BasicEffect = null; public static BasicEffect BasicEffect = null;
private List<RecordedBuffer> recordedBuffers = new List<RecordedBuffer>(); private readonly List<RecordedBuffer> recordedBuffers = new List<RecordedBuffer>();
private List<Command> commandList = new List<Command>(); private readonly List<Command> commandList = new List<Command>();
private SpriteSortMode currentSortMode; private SpriteSortMode currentSortMode;
private IndexBuffer indexBuffer = null; private IndexBuffer indexBuffer = null;
@@ -170,16 +172,45 @@ namespace Barotrauma
currentSortMode = sortMode; currentSortMode = sortMode;
} }
public void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth) private void AppendCommand(Command command)
{ {
if (isDisposed) { return; } if (isDisposed) { return; }
Command command = new Command(texture, pos, srcRect ?? texture.Bounds, color, rotation, origin, scale, effects, depth, commandList?.Count ?? 0);
if (commandList.Count == 0) { Min = command.Min; Max = command.Max; } if (commandList.Count == 0) { Min = command.Min; Max = command.Max; }
Min = new Vector2(Math.Min(command.Min.X, Min.X), Math.Min(command.Min.Y, Min.Y)); Min = new Vector2(Math.Min(command.Min.X, Min.X), Math.Min(command.Min.Y, Min.Y));
Max = new Vector2(Math.Max(command.Max.X, Max.X), Math.Max(command.Max.Y, Max.Y)); Max = new Vector2(Math.Max(command.Max.X, Max.X), Math.Max(command.Max.Y, Max.Y));
commandList?.Add(command); commandList.Add(command);
}
public void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
{
if (isDisposed) { return; }
var command = Command.FromTransform(texture, pos, srcRect ?? texture.Bounds, color, rotation, origin, scale, effects, depth, commandList.Count);
AppendCommand(command);
}
public void Draw(Texture2D texture, VertexPositionColorTexture[] vertices, float layerDepth, int? count = null)
{
if (isDisposed) { return; }
int iters = count ?? (vertices.Length / 4);
for (int i=0;i<iters;i++)
{
var subset = vertices[((i * 4) + 0)..((i * 4) + 4)];
var command = new Command(
texture,
subset[2],
subset[3],
subset[0],
subset[1],
layerDepth,
Command.GetMinPosition(subset),
Command.GetMaxPosition(subset),
commandList.Count);
AppendCommand(command);
}
} }
public void End() public void End()
@@ -309,15 +340,12 @@ namespace Barotrauma
public void Dispose() public void Dispose()
{ {
isDisposed = true; isDisposed = true;
if (recordedBuffers != null) foreach (var buffer in recordedBuffers)
{ {
foreach (var buffer in recordedBuffers) buffer.VertexBuffer.Dispose();
{
buffer.VertexBuffer.Dispose();
}
recordedBuffers.Clear(); recordedBuffers = null;
} }
commandList?.Clear(); commandList = null; recordedBuffers.Clear();
commandList.Clear();
indexBuffer?.Dispose(); indexBuffer = null; indexBuffer?.Dispose(); indexBuffer = null;
ReadyToRender = false; ReadyToRender = false;
} }

View File

@@ -6,3 +6,5 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "RECS0026:Possible unassigned object created by 'new'", Justification = "<Pending>", Scope = "member", Target = "~M:Barotrauma.GameSettings.CreateSettingsFrame")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "RECS0026:Possible unassigned object created by 'new'", Justification = "<Pending>", Scope = "member", Target = "~M:Barotrauma.GameSettings.CreateSettingsFrame")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "IDE0047")]

View File

@@ -11,7 +11,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.20.16.1</Version> <Version>0.21.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>

View File

@@ -11,7 +11,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.20.16.1</Version> <Version>0.21.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>

View File

@@ -79,3 +79,8 @@
/processorParam:DebugMode=Auto /processorParam:DebugMode=Auto
/build:blueprintshader.fx /build:blueprintshader.fx
#begin wearableclip.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:wearableclip.fx

View File

@@ -78,3 +78,9 @@
/processor:EffectProcessor /processor:EffectProcessor
/processorParam:DebugMode=Auto /processorParam:DebugMode=Auto
/build:thresholdtint_opengl.fx /build:thresholdtint_opengl.fx
#begin wearableclip_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:wearableclip_opengl.fx

View File

@@ -0,0 +1,42 @@
Texture2D xTexture;
sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; };
Texture2D xStencil;
sampler StencilSampler = sampler_state { Texture = <xStencil>; };
float aCutoff;
float4x4 wearableUvToClipperUv;
float clipperTexelSize;
float stencilSample(float2 texCoord, float2 offset)
{
return xStencil.Sample(
StencilSampler,
mul(float4(texCoord.x, texCoord.y, 0, 1), wearableUvToClipperUv).xy + offset).a;
}
float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = xTexture.Sample(TextureSampler, texCoord) * color;
float minStencil = stencilSample(texCoord, float2(0,0));
minStencil = min(minStencil, stencilSample(texCoord, float2(-clipperTexelSize,0)));
minStencil = min(minStencil, stencilSample(texCoord, float2(clipperTexelSize,0)));
minStencil = min(minStencil, stencilSample(texCoord, float2(0,-clipperTexelSize)));
minStencil = min(minStencil, stencilSample(texCoord, float2(0,clipperTexelSize)));
float aDiff = minStencil - aCutoff;
clip(aDiff);
return c;
}
technique StencilShader
{
pass Pass1
{
PixelShader = compile ps_4_0_level_9_1 main();
}
}

View File

@@ -0,0 +1,42 @@
Texture2D xTexture;
sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; };
Texture2D xStencil;
sampler StencilSampler = sampler_state { Texture = <xStencil>; };
float aCutoff;
float4x4 wearableUvToClipperUv;
float clipperTexelSize;
float stencilSample(float2 texCoord, float2 offset)
{
return tex2D(
StencilSampler,
mul(float4(texCoord.x, texCoord.y, 0, 1), wearableUvToClipperUv).xy + offset).a;
}
float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = tex2D(TextureSampler, texCoord) * color;
float minStencil = stencilSample(texCoord, float2(0,0));
minStencil = min(minStencil, stencilSample(texCoord, float2(-clipperTexelSize,0)));
minStencil = min(minStencil, stencilSample(texCoord, float2(clipperTexelSize,0)));
minStencil = min(minStencil, stencilSample(texCoord, float2(0,-clipperTexelSize)));
minStencil = min(minStencil, stencilSample(texCoord, float2(0,clipperTexelSize)));
float aDiff = minStencil - aCutoff;
clip(aDiff);
return c;
}
technique StencilShader
{
pass Pass1
{
PixelShader = compile ps_2_0 main();
}
}

View File

@@ -11,7 +11,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.20.16.1</Version> <Version>0.21.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>

View File

@@ -11,7 +11,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.20.16.1</Version> <Version>0.21.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -11,7 +11,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.20.16.1</Version> <Version>0.21.6.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -14,6 +14,13 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool Discarded; public bool Discarded;
public void ApplyDeathEffects()
{
RespawnManager.ReduceCharacterSkills(this);
RemoveSavedStatValuesOnDeath();
CauseOfDeath = null;
}
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel) partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
{ {
if (Character == null || Character.Removed) { return; } if (Character == null || Character.Removed) { return; }

View File

@@ -8,8 +8,9 @@ namespace Barotrauma
{ {
partial class Character partial class Character
{ {
public Address OwnerClientAddress; private Address ownerClientAddress;
public string OwnerClientName; private Option<AccountId> ownerClientAccountId;
public bool ClientDisconnected; public bool ClientDisconnected;
public float KillDisconnectedTimer; public float KillDisconnectedTimer;
@@ -19,6 +20,35 @@ namespace Barotrauma
public bool HealthUpdatePending; public bool HealthUpdatePending;
public void SetOwnerClient(Client client)
{
if (client == null)
{
ownerClientAddress = null;
ownerClientAccountId = Option<AccountId>.None();
IsRemotePlayer = false;
}
else
{
ownerClientAddress = client.Connection.Endpoint.Address;
ownerClientAccountId = client.AccountId;
IsRemotePlayer = true;
}
}
public bool IsClientOwner(Client client)
{
if (ownerClientAccountId.TryUnwrap(out var accountId)
&& client.AccountId.TryUnwrap(out var clientId))
{
return accountId == clientId;
}
else
{
return ownerClientAddress == client.Connection.Endpoint.Address;
}
}
public float GetPositionUpdateInterval(Client recipient) public float GetPositionUpdateInterval(Client recipient)
{ {
if (!Enabled) { return 1000.0f; } if (!Enabled) { return 1000.0f; }
@@ -306,12 +336,8 @@ namespace Barotrauma
} }
} }
public void ServerWritePosition(IWriteMessage msg, Client c) public void ServerWritePosition(ReadWriteMessage tempBuffer, Client c)
{ {
msg.WriteUInt16(ID);
IWriteMessage tempBuffer = new WriteOnlyMessage();
if (this == c.Character) if (this == c.Character)
{ {
tempBuffer.WriteBoolean(true); tempBuffer.WriteBoolean(true);
@@ -409,11 +435,6 @@ namespace Barotrauma
AIController?.ServerWrite(tempBuffer); AIController?.ServerWrite(tempBuffer);
HealthUpdatePending = false; HealthUpdatePending = false;
} }
tempBuffer.WritePadBits();
msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes);
msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
} }
public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)

View File

@@ -0,0 +1,51 @@
#nullable enable
using System;
using System.Collections.Generic;
using Barotrauma.Networking;
namespace Barotrauma
{
internal static class HealingCooldown
{
private static readonly Dictionary<Client, DateTimeOffset> HealingCooldowns = new();
// Little bit less than client's 0.5 second cooldown to account for latency
private const float CooldownDuration = 0.4f;
public static bool IsOnCooldown(Client client)
{
RemoveExpiredCooldowns();
return HealingCooldowns.ContainsKey(client);
}
public static void SetCooldown(Client client)
{
RemoveExpiredCooldowns();
DateTimeOffset newCooldown = DateTimeOffset.UtcNow.AddSeconds(CooldownDuration);
HealingCooldowns[client] = newCooldown;
}
private static void RemoveExpiredCooldowns()
{
HashSet<Client>? expiredCooldowns = null;
DateTimeOffset now = DateTimeOffset.UtcNow;
foreach (var (client, cooldown) in HealingCooldowns)
{
if (now < cooldown) { continue; }
expiredCooldowns ??= new HashSet<Client>();
expiredCooldowns.Add(client);
}
if (expiredCooldowns is null) { return; }
foreach (Client expiredCooldown in expiredCooldowns)
{
HealingCooldowns.Remove(expiredCooldown);
}
}
}
}

View File

@@ -1401,7 +1401,7 @@ namespace Barotrauma
MultiPlayerCampaign.StartCampaignSetup(); MultiPlayerCampaign.StartCampaignSetup();
return; return;
} }
if (!GameMain.Server.StartGame()) { NewMessage("Failed to start a new round", Color.Yellow); } if (!GameMain.Server.TryStartGame()) { NewMessage("Failed to start a new round", Color.Yellow); }
} }
})); }));

View File

@@ -70,6 +70,7 @@ namespace Barotrauma
public static ContentPackage VanillaContent => ContentPackageManager.VanillaCorePackage; public static ContentPackage VanillaContent => ContentPackageManager.VanillaCorePackage;
public readonly string[] CommandLineArgs; public readonly string[] CommandLineArgs;
public GameMain(string[] args) public GameMain(string[] args)

View File

@@ -27,12 +27,9 @@ namespace Barotrauma
AnyOneAllowedToManageCampaign(permissions); AnyOneAllowedToManageCampaign(permissions);
} }
public bool AllowedToManageWallets(Client client) public static bool AllowedToManageWallets(Client client)
{ {
return return AllowedToManageCampaign(client, ClientPermissions.ManageMoney);
client.HasPermission(ClientPermissions.ManageCampaign) ||
client.HasPermission(ClientPermissions.ManageMoney) ||
IsOwner(client);
} }
public override void ShowStartMessage() public override void ShowStartMessage()

View File

@@ -109,15 +109,22 @@ namespace Barotrauma
return AccountId == other.AccountId && other.ClientAddress == ClientAddress; return AccountId == other.AccountId && other.ClientAddress == ClientAddress;
} }
public void Reset()
{
itemData = null;
healthData = null;
WalletData = null;
}
public void SpawnInventoryItems(Character character, Inventory inventory) public void SpawnInventoryItems(Character character, Inventory inventory)
{ {
if (character == null) if (character == null)
{ {
throw new System.InvalidOperationException($"Failed to spawn inventory items. Character was null."); throw new InvalidOperationException($"Failed to spawn inventory items. Character was null.");
} }
if (itemData == null) if (itemData == null)
{ {
throw new System.InvalidOperationException($"Failed to spawn inventory items for the character \"{character.Name}\". No saved inventory data."); throw new InvalidOperationException($"Failed to spawn inventory items for the character \"{character.Name}\". No saved inventory data.");
} }
character.SpawnInventoryItems(inventory, itemData.FromPackage(null)); character.SpawnInventoryItems(inventory, itemData.FromPackage(null));
} }

View File

@@ -146,7 +146,7 @@ namespace Barotrauma
{ {
NextLevel = map.SelectedConnection?.LevelData ?? map.CurrentLocation.LevelData; NextLevel = map.SelectedConnection?.LevelData ?? map.CurrentLocation.LevelData;
MirrorLevel = false; MirrorLevel = false;
GameMain.Server.StartGame(); GameMain.Server.TryStartGame();
} }
public static void StartCampaignSetup() public static void StartCampaignSetup()
@@ -240,9 +240,7 @@ namespace Barotrauma
//reduce skills if the character has died //reduce skills if the character has died
if (characterInfo.CauseOfDeath != null && characterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected) if (characterInfo.CauseOfDeath != null && characterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected)
{ {
RespawnManager.ReduceCharacterSkills(characterInfo); characterInfo.ApplyDeathEffects();
characterInfo.RemoveSavedStatValuesOnDeath();
characterInfo.CauseOfDeath = null;
} }
c.CharacterInfo = characterInfo; c.CharacterInfo = characterInfo;
SetClientCharacterData(c); SetClientCharacterData(c);
@@ -254,13 +252,21 @@ namespace Barotrauma
{ {
if (data.HasSpawned && !GameMain.Server.ConnectedClients.Any(c => data.MatchesClient(c))) if (data.HasSpawned && !GameMain.Server.ConnectedClients.Any(c => data.MatchesClient(c)))
{ {
var character = Character.CharacterList.Find(c => c.Info == data.CharacterInfo && !c.IsHusk); var character = Character.CharacterList.Find(c => c.Info == data.CharacterInfo && !c.IsHusk);
if (character != null && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected)) if (character != null &&
(!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected))
{ {
//character still alive (or killed by Disconnect) -> save it as-is
characterData.RemoveAll(cd => cd.IsDuplicate(data)); characterData.RemoveAll(cd => cd.IsDuplicate(data));
data.Refresh(character); data.Refresh(character);
characterData.Add(data); characterData.Add(data);
} }
else
{
//character dead or removed -> reduce skills, remove items, health data, etc
data.CharacterInfo.ApplyDeathEffects();
data.Reset();
}
} }
} }
@@ -395,7 +401,7 @@ namespace Barotrauma
yield return new WaitForSeconds(EndTransitionDuration * 0.5f); yield return new WaitForSeconds(EndTransitionDuration * 0.5f);
} }
GameMain.Server.StartGame(); GameMain.Server.TryStartGame();
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }

View File

@@ -1,4 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
@@ -19,7 +20,7 @@ namespace Barotrauma
bool accessible = c.Character.CanAccessInventory(this); bool accessible = c.Character.CanAccessInventory(this);
if (this is CharacterInventory characterInventory && accessible) if (this is CharacterInventory characterInventory && accessible)
{ {
if (Owner == null || !(Owner is Character ownerCharacter)) if (Owner == null || Owner is not Character ownerCharacter)
{ {
accessible = false; accessible = false;
} }
@@ -39,7 +40,7 @@ namespace Barotrauma
{ {
foreach (ushort id in newItemIDs[i]) foreach (ushort id in newItemIDs[i])
{ {
if (!(Entity.FindEntityByID(id) is Item item)) { continue; } if (Entity.FindEntityByID(id) is not Item item) { continue; }
item.PositionUpdateInterval = 0.0f; item.PositionUpdateInterval = 0.0f;
if (item.ParentInventory != null && item.ParentInventory != this) if (item.ParentInventory != null && item.ParentInventory != this)
{ {
@@ -94,7 +95,15 @@ namespace Barotrauma
{ {
foreach (ushort id in newItemIDs[i]) foreach (ushort id in newItemIDs[i])
{ {
if (!(Entity.FindEntityByID(id) is Item item) || slots[i].Contains(item)) { continue; } if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
if (item.GetComponent<Pickable>() is not Pickable pickable ||
(pickable.IsAttached && !pickable.PickingDone) ||
item.AllowedSlots.None())
{
DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})");
continue;
}
if (GameMain.Server != null) if (GameMain.Server != null)
{ {
@@ -105,7 +114,7 @@ namespace Barotrauma
(c.Character == null || item.PreviousParentInventory == null || !c.Character.CanAccessInventory(item.PreviousParentInventory))) (c.Character == null || item.PreviousParentInventory == null || !c.Character.CanAccessInventory(item.PreviousParentInventory)))
{ {
#if DEBUG || UNSTABLE #if DEBUG || UNSTABLE
DebugConsole.NewMessage($"Client {c.Name} failed to pick up item \"{item}\" (parent inventory: {(item.ParentInventory?.Owner.ToString() ?? "null")}). No access.", Color.Yellow); DebugConsole.NewMessage($"Client {c.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
#endif #endif
if (item.body != null && !c.PendingPositionUpdates.Contains(item)) if (item.body != null && !c.PendingPositionUpdates.Contains(item))
{ {

View File

@@ -153,25 +153,27 @@ namespace Barotrauma
(components[containerIndex] as ItemContainer).Inventory.ServerEventRead(msg, c); (components[containerIndex] as ItemContainer).Inventory.ServerEventRead(msg, c);
break; break;
case EventType.Treatment: case EventType.Treatment:
if (c.Character == null || !c.Character.CanInteractWith(this)) return; if (c.Character == null || !c.Character.CanInteractWith(this)) { return; }
UInt16 characterID = msg.ReadUInt16(); UInt16 characterID = msg.ReadUInt16();
byte limbIndex = msg.ReadByte(); byte limbIndex = msg.ReadByte();
Character targetCharacter = FindEntityByID(characterID) as Character; if (HealingCooldown.IsOnCooldown(c)) { return; }
if (targetCharacter == null) break; if (FindEntityByID(characterID) is not Character targetCharacter) { break; }
if (targetCharacter != c.Character && c.Character.SelectedCharacter != targetCharacter) break; if (targetCharacter != c.Character && c.Character.SelectedCharacter != targetCharacter) { break; }
HealingCooldown.SetCooldown(c);
Limb targetLimb = limbIndex < targetCharacter.AnimController.Limbs.Length ? targetCharacter.AnimController.Limbs[limbIndex] : null; Limb targetLimb = limbIndex < targetCharacter.AnimController.Limbs.Length ? targetCharacter.AnimController.Limbs[limbIndex] : null;
if (ContainedItems == null || ContainedItems.All(i => i == null)) if (ContainedItems == null || ContainedItems.All(static i => i == null))
{ {
GameServer.Log(GameServer.CharacterLogName(c.Character) + " used item " + Name, ServerLog.MessageType.ItemInteraction); GameServer.Log($"{GameServer.CharacterLogName(c.Character)} used item {Name}", ServerLog.MessageType.ItemInteraction);
} }
else else
{ {
GameServer.Log( GameServer.Log(
GameServer.CharacterLogName(c.Character) + " used item " + Name + " (contained items: " + string.Join(", ", ContainedItems.Select(i => i.Name)) + ")", $"{GameServer.CharacterLogName(c.Character)} used item {Name} (contained items: {string.Join(", ", ContainedItems.Select(i => i.Name))})",
ServerLog.MessageType.ItemInteraction); ServerLog.MessageType.ItemInteraction);
} }
@@ -348,15 +350,9 @@ namespace Barotrauma
} }
} }
public void ServerWritePosition(IWriteMessage msg, Client c) public void ServerWritePosition(ReadWriteMessage tempBuffer, Client c)
{ {
msg.WriteUInt16(ID);
IWriteMessage tempBuffer = new WriteOnlyMessage();
body.ServerWrite(tempBuffer); body.ServerWrite(tempBuffer);
msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes);
msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
msg.WritePadBits();
} }
public void CreateServerEvent<T>(T ic) where T : ItemComponent, IServerSerializable public void CreateServerEvent<T>(T ic) where T : ItemComponent, IServerSerializable
@@ -378,7 +374,7 @@ namespace Barotrauma
if (!components.Contains(ic)) { return; } if (!components.Contains(ic)) { return; }
var eventData = new ComponentStateEventData(ic, extraData); var eventData = new ComponentStateEventData(ic, extraData);
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); } if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false."); }
GameMain.Server.CreateEntityEvent(this, eventData); GameMain.Server.CreateEntityEvent(this, eventData);
} }

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