Build 0.18.0.0

This commit is contained in:
Markus Isberg
2022-05-13 00:55:52 +09:00
parent 15d18e6ff6
commit 7547a9b78a
218 changed files with 3881 additions and 2192 deletions
@@ -400,7 +400,7 @@ namespace Barotrauma
partial void UpdateControlled(float deltaTime, Camera cam) partial void UpdateControlled(float deltaTime, Camera cam)
{ {
if (controlled != this) return; if (controlled != this) { return; }
ControlLocalPlayer(deltaTime, cam); ControlLocalPlayer(deltaTime, cam);
@@ -2008,8 +2008,27 @@ namespace Barotrauma
DisplayedVitality = Vitality; DisplayedVitality = Vitality;
} }
partial void UpdateSkinTint()
{
if (!Character.IsVisible) { return; }
FaceTint = DefaultFaceTint;
BodyTint = Color.TransparentBlack;
if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; }
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{
var affliction = kvp.Key;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
}
}
partial void UpdateLimbAfflictionOverlays() partial void UpdateLimbAfflictionOverlays()
{ {
if (!Character.IsVisible) { return; }
foreach (Limb limb in Character.AnimController.Limbs) foreach (Limb limb in Character.AnimController.Limbs)
{ {
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; } if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
@@ -58,21 +58,19 @@ namespace Barotrauma
public class OutfitPreview public class OutfitPreview
{ {
/// <summary> public readonly List<(Sprite sprite, Vector2 drawOffset)> Sprites;
/// Pair.First = sprite, Pair.Second = draw offset
/// </summary>
public readonly List<Pair<Sprite, Vector2>> Sprites;
public Vector2 Dimensions; public Vector2 Dimensions;
public OutfitPreview() public OutfitPreview()
{ {
Sprites = new List<Pair<Sprite, Vector2>>(); Sprites = new List<(Sprite sprite, Vector2 drawOffset)>();
Dimensions = Vector2.One; Dimensions = Vector2.One;
} }
public void AddSprite(Sprite sprite, Vector2 drawOffset) public void AddSprite(Sprite sprite, Vector2 drawOffset)
{ {
Sprites.Add(new Pair<Sprite, Vector2>(sprite, drawOffset)); Sprites.Add((sprite, drawOffset));
} }
} }
@@ -224,14 +224,14 @@ namespace Barotrauma
public float DamageOverlayStrength public float DamageOverlayStrength
{ {
get { return damageOverlayStrength; } get { return damageOverlayStrength; }
set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
} }
private float burnOverLayStrength; private float burnOverLayStrength;
public float BurnOverlayStrength public float BurnOverlayStrength
{ {
get { return burnOverLayStrength; } get { return burnOverLayStrength; }
set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
} }
public string HitSoundTag => Params?.Sound?.Tag; public string HitSoundTag => Params?.Sound?.Tag;
@@ -279,7 +279,7 @@ namespace Barotrauma
for (int i = 0; i < Params.decorativeSpriteParams.Count; i++) for (int i = 0; i < Params.decorativeSpriteParams.Count; i++)
{ {
var param = Params.decorativeSpriteParams[i]; var param = Params.decorativeSpriteParams[i];
var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param)); var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param, ref _texturePath));
DecorativeSprites.Add(decorativeSprite); DecorativeSprites.Add(decorativeSprite);
int groupID = decorativeSprite.RandomGroupID; int groupID = decorativeSprite.RandomGroupID;
if (!DecorativeSpriteGroups.ContainsKey(groupID)) if (!DecorativeSpriteGroups.ContainsKey(groupID))
@@ -295,13 +295,13 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "sprite": case "sprite":
Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams)); Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams, ref _texturePath));
break; break;
case "damagedsprite": case "damagedsprite":
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams)); DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath));
break; break;
case "conditionalsprite": case "conditionalsprite":
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null)); var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath));
ConditionalSprites.Add(conditionalSprite); ConditionalSprites.Add(conditionalSprite);
if (conditionalSprite.DeformableSprite != null) if (conditionalSprite.DeformableSprite != null)
{ {
@@ -311,7 +311,7 @@ namespace Barotrauma
} }
break; break;
case "deformablesprite": case "deformablesprite":
_deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams)); _deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams, ref _texturePath));
var deformations = CreateDeformations(subElement); var deformations = CreateDeformations(subElement);
Deformations.AddRange(deformations); Deformations.AddRange(deformations);
NonConditionalDeformations.AddRange(deformations); NonConditionalDeformations.AddRange(deformations);
@@ -435,33 +435,33 @@ namespace Barotrauma
{ {
Sprite.Remove(); Sprite.Remove();
var source = Sprite.SourceElement; var source = Sprite.SourceElement;
Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams)); Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath));
} }
if (_deformSprite != null) if (_deformSprite != null)
{ {
_deformSprite.Remove(); _deformSprite.Remove();
var source = _deformSprite.Sprite.SourceElement; var source = _deformSprite.Sprite.SourceElement;
_deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams)); _deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath));
} }
if (DamagedSprite != null) if (DamagedSprite != null)
{ {
DamagedSprite.Remove(); DamagedSprite.Remove();
var source = DamagedSprite.SourceElement; var source = DamagedSprite.SourceElement;
DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams)); DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath));
} }
for (int i = 0; i < ConditionalSprites.Count; i++) for (int i = 0; i < ConditionalSprites.Count; i++)
{ {
var conditionalSprite = ConditionalSprites[i]; var conditionalSprite = ConditionalSprites[i];
var source = conditionalSprite.ActiveSprite.SourceElement; var source = conditionalSprite.ActiveSprite.SourceElement;
conditionalSprite.Remove(); conditionalSprite.Remove();
ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null)); ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null, ref _texturePath));
} }
for (int i = 0; i < DecorativeSprites.Count; i++) for (int i = 0; i < DecorativeSprites.Count; i++)
{ {
var decorativeSprite = DecorativeSprites[i]; var decorativeSprite = DecorativeSprites[i];
decorativeSprite.Remove(); decorativeSprite.Remove();
var source = decorativeSprite.Sprite.SourceElement; var source = decorativeSprite.Sprite.SourceElement;
DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i])); DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath));
} }
} }
@@ -472,16 +472,17 @@ namespace Barotrauma
} }
private string _texturePath; private string _texturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams) private string _damagedTexturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path)
{ {
if (_texturePath == null) if (path == null)
{ {
if (spriteParams != null) if (spriteParams != null)
{ {
ContentPath texturePath = ContentPath texturePath =
character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage) character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage)
?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath()); ?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath());
_texturePath = GetSpritePath(texturePath); path = GetSpritePath(texturePath);
} }
else else
{ {
@@ -489,10 +490,10 @@ namespace Barotrauma
texturePath = texturePath.IsNullOrWhiteSpace() texturePath = texturePath.IsNullOrWhiteSpace()
? ContentPath.FromRaw(character.Prefab.ContentPackage, ragdoll.RagdollParams.Texture) ? ContentPath.FromRaw(character.Prefab.ContentPackage, ragdoll.RagdollParams.Texture)
: texturePath; : texturePath;
_texturePath = GetSpritePath(texturePath); path = GetSpritePath(texturePath);
} }
} }
return _texturePath; return path;
} }
/// <summary> /// <summary>
@@ -625,12 +626,7 @@ namespace Barotrauma
{ {
if (!body.Enabled) { return; } if (!body.Enabled) { return; }
if (!IsDead) if (IsDead)
{
DamageOverlayStrength -= deltaTime;
BurnOverlayStrength -= deltaTime;
}
else
{ {
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams != null && spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime) if (spriteParams != null && spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime)
@@ -688,7 +684,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{ {
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; float brightness = Math.Max(1.0f - burnOverLayStrength, 0.2f);
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; } if (spriteParams == null) { return; }
@@ -831,32 +827,6 @@ namespace Barotrauma
{ {
LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically; LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
} }
if (damageOverlayStrength > 0.0f && DamagedSprite != null && !hideLimb)
{
DamagedSprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin,
-body.DrawRotation,
Scale, spriteEffect, activeSprite.Depth - (depthStep * 90));
}
foreach (var decorativeSprite in DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f);
if (deadTimer > 0)
{
c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer));
}
c = overrideColor ?? c;
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - (depthStep * 100));
}
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)
@@ -925,6 +895,36 @@ namespace Barotrauma
//if there are multiple sprites on this limb, make the successive ones be drawn in front //if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step; depthStep += step;
} }
if (!Hide && onlyDrawable == null)
{
foreach (var decorativeSprite in DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f);
if (deadTimer > 0)
{
c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer));
}
c = overrideColor ?? c;
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - depthStep);
depthStep += step;
}
if (damageOverlayStrength > 0.0f && DamagedSprite != null)
{
DamagedSprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
color * damageOverlayStrength, activeSprite.Origin,
-body.DrawRotation,
Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
}
}
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
@@ -359,7 +359,7 @@ namespace Barotrauma.Transition
else else
{ {
//copying a mod: we have a neat method for that! //copying a mod: we have a neat method for that!
await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath); await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath, SteamManager.Workshop.ShouldCorrectPaths.Yes);
return null; return null;
} }
@@ -125,7 +125,7 @@ namespace Barotrauma
{ {
if (isOpen) if (isOpen)
{ {
frame.AddToGUIUpdateList(); frame.AddToGUIUpdateList(order: 1);
} }
} }
@@ -1714,9 +1714,47 @@ namespace Barotrauma
//check missing mission texts //check missing mission texts
foreach (var missionPrefab in MissionPrefab.Prefabs) foreach (var missionPrefab in MissionPrefab.Prefabs)
{ {
Identifier missionId = (missionPrefab.ConfigElement.GetAttribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty)); Identifier missionId = missionPrefab.ConfigElement.GetAttribute("textidentifier") == null ?
addIfMissing($"missionname.{missionId}".ToIdentifier(), language); missionPrefab.Identifier :
addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language); missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty);
if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("name", Identifier.Empty)))
{
addIfMissing($"missionname.{missionId}".ToIdentifier(), language);
}
if (missionPrefab.Type == MissionType.Combat)
{
addIfMissing($"MissionDescriptionNeutral.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionDescription1.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionDescription2.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionTeam1.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionTeam2.{missionId}".ToIdentifier(), language);
}
else
{
if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("description", Identifier.Empty)))
{
addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language);
}
if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("successmessage", Identifier.Empty)))
{
addIfMissing($"missionsuccess.{missionId}".ToIdentifier(), language);
}
//only check failure message if there's something defined in the xml (otherwise we just use the generic "missionfailed" text)
if (missionPrefab.ConfigElement.GetAttribute("failuremessage") != null &&
!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("failuremessage", Identifier.Empty)))
{
addIfMissing($"missionfailure.{missionId}".ToIdentifier(), language);
}
}
for (int i = 0; i<missionPrefab.Messages.Length; i++)
{
if (missionPrefab.Messages[i].IsNullOrWhiteSpace())
{
addIfMissing($"MissionMessage{i}.{missionId}".ToIdentifier(), language);
}
}
} }
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent)))) foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
@@ -2503,7 +2541,7 @@ namespace Barotrauma
var entity = MapEntity.mapEntityList[i] as ISerializableEntity; var entity = MapEntity.mapEntityList[i] as ISerializableEntity;
if (entity != null) if (entity != null)
{ {
List<Pair<object, SerializableProperty>> allProperties = new List<Pair<object, SerializableProperty>>(); List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>();
if (entity is Item item) if (entity is Item item)
{ {
@@ -2518,14 +2556,14 @@ namespace Barotrauma
for (int k = 0; k < properties.Count; k++) for (int k = 0; k < properties.Count; k++)
{ {
allProperties.Add(new Pair<object, SerializableProperty>(entity, properties[k])); allProperties.Add((entity, properties[k]));
} }
} }
for (int j = 0; j < allProperties.Count; j++) for (int j = 0; j < allProperties.Count; j++)
{ {
var property = allProperties[j].Second; var property = allProperties[j].property;
string propertyName = (allProperties[j].First.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant(); string propertyName = (allProperties[j].obj.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant();
LocalizedString displayName = TextManager.Get($"sp.{propertyName}.name"); LocalizedString displayName = TextManager.Get($"sp.{propertyName}.name");
if (displayName.IsNullOrEmpty()) if (displayName.IsNullOrEmpty())
{ {
@@ -35,7 +35,6 @@ namespace Barotrauma
public TextGetterHandler TextGetter; public TextGetterHandler TextGetter;
public bool Wrap; public bool Wrap;
private bool playerInput;
public bool RoundToNearestPixel = true; public bool RoundToNearestPixel = true;
@@ -287,8 +286,7 @@ namespace Barotrauma
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
/// </summary> /// </summary>
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null, public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
bool playerInput = false)
: base(style, rectT) : base(style, rectT)
{ {
if (color.HasValue) if (color.HasValue)
@@ -307,7 +305,6 @@ namespace Barotrauma
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
this.Wrap = wrap; this.Wrap = wrap;
this.Text = text ?? ""; this.Text = text ?? "";
this.playerInput = playerInput;
if (rectT.Rect.Height == 0 && !text.IsNullOrEmpty()) if (rectT.Rect.Height == 0 && !text.IsNullOrEmpty())
{ {
CalculateHeightFromText(); CalculateHeightFromText();
@@ -261,7 +261,7 @@ namespace Barotrauma
this.color = color ?? Color.White; this.color = color ?? Color.White;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color); frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style); GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap, playerInput: true); textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
GUIStyle.Apply(textBlock, "", this); GUIStyle.Apply(textBlock, "", this);
if (font != null) { textBlock.Font = font; } if (font != null) { textBlock.Font = font; }
CaretEnabled = true; CaretEnabled = true;
@@ -657,6 +657,7 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
GUILayoutGroup parentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup parentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal: true) { Stretch = true };
if (!(affliction.Prefab is { } prefab)) { return; } if (!(affliction.Prefab is { } prefab)) { return; }
@@ -676,7 +677,7 @@ namespace Barotrauma
GUIFrame textContainer = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), textLayout.RectTransform), style: null); GUIFrame textContainer = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), textLayout.RectTransform), style: null);
GUITextBlock afflictionName = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform), name, font: GUIStyle.SubHeadingFont); GUITextBlock afflictionName = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform), name, font: GUIStyle.SubHeadingFont);
GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
{ {
Padding = Vector4.Zero Padding = Vector4.Zero
}; };
@@ -746,12 +747,12 @@ namespace Barotrauma
ClosePopup(); ClosePopup();
GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.45f), container.RectTransform) GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.5f), container.RectTransform)
{ {
ScreenSpaceOffset = location.ToPoint() ScreenSpaceOffset = location.ToPoint()
}); });
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true }; GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true };
if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight) if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight)
{ {
@@ -819,7 +820,9 @@ namespace Barotrauma
if (!(affliction.Prefab is { } prefab)) { return ImmutableArray<GUIComponent>.Empty; } if (!(affliction.Prefab is { } prefab)) { return ImmutableArray<GUIComponent>.Empty; }
GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement"); GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement");
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), backgroundFrame.RectTransform, Anchor.Center)) new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine");
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), backgroundFrame.RectTransform, Anchor.Center))
{ {
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
@@ -862,13 +865,27 @@ namespace Barotrauma
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform)); GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform))
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), bottomTextLayout.RectTransform), ToolBox.LimitString(prefab.Description, GUIStyle.Font, GUI.IntScale(64)), wrap: true) {
RelativeSpacing = 0.05f
};
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform), prefab.Description, font: GUIStyle.SmallFont, wrap: true)
{ {
ToolTip = prefab.Description ToolTip = prefab.Description
}; };
bool truncated = false;
while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
{
var split = descriptionBlock.WrappedText.Value.Split('\n');
descriptionBlock.Text = string.Join('\n', split.Take(split.Length - 1));
truncated = true;
}
if (truncated)
{
descriptionBlock.Text += "...";
}
GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.LargeFont); GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont);
GUIButton buyButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style: "CrewManagementAddButton"); GUIButton buyButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style: "CrewManagementAddButton");
@@ -923,6 +940,7 @@ namespace Barotrauma
}); });
} }
#warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString
public static void EnsureTextDoesntOverflow(string? text, GUITextBlock textBlock, Rectangle bounds, ImmutableArray<GUILayoutGroup>? layoutGroups = null) public static void EnsureTextDoesntOverflow(string? text, GUITextBlock textBlock, Rectangle bounds, ImmutableArray<GUILayoutGroup>? layoutGroups = null)
{ {
if (string.IsNullOrWhiteSpace(text)) { return; } if (string.IsNullOrWhiteSpace(text)) { return; }
@@ -46,9 +46,7 @@ namespace Barotrauma
private int buyTotal, sellTotal, sellFromSubTotal; private int buyTotal, sellTotal, sellFromSubTotal;
private GUITextBlock storeNameBlock; private GUITextBlock storeNameBlock;
private GUITextBlock merchantBalanceBlock; private GUITextBlock reputationEffectBlock;
private GUITextBlock currentSellValueBlock, newSellValueBlock;
private GUIImage sellValueChangeArrow;
private GUIDropDown sortingDropDown; private GUIDropDown sortingDropDown;
private GUITextBox searchBox; private GUITextBox searchBox;
private GUILayoutGroup categoryButtonContainer; private GUILayoutGroup categoryButtonContainer;
@@ -376,41 +374,29 @@ namespace Barotrauma
AutoScaleVertical = true, AutoScaleVertical = true,
ForceUpperCase = ForceUpperCase.Yes ForceUpperCase = ForceUpperCase.Yes
}; };
merchantBalanceBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), "",
"", font: GUIStyle.SubHeadingFont) color: Color.White, font: GUIStyle.SubHeadingFont)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
TextScale = 1.1f, TextScale = 1.1f,
TextGetter = () => TextGetter = () => GetMerchantBalanceText()
{
merchantBalanceBlock.TextColor = ActiveStore?.BalanceColor ?? Color.Red;
return GetMerchantBalanceText();
}
}; };
// Item sell value ------------------------------------------------ // Item sell value ------------------------------------------------
var sellValueContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform)) var reputationEffectContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform))
{ {
CanBeFocused = true, CanBeFocused = true,
RelativeSpacing = 0.005f RelativeSpacing = 0.005f,
ToolTip = TextManager.Get("campaignstore.reputationtooltip")
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform),
TextManager.Get("campaignstore.sellvalue"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft) TextManager.Get("reputation"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
CanBeFocused = false, CanBeFocused = false,
ForceUpperCase = ForceUpperCase.Yes ForceUpperCase = ForceUpperCase.Yes,
}; };
reputationEffectBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform), "", font: GUIStyle.SubHeadingFont)
var valueChangeGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
CanBeFocused = false,
RelativeSpacing = 0.02f
};
float blockWidth = GUI.IsFourByThree() ? 0.32f : 0.28f;
Point blockMaxSize = new Point((int)(GameSettings.CurrentConfig.Graphics.TextScale * 60), valueChangeGroup.Rect.Height);
currentSellValueBlock = new GUITextBlock(new RectTransform(new Vector2(blockWidth, 1.0f), valueChangeGroup.RectTransform) { MaxSize = blockMaxSize },
"", font: GUIStyle.SubHeadingFont)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
CanBeFocused = false, CanBeFocused = false,
@@ -419,64 +405,27 @@ namespace Barotrauma
{ {
if (CurrentLocation != null) if (CurrentLocation != null)
{ {
int balanceAfterTransaction = activeTab switch Color textColor = GUIStyle.ColorReputationNeutral;
string sign = "";
int reputationModifier = (int)MathF.Round((CurrentLocation.GetStoreReputationModifier(activeTab == StoreTab.Buy) - 1) * 100);
if (reputationModifier > 0)
{ {
StoreTab.Buy => ActiveStore.Balance + buyTotal, textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh;
StoreTab.Sell => ActiveStore.Balance - sellTotal, sign = "+";
StoreTab.SellSub => ActiveStore.Balance - sellFromSubTotal,
_ => throw new NotImplementedException(),
};
if (balanceAfterTransaction != ActiveStore.Balance)
{
var newStatus = CurrentLocation.GetStoreBalanceStatus(balanceAfterTransaction);
if (ActiveStore.ActiveBalanceStatus.SellPriceModifier != newStatus.SellPriceModifier)
{
string tooltipTag = newStatus.SellPriceModifier > ActiveStore.ActiveBalanceStatus.SellPriceModifier ?
"campaingstore.valueincreasetooltip" : "campaingstore.valuedecreasetooltip";
sellValueContainer.ToolTip = TextManager.Get(tooltipTag);
currentSellValueBlock.TextColor = newStatus.Color;
sellValueChangeArrow.Color = newStatus.Color;
sellValueChangeArrow.Visible = true;
newSellValueBlock.TextColor = newStatus.Color;
newSellValueBlock.Text = $"{(newStatus.SellPriceModifier * 100).FormatZeroDecimal()} %";
return $"{(ActiveStore.ActiveBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %";
}
} }
sellValueContainer.ToolTip = TextManager.Get("campaignstore.sellvaluetooltip"); else if (reputationModifier < 0)
currentSellValueBlock.TextColor = ActiveStore.BalanceColor; {
sellValueChangeArrow.Visible = false; textColor = IsBuying ? GUIStyle.ColorReputationHigh : GUIStyle.ColorReputationLow;
newSellValueBlock.Text = null; }
return $"{(ActiveStore.ActiveBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %"; reputationEffectBlock.TextColor = textColor;
return $"{sign}{reputationModifier}%";
} }
else else
{ {
sellValueContainer.ToolTip = null; return "";
sellValueChangeArrow.Visible = false;
newSellValueBlock.Text = null;
return null;
} }
} }
}; };
Vector4 newPadding = currentSellValueBlock.Padding;
newPadding.Z = 0;
currentSellValueBlock.Padding = newPadding;
float relativeHeight = 0.45f;
float relativeWidth = (relativeHeight * valueChangeGroup.Rect.Height) / valueChangeGroup.Rect.Width;
sellValueChangeArrow = new GUIImage(new RectTransform(new Vector2(relativeWidth, relativeHeight), valueChangeGroup.RectTransform), "StoreArrow", scaleToFit: true)
{
CanBeFocused = false,
Visible = false
};
newSellValueBlock = new GUITextBlock(new RectTransform(new Vector2(blockWidth, 1.0f), valueChangeGroup.RectTransform) { MaxSize = blockMaxSize },
"", font: GUIStyle.SubHeadingFont)
{
AutoScaleVertical = true,
CanBeFocused = false,
TextScale = 1.1f
};
newPadding = newSellValueBlock.Padding;
newPadding.X = 0;
newSellValueBlock.Padding = newPadding;
// Store mode buttons ------------------------------------------------ // Store mode buttons ------------------------------------------------
var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style: null); var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style: null);
@@ -626,7 +626,7 @@ namespace Barotrauma
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
@@ -664,7 +664,7 @@ namespace Barotrauma
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
@@ -54,47 +54,65 @@ namespace Barotrauma
private ushort currentPing; private ushort currentPing;
private readonly Character character; private readonly Character character;
private readonly bool hasCharacter; private readonly bool wasCharacterAlive;
private readonly GUITextBlock textBlock; private readonly GUITextBlock textBlock;
private readonly GUIFrame frame; private readonly GUIFrame frame;
private readonly GUIImage permissionIcon; private readonly GUIImage permissionIcon;
public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock, GUIImage permissionIcon) public LinkedGUI(Client client, GUIFrame frame, GUITextBlock textBlock, GUIImage permissionIcon)
{ {
this.Client = client; this.Client = client;
this.textBlock = textBlock; this.textBlock = textBlock;
this.frame = frame; this.frame = frame;
this.hasCharacter = hasCharacter;
this.permissionIcon = permissionIcon; this.permissionIcon = permissionIcon;
character = client?.Character;
wasCharacterAlive = client.Character != null && !client.Character.IsDead;
} }
public LinkedGUI(Character character, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock) public LinkedGUI(Character character, GUIFrame frame, GUITextBlock textBlock)
{ {
this.character = character; this.character = character;
this.textBlock = textBlock; this.textBlock = textBlock;
this.frame = frame; this.frame = frame;
this.hasCharacter = hasCharacter; wasCharacterAlive = character != null && !character.IsDead;
} }
public bool HasMultiplayerCharacterChanged() public bool HasMultiplayerCharacterChanged()
{ {
if (Client == null) { return false; } if (Client == null) { return false; }
bool characterState = Client.Character != null;
if (characterState && Client.Character.IsDead) characterState = false; if (GameSettings.CurrentConfig.VerboseLogging)
return hasCharacter != characterState; {
if (Client.Character != character)
{
DebugConsole.Log($"Refreshing tab menu crew list (client \"{Client.Name}\"'s character changed from \"{character?.Name ?? "null"}\" to \"{Client.Character?.Name ?? "null"}\")");
}
}
return Client.Character != character;
} }
public bool HasMultiplayerCharacterDied() public bool HasCharacterDied()
{
if (Client == null || !hasCharacter || Client.Character == null) { return false; }
return Client.Character.IsDead;
}
public bool HasAICharacterDied()
{ {
if (character == null) { return false; } if (character == null) { return false; }
return character.IsDead; bool isAlive = !(character?.IsDead ?? true);
if (GameSettings.CurrentConfig.VerboseLogging)
{
if (wasCharacterAlive && !isAlive)
{
DebugConsole.Log(Client == null ?
$"Refreshing tab menu crew list (character \"{character?.Name ?? "null"}\" died)" :
$"Refreshing tab menu crew list (client \"{Client.Name}\"'s character \"{character?.Name ?? "null"}\" died)");
}
else if (!wasCharacterAlive && isAlive)
{
DebugConsole.Log(Client == null ?
$"Refreshing tab menu crew list (character \"{character?.Name ?? "null"}\" came back to life)" :
$"Refreshing tab menu crew list (client \"{Client.Name}\"'s character \"{character?.Name ?? "null"}\" came back to life)");
}
}
return isAlive != wasCharacterAlive;
} }
public void TryPingRefresh() public void TryPingRefresh()
@@ -207,7 +225,7 @@ namespace Barotrauma
{ {
linkedGUIList[i].TryPingRefresh(); linkedGUIList[i].TryPingRefresh();
linkedGUIList[i].TryPermissionIconRefresh(GetPermissionIcon(linkedGUIList[i].Client)); linkedGUIList[i].TryPermissionIconRefresh(GetPermissionIcon(linkedGUIList[i].Client));
if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasMultiplayerCharacterDied() || linkedGUIList[i].HasAICharacterDied()) if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasCharacterDied())
{ {
RemoveCurrentElements(); RemoveCurrentElements();
CreateMultiPlayerList(true); CreateMultiPlayerList(true);
@@ -219,10 +237,11 @@ namespace Barotrauma
{ {
for (int i = 0; i < linkedGUIList.Count; i++) for (int i = 0; i < linkedGUIList.Count; i++)
{ {
if (linkedGUIList[i].HasAICharacterDied()) if (linkedGUIList[i].HasCharacterDied())
{ {
RemoveCurrentElements(); RemoveCurrentElements();
CreateSinglePlayerList(true); CreateSinglePlayerList(true);
return;
} }
} }
} }
@@ -297,6 +316,10 @@ namespace Barotrauma
var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame"); var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame");
GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right); GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right);
if (GameMain.IsMultiplayer)
{
balanceText.ToolTip = TextManager.Get("bankdescription");
}
GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform) GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform)
{ {
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8)) AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
@@ -337,7 +360,7 @@ namespace Barotrauma
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character"); var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character");
talentsButton.OnAddedToGUIUpdateList += (component) => talentsButton.OnAddedToGUIUpdateList += (component) =>
{ {
talentsButton.Enabled = Character.Controlled?.Info != null; talentsButton.Enabled = Character.Controlled?.Info != null || (GameMain.Client?.CharacterInfo != null && GameMain.GameSession?.GameMode is MultiPlayerCampaign);
if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents)
{ {
SelectInfoFrameTab(InfoFrameTab.Crew); SelectInfoFrameTab(InfoFrameTab.Crew);
@@ -560,7 +583,7 @@ namespace Barotrauma
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor); ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, textBlock: null)); linkedGUIList.Add(new LinkedGUI(character, frame, textBlock: null));
} }
private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame) private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame)
@@ -657,7 +680,7 @@ namespace Barotrauma
if (client != null) if (client != null)
{ {
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon); CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
linkedGUIList.Add(new LinkedGUI(client, frame, true, linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center), new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon)); permissionIcon));
} }
@@ -668,12 +691,12 @@ namespace Barotrauma
if (character is AICharacter) if (character is AICharacter)
{ {
linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, linkedGUIList.Add(new LinkedGUI(character, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes })); new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
} }
else else
{ {
linkedGUIList.Add(new LinkedGUI(client: null, frame, true, textBlock: null, permissionIcon: null)); linkedGUIList.Add(new LinkedGUI(client: null, frame, textBlock: null, permissionIcon: null));
new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect)) new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect))
{ {
@@ -718,7 +741,7 @@ namespace Barotrauma
}; };
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon); CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
linkedGUIList.Add(new LinkedGUI(client, frame, false, linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center), new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon)); permissionIcon));
@@ -775,19 +798,27 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
new GUIFrame(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("walletdescription")
};
if (character.IsBot) { return; } if (character.IsBot) { return; }
Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite; Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite;
GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), walletSprite, scaleToFit: true); GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), walletSprite, scaleToFit: true) { CanBeFocused = false };
GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.Font) GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.Font)
{ {
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
Padding = Vector4.Zero Padding = Vector4.Zero,
CanBeFocused = false
}; };
GUIImage largeIcon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), walletSprite, scaleToFit: true) GUIImage largeIcon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), walletSprite, scaleToFit: true)
{ {
CanBeFocused = false,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false Visible = false
}; };
@@ -971,16 +1002,25 @@ namespace Barotrauma
float relativeX = icon.RectTransform.NonScaledSize.X / (float)icon.Parent.RectTransform.NonScaledSize.X; float relativeX = icon.RectTransform.NonScaledSize.X / (float)icon.Parent.RectTransform.NonScaledSize.X;
GUILayoutGroup headerTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativeX, 1f), headerLayout.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup headerTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativeX, 1f), headerLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.Get("crewwallet.wallet"), font: GUIStyle.LargeFont); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.Get("crewwallet.wallet"), font: GUIStyle.LargeFont);
GUIFrame walletTooltipFrame = new GUIFrame(new RectTransform(Vector2.One, headerLayout.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("walletdescription")
};
GUITextBlock moneyBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.FormatCurrency(targetWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right); GUITextBlock moneyBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.FormatCurrency(targetWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right);
GUILayoutGroup middleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), walletLayout.RectTransform)); GUILayoutGroup middleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), walletLayout.RectTransform));
GUILayoutGroup salaryTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true); GUILayoutGroup salaryTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true);
GUITextBlock salaryTitle = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), TextManager.Get("crewwallet.salary"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); GUITextBlock salaryTitle = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), TextManager.Get("crewwallet.salary"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
GUITextBlock rewardBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), string.Empty, textAlignment: Alignment.BottomRight); GUITextBlock rewardBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), string.Empty, textAlignment: Alignment.BottomRight);
GUIFrame salaryTooltipFrame = new GUIFrame(new RectTransform(Vector2.One, middleLayout.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("crewwallet.salary.tooltip")
};
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center); GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center);
GUIScrollBar salarySlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 1f), sliderLayout.RectTransform), style: "GUISlider", barSize: 0.03f) GUIScrollBar salarySlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 1f), sliderLayout.RectTransform), style: "GUISlider", barSize: 0.03f)
{ {
ToolTip = TextManager.Get("crewwallet.salary.tooltip"),
Range = new Vector2(0, 1), Range = new Vector2(0, 1),
BarScrollValue = targetWallet.RewardDistribution / 100f, BarScrollValue = targetWallet.RewardDistribution / 100f,
Step = 0.01f, Step = 0.01f,
@@ -1050,149 +1090,195 @@ namespace Barotrauma
return true; return true;
} }
}; };
Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier();
ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen); ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen);
ToggleCenterButton(centerButton, isSending); ToggleCenterButton(centerButton, isSending);
if (!(Character.Controlled is { } myCharacter))
{
salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
}
bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets();
salarySlider.Enabled = hasMoneyPermissions;
Wallet otherWallet; Wallet otherWallet;
GameMain.Client?.OnPermissionChanged.RegisterOverwriteExisting(eventIdentifier, e => UpdateWalletInterface(registerEvents: false));
UpdateWalletInterface(registerEvents: true);
switch (hasMoneyPermissions) void UpdateWalletInterface(bool registerEvents)
{ {
case true: if (!(Character.Controlled is { } myCharacter))
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups);
updateButtonText();
if (!hasMoneyPermissions)
{
if (character != Character.Controlled)
{ {
centerButton.Enabled = centerButton.CanBeFocused = false; salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
} }
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets();
salarySlider.Enabled = hasMoneyPermissions;
UpdateAllInputs(); switch (hasMoneyPermissions)
{
case true:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups);
UpdatedConfirmButtonText();
if (!hasMoneyPermissions)
{
if (character != Character.Controlled)
{
centerButton.Enabled = centerButton.CanBeFocused = false;
}
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
centerButton.OnClicked = (btn, o) =>
{
isSending = !isSending;
updateButtonText();
ToggleCenterButton(btn, isSending);
UpdateAllInputs(); UpdateAllInputs();
return true;
};
void updateButtonText() if (!registerEvents) { return; }
{
confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney");
}
transferAmountInput.OnValueChanged = input => centerButton.OnClicked = (btn, o) =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier();
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (e.Wallet == targetWallet)
{ {
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance); isSending = !isSending;
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f; UpdatedConfirmButtonText();
ToggleCenterButton(btn, isSending);
UpdateAllInputs();
return true;
};
transferAmountInput.OnValueChanged = input =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
resetButton.OnClicked = (button, o) =>
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (e.Wallet == targetWallet)
{
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance);
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
}
UpdateAllInputs();
});
registeredEvents.Add(eventIdentifier);
void UpdatedConfirmButtonText()
{
confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney");
} }
UpdateAllInputs();
});
registeredEvents.Add(eventIdentifier);
resetButton.OnClicked = (button, o) => void UpdateAllInputs()
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
void UpdateAllInputs()
{
UpdateInputs();
UpdateMaxInput();
}
void UpdateInputs()
{
confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
if (transferAmountInput.IntValue == 0)
{ {
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); UpdateInputs();
rightBalance.TextColor = GUIStyle.TextColorNormal; UpdateMaxInput();
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
} }
else if (isSending)
void UpdateInputs()
{ {
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue); confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
rightBalance.TextColor = GUIStyle.Blue; if (transferAmountInput.IntValue == 0)
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue); {
leftBalance.TextColor = GUIStyle.Red; rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
rightBalance.TextColor = GUIStyle.TextColorNormal;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
}
else if (isSending)
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Blue;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Red;
}
else
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Red;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue;
}
} }
else
void UpdateMaxInput()
{ {
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue); int maxValue = isSending ? targetWallet.Balance : otherWallet.Balance;
rightBalance.TextColor = GUIStyle.Red; transferAmountInput.MaxValueInt = maxValue;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue; transferAmountInput.Enabled = true;
transferAmountInput.ToolTip = string.Empty;
if (!hasMoneyPermissions && GameMain.Client?.ServerSettings is { } serverSettings)
{
transferAmountInput.MaxValueInt = Math.Min(maxValue, serverSettings.MaximumTransferRequest);
if (serverSettings.MaximumTransferRequest <= 0)
{
transferAmountInput.Enabled = false;
transferAmountInput.ToolTip = TextManager.Get("wallettransferrequestdisabled");
}
}
} }
} }
void UpdateMaxInput() void SetRewardText(int value, GUITextBlock block)
{ {
transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance; var (_, percentage, sum) = Mission.GetRewardShare(value, salaryCrew, Option<int>.None());
LocalizedString tooltip = string.Empty;
block.TextColor = GUIStyle.TextColorNormal;
if (sum > 100)
{
tooltip = TextManager.GetWithVariables("crewwallet.salary.over100toolitp", ("[sum]", $"{(int)sum}"), ("[newvalue]", $"{percentage}"));
block.TextColor = GUIStyle.Orange;
}
LocalizedString text = TextManager.GetWithVariable("percentageformat", "[value]", $"{value}");
block.Text = text;
block.ToolTip = RichString.Rich(tooltip);
} }
static void ToggleTransferMenuIcon(GUIButton btn, bool open) static void ToggleTransferMenuIcon(GUIButton btn, bool open)
@@ -1235,24 +1321,6 @@ namespace Barotrauma
transfer.Write(msg); transfer.Write(msg);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
} }
void SetRewardText(int value, GUITextBlock block)
{
var (_, percentage, sum) = Mission.GetRewardShare(value, salaryCrew, Option<int>.None());
LocalizedString tooltip = string.Empty;
block.TextColor = GUIStyle.TextColorNormal;
if (sum > 100)
{
tooltip = TextManager.GetWithVariables("crewwallet.salary.over100toolitp", ("[sum]", $"{(int)sum}"), ("[newvalue]", $"{percentage}"));
block.TextColor = GUIStyle.Orange;
}
LocalizedString text = TextManager.GetWithVariable("percentageformat", "[value]", $"{value}");
block.Text = text;
block.ToolTip = RichString.Rich(tooltip);
}
} }
private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null) private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null)
@@ -1740,9 +1808,6 @@ namespace Barotrauma
talentButtons.Clear(); talentButtons.Clear();
talentCornerIcons.Clear(); talentCornerIcons.Clear();
Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; }
GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15); int padding = GUI.IntScale(15);
GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
@@ -1762,13 +1827,20 @@ namespace Barotrauma
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false); GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
} }
/*Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; }
if (controlledCharacter.Info is null) if (controlledCharacter.Info is null)
{ {
DebugConsole.ThrowError("No character info found for talent UI"); DebugConsole.ThrowError("No character info found for talent UI");
return; return;
} }*/
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); Character controlledCharacter = Character.Controlled;
CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info == null) { return; }
Job job = info.Job;
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -1776,9 +1848,7 @@ namespace Barotrauma
}; };
GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true);
CharacterInfo info = controlledCharacter.Info;
Job job = info.Job;
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) =>
{ {
@@ -1801,11 +1871,11 @@ namespace Barotrauma
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont); GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
GUIFrame endocrineFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null); GUIFrame talentsOutsideTreeFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null);
if (!(GameMain.NetworkMember is null)) if (!(GameMain.NetworkMember is null))
{ {
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), endocrineFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), talentsOutsideTreeFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"))
{ {
IgnoreLayoutGroups = true IgnoreLayoutGroups = true
}; };
@@ -1852,13 +1922,14 @@ namespace Barotrauma
} }
} }
IEnumerable<TalentPrefab> endocrineTalents = info.GetEndocrineTalents().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
if (endocrineTalents.Count() > 0) if (talentsOutsideTree.Count() > 0)
{ {
GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), endocrineFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") //TODO: replace with something more generic
GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), talentsOutsideTreeFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon")
{ {
ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", endocrineTalents.Select(e => e.DisplayName))}" ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", talentsOutsideTree.Select(e => e.DisplayName))}"
}; };
} }
@@ -1870,49 +1941,55 @@ namespace Barotrauma
skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint(); skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint();
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
CreateTalentSkillList(controlledCharacter, skillListBox); CreateTalentSkillList(controlledCharacter, info, skillListBox);
if (!TalentTree.JobTalentTrees.TryGet(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } if (controlledCharacter != null)
new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine");
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{ {
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null); if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine");
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
for (int i = 0; i < 4; i++) GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
selectedTalents = info.GetUnlockedTalentsInTree().ToList();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{ {
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize; GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground"); for (int i = 0; i < 4; i++)
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{ {
CanBeFocused = false GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize; Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
{
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false,
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Count <= i) { continue; }
if (subTree.TalentOptionStages.Count > i)
{
TalentOption talentOption = subTree.TalentOptionStages[i]; TalentOption talentOption = subTree.TalentOptionStages[i];
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier)) foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier))
@@ -1929,6 +2006,7 @@ namespace Barotrauma
ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description), ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description),
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
Enabled = controlledCharacter != null,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
// deselect other buttons in tier by removing their selected talents from pool // deselect other buttons in tier by removing their selected talents from pool
@@ -1961,7 +2039,7 @@ namespace Barotrauma
}, },
}; };
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent; talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage; GUIComponent iconImage;
if (talent.Icon is null) if (talent.Icon is null)
@@ -1971,6 +2049,7 @@ namespace Barotrauma
OutlineColor = GUIStyle.Red, OutlineColor = GUIStyle.Red,
TextColor = GUIStyle.Red, TextColor = GUIStyle.Red,
PressedColor = unselectableColor, PressedColor = unselectableColor,
DisabledColor = unselectableColor,
CanBeFocused = false, CanBeFocused = false,
}; };
} }
@@ -1979,63 +2058,63 @@ namespace Barotrauma
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{ {
PressedColor = unselectableColor, PressedColor = unselectableColor,
DisabledColor = unselectableColor * 0.5f,
CanBeFocused = false, CanBeFocused = false,
}; };
} }
iconImage.Enabled = talentButton.Enabled;
talentButtons.Add((talentButton, iconImage)); talentButtons.Add((talentButton, iconImage));
} }
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
} }
} }
} GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f };
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform)); GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null); GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUIStyle.Green) barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{ {
IsHorizontal = true, IsHorizontal = true,
}; };
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight) experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{ {
Shadow = true, Shadow = true,
ToolTip = TextManager.Get("experiencetooltip") ToolTip = TextManager.Get("experiencetooltip")
}; };
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale") talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{ {
OnClicked = ResetTalentSelection OnClicked = ResetTalentSelection
}; };
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale") talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{ {
OnClicked = ApplyTalentSelection, OnClicked = ApplyTalentSelection,
}; };
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock); GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
}
UpdateTalentInfo(); UpdateTalentInfo();
} }
private void CreateTalentSkillList(Character character, GUIListBox parent) private void CreateTalentSkillList(Character character, CharacterInfo info, GUIListBox parent)
{ {
parent.Content.ClearChildren(); parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>(); List<GUITextBlock> skillNames = new List<GUITextBlock>();
foreach (Skill skill in character.Info.Job.GetSkills()) foreach (Skill skill in info.Job.GetSkills())
{ {
GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false };
skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value))); skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value)));
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) }; new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) };
float modifiedSkillLevel = character.GetSkillLevel(skill.Identifier); float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level;
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
{ {
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level); int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level);
@@ -2129,7 +2208,7 @@ namespace Barotrauma
talentButton.icon.HoverColor = hoverColor; talentButton.icon.HoverColor = hoverColor;
} }
CreateTalentSkillList(controlledCharacter, skillListBox); CreateTalentSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
} }
private void ApplyTalents(Character controlledCharacter) private void ApplyTalents(Character controlledCharacter)
@@ -2157,6 +2236,7 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData) private bool ResetTalentSelection(GUIButton guiButton, object userData)
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return false; }
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentInfo(); UpdateTalentInfo();
return true; return true;
@@ -546,6 +546,10 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
#if DEBUG
LevelGenerationParams.CheckValidity();
#endif
MainMenuScreen.Select(); MainMenuScreen.Select();
foreach (Identifier steamError in SteamManager.InitializationErrors) foreach (Identifier steamError in SteamManager.InitializationErrors)
@@ -1033,11 +1037,6 @@ namespace Barotrauma
{ {
GUI.SetSavingIndicatorState(true); GUI.SetSavingIndicatorState(true);
if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
{
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
}
// Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called) // Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called)
if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null) if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null)
{ {
@@ -81,7 +81,6 @@ namespace Barotrauma
: this(isSinglePlayer) : this(isSinglePlayer)
{ {
AddCharacterElements(element); AddCharacterElements(element);
ActiveOrdersElement = element.GetChildElement("activeorders");
} }
partial void InitProjectSpecific() partial void InitProjectSpecific()
@@ -3661,9 +3660,9 @@ namespace Barotrauma
crewList.ClearChildren(); crewList.ClearChildren();
} }
public void Save(XElement parentElement) public XElement Save(XElement parentElement)
{ {
XElement element = new XElement("crew"); var element = new XElement("crew");
for (int i = 0; i < characterInfos.Count; i++) for (int i = 0; i < characterInfos.Count; i++)
{ {
var ci = characterInfos[i]; var ci = characterInfos[i];
@@ -3674,8 +3673,8 @@ namespace Barotrauma
infoElement.Add(new XAttribute("crewlistindex", ci.CrewListIndex)); infoElement.Add(new XAttribute("crewlistindex", ci.CrewListIndex));
if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); } if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
} }
SaveActiveOrders(element); parentElement?.Add(element);
parentElement.Add(element); return element;
} }
public static void ClientReadActiveOrders(IReadMessage inc) public static void ClientReadActiveOrders(IReadMessage inc)
@@ -13,6 +13,11 @@ namespace Barotrauma
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged) partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged)
{ {
if (Owner is Some<Character> { Value: var character })
{
if (!character.IsPlayer) { return; }
}
CampaignMode campaign = GameMain.GameSession?.Campaign; CampaignMode campaign = GameMain.GameSession?.Campaign;
WalletChangedData data = new WalletChangedData WalletChangedData data = new WalletChangedData
{ {
@@ -92,6 +92,7 @@ namespace Barotrauma
break; break;
case "crew": case "crew":
GameMain.GameSession.CrewManager = new CrewManager(subElement, true); GameMain.GameSession.CrewManager = new CrewManager(subElement, true);
ActiveOrdersElement = element.GetChildElement("activeorders");
break; break;
case "map": case "map":
map = Map.Load(this, subElement, Settings); map = Map.Load(this, subElement, Settings);
@@ -242,11 +243,10 @@ namespace Barotrauma
crewDead = false; crewDead = false;
endTimer = 5.0f; endTimer = 5.0f;
CrewManager.InitSinglePlayerRound(); CrewManager.InitSinglePlayerRound();
if (petsElement != null) LoadPets();
{ LoadActiveOrders();
PetBehavior.LoadPets(petsElement);
} CargoManager.InitPurchasedIDCards();
CrewManager.LoadActiveOrders();
GUI.DisableSavingIndicatorDelayed(); GUI.DisableSavingIndicatorDelayed();
} }
@@ -461,41 +461,7 @@ namespace Barotrauma
if (success) if (success)
{ {
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
{
Submarine.MainSub = leavingSub;
GameMain.GameSession.Submarine = leavingSub;
GameMain.GameSession.SubmarineInfo = leavingSub.Info;
leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub");
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info);
foreach (Submarine sub in subsToLeaveBehind)
{
GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name);
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
LinkedSubmarine.CreateDummy(leavingSub, sub);
}
}
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
if (PendingSubmarineSwitch != null)
{
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
{
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
{
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
break;
}
}
}
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
PendingSubmarineSwitch = null;
} }
else else
{ {
@@ -766,11 +732,10 @@ namespace Barotrauma
c.Info.SaveOrderData(); c.Info.SaveOrderData();
} }
petsElement = new XElement("pets"); SavePets(modeElement);
PetBehavior.SavePets(petsElement); var crewManagerElement = CrewManager.Save(modeElement);
modeElement.Add(petsElement); SaveActiveOrders(crewManagerElement);
CrewManager.Save(modeElement);
CampaignMetadata.Save(modeElement); CampaignMetadata.Save(modeElement);
Map.Save(modeElement); Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement); CargoManager?.SavePurchasedItems(modeElement);
@@ -257,7 +257,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2.0f); yield return new WaitForSeconds(2.0f);
}*/ }*/
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Medical supplies objective TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Medical supplies objective
do do
{ {
@@ -275,7 +275,7 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected); do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(0.5f, false); yield return new WaitForSeconds(0.5f, false);
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Retrieve equipment TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Retrieve equipment
bool firstSlotRemoved = false; bool firstSlotRemoved = false;
bool secondSlotRemoved = false; bool secondSlotRemoved = false;
bool thirdSlotRemoved = false; bool thirdSlotRemoved = false;
@@ -330,7 +330,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(0.0f, false); yield return new WaitForSeconds(0.0f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected); do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Equipment & inventory objective TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Equipment & inventory objective
SetHighlight(mechanic_equipmentCabinet.Item, true); SetHighlight(mechanic_equipmentCabinet.Item, true);
bool firstSlotRemoved = false; bool firstSlotRemoved = false;
bool secondSlotRemoved = false; bool secondSlotRemoved = false;
@@ -372,7 +372,7 @@ namespace Barotrauma.Tutorials
// Room 3 // Room 3
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected); do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Welding objective TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Welding objective
do do
{ {
if (!mechanic.HasEquippedItem("divingmask".ToIdentifier())) if (!mechanic.HasEquippedItem("divingmask".ToIdentifier()))
@@ -106,7 +106,7 @@ namespace Barotrauma.Tutorials
Character.Controlled = character; Character.Controlled = character;
character.GiveJobItems(null); character.GiveJobItems(null);
var idCard = character.Inventory.FindItemByIdentifier("idcard".ToIdentifier()); var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
if (idCard == null) if (idCard == null)
{ {
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!"); DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
@@ -525,6 +525,7 @@ namespace Barotrauma
if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner)
{ {
syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f); syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f);
doubleClickedItems.Clear();
return; return;
} }
@@ -931,7 +932,7 @@ namespace Barotrauma
// Move the item from the subinventory to the selected container // Move the item from the subinventory to the selected container
return QuickUseAction.PutToContainer; return QuickUseAction.PutToContainer;
} }
else else if (character.Inventory.AccessibleWhenAlive || character.Inventory.AccessibleByOwner)
{ {
// Take from the subinventory and place it in the character's main inventory if no target container is selected // Take from the subinventory and place it in the character's main inventory if no target container is selected
return QuickUseAction.TakeFromContainer; return QuickUseAction.TakeFromContainer;
@@ -959,7 +960,8 @@ namespace Barotrauma
} }
else if (character.SelectedBy?.Inventory != null && else if (character.SelectedBy?.Inventory != null &&
Character.Controlled == character.SelectedBy && Character.Controlled == character.SelectedBy &&
!character.SelectedBy.Inventory.Locked && !character.SelectedBy.Inventory.Locked &&
(character.SelectedBy.Inventory.AccessibleWhenAlive || character.SelectedBy.Inventory.AccessibleByOwner) &&
allowInventorySwap) allowInventorySwap)
{ {
return QuickUseAction.TakeFromCharacter; return QuickUseAction.TakeFromCharacter;
@@ -66,6 +66,9 @@ namespace Barotrauma.Items.Components
rect.Height = (int)(rect.Height * (1.0f - openState)); rect.Height = (int)(rect.Height * (1.0f - openState));
} }
//only merge the door's convex hull with overlapping wall segments if it's fully open or fully closed
//it's the heaviest part of changing the convex hull, and doesn't need to be done while the door is still in motion
bool mergeOverlappingSegments = openState <= 0.0f || openState >= 1.0f;
if (Window.Height > 0 && Window.Width > 0) if (Window.Height > 0 && Window.Width > 0)
{ {
if (IsHorizontal) if (IsHorizontal)
@@ -88,7 +91,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull2.Enabled = true; convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2)); convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
} }
} }
} }
@@ -112,7 +115,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull2.Enabled = true; convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2)); convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
} }
} }
} }
@@ -127,7 +130,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull.Enabled = true; convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect)); convexHull.SetVertices(GetConvexHullCorners(rect), mergeOverlappingSegments);
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
@@ -78,7 +79,7 @@ namespace Barotrauma.Items.Components
activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
{ {
TextBlock = { AutoScaleHorizontal = true }, TextBlock = { AutoScaleHorizontal = true },
OnClicked = ToggleActive OnClicked = OnActivateButtonClicked
}; };
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true) TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
@@ -164,7 +165,7 @@ namespace Barotrauma.Items.Components
} }
} }
} }
activateButton.Enabled = outputsFound; activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
activateButton.Text = TextManager.Get(ActivateButtonText); activateButton.Text = TextManager.Get(ActivateButtonText);
}; };
} }
@@ -236,8 +237,19 @@ namespace Barotrauma.Items.Components
inSufficientPowerWarning.Visible = IsActive && !hasPower; inSufficientPowerWarning.Visible = IsActive && !hasPower;
} }
private bool ToggleActive(GUIButton button, object obj) private bool OnActivateButtonClicked(GUIButton button, object obj)
{ {
var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false);
if (disallowedItem != null)
{
int index = inputContainer.Inventory.FindIndex(disallowedItem);
if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length)
{
var slot = inputContainer.Inventory.visualSlots[index];
slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
}
return true;
}
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
pendingState = !IsActive; pendingState = !IsActive;
@@ -247,7 +259,6 @@ namespace Barotrauma.Items.Components
{ {
SetActive(!IsActive, Character.Controlled); SetActive(!IsActive, Character.Controlled);
} }
return true; return true;
} }
@@ -1367,6 +1367,15 @@ namespace Barotrauma.Items.Components
pingRadius, prevPingRadius, pingRadius, prevPingRadius,
250.0f, 150.0f, range, pingStrength, passive); 250.0f, 150.0f, range, pingStrength, passive);
} }
if (pingSource.Y - Level.Loaded.BottomPos < range)
{
CreateBlipsForLine(
new Vector2(pingSource.X - range, Level.Loaded.BottomPos),
new Vector2(pingSource.X + range, Level.Loaded.BottomPos),
pingSource, transducerPos,
pingRadius, prevPingRadius,
250.0f, 150.0f, range, pingStrength, passive);
}
List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7); List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
foreach (Voronoi2.VoronoiCell cell in cells) foreach (Voronoi2.VoronoiCell cell in cells)
@@ -23,10 +23,10 @@ namespace Barotrauma.Items.Components
} }
#endif #endif
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private List<ParticleEmitter> particleEmitterHitStructure = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitterHitStructure = new List<ParticleEmitter>();
private List<ParticleEmitter> particleEmitterHitCharacter = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitterHitCharacter = new List<ParticleEmitter>();
private List<Pair<RelatedItem, ParticleEmitter>> particleEmitterHitItem = new List<Pair<RelatedItem, ParticleEmitter>>(); private readonly List<(RelatedItem relatedItem, ParticleEmitter emitter)> particleEmitterHitItem = new List<(RelatedItem relatedItem, ParticleEmitter emitter)>();
private float prevProgressBarState; private float prevProgressBarState;
private Item prevProgressBarTarget = null; private Item prevProgressBarTarget = null;
@@ -46,10 +46,7 @@ namespace Barotrauma.Items.Components
Identifier[] excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifiers", Array.Empty<Identifier>()); Identifier[] excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifiers", Array.Empty<Identifier>());
if (excludedIdentifiers.Length == 0) { excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifier", Array.Empty<Identifier>()); } if (excludedIdentifiers.Length == 0) { excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifier", Array.Empty<Identifier>()); }
particleEmitterHitItem.Add( particleEmitterHitItem.Add((new RelatedItem(identifiers, excludedIdentifiers), new ParticleEmitter(subElement)));
new Pair<RelatedItem, ParticleEmitter>(
new RelatedItem(identifiers, excludedIdentifiers),
new ParticleEmitter(subElement)));
break; break;
case "particleemitterhitstructure": case "particleemitterhitstructure":
particleEmitterHitStructure.Add(new ParticleEmitter(subElement)); particleEmitterHitStructure.Add(new ParticleEmitter(subElement));
@@ -139,11 +136,11 @@ namespace Barotrauma.Items.Components
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition); Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition; if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitItem) foreach ((RelatedItem relatedItem, ParticleEmitter emitter) in particleEmitterHitItem)
{ {
if (!emitter.First.MatchesItem(targetItem)) { continue; } if (!relatedItem.MatchesItem(targetItem)) { continue; }
float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
} }
} }
#if DEBUG #if DEBUG
@@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components
int totalWireCount = 0; int totalWireCount = 0;
foreach (Connection c in panel.Connections) foreach (Connection c in panel.Connections)
{ {
totalWireCount += c.Wires.Count(w => w != null); totalWireCount += c.Wires.Count;
} }
Wire equippedWire = null; Wire equippedWire = null;
@@ -87,8 +87,8 @@ namespace Barotrauma.Items.Components
(DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null) || (DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null) ||
(DraggingConnected.Connections.Contains(c) && DraggingConnected.Connections.Contains(null))) (DraggingConnected.Connections.Contains(c) && DraggingConnected.Connections.Contains(null)))
{ {
int linkIndex = c.FindWireIndex(DraggingConnected.Item); var linkedWire = c.FindWireByItem(DraggingConnected.Item);
if (linkIndex > -1 || panel.DisconnectedWires.Contains(DraggingConnected)) if (linkedWire != null || panel.DisconnectedWires.Contains(DraggingConnected))
{ {
Inventory.DraggingItems.Clear(); Inventory.DraggingItems.Clear();
Inventory.DraggingItems.Add(DraggingConnected.Item); Inventory.DraggingItems.Add(DraggingConnected.Item);
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval); c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval);
} }
rightPos.Y += connectorIntervalLeft; rightPos.Y += connectorIntervalLeft;
rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; rightWirePos.Y += c.Wires.Count * wireInterval;
} }
else else
{ {
@@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components
c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval); c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval);
} }
leftPos.Y += connectorIntervalRight; leftPos.Y += connectorIntervalRight;
leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; leftWirePos.Y += c.Wires.Count * wireInterval;
} }
} }
} }
@@ -228,15 +228,15 @@ namespace Barotrauma.Items.Components
{ {
float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale; float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale;
for (int i = 0; i < MaxWires; i++) foreach (var wire in wires)
{ {
if (wires[i] == null || wires[i].Hidden || (DraggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; } if (wire.Hidden || (DraggingConnected == wire && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; }
if (wires[i].HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; } if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
Connection recipient = wires[i].OtherConnection(this); Connection recipient = wire.OtherConnection(this);
LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})"; LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
if (wires[i].Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); } if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
DrawWire(spriteBatch, wires[i], position, wirePosition, equippedWire, panel, label); DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label);
wirePosition.Y += wireInterval; wirePosition.Y += wireInterval;
} }
@@ -248,18 +248,17 @@ namespace Barotrauma.Items.Components
if (!PlayerInput.PrimaryMouseButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
if ((GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) && if ((GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) &&
Wires.Count(w => w != null) < MaxPlayerConnectableWires) Wires.Count < MaxPlayerConnectableWires)
{ {
//find an empty cell for the new connection //find an empty cell for the new connection
int index = FindEmptyIndex(); if (WireSlotsAvailable() && !Wires.Contains(DraggingConnected))
if (index > -1 && !Wires.Contains(DraggingConnected))
{ {
bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item); bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item);
DraggingConnected.RemoveConnection(panel.Item); DraggingConnected.RemoveConnection(panel.Item);
if (DraggingConnected.Connect(this, !alreadyConnected, true)) if (DraggingConnected.Connect(this, !alreadyConnected, true))
{ {
var otherConnection = DraggingConnected.OtherConnection(this); var otherConnection = DraggingConnected.OtherConnection(this);
SetWire(index, DraggingConnected); ConnectWire(DraggingConnected);
} }
} }
} }
@@ -284,7 +283,7 @@ namespace Barotrauma.Items.Components
flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale); flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale);
} }
if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen))) if (Wires.Any(w => w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen)))
{ {
int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count;
screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale); screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale);
@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
} }
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; } if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; }
MoveConnectedWires(amount); MoveConnectedWires(amount);
@@ -173,9 +173,8 @@ namespace Barotrauma.Items.Components
private void ApplyRemoteState(IReadMessage msg) private void ApplyRemoteState(IReadMessage msg)
{ {
List<Wire> prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList(); List<Wire> prevWires = Connections.SelectMany(c => c.Wires).ToList();
List<Wire> newWires = new List<Wire>();
ushort userID = msg.ReadUInt16(); ushort userID = msg.ReadUInt16();
if (userID == 0) if (userID == 0)
@@ -195,7 +194,9 @@ namespace Barotrauma.Items.Components
foreach (Connection connection in Connections) foreach (Connection connection in Connections)
{ {
for (int i = 0; i < connection.MaxWires; i++) HashSet<Wire> newWires = new HashSet<Wire>();
uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++)
{ {
ushort wireId = msg.ReadUInt16(); ushort wireId = msg.ReadUInt16();
@@ -204,9 +205,18 @@ namespace Barotrauma.Items.Components
if (wireComponent == null) { continue; } if (wireComponent == null) { continue; }
newWires.Add(wireComponent); newWires.Add(wireComponent);
}
connection.SetWire(i, wireComponent); Wire[] oldWires = connection.Wires.Where(w => !newWires.Contains(w)).ToArray();
wireComponent.Connect(connection, false); foreach (var wire in oldWires)
{
connection.DisconnectWire(wire);
}
foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray())
{
connection.ConnectWire(wire);
wire.Connect(connection, false);
} }
} }
@@ -205,7 +205,7 @@ namespace Barotrauma.Items.Components
foreach (var uiElement in uiElements) foreach (var uiElement in uiElements)
{ {
if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; } if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; }
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Any(w => w != null)); bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
if (visible) { visibleElementCount++; } if (visible) { visibleElementCount++; }
if (uiElement.Visible != visible) if (uiElement.Visible != visible)
{ {
@@ -526,7 +526,7 @@ namespace Barotrauma.Items.Components
} }
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
//only used in the sub editor, hence only in the client project //only used in the sub editor, hence only in the client project
if (!item.IsSelected) { return; } if (!item.IsSelected) { return; }
@@ -175,7 +175,7 @@ namespace Barotrauma.Items.Components
}; };
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
widgets.Clear(); widgets.Clear();
} }
@@ -6,8 +6,10 @@ using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
{ {
private GUIMessageBox autodockingVerification;
public Vector2 DrawSize public Vector2 DrawSize
{ {
//use the extents of the item as the draw size //use the extents of the item as the draw size
@@ -180,5 +182,10 @@ namespace Barotrauma.Items.Components
Undock(); Undock();
} }
} }
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write((byte)allowOutpostAutoDocking);
}
} }
} }
@@ -265,7 +265,7 @@ namespace Barotrauma
else else
{ {
LocalizedString description = item.Description; LocalizedString description = item.Description;
if (item.Prefab.Identifier == "idcard" || item.Tags.Contains("despawncontainer")) if (item.HasTag("identitycard") || item.HasTag("despawncontainer"))
{ {
string[] readTags = item.Tags.Split(','); string[] readTags = item.Tags.Split(',');
string idName = null; string idName = null;
@@ -1,4 +1,6 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.MapCreatures.Behavior;
using Barotrauma.Networking; using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -6,13 +8,8 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -720,7 +717,7 @@ namespace Barotrauma
//remove identifiers from the available container tags //remove identifiers from the available container tags
//(otherwise the list will include many irrelevant options, //(otherwise the list will include many irrelevant options,
//e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc) //e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc)
.Where(t => !ItemPrefab.Prefabs.Any(ip => ip.Identifier == t)) .Where(t => !ItemPrefab.Prefabs.ContainsKey(t))
.ToImmutableHashSet(); .ToImmutableHashSet();
new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...") new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...")
{ {
@@ -1174,7 +1171,7 @@ namespace Barotrauma
texts.Clear(); texts.Clear();
string nameText = Name; string nameText = Name;
if (Prefab.Identifier == "idcard" || Tags.Contains("despawncontainer")) if (Prefab.Tags.Contains("identitycard") || Tags.Contains("despawncontainer"))
{ {
string[] readTags = Tags.Split(','); string[] readTags = Tags.Split(',');
string idName = null; string idName = null;
@@ -80,15 +80,17 @@ namespace Barotrauma
} }
Vector2 center = new Vector2((minX + maxX) / 2.0f, (minY + maxY) / 2.0f); Vector2 center = new Vector2((minX + maxX) / 2.0f, (minY + maxY) / 2.0f);
if (Submarine.MainSub != null) { center -= Submarine.MainSub.HiddenSubPosition; } if (Submarine.MainSub != null) { center -= Submarine.MainSub.HiddenSubPosition; }
center.X -= MathUtils.RoundTowardsClosest(center.X, Submarine.GridSize.X);
center.Y -= MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y); Vector2 offsetFromGrid = new Vector2(
MathUtils.RoundTowardsClosest(center.X, Submarine.GridSize.X) - center.X,
MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2);
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
assemblyEntities.ForEach(e => MapEntity.AddSelection(e)); assemblyEntities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities) foreach (MapEntity mapEntity in assemblyEntities)
{ {
mapEntity.Move(-center); mapEntity.Move(-center - offsetFromGrid);
mapEntity.Submarine = Submarine.MainSub; mapEntity.Submarine = Submarine.MainSub;
var entityElement = mapEntity.Save(element); var entityElement = mapEntity.Save(element);
if (disabledEntities.Contains(mapEntity)) if (disabledEntities.Contains(mapEntity))
@@ -461,7 +461,7 @@ namespace Barotrauma.Lights
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) * Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) * Matrix.CreateRotationZ(amount) *
Matrix.CreateTranslation(origin.X, origin.Y, 0.0f); Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix); SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix);
} }
private void CalculateDimensions() private void CalculateDimensions()
@@ -541,7 +541,7 @@ namespace Barotrauma.Lights
} }
} }
public void SetVertices(Vector2[] points, Matrix? rotationMatrix = null) public void SetVertices(Vector2[] points, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null)
{ {
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported"); Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
@@ -594,13 +594,16 @@ namespace Barotrauma.Lights
if (ParentEntity == null) { return; } if (ParentEntity == null) { return; }
var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine); if (mergeOverlappingSegments)
if (chList != null)
{ {
overlappingHulls.Clear(); var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine);
foreach (ConvexHull ch in chList.List) if (chList != null)
{ {
MergeOverlappingSegments(ch); overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
}
} }
} }
} }
@@ -81,11 +81,18 @@ namespace Barotrauma
} }
catch (System.IO.FileNotFoundException e) catch (System.IO.FileNotFoundException e)
{ {
string errorMsg = "Failed to load sound file \"" + filename + "\"."; 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()); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null; return null;
} }
catch (System.IO.InvalidDataException e)
{
string errorMsg = "Failed to load sound file \"" + filename + "\" (invalid data).";
DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:InvalidData" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null;
}
} }
RoundSound newSound = new RoundSound(element, existingSound); RoundSound newSound = new RoundSound(element, existingSound);
@@ -525,7 +525,7 @@ namespace Barotrauma
Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2)); Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2));
for (int i = 0; i < item.Connections.Count; i++) for (int i = 0; i < item.Connections.Count; i++)
{ {
int wireCount = item.Connections[i].Wires.Count(w => w != null); int wireCount = item.Connections[i].Wires.Count;
if (doorLinks + wireCount > item.Connections[i].MaxWires) if (doorLinks + wireCount > item.Connections[i].MaxWires)
{ {
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System; using System;
namespace Barotrauma.Networking namespace Barotrauma.Networking
@@ -183,6 +184,11 @@ namespace Barotrauma.Networking
break; break;
default: default:
GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor); GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor);
if (type == ChatMessageType.Radio && CanUseRadio(senderCharacter, out WifiComponent radio))
{
Signal s = new Signal(txt, sender: senderCharacter, source: radio.Item);
radio.TransmitSignal(s, sentFromChat: true);
}
break; break;
} }
LastID = id; LastID = id;
@@ -3,6 +3,7 @@ using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.IO; using Barotrauma.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
@@ -11,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -182,6 +184,20 @@ namespace Barotrauma.Networking
get { return ownerKey > 0 || steamP2POwner; } get { return ownerKey > 0 || steamP2POwner; }
} }
internal readonly struct PermissionChangedEvent
{
public readonly ClientPermissions NewPermissions;
public readonly ImmutableArray<string> NewPermittedConsoleCommands;
public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList<string> newPermittedConsoleCommands)
{
NewPermissions = newPermissions;
NewPermittedConsoleCommands = newPermittedConsoleCommands.ToImmutableArray();
}
}
public readonly NamedEvent<PermissionChangedEvent> OnPermissionChanged = new NamedEvent<PermissionChangedEvent>();
public GameClient(string newName, string ip, UInt64 steamId, string serverName = null, int ownerKey = 0, bool steamP2POwner = false) public GameClient(string newName, string ip, UInt64 steamId, string serverName = null, int ownerKey = 0, bool steamP2POwner = false)
{ {
//TODO: gui stuff should probably not be here? //TODO: gui stuff should probably not be here?
@@ -570,7 +586,12 @@ namespace Barotrauma.Networking
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
#if DEBUG #if DEBUG
if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return; if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.P)) return;
if (PlayerInput.KeyHit(Keys.Home))
{
OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, permittedConsoleCommands));
}
#endif #endif
foreach (Client c in ConnectedClients) foreach (Client c in ConnectedClients)
@@ -1019,40 +1040,25 @@ namespace Barotrauma.Networking
GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers); GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers);
} }
byte equalityCheckValueCount = inc.ReadByte(); var levelEqualityCheckValues = new Dictionary<Level.LevelGenStage, int>();
List<int> levelEqualityCheckValues = new List<int>(); foreach (Level.LevelGenStage stage in Enum.GetValues(typeof(Level.LevelGenStage)).OfType<Level.LevelGenStage>().OrderBy(s => s))
for (int i = 0; i < equalityCheckValueCount; i++)
{ {
levelEqualityCheckValues.Add(inc.ReadInt32()); levelEqualityCheckValues.Add(stage, inc.ReadInt32());
} }
if (Level.Loaded.EqualityCheckValues.Count != levelEqualityCheckValues.Count) foreach (var stage in levelEqualityCheckValues.Keys)
{ {
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + if (Level.Loaded.EqualityCheckValues[stage] != levelEqualityCheckValues[stage])
" (client value count: " + Level.Loaded.EqualityCheckValues.Count +
", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
", mirrored: " + Level.Loaded.Mirrored + ").";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
else
{
for (int i = 0; i < equalityCheckValueCount; i++)
{ {
if (Level.Loaded.EqualityCheckValues[i] != levelEqualityCheckValues[i]) string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" +
{ " (client value " + stage + ": " + Level.Loaded.EqualityCheckValues[stage].ToString("X") +
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + ", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") +
" (client value #" + i + ": " + Level.Loaded.EqualityCheckValues[i] + ", level value count: " + levelEqualityCheckValues.Count +
", server value #" + i + ": " + levelEqualityCheckValues[i].ToString("X") + ", seed: " + Level.Loaded.Seed +
", level value count: " + levelEqualityCheckValues.Count + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
", seed: " + Level.Loaded.Seed + ", mirrored: " + Level.Loaded.Mirrored + ").";
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
", mirrored: " + Level.Loaded.Mirrored + ")."; throw new Exception(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
} }
} }
@@ -1076,6 +1082,7 @@ namespace Barotrauma.Networking
reconnectBox?.Close(); reconnectBox?.Close();
reconnectBox = null; reconnectBox = null;
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore(); ContentPackageManager.EnabledPackages.Restore();
GUI.ClearCursorWait(); GUI.ClearCursorWait();
@@ -1380,18 +1387,13 @@ namespace Barotrauma.Networking
private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<string> permittedConsoleCommands) private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<string> permittedConsoleCommands)
{ {
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
{ {
if (newPermissions == permissions) return; if (newPermissions == permissions) return;
} }
bool refreshCampaignUI = false; bool refreshCampaignUI = permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) ||
permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound);
if (permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) ||
permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound))
{
refreshCampaignUI = true;
}
permissions = newPermissions; permissions = newPermissions;
this.permittedConsoleCommands = new List<string>(permittedConsoleCommands); this.permittedConsoleCommands = new List<string>(permittedConsoleCommands);
@@ -1430,7 +1432,7 @@ namespace Barotrauma.Networking
if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands)) if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands))
{ {
var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform),
TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont); TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont);
var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)); var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform));
foreach (string permittedCommand in permittedConsoleCommands) foreach (string permittedCommand in permittedConsoleCommands)
{ {
@@ -1469,6 +1471,7 @@ namespace Barotrauma.Networking
} }
GameMain.NetLobbyScreen.RefreshEnabledElements(); GameMain.NetLobbyScreen.RefreshEnabledElements();
OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, this.permittedConsoleCommands));
} }
private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc) private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc)
@@ -3680,7 +3683,9 @@ namespace Barotrauma.Networking
} }
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); errorLines.Add("Level: " + Level.Loaded.Seed + ", "
+ string.Join("; ", Level.Loaded.EqualityCheckValues.Select(cv
=> cv.Key + "=" + cv.Value.ToString("X"))));
errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate);
errorLines.Add("Entities:"); errorLines.Add("Entities:");
foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex)) foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex))
@@ -621,7 +621,7 @@ namespace Barotrauma.Networking
{ {
Stretch = true Stretch = true
}; };
var losModeRadioButtonGroup = new GUIRadioButtonGroup(); var losModeRadioButtonGroup = new GUIRadioButtonGroup();
LosMode[] losModes = (LosMode[])Enum.GetValues(typeof(LosMode)); LosMode[] losModes = (LosMode[])Enum.GetValues(typeof(LosMode));
for (int i = 0; i < losModes.Length; i++) for (int i = 0; i < losModes.Length; i++)
@@ -634,6 +634,14 @@ namespace Barotrauma.Networking
var traitorsMinPlayerCount = CreateLabeledNumberInput(roundsTab, "ServerSettingsTraitorsMinPlayerCount", 1, 16, "ServerSettingsTraitorsMinPlayerCountToolTip"); var traitorsMinPlayerCount = CreateLabeledNumberInput(roundsTab, "ServerSettingsTraitorsMinPlayerCount", 1, 16, "ServerSettingsTraitorsMinPlayerCountToolTip");
GetPropertyData(nameof(TraitorsMinPlayerCount)).AssignGUIComponent(traitorsMinPlayerCount); GetPropertyData(nameof(TraitorsMinPlayerCount)).AssignGUIComponent(traitorsMinPlayerCount);
var maximumTransferAmount = CreateLabeledNumberInput(roundsTab, "serversettingsmaximumtransferrequest", 0, CampaignMode.MaxMoney, "serversettingsmaximumtransferrequesttooltip");
GetPropertyData(nameof(MaximumTransferRequest)).AssignGUIComponent(maximumTransferAmount);
var lootedMoneyDestination = CreateLabeledDropdown(roundsTab, "serversettingslootedmoneydestination", numElements: 2, "serversettingslootedmoneydestinationtooltip");
lootedMoneyDestination.AddItem(TextManager.Get("lootedmoneydestination.bank"), LootedMoneyDestination.Bank);
lootedMoneyDestination.AddItem(TextManager.Get("lootedmoneydestination.wallet"), LootedMoneyDestination.Wallet);
GetPropertyData(nameof(LootedMoneyDestination)).AssignGUIComponent(lootedMoneyDestination);
var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton"));
GetPropertyData(nameof(AllowRagdollButton)).AssignGUIComponent(ragdollButtonBox); GetPropertyData(nameof(AllowRagdollButton)).AssignGUIComponent(ragdollButtonBox);
@@ -991,6 +999,32 @@ namespace Barotrauma.Networking
return input; return input;
} }
private GUIDropDown CreateLabeledDropdown(GUIComponent parent, string labelTag, int numElements, string toolTipTag = null)
{
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f,
ToolTip = TextManager.Get(labelTag)
};
var label = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform),
TextManager.Get(labelTag), textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont)
{
AutoScaleHorizontal = true
};
if (!string.IsNullOrEmpty(toolTipTag))
{
label.ToolTip = TextManager.Get(toolTipTag);
}
var input = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), elementCount: numElements);
container.RectTransform.MinSize = new Point(0, input.RectTransform.MinSize.Y);
container.RectTransform.MaxSize = new Point(int.MaxValue, input.RectTransform.MaxSize.Y);
return input;
}
private bool SelectSettingsTab(GUIButton button, object obj) private bool SelectSettingsTab(GUIButton button, object obj)
{ {
settingsTabIndex = (int)obj; settingsTabIndex = (int)obj;
@@ -103,7 +103,7 @@ namespace Barotrauma.Particles
{ {
return debugName; return debugName;
} }
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null) public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
this.prefab = prefab; this.prefab = prefab;
#if DEBUG #if DEBUG
@@ -149,13 +149,13 @@ namespace Barotrauma.Particles
if (prefab.LifeTimeMin <= 0.0f) if (prefab.LifeTimeMin <= 0.0f)
{ {
totalLifeTime = prefab.LifeTime; totalLifeTime = prefab.LifeTime * lifeTimeMultiplier;
lifeTime = prefab.LifeTime; lifeTime = prefab.LifeTime * lifeTimeMultiplier;
} }
else else
{ {
totalLifeTime = Rand.Range(prefab.LifeTimeMin, prefab.LifeTime); totalLifeTime = Rand.Range(prefab.LifeTimeMin, prefab.LifeTime) * lifeTimeMultiplier;
lifeTime = totalLifeTime; lifeTime = totalLifeTime * lifeTimeMultiplier;
} }
startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax); startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax);
@@ -85,6 +85,9 @@ namespace Barotrauma.Particles
[Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)] [Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)]
public Color ColorMultiplier { get; set; } public Color ColorMultiplier { get; set; }
[Editable, Serialize(1f, IsPropertySaveable.Yes)]
public float LifeTimeMultiplier { get; set; }
[Editable, Serialize(false, IsPropertySaveable.Yes)] [Editable, Serialize(false, IsPropertySaveable.Yes)]
public bool DrawOnTop { get; set; } public bool DrawOnTop { get; set; }
@@ -197,7 +200,7 @@ namespace Barotrauma.Particles
position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax); position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
} }
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints); var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
if (particle != null) if (particle != null)
{ {
@@ -76,7 +76,7 @@ namespace Barotrauma.Particles
return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints); return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints);
} }
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null) public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
if (prefab == null || prefab.Sprites.Count == 0) { return null; } if (prefab == null || prefab.Sprites.Count == 0) { return null; }
@@ -115,7 +115,7 @@ namespace Barotrauma.Particles
if (particles[particleCount] == null) { particles[particleCount] = new Particle(); } if (particles[particleCount] == null) { particles[particleCount] = new Particle(); }
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints); particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, lifeTimeMultiplier, tracerPoints: tracerPoints);
particleCount++; particleCount++;
@@ -77,9 +77,20 @@ namespace Barotrauma
if (remoteContentDoc?.Root != null) if (remoteContentDoc?.Root != null)
{ {
remoteContentContainer.ClearChildren(); remoteContentContainer.ClearChildren();
foreach (var subElement in remoteContentDoc.Root.Elements()) try
{ {
GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform); foreach (var subElement in remoteContentDoc.Root.Elements())
{
GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
}
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentParse:Exception", GameAnalyticsManager.ErrorSeverity.Error,
"Reading received remote main menu content failed. " + e.Message);
} }
} }
}; };
@@ -20,10 +20,11 @@ namespace Barotrauma
private ServerContentPackage? currentDownload; private ServerContentPackage? currentDownload;
private readonly List<ContentPackage> downloadedPackages = new List<ContentPackage>(); private readonly List<ContentPackage> downloadedPackages = new List<ContentPackage>();
public IEnumerable<ContentPackage> DownloadedPackages => downloadedPackages;
private bool confirmDownload; private bool confirmDownload;
private void Reset() public void Reset()
{ {
pendingDownloads.Clear(); pendingDownloads.Clear();
downloadedPackages.Clear(); downloadedPackages.Clear();
@@ -255,12 +256,6 @@ namespace Barotrauma
} }
} }
public override void Deselect()
{
Reset();
base.Deselect();
}
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
base.Update(deltaTime); base.Update(deltaTime);
@@ -1400,7 +1400,7 @@ namespace Barotrauma
public void CreatePlayerFrame(GUIComponent parent, bool createPendingText = true, bool alwaysAllowEditing = false) public void CreatePlayerFrame(GUIComponent parent, bool createPendingText = true, bool alwaysAllowEditing = false)
{ {
UpdatePlayerFrame( UpdatePlayerFrame(
Character.Controlled?.Info ?? playerInfoContainer.Children?.First().UserData as CharacterInfo, Character.Controlled?.Info ?? playerInfoContainer.Children?.First().UserData as CharacterInfo ?? GameMain.Client.CharacterInfo,
allowEditing: alwaysAllowEditing || campaignCharacterInfo == null, allowEditing: alwaysAllowEditing || campaignCharacterInfo == null,
parent: parent, parent: parent,
createPendingText: createPendingText); createPendingText: createPendingText);
@@ -3131,10 +3131,11 @@ namespace Barotrauma
retVal[i] = new GUIImage[outfitPreview.Sprites.Count]; retVal[i] = new GUIImage[outfitPreview.Sprites.Count];
for (int j = 0; j < outfitPreview.Sprites.Count; j++) for (int j = 0; j < outfitPreview.Sprites.Count; j++)
{ {
Pair<Sprite, Vector2> sprite = outfitPreview.Sprites[j]; Sprite sprite = outfitPreview.Sprites[j].sprite;
Vector2 drawOffset = outfitPreview.Sprites[j].drawOffset;
float aspectRatio = outfitPreview.Dimensions.Y / outfitPreview.Dimensions.X; float aspectRatio = outfitPreview.Dimensions.Y / outfitPreview.Dimensions.X;
retVal[i][j] = new GUIImage(new RectTransform(new Vector2(0.7f / aspectRatio, 0.7f), innerFrame.RectTransform, Anchor.Center) retVal[i][j] = new GUIImage(new RectTransform(new Vector2(0.7f / aspectRatio, 0.7f), innerFrame.RectTransform, Anchor.Center)
{ RelativeOffset = sprite.Second / outfitPreview.Dimensions }, sprite.First, scaleToFit: true) { RelativeOffset = drawOffset / outfitPreview.Dimensions }, sprite, scaleToFit: true)
{ {
PressedColor = Color.White, PressedColor = Color.White,
CanBeFocused = false CanBeFocused = false
@@ -1190,18 +1190,23 @@ namespace Barotrauma
frame.RectTransform.MaxSize = new Point(int.MaxValue, frame.Rect.Width); frame.RectTransform.MaxSize = new Point(int.MaxValue, frame.Rect.Width);
LocalizedString name = legacy ? TextManager.GetWithVariable("legacyitemformat", "[name]", ep.Name) : ep.Name; LocalizedString name = legacy ? TextManager.GetWithVariable("legacyitemformat", "[name]", ep.Name) : ep.Name;
frame.ToolTip = ep.Description.IsNullOrEmpty() ? name : name + '\n' + ep.Description; frame.ToolTip = $"{frame.ToolTip}\n‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{name}‖color:end‖";
if (!ep.Description.IsNullOrEmpty())
{
frame.ToolTip += '\n' + ep.Description;
}
if (ep.ContentPackage != GameMain.VanillaContent && ep.ContentPackage != null) if (ep.ContentPackage != GameMain.VanillaContent && ep.ContentPackage != null)
{ {
frame.Color = Color.Magenta; frame.Color = Color.Magenta;
frame.ToolTip = RichString.Rich($"{frame.ToolTip}\n‖color:{XMLExtensions.ToStringHex(Color.MediumPurple)}‖{ep.ContentPackage?.Name}‖color:end‖"); frame.ToolTip = $"{frame.ToolTip}\n‖color:{XMLExtensions.ToStringHex(Color.MediumPurple)}‖{ep.ContentPackage?.Name}‖color:end‖";
} }
if (ep.HideInMenus) if (ep.HideInMenus)
{ {
frame.Color = Color.Red; frame.Color = Color.Red;
name = "[HIDDEN] " + name; name = "[HIDDEN] " + name;
} }
frame.ToolTip = RichString.Rich(frame.ToolTip);
GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -1976,7 +1981,7 @@ namespace Barotrauma
return true; return true;
}; };
nameBox.Text = subNameLabel?.Text?.SanitizedValue ?? ""; nameBox.Text = MainSub?.Info.Name ?? "";
submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit; submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit;
@@ -2750,6 +2755,8 @@ namespace Barotrauma
} }
} }
nameBox.Text = nameBox.Text.Trim();
bool hideInMenus = nameBox.Parent.GetChildByUserData("hideinmenus") is GUITickBox hideInMenusTickBox && hideInMenusTickBox.Selected; bool hideInMenus = nameBox.Parent.GetChildByUserData("hideinmenus") is GUITickBox hideInMenusTickBox && hideInMenusTickBox.Selected;
string saveFolder = Path.Combine(ContentPackage.LocalModsDir, nameBox.Text); string saveFolder = Path.Combine(ContentPackage.LocalModsDir, nameBox.Text);
string filePath = Path.Combine(saveFolder, $"{nameBox.Text}.xml").CleanUpPathCrossPlatform(); string filePath = Path.Combine(saveFolder, $"{nameBox.Text}.xml").CleanUpPathCrossPlatform();
@@ -2884,8 +2891,30 @@ namespace Barotrauma
{ {
if (deleteButtonHolder.FindChild("delete") is GUIButton deleteBtn) if (deleteButtonHolder.FindChild("delete") is GUIButton deleteBtn)
{ {
deleteBtn.Enabled = userData is SubmarineInfo subInfo deleteBtn.ToolTip = string.Empty;
&& GetContentPackageIntrinsicallyTiedToSub(subInfo) != null; if (!(userData is SubmarineInfo subInfo))
{
deleteBtn.Enabled = false;
return true;
}
var package = GetContentPackageIntrinsicallyTiedToSub(subInfo);
if (package != null)
{
deleteBtn.Enabled = true;
}
else
{
deleteBtn.Enabled = false;
if (ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == subInfo.FilePath) ?? false)
{
deleteBtn.ToolTip = TextManager.Get("cantdeletevanillasub");
}
else if (ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == subInfo.FilePath)) is ContentPackage subPackage)
{
deleteBtn.ToolTip = TextManager.GetWithVariable("cantdeletemodsub", "[modname]", subPackage.Name);
}
}
} }
return true; return true;
} }
@@ -2925,6 +2954,21 @@ namespace Barotrauma
ToolTip = sub.FilePath ToolTip = sub.FilePath
}; };
if (!(ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == sub.FilePath) ?? false))
{
if (GetContentPackageIntrinsicallyTiedToSub(sub) == null &&
ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
{
//workshop mod
textBlock.OverrideTextColor(Color.MediumPurple);
}
else
{
//local mod
textBlock.OverrideTextColor(GUIStyle.TextColorBright);
}
}
if (sub.HasTag(SubmarineTag.Shuttle)) if (sub.HasTag(SubmarineTag.Shuttle))
{ {
var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), textBlock.RectTransform, Anchor.CenterRight),
@@ -3037,6 +3081,24 @@ namespace Barotrauma
if (!(child.UserData is SubmarineInfo sub)) { continue; } if (!(child.UserData is SubmarineInfo sub)) { continue; }
child.Visible = string.IsNullOrEmpty(filter) || sub.Name.ToLower().Contains(filter.ToLower()); child.Visible = string.IsNullOrEmpty(filter) || sub.Name.ToLower().Contains(filter.ToLower());
} }
//go through the elements backwards, and disable the labels for sub categories if there's no subs visible in them
bool subVisibleInCategory = false;
foreach (GUIComponent child in subList.Content.Children.Reverse())
{
if (!(child.UserData is SubmarineInfo sub))
{
if (child.Enabled)
{
child.Visible = subVisibleInCategory;
}
subVisibleInCategory = false;
}
else
{
subVisibleInCategory |= child.Visible;
}
}
} }
/// <summary> /// <summary>
@@ -4828,7 +4890,7 @@ namespace Barotrauma
} }
} }
if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.ToggleInventory].IsHit() && mode == Mode.Default) if (PlayerInput.KeyHit(Keys.Q) && mode == Mode.Default)
{ {
toggleEntityMenuButton.OnClicked?.Invoke(toggleEntityMenuButton, toggleEntityMenuButton.UserData); toggleEntityMenuButton.OnClicked?.Invoke(toggleEntityMenuButton, toggleEntityMenuButton.UserData);
} }
@@ -233,16 +233,18 @@ namespace Barotrauma
float dist = diff.Length(); float dist = diff.Length();
float distFallOff = dist / FlowSoundRange; float distFallOff = dist / FlowSoundRange;
if (distFallOff >= 0.99f) continue; if (distFallOff >= 0.99f) { continue; }
float gain = MathHelper.Clamp(gapFlow / 100.0f, 0.0f, 1.0f);
//flow at the left side //flow at the left side
if (diff.X < 0) if (diff.X < 0)
{ {
targetFlowLeft[flowSoundIndex] += 1.0f - distFallOff; targetFlowLeft[flowSoundIndex] += gain - distFallOff;
} }
else else
{ {
targetFlowRight[flowSoundIndex] += 1.0f - distFallOff; targetFlowRight[flowSoundIndex] += gain - distFallOff;
} }
} }
} }
@@ -287,7 +289,7 @@ namespace Barotrauma
flowSoundChannels[i] = FlowSounds[i].Sound.Play(1.0f, FlowSoundRange, soundPos); flowSoundChannels[i] = FlowSounds[i].Sound.Play(1.0f, FlowSoundRange, soundPos);
flowSoundChannels[i].Looping = true; flowSoundChannels[i].Looping = true;
} }
flowSoundChannels[i].Gain = Math.Min(Math.Max(flowVolumeRight[i], flowVolumeLeft[i]), 1.0f); flowSoundChannels[i].Gain = Math.Max(flowVolumeRight[i], flowVolumeLeft[i]);
flowSoundChannels[i].Position = new Vector3(soundPos, 0.0f); flowSoundChannels[i].Position = new Vector3(soundPos, 0.0f);
} }
} }
@@ -853,7 +855,7 @@ namespace Barotrauma
if (SplashSounds.Count == 0) { return; } if (SplashSounds.Count == 0) { return; }
int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2.0f, 2.0f)), 0, SplashSounds.Count - 1); int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2.0f, 2.0f)), 0, SplashSounds.Count - 1);
float range = 800.0f; float range = 800.0f;
var channel = SplashSounds[splashIndex].Sound.Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null)); SplashSounds[splashIndex].Sound?.Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null));
} }
public static void PlayDamageSound(string damageType, float damage, PhysicsBody body) public static void PlayDamageSound(string damageType, float damage, PhysicsBody body)
@@ -39,9 +39,8 @@ namespace Barotrauma
get { return texture != null && !cannotBeLoaded; } get { return texture != null && !cannotBeLoaded; }
} }
public Sprite(Sprite other) : this(other.texture, other.sourceRect, other.offset, other.rotation) public Sprite(Sprite other) : this(other.texture, other.sourceRect, other.offset, other.rotation, other.FilePath.Value)
{ {
FilePath = other.FilePath;
Compress = other.Compress; Compress = other.Compress;
size = other.size; size = other.size;
effects = other.effects; effects = other.effects;
@@ -58,6 +57,17 @@ namespace Barotrauma
rotation = newRotation; rotation = newRotation;
FilePath = ContentPath.FromRaw(path); FilePath = ContentPath.FromRaw(path);
AddToList(this); AddToList(this);
if (!string.IsNullOrEmpty(path))
{
Identifier fullPath = Path.GetFullPath(path).CleanUpPathCrossPlatform(correctFilenameCase: false).ToIdentifier();
lock (list)
{
if (!textureRefCounts.TryAdd(fullPath, new TextureRefCounter { RefCount = 1, Texture = texture }))
{
textureRefCounts[fullPath].RefCount++;
}
}
}
} }
partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn) partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn)
@@ -16,6 +16,8 @@ namespace Barotrauma.Steam
{ {
public static partial class Workshop public static partial class Workshop
{ {
public const int MaxThumbnailSize = 1024 * 1024;
public static readonly ImmutableArray<Identifier> Tags = new [] public static readonly ImmutableArray<Identifier> Tags = new []
{ {
"submarine", "submarine",
@@ -177,7 +179,7 @@ namespace Barotrauma.Steam
DeletePublishStagingCopy(); DeletePublishStagingCopy();
Directory.CreateDirectory(PublishStagingDir); Directory.CreateDirectory(PublishStagingDir);
await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir); await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir, ShouldCorrectPaths.No);
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be //Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
ModProject modProject = new ModProject(contentPackage) ModProject modProject = new ModProject(contentPackage)
@@ -218,7 +220,7 @@ namespace Barotrauma.Steam
throw new Exception($"{newPath} already exists"); throw new Exception($"{newPath} already exists");
} }
await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, newPath); await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, newPath, ShouldCorrectPaths.Yes);
ModProject modProject = new ModProject(contentPackage); ModProject modProject = new ModProject(contentPackage);
modProject.DiscardHashAndInstallTime(); modProject.DiscardHashAndInstallTime();
@@ -197,6 +197,11 @@ namespace Barotrauma.Steam
FileSelection.OnFileSelected = (fn) => FileSelection.OnFileSelected = (fn) =>
{ {
if (new FileInfo(fn).Length > SteamManager.Workshop.MaxThumbnailSize)
{
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("WorkshopItemPreviewImageTooLarge"));
return;
}
thumbnailPath = fn; thumbnailPath = fn;
CreateLocalThumbnail(thumbnailPath, thumbnailContainer); CreateLocalThumbnail(thumbnailPath, thumbnailContainer);
}; };
@@ -119,6 +119,11 @@ namespace Barotrauma.Steam
{ {
CanBeFocused = false CanBeFocused = false
}; };
new GUICustomComponent(new RectTransform(Vector2.Zero, searchHolder.RectTransform), onUpdate:
(f, component) =>
{
searchTitle.RectTransform.NonScaledSize = searchBox.Frame.RectTransform.NonScaledSize;
});
searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = searchBox.Text.IsNullOrWhiteSpace(); }; searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = searchBox.Text.IsNullOrWhiteSpace(); };
@@ -8,7 +8,6 @@ namespace Barotrauma
private readonly int maxWidth; private readonly int maxWidth;
private ScalableFont? cachedFont = null; private ScalableFont? cachedFont = null;
private uint cachedFontSize = 0;
public LimitLString(LocalizedString text, GUIFont font, int maxWidth) public LimitLString(LocalizedString text, GUIFont font, int maxWidth)
{ {
@@ -27,7 +26,6 @@ namespace Barotrauma
{ {
cachedValue = ToolBox.LimitString(nestedStr.Value, font.Value, maxWidth); cachedValue = ToolBox.LimitString(nestedStr.Value, font.Value, maxWidth);
cachedFont = font.Value; cachedFont = font.Value;
cachedFontSize = font.Size;
UpdateLanguage(); UpdateLanguage();
} }
} }
@@ -404,7 +404,7 @@ namespace Barotrauma
public static string LimitString(string str, ScalableFont font, int maxWidth) public static string LimitString(string str, ScalableFont font, int maxWidth)
{ {
if (maxWidth <= 0 || string.IsNullOrWhiteSpace(str)) return ""; if (maxWidth <= 0 || string.IsNullOrWhiteSpace(str)) { return ""; }
float currWidth = font.MeasureString("...").X; float currWidth = font.MeasureString("...").X;
for (int i = 0; i < str.Length; i++) for (int i = 0; i < str.Length; i++)
@@ -6,7 +6,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.17.15.0</Version> <Version>0.18.0.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>
+1 -1
View File
@@ -6,7 +6,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.17.15.0</Version> <Version>0.18.0.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>
@@ -6,7 +6,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.17.15.0</Version> <Version>0.18.0.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>
@@ -6,7 +6,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.17.15.0</Version> <Version>0.18.0.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>
+1 -1
View File
@@ -6,7 +6,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.17.15.0</Version> <Version>0.18.0.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>
@@ -37,7 +37,7 @@ namespace Barotrauma
partial void OnPermanentStatChanged(StatTypes statType) partial void OnPermanentStatChanged(StatTypes statType)
{ {
if (Character == null || Character.Removed) { return; } if (Character == null || Character.Removed) { return; }
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdatePermanentStatsEventData()); GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdatePermanentStatsEventData(statType));
} }
public void ServerWrite(IWriteMessage msg) public void ServerWrite(IWriteMessage msg)
@@ -25,9 +25,9 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Saves bots in multiplayer /// Saves bots in multiplayer
/// </summary> /// </summary>
public void SaveMultiplayer(XElement root) public XElement SaveMultiplayer(XElement parentElement)
{ {
XElement saveElement = new XElement("bots", new XAttribute("hasbots", HasBots)); var element = new XElement("bots", new XAttribute("hasbots", HasBots));
foreach (CharacterInfo info in characterInfos) foreach (CharacterInfo info in characterInfos)
{ {
if (Level.Loaded != null) if (Level.Loaded != null)
@@ -35,13 +35,13 @@ namespace Barotrauma
if (!info.IsNewHire && (info.Character == null || info.Character.IsDead)) { continue; } if (!info.IsNewHire && (info.Character == null || info.Character.IsDead)) { continue; }
} }
XElement characterElement = info.Save(saveElement); XElement characterElement = info.Save(element);
if (info.InventoryData != null) { characterElement.Add(info.InventoryData); } if (info.InventoryData != null) { characterElement.Add(info.InventoryData); }
if (info.HealthData != null) { characterElement.Add(info.HealthData); } if (info.HealthData != null) { characterElement.Add(info.HealthData); }
if (info.OrderData != null) { characterElement.Add(info.OrderData); } if (info.OrderData != null) { characterElement.Add(info.OrderData); }
} }
SaveActiveOrders(saveElement); parentElement?.Add(element);
root.Add(saveElement); return element;
} }
public void ServerWriteActiveOrders(IWriteMessage msg) public void ServerWriteActiveOrders(IWriteMessage msg)
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -10,6 +11,31 @@ namespace Barotrauma
protected set; protected set;
} }
private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection;
/// <summary>
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
/// </summary>
public bool AllowedToManageCampaign(Client client, ClientPermissions permissions)
{
//allow managing the campaign if the client has permissions, is the owner, or the only client in the server,
//or if no-one has management permissions
return
client.HasPermission(permissions) ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Server.ConnectedClients.Count == 1 ||
IsOwner(client) ||
GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions)));
}
public bool AllowedToManageWallets(Client client)
{
return
client.HasPermission(ClientPermissions.ManageCampaign) ||
client.HasPermission(ClientPermissions.ManageMoney) ||
IsOwner(client);
}
public override void ShowStartMessage() public override void ShowStartMessage()
{ {
foreach (Mission mission in Missions) foreach (Mission mission in Missions)
@@ -163,29 +163,6 @@ namespace Barotrauma
private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection; private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection;
/// <summary>
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
/// </summary>
public bool AllowedToManageCampaign(Client client, ClientPermissions permissions)
{
//allow managing the campaign if the client has permissions, is the owner, or the only client in the server,
//or if no-one has management permissions
return
client.HasPermission(permissions) ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Server.ConnectedClients.Count == 1 ||
IsOwner(client) ||
GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions)));
}
public bool AllowedToManageWallets(Client client)
{
return
client.HasPermission(ClientPermissions.ManageCampaign) ||
client.HasPermission(ClientPermissions.ManageMoney) ||
IsOwner(client);
}
public void SaveExperiencePoints(Client client) public void SaveExperiencePoints(Client client)
{ {
ClearSavedExperiencePoints(client); ClearSavedExperiencePoints(client);
@@ -200,14 +177,6 @@ namespace Barotrauma
savedExperiencePoints.RemoveAll(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint)); savedExperiencePoints.RemoveAll(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint));
} }
public void LoadPets()
{
if (petsElement != null)
{
PetBehavior.LoadPets(petsElement);
}
}
public void SavePlayers() public void SavePlayers()
{ {
//refresh the character data of clients who are still in the server //refresh the character data of clients who are still in the server
@@ -261,8 +230,7 @@ namespace Barotrauma
characterData.ForEach(cd => cd.HasSpawned = false); characterData.ForEach(cd => cd.HasSpawned = false);
petsElement = new XElement("pets"); SavePets();
PetBehavior.SavePets(petsElement);
//remove all items that are in someone's inventory //remove all items that are in someone's inventory
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
@@ -285,6 +253,8 @@ namespace Barotrauma
c.Inventory.DeleteAllItems(); c.Inventory.DeleteAllItems();
} }
SaveActiveOrders();
} }
public void MoveDiscardedCharacterBalancesToBank() public void MoveDiscardedCharacterBalancesToBank()
@@ -348,44 +318,10 @@ namespace Barotrauma
if (success) if (success)
{ {
SavePlayers(); SavePlayers();
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
LeaveUnconnectedSubs(leavingSub);
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
{
Submarine.MainSub = leavingSub;
GameMain.GameSession.Submarine = leavingSub;
GameMain.GameSession.SubmarineInfo = leavingSub.Info;
leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub");
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info);
foreach (Submarine sub in subsToLeaveBehind)
{
GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name);
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
LinkedSubmarine.CreateDummy(leavingSub, sub);
}
}
NextLevel = newLevel; NextLevel = newLevel;
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
if (PendingSubmarineSwitch != null)
{
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
{
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
{
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
break;
}
}
}
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
PendingSubmarineSwitch = null;
} }
else else
{ {
@@ -977,6 +913,8 @@ namespace Barotrauma
{ {
NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg); NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg);
if (GameMain.Server is null) { return; }
switch (transfer.Sender) switch (transfer.Sender)
{ {
case Some<ushort> { Value: var id }: case Some<ushort> { Value: var id }:
@@ -992,7 +930,8 @@ namespace Barotrauma
{ {
if (transfer.Receiver is Some<ushort> { Value: var receiverId } && receiverId == sender.CharacterID) if (transfer.Receiver is Some<ushort> { Value: var receiverId } && receiverId == sender.CharacterID)
{ {
GameMain.Server?.Voting.StartTransferVote(sender, null, transfer.Amount, sender); if (transfer.Amount > GameMain.Server.ServerSettings.MaximumTransferRequest) { return; }
GameMain.Server.Voting.StartTransferVote(sender, null, transfer.Amount, sender);
GameServer.Log($"{sender.Name} started a vote to transfer {transfer.Amount} mk from the bank.", ServerLog.MessageType.Money); GameServer.Log($"{sender.Name} started a vote to transfer {transfer.Amount} mk from the bank.", ServerLog.MessageType.Money);
} }
return; return;
@@ -1301,7 +1240,11 @@ namespace Barotrauma
} }
// save bots // save bots
CrewManager.SaveMultiplayer(modeElement); var crewManagerElement = CrewManager.SaveMultiplayer(modeElement);
if (ActiveOrdersElement != null)
{
crewManagerElement.Add(ActiveOrdersElement);
}
XElement savedExperiencePointsElement = new XElement("SavedExperiencePoints"); XElement savedExperiencePointsElement = new XElement("SavedExperiencePoints");
foreach (var savedExperiencePoint in savedExperiencePoints) foreach (var savedExperiencePoint in savedExperiencePoints)
@@ -1,19 +1,27 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using System;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
{ {
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{ {
msg.Write(docked); msg.Write(docked);
if (docked) if (docked)
{ {
msg.Write(DockingTarget.item.ID); msg.Write(DockingTarget.item.ID);
msg.Write(IsLocked); msg.Write(IsLocked);
} }
} }
public void ServerEventRead(IReadMessage msg, Client c)
{
var allowOutpostAutoDocking = (AllowOutpostAutoDocking)msg.ReadByte();
if (outpostAutoDockingPromptShown &&
(GameMain.GameSession?.Campaign?.AllowedToManageCampaign(c, ClientPermissions.ManageMap) ?? false))
{
this.allowOutpostAutoDocking = allowOutpostAutoDocking;
}
}
} }
} }
@@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components
set; set;
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
//do nothing //do nothing
} }
@@ -15,7 +15,8 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < Connections.Count; i++) for (int i = 0; i < Connections.Count; i++)
{ {
wires[i] = new List<Wire>(); wires[i] = new List<Wire>();
for (int j = 0; j < Connections[i].MaxWires; j++) uint wireCount = msg.ReadVariableUInt32();
for (int j = 0; j < wireCount; j++)
{ {
ushort wireId = msg.ReadUInt16(); ushort wireId = msg.ReadUInt16();
@@ -91,12 +92,8 @@ namespace Barotrauma.Items.Components
//go through existing wire links //go through existing wire links
for (int i = 0; i < Connections.Count; i++) for (int i = 0; i < Connections.Count; i++)
{ {
int j = -1; foreach (Wire existingWire in Connections[i].Wires.ToArray())
foreach (Wire existingWire in Connections[i].Wires)
{ {
j++;
if (existingWire == null) { continue; }
//existing wire not in the list of new wires -> disconnect it //existing wire not in the list of new wires -> disconnect it
if (!wires[i].Contains(existingWire)) if (!wires[i].Contains(existingWire))
{ {
@@ -163,7 +160,7 @@ namespace Barotrauma.Items.Components
}*/ }*/
} }
Connections[i].SetWire(j, null); Connections[i].DisconnectWire(existingWire);
} }
} }
} }
@@ -42,7 +42,7 @@ namespace Barotrauma.MapCreatures.Behavior
foreach (BallastFloraBranch branch in Branches) foreach (BallastFloraBranch branch in Branches)
{ {
//don't notify about minuscule amounts of damage (<= 1.0f) //don't notify about minuscule amounts of damage (<= 1.0f)
if (branch.AccumulatedDamage > 1.0f) if (Math.Abs(branch.AccumulatedDamage) > 1.0f)
{ {
CreateNetworkMessage(new BranchDamageEventData(branch)); CreateNetworkMessage(new BranchDamageEventData(branch));
branch.AccumulatedDamage = 0.0f; branch.AccumulatedDamage = 0.0f;
@@ -24,7 +24,7 @@ namespace Barotrauma.Networking
public UInt16 LastRecvCampaignUpdate = 0; public UInt16 LastRecvCampaignUpdate = 0;
public UInt16 LastRecvCampaignSave = 0; public UInt16 LastRecvCampaignSave = 0;
public Pair<UInt16, float> LastCampaignSaveSendTime; public (UInt16 saveId, float time) LastCampaignSaveSendTime;
public readonly List<ChatMessage> ChatMsgQueue = new List<ChatMessage>(); public readonly List<ChatMessage> ChatMsgQueue = new List<ChatMessage>();
public UInt16 LastChatMsgQueueID; public UInt16 LastChatMsgQueueID;
@@ -391,7 +391,7 @@ namespace Barotrauma.Networking
StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath); StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
{ {
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now); client.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
} }
} }
break; break;
@@ -955,7 +955,9 @@ namespace Barotrauma.Networking
} }
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); errorLines.Add("Level: " + Level.Loaded.Seed + ", "
+ string.Join("; ", Level.Loaded.EqualityCheckValues.Select(cv
=> cv.Key + "=" + cv.Value.ToString("X"))));
errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate);
errorLines.Add("Entities:"); errorLines.Add("Entities:");
foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex)) foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex))
@@ -1548,11 +1550,11 @@ namespace Barotrauma.Networking
NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave)) NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave))
{ {
//already sent an up-to-date campaign save //already sent an up-to-date campaign save
if (c.LastCampaignSaveSendTime != null && campaign.LastSaveID == c.LastCampaignSaveSendTime.First) if (c.LastCampaignSaveSendTime != default && campaign.LastSaveID == c.LastCampaignSaveSendTime.saveId)
{ {
//the save was sent less than 5 second ago, don't attempt to resend yet //the save was sent less than 5 second ago, don't attempt to resend yet
//(the client may have received it but hasn't acked us yet) //(the client may have received it but hasn't acked us yet)
if (c.LastCampaignSaveSendTime.Second > NetTime.Now - 5.0f) if (c.LastCampaignSaveSendTime.time > NetTime.Now - 5.0f)
{ {
return; return;
} }
@@ -1561,7 +1563,7 @@ namespace Barotrauma.Networking
if (!FileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave)) if (!FileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave))
{ {
FileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath); FileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
c.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)NetTime.Now); c.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)NetTime.Now);
} }
} }
} }
@@ -2193,7 +2195,7 @@ namespace Barotrauma.Networking
Level.Loaded?.SpawnNPCs(); Level.Loaded?.SpawnNPCs();
Level.Loaded?.SpawnCorpses(); Level.Loaded?.SpawnCorpses();
Level.Loaded?.PrepareBeaconStation(); Level.Loaded?.PrepareBeaconStation();
AutoItemPlacer.PlaceIfNeeded(); AutoItemPlacer.SpawnItems();
CrewManager crewManager = campaign?.CrewManager; CrewManager crewManager = campaign?.CrewManager;
@@ -2388,7 +2390,9 @@ namespace Barotrauma.Networking
} }
campaign?.LoadPets(); campaign?.LoadPets();
crewManager?.LoadActiveOrders(); campaign?.LoadActiveOrders();
campaign?.CargoManager.InitPurchasedIDCards();
foreach (Submarine sub in Submarine.MainSubs) foreach (Submarine sub in Submarine.MainSubs)
{ {
@@ -2400,7 +2404,7 @@ namespace Barotrauma.Networking
spawnList.Add(new PurchasedItem(kvp.Key, kvp.Value, buyer: null)); spawnList.Add(new PurchasedItem(kvp.Key, kvp.Value, buyer: null));
} }
CargoManager.CreateItems(spawnList, sub); CargoManager.CreateItems(spawnList, sub, cargoManager: null);
} }
TraitorManager = null; TraitorManager = null;
@@ -2531,10 +2535,9 @@ namespace Barotrauma.Networking
{ {
msg.Write(mission.Prefab.Identifier); msg.Write(mission.Prefab.Identifier);
} }
msg.Write((byte)GameMain.GameSession.Level.EqualityCheckValues.Count); foreach (Level.LevelGenStage stage in Enum.GetValues(typeof(Level.LevelGenStage)).OfType<Level.LevelGenStage>().OrderBy(s => s))
foreach (int equalityCheckValue in GameMain.GameSession.Level.EqualityCheckValues)
{ {
msg.Write(equalityCheckValue); msg.Write(GameMain.GameSession.Level.EqualityCheckValues[stage]);
} }
foreach (Mission mission in GameMain.GameSession.Missions) foreach (Mission mission in GameMain.GameSession.Missions)
{ {
@@ -3178,9 +3181,9 @@ namespace Barotrauma.Networking
Client recipient = connectedClients.Find(c => c.Connection == transfer.Connection); Client recipient = connectedClients.Find(c => c.Connection == transfer.Connection);
if (transfer.FileType == FileTransferType.CampaignSave && if (transfer.FileType == FileTransferType.CampaignSave &&
(transfer.Status == FileTransferStatus.Sending || transfer.Status == FileTransferStatus.Finished) && (transfer.Status == FileTransferStatus.Sending || transfer.Status == FileTransferStatus.Finished) &&
recipient.LastCampaignSaveSendTime != null) recipient.LastCampaignSaveSendTime != default)
{ {
recipient.LastCampaignSaveSendTime.Second = (float)Lidgren.Network.NetTime.Now; recipient.LastCampaignSaveSendTime.time = (float)NetTime.Now;
} }
} }
@@ -278,12 +278,12 @@ namespace Barotrauma
static bool isValid(Item item) static bool isValid(Item item)
{ {
return item.Prefab.Identifier == "idcard" || item.GetComponent<RangedWeapon>() != null || item.GetComponent<MeleeWeapon>() != null; return item.GetComponent<IdCard>() != null || item.GetComponent<RangedWeapon>() != null || item.GetComponent<MeleeWeapon>() != null;
} }
if (foundItem == null) { return; } if (foundItem == null) { return; }
bool isIdCard = ((MapEntity)foundItem).Prefab.Identifier == "idcard"; bool isIdCard = foundItem.GetComponent<IdCard>() != null;
bool isWeapon = foundItem.GetComponent<RangedWeapon>() != null || foundItem.GetComponent<MeleeWeapon>() != null; bool isWeapon = foundItem.GetComponent<RangedWeapon>() != null || foundItem.GetComponent<MeleeWeapon>() != null;
if (isIdCard) if (isIdCard)
@@ -441,32 +441,43 @@ namespace Barotrauma.Networking
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", GameServer.ClientLogName(clients[i]), clients[i].Connection?.EndPointString, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning); GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", GameServer.ClientLogName(clients[i]), clients[i].Connection?.EndPointString, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
} }
if (divingSuitPrefab != null && oxyPrefab != null && RespawnShuttle != null) if (RespawnShuttle != null)
{ {
Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position; Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position;
if (divingSuitPrefab != null && oxyPrefab != null) if (divingSuitPrefab != null)
{ {
var divingSuit = new Item(divingSuitPrefab, pos, respawnSub); var divingSuit = new Item(divingSuitPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(divingSuit)); Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(divingSuit));
respawnItems.Add(divingSuit); respawnItems.Add(divingSuit);
var oxyTank = new Item(oxyPrefab, pos, respawnSub); if (oxyPrefab != null && divingSuit.GetComponent<ItemContainer>() != null)
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank)); {
divingSuit.Combine(oxyTank, user: null); var oxyTank = new Item(oxyPrefab, pos, respawnSub);
respawnItems.Add(oxyTank); Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank));
divingSuit.Combine(oxyTank, user: null);
respawnItems.Add(oxyTank);
}
} }
if (scooterPrefab != null && batteryPrefab != null) if (!(GameMain.GameSession.GameMode is CampaignMode))
{ {
var scooter = new Item(scooterPrefab, pos, respawnSub); if (scooterPrefab != null)
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(scooter)); {
var scooter = new Item(scooterPrefab, pos, respawnSub);
var battery = new Item(batteryPrefab, pos, respawnSub); Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(scooter));
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(battery)); respawnItems.Add(scooter);
if (batteryPrefab != null)
scooter.Combine(battery, user: null); {
respawnItems.Add(scooter); var battery = new Item(batteryPrefab, pos, respawnSub);
respawnItems.Add(battery); Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(battery));
scooter.Combine(battery, user: null);
respawnItems.Add(battery);
}
}
}
if (respawnContainer != null)
{
AutoItemPlacer.RegenerateLoot(RespawnShuttle, respawnContainer);
} }
} }
@@ -504,7 +515,7 @@ namespace Barotrauma.Networking
//add the ID card tags they should've gotten when spawning in the shuttle //add the ID card tags they should've gotten when spawning in the shuttle
foreach (Item item in character.Inventory.AllItems.Distinct()) foreach (Item item in character.Inventory.AllItems.Distinct())
{ {
if (item.Prefab.Identifier != "idcard") { continue; } if (item.GetComponent<IdCard>() == null) { continue; }
foreach (string s in shuttleSpawnPoints[i].IdCardTags) foreach (string s in shuttleSpawnPoints[i].IdCardTags)
{ {
item.AddTag(s); item.AddTag(s);
@@ -44,12 +44,14 @@ namespace Barotrauma
{ {
GameMain.Server?.SwitchSubmarine(); GameMain.Server?.SwitchSubmarine();
} }
else
{
voting.RegisterRejectedVote(this);
}
voting.StopSubmarineVote(passed); voting.StopSubmarineVote(passed);
} }
} }
public static IVote ActiveVote;
public class TransferVote : IVote public class TransferVote : IVote
{ {
public Client VoteStarter { get; } public Client VoteStarter { get; }
@@ -83,12 +85,22 @@ namespace Barotrauma
toWallet.Give(TransferAmount); toWallet.Give(TransferAmount);
} }
} }
else
{
voting.RegisterRejectedVote(this);
}
voting.StopMoneyTransferVote(passed); voting.StopMoneyTransferVote(passed);
} }
} }
public static IVote ActiveVote;
private static readonly Queue<IVote> pendingVotes = new Queue<IVote>(); private static readonly Queue<IVote> pendingVotes = new Queue<IVote>();
private readonly TimeSpan rejectedVoteCooldown = new TimeSpan(0, 1, 0);
private readonly Dictionary<Client, (VoteType voteType, DateTime time)> rejectedVoteTimes = new Dictionary<Client, (VoteType voteType, DateTime time)>();
private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender) private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender)
{ {
if (ActiveVote == null) if (ActiveVote == null)
@@ -136,6 +148,10 @@ namespace Barotrauma
public void StartTransferVote(Client starter, Client from, int transferAmount, Client to) public void StartTransferVote(Client starter, Client from, int transferAmount, Client to)
{ {
if (ShouldRejectVote(starter, VoteType.TransferMoney))
{
return;
}
if (ActiveVote == null) if (ActiveVote == null)
{ {
starter.SetVote(VoteType.TransferMoney, 2); starter.SetVote(VoteType.TransferMoney, 2);
@@ -156,6 +172,31 @@ namespace Barotrauma
} }
} }
private bool ShouldRejectVote(Client sender, VoteType voteType)
{
if (rejectedVoteTimes.ContainsKey(sender))
{
TimeSpan remainingCooldown = (rejectedVoteTimes[sender].time + rejectedVoteCooldown) - DateTime.Now;
if (rejectedVoteTimes[sender].voteType == voteType &&
remainingCooldown.TotalSeconds > 0)
{
GameMain.Server.SendDirectChatMessage(
TextManager.FormatServerMessage("voterejectedpleasewait", ("[time]", ((int)remainingCooldown.TotalSeconds).ToString())),
sender, ChatMessageType.ServerMessageBox);
return true;
}
}
return false;
}
protected void RegisterRejectedVote(IVote vote)
{
if (vote.VoteStarter != null)
{
rejectedVoteTimes[vote.VoteStarter] = (vote.VoteType, DateTime.Now);
}
}
public void Update(float deltaTime) public void Update(float deltaTime)
{ {
if (ActiveVote == null) { return; } if (ActiveVote == null) { return; }
@@ -227,7 +268,6 @@ namespace Barotrauma
GameServer.Log(GameServer.ClientLogName(sender) + (ready ? " is ready to start the game." : " is not ready to start the game."), ServerLog.MessageType.ServerMessage); GameServer.Log(GameServer.ClientLogName(sender) + (ready ? " is ready to start the game." : " is not ready to start the game."), ServerLog.MessageType.ServerMessage);
} }
break; break;
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
@@ -240,18 +280,25 @@ namespace Barotrauma
int amount = inc.ReadInt32(); int amount = inc.ReadInt32();
int fromClientId = inc.ReadByte(); int fromClientId = inc.ReadByte();
int toClientId = inc.ReadByte(); int toClientId = inc.ReadByte();
pendingVotes.Enqueue(new TransferVote(sender, if (!ShouldRejectVote(sender, voteType))
GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId), {
amount, pendingVotes.Enqueue(new TransferVote(sender,
GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId))); GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId),
amount,
GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId)));
}
} }
else else
{ {
string subName = inc.ReadString(); string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName); SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo))) if (!ShouldRejectVote(sender, voteType))
{ {
StartSubmarineVote(subInfo, voteType, sender); if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign &&
(campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
{
StartSubmarineVote(subInfo, voteType, sender);
}
} }
} }
} }
@@ -6,7 +6,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.17.15.0</Version> <Version>0.18.0.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>
@@ -31,6 +31,17 @@ namespace Barotrauma
if (_previousAiTarget != null) if (_previousAiTarget != null)
{ {
_lastAiTarget = _previousAiTarget; _lastAiTarget = _previousAiTarget;
if (_selectedAiTarget != null)
{
if (_selectedAiTarget.Entity is Item i && _previousAiTarget.Entity is Character c)
{
if (i.IsOwnedBy(c)) { return; }
}
else if (_previousAiTarget.Entity is Item it && _selectedAiTarget.Entity is Character ch)
{
if (it.IsOwnedBy(ch)) { return; }
}
}
} }
OnTargetChanged(_previousAiTarget, _selectedAiTarget); OnTargetChanged(_previousAiTarget, _selectedAiTarget);
} }
@@ -1055,6 +1055,9 @@ namespace Barotrauma
private Vector2 attackWorldPos; private Vector2 attackWorldPos;
private Vector2 attackSimPos; private Vector2 attackSimPos;
private float reachTimer;
// How long the monster tries to reach out for the target when it's close to it before ignoring it.
private const float reachTimeOut = 10;
private void UpdateAttack(float deltaTime) private void UpdateAttack(float deltaTime)
{ {
@@ -1427,6 +1430,22 @@ namespace Barotrauma
// Check that we can reach the target // Check that we can reach the target
distance = toTarget.Length(); distance = toTarget.Length();
canAttack = distance < AttackLimb.attack.Range; canAttack = distance < AttackLimb.attack.Range;
if (canAttack)
{
reachTimer = 0;
}
else if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && distance < AttackLimb.attack.Range * 5)
{
reachTimer += deltaTime;
if (reachTimer > reachTimeOut)
{
reachTimer = 0;
IgnoreTarget(SelectedAiTarget);
State = AIState.Idle;
ResetAITarget();
return;
}
}
// Crouch if the target is down (only humanoids), so that we can reach it. // Crouch if the target is down (only humanoids), so that we can reach it.
if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2) if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2)
@@ -1958,9 +1977,8 @@ namespace Barotrauma
} }
if (!isFriendly && attackResult.Damage > 0.0f) if (!isFriendly && attackResult.Damage > 0.0f)
{ {
ignoredTargets.Remove(attacker.AiTarget);
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls; bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
if (AIParams.AttackWhenProvoked && canAttack) if (AIParams.AttackWhenProvoked && canAttack && !ignoredTargets.Contains(attacker.AiTarget))
{ {
if (attacker.IsHusk) if (attacker.IsHusk)
{ {
@@ -3476,6 +3494,7 @@ namespace Barotrauma
{ {
observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f); observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f);
} }
reachTimer = 0;
} }
protected override void OnStateChanged(AIState from, AIState to) protected override void OnStateChanged(AIState from, AIState to)
@@ -3496,6 +3515,7 @@ namespace Barotrauma
SetStateResetTimer(); SetStateResetTimer();
} }
blockCheckTimer = 0; blockCheckTimer = 0;
reachTimer = 0;
} }
private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f); private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f);
@@ -59,7 +59,11 @@ namespace Barotrauma
private readonly float enemyCheckInterval = 0.2f; private readonly float enemyCheckInterval = 0.2f;
private readonly float enemySpotDistanceOutside = 800; private readonly float enemySpotDistanceOutside = 800;
private readonly float enemySpotDistanceInside = 1000; private readonly float enemySpotDistanceInside = 1000;
private float enemycheckTimer; private float enemyCheckTimer;
private readonly float reportProblemsInterval = 1.0f;
private float reportProblemsTimer;
/// <summary> /// <summary>
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity. /// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity.
@@ -166,6 +170,7 @@ namespace Barotrauma
objectiveManager = new AIObjectiveManager(c); objectiveManager = new AIObjectiveManager(c);
reactTimer = GetReactionTime(); reactTimer = GetReactionTime();
SortTimer = Rand.Range(0f, sortObjectiveInterval); SortTimer = Rand.Range(0f, sortObjectiveInterval);
reportProblemsTimer = Rand.Range(0f, reportProblemsInterval);
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
@@ -309,10 +314,10 @@ namespace Barotrauma
{ {
// Spot enemies while staying outside or inside an enemy ship. // Spot enemies while staying outside or inside an enemy ship.
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior // does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
enemycheckTimer -= deltaTime; enemyCheckTimer -= deltaTime;
if (enemycheckTimer < 0) if (enemyCheckTimer < 0)
{ {
enemycheckTimer = enemyCheckInterval * Rand.Range(0.75f, 1.25f); enemyCheckTimer = enemyCheckInterval * Rand.Range(0.75f, 1.25f);
if (!objectiveManager.IsCurrentObjective<AIObjectiveCombat>()) if (!objectiveManager.IsCurrentObjective<AIObjectiveCombat>())
{ {
float closestDistance = 0; float closestDistance = 0;
@@ -407,19 +412,29 @@ namespace Barotrauma
{ {
if (Character.IsOnPlayerTeam) if (Character.IsOnPlayerTeam)
{ {
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h)); foreach (Hull h in VisibleHulls)
{
PropagateHullSafety(Character, h);
}
} }
else else
{ {
// Outpost npcs don't inform each other about threats, like crew members do. foreach (Hull h in VisibleHulls)
VisibleHulls.ForEach(h => RefreshHullSafety(h)); {
RefreshHullSafety(h);
}
} }
} }
if (Character.SpeechImpediment < 100.0f) if (Character.SpeechImpediment < 100.0f)
{ {
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck) reportProblemsTimer -= deltaTime;
if (reportProblemsTimer <= 0.0f)
{ {
ReportProblems(); if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
{
ReportProblems();
}
reportProblemsTimer = reportProblemsInterval;
} }
UpdateSpeaking(); UpdateSpeaking();
} }
@@ -785,9 +800,10 @@ namespace Barotrauma
if (item == null || item.Removed) { return; } if (item == null || item.Removed) { return; }
if (!itemsToRelocate.Contains(item)) { return; } if (!itemsToRelocate.Contains(item)) { return; }
var mainSub = Submarine.MainSub; var mainSub = Submarine.MainSub;
if (item.ParentInventory != null) Entity owner = item.GetRootInventoryOwner();
if (owner != null)
{ {
if (item.ParentInventory.Owner is Character c) if (owner is Character c)
{ {
if (c.TeamID == CharacterTeamType.Team1 || c.TeamID == CharacterTeamType.Team2) if (c.TeamID == CharacterTeamType.Team1 || c.TeamID == CharacterTeamType.Team2)
{ {
@@ -795,24 +811,37 @@ namespace Barotrauma
return; return;
} }
} }
else if (item.ParentInventory.Owner.Submarine == mainSub) else if (owner.Submarine == mainSub)
{ {
// Placed inside an inventory that's already in the main sub. // Placed inside an inventory that's already in the main sub.
return; return;
} }
} }
// Laying on ground inside the main sub. // Laying on the ground inside the main sub.
if (item.Submarine == mainSub) if (item.Submarine == mainSub)
{ {
return; return;
} }
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub); if (owner != null && owner != item)
if (wp != null)
{ {
item.Submarine = mainSub; item.Drop(null);
item.SetTransform(wp.SimPosition, 0.0f); }
item.Submarine = mainSub;
Item newContainer = mainSub.FindContainerFor(item, onlyPrimary: false);
if (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null))
{
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub) ?? WayPoint.GetRandom(SpawnType.Path, null, mainSub);
if (wp != null)
{
item.SetTransform(wp.SimPosition, 0.0f, findNewHull: false, setPrevTransform: false);
}
else
{
DebugConsole.AddWarning($"Failed to relocate item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!");
}
} }
itemsToRelocate.Remove(item); itemsToRelocate.Remove(item);
DebugConsole.Log($"Relocated item {item.Prefab.Identifier} ({item.ID}) back to the main sub.");
} }
} }
@@ -1149,7 +1178,7 @@ namespace Barotrauma
bool isAttackerFightingEnemy = false; bool isAttackerFightingEnemy = false;
float minorDamageThreshold = 1; float minorDamageThreshold = 1;
float majorDamageThreshold = 20; float majorDamageThreshold = 20;
if (attacker.TeamID == Character.TeamID) if (attacker.TeamID == Character.TeamID && !attacker.IsInstigator)
{ {
minorDamageThreshold = 10; minorDamageThreshold = 10;
majorDamageThreshold = 40; majorDamageThreshold = 40;
@@ -1356,6 +1385,10 @@ namespace Barotrauma
Character FindInstigator() Character FindInstigator()
{ {
if (Character.IsInstigator)
{
return Character;
}
if (attacker.IsInstigator) if (attacker.IsInstigator)
{ {
return attacker; return attacker;
@@ -1545,7 +1578,7 @@ namespace Barotrauma
(!requireEquipped || character.HasEquippedItem(i)) && (!requireEquipped || character.HasEquippedItem(i)) &&
(predicate == null || predicate(i)), recursive, matchingItems); (predicate == null || predicate(i)), recursive, matchingItems);
items = matchingItems; items = matchingItems;
return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage))); return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.OwnInventory == null || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage)));
} }
public static void StructureDamaged(Structure structure, float damageAmount, Character character) public static void StructureDamaged(Structure structure, float damageAmount, Character character)
@@ -1889,7 +1922,7 @@ namespace Barotrauma
float fireFactor = 1; float fireFactor = 1;
if (!ignoreFire) if (!ignoreFire)
{ {
float calculateFire(Hull h) => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X; static float calculateFire(Hull h) => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X;
// Even the smallest fire reduces the safety by 50% // Even the smallest fire reduces the safety by 50%
float fire = visibleHulls == null ? calculateFire(hull) : visibleHulls.Sum(h => calculateFire(h)); float fire = visibleHulls == null ? calculateFire(hull) : visibleHulls.Sum(h => calculateFire(h));
fireFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); fireFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
@@ -1897,10 +1930,22 @@ namespace Barotrauma
float enemyFactor = 1; float enemyFactor = 1;
if (!ignoreEnemies) if (!ignoreEnemies)
{ {
bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e) && !e.IsArrested; int enemyCount = 0;
int enemyCount = visibleHulls == null ? foreach (Character c in Character.CharacterList)
Character.CharacterList.Count(e => isValidTarget(e) && e.CurrentHull == hull) : {
Character.CharacterList.Count(e => isValidTarget(e) && visibleHulls.Contains(e.CurrentHull)); if (visibleHulls == null)
{
if (c.CurrentHull != hull) { continue; }
}
else
{
if (!visibleHulls.Contains(c.CurrentHull)) { continue; }
}
if (IsActive(c) && !IsFriendly(character, c) && !c.IsArrested)
{
enemyCount++;
}
}
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
} }
@@ -1911,6 +1956,7 @@ namespace Barotrauma
if (item.Prefab != null && item.Prefab.IsDangerous) if (item.Prefab != null && item.Prefab.IsDangerous)
{ {
dangerousItemsFactor = 0; dangerousItemsFactor = 0;
break;
} }
} }
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor; float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor;
@@ -113,7 +113,7 @@ namespace Barotrauma
else else
{ {
var connectionPanel = item.GetComponent<ConnectionPanel>(); var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Any(w => w != null))) if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Count > 0))
{ {
return false; return false;
} }
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using static Barotrauma.AIObjectiveFindSafety;
namespace Barotrauma namespace Barotrauma
{ {
@@ -775,7 +776,13 @@ namespace Barotrauma
} }
else else
{ {
retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls, allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC); HullSearchStatus hullSearchStatus = findSafety.FindBestHull(out Hull potentialSafeHull, HumanAIController.VisibleHulls, allowChangingSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
if (hullSearchStatus != HullSearchStatus.Finished)
{
findSafety.UpdateSimpleEscape(deltaTime);
return;
}
retreatTarget = potentialSafeHull;
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
} }
} }
@@ -785,21 +792,21 @@ namespace Barotrauma
{ {
UsePathingOutside = false UsePathingOutside = false
}, },
onAbandon: () => onAbandon: () =>
{
if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
{ {
if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull)) // If in the same room with an enemy -> don't try to escape because we'd want to fight it
{ SteeringManager.Reset();
// If in the same room with an enemy -> don't try to escape because we'd want to fight it RemoveSubObjective(ref retreatObjective);
SteeringManager.Reset(); }
RemoveSubObjective(ref retreatObjective); else
} {
else // else abandon and fall back to find safety mode
{ Abandon = true;
// else abandon and fall back to find safety mode }
Abandon = true; },
} onCompleted: () => RemoveSubObjective(ref retreatObjective));
},
onCompleted: () => RemoveSubObjective(ref retreatObjective));
} }
} }
@@ -1,4 +1,5 @@
using FarseerPhysics; using Barotrauma.Extensions;
using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -192,9 +193,17 @@ namespace Barotrauma
} }
else else
{ {
HullSearchStatus hullSearchStatus = FindBestHull(out Hull potentialSafeHull, allowChangingSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
if (hullSearchStatus != HullSearchStatus.Finished)
{
UpdateSimpleEscape(deltaTime);
return;
}
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f); searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
previousSafeHull = currentSafeHull; previousSafeHull = currentSafeHull;
currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC); currentSafeHull = potentialSafeHull;
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _); cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
if (currentSafeHull == null) if (currentSafeHull == null)
{ {
@@ -250,58 +259,122 @@ namespace Barotrauma
} }
} }
if (subObjectives.Any(so => so.CanBeCompleted)) { return; } if (subObjectives.Any(so => so.CanBeCompleted)) { return; }
if (currentHull != null) UpdateSimpleEscape(deltaTime);
}
}
public void UpdateSimpleEscape(float deltaTime)
{
Vector2 escapeVel = Vector2.Zero;
if (character.CurrentHull != null)
{
foreach (Hull hull in HumanAIController.VisibleHulls)
{ {
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) foreach (FireSource fireSource in hull.FireSources)
// -> attempt to manually steer away from hazards
Vector2 escapeVel = Vector2.Zero;
foreach (Hull hull in HumanAIController.VisibleHulls)
{ {
foreach (FireSource fireSource in hull.FireSources) Vector2 dir = character.Position - fireSource.Position;
{ float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
Vector2 dir = character.Position - fireSource.Position; escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
foreach (Character enemy in Character.CharacterList)
{
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; }
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
{
Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
if (escapeVel != Vector2.Zero)
{
float left = currentHull.Rect.X + 50;
float right = currentHull.Rect.Right - 50;
//only move if we haven't reached the edge of the room
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
{
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
{
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset();
}
return;
} }
} }
foreach (Character enemy in Character.CharacterList)
{
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; }
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
{
Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
}
if (escapeVel != Vector2.Zero)
{
float left = character.CurrentHull.Rect.X + 50;
float right = character.CurrentHull.Rect.Right - 50;
//only move if we haven't reached the edge of the room
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
{
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
{
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset();
}
}
else
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime); objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
} }
} }
public Hull FindBestHull(IEnumerable<Hull> ignoredHulls = null, bool allowChangingTheSubmarine = true) public enum HullSearchStatus
{ {
//sort the hulls based on distance and which sub they're in Running,
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily Finished
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive }
//path calculations, only to discard all of them when going through the hulls in the outpost)
float EstimateHullSuitability(Hull hull) private readonly List<Hull> hulls = new List<Hull>();
private int hullSearchIndex = -1;
float bestHullValue = 0;
bool bestHullIsAirlock = false;
Hull potentialBestHull;
/// <summary>
/// Tries to find the best (safe, nearby) hull the character can find a path to.
/// Checks one hull at a time, and returns HullSearchStatus.Finished when all potential hulls have been checked.
/// </summary>
public HullSearchStatus FindBestHull(out Hull bestHull, IEnumerable<Hull> ignoredHulls = null, bool allowChangingSubmarine = true)
{
if (hullSearchIndex == -1)
{
bestHullValue = 0;
potentialBestHull = null;
bestHullIsAirlock = false;
hulls.Clear();
var connectedSubs = character.Submarine?.GetConnectedSubs();
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine == null) { continue; }
// Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it.
if (hull.Submarine.Info.IsRuin) { continue; }
if (!allowChangingSubmarine && hull.Submarine != character.Submarine) { continue; }
if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; }
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; }
//sort the hulls based on distance and which sub they're in
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
//path calculations, only to discard all of them when going through the hulls in the outpost)
float hullSuitability = EstimateHullSuitability(character, hull);
if (!hulls.Any())
{
hulls.Add(hull);
}
else
{
for (int i = 0; i < hulls.Count; i++)
{
if (hullSuitability > EstimateHullSuitability(character, hulls[i]))
{
hulls.Insert(i, hull);
break;
}
}
}
}
if (hulls.None())
{
bestHull = null;
return HullSearchStatus.Finished;
}
hullSearchIndex = 0;
}
static float EstimateHullSuitability(Character character, Hull hull)
{ {
float dist = float dist =
Math.Abs(hull.WorldPosition.X - character.WorldPosition.X) + Math.Abs(hull.WorldPosition.X - character.WorldPosition.X) +
@@ -314,86 +387,91 @@ namespace Barotrauma
return suitability; return suitability;
} }
Hull bestHull = null; Hull potentialHull = hulls[hullSearchIndex];
float bestValue = 0;
bool bestIsAirlock = false; float hullSafety = 0;
foreach (Hull hull in Hull.HullList.OrderByDescending(h => EstimateHullSuitability(h))) bool hullIsAirlock = false;
bool isCharacterInside = character.CurrentHull != null && character.Submarine != null;
if (isCharacterInside)
{ {
if (hull.Submarine == null) { continue; } hullSafety = HumanAIController.GetHullSafety(potentialHull, potentialHull.GetConnectedHulls(true, 1), character);
// Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it. float yDist = Math.Abs(character.WorldPosition.Y - potentialHull.WorldPosition.Y);
if (hull.Submarine.Info.IsRuin) { continue; } yDist = yDist > 100 ? yDist * 3 : 0;
if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; } float dist = Math.Abs(character.WorldPosition.X - potentialHull.WorldPosition.X) + yDist;
if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; } float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } hullSafety *= distanceFactor;
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; } //skip the hull if the safety is already less than the best hull
float hullSafety = 0; //(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
bool hullIsAirlock = false; if (hullSafety > bestHullValue)
bool isCharacterInside = character.CurrentHull != null && character.Submarine != null; {
if (isCharacterInside)
{
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
hullSafety = HumanAIController.GetHullSafety(hull, hull.GetConnectedHulls(true, 1), character);
float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
hullSafety *= distanceFactor;
//skip the hull if the safety is already less than the best hull
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
if (hullSafety < bestValue) { continue; }
//avoid airlock modules if not allowed to change the sub //avoid airlock modules if not allowed to change the sub
if (!allowChangingTheSubmarine && hull.OutpostModuleTags.Any(t => t == "airlock")) if (allowChangingSubmarine || !potentialHull.OutpostModuleTags.Any(t => t == "airlock"))
{ {
continue; // Don't allow to go outside if not already outside.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, potentialHull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable)
{
hullSafety = 0;
HumanAIController.UnreachableHulls.Add(potentialHull);
}
else
{
// Each unsafe node reduces the hull safety value.
// Ignore the current hull, because otherwise we couldn't find a path out.
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(potentialHull, true))
{
hullSafety /= 10;
}
}
} }
// Don't allow to go outside if not already outside. else
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable)
{ {
HumanAIController.UnreachableHulls.Add(hull); hullSafety = 0;
continue;
} }
// Each unsafe node reduces the hull safety value.
// Ignore the current hull, because otherwise we couldn't find a path out.
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
{
hullSafety /= 10;
}
}
else
{
// TODO: could also target gaps that get us inside?
if (hull.IsTaggedAirlock())
{
hullSafety = 100;
hullIsAirlock = true;
}
else if(!bestIsAirlock && hull.LeadsOutside(character))
{
hullSafety = 100;
}
// Huge preference for closer targets
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
// Intentionally exclude wrecks from this check
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != CharacterTeamType.FriendlyNPC)
{
hullSafety /= 10;
}
}
if (hullSafety > bestValue || (!isCharacterInside && hullIsAirlock && !bestIsAirlock))
{
bestHull = hull;
bestValue = hullSafety;
bestIsAirlock = hullIsAirlock;
} }
} }
return bestHull; else
{
// TODO: could also target gaps that get us inside?
if (potentialHull.IsTaggedAirlock())
{
hullSafety = 100;
hullIsAirlock = true;
}
else if(!bestHullIsAirlock && potentialHull.LeadsOutside(character))
{
hullSafety = 100;
}
// Huge preference for closer targets
float distance = Vector2.DistanceSquared(character.WorldPosition, potentialHull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
// Intentionally exclude wrecks from this check
if (potentialHull.Submarine.TeamID != character.TeamID && potentialHull.Submarine.TeamID != CharacterTeamType.FriendlyNPC)
{
hullSafety /= 10;
}
}
if (hullSafety > bestHullValue || (!isCharacterInside && hullIsAirlock && !bestHullIsAirlock))
{
potentialBestHull = potentialHull;
bestHullValue = hullSafety;
bestHullIsAirlock = hullIsAirlock;
}
bestHull = potentialBestHull;
hullSearchIndex++;
if (hullSearchIndex >= hulls.Count)
{
hullSearchIndex = -1;
return HullSearchStatus.Finished;
}
return HullSearchStatus.Running;
} }
public override void Reset() public override void Reset()
@@ -165,6 +165,7 @@ namespace Barotrauma
requiredCondition = () => requiredCondition = () =>
Leak.Submarine == character.Submarine && Leak.Submarine == character.Submarine &&
Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h), Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h),
endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull),
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue) // The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
SpeakCannotReachCondition = () => !CheckObjectiveSpecific() SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
}, },
@@ -471,7 +471,8 @@ namespace Barotrauma
{ {
if (spawnItemIfNotFound) if (spawnItemIfNotFound)
{ {
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && IdentifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab)) ItemPrefab prefab = FindItemToSpawn();
if (prefab == null)
{ {
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow); DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
@@ -501,6 +502,33 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Returns the "best" item to spawn when using <see cref="spawnItemIfNotFound"/> and there's multiple suitable items.
/// Best in this context is the one that's sold at the lowest price in stores (usually the most "basic" item)
/// </summary>
/// <returns></returns>
private ItemPrefab FindItemToSpawn()
{
ItemPrefab bestItem = null;
float lowestCost = float.MaxValue;
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
{
if (!(prefab is ItemPrefab itemPrefab)) { continue; }
if (IdentifiersOrTags.Any(id => id == prefab.Identifier || prefab.Tags.Contains(id)))
{
float cost = itemPrefab.DefaultPrice != null && itemPrefab.CanBeBought ?
itemPrefab.DefaultPrice.Price :
float.MaxValue;
if (cost < lowestCost || bestItem == null)
{
bestItem = itemPrefab;
lowestCost = cost;
}
}
}
return bestItem;
}
protected override bool CheckObjectiveSpecific() protected override bool CheckObjectiveSpecific()
{ {
if (IsCompleted) { return true; } if (IsCompleted) { return true; }
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using static Barotrauma.AIObjectiveFindSafety;
namespace Barotrauma namespace Barotrauma
{ {
@@ -186,7 +187,9 @@ namespace Barotrauma
} }
else else
{ {
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls); HullSearchStatus hullSearchStatus = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(out Hull potentialSafeHull, HumanAIController.VisibleHulls);
if (hullSearchStatus != HullSearchStatus.Finished) { return; }
safeHull = potentialSafeHull;
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
} }
} }
@@ -24,7 +24,7 @@ namespace Barotrauma
public bool IsAiming => wasAiming; public bool IsAiming => wasAiming;
public bool IsAimingMelee => wasAimingMelee; public bool IsAimingMelee => wasAimingMelee;
protected bool Aiming => aiming || aimingMelee; protected bool Aiming => aiming || aimingMelee || LockFlippingUntil > Timing.TotalTime && character.IsKeyDown(InputType.Aim);
public float ArmLength => upperArmLength + forearmLength; public float ArmLength => upperArmLength + forearmLength;
@@ -275,6 +275,8 @@ namespace Barotrauma
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative. // We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
public bool IsAboveFloor => GetHeightFromFloor() > -0.1f; public bool IsAboveFloor => GetHeightFromFloor() > -0.1f;
public float LockFlippingUntil;
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
{ {
useItemTimer = 0.5f; useItemTimer = 0.5f;
@@ -380,18 +382,10 @@ namespace Barotrauma
{ {
//if holding two items that should control the characters' pose, let the item in the right hand do it //if holding two items that should control the characters' pose, let the item in the right hand do it
bool anotherItemControlsPose = equippedInLefthand && rightHandItem != item && (rightHandItem?.GetComponent<Holdable>()?.ControlPose ?? false); bool anotherItemControlsPose = equippedInLefthand && rightHandItem != item && (rightHandItem?.GetComponent<Holdable>()?.ControlPose ?? false);
if (!anotherItemControlsPose) if (!anotherItemControlsPose && TargetMovement == Vector2.Zero && inWater)
{ {
var head = GetLimb(LimbType.Head); torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
if (head != null) torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
{
head.body.SmoothRotate(itemAngle, force: 30 * head.Mass);
}
if (TargetMovement == Vector2.Zero && inWater)
{
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
}
} }
aiming = true; aiming = true;
} }
@@ -164,8 +164,6 @@ namespace Barotrauma
public float LegBendTorque => CurrentGroundedParams.LegBendTorque * RagdollParams.JointScale; public float LegBendTorque => CurrentGroundedParams.LegBendTorque * RagdollParams.JointScale;
public Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale; public Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale;
public float LockFlippingUntil;
public override Vector2 AimSourceSimPos public override Vector2 AimSourceSimPos
{ {
get get
@@ -841,7 +839,7 @@ namespace Barotrauma
rotation += 360; rotation += 360;
} }
float targetSpeed = TargetMovement.Length(); float targetSpeed = TargetMovement.Length();
if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !character.IsKeyDown(InputType.Aim)) if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming)
{ {
if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false)) if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
{ {
@@ -153,12 +153,12 @@ namespace Barotrauma
protected ActiveTeamChange currentTeamChange; protected ActiveTeamChange currentTeamChange;
const string OriginalTeamIdentifier = "original"; const string OriginalTeamIdentifier = "original";
public static void ThrowIfAccessingWalletsInSingleplayer() private void ThrowIfAccessingWalletsInSingleplayer()
{ {
#if CLIENT && DEBUG #if CLIENT && DEBUG
if (Screen.Selected is TestScreen) { return; } if (Screen.Selected is TestScreen) { return; }
#endif #endif
if (GameMain.NetworkMember is null || GameMain.IsSingleplayer) if ((GameMain.NetworkMember is null || GameMain.IsSingleplayer) && IsPlayer)
{ {
throw new InvalidOperationException($"Tried to access crew wallets in singleplayer. Use {nameof(CampaignMode)}.{nameof(CampaignMode.Bank)} or {nameof(CampaignMode)}.{nameof(CampaignMode.GetWallet)} instead."); throw new InvalidOperationException($"Tried to access crew wallets in singleplayer. Use {nameof(CampaignMode)}.{nameof(CampaignMode.Bank)} or {nameof(CampaignMode)}.{nameof(CampaignMode.GetWallet)} instead.");
} }
@@ -560,18 +560,35 @@ namespace Barotrauma
#if CLIENT #if CLIENT
CharacterHealth.SetHealthBarVisibility(value == null); CharacterHealth.SetHealthBarVisibility(value == null);
#elif SERVER #endif
if (value is { IsDead: true, Wallet: { Balance: var balance } grabbedWallet } && balance > 0) bool isServerOrSingleplayer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true };
if (IsPlayer && isServerOrSingleplayer && value is { IsDead: true, Wallet: { Balance: var balance } grabbedWallet } && balance > 0)
{ {
if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign) #if SERVER
if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign && GameMain.Server is { ServerSettings: { } settings })
{ {
mpCampaign.Bank.Give(balance); switch (settings.LootedMoneyDestination)
{
case LootedMoneyDestination.Wallet when IsPlayer:
Wallet.Give(balance);
break;
default:
mpCampaign.Bank.Give(balance);
break;
}
} }
grabbedWallet.Deduct(balance);
GameServer.Log($"{GameServer.CharacterLogName(this)} grabbed {value.Name}'s body and received {grabbedWallet.Balance} mk.", ServerLog.MessageType.Money); GameServer.Log($"{GameServer.CharacterLogName(this)} grabbed {value.Name}'s body and received {grabbedWallet.Balance} mk.", ServerLog.MessageType.Money);
} #elif CLIENT
if (GameMain.GameSession.Campaign is SinglePlayerCampaign spCampaign)
{
spCampaign.Bank.Give(balance);
}
#endif #endif
grabbedWallet.Deduct(balance);
}
} }
} }
@@ -1443,7 +1460,7 @@ namespace Barotrauma
foreach (Item item in Inventory.AllItems) foreach (Item item in Inventory.AllItems)
{ {
if (item?.Prefab.Identifier != "idcard") { continue; } if (item?.GetComponent<IdCard>() == null) { continue; }
foreach (string s in spawnPoint.IdCardTags) foreach (string s in spawnPoint.IdCardTags)
{ {
item.AddTag(s); item.AddTag(s);
@@ -3954,7 +3971,10 @@ namespace Barotrauma
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered) if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
{ {
// OnDamaged is called only for the limb that is hit. // OnDamaged is called only for the limb that is hit.
AnimController.Limbs.ForEach(l => l.ApplyStatusEffects(actionType, deltaTime)); foreach (Limb limb in AnimController.Limbs)
{
limb.ApplyStatusEffects(actionType, deltaTime);
}
} }
//OnActive effects are handled by the afflictions themselves //OnActive effects are handled by the afflictions themselves
if (actionType != ActionType.OnActive) if (actionType != ActionType.OnActive)
@@ -252,12 +252,11 @@ namespace Barotrauma
} }
/// <summary> /// <summary>
/// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to specifically get them /// Returns unlocked talents that aren't part of the character's talent tree (which can be unlocked e.g. with an endocrine booster)
/// </summary> /// </summary>
public IEnumerable<Identifier> GetEndocrineTalents() public IEnumerable<Identifier> GetUnlockedTalentsOutsideTree()
{ {
if (!TalentTree.JobTalentTrees.TryGet(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty<Identifier>(); } if (!TalentTree.JobTalentTrees.TryGet(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty<Identifier>(); }
return UnlockedTalents.Where(t => !talentTree.TalentIsInTree(t)); return UnlockedTalents.Where(t => !talentTree.TalentIsInTree(t));
} }
@@ -1182,7 +1181,7 @@ namespace Barotrauma
// Replace the name tag of any existing id cards or duffel bags // Replace the name tag of any existing id cards or duffel bags
foreach (var item in Item.ItemList) foreach (var item in Item.ItemList)
{ {
if (item.Prefab.Identifier != "idcard" && !item.Tags.Contains("despawncontainer")) { continue; } if (!item.HasTag("identitycard") && !item.HasTag("despawncontainer")) { continue; }
foreach (var tag in item.Tags.Split(',')) foreach (var tag in item.Tags.Split(','))
{ {
var splitTag = tag.Split(":"); var splitTag = tag.Split(":");
@@ -300,6 +300,7 @@ namespace Barotrauma
} }
public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"]; public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"];
public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"];
public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"]; public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"];
public static AfflictionPrefab Bleeding => Prefabs["bleeding"]; public static AfflictionPrefab Bleeding => Prefabs["bleeding"];
public static AfflictionPrefab Burn => Prefabs["burn"]; public static AfflictionPrefab Burn => Prefabs["burn"];
@@ -124,7 +124,18 @@ namespace Barotrauma
public float PressureKillDelay { get; private set; } = 5.0f; public float PressureKillDelay { get; private set; } = 5.0f;
public float Vitality { get; private set; } private float vitality;
public float Vitality
{
get
{
return Character.IsDead ? minVitality : vitality;
}
private set
{
vitality = value;
}
}
public float HealthPercentage => MathUtils.Percentage(Vitality, MaxVitality); public float HealthPercentage => MathUtils.Percentage(Vitality, MaxVitality);
@@ -725,6 +736,8 @@ namespace Barotrauma
AddLimbAffliction(limbHealth: null, newAffliction, allowStacking); AddLimbAffliction(limbHealth: null, newAffliction, allowStacking);
} }
partial void UpdateSkinTint();
partial void UpdateLimbAfflictionOverlays(); partial void UpdateLimbAfflictionOverlays();
public void Update(float deltaTime) public void Update(float deltaTime)
@@ -788,7 +801,7 @@ namespace Barotrauma
if (!Character.GodMode) if (!Character.GodMode)
{ {
UpdateLimbAfflictionOverlays(); UpdateLimbAfflictionOverlays();
UpdateSkinTint(); UpdateSkinTint();
CalculateVitality(); CalculateVitality();
if (Vitality <= MinVitality) if (Vitality <= MinVitality)
@@ -798,23 +811,6 @@ namespace Barotrauma
} }
} }
private void UpdateSkinTint()
{
FaceTint = DefaultFaceTint;
BodyTint = Color.TransparentBlack;
if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; }
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{
var affliction = kvp.Key;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
}
}
private void UpdateDamageReductions(float deltaTime) private void UpdateDamageReductions(float deltaTime)
{ {
float healthRegen = Character.Params.Health.ConstantHealthRegeneration; float healthRegen = Character.Params.Health.ConstantHealthRegeneration;
@@ -905,6 +901,7 @@ namespace Barotrauma
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
var (type, affliction) = GetCauseOfDeath(); var (type, affliction) = GetCauseOfDeath();
UpdateLimbAfflictionOverlays();
UpdateSkinTint(); UpdateSkinTint();
Character.Kill(type, affliction); Character.Kill(type, affliction);
#if CLIENT #if CLIENT
@@ -105,9 +105,9 @@ namespace Barotrauma
return spawnPointTags; return spawnPointTags;
} }
public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced) public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func<JobPrefab, bool> predicate = null)
{ {
return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync); return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate);
} }
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null) public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
@@ -209,11 +209,8 @@ namespace Barotrauma
} }
} }
if (item.Prefab.Identifier == "idcard") IdCard idCardComponent = item.GetComponent<IdCard>();
{ idCardComponent?.Initialize(spawnPoint, character);
IdCard idCardComponent = item.GetComponent<IdCard>();
idCardComponent?.Initialize(spawnPoint, character);
}
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>()) foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
{ {
@@ -293,6 +293,6 @@ namespace Barotrauma
//ClothingElement = element.GetChildElement("PortraitClothing"); //ClothingElement = element.GetChildElement("PortraitClothing");
} }
public static JobPrefab Random(Rand.RandSync sync) => Prefabs.GetRandom(p => !p.HiddenJob, sync); public static JobPrefab Random(Rand.RandSync sync, Func<JobPrefab, bool> predicate = null) => Prefabs.GetRandom(p => !p.HiddenJob && (predicate == null || predicate(p)), sync);
} }
} }
@@ -331,7 +331,7 @@ namespace Barotrauma
#if CLIENT #if CLIENT
if (isSevered) if (isSevered)
{ {
damageOverlayStrength = 100.0f; damageOverlayStrength = 1.0f;
} }
#endif #endif
} }
@@ -597,18 +597,7 @@ namespace Barotrauma
dir = Direction.Right; dir = Direction.Right;
body = new PhysicsBody(limbParams); body = new PhysicsBody(limbParams);
type = limbParams.Type; type = limbParams.Type;
if (limbParams.IgnoreCollisions) IgnoreCollisions = limbParams.IgnoreCollisions;
{
body.CollisionCategories = Category.None;
body.CollidesWith = Category.None;
IgnoreCollisions = true;
}
else
{
//limbs don't collide with each other
body.CollisionCategories = Physics.CollisionCharacter;
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
}
body.UserData = this; body.UserData = this;
pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale)) pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale))
{ {
@@ -646,10 +635,9 @@ namespace Barotrauma
} }
attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange); attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange);
} }
if (!character.VariantOf.IsEmpty) if (character is { VariantOf: { IsEmpty: false } })
{ {
var attackElement = CharacterPrefab.Prefabs.TryGet(character.VariantOf, out var basePrefab) var attackElement = character.Params.VariantFile.Root.GetChildElement("attack");
? basePrefab.ConfigElement.GetChildElement("attack") : null;
if (attackElement != null) if (attackElement != null)
{ {
attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f); attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f);
@@ -9,7 +9,7 @@ namespace Barotrauma
protected override bool MatchesSingular(Identifier identifier) => identifier == "ballastflorabehavior"; protected override bool MatchesSingular(Identifier identifier) => identifier == "ballastflorabehavior";
protected override bool MatchesPlural(Identifier identifier) => identifier == "ballastflorabehaviors"; protected override bool MatchesPlural(Identifier identifier) => identifier == "ballastflorabehaviors";
protected override PrefabCollection<BallastFloraPrefab> prefabs => BallastFloraPrefab.Prefabs; protected override PrefabCollection<BallastFloraPrefab> Prefabs => BallastFloraPrefab.Prefabs;
protected override BallastFloraPrefab CreatePrefab(ContentXElement element) protected override BallastFloraPrefab CreatePrefab(ContentXElement element)
{ {
@@ -9,7 +9,7 @@ namespace Barotrauma
protected override bool MatchesSingular(Identifier identifier) => identifier == "cave"; protected override bool MatchesSingular(Identifier identifier) => identifier == "cave";
protected override bool MatchesPlural(Identifier identifier) => identifier == "cavegenerationparameters"; protected override bool MatchesPlural(Identifier identifier) => identifier == "cavegenerationparameters";
protected override PrefabCollection<CaveGenerationParams> prefabs => CaveGenerationParams.CaveParams; protected override PrefabCollection<CaveGenerationParams> Prefabs => CaveGenerationParams.CaveParams;
protected override CaveGenerationParams CreatePrefab(ContentXElement element) protected override CaveGenerationParams CreatePrefab(ContentXElement element)
{ {
return new CaveGenerationParams(element, this); return new CaveGenerationParams(element, this);
@@ -77,7 +77,7 @@ namespace Barotrauma
{ {
HashSet<string> texturePaths = new HashSet<string> HashSet<string> texturePaths = new HashSet<string>
{ {
ragdollParams.Texture ContentPath.FromRaw(CharacterPrefab.Prefabs[speciesName].ContentPackage, ragdollParams.Texture).Value
}; };
foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs) foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs)
{ {
@@ -9,7 +9,7 @@ namespace Barotrauma
protected override bool MatchesSingular(Identifier identifier) => identifier == "corpse"; protected override bool MatchesSingular(Identifier identifier) => identifier == "corpse";
protected override bool MatchesPlural(Identifier identifier) => identifier == "corpses"; protected override bool MatchesPlural(Identifier identifier) => identifier == "corpses";
protected override PrefabCollection<CorpsePrefab> prefabs => CorpsePrefab.Prefabs; protected override PrefabCollection<CorpsePrefab> Prefabs => CorpsePrefab.Prefabs;
protected override CorpsePrefab CreatePrefab(ContentXElement element) protected override CorpsePrefab CreatePrefab(ContentXElement element)
{ {
return new CorpsePrefab(element, this); return new CorpsePrefab(element, this);
@@ -8,7 +8,7 @@ namespace Barotrauma
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
protected override bool MatchesPlural(Identifier identifier) => identifier == "EventManagerSettings"; protected override bool MatchesPlural(Identifier identifier) => identifier == "EventManagerSettings";
protected override PrefabCollection<EventManagerSettings> prefabs => EventManagerSettings.Prefabs; protected override PrefabCollection<EventManagerSettings> Prefabs => EventManagerSettings.Prefabs;
protected override EventManagerSettings CreatePrefab(ContentXElement element) protected override EventManagerSettings CreatePrefab(ContentXElement element)
{ {
return new EventManagerSettings(element, this); return new EventManagerSettings(element, this);
@@ -9,7 +9,7 @@ namespace Barotrauma
protected override bool MatchesSingular(Identifier identifier) => identifier == "faction"; protected override bool MatchesSingular(Identifier identifier) => identifier == "faction";
protected override bool MatchesPlural(Identifier identifier) => identifier == "factions"; protected override bool MatchesPlural(Identifier identifier) => identifier == "factions";
protected override PrefabCollection<FactionPrefab> prefabs => FactionPrefab.Prefabs; protected override PrefabCollection<FactionPrefab> Prefabs => FactionPrefab.Prefabs;
protected override FactionPrefab CreatePrefab(ContentXElement element) protected override FactionPrefab CreatePrefab(ContentXElement element)
{ {
return new FactionPrefab(element, this); return new FactionPrefab(element, this);
@@ -9,7 +9,7 @@ namespace Barotrauma
protected abstract bool MatchesSingular(Identifier identifier); protected abstract bool MatchesSingular(Identifier identifier);
protected abstract bool MatchesPlural(Identifier identifier); protected abstract bool MatchesPlural(Identifier identifier);
protected abstract PrefabCollection<T> prefabs { get; } protected abstract PrefabCollection<T> Prefabs { get; }
protected abstract T CreatePrefab(ContentXElement element); protected abstract T CreatePrefab(ContentXElement element);
private void LoadFromXElement(ContentXElement parentElement, bool overriding) private void LoadFromXElement(ContentXElement parentElement, bool overriding)
@@ -29,14 +29,14 @@ namespace Barotrauma
} }
else if (elemName == "clear") else if (elemName == "clear")
{ {
prefabs.AddOverrideFile(this); Prefabs.AddOverrideFile(this);
} }
else if (MatchesSingular(elemName)) else if (MatchesSingular(elemName))
{ {
T prefab = CreatePrefab(parentElement); T prefab = CreatePrefab(parentElement);
try try
{ {
prefabs.Add(prefab, overriding); Prefabs.Add(prefab, overriding);
} }
catch catch
{ {
@@ -53,7 +53,7 @@ namespace Barotrauma
} }
else else
{ {
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); DebugConsole.ThrowError($"GenericPrefabFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
} }
} }
@@ -68,12 +68,12 @@ namespace Barotrauma
public override sealed void UnloadFile() public override sealed void UnloadFile()
{ {
prefabs.RemoveByFile(this); Prefabs.RemoveByFile(this);
} }
public sealed override void Sort() public sealed override void Sort()
{ {
prefabs.SortAll(); Prefabs.SortAll();
} }
} }
} }

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