Unstable 0.1500.4.0 (Shrek edition)

This commit is contained in:
Markus Isberg
2021-09-23 21:29:31 +09:00
parent 5a6bbcc79e
commit 3043a9a7bc
124 changed files with 3571 additions and 1848 deletions
@@ -23,12 +23,30 @@ namespace Barotrauma
private Sprite disguisedJobIcon; private Sprite disguisedJobIcon;
private Color disguisedJobColor; private Color disguisedJobColor;
private Sprite tintMask;
private float tintHighlightThreshold;
private float tintHighlightMultiplier;
public static void Init() public static void Init()
{ {
infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite(); infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0); new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
} }
partial void LoadHeadSpriteProjectSpecific(XElement limbElement)
{
XElement maskElement = limbElement.Element("tintmask");
if (maskElement != null)
{
string tintMaskPath = maskElement.GetAttributeString("texture", "");
if (!string.IsNullOrWhiteSpace(tintMaskPath))
{
tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this));
tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f);
tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f);
}
}
}
public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null) public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null)
{ {
@@ -458,6 +476,7 @@ namespace Barotrauma
} }
else else
{ {
//TODO: disguise skin and hair colors
sheetIndex = disguisedSheetIndex; sheetIndex = disguisedSheetIndex;
portraitToDraw = disguisedPortrait; portraitToDraw = disguisedPortrait;
attachmentsToDraw = disguisedAttachmentSprites; attachmentsToDraw = disguisedAttachmentSprites;
@@ -465,22 +484,74 @@ namespace Barotrauma
if (portraitToDraw != null) if (portraitToDraw != null)
{ {
var currEffect = spriteBatch.GetCurrentEffect();
// Scale down the head sprite 10% // Scale down the head sprite 10%
float scale = targetWidth * 0.9f / Portrait.size.X; float scale = targetWidth * 0.9f / Portrait.size.X;
if (sheetIndex.HasValue) if (sheetIndex.HasValue)
{ {
SetHeadEffect(spriteBatch);
portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size); portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size);
} }
portraitToDraw.Draw(spriteBatch, screenPos + offset, Color.White, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); portraitToDraw.Draw(spriteBatch, screenPos + offset, SkinColor, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
if (attachmentsToDraw != null) if (attachmentsToDraw != null)
{ {
float depthStep = 0.000001f; float depthStep = 0.000001f;
foreach (var attachment in attachmentsToDraw) foreach (var attachment in attachmentsToDraw)
{ {
DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); SetAttachmentEffect(spriteBatch, attachment);
DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
depthStep += depthStep; depthStep += depthStep;
} }
} }
spriteBatch.SwapEffect(currEffect);
}
}
//TODO: I hate this so much :(
private SpriteBatch.EffectWithParams headEffectParameters;
private Dictionary<WearableType, SpriteBatch.EffectWithParams> attachmentEffectParameters
= new Dictionary<WearableType, SpriteBatch.EffectWithParams>();
private void SetHeadEffect(SpriteBatch spriteBatch)
{
headEffectParameters.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
headEffectParameters.Params ??= new Dictionary<string, object>();
headEffectParameters.Params["xBaseTexture"] = headSprite.Texture;
headEffectParameters.Params["xTintMaskTexture"] = tintMask?.Texture ?? GUI.WhiteTexture;
headEffectParameters.Params["xCutoffTexture"] = GUI.WhiteTexture;
headEffectParameters.Params["baseToCutoffSizeRatio"] = 1.0f;
headEffectParameters.Params["highlightThreshold"] = tintHighlightThreshold;
headEffectParameters.Params["highlightMultiplier"] = tintHighlightMultiplier;
spriteBatch.SwapEffect(headEffectParameters);
}
private void SetAttachmentEffect(SpriteBatch spriteBatch, WearableSprite attachment)
{
if (!attachmentEffectParameters.ContainsKey(attachment.Type))
{
attachmentEffectParameters.Add(attachment.Type, new SpriteBatch.EffectWithParams(GameMain.GameScreen.ThresholdTintEffect, new Dictionary<string, object>()));
}
var parameters = attachmentEffectParameters[attachment.Type].Params;
parameters["xBaseTexture"] = attachment.Sprite.Texture;
parameters["xTintMaskTexture"] = GUI.WhiteTexture;
parameters["xCutoffTexture"] = GUI.WhiteTexture;
parameters["baseToCutoffSizeRatio"] = 1.0f;
parameters["highlightThreshold"] = tintHighlightThreshold;
parameters["highlightMultiplier"] = tintHighlightMultiplier;
spriteBatch.SwapEffect(attachmentEffectParameters[attachment.Type]);
}
private Color GetAttachmentColor(WearableSprite attachment)
{
switch (attachment.Type)
{
case WearableType.Hair:
return HairColor;
case WearableType.Beard:
case WearableType.Moustache:
return FacialHairColor;
default:
return Color.White;
} }
} }
@@ -489,34 +560,28 @@ namespace Barotrauma
var headSprite = HeadSprite; var headSprite = HeadSprite;
if (headSprite != null) if (headSprite != null)
{ {
var currEffect = spriteBatch.GetCurrentEffect();
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
if (Head.SheetIndex.HasValue) if (Head.SheetIndex.HasValue)
{ {
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size); headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size);
} }
headSprite.Draw(spriteBatch, screenPos, scale: scale); SetHeadEffect(spriteBatch);
headSprite.Draw(spriteBatch, screenPos, scale: scale, color: SkinColor);
if (AttachmentSprites != null) if (AttachmentSprites != null)
{ {
float depthStep = 0.000001f; float depthStep = 0.000001f;
foreach (var attachment in AttachmentSprites) foreach (var attachment in AttachmentSprites)
{ {
DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep); SetAttachmentEffect(spriteBatch, attachment);
DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment));
depthStep += depthStep; depthStep += depthStep;
} }
} }
spriteBatch.SwapEffect(currEffect);
} }
} }
public void DrawJobIcon(SpriteBatch spriteBatch, Vector2 pos, float scale = 1.0f, bool evaluateDisguise = false)
{
if (evaluateDisguise && IsDisguised) return;
var icon = !IsDisguisedAsAnother || !evaluateDisguise ? Job?.Prefab?.Icon : disguisedJobIcon;
if (icon == null) { return; }
Color iconColor = !IsDisguisedAsAnother || !evaluateDisguise ? Job.Prefab.UIColor : disguisedJobColor;
icon.Draw(spriteBatch, pos, iconColor, scale: scale);
}
public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise = false) public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise = false)
{ {
if (evaluateDisguise && IsDisguised) return; if (evaluateDisguise && IsDisguised) return;
@@ -527,7 +592,7 @@ namespace Barotrauma
icon.Draw(spriteBatch, area.Center.ToVector2(), iconColor, scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height)); icon.Draw(spriteBatch, area.Center.ToVector2(), iconColor, scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height));
} }
private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, SpriteEffects spriteEffects = SpriteEffects.None) private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, Color? color = null, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
if (attachment.InheritSourceRect) if (attachment.InheritSourceRect)
{ {
@@ -544,7 +609,7 @@ namespace Barotrauma
attachment.Sprite.SourceRect = head.SourceRect; attachment.Sprite.SourceRect = head.SourceRect;
} }
} }
Vector2 origin = attachment.Sprite.Origin; Vector2 origin;
if (attachment.InheritOrigin) if (attachment.InheritOrigin)
{ {
origin = head.Origin; origin = head.Origin;
@@ -559,7 +624,7 @@ namespace Barotrauma
{ {
depth = head.Depth - depthStep; depth = head.Depth - depthStep;
} }
attachment.Sprite.Draw(spriteBatch, drawPos, Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects); attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
} }
public static CharacterInfo ClientRead(string speciesName, IReadMessage inc) public static CharacterInfo ClientRead(string speciesName, IReadMessage inc)
@@ -574,6 +639,9 @@ namespace Barotrauma
int beardIndex = inc.ReadByte(); int beardIndex = inc.ReadByte();
int moustacheIndex = inc.ReadByte(); int moustacheIndex = inc.ReadByte();
int faceAttachmentIndex = inc.ReadByte(); int faceAttachmentIndex = inc.ReadByte();
Color skinColor = inc.ReadColorR8G8B8();
Color hairColor = inc.ReadColorR8G8B8();
Color facialHairColor = inc.ReadColorR8G8B8();
string ragdollFile = inc.ReadString(); string ragdollFile = inc.ReadString();
string jobIdentifier = inc.ReadString(); string jobIdentifier = inc.ReadString();
@@ -599,6 +667,9 @@ namespace Barotrauma
ID = infoID, ID = infoID,
}; };
ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.SkinColor = skinColor;
ch.HairColor = hairColor;
ch.FacialHairColor = facialHairColor;
if (ch.Job != null) if (ch.Job != null)
{ {
foreach (KeyValuePair<string, float> skill in skillLevels) foreach (KeyValuePair<string, float> skill in skillLevels)
@@ -627,5 +698,384 @@ namespace Barotrauma
ch.AdditionalTalentPoints = inc.ReadUInt16(); ch.AdditionalTalentPoints = inc.ReadUInt16();
return ch; return ch;
} }
public void CreateIcon(RectTransform rectT)
{
LoadHeadAttachments();
new GUICustomComponent(rectT,
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
}
public class AppearanceCustomizationMenu : IDisposable
{
public readonly CharacterInfo CharacterInfo;
public GUIListBox HeadSelectionList = null;
public bool HasIcon = true;
public GUIScrollBar.OnMovedHandler OnSliderMoved = null;
public GUIScrollBar.OnMovedHandler OnSliderReleased = null;
public Action<AppearanceCustomizationMenu> OnHeadSwitch = null;
private readonly GUIComponent parentComponent;
private readonly List<Sprite> characterSprites = new List<Sprite>();
public AppearanceCustomizationMenu(CharacterInfo info, GUIComponent parent, bool hasIcon = true)
{
CharacterInfo = info;
parentComponent = parent;
HasIcon = hasIcon;
RecreateFrameContents();
}
public void RecreateFrameContents()
{
var info = CharacterInfo;
HeadSelectionList = null;
parentComponent.ClearChildren();
ClearSprites();
float contentWidth = HasIcon ? 0.75f : 1.0f;
var content =
new GUIListBox(
new RectTransform(new Vector2(contentWidth, 1.0f), parentComponent.RectTransform,
Anchor.CenterLeft))
{ CanBeFocused = false, CanTakeKeyBoardFocus = false }
.Content;
info.LoadHeadAttachments();
if (HasIcon)
{
info.CreateIcon(
new RectTransform(new Vector2(0.25f, 1.0f), parentComponent.RectTransform, Anchor.CenterRight)
{ RelativeOffset = new Vector2(-0.01f, 0.0f) });
}
RectTransform createItemRectTransform(string labelTag, float width = 0.6f)
{
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform));
var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform),
TextManager.Get(labelTag), font: GUI.SubHeadingFont);
var bottomItem = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform),
style: null);
return new RectTransform(new Vector2(width, 1.0f), bottomItem.RectTransform, Anchor.Center);
}
RectTransform genderItemRT = createItemRectTransform("Gender", 1.0f);
GUILayoutGroup genderContainer =
new GUILayoutGroup(genderItemRT, isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
void createGenderButton(Gender gender)
{
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform),
TextManager.Get(gender.ToString()), style: "ListBoxElement")
{
UserData = gender,
OnClicked = OpenHeadSelection,
Selected = info.Gender == gender
};
}
createGenderButton(Gender.Male);
createGenderButton(Gender.Female);
int countAttachmentsOfType(WearableType wearableType)
=> info.FilterByTypeAndHeadID(
info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race),
wearableType, info.HeadSpriteId).Count();
void createAttachmentSlider(int initialValue, WearableType wearableType)
{
int attachmentCount = countAttachmentsOfType(wearableType);
if (attachmentCount > 0)
{
var labelTag = wearableType == WearableType.FaceAttachment
? "FaceAttachment.Accessories"
: $"FaceAttachment.{wearableType}";
var sliderItemRT = createItemRectTransform(labelTag);
var slider =
new GUIScrollBar(sliderItemRT, style: "GUISlider")
{
Range = new Vector2(0, attachmentCount),
StepValue = 1,
OnMoved = (bar, scroll) => SwitchAttachment(bar, wearableType),
OnReleased = OnSliderReleased,
BarSize = 1.0f / (float)(attachmentCount + 1)
};
slider.BarScrollValue = initialValue;
}
}
createAttachmentSlider(info.HairIndex, WearableType.Hair);
createAttachmentSlider(info.BeardIndex, WearableType.Beard);
createAttachmentSlider(info.MoustacheIndex, WearableType.Moustache);
createAttachmentSlider(info.FaceAttachmentIndex, WearableType.FaceAttachment);
void createColorSelector(string labelTag, IEnumerable<Color> options, Func<Color> getter,
Action<Color> setter)
{
var selectorItemRT = createItemRectTransform(labelTag, 0.4f);
var dropdown =
new GUIDropDown(selectorItemRT)
{ AllowNonText = true };
var listBoxSize = dropdown.ListBox.RectTransform.RelativeSize;
dropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.75f, listBoxSize.Y);
var dropdownButton = dropdown.GetChild<GUIButton>();
var buttonFrame =
new GUIFrame(
new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft)
{ RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null);
dropdown.OnSelected = (component, color) =>
{
setter((Color)color);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
return true;
};
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
dropdown.ListBox.UseGridLayout = true;
foreach (var option in options)
{
var optionElement =
new GUIFrame(
new RectTransform(new Vector2(0.25f, 1.0f / 3.0f),
dropdown.ListBox.Content.RectTransform),
style: "ListBoxElement")
{
UserData = option,
CanBeFocused = true
};
var colorElement =
new GUIFrame(
new RectTransform(Vector2.One * 0.75f, optionElement.RectTransform, Anchor.Center,
scaleBasis: ScaleBasis.Smallest),
style: null)
{
Color = option,
HoverColor = option,
OutlineColor = Color.Lerp(Color.Black, option, 0.5f),
CanBeFocused = false
};
}
}
if (countAttachmentsOfType(WearableType.Hair) > 0)
{
createColorSelector($"Customization.{nameof(info.HairColor)}", info.HairColors,
() => info.HairColor, (color) => info.HairColor = color);
}
if (countAttachmentsOfType(WearableType.Moustache) > 0 ||
countAttachmentsOfType(WearableType.Beard) > 0)
{
createColorSelector($"Customization.{nameof(info.FacialHairColor)}", info.FacialHairColors,
() => info.FacialHairColor, (color) => info.FacialHairColor = color);
}
createColorSelector($"Customization.{nameof(info.SkinColor)}", info.SkinColors, () => info.SkinColor,
(color) => info.SkinColor = color);
}
private bool OpenHeadSelection(GUIButton button, object userData)
{
Gender selectedGender = (Gender)userData;
if (HeadSelectionList != null)
{
HeadSelectionList.Visible = true;
foreach (GUIComponent child in HeadSelectionList.Content.Children)
{
child.Visible = (Gender)child.UserData == selectedGender;
child.Children.ForEach(c =>
c.Visible = ((Tuple<Gender, Race, int>)c.UserData).Item1 == selectedGender);
}
return true;
}
var info = CharacterInfo;
float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X;
HeadSelectionList = new GUIListBox(
new RectTransform(
new Point(parentComponent.Rect.Width,
(int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)), GUI.Canvas)
{
AbsoluteOffset = new Point(parentComponent.Rect.Right - parentComponent.Rect.Width,
button.Rect.Bottom)
});
parentComponent.RectTransform.SizeChanged += () =>
{
if (parentComponent == null || HeadSelectionList?.RectTransform == null || button == null)
{
return;
}
HeadSelectionList.RectTransform.Resize(new Point(parentComponent.Rect.Width,
(int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)));
HeadSelectionList.RectTransform.AbsoluteOffset =
new Point(parentComponent.Rect.Right - parentComponent.Rect.Width, button.Rect.Bottom);
};
new GUIFrame(
new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black)
{
UserData = "outerglow",
CanBeFocused = false
};
GUILayoutGroup row = null;
int itemsInRow = 0;
XElement headElement = info.Ragdoll.MainElement.Elements().FirstOrDefault(e =>
e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase));
XElement headSpriteElement = headElement.Element("sprite");
string spritePathWithTags = headSpriteElement.Attribute("texture").Value;
var characterConfigElement = info.CharacterConfigElement;
var heads = info.Heads;
if (heads != null)
{
row = null;
itemsInRow = 0;
foreach (var head in heads)
{
var headPreset = head.Key;
Gender gender = headPreset.Gender;
Race race = headPreset.Race;
int headIndex = headPreset.ID;
string spritePath = spritePathWithTags
.Replace("[GENDER]", gender.ToString().ToLowerInvariant())
.Replace("[RACE]", race.ToString().ToLowerInvariant());
if (!File.Exists(spritePath))
{
continue;
}
Sprite headSprite = new Sprite(headSpriteElement, "", spritePath);
headSprite.SourceRect =
new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.Value.ToPoint()),
headSprite.SourceRect.Size);
characterSprites.Add(headSprite);
if (itemsInRow >= 4 || row == null || gender != (Gender)row.UserData)
{
row = new GUILayoutGroup(
new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform),
true)
{
UserData = gender,
Visible = gender == selectedGender
};
itemsInRow = 0;
}
var btn = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), row.RectTransform),
style: "ListBoxElementSquare")
{
OutlineColor = Color.White * 0.5f,
PressedColor = Color.White * 0.5f,
UserData = new Tuple<Gender, Race, int>(gender, race, headIndex),
OnClicked = SwitchHead,
Selected = gender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId,
Visible = gender == selectedGender
};
new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true);
itemsInRow++;
}
}
return false;
}
private bool SwitchHead(GUIButton button, object obj)
{
var info = CharacterInfo;
Gender gender = ((Tuple<Gender, Race, int>)obj).Item1;
Race race = ((Tuple<Gender, Race, int>)obj).Item2;
int id = ((Tuple<Gender, Race, int>)obj).Item3;
info.Gender = gender;
info.Race = race;
info.HeadSpriteId = id;
RecreateFrameContents();
OnHeadSwitch?.Invoke(this);
return true;
}
private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type)
{
var info = CharacterInfo;
int index = (int)scrollBar.BarScrollValue;
switch (type)
{
case WearableType.Beard:
info.BeardIndex = index;
break;
case WearableType.FaceAttachment:
info.FaceAttachmentIndex = index;
break;
case WearableType.Hair:
info.HairIndex = index;
break;
case WearableType.Moustache:
info.MoustacheIndex = index;
break;
default:
DebugConsole.ThrowError($"Wearable type not implemented: {type}");
return false;
}
info.RefreshHead();
OnSliderMoved?.Invoke(scrollBar, scrollBar.BarScroll);
return true;
}
public void Update()
{
if (HeadSelectionList != null && PlayerInput.PrimaryMouseButtonDown() &&
!GUI.IsMouseOn(HeadSelectionList))
{
HeadSelectionList.Visible = false;
}
}
public void AddToGUIUpdateList()
{
HeadSelectionList?.AddToGUIUpdateList();
}
private void ClearSprites()
{
foreach (Sprite sprite in characterSprites) { sprite.Remove(); }
characterSprites.Clear();
}
public void Dispose()
{
ClearSprites();
}
~AppearanceCustomizationMenu()
{
Dispose();
}
}
} }
} }
@@ -601,8 +601,13 @@ namespace Barotrauma
.FindAll(a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null); .FindAll(a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null);
currentDisplayedAfflictions.Sort((a1, a2) => currentDisplayedAfflictions.Sort((a1, a2) =>
{ {
int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond); int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond);
return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength); if (dmgPerSecond != 0) { return dmgPerSecond; }
return Math.Sign(GetStr(a1) - GetStr(a2));
static float GetStr(Affliction affliction)
{
return affliction.Strength / affliction.Prefab.MaxStrength * (affliction.Prefab.IsBuff ? 1.0f : 10.0f);
}
}); });
HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions); HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions);
updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval; updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval;
@@ -1131,6 +1136,8 @@ namespace Barotrauma
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction); public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
private readonly List<(Affliction affliction, float strength)> displayedAfflictions = new List<(Affliction affliction, float strength)>();
private void UpdateAfflictionContainer(LimbHealth selectedLimb) private void UpdateAfflictionContainer(LimbHealth selectedLimb)
{ {
if (selectedLimb == null) if (selectedLimb == null)
@@ -1139,45 +1146,33 @@ namespace Barotrauma
return; return;
} }
var currentAfflictions = GetMatchingAfflictions(selectedLimb, a => a.ShouldShowIcon(Character)); var currentAfflictions = GetMatchingAfflictions(selectedLimb, a => a.ShouldShowIcon(Character));
var displayedAfflictions = afflictionIconContainer.Content.Children.Select(c => c.UserData as Affliction); if (currentAfflictions.Any(a => !displayedAfflictions.Any(d => d.affliction == a)) ||
if (currentAfflictions.Any(a => !displayedAfflictions.Contains(a)) || displayedAfflictions.Any(a => !currentAfflictions.Contains(a.affliction)))
displayedAfflictions.Any(a => !currentAfflictions.Contains(a)))
{ {
CreateAfflictionInfos(currentAfflictions); CreateAfflictionInfos(currentAfflictions);
CreateRecommendedTreatments();
}
//update recommended treatments if the strength of some displayed affliction has changed by > 1
else if (displayedAfflictions.Any(d => Math.Abs(d.strength - currentAfflictions.First(a => a == d.affliction).Strength) > 1.0f))
{
CreateRecommendedTreatments();
} }
UpdateAfflictionInfos(displayedAfflictions); UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction));
} }
private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions) private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
{ {
afflictionIconContainer.ClearChildren(); afflictionIconContainer.ClearChildren();
recommendedTreatmentContainer.Content.ClearChildren(); displayedAfflictions.Clear();
float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical");
//key = item identifier
//float = suitability
Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();
GetSuitableTreatments(treatmentSuitability,
normalize: true,
ignoreHiddenAfflictions: true,
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
foreach (string treatment in treatmentSuitability.Keys.ToList())
{
//prefer suggestions for items the player has
if (Character.Controlled.Inventory.FindItemByIdentifier(treatment) != null)
{
treatmentSuitability[treatment] *= 10.0f;
}
}
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault(); Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault();
GUIButton buttonToSelect = null; GUIButton buttonToSelect = null;
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
{ {
displayedAfflictions.Add((affliction, affliction.Strength));
var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter)) var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter))
{ {
Stretch = true, Stretch = true,
@@ -1233,6 +1228,39 @@ namespace Barotrauma
child.Recalculate(); child.Recalculate();
} }
buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction");
afflictionIconContainer.RecalculateChildren();
}
private void CreateRecommendedTreatments()
{
ItemPrefab prevHighlightedItem = null;
if (GUI.MouseOn?.UserData is ItemPrefab && recommendedTreatmentContainer.Content.IsParentOf(GUI.MouseOn))
{
prevHighlightedItem = (ItemPrefab)GUI.MouseOn.UserData;
}
recommendedTreatmentContainer.Content.ClearChildren();
float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical");
//key = item identifier
//float = suitability
Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();
GetSuitableTreatments(treatmentSuitability,
normalize: true,
ignoreHiddenAfflictions: true,
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
foreach (string treatment in treatmentSuitability.Keys.ToList())
{
//prefer suggestions for items the player has
if (Character.Controlled.Inventory.FindItemByIdentifier(treatment) != null)
{
treatmentSuitability[treatment] *= 10.0f;
}
}
if (!treatmentSuitability.Any()) if (!treatmentSuitability.Any())
{ {
new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center) new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center)
@@ -1248,10 +1276,6 @@ namespace Barotrauma
recommendedTreatmentContainer.AutoHideScrollBar = true; recommendedTreatmentContainer.AutoHideScrollBar = true;
} }
buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction");
afflictionIconContainer.RecalculateChildren();
List<KeyValuePair<string, float>> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList(); List<KeyValuePair<string, float>> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList();
int count = 0; int count = 0;
@@ -1286,7 +1310,7 @@ namespace Barotrauma
new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow") new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow")
{ {
CanBeFocused = false, CanBeFocused = false,
Color = Color.White * 0.7f, Color = GUI.Style.Green,
HoverColor = Color.White, HoverColor = Color.White,
PressedColor = Color.DarkGray, PressedColor = Color.DarkGray,
SelectedColor = Color.Transparent, SelectedColor = Color.Transparent,
@@ -1304,6 +1328,12 @@ namespace Barotrauma
SelectedColor = itemColor, SelectedColor = itemColor,
DisabledColor = itemColor * 0.8f DisabledColor = itemColor * 0.8f
}; };
if (item == prevHighlightedItem)
{
innerFrame.State = GUIComponent.ComponentState.Hover;
innerFrame.Children.ForEach(c => c.State = GUIComponent.ComponentState.Hover);
}
} }
recommendedTreatmentContainer.RecalculateChildren(); recommendedTreatmentContainer.RecalculateChildren();
@@ -1315,6 +1345,19 @@ namespace Barotrauma
int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond); int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond);
return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(second.Strength - first.Strength); return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(second.Strength - first.Strength);
}); });
if (count > 0)
{
var treatmentIconSize = recommendedTreatmentContainer.Content.Children.Sum(c => c.Rect.Width + recommendedTreatmentContainer.Spacing);
if (treatmentIconSize < recommendedTreatmentContainer.Content.Rect.Width)
{
var spacing = new GUIFrame(new RectTransform(new Point((recommendedTreatmentContainer.Content.Rect.Width - treatmentIconSize) / 2, 0), recommendedTreatmentContainer.Content.RectTransform), style: null)
{
CanBeFocused = false
};
spacing.RectTransform.SetAsFirstChild();
}
}
} }
private void CreateAfflictionInfoElements(GUIComponent parent, Affliction affliction) private void CreateAfflictionInfoElements(GUIComponent parent, Affliction affliction)
@@ -120,6 +120,15 @@ namespace Barotrauma
public List<SpriteDeformation> ActiveDeformations { get; set; } = new List<SpriteDeformation>(); public List<SpriteDeformation> ActiveDeformations { get; set; } = new List<SpriteDeformation>();
public Sprite Sprite { get; protected set; } public Sprite Sprite { get; protected set; }
public Sprite TintMask { get; protected set; }
public Sprite HuskMask { get; protected set; }
public float TintHighlightThreshold { get; protected set; }
public float TintHighlightMultiplier { get; protected set; }
private SpriteBatch.EffectWithParams tintEffectParams;
private SpriteBatch.EffectWithParams huskSpriteParams;
protected DeformableSprite _deformSprite; protected DeformableSprite _deformSprite;
@@ -273,6 +282,7 @@ namespace Barotrauma
DecorativeSpriteGroups[groupID].Add(decorativeSprite); DecorativeSpriteGroups[groupID].Add(decorativeSprite);
spriteAnimState.Add(decorativeSprite, new SpriteState()); spriteAnimState.Add(decorativeSprite, new SpriteState());
} }
TintMask = null;
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -308,6 +318,22 @@ namespace Barotrauma
InitialLightSourceColor = LightSource.Color; InitialLightSourceColor = LightSource.Color;
InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha; InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha;
break; break;
case "tintmask":
string tintMaskPath = subElement.GetAttributeString("texture", "");
if (!string.IsNullOrWhiteSpace(tintMaskPath))
{
TintMask = new Sprite(subElement, file: GetSpritePath(tintMaskPath));
TintHighlightThreshold = subElement.GetAttributeFloat("highlightthreshold", 0.6f);
TintHighlightMultiplier = subElement.GetAttributeFloat("highlightmultiplier", 0.8f);
}
break;
case "huskmask":
string huskMaskPath = subElement.GetAttributeString("texture", "");
if (!string.IsNullOrWhiteSpace(huskMaskPath))
{
HuskMask = new Sprite(subElement, file: GetSpritePath(huskMaskPath));
}
break;
} }
ISerializableEntity GetConditionalTarget() ISerializableEntity GetConditionalTarget()
@@ -449,20 +475,20 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Get the full path of a limb sprite, taking into account tags, gender and head id /// Get the full path of a limb sprite, taking into account tags, gender and head id
/// </summary> /// </summary>
private string GetSpritePath(string texturePath) public static string GetSpritePath(string texturePath, CharacterInfo characterInfo)
{ {
string spritePath = texturePath; string spritePath = texturePath;
string spritePathWithTags = spritePath; string spritePathWithTags = spritePath;
if (character.Info != null && character.IsHumanoid) if (characterInfo != null)
{ {
spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "female" : "male"); spritePath = spritePath.Replace("[GENDER]", (characterInfo.Gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", character.Info.Race.ToString().ToLowerInvariant()); spritePath = spritePath.Replace("[RACE]", characterInfo.Race.ToString().ToLowerInvariant());
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString()); spritePath = spritePath.Replace("[HEADID]", characterInfo.HeadSpriteId.ToString());
if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any()) if (characterInfo.HeadSprite != null && characterInfo.SpriteTags.Any())
{ {
string tags = ""; string tags = "";
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]"); characterInfo.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
spritePathWithTags = Path.Combine( spritePathWithTags = Path.Combine(
Path.GetDirectoryName(spritePath), Path.GetDirectoryName(spritePath),
@@ -472,6 +498,13 @@ namespace Barotrauma
return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath; return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath;
} }
private string GetSpritePath(string texturePath)
{
if (!character.IsHumanoid) { return texturePath; }
return GetSpritePath(texturePath, character?.Info);
}
partial void LoadParamsProjSpecific() partial void LoadParamsProjSpecific()
{ {
bool isFlipped = dir == Direction.Left; bool isFlipped = dir == Direction.Left;
@@ -638,13 +671,24 @@ namespace Barotrauma
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; } if (spriteParams == null) { return; }
Color color = new Color(spriteParams.Color.R / 255f * brightness, spriteParams.Color.G / 255f * brightness, spriteParams.Color.B / 255f * brightness, spriteParams.Color.A / 255f); Color clr = spriteParams.Color;
if (!spriteParams.IgnoreTint)
{
clr = clr.Multiply(ragdoll.RagdollParams.Color);
if (character.Info != null)
{
clr = clr.Multiply(character.Info.SkinColor);
}
}
Color color = new Color((byte)(clr.R * brightness), (byte)(clr.G * brightness), (byte)(clr.B * brightness), clr.A);
Color blankColor = new Color(brightness, brightness, brightness, 1);
if (deadTimer > 0) if (deadTimer > 0)
{ {
color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer)); color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
} }
color = overrideColor ?? color; color = overrideColor ?? color;
blankColor = overrideColor ?? blankColor;
if (isSevered) if (isSevered)
{ {
@@ -667,6 +711,8 @@ namespace Barotrauma
OtherWearables.Any(w => w.HideLimb) || OtherWearables.Any(w => w.HideLimb) ||
wearingItems.Any(w => w != null && w.HideLimb); wearingItems.Any(w => w != null && w.HideLimb);
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
var activeSprite = ActiveSprite; var activeSprite = ActiveSprite;
if (type == LimbType.Head) if (type == LimbType.Head)
{ {
@@ -698,7 +744,33 @@ namespace Barotrauma
} }
else else
{ {
bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null;
if (useTintMask)
{
tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
tintEffectParams.Params ??= new Dictionary<string, object>();
var parameters = tintEffectParams.Params;
parameters["xBaseTexture"] = Sprite.Texture;
parameters["xTintMaskTexture"] = TintMask.Texture;
if (drawHuskSprite && HuskMask != null)
{
parameters["xCutoffTexture"] = HuskMask.Texture;
parameters["baseToCutoffSizeRatio"] = (float)Sprite.Texture.Width / (float)HuskMask.Texture.Width;
}
else
{
parameters["xCutoffTexture"] = GUI.WhiteTexture;
parameters["baseToCutoffSizeRatio"] = 1.0f;
}
parameters["highlightThreshold"] = TintHighlightThreshold;
parameters["highlightMultiplier"] = TintHighlightMultiplier;
spriteBatch.SwapEffect(tintEffectParams);
}
body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically); body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
if (useTintMask)
{
spriteBatch.SwapEffect(null);
}
} }
// Handle non-exlusive, i.e. additional conditional sprites // Handle non-exlusive, i.e. additional conditional sprites
foreach (var conditionalSprite in ConditionalSprites) foreach (var conditionalSprite in ConditionalSprites)
@@ -770,15 +842,36 @@ namespace Barotrauma
} }
if (onlyDrawable == null) if (onlyDrawable == null)
{ {
if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes)) if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes) && herpesStrength > 0)
{ {
DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect); float alpha = Math.Min(herpesStrength * 2 / 100.0f, 1.0f);
DrawWearable(HerpesSprite, depthStep, spriteBatch, blankColor, alpha: alpha, spriteEffect);
depthStep += step;
}
if (drawHuskSprite)
{
bool useTintEffect = HuskMask != null && spriteBatch.GetCurrentEffect() is null;
if (useTintEffect)
{
huskSpriteParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
huskSpriteParams.Params ??= new Dictionary<string, object>();
var parameters = huskSpriteParams.Params;
parameters["xCutoffTexture"] = GUI.WhiteTexture;
parameters["baseToCutoffSizeRatio"] = 1.0f;
spriteBatch.SwapEffect(huskSpriteParams);
}
DrawWearable(HuskSprite, depthStep, spriteBatch, color, alpha: color.A / 255f, spriteEffect);
if (useTintEffect)
{
spriteBatch.SwapEffect(null);
}
depthStep += step; depthStep += step;
} }
foreach (WearableSprite wearable in OtherWearables) foreach (WearableSprite wearable in OtherWearables)
{ {
if (wearable.Type == WearableType.Husk) { continue; }
if (wearableTypesToHide.Contains(wearable.Type)) { continue; } if (wearableTypesToHide.Contains(wearable.Type)) { continue; }
DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect); DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
//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;
} }
@@ -786,7 +879,7 @@ namespace Barotrauma
foreach (WearableSprite wearable in WearingItems) foreach (WearableSprite wearable in WearingItems)
{ {
if (onlyDrawable != null && onlyDrawable != wearable) continue; if (onlyDrawable != null && onlyDrawable != wearable) continue;
DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect); DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
//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;
} }
@@ -936,7 +1029,7 @@ namespace Barotrauma
} }
} }
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, SpriteEffects spriteEffect) private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
{ {
var sprite = ActiveSprite; var sprite = ActiveSprite;
if (wearable.InheritSourceRect) if (wearable.InheritSourceRect)
@@ -955,7 +1048,7 @@ namespace Barotrauma
} }
} }
Vector2 origin = wearable.Sprite.Origin; Vector2 origin;
if (wearable.InheritOrigin) if (wearable.InheritOrigin)
{ {
origin = sprite.Origin; origin = sprite.Origin;
@@ -986,7 +1079,7 @@ namespace Barotrauma
Color wearableColor = Color.White; Color wearableColor = Color.White;
if (wearableItemComponent != null) if (wearableItemComponent != null)
{ {
// Draw outer cloths on top of inner cloths. // Draw outer clothes on top of inner clothes.
if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes)) if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes))
{ {
depth -= depthStep; depth -= depthStep;
@@ -997,15 +1090,38 @@ namespace Barotrauma
} }
wearableColor = wearableItemComponent.Item.GetSpriteColor(); wearableColor = wearableItemComponent.Item.GetSpriteColor();
} }
float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale; else if (character.Info != null)
{
if (wearable.Type == WearableType.Hair)
{
wearableColor = character.Info.HairColor;
}
else if (wearable.Type == WearableType.Beard || wearable.Type == WearableType.Moustache)
{
wearableColor = character.Info.FacialHairColor;
}
}
float scale = wearable.Scale;
if (wearable.InheritScale)
{
if (!wearable.IgnoreTextureScale)
{
scale *= TextureScale;
}
if (!wearable.IgnoreLimbScale)
{
scale *= Params.Scale;
}
if (!wearable.IgnoreRagdollScale)
{
scale *= ragdoll.RagdollParams.LimbScale;
}
}
float rotation = -body.DrawRotation - wearable.Rotation * Dir; float rotation = -body.DrawRotation - wearable.Rotation * Dir;
float finalAlpha = alpha * wearableColor.A;
wearable.Sprite.Draw(spriteBatch, Color finalColor = color.Multiply(wearableColor);
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha);
new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)), wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
origin, rotation,
Scale * textureScale, spriteEffect, depth);
} }
private WearableSprite GetWearableSprite(WearableType type, bool random = false) private WearableSprite GetWearableSprite(WearableType type, bool random = false)
@@ -1056,6 +1172,9 @@ namespace Barotrauma
HerpesSprite?.Sprite.Remove(); HerpesSprite?.Sprite.Remove();
HerpesSprite = null; HerpesSprite = null;
TintMask?.Remove();
TintMask = null;
} }
} }
} }
@@ -29,7 +29,7 @@ namespace Barotrauma
} }
} }
for (int i = 0; i < ResourceClusters.Count; i++) for (int i = 0; i < resourceClusters.Count; i++)
{ {
var amount = msg.ReadByte(); var amount = msg.ReadByte();
var rotation = msg.ReadSingle(); var rotation = msg.ReadSingle();
@@ -41,20 +41,20 @@ namespace Barotrauma
h.AttachToWall(); h.AttachToWall();
item.Rotation = rotation; item.Rotation = rotation;
} }
if (SpawnedResources.TryGetValue(item.Prefab.Identifier, out var resources)) if (spawnedResources.TryGetValue(item.Prefab.Identifier, out var resources))
{ {
resources.Add(item); resources.Add(item);
} }
else else
{ {
SpawnedResources.Add(item.Prefab.Identifier, new List<Item>() { item }); spawnedResources.Add(item.Prefab.Identifier, new List<Item>() { item });
} }
} }
} }
CalculateMissionClusterPositions(); CalculateMissionClusterPositions();
for(int i = 0; i < ResourceClusters.Count; i++) for(int i = 0; i < resourceClusters.Count; i++)
{ {
var identifier = msg.ReadString(); var identifier = msg.ReadString();
var count = msg.ReadByte(); var count = msg.ReadByte();
@@ -66,7 +66,7 @@ namespace Barotrauma
if (!(entity is Item item)) { continue; } if (!(entity is Item item)) { continue; }
resources[j] = item; resources[j] = item;
} }
RelevantLevelResources.Add(identifier, resources); relevantLevelResources.Add(identifier, resources);
} }
} }
} }
@@ -130,6 +130,7 @@ namespace Barotrauma
public static GUIStyle Style; public static GUIStyle Style;
private static Texture2D t; private static Texture2D t;
public static Texture2D WhiteTexture => t;
private static Sprite[] MouseCursorSprites => Style.CursorSprite; private static Sprite[] MouseCursorSprites => Style.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata; private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata;
@@ -862,11 +863,10 @@ namespace Barotrauma
int index = 0; int index = 0;
if (updateList.Count > 0) if (updateList.Count > 0)
{ {
index = updateList.Count - 1; index = updateList.Count;
while (updateList[index].UpdateOrder > item.UpdateOrder) while (index > 0 && updateList[index-1].UpdateOrder > item.UpdateOrder)
{ {
index--; index--;
if (index == 0) { break; }
} }
} }
if (!updateListSet.Contains(item)) if (!updateListSet.Contains(item))
@@ -1057,12 +1057,15 @@ namespace Barotrauma
if (listBox.DraggedElement != null) { return CursorState.Dragging; } if (listBox.DraggedElement != null) { return CursorState.Dragging; }
if (listBox.CanDragElements) { return CursorState.Move; } if (listBox.CanDragElements) { return CursorState.Move; }
var hoverParent = c; if (listBox.HoverCursor != CursorState.Default)
while (true)
{ {
if (hoverParent == parent || hoverParent == null) { break; } var hoverParent = c;
if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; } while (true)
hoverParent = hoverParent.Parent; {
if (hoverParent == parent || hoverParent == null) { break; }
if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; }
hoverParent = hoverParent.Parent;
}
} }
} }
@@ -24,6 +24,8 @@ namespace Barotrauma
public bool Dropped { get; set; } public bool Dropped { get; set; }
public bool AllowNonText { get; set; }
public object SelectedItemData public object SelectedItemData
{ {
get get
@@ -318,9 +320,9 @@ namespace Barotrauma
if (textBlock == null) if (textBlock == null)
{ {
textBlock = component.GetChild<GUITextBlock>(); textBlock = component.GetChild<GUITextBlock>();
if (textBlock == null) return false; if (textBlock is null && !AllowNonText) { return false; }
} }
button.Text = textBlock.Text; button.Text = textBlock?.Text ?? "";
} }
Dropped = false; Dropped = false;
// TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it. // TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
@@ -11,7 +11,7 @@ namespace Barotrauma
{ {
private Dictionary<string, GUIComponentStyle> componentStyles; private Dictionary<string, GUIComponentStyle> componentStyles;
private XElement configElement; private readonly XElement configElement;
private GraphicsDevice graphicsDevice; private GraphicsDevice graphicsDevice;
@@ -19,7 +19,7 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon; private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon; private static Sprite ownerIcon, moderatorIcon;
public enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents };
public static InfoFrameTab selectedTab; public static InfoFrameTab selectedTab;
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
@@ -34,6 +34,8 @@ namespace Barotrauma
private List<CharacterTeamType> teamIDs; private List<CharacterTeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022"; private const string inLobbyString = "\u2022 \u2022 \u2022";
private GUIFrame pendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f; public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private class LinkedGUI private class LinkedGUI
@@ -134,6 +136,13 @@ namespace Barotrauma
public void Update() public void Update()
{ {
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null)
{
int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count;
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
}
if (selectedTab != InfoFrameTab.Crew) return; if (selectedTab != InfoFrameTab.Crew) return;
if (linkedGUIList == null) return; if (linkedGUIList == null) return;
@@ -183,16 +192,8 @@ namespace Barotrauma
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
//this used to be a switch expression but i changed it because it killed enc :( //this used to be a switch expression but i changed it because it killed enc :(
Vector2 contentFrameSize; //now it's not even a switch statement anymore :(
switch (selectedTab) Vector2 contentFrameSize = new Vector2(0.45f, 0.667f);
{
case InfoFrameTab.MyCharacter:
contentFrameSize = new Vector2(0.45f, 0.5f);
break;
default:
contentFrameSize = new Vector2(0.45f, 0.667f);
break;
}
contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.12f) }); contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.12f) });
var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(25f)) }, isHorizontal: true) var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(25f)) }, isHorizontal: true)
@@ -243,6 +244,17 @@ namespace Barotrauma
{ {
TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money)) TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money))
}; };
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))
}, style: null);
pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
} }
else else
{ {
@@ -255,20 +267,17 @@ namespace Barotrauma
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine"); var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
if (GameMain.NetworkMember != null) var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character");
talentsButton.OnAddedToGUIUpdateList += (component) =>
{ {
var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character"); talentsButton.Enabled = Character.Controlled?.Info != null;
}
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents");
talentsButton.OnAddedToGUIUpdateList += (GUIComponent component) =>
{
talentsButton.Enabled = Character.Controlled?.Info != null && (GameMain.GameSession?.Campaign != null || Screen.Selected == GameMain.TestScreen || GameMain.GameSession.GameMode is TestGameMode);
if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents)
{ {
SelectInfoFrameTab(null, InfoFrameTab.Crew); SelectInfoFrameTab(null, InfoFrameTab.Crew);
} }
}; };
talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton);
} }
private bool SelectInfoFrameTab(GUIButton button, object userData) private bool SelectInfoFrameTab(GUIButton button, object userData)
@@ -300,10 +309,6 @@ namespace Barotrauma
if (traitor == null || traitorMission == null) return false; if (traitor == null || traitorMission == null) return false;
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor); CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break; break;
case InfoFrameTab.MyCharacter:
if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder);
break;
case InfoFrameTab.Submarine: case InfoFrameTab.Submarine:
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break; break;
@@ -1188,6 +1193,11 @@ namespace Barotrauma
private GUITextBlock talentPointText; private GUITextBlock talentPointText;
private GUIListBox skillListBox; private GUIListBox skillListBox;
private GUIButton talentApplyButton,
talentResetButton;
private GUIImage talentPointNotification;
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle>
{ {
{ TalentTree.TalentTreeStageState.Invalid, GUI.Style.GetComponentStyle("TalentTreeLocked") }, { TalentTree.TalentTreeStageState.Invalid, GUI.Style.GetComponentStyle("TalentTreeLocked") },
@@ -1219,9 +1229,22 @@ namespace Barotrauma
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);
GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null);
if (controlledCharacter.Info == null) GUIFrame talentFrameMain = new GUIFrame(new RectTransform(Vector2.One, paddedTalentFrame.RectTransform), style: null);
GUIFrame characterSettingsFrame = null;
GUILayoutGroup characterLayout = null;
if (!(GameMain.NetworkMember is null))
{
characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrameContent.RectTransform), style: null) { Visible = false };
characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
}
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;
@@ -1229,7 +1252,7 @@ namespace Barotrauma
selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList();
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), paddedTalentFrame.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)
{ {
AbsoluteSpacing = GUI.IntScale(5) AbsoluteSpacing = GUI.IntScale(5)
}; };
@@ -1241,11 +1264,11 @@ namespace Barotrauma
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) =>
{ {
float posY = component.Rect.Bottom - component.Rect.Width; float posY = component.Rect.Center.Y - component.Rect.Width / 2;
info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false); info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
}); });
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.375f, 1f), talentInfoLayoutGroup.RectTransform)); GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f };
Vector2 nameSize = GUI.SubHeadingFont.MeasureString(info.Name); Vector2 nameSize = GUI.SubHeadingFont.MeasureString(info.Name);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUI.SubHeadingFont) { TextColor = job.Prefab.UIColor }; GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUI.SubHeadingFont) { TextColor = job.Prefab.UIColor };
@@ -1260,7 +1283,56 @@ namespace Barotrauma
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUI.SmallFont); GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUI.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.375f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true }; if (!(GameMain.NetworkMember is null))
{
GUIButton newCharacterBox = new GUIButton(new RectTransform(Vector2.One, nameLayout.RectTransform, Anchor.BottomCenter), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"))
{
IgnoreLayoutGroups = true
};
newCharacterBox.OnClicked = (button, o) =>
{
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
{
newCharacterBox.Text = TextManager.Get("settings");
if (pendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
talentFrameMain.Visible = false;
}
};
if (!(characterLayout is null))
{
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomRight);
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("close"))
{
OnClicked = (button, o) =>
{
characterSettingsFrame!.Visible = false;
talentFrameMain.Visible = true;
return true;
}
};
}
}
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true };
string skillString = TextManager.Get("skills"); string skillString = TextManager.Get("skills");
Vector2 skillSize = GUI.SubHeadingFont.MeasureString(skillString); Vector2 skillSize = GUI.SubHeadingFont.MeasureString(skillString);
@@ -1276,6 +1348,7 @@ namespace Barotrauma
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); 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) 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); GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
@@ -1285,7 +1358,7 @@ namespace Barotrauma
int elementPadding = GUI.IntScale(8); int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize; 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"); GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.LargeFont, textAlignment: Alignment.Center); subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.SubHeadingFont, textAlignment: Alignment.Center));
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@@ -1296,7 +1369,7 @@ namespace Barotrauma
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground"); GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground");
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false }; 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.25f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight), style: null) 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 CanBeFocused = false
}; };
@@ -1316,10 +1389,12 @@ namespace Barotrauma
{ {
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null) GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
{ {
CanBeFocused = false, CanBeFocused = false
}; };
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: null) GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{ {
ToolTip = $"{talent.DisplayName}\n\n{talent.Description}", ToolTip = $"{talent.DisplayName}\n\n{talent.Description}",
UserData = talent.Identifier, UserData = talent.Identifier,
@@ -1361,7 +1436,7 @@ namespace Barotrauma
GUIComponent iconImage; GUIComponent iconImage;
if (talent.Icon is null) if (talent.Icon is null)
{ {
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: null) iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: null)
{ {
OutlineColor = GUI.Style.Red, OutlineColor = GUI.Style.Red,
TextColor = GUI.Style.Red, TextColor = GUI.Style.Red,
@@ -1387,10 +1462,11 @@ namespace Barotrauma
} }
} }
} }
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.775f, 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),
@@ -1399,19 +1475,22 @@ namespace Barotrauma
IsHorizontal = true IsHorizontal = true
}; };
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight); experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight)
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight);
new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUICharacterInfoButton")
{ {
OnClicked = ResetTalentSelection, Shadow = true
}; };
new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUICharacterInfoButton") talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
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);
UpdateTalentButtons(); UpdateTalentButtons();
} }
@@ -1419,11 +1498,12 @@ namespace Barotrauma
private void CreateTalentSkillList(Character character, GUIListBox parent) private void CreateTalentSkillList(Character character, GUIListBox parent)
{ {
parent.Content.ClearChildren(); parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>();
foreach (Skill skill in character.Info.Job.Skills) foreach (Skill skill in character.Info.Job.Skills)
{ {
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 };
new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}", returnNull: true) ?? skill.Identifier); skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}", returnNull: true) ?? skill.Identifier));
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);
@@ -1444,6 +1524,7 @@ namespace Barotrauma
} }
parent.RecalculateChildren(); parent.RecalculateChildren();
GUITextBlock.AutoScaleAndNormalize(skillNames);
} }
private void UpdateTalentButtons() private void UpdateTalentButtons()
@@ -1068,7 +1068,6 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
} }
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks);
PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
@@ -60,7 +60,7 @@ namespace Barotrauma
var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CampaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles); GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, null, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform), var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign"), style: "GUITabButton") TextManager.Get("NewCampaign"), style: "GUITabButton")
@@ -21,7 +21,8 @@ namespace Barotrauma
{ {
if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null) if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null)
{ {
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.Dispose();
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu = null;
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
} }
if (tabMenu == null && !(GameMode is TutorialMode) && !ConversationAction.IsDialogOpen) if (tabMenu == null && !(GameMode is TutorialMode) && !ConversationAction.IsDialogOpen)
@@ -39,6 +40,7 @@ namespace Barotrauma
private GUILayoutGroup topLeftButtonGroup; private GUILayoutGroup topLeftButtonGroup;
private GUIButton crewListButton, commandButton, tabMenuButton; private GUIButton crewListButton, commandButton, tabMenuButton;
private GUIImage talentPointNotification;
private GUIComponent respawnInfoFrame, respawnButtonContainer; private GUIComponent respawnInfoFrame, respawnButtonContainer;
private GUITextBlock respawnInfoText; private GUITextBlock respawnInfoText;
@@ -88,11 +90,11 @@ namespace Barotrauma
tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton") tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton")
{ {
ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)), ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)),
OnClicked = (button, userData) => OnClicked = (button, userData) => ToggleTabMenu()
{
return ToggleTabMenu();
}
}; };
talentPointNotification = CreateTalentIconNotification(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons; GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform) respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
@@ -139,11 +141,41 @@ namespace Barotrauma
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList(); GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList(); GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
} }
} }
public static GUIImage CreateTalentIconNotification(GUIComponent parent, bool offset = true)
{
GUIImage indicator = new GUIImage(new RectTransform(new Vector2(0.45f), parent.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.BothWidth), style: "TalentPointNotification")
{
Visible = false,
CanBeFocused = false
};
Point notificationSize = indicator.RectTransform.NonScaledSize;
if (offset)
{
indicator.RectTransform.AbsoluteOffset = new Point(-(notificationSize.X / 2), -(notificationSize.Y / 2));
}
return indicator;
}
public static void UpdateTalentNotificationIndicator(GUIImage indicator)
{
if (indicator != null)
{
if (Character.Controlled?.Info == null)
{
indicator.Visible = false;
}
else
{
indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0;
}
}
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
@@ -165,21 +197,17 @@ namespace Barotrauma
} }
} }
UpdateTalentNotificationIndicator(talentPointNotification);
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
if (GameMain.NetLobbyScreen?.HeadSelectionList != null) GameMain.NetLobbyScreen?.CharacterAppearanceCustomizationMenu?.Update();
{
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.HeadSelectionList))
{
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
}
}
if (GameMain.NetLobbyScreen?.JobSelectionFrame != null) if (GameMain.NetLobbyScreen?.JobSelectionFrame != null)
{ {
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame)) if (GameMain.NetLobbyScreen.JobSelectionFrame != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame))
{ {
GameMain.NetLobbyScreen.JobList.Deselect(); GameMain.NetLobbyScreen.JobList.Deselect();
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false;
} }
} }
} }
@@ -323,7 +323,7 @@ namespace Barotrauma
case Layout.Default: case Layout.Default:
{ {
int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s));
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s)); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth;
@@ -14,7 +14,7 @@ namespace Barotrauma.Items.Components
public override void AddTooltipInfo(ref string name, ref string description) public override void AddTooltipInfo(ref string name, ref string description)
{ {
if (!string.IsNullOrEmpty(materialName)) if (!string.IsNullOrEmpty(materialName) && item.ContainedItems.Count() > 0)
{ {
string mergedMaterialName = materialName; string mergedMaterialName = materialName;
foreach (Item containedItem in item.ContainedItems) foreach (Item containedItem in item.ContainedItems)
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
if (containedMaterial == null) { continue; } if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName; mergedMaterialName += ", " + containedMaterial.materialName;
} }
name = TextManager.GetWithVariable("entityname.geneticmaterial", "[type]", mergedMaterialName); name = name.Replace(materialName, mergedMaterialName);
} }
if (Tainted) if (Tainted)
@@ -164,7 +164,7 @@ namespace Barotrauma.Items.Components
} }
} }
} }
activateButton.Enabled = inputContainer.Inventory.AllItems.Any(); activateButton.Enabled = outputsFound;
activateButton.Text = TextManager.Get(ActivateButtonText); activateButton.Text = TextManager.Get(ActivateButtonText);
}; };
} }
@@ -516,11 +516,7 @@ namespace Barotrauma.Items.Components
string name = GetRecipeNameAndAmount(selectedItem); string name = GetRecipeNameAndAmount(selectedItem);
float quality = 0; float quality = GetFabricatedItemQuality(selectedItem, user);
foreach (string tag in selectedItem.TargetItem.Tags)
{
quality += user?.Info?.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag) ?? 0;
}
if (quality > 0) if (quality > 0)
{ {
name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3"); name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3");
@@ -246,25 +246,26 @@ namespace Barotrauma.Items.Components
protected override void CreateGUI() protected override void CreateGUI()
{ {
GuiFrame.ClearChildren();
GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f);
GuiFrame.CanBeFocused = true; GuiFrame.CanBeFocused = true;
new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDBack, null); var submarineBack = new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDBack, null);
GUIFrame paddedContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), style: null); GUIFrame paddedContainer = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center), style: null);
submarineContainer = new GUIFrame(new RectTransform(Vector2.One, paddedContainer.RectTransform, Anchor.Center), style: null); submarineContainer = new GUIFrame(new RectTransform(Vector2.One, paddedContainer.RectTransform, Anchor.Center), style: null);
var submarineFront = new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null)
new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null)
{ {
CanBeFocused = false CanBeFocused = false
}; };
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.2f), paddedContainer.RectTransform), isHorizontal: true); GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, isHorizontal: true) { CanBeFocused = true };
modeSwitchButtons = ImmutableArray.Create modeSwitchButtons = ImmutableArray.Create
( (
new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullStatus") { UserData = MiniMapMode.HullStatus, Enabled = EnableHullStatus, ToolTip = TextManager.Get("StatusMonitorButton.HullStatus.Tooltip") }, new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullStatus") { UserData = MiniMapMode.HullStatus, Enabled = EnableHullStatus, ToolTip = TextManager.Get("StatusMonitorButton.HullStatus.Tooltip") },
new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") },
new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.Tooltip") }, new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.Tooltip") },
new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") } new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") }
); );
foreach (GUIButton button in modeSwitchButtons) foreach (GUIButton button in modeSwitchButtons)
@@ -295,14 +296,15 @@ namespace Barotrauma.Items.Components
List<Order> reports = Order.PrefabList.FindAll(o => o.IsReport && o.SymbolSprite != null && !o.Hidden); List<Order> reports = Order.PrefabList.FindAll(o => o.IsReport && o.SymbolSprite != null && !o.Hidden);
GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter), style: null) GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null)
{ {
CanBeFocused = false CanBeFocused = false
}; };
reportFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true) reportFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true)
{ {
AbsoluteSpacing = (int)(5 * GUI.Scale) Stretch = true,
AbsoluteSpacing = GUI.IntScale(5)
}; };
if (reports.Any()) if (reports.Any())
@@ -359,10 +361,7 @@ namespace Barotrauma.Items.Components
searchBar.OnSelected += (sender, key) => searchBar.OnSelected += (sender, key) =>
{ {
itemsFoundOnSub = Item.ItemList.Where(it => itemsFoundOnSub = Item.ItemList.Where(it => VisibleOnItemFinder(it)).Select(it => it.Prefab).ToImmutableHashSet();
it.Submarine == item.Submarine &&
!it.NonInteractable && !it.HiddenInGame &&
(it.GetComponent<Holdable>() != null || it.GetComponent<Wearable>() != null)).Select(it => it.Prefab).ToImmutableHashSet();
}; };
searchBar.OnKeyHit += ControlSearchTooltip; searchBar.OnKeyHit += ControlSearchTooltip;
@@ -390,6 +389,28 @@ namespace Barotrauma.Items.Components
c.CanBeFocused = false; c.CanBeFocused = false;
c.Children.ForEach(c2 => c2.CanBeFocused = false); c.Children.ForEach(c2 => c2.CanBeFocused = false);
}); });
submarineBack.RectTransform.MaxSize =
submarineFront.RectTransform.MaxSize =
submarineContainer.RectTransform.MaxSize =
new Point(int.MaxValue, paddedContainer.Rect.Height - bottomFrame.Rect.Height - buttonLayout.Rect.Height);
}
private bool VisibleOnItemFinder(Item it)
{
if (it.Submarine != item.Submarine) { return false; }
if (it.NonInteractable || it.HiddenInGame) { return false; }
if (it.GetComponent<Pickable>() == null) { return false; }
var holdable = it.GetComponent<Holdable>();
if (holdable != null && holdable.Attached) { return false; }
var wire = it.GetComponent<Wire>();
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
if (it.HasTag("traitormissionitem")) { return false; }
return true;
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
@@ -546,10 +567,7 @@ namespace Barotrauma.Items.Components
// is there a better way to do this? // is there a better way to do this?
if (GuiFrame.Rect.Size != elementSize) if (GuiFrame.Rect.Size != elementSize)
{ {
if (item.Submarine is { } sub) CreateGUI();
{
BakeSubmarine(sub, miniMapFrame.Rect);
}
elementSize = GuiFrame.Rect.Size; elementSize = GuiFrame.Rect.Size;
} }
@@ -782,10 +800,7 @@ namespace Barotrauma.Items.Components
foreach (Item it in Item.ItemList) foreach (Item it in Item.ItemList)
{ {
if (it.Submarine != item.Submarine) { continue; } if (!VisibleOnItemFinder(it)) { continue; }
if (it.HiddenInGame || it.NonInteractable) { continue; }
if (it.GetComponent<Wire>() is { Connections: { } conn} && conn.Any()) { continue; }
if (it.HasTag("traitormissionitem")) { continue; }
if (it.Prefab == searchedPrefab) if (it.Prefab == searchedPrefab)
{ {
@@ -794,7 +809,7 @@ namespace Barotrauma.Items.Components
if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent) if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent)
{ {
foundItems.Add((Item) parent.Owner); foundItems.Add((Item)parent.Owner);
} }
else else
{ {
@@ -1165,7 +1180,7 @@ namespace Barotrauma.Items.Components
if (entity is Item it) if (entity is Item it)
{ {
if (it.GetComponent<Holdable>() != null || it.ParentInventory != null) { continue; } if (it.GetComponent<Pickable>() != null || it.ParentInventory != null) { continue; }
DrawItem(spriteBatch, it, parentRect, worldBorders, inflate); DrawItem(spriteBatch, it, parentRect, worldBorders, inflate);
} }
} }
@@ -0,0 +1,21 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Quality : ItemComponent
{
public override void AddTooltipInfo(ref string name, ref string description)
{
foreach (var statValue in statValues)
{
int roundedValue = (int)Math.Round(statValue.Value * qualityLevel * 100);
if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}%‖color:end‖ {TextManager.Get("qualitystattypenames." + statValue.Key.ToString(), true) ?? statValue.Key.ToString()}";
}
}
}
}
@@ -51,7 +51,7 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) public override bool ShouldDrawHUD(Character character)
{ {
if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false; if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false;
return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))) || CanTinker(character); return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))) || IsTinkerable(character);
} }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
@@ -162,7 +162,7 @@ namespace Barotrauma.Items.Components
tinkerButtonText = "Tinker"; tinkerButtonText = "Tinker";
tinkeringText = "Tinkering"; tinkeringText = "Tinkering";
TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText, style: "GUIButtonSmall") TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText)
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false, Visible = false,
@@ -254,7 +254,7 @@ namespace Barotrauma.Items.Components
progressBarOverlayText.Visible = false; progressBarOverlayText.Visible = false;
} }
RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition; RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition && item.ConditionPercentage < RepairThreshold;
RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
repairButtonText : repairButtonText :
repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
@@ -269,7 +269,7 @@ namespace Barotrauma.Items.Components
TinkerButton.Visible = IsTinkerable(character); TinkerButton.Visible = IsTinkerable(character);
TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible; TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible;
TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character); TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character);
TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker && CanTinker(character)) ? TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker) ?
tinkerButtonText : tinkerButtonText :
tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
@@ -326,6 +326,7 @@ namespace Barotrauma.Items.Components
deteriorateAlwaysResetTimer = msg.ReadSingle(); deteriorateAlwaysResetTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean(); DeteriorateAlways = msg.ReadBoolean();
tinkeringDuration = msg.ReadSingle(); tinkeringDuration = msg.ReadSingle();
tinkeringStrength = msg.ReadSingle();
ushort currentFixerID = msg.ReadUInt16(); ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
@@ -316,11 +316,12 @@ namespace Barotrauma
string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White);
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (item.Quality > 0) if (item.Quality > 0)
{ {
name = TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", name, fallBackTag: "itemname.quality3"); // substring by to get rid of the empty space at start, text file should be adjusted
toolTip += $"\n{TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", "", fallBackTag: "itemname.quality3")?.Substring(1)}";
} }
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))
{ {
@@ -1653,9 +1654,9 @@ namespace Barotrauma
scale: iconSize.X / stealIcon.size.X); scale: iconSize.X / stealIcon.size.X);
} }
int maxStackSize = item.Prefab.MaxStackSize; int maxStackSize = item.Prefab.MaxStackSize;
if (item.Container != null) if (inventory is ItemInventory itemInventory)
{ {
maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent<ItemContainer>()?.GetMaxStackSize(slotIndex) ?? maxStackSize); maxStackSize = Math.Min(maxStackSize, itemInventory.Container.GetMaxStackSize(slotIndex));
} }
if (maxStackSize > 1 && inventory != null) if (maxStackSize > 1 && inventory != null)
{ {
@@ -265,7 +265,7 @@ namespace Barotrauma
if (!currentDisplayLocation.Discovered) if (!currentDisplayLocation.Discovered)
{ {
RemoveFogOfWar(currentDisplayLocation); RemoveFogOfWar(currentDisplayLocation);
currentDisplayLocation.Discovered = true; currentDisplayLocation.Discover();
if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{ {
furthestDiscoveredLocation = currentDisplayLocation; furthestDiscoveredLocation = currentDisplayLocation;
@@ -426,7 +426,7 @@ namespace Barotrauma
Level.Loaded.DebugSetStartLocation(CurrentLocation); Level.Loaded.DebugSetStartLocation(CurrentLocation);
Level.Loaded.DebugSetEndLocation(null); Level.Loaded.DebugSetEndLocation(null);
CurrentLocation.Discovered = true; CurrentLocation.Discover();
OnLocationChanged?.Invoke(prevLocation, CurrentLocation); OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
SelectLocation(-1); SelectLocation(-1);
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -2755,6 +2755,9 @@ namespace Barotrauma.Networking
msg.Write((byte)characterInfo.BeardIndex); msg.Write((byte)characterInfo.BeardIndex);
msg.Write((byte)characterInfo.MoustacheIndex); msg.Write((byte)characterInfo.MoustacheIndex);
msg.Write((byte)characterInfo.FaceAttachmentIndex); msg.Write((byte)characterInfo.FaceAttachmentIndex);
msg.WriteColorR8G8B8(characterInfo.SkinColor);
msg.WriteColorR8G8B8(characterInfo.HairColor);
msg.WriteColorR8G8B8(characterInfo.FacialHairColor);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
int count = Math.Min(jobPreferences.Count, 3); int count = Math.Min(jobPreferences.Count, 3);
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
namespace Barotrauma
{
abstract class CampaignSetupUI
{
protected readonly GUIComponent newGameContainer, loadGameContainer;
protected GUIListBox subList;
protected GUIListBox saveList;
protected List<GUITickBox> subTickBoxes;
protected GUITextBox saveNameBox, seedBox;
protected GUILayoutGroup subPreviewContainer;
protected GUIButton loadGameButton;
public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
public Action<string> LoadGame;
protected enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 };
protected CategoryFilter subFilter = CategoryFilter.All;
public GUIButton StartButton
{
get;
protected set;
}
public GUITextBlock InitialMoneyText
{
get;
protected set;
}
public GUITickBox EnableRadiationToggle { get; set; }
public GUILayoutGroup CampaignSettingsContent { get; set; }
public GUIButton CampaignCustomizeButton { get; set; }
public GUIMessageBox CampaignCustomizeSettings { get; set; }
public GUITextBlock MaxMissionCountText;
public CampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer)
{
this.newGameContainer = newGameContainer;
this.loadGameContainer = loadGameContainer;
}
}
}
@@ -0,0 +1,521 @@
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using System.Globalization;
using Barotrauma.Extensions;
namespace Barotrauma
{
class MultiPlayerCampaignSetupUI : CampaignSetupUI
{
private GUIButton deleteMpSaveButton;
public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
: base(newGameContainer, loadGameContainer)
{
var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.0f
};
var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform))
{
Stretch = true,
RelativeSpacing = 0.015f
};
var rightColumn = new GUILayoutGroup(new RectTransform(Vector2.Zero, columnContainer.RectTransform))
{
Stretch = true,
RelativeSpacing = 0.015f
};
columnContainer.Recalculate();
// New game left side
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUI.SubHeadingFont);
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
{
textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); }
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont);
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
// Spacing to fix the multiplayer campaign setup layout
CreateMultiplayerCampaignSubList(leftColumn.RectTransform);
//spacing
//new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null);
// New game right side
subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform))
{
Stretch = true
};
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f),
leftColumn.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true);
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton"))
{
OnClicked = (GUIButton btn, object userData) =>
{
if (string.IsNullOrWhiteSpace(saveNameBox.Text))
{
saveNameBox.Flash(GUI.Style.Red);
return false;
}
SubmarineInfo selectedSub = null;
if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; }
selectedSub = GameMain.NetLobbyScreen.SelectedSub;
if (selectedSub.SubmarineClass == SubmarineClass.Undefined)
{
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected"));
return false;
}
if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
{
((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f;
subList.SelectedComponent.CanBeFocused = false;
subList.Deselect();
return false;
}
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings();
settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled();
settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount();
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{
if (!hasRequiredContentPackages)
{
var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"),
TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = msgBox.Close;
msgBox.Buttons[0].OnClicked += (button, obj) =>
{
if (GUIMessageBox.MessageBoxes.Count == 0)
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
return true;
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
if (selectedSub.HasTag(SubmarineTag.Shuttle))
{
var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"),
TextManager.Get("ShuttleWarning"),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = (button, obj) =>
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
return false;
}
}
else
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
return true;
}
};
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUI.Style.SmallFont, textColor: GUI.Style.Green)
{
TextGetter = () =>
{
int initialMoney = CampaignMode.InitialMoney;
if (GameMain.NetLobbyScreen.SelectedSub != null)
{
initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price;
}
initialMoney = Math.Max(initialMoney, MultiPlayerCampaign.MinimumInitialMoney);
return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney));
}
};
columnContainer.Recalculate();
leftColumn.Recalculate();
rightColumn.Recalculate();
if (submarines != null) { UpdateSubList(submarines); }
UpdateLoadMenu(saveFiles);
}
private void CreateMultiplayerCampaignSubList(RectTransform parent)
{
GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent))
{
RelativeSpacing = 0.005f,
Stretch = true
};
var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont);
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true)
{
Stretch = true
};
var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font);
var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true);
filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize;
searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
searchBox.OnTextChanged += (textBox, text) =>
{
foreach (GUIComponent child in subList.Content.Children)
{
if (!(child.UserData is SubmarineInfo sub)) { continue; }
child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower());
}
return true;
};
subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform));
subTickBoxes = new List<GUITickBox>();
for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++)
{
SubmarineInfo sub = GameMain.Client.ServerSubmarines[i];
if (!sub.IsCampaignCompatible) continue;
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) },
style: "ListBoxElement")
{
ToolTip = sub.Description,
UserData = sub
};
int buttonSize = (int)(frame.Rect.Height * 0.8f);
GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65))
{
UserData = sub,
OnSelected = (GUITickBox box) =>
{
GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected);
return true;
}
};
subTickBoxes.Add(tickBox);
tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub);
frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y);
var subTextBlock = tickBox.TextBlock;
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash);
if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name);
if (matchingSub == null)
{
subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f);
frame.ToolTip = TextManager.Get("SubNotFound");
}
else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash)
{
subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f);
frame.ToolTip = TextManager.Get("SubDoesntMatch");
}
if (!sub.RequiredContentPackagesInstalled)
{
subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f);
frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip;
}
var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight),
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{
TextColor = subTextBlock.TextColor * 0.8f,
ToolTip = subTextBlock.RawToolTip
};
}
}
public void RefreshMultiplayerCampaignSubUI(List<SubmarineInfo> campaignSubs)
{
for (int i = 0; i < subTickBoxes.Count; i++)
{
subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo);
}
}
private IEnumerable<object> WaitForCampaignSetup()
{
GUI.SetCursorWaiting();
string headerText = TextManager.Get("CampaignStartingPleaseWait");
var msgBox = new GUIMessageBox(headerText, TextManager.Get("CampaignStarting"), new string[] { TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
GameMain.NetLobbyScreen.HighlightMode(GameMain.NetLobbyScreen.SelectedModeIndex);
GameMain.NetLobbyScreen.SelectMode(GameMain.NetLobbyScreen.SelectedModeIndex);
GUI.ClearCursorWait();
CoroutineManager.StopCoroutines("WaitForCampaignSetup");
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20);
while (Screen.Selected != GameMain.GameScreen && DateTime.Now < timeOut)
{
msgBox.Header.Text = headerText + new string('.', (int)Timing.TotalTime % 3 + 1);
yield return CoroutineStatus.Running;
}
msgBox.Close();
GUI.ClearCursorWait();
yield return CoroutineStatus.Success;
}
public void UpdateSubList(IEnumerable<SubmarineInfo> submarines)
{
List<SubmarineInfo> subsToShow;
string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList();
subsToShow.Sort((s1, s2) =>
{
int p1 = s1.Price > CampaignMode.InitialMoney ? 10 : 0;
int p2 = s2.Price > CampaignMode.InitialMoney ? 10 : 0;
return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name);
});
subList.ClearChildren();
foreach (SubmarineInfo sub in subsToShow)
{
var textBlock = new GUITextBlock(
new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Rect.Width - 65), style: "ListBoxElement")
{
ToolTip = sub.Description,
UserData = sub
};
if (!sub.RequiredContentPackagesInstalled)
{
textBlock.TextColor = Color.Lerp(textBlock.TextColor, Color.DarkRed, .5f);
textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.RawToolTip;
}
var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight),
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{
TextColor = sub.Price > CampaignMode.InitialMoney ? GUI.Style.Red : textBlock.TextColor * 0.8f,
ToolTip = textBlock.ToolTip
};
#if !DEBUG
if (!GameMain.DebugDraw)
{
if (sub.Price > CampaignMode.InitialMoney || !sub.IsCampaignCompatible)
{
textBlock.CanBeFocused = false;
textBlock.TextColor *= 0.5f;
}
}
#endif
}
if (SubmarineInfo.SavedSubmarines.Any())
{
var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignMode.InitialMoney).ToList();
if (validSubs.Count > 0)
{
subList.Select(validSubs[Rand.Int(validSubs.Count)]);
}
}
}
private List<string> prevSaveFiles;
public void UpdateLoadMenu(IEnumerable<string> saveFiles = null)
{
prevSaveFiles?.Clear();
prevSaveFiles = null;
loadGameContainer.ClearChildren();
if (saveFiles == null)
{
saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer);
}
var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.85f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter)
{
Stretch = true,
RelativeSpacing = 0.03f
};
saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform))
{
OnSelected = SelectSaveFile
};
foreach (string saveFile in saveFiles)
{
string fileName = saveFile;
string subName = "";
string saveTime = "";
string contentPackageStr = "";
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement")
{
UserData = saveFile
};
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), "")
{
CanBeFocused = false
};
bool isCompatible = true;
prevSaveFiles ??= new List<string>();
prevSaveFiles?.Add(saveFile);
string[] splitSaveFile = saveFile.Split(';');
saveFrame.UserData = splitSaveFile[0];
fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]);
if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; }
if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; }
if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; }
if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime))
{
DateTime time = ToolBox.Epoch.ToDateTime(unixTime);
saveTime = time.ToString();
}
if (!string.IsNullOrEmpty(contentPackageStr))
{
List<string> contentPackagePaths = contentPackageStr.Split('|').ToList();
if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out string errorMsg))
{
nameText.TextColor = GUI.Style.Red;
saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning"));
}
}
if (!isCompatible)
{
nameText.TextColor = GUI.Style.Red;
saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave");
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft),
text: subName, font: GUI.SmallFont)
{
CanBeFocused = false,
UserData = fileName
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTime, textAlignment: Alignment.Right, font: GUI.SmallFont)
{
CanBeFocused = false,
UserData = fileName
};
}
saveList.Content.RectTransform.SortChildren((c1, c2) =>
{
string file1 = c1.GUIComponent.UserData as string;
string file2 = c2.GUIComponent.UserData as string;
DateTime file1WriteTime = DateTime.MinValue;
DateTime file2WriteTime = DateTime.MinValue;
try
{
file1WriteTime = File.GetLastWriteTime(file1);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
try
{
file2WriteTime = File.GetLastWriteTime(file2);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
return file2WriteTime.CompareTo(file1WriteTime);
});
loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"))
{
OnClicked = (btn, obj) =>
{
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
return true;
},
Enabled = false
};
deleteMpSaveButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomLeft),
TextManager.Get("Delete"), style: "GUIButtonSmall")
{
OnClicked = DeleteSave,
Visible = false
};
}
private bool SelectSaveFile(GUIComponent component, object obj)
{
string fileName = (string)obj;
loadGameButton.Enabled = true;
deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner;
deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName;
if (deleteMpSaveButton.Visible)
{
deleteMpSaveButton.UserData = obj as string;
}
return true;
}
private bool DeleteSave(GUIButton button, object obj)
{
string saveFile = obj as string;
if (obj == null) { return false; }
string header = TextManager.Get("deletedialoglabel");
string body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile));
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
}
}
@@ -10,58 +10,108 @@ using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
class CampaignSetupUI class SinglePlayerCampaignSetupUI : CampaignSetupUI
{ {
private readonly GUIComponent newGameContainer, loadGameContainer; public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; }
private GUIListBox subList; private GUIButton nextButton;
private GUIListBox saveList; private GUILayoutGroup characterInfoColumns;
private List<GUITickBox> subTickBoxes;
private readonly GUITextBox saveNameBox, seedBox; public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
: base(newGameContainer, loadGameContainer)
private readonly GUILayoutGroup subPreviewContainer;
private GUIButton loadGameButton, deleteMpSaveButton;
public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
public Action<string> LoadGame;
private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 };
private CategoryFilter subFilter = CategoryFilter.All;
public GUIButton StartButton
{ {
get; UpdateNewGameMenu(submarines);
private set; UpdateLoadMenu(saveFiles);
} }
public GUITextBlock InitialMoneyText private int currentPage = 0;
private GUIListBox pageContainer;
public void Update()
{ {
get; float targetScroll =
private set; (float)currentPage / ((float)pageContainer.Content.CountChildren - 1);
pageContainer.BarScroll = MathHelper.Lerp(pageContainer.BarScroll, targetScroll, 0.2f);
if (MathUtils.NearlyEqual(pageContainer.BarScroll, targetScroll, 0.001f))
{
pageContainer.BarScroll = targetScroll;
}
for (int i=0; i<CharacterMenus.Length; i++)
{
CharacterMenus[i]?.Update();
}
pageContainer.HoverCursor = CursorState.Default;
pageContainer.Content.HoverCursor = CursorState.Default;
} }
public GUITickBox EnableRadiationToggle { get; set; } public void SetPage(int pageIndex)
public GUILayoutGroup CampaignSettingsContent { get; set; }
public GUIButton CampaignCustomizeButton { get; set; }
public GUIMessageBox CampaignCustomizeSettings { get; set; }
public GUITextBlock MaxMissionCountText;
private readonly bool isMultiplayer;
public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
{ {
this.isMultiplayer = isMultiplayer; currentPage = pageIndex;
this.newGameContainer = newGameContainer; for (int i = 0; i < pageContainer.Content.CountChildren; i++)
this.loadGameContainer = loadGameContainer; {
var child = pageContainer.Content.GetChild(i);
child.CanBeFocused = (i == currentPage);
child.GetAllChildren().ForEach(c =>
{
if (c is GUIDropDown dd)
{
dd.Dropped = false;
}
c.CanBeFocused = (i == currentPage);
});
}
var previewListBox = subPreviewContainer.GetAllChildren<GUIListBox>().FirstOrDefault();
previewListBox?.GetAllChildren()?.ForEach(c =>
{
c.CanBeFocused = false;
});
}
var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) private void UpdateNewGameMenu(IEnumerable<SubmarineInfo> submarines)
{
pageContainer =
new GUIListBox(new RectTransform(Vector2.One, newGameContainer.RectTransform), style: null, isHorizontal: true)
{
ScrollBarEnabled = false,
ScrollBarVisible = false,
HoverCursor = CursorState.Default
};
GUILayoutGroup createPageLayout()
{
var containerItem =
new GUIFrame(new RectTransform(Vector2.One, pageContainer.Content.RectTransform), style: null);
return new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, containerItem.RectTransform,
Anchor.Center));
}
CreateFirstPage(createPageLayout(), submarines);
CreateSecondPage(createPageLayout());
pageContainer.RecalculateChildren();
pageContainer.GetAllChildren().ForEach(c =>
{
c.ClampMouseRectToParent = true;
});
pageContainer.GetAllChildren<GUIDropDown>().ForEach(dd =>
{
dd.ListBox.ClampMouseRectToParent = false;
dd.ListBox.Content.ClampMouseRectToParent = false;
});
SetPage(0);
}
private void CreateFirstPage(GUILayoutGroup firstPageLayout, IEnumerable<SubmarineInfo> submarines)
{
firstPageLayout.RelativeSpacing = 0.02f;
var columnContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), firstPageLayout.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = isMultiplayer ? 0.0f : 0.02f RelativeSpacing = 0.02f
}; };
var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform))
@@ -70,7 +120,7 @@ namespace Barotrauma
RelativeSpacing = 0.015f RelativeSpacing = 0.015f
}; };
var rightColumn = new GUILayoutGroup(new RectTransform(isMultiplayer ? Vector2.Zero : new Vector2(1.5f, 1.0f), columnContainer.RectTransform)) var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.5f, 1.0f), columnContainer.RectTransform))
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.015f RelativeSpacing = 0.015f
@@ -88,47 +138,37 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont);
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
if (!isMultiplayer) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont);
var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3);
moddedDropdown.AddItem(TextManager.Get("clientpermission.all"), CategoryFilter.All);
moddedDropdown.AddItem(TextManager.Get("servertag.modded.false"), CategoryFilter.Vanilla);
moddedDropdown.AddItem(TextManager.Get("customrank"), CategoryFilter.Custom);
moddedDropdown.Select(0);
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont); Stretch = true
};
var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3); subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true };
moddedDropdown.AddItem(TextManager.Get("clientpermission.all"), CategoryFilter.All);
moddedDropdown.AddItem(TextManager.Get("servertag.modded.false"), CategoryFilter.Vanilla);
moddedDropdown.AddItem(TextManager.Get("customrank"), CategoryFilter.Custom);
moddedDropdown.Select(0);
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font);
{ var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true);
Stretch = true filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize;
}; searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true }; moddedDropdown.OnSelected = (component, data) =>
var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font);
var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true);
filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize;
searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
moddedDropdown.OnSelected = (component, data) =>
{
searchBox.Text = string.Empty;
subFilter = (CategoryFilter)data;
UpdateSubList(SubmarineInfo.SavedSubmarines);
return true;
};
subList.OnSelected = OnSubSelected;
}
else // Spacing to fix the multiplayer campaign setup layout
{ {
CreateMultiplayerCampaignSubList(leftColumn.RectTransform); searchBox.Text = string.Empty;
subFilter = (CategoryFilter)data;
UpdateSubList(SubmarineInfo.SavedSubmarines);
return true;
};
//spacing subList.OnSelected = OnSubSelected;
//new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null);
}
// New game right side // New game right side
subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform))
@@ -136,174 +176,160 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), var firstPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
(isMultiplayer ? leftColumn : rightColumn).RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); firstPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
if (!isMultiplayer) { buttonContainer.IgnoreLayoutGroups = true; }
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton"))
{ {
OnClicked = (GUIButton btn, object userData) => RelativeSpacing = 0.025f
{
if (string.IsNullOrWhiteSpace(saveNameBox.Text))
{
saveNameBox.Flash(GUI.Style.Red);
return false;
}
SubmarineInfo selectedSub = null;
if (!isMultiplayer)
{
if (!(subList.SelectedData is SubmarineInfo)) { return false; }
selectedSub = subList.SelectedData as SubmarineInfo;
}
else
{
if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; }
selectedSub = GameMain.NetLobbyScreen.SelectedSub;
}
if (selectedSub.SubmarineClass == SubmarineClass.Undefined)
{
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected"));
return false;
}
if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
{
((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f;
subList.SelectedComponent.CanBeFocused = false;
subList.Deselect();
return false;
}
string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings();
if (isMultiplayer)
{
settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled();
settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount();
}
else
{
settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false;
if (MaxMissionCountText != null && Int32.TryParse(MaxMissionCountText.Text, out int missionCount))
{
settings.MaxMissionCount = missionCount;
}
else
{
settings.MaxMissionCount = CampaignSettings.DefaultMaxMissionCount;
}
}
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{
if (!hasRequiredContentPackages)
{
var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"),
TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = msgBox.Close;
msgBox.Buttons[0].OnClicked += (button, obj) =>
{
if (GUIMessageBox.MessageBoxes.Count == 0)
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer)
{
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
}
return true;
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
if (selectedSub.HasTag(SubmarineTag.Shuttle))
{
var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"),
TextManager.Get("ShuttleWarning"),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = (button, obj) =>
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer)
{
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
return false;
}
}
else
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer)
{
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
}
return true;
}
}; };
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(isMultiplayer ? 0.6f : 0.3f, 1f), buttonContainer.RectTransform), "", font: isMultiplayer ? GUI.Style.SmallFont : GUI.Style.Font, textColor: GUI.Style.Green) InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1f), firstPageButtonContainer.RectTransform), "", font: GUI.Style.Font, textColor: GUI.Style.Green, textAlignment: Alignment.CenterLeft)
{ {
TextGetter = () => TextGetter = () =>
{ {
int initialMoney = CampaignMode.InitialMoney; int initialMoney = CampaignMode.InitialMoney;
if (isMultiplayer) if (subList.SelectedData is SubmarineInfo subInfo)
{
if (GameMain.NetLobbyScreen.SelectedSub != null)
{
initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price;
}
}
else if (subList.SelectedData is SubmarineInfo subInfo)
{ {
initialMoney -= subInfo.Price; initialMoney -= subInfo.Price;
} }
initialMoney = Math.Max(initialMoney, isMultiplayer ? MultiPlayerCampaign.MinimumInitialMoney : 0); initialMoney = Math.Max(initialMoney, 0);
return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney));
} }
}; };
if (!isMultiplayer) CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), firstPageButtonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton"))
{ {
CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton")) OnClicked = (tb, userdata) =>
{ {
OnClicked = (tb, userdata) => CreateCustomizeWindow();
{ return true;
CreateCustomizeWindow(); }
return true; };
}
};
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton") nextButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), firstPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Next"))
{
OnClicked = (GUIButton btn, object userData) =>
{ {
IgnoreLayoutGroups = true, SetPage(1);
OnClicked = (btn, userdata) => { GameMain.Instance.ShowCampaignDisclaimer(); return true; } return false;
}; }
disclaimerBtn.RectTransform.MaxSize = new Point((int)(30 * GUI.Scale)); };
}
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton")
{
IgnoreLayoutGroups = true,
OnClicked = (btn, userdata) => { GameMain.Instance.ShowCampaignDisclaimer(); return true; }
};
disclaimerBtn.RectTransform.MaxSize = new Point((int)(30 * GUI.Scale));
columnContainer.Recalculate(); columnContainer.Recalculate();
leftColumn.Recalculate(); leftColumn.Recalculate();
rightColumn.Recalculate(); rightColumn.Recalculate();
if (submarines != null) { UpdateSubList(submarines); } if (submarines != null) { UpdateSubList(submarines); }
UpdateLoadMenu(saveFiles); }
private void CreateSecondPage(GUILayoutGroup secondPageLayout)
{
secondPageLayout.RelativeSpacing = 0.01f;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.04f), secondPageLayout.RectTransform),
TextManager.Get("Crew"), font: GUI.Style.SubHeadingFont, textAlignment: Alignment.TopLeft);
characterInfoColumns = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.01f
};
var secondPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
secondPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
{
RelativeSpacing = 0.2f
};
var backButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), secondPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Back"))
{
OnClicked = (GUIButton btn, object userData) =>
{
SetPage(0);
return false;
}
};
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), secondPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton"))
{
OnClicked = FinishSetup
};
}
public void RandomizeCrew()
{
var characterInfos = new List<(CharacterInfo Info, JobPrefab Job)>();
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
characterInfos.Add((new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant), jobPrefab));
}
}
characterInfoColumns.ClearChildren();
CharacterMenus?.ForEach(m => m.Dispose());
CharacterMenus = new CharacterInfo.AppearanceCustomizationMenu[characterInfos.Count];
for (int i = 0; i < characterInfos.Count; i++)
{
var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f / characterInfos.Count, 1.0f),
characterInfoColumns.RectTransform));
var (characterInfo, job) = characterInfos[i];
characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.275f), subLayout.RectTransform));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), subLayout.RectTransform), job.Name, job.UIColor);
var characterName = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), subLayout.RectTransform))
{
Text = characterInfo.Name,
UserData = "random"
};
characterName.OnDeselected += (sender, key) =>
{
if (string.IsNullOrWhiteSpace(sender.Text))
{
characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced);
sender.Text = characterInfo.Name;
sender.UserData = "random";
}
else
{
characterInfo.Name = sender.Text;
sender.UserData = "user";
}
};
characterName.OnEnterPressed += (sender, text) =>
{
sender.Deselect();
return false;
};
var customizationFrame =
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), subLayout.RectTransform), style: null);
CharacterMenus[i] =
new CharacterInfo.AppearanceCustomizationMenu(characterInfo, customizationFrame, hasIcon: false)
{
OnHeadSwitch = menu =>
{
if (characterName.UserData is string ud && ud == "random")
{
characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced);
characterName.Text = characterInfo.Name;
characterName.UserData = "random";
}
}
};
}
} }
private void CreateCustomizeWindow() private void CreateCustomizeWindow()
@@ -355,104 +381,91 @@ namespace Barotrauma
maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip); maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip);
} }
private void CreateMultiplayerCampaignSubList(RectTransform parent) private bool FinishSetup(GUIButton btn, object userdata)
{ {
GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent)) if (string.IsNullOrWhiteSpace(saveNameBox.Text))
{ {
RelativeSpacing = 0.005f, saveNameBox.Flash(GUI.Style.Red);
Stretch = true return false;
}; }
var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont); SubmarineInfo selectedSub = null;
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true) if (!(subList.SelectedData is SubmarineInfo)) { return false; }
selectedSub = subList.SelectedData as SubmarineInfo;
if (selectedSub.SubmarineClass == SubmarineClass.Undefined)
{ {
Stretch = true new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected"));
}; return false;
var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font); }
var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true);
filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize; if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
searchBox.OnTextChanged += (textBox, text) =>
{ {
foreach (GUIComponent child in subList.Content.Children) ((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f;
subList.SelectedComponent.CanBeFocused = false;
subList.Deselect();
return false;
}
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings();
settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false;
if (MaxMissionCountText != null && Int32.TryParse(MaxMissionCountText.Text, out int missionCount))
{
settings.MaxMissionCount = missionCount;
}
else
{
settings.MaxMissionCount = CampaignSettings.DefaultMaxMissionCount;
}
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{
if (!hasRequiredContentPackages)
{ {
if (!(child.UserData is SubmarineInfo sub)) { continue; } var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"),
child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower()); TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)),
} new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
return true;
};
subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform)); msgBox.Buttons[0].OnClicked = msgBox.Close;
subTickBoxes = new List<GUITickBox>(); msgBox.Buttons[0].OnClicked += (button, obj) =>
for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++)
{
SubmarineInfo sub = GameMain.Client.ServerSubmarines[i];
if (!sub.IsCampaignCompatible) continue;
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) },
style: "ListBoxElement")
{
ToolTip = sub.Description,
UserData = sub
};
int buttonSize = (int)(frame.Rect.Height * 0.8f);
GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65))
{
UserData = sub,
OnSelected = (GUITickBox box) =>
{ {
GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected); if (GUIMessageBox.MessageBoxes.Count == 0)
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
}
return true; return true;
} };
};
subTickBoxes.Add(tickBox);
tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub);
frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y); msgBox.Buttons[1].OnClicked = msgBox.Close;
var subTextBlock = tickBox.TextBlock;
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash);
if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name);
if (matchingSub == null)
{
subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f);
frame.ToolTip = TextManager.Get("SubNotFound");
}
else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash)
{
subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f);
frame.ToolTip = TextManager.Get("SubDoesntMatch");
} }
if (!sub.RequiredContentPackagesInstalled) if (selectedSub.HasTag(SubmarineTag.Shuttle))
{ {
subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f); var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"),
frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip; TextManager.Get("ShuttleWarning"),
} new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), msgBox.Buttons[0].OnClicked = (button, obj) =>
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) {
{ StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
TextColor = subTextBlock.TextColor * 0.8f, return true;
ToolTip = subTextBlock.RawToolTip };
}; msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
return false;
}
} }
} else
public void RefreshMultiplayerCampaignSubUI(List<SubmarineInfo> campaignSubs)
{
for (int i = 0; i < subTickBoxes.Count; i++)
{ {
subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
} }
return true;
} }
public void RandomizeSeed() public void RandomizeSeed()
@@ -478,54 +491,28 @@ namespace Barotrauma
if (!(obj is SubmarineInfo sub)) { return true; } if (!(obj is SubmarineInfo sub)) { return true; }
#if !DEBUG #if !DEBUG
if (!isMultiplayer && sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw) if (sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw)
{ {
StartButton.Enabled = false; SetPage(0);
nextButton.Enabled = false;
return false; return false;
} }
#endif #endif
StartButton.Enabled = true; nextButton.Enabled = true;
sub.CreatePreviewWindow(subPreviewContainer); sub.CreatePreviewWindow(subPreviewContainer);
return true; return true;
} }
private IEnumerable<object> WaitForCampaignSetup()
{
GUI.SetCursorWaiting();
string headerText = TextManager.Get("CampaignStartingPleaseWait");
var msgBox = new GUIMessageBox(headerText, TextManager.Get("CampaignStarting"), new string[] { TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
GameMain.NetLobbyScreen.HighlightMode(GameMain.NetLobbyScreen.SelectedModeIndex);
GameMain.NetLobbyScreen.SelectMode(GameMain.NetLobbyScreen.SelectedModeIndex);
GUI.ClearCursorWait();
CoroutineManager.StopCoroutines("WaitForCampaignSetup");
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20);
while (Screen.Selected != GameMain.GameScreen && DateTime.Now < timeOut)
{
msgBox.Header.Text = headerText + new string('.', (int)Timing.TotalTime % 3 + 1);
yield return CoroutineStatus.Running;
}
msgBox.Close();
GUI.ClearCursorWait();
yield return CoroutineStatus.Success;
}
public void CreateDefaultSaveName() public void CreateDefaultSaveName()
{ {
string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer);
saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath); saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath);
} }
public void UpdateSubList(IEnumerable<SubmarineInfo> submarines) public void UpdateSubList(IEnumerable<SubmarineInfo> submarines)
{ {
List<SubmarineInfo> subsToShow; List<SubmarineInfo> subsToShow;
if (!isMultiplayer && subFilter != CategoryFilter.All) if (subFilter != CategoryFilter.All)
{ {
subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList(); subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList();
} }
@@ -596,10 +583,10 @@ namespace Barotrauma
if (saveFiles == null) if (saveFiles == null)
{ {
saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
} }
var leftColumn = new GUILayoutGroup(new RectTransform(isMultiplayer ? new Vector2(1.0f, 0.85f) : new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter) var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.03f RelativeSpacing = 0.03f
@@ -610,26 +597,23 @@ namespace Barotrauma
OnSelected = SelectSaveFile OnSelected = SelectSaveFile
}; };
if (!isMultiplayer) new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder"))
{ {
new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder")) OnClicked = (btn, userdata) =>
{ {
OnClicked = (btn, userdata) => try
{ {
try ToolBox.OpenFileWithShell(SaveUtil.SaveFolder);
{
ToolBox.OpenFileWithShell(SaveUtil.SaveFolder);
}
catch (Exception e)
{
new GUIMessageBox(
TextManager.Get("error"),
TextManager.GetWithVariables("showinfoldererror", new string[] { "[folder]", "[errormessage]" }, new string[] { SaveUtil.SaveFolder, e.Message }));
}
return true;
} }
}; catch (Exception e)
} {
new GUIMessageBox(
TextManager.Get("error"),
TextManager.GetWithVariables("showinfoldererror", new string[] { "[folder]", "[errormessage]" }, new string[] { SaveUtil.SaveFolder, e.Message }));
}
return true;
}
};
foreach (string saveFile in saveFiles) foreach (string saveFile in saveFiles)
{ {
@@ -649,39 +633,27 @@ namespace Barotrauma
bool isCompatible = true; bool isCompatible = true;
prevSaveFiles ??= new List<string>(); prevSaveFiles ??= new List<string>();
if (!isMultiplayer)
{
nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
if (doc?.Root == null) nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
{ XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUI.Style.Red; if (doc?.Root == null)
continue;
}
if (doc.Root.GetChildElement("multiplayercampaign") != null)
{
//multiplayer campaign save in the wrong folder -> don't show the save
saveList.Content.RemoveChild(saveFrame);
continue;
}
subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", "");
isCompatible = SaveUtil.IsSaveFileCompatible(doc);
contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", "");
prevSaveFiles?.Add(saveFile);
}
else
{ {
prevSaveFiles?.Add(saveFile); DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
string[] splitSaveFile = saveFile.Split(';'); nameText.TextColor = GUI.Style.Red;
saveFrame.UserData = splitSaveFile[0]; continue;
fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]);
if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; }
if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; }
if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; }
} }
if (doc.Root.GetChildElement("multiplayercampaign") != null)
{
//multiplayer campaign save in the wrong folder -> don't show the save
saveList.Content.RemoveChild(saveFrame);
continue;
}
subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", "");
isCompatible = SaveUtil.IsSaveFileCompatible(doc);
contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", "");
prevSaveFiles?.Add(saveFile);
if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime)) if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime))
{ {
DateTime time = ToolBox.Epoch.ToDateTime(unixTime); DateTime time = ToolBox.Epoch.ToDateTime(unixTime);
@@ -748,38 +720,16 @@ namespace Barotrauma
{ {
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string); LoadGame?.Invoke(saveList.SelectedData as string);
if (isMultiplayer)
{
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
return true; return true;
}, },
Enabled = false Enabled = false
}; };
deleteMpSaveButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomLeft),
TextManager.Get("Delete"), style: "GUIButtonSmall")
{
OnClicked = DeleteSave,
Visible = false
};
} }
private bool SelectSaveFile(GUIComponent component, object obj) private bool SelectSaveFile(GUIComponent component, object obj)
{ {
string fileName = (string)obj; string fileName = (string)obj;
if (isMultiplayer)
{
loadGameButton.Enabled = true;
deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner;
deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName;
if (deleteMpSaveButton.Visible)
{
deleteMpSaveButton.UserData = obj as string;
}
return true;
}
XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); XDocument doc = SaveUtil.LoadGameSessionDoc(fileName);
if (doc?.Root == null) if (doc?.Root == null)
{ {
@@ -868,6 +818,5 @@ namespace Barotrauma
} }
loadGameContainer.RemoveChild(prevFrame); loadGameContainer.RemoveChild(prevFrame);
} }
} }
} }
@@ -182,7 +182,7 @@ namespace Barotrauma.CharacterEditor
showSpritesheet = false; showSpritesheet = false;
isFrozen = false; isFrozen = false;
autoFreeze = false; autoFreeze = false;
limbPairEditing = true; limbPairEditing = false;
uniformScaling = true; uniformScaling = true;
lockSpriteOrigin = true; lockSpriteOrigin = true;
lockSpritePosition = false; lockSpritePosition = false;
@@ -1581,7 +1581,6 @@ namespace Barotrauma.CharacterEditor
Character.Controlled = character; Character.Controlled = character;
SetWallCollisions(character.AnimController.forceStanding); SetWallCollisions(character.AnimController.forceStanding);
CreateTextures(); CreateTextures();
limbPairEditing = character.IsHumanoid;
CreateGUI(); CreateGUI();
ClearWidgets(); ClearWidgets();
ClearSelection(); ClearSelection();
@@ -1803,6 +1802,7 @@ namespace Barotrauma.CharacterEditor
{ {
case AnimationType.Walk: case AnimationType.Walk:
case AnimationType.Run: case AnimationType.Run:
case AnimationType.Crouch:
if (!ragdollParams.CanWalk) { continue; } if (!ragdollParams.CanWalk) { continue; }
break; break;
case AnimationType.SwimSlow: case AnimationType.SwimSlow:
@@ -1819,8 +1819,8 @@ namespace Barotrauma.CharacterEditor
{ {
AllFiles.Add(configFilePath); AllFiles.Add(configFilePath);
} }
limbPairEditing = false;
SpawnCharacter(configFilePath, ragdollParams); SpawnCharacter(configFilePath, ragdollParams);
limbPairEditing = false;
limbsToggle.Selected = true; limbsToggle.Selected = true;
recalculateColliderToggle.Selected = true; recalculateColliderToggle.Selected = true;
lockSpriteOriginToggle.Selected = false; lockSpriteOriginToggle.Selected = false;
@@ -2599,11 +2599,15 @@ namespace Barotrauma.CharacterEditor
var layoutGroupAnimation = new GUILayoutGroup(new RectTransform(Vector2.One, animationControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false }; var layoutGroupAnimation = new GUILayoutGroup(new RectTransform(Vector2.One, animationControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false };
var animationSelectionElement = new GUIFrame(new RectTransform(new Point(elementSize.X * 2 - (int)(5 * GUI.xScale), elementSize.Y), layoutGroupAnimation.RectTransform), style: null); var animationSelectionElement = new GUIFrame(new RectTransform(new Point(elementSize.X * 2 - (int)(5 * GUI.xScale), elementSize.Y), layoutGroupAnimation.RectTransform), style: null);
var animationSelectionText = new GUITextBlock(new RectTransform(new Point(elementSize.X, elementSize.Y), animationSelectionElement.RectTransform), GetCharacterEditorTranslation("SelectedAnimation") + ": ", Color.WhiteSmoke, textAlignment: Alignment.Center); var animationSelectionText = new GUITextBlock(new RectTransform(new Point(elementSize.X, elementSize.Y), animationSelectionElement.RectTransform), GetCharacterEditorTranslation("SelectedAnimation") + ": ", Color.WhiteSmoke, textAlignment: Alignment.Center);
animSelection = new GUIDropDown(new RectTransform(new Point((int)(100 * GUI.xScale), elementSize.Y), animationSelectionElement.RectTransform, Anchor.TopRight), elementCount: 4); animSelection = new GUIDropDown(new RectTransform(new Point((int)(100 * GUI.xScale), elementSize.Y), animationSelectionElement.RectTransform, Anchor.TopRight), elementCount: 5);
if (character.AnimController.CanWalk) if (character.AnimController.CanWalk)
{ {
animSelection.AddItem(AnimationType.Walk.ToString(), AnimationType.Walk); animSelection.AddItem(AnimationType.Walk.ToString(), AnimationType.Walk);
animSelection.AddItem(AnimationType.Run.ToString(), AnimationType.Run); animSelection.AddItem(AnimationType.Run.ToString(), AnimationType.Run);
if (character.IsHumanoid)
{
animSelection.AddItem(AnimationType.Crouch.ToString(), AnimationType.Crouch);
}
} }
animSelection.AddItem(AnimationType.SwimSlow.ToString(), AnimationType.SwimSlow); animSelection.AddItem(AnimationType.SwimSlow.ToString(), AnimationType.SwimSlow);
animSelection.AddItem(AnimationType.SwimFast.ToString(), AnimationType.SwimFast); animSelection.AddItem(AnimationType.SwimFast.ToString(), AnimationType.SwimFast);
@@ -2622,25 +2626,15 @@ namespace Barotrauma.CharacterEditor
switch (character.AnimController.ForceSelectAnimationType) switch (character.AnimController.ForceSelectAnimationType)
{ {
case AnimationType.Walk: case AnimationType.Walk:
character.AnimController.forceStanding = true;
character.ForceRun = false;
if (!wallCollisionsEnabled)
{
SetWallCollisions(true);
}
if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run)
{
TeleportTo(spawnPosition);
}
break;
case AnimationType.Run: case AnimationType.Run:
case AnimationType.Crouch:
character.AnimController.forceStanding = true; character.AnimController.forceStanding = true;
character.ForceRun = true; character.ForceRun = character.AnimController.ForceSelectAnimationType == AnimationType.Run;
if (!wallCollisionsEnabled) if (!wallCollisionsEnabled)
{ {
SetWallCollisions(true); SetWallCollisions(true);
} }
if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run) if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run && previousAnim != AnimationType.Crouch)
{ {
TeleportTo(spawnPosition); TeleportTo(spawnPosition);
} }
@@ -3046,21 +3040,24 @@ namespace Barotrauma.CharacterEditor
loadBox.Buttons[1].OnClicked += (btn, data) => loadBox.Buttons[1].OnClicked += (btn, data) =>
{ {
string fileName = Path.GetFileNameWithoutExtension(selectedFile); string fileName = Path.GetFileNameWithoutExtension(selectedFile);
if (character.IsHumanoid) if (character.IsHumanoid && character.AnimController is HumanoidAnimController humanAnimController)
{ {
switch (selectedType) switch (selectedType)
{ {
case AnimationType.Walk: case AnimationType.Walk:
character.AnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName); humanAnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName);
break; break;
case AnimationType.Run: case AnimationType.Run:
character.AnimController.RunParams = HumanRunParams.GetAnimParams(character, fileName); humanAnimController.RunParams = HumanRunParams.GetAnimParams(character, fileName);
break;
case AnimationType.Crouch:
humanAnimController.HumanCrouchParams = HumanCrouchParams.GetAnimParams(character, fileName);
break; break;
case AnimationType.SwimSlow: case AnimationType.SwimSlow:
character.AnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName); humanAnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName);
break; break;
case AnimationType.SwimFast: case AnimationType.SwimFast:
character.AnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName); humanAnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName);
break; break;
default: default:
DebugConsole.ThrowError(GetCharacterEditorTranslation("AnimationTypeNotImplemented").Replace("[type]", selectedType.ToString())); DebugConsole.ThrowError(GetCharacterEditorTranslation("AnimationTypeNotImplemented").Replace("[type]", selectedType.ToString()));
@@ -4152,7 +4149,7 @@ namespace Barotrauma.CharacterEditor
float offset = 0.1f; float offset = 0.1f;
w.refresh = () => w.refresh = () =>
{ {
var refPoint = SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset); var refPoint = SimToScreen(character.AnimController.Collider.SimPosition + GetSimSpaceForward() * offset);
var handMovement = ConvertUnits.ToDisplayUnits(humanGroundedParams.HandMoveAmount); var handMovement = ConvertUnits.ToDisplayUnits(humanGroundedParams.HandMoveAmount);
w.DrawPos = refPoint + new Vector2(handMovement.X * character.AnimController.Dir, handMovement.Y) * Cam.Zoom; w.DrawPos = refPoint + new Vector2(handMovement.X * character.AnimController.Dir, handMovement.Y) * Cam.Zoom;
}; };
@@ -4167,7 +4164,7 @@ namespace Barotrauma.CharacterEditor
{ {
if (w.IsSelected) if (w.IsSelected)
{ {
GUI.DrawLine(sp, w.DrawPos, SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset), GUI.Style.Green); GUI.DrawLine(sp, w.DrawPos, SimToScreen(character.AnimController.Collider.SimPosition + GetSimSpaceForward() * offset), GUI.Style.Green);
} }
}; };
}).Draw(spriteBatch, deltaTime); }).Draw(spriteBatch, deltaTime);
@@ -4731,7 +4728,7 @@ namespace Barotrauma.CharacterEditor
rotation: 0, rotation: 0,
origin: orig, origin: orig,
sourceRectangle: wearable.InheritSourceRect ? limb.ActiveSprite.SourceRect : wearable.Sprite.SourceRect, sourceRectangle: wearable.InheritSourceRect ? limb.ActiveSprite.SourceRect : wearable.Sprite.SourceRect,
scale: (wearable.InheritTextureScale ? 1 : wearable.Scale / RagdollParams.TextureScale) * spriteSheetZoom, scale: (wearable.InheritScale ? 1 : wearable.Scale / RagdollParams.TextureScale) * spriteSheetZoom,
effects: SpriteEffects.None, effects: SpriteEffects.None,
color: Color.White, color: Color.White,
layerDepth: 0); layerDepth: 0);
@@ -284,7 +284,7 @@ namespace Barotrauma.CharacterEditor
} }
isTextureSelected = true; isTextureSelected = true;
texturePathElement.Text = destinationPath; texturePathElement.Text = destinationPath.CleanUpPath();
}; };
FileSelection.ClearFileTypeFilters(); FileSelection.ClearFileTypeFilters();
FileSelection.AddFileTypeFilter("PNG", "*.png"); FileSelection.AddFileTypeFilter("PNG", "*.png");
@@ -431,7 +431,7 @@ namespace Barotrauma.CharacterEditor
box.Header.Font = GUI.LargeFont; box.Header.Font = GUI.LargeFont;
box.Content.ChildAnchor = Anchor.TopCenter; box.Content.ChildAnchor = Anchor.TopCenter;
box.Content.AbsoluteSpacing = (int)(20 * GUI.Scale); box.Content.AbsoluteSpacing = (int)(20 * GUI.Scale);
int elementSize = (int)(30 * GUI.Scale); int elementSize = (int)(40 * GUI.Scale);
var frame = new GUIFrame(new RectTransform(new Point(box.Content.Rect.Width - (int)(80 * GUI.xScale), box.Content.Rect.Height - (int)(200 * GUI.yScale)), var frame = new GUIFrame(new RectTransform(new Point(box.Content.Rect.Width - (int)(80 * GUI.xScale), box.Content.Rect.Height - (int)(200 * GUI.yScale)),
box.Content.RectTransform, Anchor.Center), style: null, color: ParamsEditor.Color) box.Content.RectTransform, Anchor.Center), style: null, color: ParamsEditor.Color)
{ {
@@ -625,8 +625,12 @@ namespace Barotrauma.CharacterEditor
var jointsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false }; var jointsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), jointsElement.RectTransform), GetCharacterEditorTranslation("Joints"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), jointsElement.RectTransform), GetCharacterEditorTranslation("Joints"), font: GUI.SubHeadingFont);
var jointButtonElement = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), jointsElement.RectTransform) var jointButtonElement = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), jointsElement.RectTransform)
{ RelativeOffset = new Vector2(0.15f, 0) }, style: null) {
{ CanBeFocused = false }; RelativeOffset = new Vector2(0.15f, 0)
}, style: null)
{
CanBeFocused = false
};
var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform)); var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform));
var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style: "GUIMinusButton") var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style: "GUIMinusButton")
{ {
@@ -824,7 +828,7 @@ namespace Barotrauma.CharacterEditor
{ {
CanBeFocused = false CanBeFocused = false
}; };
var group = new GUILayoutGroup(new RectTransform(Vector2.One, limbElement.RectTransform)) { AbsoluteSpacing = 2 }; var group = new GUILayoutGroup(new RectTransform(Vector2.One, limbElement.RectTransform)) { AbsoluteSpacing = 16 };
var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), name, font: GUI.SubHeadingFont); var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), name, font: GUI.SubHeadingFont);
var idField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null); var idField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null); var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
@@ -25,6 +25,7 @@ namespace Barotrauma
public Effect PostProcessEffect { get; private set; } public Effect PostProcessEffect { get; private set; }
public Effect GradientEffect { get; private set; } public Effect GradientEffect { get; private set; }
public Effect GrainEffect { get; private set; } public Effect GrainEffect { get; private set; }
public Effect ThresholdTintEffect { get; private set; }
public Effect BlueprintEffect { get; set; } public Effect BlueprintEffect { get; set; }
public GameScreen(GraphicsDevice graphics, ContentManager content) public GameScreen(GraphicsDevice graphics, ContentManager content)
@@ -38,21 +39,20 @@ namespace Barotrauma
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
}; };
Effect LoadEffect(string path)
=> content.Load<Effect>(path
#if LINUX || OSX #if LINUX || OSX
//var blurEffect = content.Load<Effect>("Effects/blurshader_opengl"); +"_opengl"
damageEffect = content.Load<Effect>("Effects/damageshader_opengl");
PostProcessEffect = content.Load<Effect>("Effects/postprocess_opengl");
GradientEffect = content.Load<Effect>("Effects/gradientshader_opengl");
GrainEffect = content.Load<Effect>("Effects/grainshader_opengl");
BlueprintEffect = content.Load<Effect>("Effects/blueprintshader_opengl");
#else
//var blurEffect = content.Load<Effect>("Effects/blurshader");
damageEffect = content.Load<Effect>("Effects/damageshader");
PostProcessEffect = content.Load<Effect>("Effects/postprocess");
GradientEffect = content.Load<Effect>("Effects/gradientshader");
GrainEffect = content.Load<Effect>("Effects/grainshader");
BlueprintEffect = content.Load<Effect>("Effects/blueprintshader");
#endif #endif
);
//var blurEffect = LoadEffect("Effects/blurshader");
damageEffect = LoadEffect("Effects/damageshader");
PostProcessEffect = LoadEffect("Effects/postprocess");
GradientEffect = LoadEffect("Effects/gradientshader");
GrainEffect = LoadEffect("Effects/grainshader");
ThresholdTintEffect = LoadEffect("Effects/thresholdtint");
BlueprintEffect = LoadEffect("Effects/blueprintshader");
damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png"); damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png");
damageEffect.Parameters["xStencil"].SetValue(damageStencil); damageEffect.Parameters["xStencil"].SetValue(damageStencil);
@@ -40,7 +40,7 @@ namespace Barotrauma
private readonly GUIFrame[] menuTabs; private readonly GUIFrame[] menuTabs;
private CampaignSetupUI campaignSetupUI; private SinglePlayerCampaignSetupUI campaignSetupUI;
private GUITextBox serverNameBox, /*portBox, queryPortBox,*/ passwordBox, maxPlayersBox; private GUITextBox serverNameBox, /*portBox, queryPortBox,*/ passwordBox, maxPlayersBox;
private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox; private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
@@ -594,6 +594,8 @@ namespace Barotrauma
GameMain.Instance.ShowCampaignDisclaimer(() => { SelectTab(null, Tab.NewGame); }); GameMain.Instance.ShowCampaignDisclaimer(() => { SelectTab(null, Tab.NewGame); });
return true; return true;
} }
campaignSetupUI.RandomizeCrew();
campaignSetupUI.SetPage(0);
campaignSetupUI.CreateDefaultSaveName(); campaignSetupUI.CreateDefaultSaveName();
campaignSetupUI.RandomizeSeed(); campaignSetupUI.RandomizeSeed();
campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines); campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines);
@@ -985,24 +987,32 @@ namespace Barotrauma
if (selectedTab < Tab.Empty && menuTabs[(int)selectedTab] != null) if (selectedTab < Tab.Empty && menuTabs[(int)selectedTab] != null)
{ {
menuTabs[(int)selectedTab].AddToGUIUpdateList(); menuTabs[(int)selectedTab].AddToGUIUpdateList();
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.CharacterMenus?.ForEach(m => m.AddToGUIUpdateList());
break;
}
} }
} }
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
#if !DEBUG #if !DEBUG && USE_STEAM
#if USE_STEAM
if (GameMain.Config.UseSteamMatchmaking) if (GameMain.Config.UseSteamMatchmaking)
{ {
hostServerButton.Enabled = Steam.SteamManager.IsInitialized; hostServerButton.Enabled = Steam.SteamManager.IsInitialized;
} }
steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized; steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized;
#endif #elif USE_STEAM
#else
#if USE_STEAM
steamWorkshopButton.Enabled = true; steamWorkshopButton.Enabled = true;
#endif #endif
#endif switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.Update();
break;
}
} }
public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch) public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
@@ -1119,6 +1129,11 @@ namespace Barotrauma
selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub")); selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, settings, mapSeed); GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, settings, mapSeed);
GameMain.GameSession.CrewManager.CharacterInfos.Clear();
foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
{
GameMain.GameSession.CrewManager.AddCharacterInfo(characterInfo);
}
((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel(); ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
} }
@@ -1146,7 +1161,7 @@ namespace Barotrauma
menuTabs[(int)Tab.NewGame].ClearChildren(); menuTabs[(int)Tab.NewGame].ClearChildren();
menuTabs[(int)Tab.LoadGame].ClearChildren(); menuTabs[(int)Tab.LoadGame].ClearChildren();
var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.025f) }) var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center))
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
@@ -1154,30 +1169,14 @@ namespace Barotrauma
var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center), var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center),
style: "InnerFrame"); style: "InnerFrame");
var paddedNewGame = new GUIFrame(new RectTransform(new Vector2(0.95f), newGameContent.RectTransform, Anchor.Center), style: null);
var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) }, var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) },
style: null); style: null);
campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame, SubmarineInfo.SavedSubmarines) campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame, SubmarineInfo.SavedSubmarines)
{ {
LoadGame = LoadGame, LoadGame = LoadGame,
StartNewGame = StartGame StartNewGame = StartGame
}; };
var startButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), innerNewGame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.BottomRight)
{
RelativeSpacing = 0.05f
};
campaignSetupUI.StartButton.RectTransform.Parent = startButtonContainer.RectTransform;
campaignSetupUI.StartButton.RectTransform.MinSize = new Point(
(int)(campaignSetupUI.StartButton.TextBlock.TextSize.X * 1.5f),
campaignSetupUI.StartButton.RectTransform.MinSize.Y);
startButtonContainer.RectTransform.MinSize = new Point(0, campaignSetupUI.StartButton.RectTransform.MinSize.Y);
if (campaignSetupUI.CampaignCustomizeButton != null)
{
campaignSetupUI.CampaignCustomizeButton.RectTransform.Parent = startButtonContainer.RectTransform;
}
campaignSetupUI.InitialMoneyText.RectTransform.Parent = startButtonContainer.RectTransform;
} }
private void CreateHostServerFields() private void CreateHostServerFields()
@@ -12,7 +12,6 @@ namespace Barotrauma
{ {
partial class NetLobbyScreen : Screen partial class NetLobbyScreen : Screen
{ {
private readonly List<Sprite> characterSprites = new List<Sprite>();
//private readonly List<Sprite> jobPreferenceSprites = new List<Sprite>(); //private readonly List<Sprite> jobPreferenceSprites = new List<Sprite>();
private readonly GUIFrame infoFrame, modeFrame; private readonly GUIFrame infoFrame, modeFrame;
@@ -83,6 +82,7 @@ namespace Barotrauma
private GUILayoutGroup infoContainer; private GUILayoutGroup infoContainer;
private GUIComponent changesPendingText; private GUIComponent changesPendingText;
private bool createPendingChangesText = true;
public GUIButton PlayerFrame; public GUIButton PlayerFrame;
private readonly GUIComponent subPreviewContainer; private readonly GUIComponent subPreviewContainer;
@@ -103,9 +103,10 @@ namespace Barotrauma
private GUIFrame characterInfoFrame; private GUIFrame characterInfoFrame;
private GUIFrame appearanceFrame; private GUIFrame appearanceFrame;
public GUIListBox HeadSelectionList; public CharacterInfo.AppearanceCustomizationMenu CharacterAppearanceCustomizationMenu;
public GUIFrame JobSelectionFrame; public GUIFrame JobSelectionFrame;
public GUIFrame JobPreferenceContainer;
public GUIListBox JobList; public GUIListBox JobList;
private float autoRestartTimer; private float autoRestartTimer;
@@ -238,7 +239,7 @@ namespace Barotrauma
get { return shuttleList.SelectedData as SubmarineInfo; } get { return shuttleList.SelectedData as SubmarineInfo; }
} }
public CampaignSetupUI CampaignSetupUI; public MultiPlayerCampaignSetupUI CampaignSetupUI;
public List<SubmarineInfo> CampaignSubmarines = new List<SubmarineInfo>(); public List<SubmarineInfo> CampaignSubmarines = new List<SubmarineInfo>();
// Passed onto the gamesession when created // Passed onto the gamesession when created
@@ -1249,11 +1250,9 @@ namespace Barotrauma
{ {
chatInput.Deselect(); chatInput.Deselect();
CampaignCharacterDiscarded = false; CampaignCharacterDiscarded = false;
HeadSelectionList = null;
JobSelectionFrame = null;
foreach (Sprite sprite in characterSprites) { sprite.Remove(); } CharacterAppearanceCustomizationMenu?.Dispose();
characterSprites.Clear(); JobSelectionFrame = null;
/*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); } /*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); }
jobPreferenceSprites.Clear();*/ jobPreferenceSprites.Clear();*/
@@ -1263,8 +1262,8 @@ namespace Barotrauma
{ {
if (GameMain.NetworkMember == null) { return; } if (GameMain.NetworkMember == null) { return; }
if (HeadSelectionList != null) { HeadSelectionList.Visible = false; } CharacterAppearanceCustomizationMenu?.Dispose();
if (JobSelectionFrame != null) { JobSelectionFrame.Visible = false; } JobSelectionFrame = null;
infoFrameContent.Recalculate(); infoFrameContent.Recalculate();
@@ -1405,29 +1404,24 @@ namespace Barotrauma
UpdatePlayerFrame(characterInfo, allowEditing, playerInfoContainer); UpdatePlayerFrame(characterInfo, allowEditing, playerInfoContainer);
} }
public void CreatePlayerFrame(GUIComponent parent) 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,
allowEditing: campaignCharacterInfo == null, allowEditing: alwaysAllowEditing || campaignCharacterInfo == null,
parent: parent); parent: parent,
createPendingText: createPendingText);
} }
private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent) private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent, bool createPendingText = true)
{ {
createPendingChangesText = createPendingText;
if (characterInfo == null || CampaignCharacterDiscarded) if (characterInfo == null || CampaignCharacterDiscarded)
{ {
characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, GameMain.Client.Name, null); characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, GameMain.Client.Name, null);
characterInfo.RecreateHead( characterInfo.RecreateHead(GameMain.Config.PlayerCharacterCustomization);
GameMain.Config.CharacterHeadIndex,
GameMain.Config.CharacterRace,
GameMain.Config.CharacterGender,
GameMain.Config.CharacterHairIndex,
GameMain.Config.CharacterBeardIndex,
GameMain.Config.CharacterMoustacheIndex,
GameMain.Config.CharacterFaceAttachmentIndex);
GameMain.Client.CharacterInfo = characterInfo; GameMain.Client.CharacterInfo = characterInfo;
characterInfo.OmitJobInPortraitClothing = true; characterInfo.OmitJobInPortraitClothing = false;
} }
parent.ClearChildren(); parent.ClearChildren();
@@ -1436,7 +1430,7 @@ namespace Barotrauma
infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, isGameRunning ? 0.95f : 0.9f), parent.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter) infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, isGameRunning ? 0.95f : 0.9f), parent.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter)
{ {
RelativeSpacing = 0.025f, RelativeSpacing = 0.015f,
Stretch = true, Stretch = true,
UserData = characterInfo UserData = characterInfo
}; };
@@ -1454,6 +1448,7 @@ namespace Barotrauma
CreateChangesPendingText(); CreateChangesPendingText();
} }
CharacterNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.RectTransform), !nameChangePending ? characterInfo.Name : GameMain.Client.PendingName, textAlignment: Alignment.Center) CharacterNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.RectTransform), !nameChangePending ? characterInfo.Name : GameMain.Client.PendingName, textAlignment: Alignment.Center)
{ {
MaxTextLength = Client.MaxNameLength, MaxTextLength = Client.MaxNameLength,
@@ -1477,7 +1472,10 @@ namespace Barotrauma
{ {
GameMain.Client.PendingName = tb.Text; GameMain.Client.PendingName = tb.Text;
TabMenu.PendingChanges = true; TabMenu.PendingChanges = true;
CreateChangesPendingText(); if (createPendingText)
{
CreateChangesPendingText();
}
} }
else else
{ {
@@ -1485,15 +1483,12 @@ namespace Barotrauma
} }
GameMain.Client.SetName(tb.Text); GameMain.Client.SetName(tb.Text);
}; }
}; };
new GUICustomComponent(new RectTransform(new Vector2(0.6f, 0.16f), infoContainer.RectTransform, Anchor.TopCenter),
onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
if (allowEditing) if (allowEditing)
{ {
GUILayoutGroup characterInfoTabs = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), infoContainer.RectTransform), isHorizontal: true) GUILayoutGroup characterInfoTabs = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), infoContainer.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
@@ -1518,7 +1513,10 @@ namespace Barotrauma
characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), infoContainer.RectTransform), style: null); characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), infoContainer.RectTransform), style: null);
characterInfoFrame.RectTransform.SizeChanged += RecalculateSubDescription; characterInfoFrame.RectTransform.SizeChanged += RecalculateSubDescription;
JobList = new GUIListBox(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), true) JobPreferenceContainer = new GUIFrame(new RectTransform(Vector2.One, characterInfoFrame.RectTransform),
style: "GUIFrameListBox");
characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.4f), JobPreferenceContainer.RectTransform, Anchor.TopCenter));
JobList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), JobPreferenceContainer.RectTransform, Anchor.BottomCenter), true)
{ {
Enabled = true, Enabled = true,
OnSelected = (child, obj) => OnSelected = (child, obj) =>
@@ -1554,7 +1552,7 @@ namespace Barotrauma
}; };
} }
UpdateJobPreferences(JobList); UpdateJobPreferences();
appearanceFrame = new GUIFrame(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), style: "GUIFrameListBox") appearanceFrame = new GUIFrame(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), style: "GUIFrameListBox")
{ {
@@ -1564,6 +1562,8 @@ namespace Barotrauma
} }
else else
{ {
characterInfo.CreateIcon(new RectTransform(new Vector2(0.6f, 0.16f), infoContainer.RectTransform, Anchor.TopCenter));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, font: GUI.SubHeadingFont, wrap: true) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, font: GUI.SubHeadingFont, wrap: true)
{ {
HoverColor = Color.Transparent, HoverColor = Color.Transparent,
@@ -1588,17 +1588,10 @@ namespace Barotrauma
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
var confirmation = new GUIMessageBox(TextManager.Get("NewCampaignCharacterHeader"), TextManager.Get("NewCampaignCharacterText"), TryDiscardCampaignCharacter(() =>
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmation.Buttons[0].OnClicked += confirmation.Close;
confirmation.Buttons[0].OnClicked += (btn2, userdata2) =>
{ {
CampaignCharacterDiscarded = true;
campaignCharacterInfo = null;
UpdatePlayerFrame(null, true, parent); UpdatePlayerFrame(null, true, parent);
return true; });
};
confirmation.Buttons[1].OnClicked += confirmation.Close;
return true; return true;
} }
}; };
@@ -1677,9 +1670,24 @@ namespace Barotrauma
} }
} }
public void TryDiscardCampaignCharacter(Action onYes)
{
var confirmation = new GUIMessageBox(TextManager.Get("NewCampaignCharacterHeader"), TextManager.Get("NewCampaignCharacterText"),
new[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmation.Buttons[0].OnClicked += confirmation.Close;
confirmation.Buttons[0].OnClicked += (btn2, userdata2) =>
{
CampaignCharacterDiscarded = true;
campaignCharacterInfo = null;
onYes();
return true;
};
confirmation.Buttons[1].OnClicked += confirmation.Close;
}
private void CreateChangesPendingText() private void CreateChangesPendingText()
{ {
if (changesPendingText != null || infoContainer == null) { return; } if (!createPendingChangesText || changesPendingText != null || infoContainer == null) { return; }
changesPendingText = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.Parent.Parent.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, -0.03f) }, changesPendingText = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.Parent.Parent.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, -0.03f) },
style: "OuterGlow") style: "OuterGlow")
@@ -1692,6 +1700,21 @@ namespace Barotrauma
changesPendingText.RectTransform.MinSize = new Point((int)(text.TextSize.X * 1.2f), (int)(text.TextSize.Y * 2.0f)); changesPendingText.RectTransform.MinSize = new Point((int)(text.TextSize.X * 1.2f), (int)(text.TextSize.Y * 2.0f));
} }
public static void CreateChangesPendingFrame(GUIComponent parent)
{
parent.ClearChildren();
GUIFrame changesPendingFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center),
style: "OuterGlow")
{
Color = Color.Black
};
new GUITextBlock(new RectTransform(Vector2.One, changesPendingFrame.RectTransform, Anchor.Center),
TextManager.Get("tabmenu.characterchangespending"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, style: null)
{
AutoScaleHorizontal = true
};
}
private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot) private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot)
{ {
jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(400 * GUI.Scale), (int)(180 * GUI.Scale)), GUI.Canvas, pivot: Pivot.BottomRight), jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(400 * GUI.Scale), (int)(180 * GUI.Scale)), GUI.Canvas, pivot: Pivot.BottomRight),
@@ -2459,7 +2482,7 @@ namespace Barotrauma
//CampaignSetupUI?.AddToGUIUpdateList(); //CampaignSetupUI?.AddToGUIUpdateList();
JobInfoFrame?.AddToGUIUpdateList(); JobInfoFrame?.AddToGUIUpdateList();
HeadSelectionList?.AddToGUIUpdateList(); CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
JobSelectionFrame?.AddToGUIUpdateList(); JobSelectionFrame?.AddToGUIUpdateList();
} }
@@ -2529,10 +2552,7 @@ namespace Barotrauma
} }
} }
if (HeadSelectionList != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(HeadSelectionList)) CharacterAppearanceCustomizationMenu?.Update();
{
HeadSelectionList.Visible = false;
}
if (JobSelectionFrame != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(JobSelectionFrame)) if (JobSelectionFrame != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(JobSelectionFrame))
{ {
JobList.Deselect(); JobList.Deselect();
@@ -2709,212 +2729,56 @@ namespace Barotrauma
jobPreferencesButton.Selected = true; jobPreferencesButton.Selected = true;
appearanceButton.Selected = false; appearanceButton.Selected = false;
JobList.Visible = true; JobPreferenceContainer.Visible = true;
appearanceFrame.Visible = false; appearanceFrame.Visible = false;
return false; return false;
} }
private bool SelectAppearanceTab(GUIButton button, object userData) private bool SelectAppearanceTab(GUIButton button, object _)
{ {
jobPreferencesButton.Selected = false; jobPreferencesButton.Selected = false;
appearanceButton.Selected = true; appearanceButton.Selected = true;
JobList.Visible = false; JobPreferenceContainer.Visible = false;
appearanceFrame.Visible = true; appearanceFrame.Visible = true;
appearanceFrame.ClearChildren(); appearanceFrame.ClearChildren();
if (HeadSelectionList != null) { HeadSelectionList.Visible = false; }
GUIButton maleButton = null;
GUIButton femaleButton = null;
var info = GameMain.Client.CharacterInfo; var info = GameMain.Client.CharacterInfo;
CharacterAppearanceCustomizationMenu = new CharacterInfo.AppearanceCustomizationMenu(info, appearanceFrame)
GUILayoutGroup content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), appearanceFrame.RectTransform, Anchor.Center))
{ {
RelativeSpacing = 0.05f OnHeadSwitch = menu =>
};
Vector2 elementSize = new Vector2(1.0f, 0.18f);
GUILayoutGroup genderContainer = new GUILayoutGroup(new RectTransform(elementSize, content.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform), TextManager.Get("Gender"), font: GUI.SubHeadingFont);
maleButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform),
TextManager.Get("Male"), style: "ListBoxElement")
{
UserData = Gender.Male,
OnClicked = OpenHeadSelection,
Selected = info.Gender == Gender.Male
};
femaleButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform),
TextManager.Get("Female"), style: "ListBoxElement")
{
UserData = Gender.Female,
OnClicked = OpenHeadSelection,
Selected = info.Gender == Gender.Female
};
int hairCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.Hair, info.HeadSpriteId).Count();
if (hairCount > 0)
{
var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Hair"), font: GUI.SubHeadingFont);
var hairSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider")
{ {
Range = new Vector2(0, hairCount), StoreHead(true);
StepValue = 1, UpdateJobPreferences();
BarScrollValue = info.HairIndex, SelectAppearanceTab(button, _);
OnMoved = SwitchHair, },
OnReleased = SaveHead, OnSliderMoved = (bar, scroll) =>
BarSize = 1.0f / (float)(hairCount + 1)
};
}
int beardCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.Beard, info.HeadSpriteId).Count();
if (beardCount > 0)
{
var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Beard"), font: GUI.SubHeadingFont);
var beardSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider")
{ {
Range = new Vector2(0, beardCount), StoreHead(false);
StepValue = 1, return false;
BarScrollValue = info.BeardIndex, },
OnMoved = SwitchBeard, OnSliderReleased = SaveHead
OnReleased = SaveHead, };
BarSize = 1.0f / (float)(beardCount + 1)
};
}
int moustacheCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.Moustache, info.HeadSpriteId).Count();
if (moustacheCount > 0)
{
var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Moustache"), font: GUI.SubHeadingFont);
var moustacheSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider")
{
Range = new Vector2(0, moustacheCount),
StepValue = 1,
BarScrollValue = info.MoustacheIndex,
OnMoved = SwitchMoustache,
OnReleased = SaveHead,
BarSize = 1.0f / (float)(moustacheCount + 1)
};
}
int faceAttachmentCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.FaceAttachment, info.HeadSpriteId).Count();
if (faceAttachmentCount > 0)
{
var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Accessories"), font: GUI.SubHeadingFont);
var faceAttachmentSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider")
{
Range = new Vector2(0, faceAttachmentCount),
StepValue = 1,
BarScrollValue = info.FaceAttachmentIndex,
OnMoved = SwitchFaceAttachment,
OnReleased = SaveHead,
BarSize = 1.0f / (float)(faceAttachmentCount + 1)
};
}
return false; return false;
} }
private bool OpenHeadSelection(GUIButton button, object userData) private bool SaveHead(GUIScrollBar scrollBar, float barScroll) => StoreHead(true);
private bool StoreHead(bool save)
{ {
Gender selectedGender = (Gender)userData; GameMain.Config.PlayerCharacterCustomization = GameMain.Client.CharacterInfo.Head;
if (HeadSelectionList != null) if (save)
{ {
HeadSelectionList.Visible = true; if (GameMain.GameSession?.IsRunning ?? false)
foreach (GUIComponent child in HeadSelectionList.Content.Children)
{ {
child.Visible = (Gender)child.UserData == selectedGender; TabMenu.PendingChanges = true;
child.Children.ForEach(c => c.Visible = ((Tuple<Gender, Race, int>)c.UserData).Item1 == selectedGender); CreateChangesPendingText();
} }
return true; GameMain.Config.SaveNewPlayerConfig();
} }
return true;
var info = GameMain.Client.CharacterInfo;
HeadSelectionList = new GUIListBox(
new RectTransform(new Point(characterInfoFrame.Rect.Width, (characterInfoFrame.Rect.Bottom - button.Rect.Bottom) + characterInfoFrame.Rect.Height * 2), GUI.Canvas)
{
AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom)
});
characterInfoFrame.RectTransform.SizeChanged += () =>
{
if (characterInfoFrame == null || HeadSelectionList?.RectTransform == null || button == null) { return; }
HeadSelectionList.RectTransform.Resize(new Point(characterInfoFrame.Rect.Width, (characterInfoFrame.Rect.Bottom - button.Rect.Bottom) + characterInfoFrame.Rect.Height * 2));
HeadSelectionList.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom);
if (SelectedSub != null) { CreateSubPreview(SelectedSub); }
};
new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black)
{
UserData = "outerglow",
CanBeFocused = false
};
GUILayoutGroup row = null;
int itemsInRow = 0;
XElement headElement = info.Ragdoll.MainElement.Elements().FirstOrDefault(e => e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase));
XElement headSpriteElement = headElement.Element("sprite");
string spritePathWithTags = headSpriteElement.Attribute("texture").Value;
var characterConfigElement = info.CharacterConfigElement;
var heads = info.Heads;
if (heads != null)
{
row = null;
itemsInRow = 0;
foreach (var head in heads)
{
var headPreset = head.Key;
Gender gender = headPreset.Gender;
Race race = headPreset.Race;
int headIndex = headPreset.ID;
string spritePath = spritePathWithTags
.Replace("[GENDER]", gender.ToString().ToLowerInvariant())
.Replace("[RACE]", race.ToString().ToLowerInvariant());
if (!File.Exists(spritePath)) { continue; }
Sprite headSprite = new Sprite(headSpriteElement, "", spritePath);
headSprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.Value.ToPoint()), headSprite.SourceRect.Size);
characterSprites.Add(headSprite);
if (itemsInRow >= 4 || row == null || gender != (Gender)row.UserData)
{
row = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform), true)
{
UserData = gender,
Visible = gender == selectedGender
};
itemsInRow = 0;
}
var btn = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), row.RectTransform), style: "ListBoxElementSquare")
{
OutlineColor = Color.White * 0.5f,
PressedColor = Color.White * 0.5f,
UserData = new Tuple<Gender, Race, int>(gender, race, headIndex),
OnClicked = SwitchHead,
Selected = gender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId,
Visible = gender == selectedGender
};
new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true);
itemsInRow++;
}
}
return false;
} }
private bool SwitchJob(GUIButton _, object obj) private bool SwitchJob(GUIButton _, object obj)
@@ -2947,7 +2811,7 @@ namespace Barotrauma
} }
} }
UpdateJobPreferences(JobList); UpdateJobPreferences();
if (moveToNext) if (moveToNext)
{ {
@@ -2978,14 +2842,14 @@ namespace Barotrauma
return true; return true;
} }
Point frameSize = new Point(characterInfoFrame.Rect.Width, characterInfoFrame.Rect.Height * 2); Point frameSize = new Point(characterInfoFrame.Rect.Width, (int)(characterInfoFrame.Rect.Height * 2 * 0.6f));
JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft) JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft)
{ AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - frameSize.X, characterInfoFrame.Rect.Bottom) }, style:"GUIFrameListBox"); { AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - frameSize.X, characterInfoFrame.Rect.Bottom) }, style:"GUIFrameListBox");
characterInfoFrame.RectTransform.SizeChanged += () => characterInfoFrame.RectTransform.SizeChanged += () =>
{ {
if (characterInfoFrame == null || JobSelectionFrame?.RectTransform == null) { return; } if (characterInfoFrame == null || JobSelectionFrame?.RectTransform == null) { return; }
Point size = new Point(characterInfoFrame.Rect.Width, characterInfoFrame.Rect.Height * 2); Point size = new Point(characterInfoFrame.Rect.Width, (int)(characterInfoFrame.Rect.Height * 2 * 0.6f));
JobSelectionFrame.RectTransform.Resize(size); JobSelectionFrame.RectTransform.Resize(size);
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - size.X, characterInfoFrame.Rect.Bottom); JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - size.X, characterInfoFrame.Rect.Bottom);
}; };
@@ -3141,88 +3005,6 @@ namespace Barotrauma
return retVal; return retVal;
} }
private bool SwitchHead(GUIButton button, object obj)
{
var info = GameMain.Client.CharacterInfo;
Gender gender = ((Tuple<Gender, Race, int>)obj).Item1;
Race race = ((Tuple<Gender, Race, int>)obj).Item2;
int id = ((Tuple<Gender, Race, int>)obj).Item3;
if (gender != info.Gender || race != info.Race || id != info.HeadSpriteId)
{
info.Head = new CharacterInfo.HeadInfo(id, gender, race);
info.ReloadHeadAttachments();
}
StoreHead(true);
UpdateJobPreferences(JobList);
SelectAppearanceTab(button, obj);
return true;
}
private bool SaveHead(GUIScrollBar scrollBar, float barScroll) => StoreHead(true);
private bool SwitchHair(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.Hair);
private bool SwitchBeard(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.Beard);
private bool SwitchMoustache(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.Moustache);
private bool SwitchFaceAttachment(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.FaceAttachment);
private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type)
{
var info = GameMain.Client.CharacterInfo;
int index = (int)scrollBar.BarScrollValue;
switch (type)
{
case WearableType.Beard:
info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, index, info.MoustacheIndex, info.FaceAttachmentIndex);
break;
case WearableType.FaceAttachment:
info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, info.BeardIndex, info.MoustacheIndex, index);
break;
case WearableType.Hair:
info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, index, info.BeardIndex, info.MoustacheIndex, info.FaceAttachmentIndex);
break;
case WearableType.Moustache:
info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, info.BeardIndex, index, info.FaceAttachmentIndex);
break;
default:
DebugConsole.ThrowError($"Wearable type not implemented: {type}");
return false;
}
info.ReloadHeadAttachments();
StoreHead(false);
return true;
}
private bool StoreHead(bool save)
{
var info = GameMain.Client.CharacterInfo;
var config = GameMain.Config;
config.CharacterRace = info.Race;
config.CharacterGender = info.Gender;
config.CharacterHeadIndex = info.HeadSpriteId;
config.CharacterHairIndex = info.HairIndex;
config.CharacterBeardIndex = info.BeardIndex;
config.CharacterMoustacheIndex = info.MoustacheIndex;
config.CharacterFaceAttachmentIndex = info.FaceAttachmentIndex;
if (save)
{
if (GameMain.GameSession?.IsRunning ?? false)
{
TabMenu.PendingChanges = true;
CreateChangesPendingText();
}
GameMain.Config.SaveNewPlayerConfig();
}
return true;
}
public void SelectMode(int modeIndex) public void SelectMode(int modeIndex)
{ {
if (modeIndex < 0 || modeIndex >= modeList.Content.CountChildren) { return; } if (modeIndex < 0 || modeIndex >= modeList.Content.CountChildren) { return; }
@@ -3405,8 +3187,13 @@ namespace Barotrauma
return true; return true;
} }
private void UpdateJobPreferences(GUIListBox listBox) private void UpdateJobPreferences()
{ {
GUICustomComponent characterIcon = JobPreferenceContainer.GetChild<GUICustomComponent>();
JobPreferenceContainer.RemoveChild(characterIcon);
GameMain.Client.CharacterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.4f), JobPreferenceContainer.RectTransform, Anchor.TopCenter));
GUIListBox listBox = JobPreferenceContainer.GetChild<GUIListBox>();
/*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); } /*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); }
jobPreferenceSprites.Clear();*/ jobPreferenceSprites.Clear();*/
@@ -3437,7 +3224,7 @@ namespace Barotrauma
variantButton.OnClicked = (btn, obj) => variantButton.OnClicked = (btn, obj) =>
{ {
btn.Parent.UserData = obj; btn.Parent.UserData = obj;
UpdateJobPreferences(listBox); UpdateJobPreferences();
return false; return false;
}; };
} }
@@ -41,21 +41,13 @@ namespace Barotrauma
public Sprite(Texture2D texture, Rectangle? sourceRectangle, Vector2? newOffset, float newRotation = 0.0f, string path = null) public Sprite(Texture2D texture, Rectangle? sourceRectangle, Vector2? newOffset, float newRotation = 0.0f, string path = null)
{ {
this.texture = texture; this.texture = texture;
sourceRect = sourceRectangle ?? new Rectangle(0, 0, texture.Width, texture.Height); sourceRect = sourceRectangle ?? new Rectangle(0, 0, texture.Width, texture.Height);
offset = newOffset ?? Vector2.Zero; offset = newOffset ?? Vector2.Zero;
size = new Vector2(sourceRect.Width, sourceRect.Height); size = new Vector2(sourceRect.Width, sourceRect.Height);
origin = Vector2.Zero; origin = Vector2.Zero;
effects = SpriteEffects.None; effects = SpriteEffects.None;
rotation = newRotation; rotation = newRotation;
FilePath = path; FilePath = path;
AddToList(this); AddToList(this);
} }
@@ -85,7 +77,7 @@ namespace Barotrauma
EnsureLazyLoaded(isAsync: true); EnsureLazyLoaded(isAsync: true);
} }
public void EnsureLazyLoaded(bool isAsync=false) public void EnsureLazyLoaded(bool isAsync = false)
{ {
if (!LazyLoad || texture != null || cannotBeLoaded || loadingAsync) { return; } if (!LazyLoad || texture != null || cannotBeLoaded || loadingAsync) { return; }
loadingAsync = isAsync; loadingAsync = isAsync;
@@ -382,7 +374,7 @@ namespace Barotrauma
{ {
foreach (Sprite s in LoadedSprites) foreach (Sprite s in LoadedSprites)
{ {
if (s.FullPath == FullPath) return; if (s.FullPath == FullPath) { return; }
} }
} }
} }
@@ -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.1500.3.0</Version> <Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</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.1500.3.0</Version> <Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -67,6 +67,12 @@
/processorParam:DebugMode=Auto /processorParam:DebugMode=Auto
/build:grainshader.fx /build:grainshader.fx
#begin thresholdtint.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:thresholdtint.fx
#begin blueprintshader.fx #begin blueprintshader.fx
/importer:EffectImporter /importer:EffectImporter
/processor:EffectProcessor /processor:EffectProcessor
@@ -72,3 +72,9 @@
/processor:EffectProcessor /processor:EffectProcessor
/processorParam:DebugMode=Auto /processorParam:DebugMode=Auto
/build:blueprintshader_opengl.fx /build:blueprintshader_opengl.fx
#begin thresholdtint_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:thresholdtint_opengl.fx
@@ -0,0 +1,32 @@
Texture2D xBaseTexture;
sampler BaseTextureSampler = sampler_state { Texture = <xBaseTexture>; };
Texture2D xTintMaskTexture;
sampler TintMaskTextureSampler = sampler_state { Texture = <xTintMaskTexture>; };
Texture2D xCutoffTexture;
sampler CutoffTextureSampler = sampler_state { Texture = <xCutoffTexture>; };
float highlightThreshold;
float highlightMultiplier;
float baseToCutoffSizeRatio;
float4 mainPS(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 baseSample = xBaseTexture.Sample(BaseTextureSampler, texCoord);
float3 tintMaskSample = xTintMaskTexture.Sample(TintMaskTextureSampler, texCoord).rgb;
float cutoffSample = xCutoffTexture.Sample(CutoffTextureSampler, texCoord * baseToCutoffSizeRatio).r;
float3 highlight = saturate((baseSample.rgb - (highlightThreshold * float3(1,1,1))) * highlightMultiplier);
float3 tinted = saturate(baseSample.rgb * clr.rgb + highlight);
return float4(
(tinted * tintMaskSample) + (baseSample.rgb * (float3(1,1,1) - tintMaskSample)),
baseSample.a * cutoffSample * clr.a);
}
technique ThresholdTintShader
{
pass Pass1
{
PixelShader = compile ps_4_0_level_9_1 mainPS();
}
}
@@ -0,0 +1,32 @@
Texture2D xBaseTexture;
sampler BaseTextureSampler = sampler_state { Texture = <xBaseTexture>; };
Texture2D xTintMaskTexture;
sampler TintMaskTextureSampler = sampler_state { Texture = <xTintMaskTexture>; };
Texture2D xCutoffTexture;
sampler CutoffTextureSampler = sampler_state { Texture = <xCutoffTexture>; };
float highlightThreshold;
float highlightMultiplier;
float baseToCutoffSizeRatio;
float4 mainPS(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 baseSample = tex2D(BaseTextureSampler, texCoord);
float3 tintMaskSample = tex2D(TintMaskTextureSampler, texCoord).rgb;
float cutoffSample = tex2D(CutoffTextureSampler, texCoord * baseToCutoffSizeRatio).r;
float3 highlight = saturate((baseSample.rgb - (highlightThreshold * float3(1,1,1))) * highlightMultiplier);
float3 tinted = saturate(baseSample.rgb * clr.rgb + highlight);
return float4(
(tinted * tintMaskSample) + (baseSample.rgb * (float3(1,1,1) - tintMaskSample)),
baseSample.a * cutoffSample * clr.a);
}
technique ThresholdTintShader
{
pass Pass1
{
PixelShader = compile ps_2_0 mainPS();
}
}
@@ -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.1500.3.0</Version> <Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</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.1500.3.0</Version> <Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</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.1500.3.0</Version> <Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -39,10 +39,13 @@ namespace Barotrauma
msg.Write((byte)Gender); msg.Write((byte)Gender);
msg.Write((byte)Race); msg.Write((byte)Race);
msg.Write((byte)HeadSpriteId); msg.Write((byte)HeadSpriteId);
msg.Write((byte)Head.HairIndex); msg.Write((byte)HairIndex);
msg.Write((byte)Head.BeardIndex); msg.Write((byte)BeardIndex);
msg.Write((byte)Head.MoustacheIndex); msg.Write((byte)MoustacheIndex);
msg.Write((byte)Head.FaceAttachmentIndex); msg.Write((byte)FaceAttachmentIndex);
msg.WriteColorR8G8B8(SkinColor);
msg.WriteColorR8G8B8(HairColor);
msg.WriteColorR8G8B8(FacialHairColor);
msg.Write(ragdollFileName); msg.Write(ragdollFileName);
if (Job != null) if (Job != null)
@@ -12,10 +12,10 @@ namespace Barotrauma
msg.Write((byte)(Level.Loaded == null || !Level.Loaded.Caves.Contains(cave) ? 255 : Level.Loaded.Caves.IndexOf(cave))); msg.Write((byte)(Level.Loaded == null || !Level.Loaded.Caves.Contains(cave) ? 255 : Level.Loaded.Caves.IndexOf(cave)));
} }
foreach (var kvp in SpawnedResources) foreach (var kvp in spawnedResources)
{ {
msg.Write((byte)kvp.Value.Count); msg.Write((byte)kvp.Value.Count);
var rotation = ResourceClusters[kvp.Key].Second; var rotation = resourceClusters[kvp.Key].rotation;
msg.Write(rotation); msg.Write(rotation);
foreach (var r in kvp.Value) foreach (var r in kvp.Value)
{ {
@@ -23,7 +23,7 @@ namespace Barotrauma
} }
} }
foreach (var kvp in RelevantLevelResources) foreach (var kvp in relevantLevelResources)
{ {
msg.Write(kvp.Key); msg.Write(kvp.Key);
msg.Write((byte)kvp.Value.Length); msg.Write((byte)kvp.Value.Length);
@@ -39,6 +39,7 @@ namespace Barotrauma.Items.Components
msg.Write(deteriorateAlwaysResetTimer); msg.Write(deteriorateAlwaysResetTimer);
msg.Write(DeteriorateAlways); msg.Write(DeteriorateAlways);
msg.Write(tinkeringDuration); msg.Write(tinkeringDuration);
msg.Write(tinkeringStrength);
msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID);
msg.WriteRangedInteger((int)currentFixerAction, 0, 2); msg.WriteRangedInteger((int)currentFixerAction, 0, 2);
} }
@@ -3464,15 +3464,15 @@ namespace Barotrauma.Networking
} }
catch (Exception e) catch (Exception e)
{ {
//gender = Gender.Male;
//race = Race.White;
//headSpriteId = 0;
DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }"); DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }");
} }
int hairIndex = message.ReadByte(); int hairIndex = message.ReadByte();
int beardIndex = message.ReadByte(); int beardIndex = message.ReadByte();
int moustacheIndex = message.ReadByte(); int moustacheIndex = message.ReadByte();
int faceAttachmentIndex = message.ReadByte(); int faceAttachmentIndex = message.ReadByte();
Color skinColor = message.ReadColorR8G8B8();
Color hairColor = message.ReadColorR8G8B8();
Color facialHairColor = message.ReadColorR8G8B8();
List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>(); List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>();
int count = message.ReadByte(); int count = message.ReadByte();
@@ -3489,6 +3489,9 @@ namespace Barotrauma.Networking
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name); sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name);
sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
sender.CharacterInfo.SkinColor = skinColor;
sender.CharacterInfo.HairColor = hairColor;
sender.CharacterInfo.FacialHairColor = facialHairColor;
if (jobPreferences.Count > 0) if (jobPreferences.Count > 0)
{ {
@@ -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.1500.3.0</Version> <Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -118,6 +118,7 @@
<Character file="Content/Characters/Variants/Mudraptor_hatchling/Mudraptor_hatchling.xml" /> <Character file="Content/Characters/Variants/Mudraptor_hatchling/Mudraptor_hatchling.xml" />
<Character file="Content/Characters/Variants/Mudraptor_passive/Mudraptor_passive.xml" /> <Character file="Content/Characters/Variants/Mudraptor_passive/Mudraptor_passive.xml" />
<Character file="Content/Characters/Variants/Tigerthresher_hatchling/Tigerthresher_hatchling.xml" /> <Character file="Content/Characters/Variants/Tigerthresher_hatchling/Tigerthresher_hatchling.xml" />
<Character file="Content/Characters/TintTest/TintTest.xml" />
<MapCreature file="Content/Items/Gardening/ballastflora.xml" /> <MapCreature file="Content/Items/Gardening/ballastflora.xml" />
<Wreck file="Content/Map/Wrecks/Dugong_Wrecked.sub" /> <Wreck file="Content/Map/Wrecks/Dugong_Wrecked.sub" />
<Wreck file="Content/Map/Wrecks/Kastrull_Wrecked.sub" /> <Wreck file="Content/Map/Wrecks/Kastrull_Wrecked.sub" />
@@ -464,7 +464,7 @@ namespace Barotrauma
}; };
break; break;
case "return": case "return":
newObjective = new AIObjectiveReturn(character, this, priorityModifier: priorityModifier); newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier);
newObjective.Abandoned += () => DismissSelf(order, option); newObjective.Abandoned += () => DismissSelf(order, option);
newObjective.Completed += () => DismissSelf(order, option); newObjective.Completed += () => DismissSelf(order, option);
break; break;
@@ -408,7 +408,7 @@ namespace Barotrauma
} }
bool isCompleted = bool isCompleted =
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) || AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold); targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold);
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam) if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
{ {
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.AIController is HumanAIController humanAI) if (character.AIController is HumanAIController humanAI)
{ {
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) || if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) ||
target.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold)) target.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold))
{ {
return false; return false;
} }
@@ -12,7 +12,7 @@ namespace Barotrauma
private bool usingEscapeBehavior; private bool usingEscapeBehavior;
public Submarine ReturnTarget { get; } public Submarine ReturnTarget { get; }
public AIObjectiveReturn(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
{ {
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded); ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
if (ReturnTarget == null) if (ReturnTarget == null)
@@ -23,10 +23,12 @@ namespace Barotrauma
Submarine GetReturnTarget(IEnumerable<Submarine> subs) Submarine GetReturnTarget(IEnumerable<Submarine> subs)
{ {
var requiredTeamID = orderGiver?.TeamID ?? character?.TeamID;
Submarine returnTarget = null; Submarine returnTarget = null;
foreach (var sub in subs) foreach (var sub in subs)
{ {
if (sub?.TeamID != character.TeamID) { continue; } if (sub == null) { continue; }
if (sub.TeamID != requiredTeamID) { continue; }
returnTarget = sub; returnTarget = sub;
break; break;
} }
@@ -229,7 +231,7 @@ namespace Barotrauma
protected override void OnAbandon() protected override void OnAbandon()
{ {
base.OnAbandon(); base.OnAbandon();
SteeringManager.Reset(); SteeringManager?.Reset();
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective) if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
{ {
string msg = TextManager.Get("dialogcannotreturn", returnNull: true); string msg = TextManager.Get("dialogcannotreturn", returnNull: true);
@@ -24,8 +24,6 @@ namespace Barotrauma
public readonly Vector2 Position; public readonly Vector2 Position;
public readonly int WayPointID; public readonly int WayPointID;
public bool blocked;
public override string ToString() public override string ToString()
{ {
return $"PathNode {WayPointID}"; return $"PathNode {WayPointID}";
@@ -81,6 +79,31 @@ namespace Barotrauma
return nodeList; return nodeList;
} }
private bool? blocked;
public bool IsBlocked()
{
if (blocked.HasValue) { return blocked.Value; }
blocked = false;
if (Waypoint.Submarine != null) { return blocked.Value; }
if (Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { return blocked.Value; }
foreach (var w in Level.Loaded.ExtraWalls)
{
if (!(w is DestructibleLevelWall d)) { return blocked.Value; }
if (d.Destroyed) { return blocked.Value; }
if (!d.IsPointInside(Waypoint.Position)) { return blocked.Value; }
blocked = true;
break;
}
return blocked.Value;
}
public void ResetBlocked()
{
blocked = null;
}
} }
class PathFinder class PathFinder
@@ -146,7 +169,10 @@ namespace Barotrauma
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true) public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{ {
UpdateBlockedNodes(); foreach (PathNode node in nodes)
{
node.ResetBlocked();
}
//sort nodes roughly according to distance //sort nodes roughly according to distance
sortedNodes.Clear(); sortedNodes.Clear();
@@ -202,11 +228,11 @@ namespace Barotrauma
{ {
if (startNode == null || node.TempDistance < startNode.TempDistance) if (startNode == null || node.TempDistance < startNode.TempDistance)
{ {
if (node.blocked) { continue; }
if (nodeFilter != null && !nodeFilter(node)) { continue; } if (nodeFilter != null && !nodeFilter(node)) { continue; }
if (startNodeFilter != null && !startNodeFilter(node)) { continue; } if (startNodeFilter != null && !startNodeFilter(node)) { continue; }
// Always check the visibility for the start node // Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { continue; } if (!IsWaypointVisible(node, start)) { continue; }
if (node.IsBlocked()) { continue; }
startNode = node; startNode = node;
} }
} }
@@ -251,11 +277,11 @@ namespace Barotrauma
{ {
if (endNode == null || node.TempDistance < endNode.TempDistance) if (endNode == null || node.TempDistance < endNode.TempDistance)
{ {
if (node.blocked) { continue; }
if (nodeFilter != null && !nodeFilter(node)) { continue; } if (nodeFilter != null && !nodeFilter(node)) { continue; }
if (endNodeFilter != null && !endNodeFilter(node)) { continue; } if (endNodeFilter != null && !endNodeFilter(node)) { continue; }
// Only check the visibility for the end node when allowed (fix leaks) // Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; } if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; }
if (node.IsBlocked()) { continue; }
endNode = node; endNode = node;
} }
} }
@@ -326,15 +352,13 @@ namespace Barotrauma
float dist = float.MaxValue; float dist = float.MaxValue;
foreach (PathNode node in nodes) foreach (PathNode node in nodes)
{ {
if (node.state != 1) { continue; } if (node.state != 1 || node.F > dist) { continue; }
if (isCharacter && node.Waypoint.isObstructed) { continue; } if (isCharacter && node.Waypoint.isObstructed) { continue; }
if (node.blocked) { continue; }
if (filter != null && !filter(node)) { continue; } if (filter != null && !filter(node)) { continue; }
if (node.F < dist) if (node.IsBlocked()) { continue; }
{
dist = node.F; dist = node.F;
currNode = node; currNode = node;
}
} }
if (currNode == null || currNode == end) { break; } if (currNode == null || currNode == end) { break; }
@@ -436,25 +460,6 @@ namespace Barotrauma
return path; return path;
} }
private void UpdateBlockedNodes()
{
if (!isCharacter) { return; }
foreach (var n in nodes)
{
n.blocked = false;
if (n.Waypoint.Submarine != null) { continue; }
if (n.Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { continue; }
foreach (var w in Level.Loaded.ExtraWalls)
{
if (!(w is DestructibleLevelWall d)) { continue; }
if (d.Destroyed) { continue; }
if (!d.IsPointInside(n.Waypoint.Position)) { continue; }
n.blocked = true;
break;
}
}
}
} }
} }
@@ -42,6 +42,10 @@ namespace Barotrauma
} }
else else
{ {
if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
{
return humanAnimController.HumanCrouchParams;
}
return IsMovingFast ? RunParams : WalkParams; return IsMovingFast ? RunParams : WalkParams;
} }
} }
@@ -96,7 +100,12 @@ namespace Barotrauma
{ {
if (CanWalk) if (CanWalk)
{ {
return new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams }; var anims = new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams };
if (this is HumanoidAnimController humanAnimController)
{
anims.Add(humanAnimController.HumanCrouchParams);
}
return anims;
} }
else else
{ {
@@ -154,7 +163,7 @@ namespace Barotrauma
public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { } public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { }
public float GetSpeed(AnimationType type) public virtual float GetSpeed(AnimationType type)
{ {
GroundedMovementParams movementParams; GroundedMovementParams movementParams;
switch (type) switch (type)
@@ -207,7 +216,14 @@ namespace Barotrauma
} }
else else
{ {
animType = AnimationType.Walk; if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
{
animType = AnimationType.Crouch;
}
else
{
animType = AnimationType.Walk;
}
} }
} }
return GetSpeed(animType); return GetSpeed(animType);
@@ -221,6 +237,12 @@ namespace Barotrauma
return WalkParams; return WalkParams;
case AnimationType.Run: case AnimationType.Run:
return RunParams; return RunParams;
case AnimationType.Crouch:
if (this is HumanoidAnimController humanAnimController)
{
return humanAnimController.HumanCrouchParams;
}
throw new NotImplementedException(type.ToString());
case AnimationType.SwimSlow: case AnimationType.SwimSlow:
return SwimSlowParams; return SwimSlowParams;
case AnimationType.SwimFast: case AnimationType.SwimFast:
@@ -97,9 +97,9 @@ namespace Barotrauma
public new FishSwimParams CurrentSwimParams => base.CurrentSwimParams as FishSwimParams; public new FishSwimParams CurrentSwimParams => base.CurrentSwimParams as FishSwimParams;
public float? TailAngle => GetValidOrNull(CurrentAnimationParams, CurrentFishAnimation?.TailAngleInRadians); public float? TailAngle => GetValidOrNull(CurrentAnimationParams, CurrentFishAnimation?.TailAngleInRadians);
public float FootTorque => CurrentFishAnimation.FootTorque; public float FootTorque => CurrentAnimationParams.FootTorque;
public float HeadTorque => CurrentFishAnimation.HeadTorque; public float HeadTorque => CurrentAnimationParams.HeadTorque;
public float TorsoTorque => CurrentFishAnimation.TorsoTorque; public float TorsoTorque => CurrentAnimationParams.TorsoTorque;
public float TailTorque => CurrentFishAnimation.TailTorque; public float TailTorque => CurrentFishAnimation.TailTorque;
public float HeadMoveForce => CurrentGroundedParams.HeadMoveForce; public float HeadMoveForce => CurrentGroundedParams.HeadMoveForce;
public float TorsoMoveForce => CurrentGroundedParams.TorsoMoveForce; public float TorsoMoveForce => CurrentGroundedParams.TorsoMoveForce;
@@ -2,7 +2,6 @@
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
@@ -73,6 +72,20 @@ namespace Barotrauma
set { _humanRunParams = value; } set { _humanRunParams = value; }
} }
private HumanCrouchParams _humanCrouchParams;
public HumanCrouchParams HumanCrouchParams
{
get
{
if (_humanCrouchParams == null)
{
_humanCrouchParams = HumanCrouchParams.GetDefaultAnimParams(character);
}
return _humanCrouchParams;
}
set { _humanCrouchParams = value; }
}
private HumanSwimSlowParams _humanSwimSlowParams; private HumanSwimSlowParams _humanSwimSlowParams;
public HumanSwimSlowParams HumanSwimSlowParams public HumanSwimSlowParams HumanSwimSlowParams
{ {
@@ -102,8 +115,11 @@ namespace Barotrauma
} }
public new HumanGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as HumanGroundedParams; public new HumanGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as HumanGroundedParams;
public new HumanSwimParams CurrentSwimParams => base.CurrentSwimParams as HumanSwimParams; public new HumanSwimParams CurrentSwimParams => base.CurrentSwimParams as HumanSwimParams;
public IHumanAnimation CurrentHumanAnimParams => CurrentAnimationParams as IHumanAnimation;
public override GroundedMovementParams WalkParams public override GroundedMovementParams WalkParams
{ {
get { return HumanWalkParams; } get { return HumanWalkParams; }
@@ -169,42 +185,9 @@ namespace Barotrauma
private float swimmingStateLockTimer; private float swimmingStateLockTimer;
private float useItemTimer; private float useItemTimer;
public override float? TorsoPosition
{
get
{
return Crouching && !swimming ? CurrentGroundedParams.CrouchingTorsoPos * RagdollParams.JointScale : base.TorsoPosition;
}
}
public override float? HeadPosition
{
get
{
return Crouching && !swimming ? CurrentGroundedParams.CrouchingHeadPos * RagdollParams.JointScale : base.HeadPosition;
}
}
public override float? TorsoAngle
{
get
{
return Crouching && !swimming ? MathHelper.ToRadians(CurrentGroundedParams.CrouchingTorsoAngle) : base.TorsoAngle;
}
}
public override float? HeadAngle
{
get
{
return Crouching && !swimming ? MathHelper.ToRadians(CurrentGroundedParams.CrouchingHeadAngle) : base.HeadAngle;
}
}
public float HeadLeanAmount => CurrentGroundedParams.HeadLeanAmount; public float HeadLeanAmount => CurrentGroundedParams.HeadLeanAmount;
public float TorsoLeanAmount => CurrentGroundedParams.TorsoLeanAmount; public float TorsoLeanAmount => CurrentGroundedParams.TorsoLeanAmount;
public Vector2 FootMoveOffset => (Crouching ? CurrentGroundedParams.CrouchingFootMoveOffset : CurrentGroundedParams.FootMoveOffset) * RagdollParams.JointScale; public Vector2 FootMoveOffset => CurrentGroundedParams.FootMoveOffset * RagdollParams.JointScale;
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;
@@ -298,7 +281,7 @@ namespace Barotrauma
LimbType lowerLegType = LimbType.RightLeg; LimbType lowerLegType = LimbType.RightLeg;
LimbType footType = LimbType.RightFoot; LimbType footType = LimbType.RightFoot;
var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType); var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType) ?? GetJointBetweenLimbs(LimbType.Torso, upperLegType);
Vector2 localAnchorWaist = Vector2.Zero; Vector2 localAnchorWaist = Vector2.Zero;
Vector2 localAnchorKnee = Vector2.Zero; Vector2 localAnchorKnee = Vector2.Zero;
if (waistJoint != null) if (waistJoint != null)
@@ -336,7 +319,7 @@ namespace Barotrauma
levitatingCollider = true; levitatingCollider = true;
ColliderIndex = Crouching && !swimming ? 1 : 0; ColliderIndex = Crouching && !swimming ? 1 : 0;
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false || if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false ||
(ForceSelectAnimationType != AnimationType.Walk && ForceSelectAnimationType != AnimationType.NotDefined)) (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{ {
Crouching = false; Crouching = false;
ColliderIndex = 0; ColliderIndex = 0;
@@ -439,9 +422,8 @@ namespace Barotrauma
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform); midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f; if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
HandIK(rightHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
HandIK(rightHand, midPos); HandIK(leftHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
HandIK(leftHand, midPos);
} }
else if (character.AnimController.AnimationTestPose) else if (character.AnimController.AnimationTestPose)
{ {
@@ -638,7 +620,7 @@ namespace Barotrauma
Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y); Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y);
} }
getUpForce = getUpForce * Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f); getUpForce *= Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f);
torso.PullJointEnabled = true; torso.PullJointEnabled = true;
head.PullJointEnabled = true; head.PullJointEnabled = true;
@@ -710,9 +692,12 @@ namespace Barotrauma
float torsoAngle = TorsoAngle.Value; float torsoAngle = TorsoAngle.Value;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
torsoAngle -= herpesStrength / 150.0f; torsoAngle -= herpesStrength / 150.0f;
torso.body.SmoothRotate(torsoAngle * Dir, 50.0f); torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque);
}
if (HeadAngle.HasValue)
{
head.body.SmoothRotate(HeadAngle.Value * Dir, CurrentGroundedParams.HeadTorque);
} }
if (HeadAngle.HasValue) head.body.SmoothRotate(HeadAngle.Value * Dir, 50.0f);
if (!onGround) if (!onGround)
{ {
@@ -743,12 +728,23 @@ namespace Barotrauma
Vector2 footPos = stepSize * -i; Vector2 footPos = stepSize * -i;
footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y); footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y);
if (footPos.Y < 0.0f) footPos.Y = -0.15f; if (footPos.Y < 0.0f) { footPos.Y = -0.15f; }
//make the character limp if the feet are damaged //make the character limp if the feet are damaged
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true); float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true);
footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f)); footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
if (CurrentGroundedParams.FootLiftHorizontalFactor > 0)
{
// Calculate the foot y dynamically based on the foot position relative to the waist,
// so that the foot aims higher when it's behind the waist and lower when it's in the front.
float xDiff = (foot.SimPosition.X - waistPos.X + FootMoveOffset.X) * Dir;
float min = MathUtils.InverseLerp(1, 0, CurrentGroundedParams.FootLiftHorizontalFactor);
float max = 1 + MathUtils.InverseLerp(0, 1, CurrentGroundedParams.FootLiftHorizontalFactor);
float xFactor = MathHelper.Lerp(min, max, MathUtils.InverseLerp(RagdollParams.JointScale, -RagdollParams.JointScale, xDiff));
footPos.Y *= xFactor;
}
if (onSlope && Stairs == null) if (onSlope && Stairs == null)
{ {
footPos.Y *= 2.0f; footPos.Y *= 2.0f;
@@ -770,7 +766,7 @@ namespace Barotrauma
foot.DebugTargetPos = colliderPos + footPos; foot.DebugTargetPos = colliderPos + footPos;
MoveLimb(foot, colliderPos + footPos, CurrentGroundedParams.FootMoveStrength); MoveLimb(foot, colliderPos + footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, colliderPos + footPos, FootIK(foot, colliderPos + footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians); CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
} }
} }
@@ -789,7 +785,7 @@ namespace Barotrauma
HandIK(rightHand, torso.SimPosition + posAddition + HandIK(rightHand, torso.SimPosition + posAddition +
new Vector2( new Vector2(
-handPos.X, -handPos.X,
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength); (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
} }
if (leftHand != null && !leftHand.Disabled) if (leftHand != null && !leftHand.Disabled)
@@ -797,16 +793,14 @@ namespace Barotrauma
HandIK(leftHand, torso.SimPosition + posAddition + HandIK(leftHand, torso.SimPosition + posAddition +
new Vector2( new Vector2(
handPos.X, handPos.X,
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength); (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
} }
} }
else else
{ {
for (int i = -1; i < 2; i += 2) for (int i = -1; i < 2; i += 2)
{ {
Vector2 footPos = colliderPos; Vector2 footPos = colliderPos;
if (Crouching) if (Crouching)
{ {
footPos = new Vector2( footPos = new Vector2(
@@ -817,27 +811,24 @@ namespace Barotrauma
//lift the foot at the back up a bit //lift the foot at the back up a bit
footPos.Y += 0.15f; footPos.Y += 0.15f;
} }
footPos.X += torso.SimPosition.X; footPos.X += colliderPos.X;
} }
else else
{ {
footPos = new Vector2(colliderPos.X + stepSize.X * i * 0.2f, colliderPos.Y - 0.1f); footPos = new Vector2(colliderPos.X + stepSize.X * i * 0.2f, colliderPos.Y - 0.1f);
} }
if (Stairs == null) if (Stairs == null)
{ {
footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y); footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y);
} }
var foot = i == -1 ? rightFoot : leftFoot; var foot = i == -1 ? rightFoot : leftFoot;
if (foot != null && !foot.Disabled) if (foot != null && !foot.Disabled)
{ {
foot.DebugRefPos = colliderPos; foot.DebugRefPos = colliderPos;
foot.DebugTargetPos = footPos; foot.DebugTargetPos = footPos;
MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength); MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, footPos, FootIK(foot, footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians); CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
} }
} }
@@ -970,7 +961,7 @@ namespace Barotrauma
if (!aiming) if (!aiming)
{ {
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2; float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier); Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
} }
} }
else else
@@ -981,7 +972,7 @@ namespace Barotrauma
Vector2 diff = (mousePos - torso.SimPosition) * Dir; Vector2 diff = (mousePos - torso.SimPosition) * Dir;
TargetMovement = new Vector2(0.0f, -0.1f); TargetMovement = new Vector2(0.0f, -0.1f);
float newRotation = MathUtils.VectorToAngle(diff); float newRotation = MathUtils.VectorToAngle(diff);
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier); Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
} }
} }
@@ -991,19 +982,19 @@ namespace Barotrauma
if (TorsoAngle.HasValue) if (TorsoAngle.HasValue)
{ {
torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque); torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.TorsoTorque);
} }
else else
{ {
torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque); torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.TorsoTorque);
} }
if (HeadAngle.HasValue) if (HeadAngle.HasValue)
{ {
head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque); head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.HeadTorque);
} }
else else
{ {
head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque); head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.HeadTorque);
} }
//dont try to move upwards if head is already out of water //dont try to move upwards if head is already out of water
@@ -1044,25 +1035,25 @@ namespace Barotrauma
float legMoveMultiplier = 1.0f; float legMoveMultiplier = 1.0f;
if (movement.LengthSquared() < 0.001f) if (movement.LengthSquared() < 0.001f)
{ {
//TODO: expose these? // Swimming in place (TODO: expose?)
legMoveMultiplier = 0.3f; legMoveMultiplier = 0.3f;
legCyclePos += 0.4f; legCyclePos += 0.4f;
handCyclePos += 0.1f; handCyclePos += 0.1f;
} }
var waist = GetLimb(LimbType.Waist); var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength); footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f); Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f);
transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation)); transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
float torque = CurrentSwimParams.FootRotateStrength * character.SpeedMultiplier * (1.2f - character.GetLegPenalty()); float legTorque = CurrentSwimParams.LegTorque * character.SpeedMultiplier * (1.2f - character.GetLegPenalty());
if (rightFoot != null && !rightFoot.Disabled) if (rightFoot != null && !rightFoot.Disabled)
{ {
FootIK(rightFoot, footPos - transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians); FootIK(rightFoot, footPos - transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
} }
if (leftFoot != null && !leftFoot.Disabled) if (leftFoot != null && !leftFoot.Disabled)
{ {
FootIK(leftFoot, footPos + transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians); FootIK(leftFoot, footPos + transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
} }
handPos = (torso.SimPosition + head.SimPosition) / 2.0f; handPos = (torso.SimPosition + head.SimPosition) / 2.0f;
@@ -1071,7 +1062,7 @@ namespace Barotrauma
// -> hands just float around // -> hands just float around
if ((!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0) || TargetMovement.LengthSquared() < 0.001f) if ((!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0) || TargetMovement.LengthSquared() < 0.001f)
{ {
handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.6f, torso.Rotation); handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.2f, torso.Rotation);
float wobbleAmount = 0.1f; float wobbleAmount = 0.1f;
@@ -1079,14 +1070,14 @@ namespace Barotrauma
{ {
MoveLimb(rightHand, new Vector2( MoveLimb(rightHand, new Vector2(
handPos.X + (float)Math.Sin(handCyclePos / 1.5f) * wobbleAmount, handPos.X + (float)Math.Sin(handCyclePos / 1.5f) * wobbleAmount,
handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), 1.5f); handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength);
} }
if (leftHand != null && !leftHand.Disabled) if (leftHand != null && !leftHand.Disabled)
{ {
MoveLimb(leftHand, new Vector2( MoveLimb(leftHand, new Vector2(
handPos.X + (float)Math.Sin(handCyclePos / 2.0f) * wobbleAmount, handPos.X + (float)Math.Sin(handCyclePos / 2.0f) * wobbleAmount,
handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), 1.5f); handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength);
} }
return; return;
@@ -1107,8 +1098,8 @@ namespace Barotrauma
Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset; Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset;
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X); rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix); rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty());
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetRightHandPenalty())); HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
} }
if (leftHand != null && !leftHand.Disabled) if (leftHand != null && !leftHand.Disabled)
@@ -1116,8 +1107,8 @@ namespace Barotrauma
Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset; Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset;
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X); leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix); leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty());
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty())); HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
} }
} }
@@ -1173,15 +1164,16 @@ namespace Barotrauma
} }
float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.radius - Collider.height / 2.0f; float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.radius - Collider.height / 2.0f;
float headPos = HeadPosition ?? 0;
MoveLimb(head, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.HeadPosition), 10.5f); float torsoPos = TorsoPosition ?? 0;
MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.TorsoPosition), 10.5f); MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), 10.5f);
MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + torsoPos), 10.5f);
Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f); Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f);
Vector2 handPos = new Vector2( Vector2 handPos = new Vector2(
ladderSimPos.X, ladderSimPos.X,
bottomPos + WalkParams.TorsoPosition + movement.Y * 0.1f - ladderSimPos.Y); bottomPos + torsoPos + movement.Y * 0.1f - ladderSimPos.Y);
//prevent the hands from going above the top of the ladders //prevent the hands from going above the top of the ladders
handPos.Y = Math.Min(-0.5f, handPos.Y); handPos.Y = Math.Min(-0.5f, handPos.Y);
@@ -1258,7 +1250,8 @@ namespace Barotrauma
//apply forces to the collider to move the Character up/down //apply forces to the collider to move the Character up/down
Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
head.body.SmoothRotate(0.0f); float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque);
if (!character.SelectedConstruction.Prefab.Triggers.Any()) if (!character.SelectedConstruction.Prefab.Triggers.Any())
{ {
@@ -1881,7 +1874,7 @@ namespace Barotrauma
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; } if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; }
HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i]); HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
} }
} }
} }
@@ -1906,7 +1899,7 @@ namespace Barotrauma
return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength; return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
} }
private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) private void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f)
{ {
Vector2 shoulderPos; Vector2 shoulderPos;
@@ -1948,9 +1941,11 @@ namespace Barotrauma
armAngle -= MathHelper.TwoPi; armAngle -= MathHelper.TwoPi;
} }
arm?.body.SmoothRotate((armAngle - upperArmAngle), 20.0f * force * arm.Mass, wrapAngle: false); arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false);
forearm?.body.SmoothRotate((armAngle + lowerArmAngle), 20.0f * force * forearm.Mass, wrapAngle: false); float forearmAngle = armAngle + lowerArmAngle;
hand?.body.SmoothRotate((armAngle + lowerArmAngle), 100.0f * force * hand.Mass, wrapAngle: false); forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false);
float handAngle = forearm != null ? armAngle : forearmAngle;
hand?.body.SmoothRotate(handAngle, 100.0f * handTorque * hand.Mass, wrapAngle: false);
} }
private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle) private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle)
@@ -1976,12 +1971,12 @@ namespace Barotrauma
upperLeg = GetLimb(LimbType.RightThigh); upperLeg = GetLimb(LimbType.RightThigh);
lowerLeg = GetLimb(LimbType.RightLeg); lowerLeg = GetLimb(LimbType.RightLeg);
} }
var torso = GetLimb(LimbType.Torso); Limb torso = GetLimb(LimbType.Torso);
var waist = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type); LimbJoint waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type) ?? GetJointBetweenLimbs(LimbType.Torso, upperLeg.type);
Vector2 waistPos = Vector2.Zero; Vector2 waistPos = Vector2.Zero;
if (waist != null) if (waistJoint != null)
{ {
waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB; waistPos = waistJoint.LimbA == upperLeg ? waistJoint.WorldAnchorA : waistJoint.WorldAnchorB;
} }
//distance from waist joint to the target position //distance from waist joint to the target position
@@ -2137,5 +2132,18 @@ namespace Barotrauma
} }
} }
public override float GetSpeed(AnimationType type)
{
if (type == AnimationType.Crouch)
{
if (!CanWalk)
{
DebugConsole.ThrowError($"{character.SpeciesName} cannot crouch!");
return 0;
}
return IsMovingBackwards ? HumanCrouchParams.MovementSpeed * HumanCrouchParams.BackwardsMovementMultiplier : HumanCrouchParams.MovementSpeed;
}
return base.GetSpeed(type);
}
} }
} }
@@ -309,8 +309,6 @@ namespace Barotrauma
public string TraitorCurrentObjective = ""; public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase); public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male;
public bool IsFemale => Info != null && Info.HasGenders && Info.Gender == Gender.Female;
private float attackCoolDown; private float attackCoolDown;
@@ -1665,9 +1663,9 @@ namespace Barotrauma
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f; AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
} }
if (AnimController is HumanoidAnimController) if (AnimController is HumanoidAnimController humanAnimController)
{ {
((HumanoidAnimController)AnimController).Crouching = IsKeyDown(InputType.Crouch); humanAnimController.Crouching = humanAnimController.ForceSelectAnimationType == AnimationType.Crouch || IsKeyDown(InputType.Crouch);
} }
if (!aiControlled && if (!aiControlled &&
@@ -2760,9 +2758,9 @@ namespace Barotrauma
//ragdoll button //ragdoll button
if (IsRagdolled || !CanMove) if (IsRagdolled || !CanMove)
{ {
if (AnimController is HumanoidAnimController) if (AnimController is HumanoidAnimController humanAnimController)
{ {
((HumanoidAnimController)AnimController).Crouching = false; humanAnimController.Crouching = false;
} }
AnimController.ResetPullJoints(); AnimController.ResetPullJoints();
SelectedConstruction = null; SelectedConstruction = null;
@@ -3505,9 +3503,9 @@ namespace Barotrauma
} }
} }
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null) public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null, float damageMultiplier = 1f)
{ {
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker); return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker, damageMultiplier: damageMultiplier);
} }
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1) public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1)
@@ -4351,7 +4349,7 @@ namespace Barotrauma
return GiveTalent(talentPrefab, addingFirstTime); return GiveTalent(talentPrefab, addingFirstTime);
} }
private bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true) public bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true)
{ {
if (addingFirstTime) if (addingFirstTime)
{ {
@@ -1,9 +1,9 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking;
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.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -14,7 +14,6 @@ namespace Barotrauma
public enum Gender { None, Male, Female }; public enum Gender { None, Male, Female };
public enum Race { None, White, Black, Brown, Asian }; public enum Race { None, White, Black, Brown, Asian };
// TODO: Generating the HeadInfo could be simplified.
partial class CharacterInfo partial class CharacterInfo
{ {
public class HeadInfo public class HeadInfo
@@ -25,15 +24,7 @@ namespace Barotrauma
get { return _headSpriteId; } get { return _headSpriteId; }
set set
{ {
_headSpriteId = value; _headSpriteId = Math.Max(Math.Clamp(value, (int)headSpriteRange.X, (int)headSpriteRange.Y), 1);
if (_headSpriteId < (int)headSpriteRange.X)
{
_headSpriteId = (int)headSpriteRange.Y;
}
if (_headSpriteId > (int)headSpriteRange.Y)
{
_headSpriteId = (int)headSpriteRange.X;
}
GetSpriteSheetIndex(); GetSpriteSheetIndex();
} }
} }
@@ -42,6 +33,10 @@ namespace Barotrauma
public Gender gender; public Gender gender;
public Race race; public Race race;
public Color HairColor;
public Color FacialHairColor;
public Color SkinColor;
public int HairIndex { get; set; } = -1; public int HairIndex { get; set; } = -1;
public int BeardIndex { get; set; } = -1; public int BeardIndex { get; set; } = -1;
public int MoustacheIndex { get; set; } = -1; public int MoustacheIndex { get; set; } = -1;
@@ -74,11 +69,11 @@ namespace Barotrauma
FaceAttachmentIndex = -1; FaceAttachmentIndex = -1;
} }
private void GetSpriteSheetIndex() public void GetSpriteSheetIndex()
{ {
if (heads != null && heads.Any()) if (heads != null && heads.Any())
{ {
var matchingHead = heads.Keys.FirstOrDefault(h => h.Gender == gender && h.Race == race && h.ID == _headSpriteId); var matchingHead = heads.Keys.FirstOrDefault(h => h.ID == HeadSpriteId && IsMatchingGender(h.Gender, gender) && IsMatchingRace(h.Race, race));
if (matchingHead != null) if (matchingHead != null)
{ {
if (heads.TryGetValue(matchingHead, out Vector2 index)) if (heads.TryGetValue(matchingHead, out Vector2 index))
@@ -99,14 +94,13 @@ namespace Barotrauma
if (head != value && value != null) if (head != value && value != null)
{ {
head = value; head = value;
if (head.race == Race.None) if (!IsValidRace(head.race))
{ {
head.race = GetRandomRace(Rand.RandSync.Unsynced); head.race = GetRandomRace(Rand.RandSync.Unsynced);
} }
CalculateHeadSpriteRange(); CalculateHeadSpriteRange();
Head.HeadSpriteId = value.HeadSpriteId; Head.HeadSpriteId = value.HeadSpriteId;
HeadSprite = null; RefreshHeadSprites();
AttachmentSprites = null;
} }
} }
} }
@@ -157,7 +151,9 @@ namespace Barotrauma
public bool HasNickname => Name != OriginalName; public bool HasNickname => Name != OriginalName;
public string OriginalName { get; private set; } public string OriginalName { get; private set; }
public string Name; public string Name;
public string DisplayName public string DisplayName
{ {
get get
@@ -387,29 +383,31 @@ namespace Barotrauma
set set
{ {
Head.HeadSpriteId = value; Head.HeadSpriteId = value;
HeadSprite = null;
AttachmentSprites = null;
ResetHeadAttachments(); ResetHeadAttachments();
RefreshHeadSprites();
} }
} }
public readonly bool HasGenders; public readonly bool HasGenders;
public readonly bool HasRaces;
public Gender Gender public Gender Gender
{ {
get { return Head.gender; } get { return Head.gender; }
set set
{ {
if (Head.gender == value) return; Gender previousValue = Head.gender;
Head.gender = value; Head.gender = value;
if (Head.gender == Gender.None) if (!IsValidGender(Head.gender))
{ {
Head.gender = Gender.Male; Head.gender = GetDefaultGender();
}
if (Head.gender != previousValue)
{
CalculateHeadSpriteRange();
ResetHeadAttachments();
RefreshHeadSprites();
} }
CalculateHeadSpriteRange();
ResetHeadAttachments();
HeadSprite = null;
AttachmentSprites = null;
} }
} }
@@ -418,28 +416,82 @@ namespace Barotrauma
get { return Head.race; } get { return Head.race; }
set set
{ {
if (Head.race == value) { return; } Race previousValue = Head.race;
Head.race = value; Head.race = value;
if (Head.race == Race.None) if (!IsValidRace(Head.race))
{ {
Head.race = Race.White; Head.race = GetDefaultRace();
}
if (Head.race != previousValue)
{
CalculateHeadSpriteRange();
ResetHeadAttachments();
RefreshHeadSprites();
} }
CalculateHeadSpriteRange();
ResetHeadAttachments();
HeadSprite = null;
AttachmentSprites = null;
} }
} }
public int HairIndex { get => Head.HairIndex; set => Head.HairIndex = value; } private bool IsValidRace(Race race) => HasRaces ? race != Race.None : race == Race.None;
public int BeardIndex { get => Head.BeardIndex; set => Head.BeardIndex = value; }
public int MoustacheIndex { get => Head.MoustacheIndex; set => Head.MoustacheIndex = value; }
public int FaceAttachmentIndex { get => Head.FaceAttachmentIndex; set => Head.FaceAttachmentIndex = value; }
public XElement HairElement { get => Head.HairElement; set => Head.HairElement = value; } private bool IsValidGender(Gender gender) => HasGenders ? gender != Gender.None : gender == Gender.None;
public XElement BeardElement { get => Head.BeardElement; set => Head.BeardElement = value; }
public XElement MoustacheElement { get => Head.MoustacheElement; set => Head.MoustacheElement = value; } private Gender GetDefaultGender() => HasGenders ? Gender.Male : Gender.None;
public XElement FaceAttachment { get => Head.FaceAttachment; set => Head.FaceAttachment = value; }
private Race GetDefaultRace() => HasRaces ? Race.White : Race.None;
public int HairIndex
{
get => Head.HairIndex;
set => Head.HairIndex = value;
}
public int BeardIndex
{
get => Head.BeardIndex;
set => Head.BeardIndex = value;
}
public int MoustacheIndex
{
get => Head.MoustacheIndex;
set => Head.MoustacheIndex = value;
}
public int FaceAttachmentIndex
{
get => Head.FaceAttachmentIndex;
set => Head.FaceAttachmentIndex = value;
}
public readonly ImmutableArray<Color> HairColors;
public readonly ImmutableArray<Color> FacialHairColors;
public readonly ImmutableArray<Color> SkinColors;
public Color HairColor
{
get => Head.HairColor;
set => Head.HairColor = value;
}
public Color FacialHairColor
{
get => Head.FacialHairColor;
set => Head.FacialHairColor = value;
}
public Color SkinColor
{
get => Head.SkinColor;
set => Head.SkinColor = value;
}
public XElement HairElement => Head.HairElement;
public XElement BeardElement => Head.BeardElement;
public XElement MoustacheElement => Head.MoustacheElement;
public XElement FaceAttachment => Head.FaceAttachment;
private RagdollParams ragdoll; private RagdollParams ragdoll;
public RagdollParams Ragdoll public RagdollParams Ragdoll
@@ -480,16 +532,18 @@ namespace Barotrauma
if (doc == null) { return; } if (doc == null) { return; }
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root; CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
// TODO: support for variants // TODO: support for variants
head = new HeadInfo(); Head = new HeadInfo();
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false); HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
if (HasGenders) Head.gender = GetRandomGender(randSync);
{ HasRaces = CharacterConfigElement.GetAttributeBool("races", false);
Head.gender = GetRandomGender(randSync);
}
Head.race = GetRandomRace(randSync); Head.race = GetRandomRace(randSync);
CalculateHeadSpriteRange(); CalculateHeadSpriteRange();
Head.HeadSpriteId = GetRandomHeadID(randSync); HeadSpriteId = GetRandomHeadID(randSync);
Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant); Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant);
HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray();
SetColors();
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
{ {
@@ -502,23 +556,7 @@ namespace Barotrauma
else else
{ {
name = ""; name = "";
if (CharacterConfigElement.Element("name") != null) Name = GetRandomName(randSync);
{
string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
Name = ToolBox.GetRandomLine(firstNamePath, randSync);
}
string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (Name != "") Name += " ";
Name += ToolBox.GetRandomLine(lastNamePath, randSync);
}
}
} }
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name; OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId); personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
@@ -530,6 +568,53 @@ namespace Barotrauma
LoadHeadAttachments(); LoadHeadAttachments();
} }
public string GetRandomName(Rand.RandSync randSync)
{
string name = "";
if (CharacterConfigElement.Element("name") != null)
{
string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
name = ToolBox.GetRandomLine(firstNamePath, randSync);
}
string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (name != "") { name += " "; }
name += ToolBox.GetRandomLine(lastNamePath, randSync);
}
}
return name;
}
private void SetColors()
{
HairColor = HairColors.GetRandom();
FacialHairColor = FacialHairColors.GetRandom();
SkinColor = SkinColors.GetRandom();
}
private void CheckColors()
{
if (HairColor == Color.Black)
{
HairColor = HairColors.GetRandom();
}
if (FacialHairColor == Color.Black)
{
FacialHairColor = FacialHairColors.GetRandom();
}
if (SkinColor == Color.Black)
{
SkinColor = SkinColors.GetRandom();
}
}
// Used for loading the data // Used for loading the data
public CharacterInfo(XElement infoElement) public CharacterInfo(XElement infoElement)
{ {
@@ -542,7 +627,7 @@ namespace Barotrauma
ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0); ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0);
UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true)); UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true));
AdditionalTalentPoints = infoElement.GetAttributeInt("additionaltalentpoints", 0); AdditionalTalentPoints = infoElement.GetAttributeInt("additionaltalentpoints", 0);
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race); Enum.TryParse(infoElement.GetAttributeString("race", "None"), true, out Race race);
Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender); Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender);
_speciesName = infoElement.GetAttributeString("speciesname", null); _speciesName = infoElement.GetAttributeString("speciesname", null);
XDocument doc = null; XDocument doc = null;
@@ -560,14 +645,19 @@ namespace Barotrauma
// TODO: support for variants // TODO: support for variants
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root; CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false); HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
if (HasGenders && gender == Gender.None) HasRaces = CharacterConfigElement.GetAttributeBool("hasraces", false);
if (!IsValidGender(gender))
{ {
gender = GetRandomGender(Rand.RandSync.Unsynced); gender = GetRandomGender(Rand.RandSync.Unsynced);
} }
else if (!HasGenders) if (!IsValidRace(race))
{ {
gender = Gender.None; race = GetRandomRace(Rand.RandSync.Unsynced);
} }
HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray();
RecreateHead( RecreateHead(
infoElement.GetAttributeInt("headspriteid", 1), infoElement.GetAttributeInt("headspriteid", 1),
race, race,
@@ -577,6 +667,11 @@ namespace Barotrauma
infoElement.GetAttributeInt("moustacheindex", -1), infoElement.GetAttributeInt("moustacheindex", -1),
infoElement.GetAttributeInt("faceattachmentindex", -1)); infoElement.GetAttributeInt("faceattachmentindex", -1));
SkinColor = infoElement.GetAttributeColor("skincolor", Color.White);
HairColor = infoElement.GetAttributeColor("haircolor", Color.White);
FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White);
CheckColors();
if (string.IsNullOrEmpty(Name)) if (string.IsNullOrEmpty(Name))
{ {
if (CharacterConfigElement.Element("name") != null) if (CharacterConfigElement.Element("name") != null)
@@ -652,8 +747,25 @@ namespace Barotrauma
LoadHeadAttachments(); LoadHeadAttachments();
} }
public Gender GetRandomGender(Rand.RandSync randSync) => (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male; public Gender GetRandomGender(Rand.RandSync randSync)
public Race GetRandomRace(Rand.RandSync randSync) => new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync); {
if (HasGenders)
{
return (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
}
return Gender.None;
}
public Race GetRandomRace(Rand.RandSync randSync)
{
if (HasRaces)
{
return new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync);
}
return Race.None;
}
public int GetRandomHeadID(Rand.RandSync randSync) => Head.headSpriteRange != Vector2.Zero ? Rand.Range((int)Head.headSpriteRange.X, (int)Head.headSpriteRange.Y + 1, randSync) : 0; public int GetRandomHeadID(Rand.RandSync randSync) => Head.headSpriteRange != Vector2.Zero ? Rand.Range((int)Head.headSpriteRange.X, (int)Head.headSpriteRange.Y + 1, randSync) : 0;
private List<XElement> hairs; private List<XElement> hairs;
@@ -720,10 +832,13 @@ namespace Barotrauma
{ {
if (elements == null) { return elements; } if (elements == null) { return elements; }
return elements.Where(w => return elements.Where(w =>
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == gender && IsMatchingGender(Enum.Parse<Gender>(w.GetAttributeString("gender", "None"), ignoreCase: true), gender) &&
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == race); IsMatchingRace(Enum.Parse<Race>(w.GetAttributeString("race", "None"), ignoreCase: true), race));
} }
public static bool IsMatchingGender(Gender gender, Gender myGender) => gender == Gender.None || gender == myGender;
public static bool IsMatchingRace(Race race, Race myRace) => race == Race.None || race == myRace;
private void LoadHeadPresets() private void LoadHeadPresets()
{ {
if (CharacterConfigElement == null) { return; } if (CharacterConfigElement == null) { return; }
@@ -752,9 +867,16 @@ namespace Barotrauma
// If there are any head presets defined, use them. // If there are any head presets defined, use them.
if (heads.Any()) if (heads.Any())
{ {
var ids = heads.Keys.Where(h => h.Race == Race && h.Gender == Gender).Select(w => w.ID); var ids = heads.Keys.Where(h => IsMatchingRace(Race, h.Race) && IsMatchingGender(Gender, h.Gender)).Select(w => w.ID);
ids = ids.OrderBy(id => id); ids = ids.OrderBy(id => id);
Head.headSpriteRange = new Vector2(ids.First(), ids.Last()); if (ids.Any())
{
Head.headSpriteRange = new Vector2(ids.First(), ids.Last());
}
else
{
DebugConsole.ThrowError($"[CharacterInfo] Couldn't find a head definition that matches {Race} and {Gender}!");
}
} }
// Else we calculate the range from the wearables. // Else we calculate the range from the wearables.
if (Head.headSpriteRange == Vector2.Zero) if (Head.headSpriteRange == Vector2.Zero)
@@ -787,26 +909,72 @@ namespace Barotrauma
} }
} }
public void RecreateHead(HeadInfo headInfo)
{
RecreateHead(
headInfo.HeadSpriteId,
headInfo.race,
headInfo.gender,
headInfo.HairIndex,
headInfo.BeardIndex,
headInfo.MoustacheIndex,
headInfo.FaceAttachmentIndex);
SkinColor = headInfo.SkinColor;
HairColor = headInfo.HairColor;
FacialHairColor = headInfo.FacialHairColor;
CheckColors();
}
/// <summary>
/// Recreates the head info and checks that everything is valid.
/// </summary>
public void RecreateHead(int headID, Race race, Gender gender, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex) public void RecreateHead(int headID, Race race, Gender gender, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex)
{ {
if (HasGenders && gender == Gender.None) if (!IsValidGender(gender))
{ {
gender = GetRandomGender(Rand.RandSync.Unsynced); gender = GetRandomGender(Rand.RandSync.Unsynced);
} }
else if (!HasGenders) if (!IsValidRace(race))
{ {
gender = Gender.None; race = GetRandomRace(Rand.RandSync.Unsynced);
} }
if (heads == null) if (heads == null)
{ {
LoadHeadPresets(); LoadHeadPresets();
} }
head = new HeadInfo(headID, gender, race, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); Color skin = Color.Black;
Color hair = Color.Black;
Color facialHair = Color.Black;
if (head != null)
{
skin = head.SkinColor;
hair = head.HairColor;
facialHair = head.FacialHairColor;
}
head = new HeadInfo(headID, gender, race, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex)
{
SkinColor = skin,
HairColor = hair,
FacialHairColor = facialHair
};
CalculateHeadSpriteRange(); CalculateHeadSpriteRange();
ReloadHeadAttachments(); ReloadHeadAttachments();
RefreshHead();
} }
public void LoadHeadSprite() /// <summary>
/// Reloads the head sprite and the attachment sprites.
/// </summary>
public void RefreshHead()
{
ReloadHeadAttachments();
RefreshHeadSprites();
}
partial void LoadHeadSpriteProjectSpecific(XElement limbElement);
private void LoadHeadSprite()
{ {
foreach (XElement limbElement in Ragdoll.MainElement.Elements()) foreach (XElement limbElement in Ragdoll.MainElement.Elements())
{ {
@@ -816,6 +984,7 @@ namespace Barotrauma
if (spriteElement == null) { continue; } if (spriteElement == null) { continue; }
string spritePath = spriteElement.Attribute("texture").Value; string spritePath = spriteElement.Attribute("texture").Value;
if (string.IsNullOrEmpty(spritePath)) { continue; }
spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant()); spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant());
@@ -823,6 +992,8 @@ namespace Barotrauma
string fileName = Path.GetFileNameWithoutExtension(spritePath); string fileName = Path.GetFileNameWithoutExtension(spritePath);
if (string.IsNullOrEmpty(fileName)) { continue; }
//go through the files in the directory to find a matching sprite //go through the files in the directory to find a matching sprite
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath))) foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
{ {
@@ -847,13 +1018,12 @@ namespace Barotrauma
break; break;
} }
LoadHeadSpriteProjectSpecific(limbElement);
break; break;
} }
} }
/// <summary>
/// Loads only the elements according to the indices, not the sprites.
/// </summary>
public void LoadHeadAttachments() public void LoadHeadAttachments()
{ {
if (Wearables != null) if (Wearables != null)
@@ -1138,7 +1308,7 @@ namespace Barotrauma
new XAttribute("name", Name), new XAttribute("name", Name),
new XAttribute("originalname", OriginalName), new XAttribute("originalname", OriginalName),
new XAttribute("speciesname", SpeciesName), new XAttribute("speciesname", SpeciesName),
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"), new XAttribute("gender", Head.gender.ToString()),
new XAttribute("race", Head.race.ToString()), new XAttribute("race", Head.race.ToString()),
new XAttribute("salary", Salary), new XAttribute("salary", Salary),
new XAttribute("experiencepoints", ExperiencePoints), new XAttribute("experiencepoints", ExperiencePoints),
@@ -1149,6 +1319,9 @@ namespace Barotrauma
new XAttribute("beardindex", BeardIndex), new XAttribute("beardindex", BeardIndex),
new XAttribute("moustacheindex", MoustacheIndex), new XAttribute("moustacheindex", MoustacheIndex),
new XAttribute("faceattachmentindex", FaceAttachmentIndex), new XAttribute("faceattachmentindex", FaceAttachmentIndex),
new XAttribute("skincolor", XMLExtensions.ColorToString(SkinColor)),
new XAttribute("haircolor", XMLExtensions.ColorToString(HairColor)),
new XAttribute("facialhaircolor", XMLExtensions.ColorToString(FacialHairColor)),
new XAttribute("startitemsgiven", StartItemsGiven), new XAttribute("startitemsgiven", StartItemsGiven),
new XAttribute("ragdoll", ragdollFileName), new XAttribute("ragdoll", ragdollFileName),
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name)); new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
@@ -1462,13 +1635,19 @@ namespace Barotrauma
if (healthData != null) { character?.CharacterHealth.Load(healthData); } if (healthData != null) { character?.CharacterHealth.Load(healthData); }
} }
public void ReloadHeadAttachments() /// <summary>
/// Reloads the attachment xml elements according to the indices. Doesn't reload the sprites.
/// </summary>
private void ReloadHeadAttachments()
{ {
ResetLoadedAttachments(); ResetLoadedAttachments();
LoadHeadAttachments(); LoadHeadAttachments();
} }
public void ResetHeadAttachments() /// <summary>
/// Loads only the elements according to the indices, not the sprites.
/// </summary>
private void ResetHeadAttachments()
{ {
ResetAttachmentIndices(); ResetAttachmentIndices();
ResetLoadedAttachments(); ResetLoadedAttachments();
@@ -1500,6 +1679,12 @@ namespace Barotrauma
AttachmentSprites = null; AttachmentSprites = null;
} }
private void RefreshHeadSprites()
{
HeadSprite = null;
AttachmentSprites = null;
}
// This could maybe be a LookUp instead? // This could maybe be a LookUp instead?
private readonly Dictionary<StatTypes, List<SavedStatValue>> savedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>(); private readonly Dictionary<StatTypes, List<SavedStatValue>> savedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>();
@@ -1541,7 +1726,7 @@ namespace Barotrauma
} }
} }
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue) public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false)
{ {
if (!savedStatValues.ContainsKey(statType)) if (!savedStatValues.ContainsKey(statType))
{ {
@@ -1550,7 +1735,7 @@ namespace Barotrauma
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{ {
savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue); savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue);
} }
else else
{ {
@@ -628,6 +628,11 @@ namespace Barotrauma
Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", ""); Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", "");
IsBuff = element.GetAttributeBool("isbuff", false); IsBuff = element.GetAttributeBool("isbuff", false);
if (element.Attribute("nameidentifier") != null)
{
Name = TextManager.Get(element.GetAttributeString("nameidentifier", string.Empty), returnNull: true) ?? Name;
}
LimbSpecific = element.GetAttributeBool("limbspecific", false); LimbSpecific = element.GetAttributeBool("limbspecific", false);
if (!LimbSpecific) if (!LimbSpecific)
{ {
@@ -669,6 +674,15 @@ namespace Barotrauma
case "afflictionoverlay": case "afflictionoverlay":
AfflictionOverlay = new Sprite(subElement); AfflictionOverlay = new Sprite(subElement);
break; break;
case "statvalue":
DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.");
break;
case "effect":
case "periodiceffect":
break;
default:
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})");
break;
} }
} }
@@ -979,7 +979,7 @@ namespace Barotrauma
float minSuitability = -10, maxSuitability = 10; float minSuitability = -10, maxSuitability = 10;
foreach (Affliction affliction in getAfflictions(limb)) foreach (Affliction affliction in getAfflictions(limb))
{ {
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } if (affliction.Strength <= affliction.Prefab.TreatmentThreshold) { continue; }
if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; } if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability) foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{ {
@@ -1088,7 +1088,7 @@ namespace Barotrauma
/// Automatically filters out buffs. /// Automatically filters out buffs.
/// </summary> /// </summary>
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions, bool excludeBuffs = true) => public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions, bool excludeBuffs = true) =>
afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength); afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength / a.Prefab.MaxStrength);
public void Save(XElement healthElement) public void Save(XElement healthElement)
{ {
@@ -275,7 +275,8 @@ namespace Barotrauma
Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X)); Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X));
ClothingElement = element.GetChildElement("PortraitClothing"); // Disabled on purpose, TODO: remove all references?
//ClothingElement = element.GetChildElement("PortraitClothing");
} }
@@ -744,7 +744,7 @@ namespace Barotrauma
} }
if (attacker != null) if (attacker != null)
{ {
var abilityAffliction = new AbilityAffliction(newAffliction); var abilityAffliction = new AbilityAfflictionCharacter(newAffliction, character);
attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction); attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction);
} }
if (applyAffliction) if (applyAffliction)
@@ -14,6 +14,7 @@ namespace Barotrauma
NotDefined, NotDefined,
Walk, Walk,
Run, Run,
Crouch,
SwimSlow, SwimSlow,
SwimFast SwimFast
} }
@@ -56,12 +57,15 @@ namespace Barotrauma
{ {
[Serialize(25.0f, true, description: "Turning speed (or rather a force applied on the main collider to make it turn). Note that you can set a limb-specific steering forces too (additional)."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] [Serialize(25.0f, true, description: "Turning speed (or rather a force applied on the main collider to make it turn). Note that you can set a limb-specific steering forces too (additional)."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float SteerTorque { get; set; } public float SteerTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to move the legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float LegTorque { get; set; }
} }
abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams> abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams>
{ {
public string SpeciesName { get; private set; } public string SpeciesName { get; private set; }
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run; public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run || AnimationType == AnimationType.Crouch;
public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast; public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast;
protected static Dictionary<string, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<string, Dictionary<string, AnimationParams>>(); protected static Dictionary<string, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<string, Dictionary<string, AnimationParams>>();
@@ -110,8 +114,18 @@ namespace Barotrauma
} }
} }
} }
public float TorsoAngleInRadians { get; private set; } = float.NaN; public float TorsoAngleInRadians { get; private set; } = float.NaN;
[Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float TorsoTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float FootTorque { get; set; }
[Serialize(AnimationType.NotDefined, true), Editable] [Serialize(AnimationType.NotDefined, true), Editable]
public virtual AnimationType AnimationType { get; protected set; } public virtual AnimationType AnimationType { get; protected set; }
@@ -402,6 +416,8 @@ namespace Barotrauma
return typeof(HumanWalkParams); return typeof(HumanWalkParams);
case AnimationType.Run: case AnimationType.Run:
return typeof(HumanRunParams); return typeof(HumanRunParams);
case AnimationType.Crouch:
return typeof(HumanCrouchParams);
case AnimationType.SwimSlow: case AnimationType.SwimSlow:
return typeof(HumanSwimSlowParams); return typeof(HumanSwimSlowParams);
case AnimationType.SwimFast: case AnimationType.SwimFast:
@@ -87,18 +87,9 @@ namespace Barotrauma
[Serialize(8.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] [Serialize(8.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootMoveForce { get; set; } public float FootMoveForce { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float TorsoTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] [Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float TailTorque { get; set; } public float TailTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float FootTorque { get; set; }
[Serialize(0.0f, true, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000)] [Serialize(0.0f, true, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float LegTorque { get; set; } public float LegTorque { get; set; }
@@ -173,20 +164,12 @@ namespace Barotrauma
[Editable, Serialize(true, true, description: "Should the character face towards the direction it's heading.")] [Editable, Serialize(true, true, description: "Should the character face towards the direction it's heading.")]
public bool RotateTowardsMovement { get; set; } public bool RotateTowardsMovement { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
public float TorsoTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)] [Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
public float TailTorque { get; set; } public float TailTorque { get; set; }
[Serialize(1f, true, description: "Multiplier applied based on the angle difference between the tail and the main limb. Increasing the value prevents snake-like characters from getting tangled on themselves. Default = 1 (no boost)"), Editable(MinValueFloat = 1, MaxValueFloat = 100)] [Serialize(1f, true, description: "Multiplier applied based on the angle difference between the tail and the main limb. Increasing the value prevents snake-like characters from getting tangled on themselves. Default = 1 (no boost)"), Editable(MinValueFloat = 1, MaxValueFloat = 100)]
public float TailTorqueMultiplier { get; set; } public float TailTorqueMultiplier { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float FootTorque { get; set; }
[Serialize(null, true), Editable] [Serialize(null, true), Editable]
public string FootAngles public string FootAngles
@@ -224,10 +207,7 @@ namespace Barotrauma
Dictionary<int, float> FootAnglesInRadians { get; set; } Dictionary<int, float> FootAnglesInRadians { get; set; }
float TailAngle { get; set; } float TailAngle { get; set; }
float TailAngleInRadians { get; } float TailAngleInRadians { get; }
float HeadTorque { get; set; }
float TorsoTorque { get; set; }
float TailTorque { get; set; } float TailTorque { get; set; }
float FootTorque { get; set; }
bool Flip { get; set; } bool Flip { get; set; }
float FlipCooldown { get; set; } float FlipCooldown { get; set; }
float FlipDelay { get; set; } float FlipDelay { get; set; }
@@ -24,6 +24,17 @@ namespace Barotrauma
public override void StoreSnapshot() => StoreSnapshot<HumanRunParams>(); public override void StoreSnapshot() => StoreSnapshot<HumanRunParams>();
} }
class HumanCrouchParams : HumanGroundedParams
{
public static HumanCrouchParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanCrouchParams>(character, AnimationType.Crouch);
public static HumanCrouchParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<HumanCrouchParams>(character.SpeciesName, AnimationType.Crouch, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<HumanCrouchParams>();
}
class HumanSwimFastParams: HumanSwimParams class HumanSwimFastParams: HumanSwimParams
{ {
public static HumanSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimFastParams>(character, AnimationType.SwimFast); public static HumanSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimFastParams>(character, AnimationType.SwimFast);
@@ -58,9 +69,6 @@ namespace Barotrauma
[Serialize("0.5, 0.1", true), Editable(DecimalCount = 2)] [Serialize("0.5, 0.1", true), Editable(DecimalCount = 2)]
public Vector2 HandMoveAmount { get; set; } public Vector2 HandMoveAmount { get; set; }
[Serialize(0.5f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(5.0f, true), Editable] [Serialize(5.0f, true), Editable]
public float HandCycleSpeed { get; set; } public float HandCycleSpeed { get; set; }
@@ -81,8 +89,17 @@ namespace Barotrauma
} }
public float FootAngleInRadians { get; private set; } public float FootAngleInRadians { get; private set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] [Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 20, DecimalCount = 2)]
public float FootRotateStrength { get; set; } public float ArmMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the arms to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float ArmIKStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the hands to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandIKStrength { get; set; }
} }
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
@@ -90,28 +107,6 @@ namespace Barotrauma
[Serialize(0.3f, true, description: "How much force is used to force the character upright."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)] [Serialize(0.3f, true, description: "How much force is used to force the character upright."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
public float GetUpForce { get; set; } public float GetUpForce { get; set; }
// -- TODO: use a separate clip for crawling -> replace these when implemented.
[Serialize(0.65f, true, description: "Height of the torso when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
public float CrouchingTorsoPos { get; set; }
[Serialize(0.65f, true, description: "Height of the head when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
public float CrouchingHeadPos { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true, description: "Angle of the torso when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float CrouchingTorsoAngle { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true, description: "Angle of the head when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float CrouchingHeadAngle { get; set; }
// --
[Serialize(0.25f, true, description: "How much the character's head leans forwards when moving."), Editable(DecimalCount = 2)] [Serialize(0.25f, true, description: "How much the character's head leans forwards when moving."), Editable(DecimalCount = 2)]
public float HeadLeanAmount { get; set; } public float HeadLeanAmount { get; set; }
@@ -121,6 +116,9 @@ namespace Barotrauma
[Serialize(15.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] [Serialize(15.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootMoveStrength { get; set; } public float FootMoveStrength { get; set; }
[Serialize(0f, true, description: "How much the horizontal difference of waist and the foot positions has an effect to lifting the foot."), Editable(DecimalCount = 2, ValueStep = 0.1f, MinValueFloat = 0f, MaxValueFloat = 1f)]
public float FootLiftHorizontalFactor { get; set; }
/// <summary> /// <summary>
/// In degrees. /// In degrees.
/// </summary> /// </summary>
@@ -135,15 +133,9 @@ namespace Barotrauma
} }
public float FootAngleInRadians { get; private set; } public float FootAngleInRadians { get; private set; }
[Serialize(20.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootRotateStrength { get; set; }
[Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)] [Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)]
public Vector2 FootMoveOffset { get; set; } public Vector2 FootMoveOffset { get; set; }
[Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)]
public Vector2 CrouchingFootMoveOffset { get; set; }
[Serialize(10.0f, true, description: "How much torque is used to bend the characters legs when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] [Serialize(10.0f, true, description: "How much torque is used to bend the characters legs when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float LegBendTorque { get; set; } public float LegBendTorque { get; set; }
@@ -153,17 +145,33 @@ namespace Barotrauma
[Serialize("-0.15, 0.0", true, description: "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them."), Editable(DecimalCount = 2)] [Serialize("-0.15, 0.0", true, description: "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them."), Editable(DecimalCount = 2)]
public Vector2 HandMoveOffset { get; set; } public Vector2 HandMoveOffset { get; set; }
[Serialize(0.7f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(-1.0f, true, description: "The position of the hands is clamped below this (relative to the position of the character's torso)."), Editable(DecimalCount = 2)] [Serialize(-1.0f, true, description: "The position of the hands is clamped below this (relative to the position of the character's torso)."), Editable(DecimalCount = 2)]
public float HandClampY { get; set; } public float HandClampY { get; set; }
[Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float ArmMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the arms to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float ArmIKStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the hands to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandIKStrength { get; set; }
} }
public interface IHumanAnimation public interface IHumanAnimation
{ {
float FootAngle { get; set; } float FootAngle { get; set; }
float FootAngleInRadians { get; } float FootAngleInRadians { get; }
float FootRotateStrength { get; set; }
float ArmMoveStrength { get; set; }
float HandMoveStrength { get; set; }
float ArmIKStrength { get; set; }
float HandIKStrength { get; set; }
} }
} }
@@ -34,6 +34,9 @@ namespace Barotrauma
[Serialize("", true, description: "Default path for the limb sprite textures. Used only if the limb specific path for the limb is not defined"), Editable] [Serialize("", true, description: "Default path for the limb sprite textures. Used only if the limb specific path for the limb is not defined"), Editable]
public string Texture { get; set; } public string Texture { get; set; }
[Serialize("1.0,1.0,1.0,1.0", true), Editable()]
public Color Color { get; set; }
[Serialize(0.0f, true, description: "The orientation of the sprites as drawn on the sprite sheet. Can be overridden by setting a value for Limb's 'Sprite Orientation'. Used mainly for animations and widgets."), Editable(-360, 360)] [Serialize(0.0f, true, description: "The orientation of the sprites as drawn on the sprite sheet. Can be overridden by setting a value for Limb's 'Sprite Orientation'. Used mainly for animations and widgets."), Editable(-360, 360)]
public float SpritesheetOrientation { get; set; } public float SpritesheetOrientation { get; set; }
@@ -556,7 +559,7 @@ namespace Barotrauma
} }
} }
public override string GenerateName() => $"Limb {ID}"; public override string GenerateName() => Type != LimbType.None ? $"{Type} ({ID})" : $"Limb {ID}";
public SpriteParams GetSprite() => deformSpriteParams ?? normalSpriteParams; public SpriteParams GetSprite() => deformSpriteParams ?? normalSpriteParams;
@@ -574,7 +577,7 @@ namespace Barotrauma
[Serialize("", true), Editable] [Serialize("", true), Editable]
public string Notes { get; set; } public string Notes { get; set; }
[Serialize(1f, true), Editable] [Serialize(1f, true), Editable(DecimalCount = 2)]
public float Scale { get; set; } public float Scale { get; set; }
[Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()] [Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()]
@@ -889,6 +892,9 @@ namespace Barotrauma
[Serialize("", true), Editable()] [Serialize("", true), Editable()]
public string Texture { get; set; } public string Texture { get; set; }
[Serialize(false, true), Editable()]
public bool IgnoreTint { get; set; }
[Serialize("1.0,1.0,1.0,1.0", true), Editable()] [Serialize("1.0,1.0,1.0,1.0", true), Editable()]
public Color Color { get; set; } public Color Color { get; set; }
@@ -33,6 +33,7 @@ namespace Barotrauma.Abilities
NotSelf = 3, NotSelf = 3,
Alive = 4, Alive = 4,
Monster = 5, Monster = 5,
InFriendlySubmarine = 6,
}; };
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings) protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
@@ -80,6 +81,8 @@ namespace Barotrauma.Abilities
return !targetCharacter.IsDead; return !targetCharacter.IsDead;
case TargetType.Monster: case TargetType.Monster:
return !targetCharacter.IsHuman; return !targetCharacter.IsHuman;
case TargetType.InFriendlySubmarine:
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
default: default:
return true; return true;
} }
@@ -0,0 +1,29 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAffliction : AbilityConditionData
{
private readonly string[] afflictions;
public AbilityConditionAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true);
}
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction)
{
return afflictions.Any(a => a == affliction.Identifier);
}
else
{
LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult));
return false;
}
}
}
}
@@ -0,0 +1,41 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionLocation : AbilityConditionData
{
private readonly bool? hasOutpost;
private readonly string[] locationIdentifiers;
public AbilityConditionLocation(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
if (conditionElement.Attribute("hasoutpost") != null)
{
hasOutpost = conditionElement.GetAttributeBool("hasoutpost", false);
}
locationIdentifiers = conditionElement.GetAttributeStringArray("locationtype", new string[0]);
}
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if (abilityObject is IAbilityLocation abilityLocation)
{
if (locationIdentifiers.Any())
{
if (!locationIdentifiers.Contains(abilityLocation.Location.Type.Identifier)) { return false; }
}
if (hasOutpost.HasValue)
{
if (hasOutpost.Value != abilityLocation.Location.HasOutpost()) { return false; }
}
return true;
}
else
{
LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab));
return false;
}
}
}
}
@@ -20,6 +20,11 @@
public Mission Mission { get; set; } public Mission Mission { get; set; }
} }
interface IAbilityLocation
{
public Location Location { get; set; }
}
interface IAbilityCharacter interface IAbilityCharacter
{ {
public Character Character { get; set; } public Character Character { get; set; }
@@ -43,6 +43,17 @@ namespace Barotrauma.Abilities
public Affliction Affliction { get; set; } public Affliction Affliction { get; set; }
} }
class AbilityAfflictionCharacter : AbilityObject, IAbilityAffliction, IAbilityCharacter
{
public AbilityAfflictionCharacter(Affliction affliction, Character character)
{
Affliction = affliction;
Character = character;
}
public Character Character { get; set; }
public Affliction Affliction { get; set; }
}
class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab
{ {
public AbilityValueItem(float value, ItemPrefab itemPrefab) public AbilityValueItem(float value, ItemPrefab itemPrefab)
@@ -54,6 +65,17 @@ namespace Barotrauma.Abilities
public ItemPrefab ItemPrefab { get; set; } public ItemPrefab ItemPrefab { get; set; }
} }
class AbilityItemPrefabItem : AbilityObject, IAbilityItem, IAbilityItemPrefab
{
public AbilityItemPrefabItem(Item item, ItemPrefab itemPrefab)
{
Item = item;
ItemPrefab = itemPrefab;
}
public Item Item { get; set; }
public ItemPrefab ItemPrefab { get; set; }
}
class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString
{ {
public AbilityValueString(float value, string abilityString) public AbilityValueString(float value, string abilityString)
@@ -65,7 +87,7 @@ namespace Barotrauma.Abilities
public string String { get; set; } public string String { get; set; }
} }
class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
{ {
public AbilityValueStringCharacter(float value, string abilityString, Character character) public AbilityValueStringCharacter(float value, string abilityString, Character character)
{ {
@@ -111,6 +133,16 @@ namespace Barotrauma.Abilities
public Mission Mission { get; set; } public Mission Mission { get; set; }
} }
class AbilityLocation : AbilityObject, IAbilityLocation
{
public AbilityLocation(Location location)
{
Location = location;
}
public Location Location { get; set; }
}
// this is an exception class that should only be passed in this form, so classes that use it should cast into it directly // this is an exception class that should only be passed in this form, so classes that use it should cast into it directly
class AbilityAttackData : AbilityObject, IAbilityCharacter class AbilityAttackData : AbilityObject, IAbilityCharacter
{ {
@@ -1,5 +1,4 @@
using Barotrauma.Extensions; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -9,10 +8,12 @@ namespace Barotrauma.Abilities
class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects
{ {
private readonly bool allowSelf; private readonly bool allowSelf;
private readonly float maxDistance = float.MaxValue;
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
allowSelf = abilityElement.GetAttributeBool("allowself", true); allowSelf = abilityElement.GetAttributeBool("allowself", true);
maxDistance = abilityElement.GetAttributeFloat("maxdistance", float.MaxValue);
} }
@@ -22,6 +23,10 @@ namespace Barotrauma.Abilities
foreach (Character character in chosenCharacters) foreach (Character character in chosenCharacters)
{ {
if (maxDistance < float.MaxValue)
{
if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; }
}
ApplyEffectSpecific(character); ApplyEffectSpecific(character);
} }
} }
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
@@ -8,7 +7,7 @@ namespace Barotrauma.Abilities
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
private readonly int amount; private readonly int amount;
private StatTypes scalingStatType; private readonly StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
@@ -13,6 +13,7 @@ namespace Barotrauma.Abilities
private readonly bool removeOnDeath; private readonly bool removeOnDeath;
private readonly bool removeAfterRound; private readonly bool removeAfterRound;
private readonly bool giveOnAddingFirstTime; private readonly bool giveOnAddingFirstTime;
private readonly bool setValue;
//private readonly float maximumValue; //private readonly float maximumValue;
@@ -28,6 +29,7 @@ namespace Barotrauma.Abilities
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
setValue = abilityElement.GetAttributeBool("setvalue", false);
} }
public override void InitializeAbility(bool addingFirstTime) public override void InitializeAbility(bool addingFirstTime)
@@ -52,11 +54,11 @@ namespace Barotrauma.Abilities
{ {
if (targetAllies) if (targetAllies)
{ {
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue)); Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue));
} }
else else
{ {
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue); Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue);
} }
} }
} }
@@ -9,7 +9,7 @@ namespace Barotrauma.Abilities
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
resistanceId = abilityElement.GetAttributeString("resistanceid", ""); resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty));
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (string.IsNullOrEmpty(resistanceId)) if (string.IsNullOrEmpty(resistanceId))
@@ -0,0 +1,35 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToLevel : CharacterAbility
{
private readonly StatTypes statType;
private readonly float statPerLevel;
private readonly int maxLevel;
private float lastValue = 0f;
public CharacterAbilityModifyStatToLevel(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
statPerLevel = abilityElement.GetAttributeFloat("statperlevel", 0f);
maxLevel = abilityElement.GetAttributeInt("maxlevel", int.MaxValue);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched)
{
int level = MathHelper.Min(Character?.Info.GetCurrentLevel() ?? 0, maxLevel);
lastValue = statPerLevel * level;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -10,19 +10,24 @@ namespace Barotrauma.Abilities
private readonly List<StatusEffect> statusEffects; private readonly List<StatusEffect> statusEffects;
private readonly List<Item> openedContainers = new List<Item>(); private readonly List<Item> openedContainers = new List<Item>();
private readonly float randomChance; private readonly float randomChance;
private readonly bool oncePerContainer;
public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
randomChance = abilityElement.GetAttributeFloat("randomchance", 1f); randomChance = abilityElement.GetAttributeFloat("randomchance", 1f);
oncePerContainer = abilityElement.GetAttributeBool("oncepercontainer", false);
} }
protected override void ApplyEffect(AbilityObject abilityObject) protected override void ApplyEffect(AbilityObject abilityObject)
{ {
if ((abilityObject as IAbilityItem)?.Item is Item item) if ((abilityObject as IAbilityItem)?.Item is Item item)
{ {
if (openedContainers.Contains(item)) { return; } if (oncePerContainer)
openedContainers.Add(item); {
if (openedContainers.Contains(item)) { return; }
openedContainers.Add(item);
}
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; } if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
foreach (var statusEffect in statusEffects) foreach (var statusEffect in statusEffects)
@@ -0,0 +1,30 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityUnlockTree : CharacterAbility
{
public CharacterAbilityUnlockTree(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
public override void InitializeAbility(bool addingFirstTime)
{
if (!addingFirstTime) { return; }
if (!TalentTree.JobTalentTrees.TryGetValue(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab)));
if (subTree != null)
{
foreach (var talentOption in subTree.TalentOptionStages)
{
foreach (var talent in talentOption.Talents)
{
Character.GiveTalent(talent);
}
}
}
}
}
}
@@ -4,14 +4,14 @@ using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
class CharacterAbilityEnigmaMachine : CharacterAbility class CharacterAbilityAtmosMachine : CharacterAbility
{ {
private readonly float addedValue; private readonly float addedValue;
private readonly float multiplyValue; private readonly float multiplyValue;
private readonly string[] tags; private readonly string[] tags;
private readonly int maxMultiplyCount; private readonly int maxMultiplyCount;
public CharacterAbilityEnigmaMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
@@ -1,42 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityStonewall : CharacterAbility
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsReset;
private readonly int maxEnemyCount;
private readonly float squaredDistance;
public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset"));
maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0);
squaredDistance = MathF.Pow(abilityElement.GetAttributeFloat("distance", 0), 2);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
int numberOfEnemiesInRange = Character.CharacterList.Count(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) < squaredDistance);
foreach (var statusEffect in statusEffectsReset)
{
statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character);
}
if (conditionsMatched && numberOfEnemiesInRange > 0)
{
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character);
}
}
}
}
}
@@ -23,6 +23,7 @@ namespace Barotrauma.Abilities
characterAbility.ApplyAbilityEffect(abilityObject); characterAbility.ApplyAbilityEffect(abilityObject);
} }
} }
timesTriggered++;
} }
} }
@@ -39,6 +39,10 @@ namespace Barotrauma.Abilities
characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate); characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
} }
} }
if (conditionsMatched)
{
timesTriggered++;
}
TimeSinceLastUpdate = 0; TimeSinceLastUpdate = 0;
} }
} }
@@ -34,7 +34,7 @@ namespace Barotrauma
if (string.IsNullOrEmpty(jobIdentifier)) if (string.IsNullOrEmpty(jobIdentifier))
{ {
DebugConsole.ThrowError("No job defined for talent tree!"); DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!");
return; return;
} }
@@ -56,9 +56,10 @@
OnAllyGainMissionExperience, OnAllyGainMissionExperience,
OnGainMissionExperience, OnGainMissionExperience,
OnGainMissionMoney, OnGainMissionMoney,
OnLocationDiscovered,
OnItemDeconstructed, OnItemDeconstructed,
OnItemDeconstructedMaterial, OnItemDeconstructedMaterial,
OnItemDeconstructedRetainProbability, OnItemDeconstructedInventory,
OnStopTinkering, OnStopTinkering,
OnItemPicked, OnItemPicked,
AfterSubmarineAttacked, AfterSubmarineAttacked,
@@ -96,7 +97,6 @@
// Utility // Utility
RepairSpeed, RepairSpeed,
DeconstructorSpeedMultiplier, DeconstructorSpeedMultiplier,
TinkeringDuration,
RepairToolStructureRepairMultiplier, RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier, RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier, RepairToolDeattachTimeMultiplier,
@@ -105,6 +105,10 @@
GeneticMaterialRefineBonus, GeneticMaterialRefineBonus,
GeneticMaterialTaintedProbabilityReductionOnCombine, GeneticMaterialTaintedProbabilityReductionOnCombine,
SkillGainSpeed, SkillGainSpeed,
// Tinker
TinkeringDuration,
TinkeringStrength,
TinkeringDamage,
// Misc // Misc
ReputationGainMultiplier, ReputationGainMultiplier,
MissionMoneyGainMultiplier, MissionMoneyGainMultiplier,
@@ -114,6 +118,7 @@
Coauthor, Coauthor,
WarriorPoetMissionRuns, WarriorPoetMissionRuns,
WarriorPoetEnemiesKilled, WarriorPoetEnemiesKilled,
QuickfixRepairCount,
} }
public enum AbilityFlags public enum AbilityFlags
@@ -868,6 +868,7 @@ namespace Barotrauma
{ {
if (level == null) { return 0.0f; } if (level == null) { return 0.0f; }
var refEntity = GetRefEntity(); var refEntity = GetRefEntity();
if (refEntity == null) { return 0.0f; }
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition); Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target); var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength)) if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength))
@@ -9,10 +9,10 @@ namespace Barotrauma
{ {
partial class MineralMission : Mission partial class MineralMission : Mission
{ {
private Dictionary<string, Pair<int, float>> ResourceClusters { get; } = new Dictionary<string, Pair<int, float>>(); private readonly Dictionary<string, (int amount, float rotation)> resourceClusters = new Dictionary<string, (int amount, float rotation)>();
private Dictionary<string, List<Item>> SpawnedResources { get; } = new Dictionary<string, List<Item>>(); private readonly Dictionary<string, List<Item>> spawnedResources = new Dictionary<string, List<Item>>();
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>(); private readonly Dictionary<string, Item[]> relevantLevelResources = new Dictionary<string, Item[]>();
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>(); private readonly List<Tuple<string, Vector2>> missionClusterPositions = new List<Tuple<string, Vector2>>();
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>(); private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
@@ -20,8 +20,8 @@ namespace Barotrauma
{ {
get get
{ {
return MissionClusterPositions return missionClusterPositions
.Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1])) .Where(p => spawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(spawnedResources[p.Item1]))
.Select(p => p.Item2); .Select(p => p.Item2);
} }
} }
@@ -33,53 +33,53 @@ namespace Barotrauma
{ {
var identifier = c.GetAttributeString("identifier", null); var identifier = c.GetAttributeString("identifier", null);
if (string.IsNullOrWhiteSpace(identifier)) { continue; } if (string.IsNullOrWhiteSpace(identifier)) { continue; }
if (ResourceClusters.ContainsKey(identifier)) if (resourceClusters.ContainsKey(identifier))
{ {
ResourceClusters[identifier].First++; resourceClusters[identifier] = (resourceClusters[identifier].amount + 1, resourceClusters[identifier].rotation);
} }
else else
{ {
ResourceClusters.Add(identifier, new Pair<int, float>(1, 0.0f)); resourceClusters.Add(identifier, (1, 0.0f));
} }
} }
} }
protected override void StartMissionSpecific(Level level) protected override void StartMissionSpecific(Level level)
{ {
if (SpawnedResources.Any()) if (spawnedResources.Any())
{ {
#if DEBUG #if DEBUG
throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})"); throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})");
#else #else
DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
SpawnedResources.Clear(); spawnedResources.Clear();
#endif #endif
} }
if (RelevantLevelResources.Any()) if (relevantLevelResources.Any())
{ {
#if DEBUG #if DEBUG
throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})"); throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})");
#else #else
DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
RelevantLevelResources.Clear(); relevantLevelResources.Clear();
#endif #endif
} }
if (MissionClusterPositions.Any()) if (missionClusterPositions.Any())
{ {
#if DEBUG #if DEBUG
throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})"); throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})");
#else #else
DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
MissionClusterPositions.Clear(); missionClusterPositions.Clear();
#endif #endif
} }
caves.Clear(); caves.Clear();
if (IsClient) { return; } if (IsClient) { return; }
foreach (var kvp in ResourceClusters) foreach (var kvp in resourceClusters)
{ {
var prefab = ItemPrefab.Find(null, kvp.Key); var prefab = ItemPrefab.Find(null, kvp.Key);
if (prefab == null) if (prefab == null)
@@ -88,15 +88,14 @@ namespace Barotrauma
"couldn't find an item prefab with the identifier " + kvp.Key); "couldn't find an item prefab with the identifier " + kvp.Key);
continue; continue;
} }
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation); var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.amount, out float rotation);
if (spawnedResources.Count < kvp.Value.First) if (spawnedResources.Count < kvp.Value.amount)
{ {
DebugConsole.ThrowError("Error in MineralMission - " + DebugConsole.ThrowError("Error in MineralMission - " +
"spawned " + spawnedResources.Count + "/" + kvp.Value.First + " of " + prefab.Name); "spawned " + spawnedResources.Count + "/" + kvp.Value.amount + " of " + prefab.Name);
} }
if (spawnedResources.None()) { continue; } if (spawnedResources.None()) { continue; }
SpawnedResources.Add(kvp.Key, spawnedResources); this.spawnedResources.Add(kvp.Key, spawnedResources);
kvp.Value.Second = rotation;
foreach (Level.Cave cave in Level.Loaded.Caves) foreach (Level.Cave cave in Level.Loaded.Caves)
{ {
@@ -142,7 +141,7 @@ namespace Barotrauma
GiveReward(); GiveReward();
completed = true; completed = true;
} }
foreach (var kvp in SpawnedResources) foreach (var kvp in spawnedResources)
{ {
foreach (var i in kvp.Value) foreach (var i in kvp.Value)
{ {
@@ -152,33 +151,33 @@ namespace Barotrauma
} }
} }
} }
SpawnedResources.Clear(); spawnedResources.Clear();
RelevantLevelResources.Clear(); relevantLevelResources.Clear();
MissionClusterPositions.Clear(); missionClusterPositions.Clear();
failed = !completed && state > 0; failed = !completed && state > 0;
} }
private void FindRelevantLevelResources() private void FindRelevantLevelResources()
{ {
RelevantLevelResources.Clear(); relevantLevelResources.Clear();
foreach (var identifier in ResourceClusters.Keys) foreach (var identifier in resourceClusters.Keys)
{ {
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier && var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
i.Submarine == null && i.ParentInventory == null && i.Submarine == null && i.ParentInventory == null &&
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached))) (!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
.ToArray(); .ToArray();
RelevantLevelResources.Add(identifier, items); relevantLevelResources.Add(identifier, items);
} }
} }
private bool EnoughHaveBeenCollected() private bool EnoughHaveBeenCollected()
{ {
foreach (var kvp in ResourceClusters) foreach (var kvp in resourceClusters)
{ {
if (RelevantLevelResources.TryGetValue(kvp.Key, out var availableResources)) if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
{ {
var collected = availableResources.Count(r => HasBeenCollected(r)); var collected = availableResources.Count(r => HasBeenCollected(r));
var needed = kvp.Value.First; var needed = kvp.Value.amount;
if (collected < needed) { return false; } if (collected < needed) { return false; }
} }
else else
@@ -210,8 +209,8 @@ namespace Barotrauma
private void CalculateMissionClusterPositions() private void CalculateMissionClusterPositions()
{ {
MissionClusterPositions.Clear(); missionClusterPositions.Clear();
foreach (var kvp in SpawnedResources) foreach (var kvp in spawnedResources)
{ {
if (kvp.Value.None()) { continue; } if (kvp.Value.None()) { continue; }
var pos = Vector2.Zero; var pos = Vector2.Zero;
@@ -222,7 +221,7 @@ namespace Barotrauma
itemCount++; itemCount++;
} }
pos /= itemCount; pos /= itemCount;
MissionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos)); missionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
} }
} }
} }
@@ -180,7 +180,11 @@ namespace Barotrauma
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos)); var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
if (!path.Unreachable) if (!path.Unreachable)
{ {
preferredSpawnPos = path.Nodes[Rand.Range(0, path.Nodes.Count - 1)].WorldPosition; // spawn the sub in a random point in the path if possible var validNodes = path.Nodes.FindAll(n => !Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(n.WorldPosition))));
if (validNodes.Any())
{
preferredSpawnPos = validNodes.GetRandom().WorldPosition; // spawn the sub in a random point in the path if possible
}
} }
int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
namespace Barotrauma.Extensions namespace Barotrauma.Extensions
{ {
@@ -12,6 +11,11 @@ namespace Barotrauma.Extensions
new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value)); new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value));
} }
public static Color Multiply(this Color thisColor, Color color)
{
return new Color((byte)(thisColor.R * color.R / 255f), (byte)(thisColor.G * color.G / 255f), (byte)(thisColor.B * color.B / 255f), (byte)(thisColor.A * color.A / 255f));
}
public static Color Opaque(this Color color) public static Color Opaque(this Color color)
{ {
return new Color(color.R, color.G, color.B, (byte)255); return new Color(color.R, color.G, color.B, (byte)255);
@@ -659,16 +659,7 @@ namespace Barotrauma
} }
foreach (Location location in Map.Locations) foreach (Location location in Map.Locations)
{ {
if (location.Type != location.OriginalType) location.Reset();
{
location.ChangeType(location.OriginalType);
location.PendingLocationTypeChange = null;
}
location.CreateStore(force: true);
location.ClearMissions();
location.Discovered = false;
location.LevelData?.EventHistory?.Clear();
location.UnlockInitialMissions();
} }
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation)); Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1); Map.SelectLocation(-1);
@@ -452,9 +452,11 @@ namespace Barotrauma
StatusEffect.StopAll(); StatusEffect.StopAll();
#if CLIENT #if CLIENT
#if !DEBUG
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null; GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
#endif
if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; } if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode; if (GameMain.Client == null) { GameMain.LightManager.LosMode = GameMain.Config.LosMode; }
#endif #endif
LevelData = level?.LevelData; LevelData = level?.LevelData;
Level = level; Level = level;
@@ -661,6 +663,7 @@ namespace Barotrauma
#if SERVER #if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null); return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null);
#else #else
if (GameMain.GameSession == null) { return Enumerable.Empty<Character>(); }
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null); return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null);
#endif #endif
} }
@@ -143,14 +143,7 @@ namespace Barotrauma
return true; return true;
} }
public int CharacterHeadIndex { get; set; } internal CharacterInfo.HeadInfo PlayerCharacterCustomization { get; set; }
public int CharacterHairIndex { get; set; }
public int CharacterBeardIndex { get; set; }
public int CharacterMoustacheIndex { get; set; }
public int CharacterFaceAttachmentIndex { get; set; }
public Gender CharacterGender { get; set; }
public Race CharacterRace { get; set; }
private float aimAssistAmount; private float aimAssistAmount;
public float AimAssistAmount public float AimAssistAmount
@@ -855,171 +848,6 @@ namespace Barotrauma
UnsavedSettings = false; UnsavedSettings = false;
} }
private void SaveNewDefaultConfig()
{
XDocument doc = new XDocument();
if (doc.Root == null)
{
doc.Add(new XElement("config"));
}
doc.Root.Add(
new XAttribute("language", TextManager.Language),
new XAttribute("masterserverurl", MasterServerUrl),
new XAttribute("remotecontenturl", RemoteContentUrl),
new XAttribute("autocheckupdates", AutoCheckUpdates),
new XAttribute("musicvolume", musicVolume),
new XAttribute("soundvolume", soundVolume),
new XAttribute("microphonevolume", microphoneVolume),
new XAttribute("voicechatvolume", voiceChatVolume),
new XAttribute("voicechatcutoffprevention", VoiceChatCutoffPrevention),
new XAttribute("verboselogging", VerboseLogging),
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
new XAttribute("submarineautosave", EnableSubmarineAutoSave),
new XAttribute("maxautosaves", MaximumAutoSaves),
new XAttribute("autosaveintervalseconds", AutoSaveIntervalSeconds),
new XAttribute("subeditorbackground", XMLExtensions.ColorToString(SubEditorBackgroundColor)),
new XAttribute("subeditorundobuffer", SubEditorMaxUndoBuffer),
new XAttribute("enablesplashscreen", EnableSplashScreen),
new XAttribute("usesteammatchmaking", UseSteamMatchmaking),
new XAttribute("quickstartsub", QuickStartSubmarineName),
new XAttribute("requiresteamauthentication", RequireSteamAuthentication),
new XAttribute("aimassistamount", aimAssistAmount),
new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning));
if (!ShowUserStatisticsPrompt)
{
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
}
XElement gMode = doc.Root.Element("graphicsmode");
if (gMode == null)
{
gMode = new XElement("graphicsmode");
doc.Root.Add(gMode);
}
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
}
else
{
gMode.ReplaceAttributes(
new XAttribute("width", GraphicsWidth),
new XAttribute("height", GraphicsHeight),
new XAttribute("vsync", VSyncEnabled),
new XAttribute("framelimit", Timing.FrameLimit),
new XAttribute("displaymode", windowMode));
}
XElement gSettings = doc.Root.Element("graphicssettings");
if (gSettings == null)
{
gSettings = new XElement("graphicssettings");
doc.Root.Add(gSettings);
}
gSettings.ReplaceAttributes(
new XAttribute("particlelimit", ParticleLimit),
new XAttribute("lightmapscale", LightMapScale),
new XAttribute("chromaticaberration", ChromaticAberrationEnabled),
new XAttribute("losmode", LosMode),
new XAttribute("hudscale", HUDScale),
new XAttribute("inventoryscale", InventoryScale));
foreach (ContentPackage contentPackage in ContentPackage.CorePackages)
{
if (contentPackage.Path.Contains(VanillaContentPackagePath))
{
doc.Root.Add(new XElement("contentpackages", new XElement("core", new XAttribute("name", contentPackage.Name))));
break;
}
}
#if CLIENT
var keyMappingElement = new XElement("keymapping");
doc.Root.Add(keyMappingElement);
for (int i = 0; i < keyMapping.Length; i++)
{
KeyOrMouse bind = keyMapping[i];
if (bind.MouseButton == MouseButton.None)
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), bind.Key));
}
else
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), bind.MouseButton));
}
}
var inventoryKeyMappingElement = new XElement("inventorykeymapping");
doc.Root.Add(inventoryKeyMappingElement);
for (int i = 0; i < inventoryKeyMapping.Length; i++)
{
KeyOrMouse bind = inventoryKeyMapping[i];
if (bind.MouseButton == MouseButton.None)
{
inventoryKeyMappingElement.Add(new XAttribute($"slot{i}", bind.Key));
}
else
{
inventoryKeyMappingElement.Add(new XAttribute($"slot{i}", bind.MouseButton));
}
}
#endif
var gameplay = new XElement("gameplay");
var jobPreferences = new XElement("jobpreferences");
foreach (Pair<string, int> job in JobPreferences)
{
XElement jobElement = new XElement("job");
jobElement.Add(new XAttribute("identifier", job.First));
jobElement.Add(new XAttribute("variant", job.Second));
jobPreferences.Add(jobElement);
}
gameplay.Add(jobPreferences);
var teamPreference = new XElement("teampreference");
teamPreference.Add(new XAttribute("team", TeamPreference.ToString()));
gameplay.Add(teamPreference);
doc.Root.Add(gameplay);
var playerElement = new XElement("player",
new XAttribute("name", playerName ?? ""),
new XAttribute("headindex", CharacterHeadIndex),
new XAttribute("gender", CharacterGender),
new XAttribute("race", CharacterRace),
new XAttribute("hairindex", CharacterHairIndex),
new XAttribute("beardindex", CharacterBeardIndex),
new XAttribute("moustacheindex", CharacterMoustacheIndex),
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
doc.Root.Add(playerElement);
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true,
NewLineOnAttributes = true
};
try
{
using (var writer = XmlWriter.Create(SavePath, settings))
{
doc.WriteTo(writer);
writer.Flush();
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving game settings failed.", e);
GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
}
}
#region Load PlayerConfig #region Load PlayerConfig
public void LoadPlayerConfig() public void LoadPlayerConfig()
{ {
@@ -1307,15 +1135,20 @@ namespace Barotrauma
gameplay.Add(jobPreferences); gameplay.Add(jobPreferences);
doc.Root.Add(gameplay); doc.Root.Add(gameplay);
var playerElement = new XElement("player", var playerElement = new XElement("player", new XAttribute("name", playerName ?? ""));
new XAttribute("name", playerName ?? ""), if (PlayerCharacterCustomization != null)
new XAttribute("headindex", CharacterHeadIndex), {
new XAttribute("gender", CharacterGender), playerElement.SetAttributeValue("headindex", PlayerCharacterCustomization.HeadSpriteId);
new XAttribute("race", CharacterRace), playerElement.SetAttributeValue("gender", PlayerCharacterCustomization.gender);
new XAttribute("hairindex", CharacterHairIndex), playerElement.SetAttributeValue("race", PlayerCharacterCustomization.race);
new XAttribute("beardindex", CharacterBeardIndex), playerElement.SetAttributeValue("hairindex", PlayerCharacterCustomization.HairIndex);
new XAttribute("moustacheindex", CharacterMoustacheIndex), playerElement.SetAttributeValue("beardindex", PlayerCharacterCustomization.BeardIndex);
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex)); playerElement.SetAttributeValue("moustacheindex", PlayerCharacterCustomization.MoustacheIndex);
playerElement.SetAttributeValue("faceattachmentindex", PlayerCharacterCustomization.FaceAttachmentIndex);
playerElement.SetAttributeValue("skincolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.SkinColor));
playerElement.SetAttributeValue("haircolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.HairColor));
playerElement.SetAttributeValue("facialhaircolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.FacialHairColor));
}
doc.Root.Add(playerElement); doc.Root.Add(playerElement);
#if CLIENT #if CLIENT
@@ -1434,23 +1267,22 @@ namespace Barotrauma
if (playerElement != null) if (playerElement != null)
{ {
playerName = playerElement.GetAttributeString("name", playerName); playerName = playerElement.GetAttributeString("name", playerName);
CharacterHeadIndex = playerElement.GetAttributeInt("headindex", CharacterHeadIndex); int head = playerElement.GetAttributeInt("headindex", -1);
if (Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender g)) Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender gender);
Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race race);
int hair = playerElement.GetAttributeInt("hairindex", -1);
int beard = playerElement.GetAttributeInt("beardindex", -1);
int moustache = playerElement.GetAttributeInt("moustacheindex", -1);
int faceAttachment = playerElement.GetAttributeInt("faceattachmentindex", -1);
Color skinColor = playerElement.GetAttributeColor("skincolor", Color.Black);
Color hairColor = playerElement.GetAttributeColor("haircolor", Color.Black);
Color facialHairColor = playerElement.GetAttributeColor("facialhaircolor", Color.Black);
PlayerCharacterCustomization = new CharacterInfo.HeadInfo(head, gender, race, hair, beard, moustache, faceAttachment)
{ {
CharacterGender = g; SkinColor = skinColor,
} HairColor = hairColor,
if (Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race r)) FacialHairColor = facialHairColor
{ };
CharacterRace = r;
}
else
{
CharacterRace = Race.White;
}
CharacterHairIndex = playerElement.GetAttributeInt("hairindex", CharacterHairIndex);
CharacterBeardIndex = playerElement.GetAttributeInt("beardindex", CharacterBeardIndex);
CharacterMoustacheIndex = playerElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex);
CharacterFaceAttachmentIndex = playerElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex);
} }
} }
@@ -1656,13 +1488,7 @@ namespace Barotrauma
UseSteamMatchmaking = true; UseSteamMatchmaking = true;
RequireSteamAuthentication = true; RequireSteamAuthentication = true;
QuickStartSubmarineName = string.Empty; QuickStartSubmarineName = string.Empty;
CharacterHeadIndex = 1; PlayerCharacterCustomization = null;
CharacterHairIndex = -1;
CharacterBeardIndex = -1;
CharacterMoustacheIndex = -1;
CharacterFaceAttachmentIndex = -1;
CharacterGender = Gender.None;
CharacterRace = Race.White;
aimAssistAmount = 0.5f; aimAssistAmount = 0.5f;
EnableMouseLook = true; EnableMouseLook = true;
EnableRadialDistortion = true; EnableRadialDistortion = true;
@@ -15,14 +15,14 @@ namespace Barotrauma.Items.Components
private Character targetCharacter; private Character targetCharacter;
private AfflictionPrefab selectedEffect, selectedTaintedEffect; private AfflictionPrefab selectedEffect, selectedTaintedEffect;
[Serialize("", false)] [Serialize("", true)]
public string Effect public string Effect
{ {
get; get;
set; set;
} }
[Serialize("geneticmaterialdebuff", false)] [Serialize("geneticmaterialdebuff", true)]
public string TaintedEffect public string TaintedEffect
{ {
get; get;
@@ -30,7 +30,7 @@ namespace Barotrauma.Items.Components
} }
private bool tainted; private bool tainted;
[Serialize(false, false)] [Serialize(false, true)]
public bool Tainted public bool Tainted
{ {
get { return tainted; } get { return tainted; }
@@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components
} }
//only for saving the selected tainted effect //only for saving the selected tainted effect
[Serialize("", false)] [Serialize("", true)]
public string SelectedTaintedEffect public string SelectedTaintedEffect
{ {
get { return selectedTaintedEffect?.Identifier ?? string.Empty; } get { return selectedTaintedEffect?.Identifier ?? string.Empty; }
@@ -100,6 +100,11 @@ namespace Barotrauma.Items.Components
{ {
float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength; float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength)); character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
if (existingAffliction != null)
{
existingAffliction.Strength = selectedTaintedEffectStrength;
}
targetCharacter = character; targetCharacter = character;
#if SERVER #if SERVER
item.CreateServerEvent(this); item.CreateServerEvent(this);
@@ -111,6 +116,11 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnWearing, 1.0f); ApplyStatusEffects(ActionType.OnWearing, 1.0f);
float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength; float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength)); character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
if (existingAffliction != null)
{
existingAffliction.Strength = selectedEffectStrength;
}
targetCharacter = character; targetCharacter = character;
#if SERVER #if SERVER
item.CreateServerEvent(this); item.CreateServerEvent(this);
@@ -197,5 +207,21 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this); item.CreateServerEvent(this);
#endif #endif
} }
public static string TryCreateName(ItemPrefab prefab, XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals(nameof(GeneticMaterial), StringComparison.OrdinalIgnoreCase))
{
string nameId = subElement.GetAttributeString("nameidentifier", "");
if (!string.IsNullOrEmpty(nameId))
{
return prefab.Name.Replace("[type]", TextManager.Get(nameId, returnNull: true) ?? nameId);
}
}
}
return prefab.Name;
}
} }
} }
@@ -603,6 +603,11 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
public override bool SecondaryUse(float deltaTime, Character character = null)
{
return true;
}
private Vector2 GetAttachPosition(Character user, bool useWorldCoordinates = false) private Vector2 GetAttachPosition(Character user, bool useWorldCoordinates = false)
{ {
if (user == null) { return useWorldCoordinates ? item.WorldPosition : item.Position; } if (user == null) { return useWorldCoordinates ? item.WorldPosition : item.Position; }
@@ -61,8 +61,10 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; } if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item); Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item)
Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent()); {
DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent())
};
} }
item.IsShootable = true; item.IsShootable = true;
// TODO: should define this in xml if we have melee weapons that don't require aim to use // TODO: should define this in xml if we have melee weapons that don't require aim to use
@@ -266,16 +268,10 @@ namespace Barotrauma.Items.Components
return false; return false;
} }
Character targetCharacter = null; if (f2.Body.UserData is Limb targetLimb)
Limb targetLimb = null;
Structure targetStructure = null;
Item targetItem = null;
if (f2.Body.UserData is Limb)
{ {
targetLimb = (Limb)f2.Body.UserData;
if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; } if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; }
targetCharacter = targetLimb.character; var targetCharacter = targetLimb.character;
if (targetCharacter == picker) { return false; } if (targetCharacter == picker) { return false; }
if (AllowHitMultiple) if (AllowHitMultiple)
{ {
@@ -287,9 +283,8 @@ namespace Barotrauma.Items.Components
} }
hitTargets.Add(targetCharacter); hitTargets.Add(targetCharacter);
} }
else if (f2.Body.UserData is Character) else if (f2.Body.UserData is Character targetCharacter)
{ {
targetCharacter = (Character)f2.Body.UserData;
if (targetCharacter == picker || targetCharacter == User) { return false; } if (targetCharacter == picker || targetCharacter == User) { return false; }
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
if (AllowHitMultiple) if (AllowHitMultiple)
@@ -302,9 +297,8 @@ namespace Barotrauma.Items.Components
} }
hitTargets.Add(targetCharacter); hitTargets.Add(targetCharacter);
} }
else if (f2.Body.UserData is Structure) else if (f2.Body.UserData is Structure targetStructure)
{ {
targetStructure = (Structure)f2.Body.UserData;
if (AllowHitMultiple) if (AllowHitMultiple)
{ {
if (hitTargets.Contains(targetStructure)) { return true; } if (hitTargets.Contains(targetStructure)) { return true; }
@@ -315,9 +309,8 @@ namespace Barotrauma.Items.Components
} }
hitTargets.Add(targetStructure); hitTargets.Add(targetStructure);
} }
else if (f2.Body.UserData is Item) else if (f2.Body.UserData is Item targetItem)
{ {
targetItem = (Item)f2.Body.UserData;
if (AllowHitMultiple) if (AllowHitMultiple)
{ {
if (hitTargets.Contains(targetItem)) { return true; } if (hitTargets.Contains(targetItem)) { return true; }
@@ -350,13 +343,11 @@ namespace Barotrauma.Items.Components
Limb targetLimb = target.UserData as Limb; Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character; Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
Structure targetStructure = target.UserData as Structure;
Item targetItem = target.UserData as Item;
if (Attack != null) if (Attack != null)
{ {
Attack.SetUser(User); Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier); Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
if (targetLimb != null) if (targetLimb != null)
{ {
@@ -370,12 +361,12 @@ namespace Barotrauma.Items.Components
targetCharacter.LastDamageSource = item; targetCharacter.LastDamageSource = item;
Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f); Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
} }
else if (targetStructure != null) else if (target.UserData is Structure targetStructure)
{ {
if (targetStructure.Removed) { return; } if (targetStructure.Removed) { return; }
Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f); Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
} }
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0) else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{ {
if (targetItem.Removed) { return; } if (targetItem.Removed) { return; }
Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
@@ -196,7 +196,8 @@ namespace Barotrauma.Items.Components
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition; Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi; float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f); float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f);
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false); float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier);
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item); projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
if (i == 0) if (i == 0)
{ {
@@ -619,7 +619,7 @@ namespace Barotrauma.Items.Components
levelResource.requiredItems.Any() && levelResource.requiredItems.Any() &&
levelResource.HasRequiredItems(user, addMessage: false)) levelResource.HasRequiredItems(user, addMessage: false))
{ {
float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier); float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * (1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier));
levelResource.DeattachTimer += addedDetachTime; levelResource.DeattachTimer += addedDetachTime;
#if CLIENT #if CLIENT
Character.Controlled?.UpdateHUDProgressBar( Character.Controlled?.UpdateHUDProgressBar(
@@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components
private float userDeconstructorSpeedMultiplier = 1.0f; private float userDeconstructorSpeedMultiplier = 1.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private ItemContainer inputContainer, outputContainer; private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer public ItemContainer InputContainer
@@ -89,12 +91,20 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f) { Voltage = 1.0f; } if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
progressTimer += deltaTime * Math.Min(Voltage, 1.0f); progressTimer += deltaTime * Math.Min(Voltage, 1.0f);
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
// doesn't quite work properly, remaining time changes if tinkering stops
float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease);
if (DeconstructItemsSimultaneously) if (DeconstructItemsSimultaneously)
{ {
float deconstructTime = 0.0f; float deconstructTime = 0.0f;
foreach (Item targetItem in inputContainer.Inventory.AllItems) foreach (Item targetItem in inputContainer.Inventory.AllItems)
{ {
deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * userDeconstructorSpeedMultiplier); deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier);
} }
progressState = Math.Min(progressTimer / deconstructTime, 1.0f); progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
@@ -126,7 +136,7 @@ namespace Barotrauma.Items.Components
var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it => var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it =>
it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))); it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)));
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f; float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f); progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime) if (progressTimer > deconstructTime)
@@ -234,9 +244,13 @@ namespace Barotrauma.Items.Components
if (user != null && !user.Removed) if (user != null && !user.Removed)
{ {
var itemsCreated = new AbilityValueItem(1f, targetItem.Prefab); var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated); user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated);
amount = (int)itemsCreated.Value; amount = (int)itemsCreated.Value;
// used to spawn items directly into the deconstructor
var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer);
} }
for (int i = 0; i < amount; i++) for (int i = 0; i < amount; i++)
@@ -256,17 +270,6 @@ namespace Barotrauma.Items.Components
} }
} }
if (user != null && !user.Removed)
{
var deconstructItemRetainProbability = new AbilityValueItem(0f, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedRetainProbability, deconstructItemRetainProbability);
if (deconstructItemRetainProbability.Value > Rand.Range(0f, 1f, Rand.RandSync.Unsynced))
{
allowRemove = false;
}
}
if (targetItem.AllowDeconstruct && allowRemove) if (targetItem.AllowDeconstruct && allowRemove)
{ {
//drop all items that are inside the deconstructed item //drop all items that are inside the deconstructed item
@@ -73,6 +73,8 @@ namespace Barotrauma.Items.Components
} }
} }
private const float TinkeringForceIncrease = 1.5f;
public Engine(Item item, XElement element) public Engine(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
@@ -128,7 +130,7 @@ namespace Barotrauma.Items.Components
currForce *= maxForce * forceMultiplier; currForce *= maxForce * forceMultiplier;
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering) if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{ {
currForce *= 2.5f; currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease;
} }
//less effective when in a bad condition //less effective when in a bad condition
@@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, true)] [Serialize(1.0f, true)]
public float SkillRequirementMultiplier { get; set; } public float SkillRequirementMultiplier { get; set; }
private const float TinkeringSpeedIncrease = 1.5f;
private enum FabricatorState private enum FabricatorState
{ {
Active = 1, Active = 1,
@@ -279,7 +281,14 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0) { Voltage = 1.0f; } if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f); float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(Voltage, 1.0f);
UpdateRequiredTimeProjSpecific(); UpdateRequiredTimeProjSpecific();
@@ -329,12 +338,7 @@ namespace Barotrauma.Items.Components
} }
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem); user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
float floatQuality = 0.0f; quality = GetFabricatedItemQuality(fabricatedItem, user);
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
} }
var tempUser = user; var tempUser = user;
@@ -404,6 +408,25 @@ namespace Barotrauma.Items.Components
} }
} }
private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
{
if (user == null) { return 0; }
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
int quality = 0;
float floatQuality = 0.0f;
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
const int MaxCraftingSkill = 100;
quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0;
quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0;
return quality;
}
partial void UpdateRequiredTimeProjSpecific(); partial void UpdateRequiredTimeProjSpecific();
private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary<string, List<Item>> availableIngredients, Character character) private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary<string, List<Item>> availableIngredients, Character character)
@@ -70,6 +70,8 @@ namespace Barotrauma.Items.Components
public bool HasPower => IsActive && Voltage >= MinVoltage; public bool HasPower => IsActive && Voltage >= MinVoltage;
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f; public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
private const float TinkeringSpeedIncrease = 1.5f;
public Pump(Item item, XElement element) public Pump(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
@@ -108,7 +110,7 @@ namespace Barotrauma.Items.Components
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering) if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{ {
currFlow *= 2.5f; currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease;
} }
//less effective when in a bad condition //less effective when in a bad condition
@@ -370,7 +370,7 @@ namespace Barotrauma.Items.Components
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x"); item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y"); item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
item.SendSignal(new Signal(sub.WorldPosition.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); item.SendSignal(new Signal((sub.WorldPosition.X * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y"); item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
} }

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