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 Color disguisedJobColor;
private Sprite tintMask;
private float tintHighlightThreshold;
private float tintHighlightMultiplier;
public static void Init()
{
infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
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)
{
@@ -458,6 +476,7 @@ namespace Barotrauma
}
else
{
//TODO: disguise skin and hair colors
sheetIndex = disguisedSheetIndex;
portraitToDraw = disguisedPortrait;
attachmentsToDraw = disguisedAttachmentSprites;
@@ -465,22 +484,74 @@ namespace Barotrauma
if (portraitToDraw != null)
{
var currEffect = spriteBatch.GetCurrentEffect();
// Scale down the head sprite 10%
float scale = targetWidth * 0.9f / Portrait.size.X;
if (sheetIndex.HasValue)
{
SetHeadEffect(spriteBatch);
portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size);
}
portraitToDraw.Draw(spriteBatch, screenPos + offset, 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)
{
float depthStep = 0.000001f;
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;
}
}
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;
if (headSprite != null)
{
var currEffect = spriteBatch.GetCurrentEffect();
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
if (Head.SheetIndex.HasValue)
{
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)
{
float depthStep = 0.000001f;
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;
}
}
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)
{
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));
}
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)
{
@@ -544,7 +609,7 @@ namespace Barotrauma
attachment.Sprite.SourceRect = head.SourceRect;
}
}
Vector2 origin = attachment.Sprite.Origin;
Vector2 origin;
if (attachment.InheritOrigin)
{
origin = head.Origin;
@@ -559,7 +624,7 @@ namespace Barotrauma
{
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)
@@ -574,6 +639,9 @@ namespace Barotrauma
int beardIndex = inc.ReadByte();
int moustacheIndex = inc.ReadByte();
int faceAttachmentIndex = inc.ReadByte();
Color skinColor = inc.ReadColorR8G8B8();
Color hairColor = inc.ReadColorR8G8B8();
Color facialHairColor = inc.ReadColorR8G8B8();
string ragdollFile = inc.ReadString();
string jobIdentifier = inc.ReadString();
@@ -599,6 +667,9 @@ namespace Barotrauma
ID = infoID,
};
ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.SkinColor = skinColor;
ch.HairColor = hairColor;
ch.FacialHairColor = facialHairColor;
if (ch.Job != null)
{
foreach (KeyValuePair<string, float> skill in skillLevels)
@@ -627,5 +698,384 @@ namespace Barotrauma
ch.AdditionalTalentPoints = inc.ReadUInt16();
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);
currentDisplayedAfflictions.Sort((a1, a2) =>
{
int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond);
return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength);
int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond);
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);
updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval;
@@ -1131,6 +1136,8 @@ namespace Barotrauma
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)
{
if (selectedLimb == null)
@@ -1139,45 +1146,33 @@ namespace Barotrauma
return;
}
var currentAfflictions = GetMatchingAfflictions(selectedLimb, a => a.ShouldShowIcon(Character));
var displayedAfflictions = afflictionIconContainer.Content.Children.Select(c => c.UserData as Affliction);
if (currentAfflictions.Any(a => !displayedAfflictions.Contains(a)) ||
displayedAfflictions.Any(a => !currentAfflictions.Contains(a)))
if (currentAfflictions.Any(a => !displayedAfflictions.Any(d => d.affliction == a)) ||
displayedAfflictions.Any(a => !currentAfflictions.Contains(a.affliction)))
{
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)
{
afflictionIconContainer.ClearChildren();
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;
}
}
displayedAfflictions.Clear();
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault();
GUIButton buttonToSelect = null;
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))
{
Stretch = true,
@@ -1233,6 +1228,39 @@ namespace Barotrauma
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())
{
new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center)
@@ -1248,10 +1276,6 @@ namespace Barotrauma
recommendedTreatmentContainer.AutoHideScrollBar = true;
}
buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction");
afflictionIconContainer.RecalculateChildren();
List<KeyValuePair<string, float>> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList();
int count = 0;
@@ -1286,7 +1310,7 @@ namespace Barotrauma
new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow")
{
CanBeFocused = false,
Color = Color.White * 0.7f,
Color = GUI.Style.Green,
HoverColor = Color.White,
PressedColor = Color.DarkGray,
SelectedColor = Color.Transparent,
@@ -1304,6 +1328,12 @@ namespace Barotrauma
SelectedColor = itemColor,
DisabledColor = itemColor * 0.8f
};
if (item == prevHighlightedItem)
{
innerFrame.State = GUIComponent.ComponentState.Hover;
innerFrame.Children.ForEach(c => c.State = GUIComponent.ComponentState.Hover);
}
}
recommendedTreatmentContainer.RecalculateChildren();
@@ -1315,6 +1345,19 @@ namespace Barotrauma
int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond);
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)
@@ -120,6 +120,15 @@ namespace Barotrauma
public List<SpriteDeformation> ActiveDeformations { get; set; } = new List<SpriteDeformation>();
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;
@@ -273,6 +282,7 @@ namespace Barotrauma
DecorativeSpriteGroups[groupID].Add(decorativeSprite);
spriteAnimState.Add(decorativeSprite, new SpriteState());
}
TintMask = null;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -308,6 +318,22 @@ namespace Barotrauma
InitialLightSourceColor = LightSource.Color;
InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha;
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()
@@ -449,20 +475,20 @@ namespace Barotrauma
/// <summary>
/// Get the full path of a limb sprite, taking into account tags, gender and head id
/// </summary>
private string GetSpritePath(string texturePath)
public static string GetSpritePath(string texturePath, CharacterInfo characterInfo)
{
string spritePath = texturePath;
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("[RACE]", character.Info.Race.ToString().ToLowerInvariant());
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
spritePath = spritePath.Replace("[GENDER]", (characterInfo.Gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", characterInfo.Race.ToString().ToLowerInvariant());
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 = "";
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
characterInfo.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
spritePathWithTags = Path.Combine(
Path.GetDirectoryName(spritePath),
@@ -472,6 +498,13 @@ namespace Barotrauma
return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath;
}
private string GetSpritePath(string texturePath)
{
if (!character.IsHumanoid) { return texturePath; }
return GetSpritePath(texturePath, character?.Info);
}
partial void LoadParamsProjSpecific()
{
bool isFlipped = dir == Direction.Left;
@@ -638,13 +671,24 @@ namespace Barotrauma
var spriteParams = Params.GetSprite();
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)
{
color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
}
color = overrideColor ?? color;
blankColor = overrideColor ?? blankColor;
if (isSevered)
{
@@ -667,6 +711,8 @@ namespace Barotrauma
OtherWearables.Any(w => w.HideLimb) ||
wearingItems.Any(w => w != null && w.HideLimb);
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
var activeSprite = ActiveSprite;
if (type == LimbType.Head)
{
@@ -698,7 +744,33 @@ namespace Barotrauma
}
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);
if (useTintMask)
{
spriteBatch.SwapEffect(null);
}
}
// Handle non-exlusive, i.e. additional conditional sprites
foreach (var conditionalSprite in ConditionalSprites)
@@ -770,15 +842,36 @@ namespace Barotrauma
}
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;
}
foreach (WearableSprite wearable in OtherWearables)
{
if (wearable.Type == WearableType.Husk) { 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
depthStep += step;
}
@@ -786,7 +879,7 @@ namespace Barotrauma
foreach (WearableSprite wearable in WearingItems)
{
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
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;
if (wearable.InheritSourceRect)
@@ -955,7 +1048,7 @@ namespace Barotrauma
}
}
Vector2 origin = wearable.Sprite.Origin;
Vector2 origin;
if (wearable.InheritOrigin)
{
origin = sprite.Origin;
@@ -986,7 +1079,7 @@ namespace Barotrauma
Color wearableColor = Color.White;
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))
{
depth -= depthStep;
@@ -997,15 +1090,38 @@ namespace Barotrauma
}
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;
wearable.Sprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
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)),
origin, rotation,
Scale * textureScale, spriteEffect, depth);
float finalAlpha = alpha * wearableColor.A;
Color finalColor = color.Multiply(wearableColor);
finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha);
wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
}
private WearableSprite GetWearableSprite(WearableType type, bool random = false)
@@ -1056,6 +1172,9 @@ namespace Barotrauma
HerpesSprite?.Sprite.Remove();
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 rotation = msg.ReadSingle();
@@ -41,20 +41,20 @@ namespace Barotrauma
h.AttachToWall();
item.Rotation = rotation;
}
if (SpawnedResources.TryGetValue(item.Prefab.Identifier, out var resources))
if (spawnedResources.TryGetValue(item.Prefab.Identifier, out var resources))
{
resources.Add(item);
}
else
{
SpawnedResources.Add(item.Prefab.Identifier, new List<Item>() { item });
spawnedResources.Add(item.Prefab.Identifier, new List<Item>() { item });
}
}
}
CalculateMissionClusterPositions();
for(int i = 0; i < ResourceClusters.Count; i++)
for(int i = 0; i < resourceClusters.Count; i++)
{
var identifier = msg.ReadString();
var count = msg.ReadByte();
@@ -66,7 +66,7 @@ namespace Barotrauma
if (!(entity is Item item)) { continue; }
resources[j] = item;
}
RelevantLevelResources.Add(identifier, resources);
relevantLevelResources.Add(identifier, resources);
}
}
}
@@ -130,6 +130,7 @@ namespace Barotrauma
public static GUIStyle Style;
private static Texture2D t;
public static Texture2D WhiteTexture => t;
private static Sprite[] MouseCursorSprites => Style.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata;
@@ -862,11 +863,10 @@ namespace Barotrauma
int index = 0;
if (updateList.Count > 0)
{
index = updateList.Count - 1;
while (updateList[index].UpdateOrder > item.UpdateOrder)
index = updateList.Count;
while (index > 0 && updateList[index-1].UpdateOrder > item.UpdateOrder)
{
index--;
if (index == 0) { break; }
}
}
if (!updateListSet.Contains(item))
@@ -1057,12 +1057,15 @@ namespace Barotrauma
if (listBox.DraggedElement != null) { return CursorState.Dragging; }
if (listBox.CanDragElements) { return CursorState.Move; }
var hoverParent = c;
while (true)
if (listBox.HoverCursor != CursorState.Default)
{
if (hoverParent == parent || hoverParent == null) { break; }
if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; }
hoverParent = hoverParent.Parent;
var hoverParent = c;
while (true)
{
if (hoverParent == parent || hoverParent == null) { break; }
if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; }
hoverParent = hoverParent.Parent;
}
}
}
@@ -23,6 +23,8 @@ namespace Barotrauma
private bool selectMultiple;
public bool Dropped { get; set; }
public bool AllowNonText { get; set; }
public object SelectedItemData
{
@@ -318,9 +320,9 @@ namespace Barotrauma
if (textBlock == null)
{
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;
// 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 XElement configElement;
private readonly XElement configElement;
private GraphicsDevice graphicsDevice;
@@ -19,7 +19,7 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon;
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;
private GUIFrame infoFrame, contentFrame;
@@ -34,6 +34,8 @@ namespace Barotrauma
private List<CharacterTeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022";
private GUIFrame pendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private class LinkedGUI
@@ -134,6 +136,13 @@ namespace Barotrauma
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 (linkedGUIList == null) return;
@@ -183,16 +192,8 @@ namespace Barotrauma
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 :(
Vector2 contentFrameSize;
switch (selectedTab)
{
case InfoFrameTab.MyCharacter:
contentFrameSize = new Vector2(0.45f, 0.5f);
break;
default:
contentFrameSize = new Vector2(0.45f, 0.667f);
break;
}
//now it's not even a switch statement anymore :(
Vector2 contentFrameSize = new Vector2(0.45f, 0.667f);
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)
@@ -243,6 +244,17 @@ namespace Barotrauma
{
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
{
@@ -255,20 +267,17 @@ namespace Barotrauma
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");
}
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);
talentsButton.Enabled = Character.Controlled?.Info != null;
if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents)
{
SelectInfoFrameTab(null, InfoFrameTab.Crew);
}
};
talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton);
}
private bool SelectInfoFrameTab(GUIButton button, object userData)
@@ -300,10 +309,6 @@ namespace Barotrauma
if (traitor == null || traitorMission == null) return false;
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break;
case InfoFrameTab.MyCharacter:
if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder);
break;
case InfoFrameTab.Submarine:
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
@@ -1188,6 +1193,11 @@ namespace Barotrauma
private GUITextBlock talentPointText;
private GUIListBox skillListBox;
private GUIButton talentApplyButton,
talentResetButton;
private GUIImage talentPointNotification;
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle>
{
{ TalentTree.TalentTreeStageState.Invalid, GUI.Style.GetComponentStyle("TalentTreeLocked") },
@@ -1219,9 +1229,22 @@ namespace Barotrauma
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 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");
return;
@@ -1229,7 +1252,7 @@ namespace Barotrauma
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)
};
@@ -1241,11 +1264,11 @@ namespace Barotrauma
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);
});
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);
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);
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");
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);
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
@@ -1285,7 +1358,7 @@ namespace Barotrauma
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
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++)
{
@@ -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 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
};
@@ -1316,10 +1389,12 @@ namespace Barotrauma
{
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}",
UserData = talent.Identifier,
@@ -1361,7 +1436,7 @@ namespace Barotrauma
GUIComponent iconImage;
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,
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 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);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
@@ -1399,19 +1475,22 @@ namespace Barotrauma
IsHorizontal = true
};
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")
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight)
{
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,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
UpdateTalentButtons();
}
@@ -1419,11 +1498,12 @@ namespace Barotrauma
private void CreateTalentSkillList(Character character, GUIListBox parent)
{
parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>();
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 };
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) };
float modifiedSkillLevel = character.GetSkillLevel(skill.Identifier);
@@ -1444,6 +1524,7 @@ namespace Barotrauma
}
parent.RecalculateChildren();
GUITextBlock.AutoScaleAndNormalize(skillNames);
}
private void UpdateTalentButtons()
@@ -1068,7 +1068,6 @@ namespace Barotrauma
spriteBatch.End();
}
sw.Stop();
PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks);
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 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),
TextManager.Get("NewCampaign"), style: "GUITabButton")
@@ -21,7 +21,8 @@ namespace Barotrauma
{
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 (tabMenu == null && !(GameMode is TutorialMode) && !ConversationAction.IsDialogOpen)
@@ -39,6 +40,7 @@ namespace Barotrauma
private GUILayoutGroup topLeftButtonGroup;
private GUIButton crewListButton, commandButton, tabMenuButton;
private GUIImage talentPointNotification;
private GUIComponent respawnInfoFrame, respawnButtonContainer;
private GUITextBlock respawnInfoText;
@@ -88,11 +90,11 @@ namespace Barotrauma
tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton")
{
ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)),
OnClicked = (button, userData) =>
{
return ToggleTabMenu();
}
OnClicked = (button, userData) => ToggleTabMenu()
};
talentPointNotification = CreateTalentIconNotification(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
@@ -139,11 +141,41 @@ namespace Barotrauma
if (GameMain.NetworkMember != null)
{
GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList();
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.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)
{
if (GUI.DisableHUD) { return; }
@@ -158,28 +190,24 @@ namespace Barotrauma
else
{
tabMenu.Update();
if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) &&
if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) &&
!(GUI.KeyboardDispatcher.Subscriber is GUITextBox))
{
ToggleTabMenu();
}
}
UpdateTalentNotificationIndicator(talentPointNotification);
if (GameMain.NetworkMember != null)
{
if (GameMain.NetLobbyScreen?.HeadSelectionList != null)
{
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.HeadSelectionList))
{
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
}
}
GameMain.NetLobbyScreen?.CharacterAppearanceCustomizationMenu?.Update();
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();
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false;
}
}
}
@@ -323,7 +323,7 @@ namespace Barotrauma
case Layout.Default:
{
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 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)
{
if (!string.IsNullOrEmpty(materialName))
if (!string.IsNullOrEmpty(materialName) && item.ContainedItems.Count() > 0)
{
string mergedMaterialName = materialName;
foreach (Item containedItem in item.ContainedItems)
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName;
}
name = TextManager.GetWithVariable("entityname.geneticmaterial", "[type]", mergedMaterialName);
name = name.Replace(materialName, mergedMaterialName);
}
if (Tainted)
@@ -164,7 +164,7 @@ namespace Barotrauma.Items.Components
}
}
}
activateButton.Enabled = inputContainer.Inventory.AllItems.Any();
activateButton.Enabled = outputsFound;
activateButton.Text = TextManager.Get(ActivateButtonText);
};
}
@@ -516,11 +516,7 @@ namespace Barotrauma.Items.Components
string name = GetRecipeNameAndAmount(selectedItem);
float quality = 0;
foreach (string tag in selectedItem.TargetItem.Tags)
{
quality += user?.Info?.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag) ?? 0;
}
float quality = GetFabricatedItemQuality(selectedItem, user);
if (quality > 0)
{
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()
{
GuiFrame.ClearChildren();
GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f);
GuiFrame.CanBeFocused = true;
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);
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(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center), style: null);
submarineContainer = new GUIFrame(new RectTransform(Vector2.One, paddedContainer.RectTransform, Anchor.Center), style: null);
new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null)
var submarineFront = new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null)
{
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
(
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, 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, 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, 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.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.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.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.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") }
);
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);
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
};
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())
@@ -359,10 +361,7 @@ namespace Barotrauma.Items.Components
searchBar.OnSelected += (sender, key) =>
{
itemsFoundOnSub = Item.ItemList.Where(it =>
it.Submarine == item.Submarine &&
!it.NonInteractable && !it.HiddenInGame &&
(it.GetComponent<Holdable>() != null || it.GetComponent<Wearable>() != null)).Select(it => it.Prefab).ToImmutableHashSet();
itemsFoundOnSub = Item.ItemList.Where(it => VisibleOnItemFinder(it)).Select(it => it.Prefab).ToImmutableHashSet();
};
searchBar.OnKeyHit += ControlSearchTooltip;
@@ -390,6 +389,28 @@ namespace Barotrauma.Items.Components
c.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()
@@ -546,10 +567,7 @@ namespace Barotrauma.Items.Components
// is there a better way to do this?
if (GuiFrame.Rect.Size != elementSize)
{
if (item.Submarine is { } sub)
{
BakeSubmarine(sub, miniMapFrame.Rect);
}
CreateGUI();
elementSize = GuiFrame.Rect.Size;
}
@@ -782,10 +800,7 @@ namespace Barotrauma.Items.Components
foreach (Item it in Item.ItemList)
{
if (it.Submarine != item.Submarine) { continue; }
if (it.HiddenInGame || it.NonInteractable) { continue; }
if (it.GetComponent<Wire>() is { Connections: { } conn} && conn.Any()) { continue; }
if (it.HasTag("traitormissionitem")) { continue; }
if (!VisibleOnItemFinder(it)) { continue; }
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)
{
foundItems.Add((Item) parent.Owner);
foundItems.Add((Item)parent.Owner);
}
else
{
@@ -1165,7 +1180,7 @@ namespace Barotrauma.Items.Components
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);
}
}
@@ -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)
{
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)
@@ -162,7 +162,7 @@ namespace Barotrauma.Items.Components
tinkerButtonText = "Tinker";
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,
Visible = false,
@@ -254,7 +254,7 @@ namespace Barotrauma.Items.Components
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) ?
repairButtonText :
repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
@@ -269,7 +269,7 @@ namespace Barotrauma.Items.Components
TinkerButton.Visible = IsTinkerable(character);
TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible;
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 :
tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
@@ -326,6 +326,7 @@ namespace Barotrauma.Items.Components
deteriorateAlwaysResetTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean();
tinkeringDuration = msg.ReadSingle();
tinkeringStrength = msg.ReadSingle();
ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
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);
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
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))
{
@@ -1653,9 +1654,9 @@ namespace Barotrauma
scale: iconSize.X / stealIcon.size.X);
}
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)
{
@@ -265,7 +265,7 @@ namespace Barotrauma
if (!currentDisplayLocation.Discovered)
{
RemoveFogOfWar(currentDisplayLocation);
currentDisplayLocation.Discovered = true;
currentDisplayLocation.Discover();
if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{
furthestDiscoveredLocation = currentDisplayLocation;
@@ -426,7 +426,7 @@ namespace Barotrauma
Level.Loaded.DebugSetStartLocation(CurrentLocation);
Level.Loaded.DebugSetEndLocation(null);
CurrentLocation.Discovered = true;
CurrentLocation.Discover();
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
SelectLocation(-1);
if (GameMain.Client == null)
@@ -2755,6 +2755,9 @@ namespace Barotrauma.Networking
msg.Write((byte)characterInfo.BeardIndex);
msg.Write((byte)characterInfo.MoustacheIndex);
msg.Write((byte)characterInfo.FaceAttachmentIndex);
msg.WriteColorR8G8B8(characterInfo.SkinColor);
msg.WriteColorR8G8B8(characterInfo.HairColor);
msg.WriteColorR8G8B8(characterInfo.FacialHairColor);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
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
{
class CampaignSetupUI
class SinglePlayerCampaignSetupUI : CampaignSetupUI
{
private readonly GUIComponent newGameContainer, loadGameContainer;
public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; }
private GUIListBox subList;
private GUIListBox saveList;
private List<GUITickBox> subTickBoxes;
private readonly GUITextBox saveNameBox, seedBox;
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
private GUIButton nextButton;
private GUILayoutGroup characterInfoColumns;
public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
: base(newGameContainer, loadGameContainer)
{
get;
private set;
UpdateNewGameMenu(submarines);
UpdateLoadMenu(saveFiles);
}
public GUITextBlock InitialMoneyText
private int currentPage = 0;
private GUIListBox pageContainer;
public void Update()
{
get;
private set;
float targetScroll =
(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 void SetPage(int pageIndex)
{
currentPage = pageIndex;
for (int i = 0; i < pageContainer.Content.CountChildren; i++)
{
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;
});
}
public GUITickBox EnableRadiationToggle { get; set; }
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)
private void UpdateNewGameMenu(IEnumerable<SubmarineInfo> submarines)
{
this.isMultiplayer = isMultiplayer;
this.newGameContainer = newGameContainer;
this.loadGameContainer = loadGameContainer;
pageContainer =
new GUIListBox(new RectTransform(Vector2.One, newGameContainer.RectTransform), style: null, isHorizontal: true)
{
ScrollBarEnabled = false,
ScrollBarVisible = false,
HoverCursor = CursorState.Default
};
var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true)
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,
RelativeSpacing = isMultiplayer ? 0.0f : 0.02f
RelativeSpacing = 0.02f
};
var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform))
@@ -70,7 +120,7 @@ namespace Barotrauma
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,
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);
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);
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);
subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true };
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.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) => { FilterSubs(subList, text); return true; };
subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = 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) => { 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
moddedDropdown.OnSelected = (component, data) =>
{
CreateMultiplayerCampaignSubList(leftColumn.RectTransform);
searchBox.Text = string.Empty;
subFilter = (CategoryFilter)data;
UpdateSubList(SubmarineInfo.SavedSubmarines);
return true;
};
//spacing
//new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null);
}
subList.OnSelected = OnSubSelected;
// New game right side
subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform))
@@ -136,176 +176,162 @@ namespace Barotrauma
Stretch = true
};
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f),
(isMultiplayer ? leftColumn : rightColumn).RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, 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"))
var firstPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
firstPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
{
OnClicked = (GUIButton btn, object userData) =>
{
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;
}
RelativeSpacing = 0.025f
};
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 = () =>
{
int initialMoney = CampaignMode.InitialMoney;
if (isMultiplayer)
{
if (GameMain.NetLobbyScreen.SelectedSub != null)
{
initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price;
}
}
else if (subList.SelectedData is SubmarineInfo subInfo)
if (subList.SelectedData is SubmarineInfo subInfo)
{
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));
}
};
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,
OnClicked = (btn, userdata) => { GameMain.Instance.ShowCampaignDisclaimer(); return true; }
};
disclaimerBtn.RectTransform.MaxSize = new Point((int)(30 * GUI.Scale));
}
SetPage(1);
return false;
}
};
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();
leftColumn.Recalculate();
rightColumn.Recalculate();
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()
{
CampaignCustomizeSettings = new GUIMessageBox("", "", new string[] { TextManager.Get("OK") }, new Vector2(0.2f, 0.2f));
@@ -355,106 +381,93 @@ namespace Barotrauma
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,
Stretch = true
};
saveNameBox.Flash(GUI.Style.Red);
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
};
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) =>
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected"));
return false;
}
if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
{
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; }
child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower());
}
return true;
};
var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"),
TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
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) =>
msgBox.Buttons[0].OnClicked = msgBox.Close;
msgBox.Buttons[0].OnClicked += (button, obj) =>
{
GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected);
if (GUIMessageBox.MessageBoxes.Count == 0)
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
}
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");
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
if (!sub.RequiredContentPackagesInstalled)
if (selectedSub.HasTag(SubmarineTag.Shuttle))
{
subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f);
frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip;
}
var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"),
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),
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{
TextColor = subTextBlock.TextColor * 0.8f,
ToolTip = subTextBlock.RawToolTip
};
msgBox.Buttons[0].OnClicked = (button, obj) =>
{
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
return false;
}
}
}
public void RefreshMultiplayerCampaignSubUI(List<SubmarineInfo> campaignSubs)
{
for (int i = 0; i < subTickBoxes.Count; i++)
else
{
subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo);
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
}
}
return true;
}
public void RandomizeSeed()
{
seedBox.Text = ToolBox.RandomSeed(8);
@@ -478,54 +491,28 @@ namespace Barotrauma
if (!(obj is SubmarineInfo sub)) { return true; }
#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;
}
#endif
StartButton.Enabled = true;
nextButton.Enabled = true;
sub.CreatePreviewWindow(subPreviewContainer);
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()
{
string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer);
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer);
saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath);
}
public void UpdateSubList(IEnumerable<SubmarineInfo> submarines)
{
List<SubmarineInfo> subsToShow;
if (!isMultiplayer && subFilter != CategoryFilter.All)
if (subFilter != CategoryFilter.All)
{
subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList();
}
@@ -596,10 +583,10 @@ namespace Barotrauma
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,
RelativeSpacing = 0.03f
@@ -610,26 +597,23 @@ namespace Barotrauma
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);
}
catch (Exception e)
{
new GUIMessageBox(
TextManager.Get("error"),
TextManager.GetWithVariables("showinfoldererror", new string[] { "[folder]", "[errormessage]" }, new string[] { SaveUtil.SaveFolder, e.Message }));
}
return true;
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;
}
};
foreach (string saveFile in saveFiles)
{
@@ -649,39 +633,27 @@ namespace Barotrauma
bool isCompatible = true;
prevSaveFiles ??= new List<string>();
if (!isMultiplayer)
{
nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
if (doc?.Root == null)
{
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUI.Style.Red;
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
nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
if (doc?.Root == null)
{
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]; }
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUI.Style.Red;
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);
if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime))
{
DateTime time = ToolBox.Epoch.ToDateTime(unixTime);
@@ -748,38 +720,16 @@ namespace Barotrauma
{
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string);
if (isMultiplayer)
{
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;
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);
if (doc?.Root == null)
{
@@ -868,6 +818,5 @@ namespace Barotrauma
}
loadGameContainer.RemoveChild(prevFrame);
}
}
}
@@ -182,7 +182,7 @@ namespace Barotrauma.CharacterEditor
showSpritesheet = false;
isFrozen = false;
autoFreeze = false;
limbPairEditing = true;
limbPairEditing = false;
uniformScaling = true;
lockSpriteOrigin = true;
lockSpritePosition = false;
@@ -1581,7 +1581,6 @@ namespace Barotrauma.CharacterEditor
Character.Controlled = character;
SetWallCollisions(character.AnimController.forceStanding);
CreateTextures();
limbPairEditing = character.IsHumanoid;
CreateGUI();
ClearWidgets();
ClearSelection();
@@ -1803,6 +1802,7 @@ namespace Barotrauma.CharacterEditor
{
case AnimationType.Walk:
case AnimationType.Run:
case AnimationType.Crouch:
if (!ragdollParams.CanWalk) { continue; }
break;
case AnimationType.SwimSlow:
@@ -1819,8 +1819,8 @@ namespace Barotrauma.CharacterEditor
{
AllFiles.Add(configFilePath);
}
limbPairEditing = false;
SpawnCharacter(configFilePath, ragdollParams);
limbPairEditing = false;
limbsToggle.Selected = true;
recalculateColliderToggle.Selected = true;
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 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);
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)
{
animSelection.AddItem(AnimationType.Walk.ToString(), AnimationType.Walk);
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.SwimFast.ToString(), AnimationType.SwimFast);
@@ -2622,25 +2626,15 @@ namespace Barotrauma.CharacterEditor
switch (character.AnimController.ForceSelectAnimationType)
{
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.Crouch:
character.AnimController.forceStanding = true;
character.ForceRun = true;
character.ForceRun = character.AnimController.ForceSelectAnimationType == AnimationType.Run;
if (!wallCollisionsEnabled)
{
SetWallCollisions(true);
}
if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run)
if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run && previousAnim != AnimationType.Crouch)
{
TeleportTo(spawnPosition);
}
@@ -3046,21 +3040,24 @@ namespace Barotrauma.CharacterEditor
loadBox.Buttons[1].OnClicked += (btn, data) =>
{
string fileName = Path.GetFileNameWithoutExtension(selectedFile);
if (character.IsHumanoid)
if (character.IsHumanoid && character.AnimController is HumanoidAnimController humanAnimController)
{
switch (selectedType)
{
case AnimationType.Walk:
character.AnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName);
humanAnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName);
break;
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;
case AnimationType.SwimSlow:
character.AnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName);
humanAnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName);
break;
case AnimationType.SwimFast:
character.AnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName);
humanAnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName);
break;
default:
DebugConsole.ThrowError(GetCharacterEditorTranslation("AnimationTypeNotImplemented").Replace("[type]", selectedType.ToString()));
@@ -4152,7 +4149,7 @@ namespace Barotrauma.CharacterEditor
float offset = 0.1f;
w.refresh = () =>
{
var refPoint = SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset);
var refPoint = SimToScreen(character.AnimController.Collider.SimPosition + GetSimSpaceForward() * offset);
var handMovement = ConvertUnits.ToDisplayUnits(humanGroundedParams.HandMoveAmount);
w.DrawPos = refPoint + new Vector2(handMovement.X * character.AnimController.Dir, handMovement.Y) * Cam.Zoom;
};
@@ -4167,7 +4164,7 @@ namespace Barotrauma.CharacterEditor
{
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);
@@ -4731,7 +4728,7 @@ namespace Barotrauma.CharacterEditor
rotation: 0,
origin: orig,
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,
color: Color.White,
layerDepth: 0);
@@ -284,7 +284,7 @@ namespace Barotrauma.CharacterEditor
}
isTextureSelected = true;
texturePathElement.Text = destinationPath;
texturePathElement.Text = destinationPath.CleanUpPath();
};
FileSelection.ClearFileTypeFilters();
FileSelection.AddFileTypeFilter("PNG", "*.png");
@@ -431,7 +431,7 @@ namespace Barotrauma.CharacterEditor
box.Header.Font = GUI.LargeFont;
box.Content.ChildAnchor = Anchor.TopCenter;
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)),
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 };
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)
{ 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 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
};
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 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);
@@ -25,6 +25,7 @@ namespace Barotrauma
public Effect PostProcessEffect { get; private set; }
public Effect GradientEffect { get; private set; }
public Effect GrainEffect { get; private set; }
public Effect ThresholdTintEffect { get; private set; }
public Effect BlueprintEffect { get; set; }
public GameScreen(GraphicsDevice graphics, ContentManager content)
@@ -38,21 +39,20 @@ namespace Barotrauma
CreateRenderTargets(graphics);
};
Effect LoadEffect(string path)
=> content.Load<Effect>(path
#if LINUX || OSX
//var blurEffect = content.Load<Effect>("Effects/blurshader_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");
+"_opengl"
#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");
damageEffect.Parameters["xStencil"].SetValue(damageStencil);
@@ -40,7 +40,7 @@ namespace Barotrauma
private readonly GUIFrame[] menuTabs;
private CampaignSetupUI campaignSetupUI;
private SinglePlayerCampaignSetupUI campaignSetupUI;
private GUITextBox serverNameBox, /*portBox, queryPortBox,*/ passwordBox, maxPlayersBox;
private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
@@ -594,6 +594,8 @@ namespace Barotrauma
GameMain.Instance.ShowCampaignDisclaimer(() => { SelectTab(null, Tab.NewGame); });
return true;
}
campaignSetupUI.RandomizeCrew();
campaignSetupUI.SetPage(0);
campaignSetupUI.CreateDefaultSaveName();
campaignSetupUI.RandomizeSeed();
campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines);
@@ -985,24 +987,32 @@ namespace Barotrauma
if (selectedTab < Tab.Empty && menuTabs[(int)selectedTab] != null)
{
menuTabs[(int)selectedTab].AddToGUIUpdateList();
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.CharacterMenus?.ForEach(m => m.AddToGUIUpdateList());
break;
}
}
}
public override void Update(double deltaTime)
{
#if !DEBUG
#if USE_STEAM
#if !DEBUG && USE_STEAM
if (GameMain.Config.UseSteamMatchmaking)
{
hostServerButton.Enabled = Steam.SteamManager.IsInitialized;
}
steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized;
#endif
#else
#if USE_STEAM
#elif USE_STEAM
steamWorkshopButton.Enabled = true;
#endif
#endif
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.Update();
break;
}
}
public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
@@ -1119,6 +1129,11 @@ namespace Barotrauma
selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
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();
}
@@ -1146,7 +1161,7 @@ namespace Barotrauma
menuTabs[(int)Tab.NewGame].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,
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),
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) },
style: null);
campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame, SubmarineInfo.SavedSubmarines)
campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame, SubmarineInfo.SavedSubmarines)
{
LoadGame = LoadGame,
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()
File diff suppressed because it is too large Load Diff
@@ -41,21 +41,13 @@ namespace Barotrauma
public Sprite(Texture2D texture, Rectangle? sourceRectangle, Vector2? newOffset, float newRotation = 0.0f, string path = null)
{
this.texture = texture;
sourceRect = sourceRectangle ?? new Rectangle(0, 0, texture.Width, texture.Height);
offset = newOffset ?? Vector2.Zero;
size = new Vector2(sourceRect.Width, sourceRect.Height);
origin = Vector2.Zero;
effects = SpriteEffects.None;
rotation = newRotation;
FilePath = path;
AddToList(this);
}
@@ -85,7 +77,7 @@ namespace Barotrauma
EnsureLazyLoaded(isAsync: true);
}
public void EnsureLazyLoaded(bool isAsync=false)
public void EnsureLazyLoaded(bool isAsync = false)
{
if (!LazyLoad || texture != null || cannotBeLoaded || loadingAsync) { return; }
loadingAsync = isAsync;
@@ -382,7 +374,7 @@ namespace Barotrauma
{
foreach (Sprite s in LoadedSprites)
{
if (s.FullPath == FullPath) return;
if (s.FullPath == FullPath) { return; }
}
}
}
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.3.0</Version>
<Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.3.0</Version>
<Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -67,6 +67,12 @@
/processorParam:DebugMode=Auto
/build:grainshader.fx
#begin thresholdtint.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:thresholdtint.fx
#begin blueprintshader.fx
/importer:EffectImporter
/processor:EffectProcessor
@@ -72,3 +72,9 @@
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/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>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.3.0</Version>
<Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.3.0</Version>
<Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.3.0</Version>
<Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
@@ -39,10 +39,13 @@ namespace Barotrauma
msg.Write((byte)Gender);
msg.Write((byte)Race);
msg.Write((byte)HeadSpriteId);
msg.Write((byte)Head.HairIndex);
msg.Write((byte)Head.BeardIndex);
msg.Write((byte)Head.MoustacheIndex);
msg.Write((byte)Head.FaceAttachmentIndex);
msg.Write((byte)HairIndex);
msg.Write((byte)BeardIndex);
msg.Write((byte)MoustacheIndex);
msg.Write((byte)FaceAttachmentIndex);
msg.WriteColorR8G8B8(SkinColor);
msg.WriteColorR8G8B8(HairColor);
msg.WriteColorR8G8B8(FacialHairColor);
msg.Write(ragdollFileName);
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)));
}
foreach (var kvp in SpawnedResources)
foreach (var kvp in spawnedResources)
{
msg.Write((byte)kvp.Value.Count);
var rotation = ResourceClusters[kvp.Key].Second;
var rotation = resourceClusters[kvp.Key].rotation;
msg.Write(rotation);
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((byte)kvp.Value.Length);
@@ -39,6 +39,7 @@ namespace Barotrauma.Items.Components
msg.Write(deteriorateAlwaysResetTimer);
msg.Write(DeteriorateAlways);
msg.Write(tinkeringDuration);
msg.Write(tinkeringStrength);
msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID);
msg.WriteRangedInteger((int)currentFixerAction, 0, 2);
}
@@ -3464,15 +3464,15 @@ namespace Barotrauma.Networking
}
catch (Exception e)
{
//gender = Gender.Male;
//race = Race.White;
//headSpriteId = 0;
DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }");
}
int hairIndex = message.ReadByte();
int beardIndex = message.ReadByte();
int moustacheIndex = 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>>();
int count = message.ReadByte();
@@ -3489,6 +3489,9 @@ namespace Barotrauma.Networking
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name);
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)
{
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.3.0</Version>
<Version>0.1500.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
@@ -118,6 +118,7 @@
<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/Tigerthresher_hatchling/Tigerthresher_hatchling.xml" />
<Character file="Content/Characters/TintTest/TintTest.xml" />
<MapCreature file="Content/Items/Gardening/ballastflora.xml" />
<Wreck file="Content/Map/Wrecks/Dugong_Wrecked.sub" />
<Wreck file="Content/Map/Wrecks/Kastrull_Wrecked.sub" />
@@ -464,7 +464,7 @@ namespace Barotrauma
};
break;
case "return":
newObjective = new AIObjectiveReturn(character, this, priorityModifier: priorityModifier);
newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier);
newObjective.Abandoned += () => DismissSelf(order, option);
newObjective.Completed += () => DismissSelf(order, option);
break;
@@ -408,7 +408,7 @@ namespace Barotrauma
}
bool isCompleted =
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)
{
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.AIController is HumanAIController humanAI)
{
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;
}
@@ -12,7 +12,7 @@ namespace Barotrauma
private bool usingEscapeBehavior;
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);
if (ReturnTarget == null)
@@ -23,10 +23,12 @@ namespace Barotrauma
Submarine GetReturnTarget(IEnumerable<Submarine> subs)
{
var requiredTeamID = orderGiver?.TeamID ?? character?.TeamID;
Submarine returnTarget = null;
foreach (var sub in subs)
{
if (sub?.TeamID != character.TeamID) { continue; }
if (sub == null) { continue; }
if (sub.TeamID != requiredTeamID) { continue; }
returnTarget = sub;
break;
}
@@ -229,7 +231,7 @@ namespace Barotrauma
protected override void OnAbandon()
{
base.OnAbandon();
SteeringManager.Reset();
SteeringManager?.Reset();
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
{
string msg = TextManager.Get("dialogcannotreturn", returnNull: true);
@@ -24,8 +24,6 @@ namespace Barotrauma
public readonly Vector2 Position;
public readonly int WayPointID;
public bool blocked;
public override string ToString()
{
return $"PathNode {WayPointID}";
@@ -81,6 +79,31 @@ namespace Barotrauma
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
@@ -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)
{
UpdateBlockedNodes();
foreach (PathNode node in nodes)
{
node.ResetBlocked();
}
//sort nodes roughly according to distance
sortedNodes.Clear();
@@ -202,11 +228,11 @@ namespace Barotrauma
{
if (startNode == null || node.TempDistance < startNode.TempDistance)
{
if (node.blocked) { continue; }
if (nodeFilter != null && !nodeFilter(node)) { continue; }
if (startNodeFilter != null && !startNodeFilter(node)) { continue; }
// Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { continue; }
if (node.IsBlocked()) { continue; }
startNode = node;
}
}
@@ -251,11 +277,11 @@ namespace Barotrauma
{
if (endNode == null || node.TempDistance < endNode.TempDistance)
{
if (node.blocked) { continue; }
if (nodeFilter != null && !nodeFilter(node)) { continue; }
if (endNodeFilter != null && !endNodeFilter(node)) { continue; }
// Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; }
if (node.IsBlocked()) { continue; }
endNode = node;
}
}
@@ -326,15 +352,13 @@ namespace Barotrauma
float dist = float.MaxValue;
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 (node.blocked) { continue; }
if (filter != null && !filter(node)) { continue; }
if (node.F < dist)
{
dist = node.F;
currNode = node;
}
if (node.IsBlocked()) { continue; }
dist = node.F;
currNode = node;
}
if (currNode == null || currNode == end) { break; }
@@ -436,25 +460,6 @@ namespace Barotrauma
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
{
if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
{
return humanAnimController.HumanCrouchParams;
}
return IsMovingFast ? RunParams : WalkParams;
}
}
@@ -96,7 +100,12 @@ namespace Barotrauma
{
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
{
@@ -154,7 +163,7 @@ namespace Barotrauma
public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { }
public float GetSpeed(AnimationType type)
public virtual float GetSpeed(AnimationType type)
{
GroundedMovementParams movementParams;
switch (type)
@@ -207,7 +216,14 @@ namespace Barotrauma
}
else
{
animType = AnimationType.Walk;
if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
{
animType = AnimationType.Crouch;
}
else
{
animType = AnimationType.Walk;
}
}
}
return GetSpeed(animType);
@@ -221,6 +237,12 @@ namespace Barotrauma
return WalkParams;
case AnimationType.Run:
return RunParams;
case AnimationType.Crouch:
if (this is HumanoidAnimController humanAnimController)
{
return humanAnimController.HumanCrouchParams;
}
throw new NotImplementedException(type.ToString());
case AnimationType.SwimSlow:
return SwimSlowParams;
case AnimationType.SwimFast:
@@ -97,9 +97,9 @@ namespace Barotrauma
public new FishSwimParams CurrentSwimParams => base.CurrentSwimParams as FishSwimParams;
public float? TailAngle => GetValidOrNull(CurrentAnimationParams, CurrentFishAnimation?.TailAngleInRadians);
public float FootTorque => CurrentFishAnimation.FootTorque;
public float HeadTorque => CurrentFishAnimation.HeadTorque;
public float TorsoTorque => CurrentFishAnimation.TorsoTorque;
public float FootTorque => CurrentAnimationParams.FootTorque;
public float HeadTorque => CurrentAnimationParams.HeadTorque;
public float TorsoTorque => CurrentAnimationParams.TorsoTorque;
public float TailTorque => CurrentFishAnimation.TailTorque;
public float HeadMoveForce => CurrentGroundedParams.HeadMoveForce;
public float TorsoMoveForce => CurrentGroundedParams.TorsoMoveForce;
@@ -2,7 +2,6 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
@@ -73,6 +72,20 @@ namespace Barotrauma
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;
public HumanSwimSlowParams HumanSwimSlowParams
{
@@ -102,8 +115,11 @@ namespace Barotrauma
}
public new HumanGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as HumanGroundedParams;
public new HumanSwimParams CurrentSwimParams => base.CurrentSwimParams as HumanSwimParams;
public IHumanAnimation CurrentHumanAnimParams => CurrentAnimationParams as IHumanAnimation;
public override GroundedMovementParams WalkParams
{
get { return HumanWalkParams; }
@@ -169,42 +185,9 @@ namespace Barotrauma
private float swimmingStateLockTimer;
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 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 Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale;
@@ -298,7 +281,7 @@ namespace Barotrauma
LimbType lowerLegType = LimbType.RightLeg;
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 localAnchorKnee = Vector2.Zero;
if (waistJoint != null)
@@ -336,7 +319,7 @@ namespace Barotrauma
levitatingCollider = true;
ColliderIndex = Crouching && !swimming ? 1 : 0;
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false ||
(ForceSelectAnimationType != AnimationType.Walk && ForceSelectAnimationType != AnimationType.NotDefined))
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{
Crouching = false;
ColliderIndex = 0;
@@ -439,9 +422,8 @@ namespace Barotrauma
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
HandIK(rightHand, midPos);
HandIK(leftHand, midPos);
HandIK(rightHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
HandIK(leftHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
}
else if (character.AnimController.AnimationTestPose)
{
@@ -638,7 +620,7 @@ namespace Barotrauma
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;
head.PullJointEnabled = true;
@@ -710,9 +692,12 @@ namespace Barotrauma
float torsoAngle = TorsoAngle.Value;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
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)
{
@@ -743,12 +728,23 @@ namespace Barotrauma
Vector2 footPos = stepSize * -i;
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
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));
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)
{
footPos.Y *= 2.0f;
@@ -770,7 +766,7 @@ namespace Barotrauma
foot.DebugTargetPos = colliderPos + footPos;
MoveLimb(foot, colliderPos + footPos, CurrentGroundedParams.FootMoveStrength);
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 +
new Vector2(
-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)
@@ -797,16 +793,14 @@ namespace Barotrauma
HandIK(leftHand, torso.SimPosition + posAddition +
new Vector2(
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
{
for (int i = -1; i < 2; i += 2)
{
Vector2 footPos = colliderPos;
if (Crouching)
{
footPos = new Vector2(
@@ -817,27 +811,24 @@ namespace Barotrauma
//lift the foot at the back up a bit
footPos.Y += 0.15f;
}
footPos.X += torso.SimPosition.X;
footPos.X += colliderPos.X;
}
else
{
footPos = new Vector2(colliderPos.X + stepSize.X * i * 0.2f, colliderPos.Y - 0.1f);
}
if (Stairs == null)
{
footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y);
}
var foot = i == -1 ? rightFoot : leftFoot;
if (foot != null && !foot.Disabled)
{
foot.DebugRefPos = colliderPos;
foot.DebugTargetPos = footPos;
MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians);
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
}
}
@@ -970,7 +961,7 @@ namespace Barotrauma
if (!aiming)
{
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
}
else
@@ -981,7 +972,7 @@ namespace Barotrauma
Vector2 diff = (mousePos - torso.SimPosition) * Dir;
TargetMovement = new Vector2(0.0f, -0.1f);
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)
{
torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque);
torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.TorsoTorque);
}
else
{
torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque);
torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.TorsoTorque);
}
if (HeadAngle.HasValue)
{
head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.HeadTorque);
}
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
@@ -1044,25 +1035,25 @@ namespace Barotrauma
float legMoveMultiplier = 1.0f;
if (movement.LengthSquared() < 0.001f)
{
//TODO: expose these?
// Swimming in place (TODO: expose?)
legMoveMultiplier = 0.3f;
legCyclePos += 0.4f;
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);
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f);
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)
{
FootIK(rightFoot, footPos - transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians);
FootIK(rightFoot, footPos - transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
}
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;
@@ -1071,7 +1062,7 @@ namespace Barotrauma
// -> hands just float around
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;
@@ -1079,14 +1070,14 @@ namespace Barotrauma
{
MoveLimb(rightHand, new Vector2(
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)
{
MoveLimb(leftHand, new Vector2(
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;
@@ -1107,8 +1098,8 @@ namespace Barotrauma
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 = Vector2.Transform(rightHandPos, rotationMatrix);
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()));
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty());
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
}
if (leftHand != null && !leftHand.Disabled)
@@ -1116,8 +1107,8 @@ namespace Barotrauma
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 = Vector2.Transform(leftHandPos, rotationMatrix);
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()));
float speedMultiplier = 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 headPos = HeadPosition ?? 0;
float torsoPos = TorsoPosition ?? 0;
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);
MoveLimb(head, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.HeadPosition), 10.5f);
MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.TorsoPosition), 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(
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
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
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())
{
@@ -1881,7 +1874,7 @@ namespace Barotrauma
for (int i = 0; i < 2; i++)
{
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;
}
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;
@@ -1948,9 +1941,11 @@ namespace Barotrauma
armAngle -= MathHelper.TwoPi;
}
arm?.body.SmoothRotate((armAngle - upperArmAngle), 20.0f * force * arm.Mass, wrapAngle: false);
forearm?.body.SmoothRotate((armAngle + lowerArmAngle), 20.0f * force * forearm.Mass, wrapAngle: false);
hand?.body.SmoothRotate((armAngle + lowerArmAngle), 100.0f * force * hand.Mass, wrapAngle: false);
arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false);
float forearmAngle = armAngle + lowerArmAngle;
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)
@@ -1976,12 +1971,12 @@ namespace Barotrauma
upperLeg = GetLimb(LimbType.RightThigh);
lowerLeg = GetLimb(LimbType.RightLeg);
}
var torso = GetLimb(LimbType.Torso);
var waist = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type);
Limb torso = GetLimb(LimbType.Torso);
LimbJoint waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type) ?? GetJointBetweenLimbs(LimbType.Torso, upperLeg.type);
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
@@ -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 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;
@@ -1665,9 +1663,9 @@ namespace Barotrauma
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 &&
@@ -2760,9 +2758,9 @@ namespace Barotrauma
//ragdoll button
if (IsRagdolled || !CanMove)
{
if (AnimController is HumanoidAnimController)
if (AnimController is HumanoidAnimController humanAnimController)
{
((HumanoidAnimController)AnimController).Crouching = false;
humanAnimController.Crouching = false;
}
AnimController.ResetPullJoints();
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)
@@ -4351,7 +4349,7 @@ namespace Barotrauma
return GiveTalent(talentPrefab, addingFirstTime);
}
private bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true)
public bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true)
{
if (addingFirstTime)
{
@@ -1,9 +1,9 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
@@ -14,7 +14,6 @@ namespace Barotrauma
public enum Gender { None, Male, Female };
public enum Race { None, White, Black, Brown, Asian };
// TODO: Generating the HeadInfo could be simplified.
partial class CharacterInfo
{
public class HeadInfo
@@ -25,15 +24,7 @@ namespace Barotrauma
get { return _headSpriteId; }
set
{
_headSpriteId = value;
if (_headSpriteId < (int)headSpriteRange.X)
{
_headSpriteId = (int)headSpriteRange.Y;
}
if (_headSpriteId > (int)headSpriteRange.Y)
{
_headSpriteId = (int)headSpriteRange.X;
}
_headSpriteId = Math.Max(Math.Clamp(value, (int)headSpriteRange.X, (int)headSpriteRange.Y), 1);
GetSpriteSheetIndex();
}
}
@@ -42,6 +33,10 @@ namespace Barotrauma
public Gender gender;
public Race race;
public Color HairColor;
public Color FacialHairColor;
public Color SkinColor;
public int HairIndex { get; set; } = -1;
public int BeardIndex { get; set; } = -1;
public int MoustacheIndex { get; set; } = -1;
@@ -74,11 +69,11 @@ namespace Barotrauma
FaceAttachmentIndex = -1;
}
private void GetSpriteSheetIndex()
public void GetSpriteSheetIndex()
{
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 (heads.TryGetValue(matchingHead, out Vector2 index))
@@ -99,14 +94,13 @@ namespace Barotrauma
if (head != value && value != null)
{
head = value;
if (head.race == Race.None)
if (!IsValidRace(head.race))
{
head.race = GetRandomRace(Rand.RandSync.Unsynced);
}
CalculateHeadSpriteRange();
Head.HeadSpriteId = value.HeadSpriteId;
HeadSprite = null;
AttachmentSprites = null;
RefreshHeadSprites();
}
}
}
@@ -157,7 +151,9 @@ namespace Barotrauma
public bool HasNickname => Name != OriginalName;
public string OriginalName { get; private set; }
public string Name;
public string DisplayName
{
get
@@ -387,29 +383,31 @@ namespace Barotrauma
set
{
Head.HeadSpriteId = value;
HeadSprite = null;
AttachmentSprites = null;
ResetHeadAttachments();
RefreshHeadSprites();
}
}
public readonly bool HasGenders;
public readonly bool HasRaces;
public Gender Gender
{
get { return Head.gender; }
set
{
if (Head.gender == value) return;
Gender previousValue = Head.gender;
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; }
set
{
if (Head.race == value) { return; }
Race previousValue = Head.race;
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; }
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; }
private bool IsValidRace(Race race) => HasRaces ? race != Race.None : race == Race.None;
public XElement HairElement { get => Head.HairElement; set => Head.HairElement = value; }
public XElement BeardElement { get => Head.BeardElement; set => Head.BeardElement = value; }
public XElement MoustacheElement { get => Head.MoustacheElement; set => Head.MoustacheElement = value; }
public XElement FaceAttachment { get => Head.FaceAttachment; set => Head.FaceAttachment = value; }
private bool IsValidGender(Gender gender) => HasGenders ? gender != Gender.None : gender == Gender.None;
private Gender GetDefaultGender() => HasGenders ? Gender.Male : Gender.None;
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;
public RagdollParams Ragdoll
@@ -480,16 +532,18 @@ namespace Barotrauma
if (doc == null) { return; }
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
// TODO: support for variants
head = new HeadInfo();
Head = new HeadInfo();
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
if (HasGenders)
{
Head.gender = GetRandomGender(randSync);
}
Head.gender = GetRandomGender(randSync);
HasRaces = CharacterConfigElement.GetAttributeBool("races", false);
Head.race = GetRandomRace(randSync);
CalculateHeadSpriteRange();
Head.HeadSpriteId = GetRandomHeadID(randSync);
HeadSpriteId = GetRandomHeadID(randSync);
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))
{
@@ -502,23 +556,7 @@ namespace Barotrauma
else
{
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);
}
}
Name = GetRandomName(randSync);
}
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
@@ -530,6 +568,53 @@ namespace Barotrauma
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
public CharacterInfo(XElement infoElement)
{
@@ -542,7 +627,7 @@ namespace Barotrauma
ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0);
UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true));
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);
_speciesName = infoElement.GetAttributeString("speciesname", null);
XDocument doc = null;
@@ -560,14 +645,19 @@ namespace Barotrauma
// TODO: support for variants
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
if (HasGenders && gender == Gender.None)
HasRaces = CharacterConfigElement.GetAttributeBool("hasraces", false);
if (!IsValidGender(gender))
{
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(
infoElement.GetAttributeInt("headspriteid", 1),
race,
@@ -577,6 +667,11 @@ namespace Barotrauma
infoElement.GetAttributeInt("moustacheindex", -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 (CharacterConfigElement.Element("name") != null)
@@ -652,8 +747,25 @@ namespace Barotrauma
LoadHeadAttachments();
}
public Gender GetRandomGender(Rand.RandSync randSync) => (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
public Race GetRandomRace(Rand.RandSync randSync) => new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync);
public Gender GetRandomGender(Rand.RandSync 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;
private List<XElement> hairs;
@@ -720,10 +832,13 @@ namespace Barotrauma
{
if (elements == null) { return elements; }
return elements.Where(w =>
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == gender &&
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == race);
IsMatchingGender(Enum.Parse<Gender>(w.GetAttributeString("gender", "None"), ignoreCase: true), gender) &&
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()
{
if (CharacterConfigElement == null) { return; }
@@ -752,9 +867,16 @@ namespace Barotrauma
// If there are any head presets defined, use them.
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);
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.
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)
{
if (HasGenders && gender == Gender.None)
if (!IsValidGender(gender))
{
gender = GetRandomGender(Rand.RandSync.Unsynced);
}
else if (!HasGenders)
if (!IsValidRace(race))
{
gender = Gender.None;
race = GetRandomRace(Rand.RandSync.Unsynced);
}
if (heads == null)
{
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();
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())
{
@@ -816,6 +984,7 @@ namespace Barotrauma
if (spriteElement == null) { continue; }
string spritePath = spriteElement.Attribute("texture").Value;
if (string.IsNullOrEmpty(spritePath)) { continue; }
spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant());
@@ -823,6 +992,8 @@ namespace Barotrauma
string fileName = Path.GetFileNameWithoutExtension(spritePath);
if (string.IsNullOrEmpty(fileName)) { continue; }
//go through the files in the directory to find a matching sprite
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
{
@@ -847,13 +1018,12 @@ namespace Barotrauma
break;
}
LoadHeadSpriteProjectSpecific(limbElement);
break;
}
}
/// <summary>
/// Loads only the elements according to the indices, not the sprites.
/// </summary>
public void LoadHeadAttachments()
{
if (Wearables != null)
@@ -1138,7 +1308,7 @@ namespace Barotrauma
new XAttribute("name", Name),
new XAttribute("originalname", OriginalName),
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("salary", Salary),
new XAttribute("experiencepoints", ExperiencePoints),
@@ -1149,6 +1319,9 @@ namespace Barotrauma
new XAttribute("beardindex", BeardIndex),
new XAttribute("moustacheindex", MoustacheIndex),
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("ragdoll", ragdollFileName),
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
@@ -1462,13 +1635,19 @@ namespace Barotrauma
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();
LoadHeadAttachments();
}
public void ResetHeadAttachments()
/// <summary>
/// Loads only the elements according to the indices, not the sprites.
/// </summary>
private void ResetHeadAttachments()
{
ResetAttachmentIndices();
ResetLoadedAttachments();
@@ -1500,6 +1679,12 @@ namespace Barotrauma
AttachmentSprites = null;
}
private void RefreshHeadSprites()
{
HeadSprite = null;
AttachmentSprites = null;
}
// This could maybe be a LookUp instead?
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))
{
@@ -1550,7 +1735,7 @@ namespace Barotrauma
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
{
@@ -628,6 +628,11 @@ namespace Barotrauma
Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", "");
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);
if (!LimbSpecific)
{
@@ -669,6 +674,15 @@ namespace Barotrauma
case "afflictionoverlay":
AfflictionOverlay = new Sprite(subElement);
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;
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; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{
@@ -1088,7 +1088,7 @@ namespace Barotrauma
/// Automatically filters out buffs.
/// </summary>
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)
{
@@ -275,7 +275,8 @@ namespace Barotrauma
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)
{
var abilityAffliction = new AbilityAffliction(newAffliction);
var abilityAffliction = new AbilityAfflictionCharacter(newAffliction, character);
attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction);
}
if (applyAffliction)
@@ -14,6 +14,7 @@ namespace Barotrauma
NotDefined,
Walk,
Run,
Crouch,
SwimSlow,
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)]
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>
{
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;
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;
[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]
public virtual AnimationType AnimationType { get; protected set; }
@@ -402,6 +416,8 @@ namespace Barotrauma
return typeof(HumanWalkParams);
case AnimationType.Run:
return typeof(HumanRunParams);
case AnimationType.Crouch:
return typeof(HumanCrouchParams);
case AnimationType.SwimSlow:
return typeof(HumanSwimSlowParams);
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)]
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)]
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)]
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.")]
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)]
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)]
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]
public string FootAngles
@@ -224,10 +207,7 @@ namespace Barotrauma
Dictionary<int, float> FootAnglesInRadians { get; set; }
float TailAngle { get; set; }
float TailAngleInRadians { get; }
float HeadTorque { get; set; }
float TorsoTorque { get; set; }
float TailTorque { get; set; }
float FootTorque { get; set; }
bool Flip { get; set; }
float FlipCooldown { get; set; }
float FlipDelay { get; set; }
@@ -24,6 +24,17 @@ namespace Barotrauma
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
{
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)]
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]
public float HandCycleSpeed { get; set; }
@@ -81,36 +89,23 @@ namespace Barotrauma
}
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)]
public float FootRotateStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 20, 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; }
}
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
{
[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; }
// -- 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)]
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)]
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>
/// In degrees.
/// </summary>
@@ -135,15 +133,9 @@ namespace Barotrauma
}
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)]
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)]
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)]
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)]
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
{
float FootAngle { get; set; }
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]
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)]
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;
@@ -574,7 +577,7 @@ namespace Barotrauma
[Serialize("", true), Editable]
public string Notes { get; set; }
[Serialize(1f, true), Editable]
[Serialize(1f, true), Editable(DecimalCount = 2)]
public float Scale { get; set; }
[Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()]
@@ -889,6 +892,9 @@ namespace Barotrauma
[Serialize("", true), Editable()]
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()]
public Color Color { get; set; }
@@ -33,6 +33,7 @@ namespace Barotrauma.Abilities
NotSelf = 3,
Alive = 4,
Monster = 5,
InFriendlySubmarine = 6,
};
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
@@ -80,6 +81,8 @@ namespace Barotrauma.Abilities
return !targetCharacter.IsDead;
case TargetType.Monster:
return !targetCharacter.IsHuman;
case TargetType.InFriendlySubmarine:
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
default:
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; }
}
interface IAbilityLocation
{
public Location Location { get; set; }
}
interface IAbilityCharacter
{
public Character Character { get; set; }
@@ -43,6 +43,17 @@ namespace Barotrauma.Abilities
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
{
public AbilityValueItem(float value, ItemPrefab itemPrefab)
@@ -54,6 +65,17 @@ namespace Barotrauma.Abilities
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
{
public AbilityValueString(float value, string abilityString)
@@ -65,7 +87,7 @@ namespace Barotrauma.Abilities
public string String { get; set; }
}
class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString
class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
{
public AbilityValueStringCharacter(float value, string abilityString, Character character)
{
@@ -111,6 +133,16 @@ namespace Barotrauma.Abilities
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
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.Linq;
using System.Xml.Linq;
@@ -9,10 +8,12 @@ namespace Barotrauma.Abilities
class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects
{
private readonly bool allowSelf;
private readonly float maxDistance = float.MaxValue;
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
allowSelf = abilityElement.GetAttributeBool("allowself", true);
maxDistance = abilityElement.GetAttributeFloat("maxdistance", float.MaxValue);
}
@@ -22,6 +23,10 @@ namespace Barotrauma.Abilities
foreach (Character character in chosenCharacters)
{
if (maxDistance < float.MaxValue)
{
if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; }
}
ApplyEffectSpecific(character);
}
}
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -8,7 +7,7 @@ namespace Barotrauma.Abilities
public override bool AppliesEffectOnIntervalUpdate => true;
private readonly int amount;
private StatTypes scalingStatType;
private readonly StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -13,6 +13,7 @@ namespace Barotrauma.Abilities
private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
private readonly bool giveOnAddingFirstTime;
private readonly bool setValue;
//private readonly float maximumValue;
@@ -28,6 +29,7 @@ namespace Barotrauma.Abilities
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
setValue = abilityElement.GetAttributeBool("setvalue", false);
}
public override void InitializeAbility(bool addingFirstTime)
@@ -52,11 +54,11 @@ namespace Barotrauma.Abilities
{
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
{
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)
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty));
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
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<Item> openedContainers = new List<Item>();
private readonly float randomChance;
private readonly bool oncePerContainer;
public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
randomChance = abilityElement.GetAttributeFloat("randomchance", 1f);
oncePerContainer = abilityElement.GetAttributeBool("oncepercontainer", false);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityItem)?.Item is Item item)
{
if (openedContainers.Contains(item)) { return; }
openedContainers.Add(item);
if (oncePerContainer)
{
if (openedContainers.Contains(item)) { return; }
openedContainers.Add(item);
}
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
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
{
class CharacterAbilityEnigmaMachine : CharacterAbility
class CharacterAbilityAtmosMachine : CharacterAbility
{
private readonly float addedValue;
private readonly float multiplyValue;
private readonly string[] tags;
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);
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);
}
}
timesTriggered++;
}
}
@@ -39,6 +39,10 @@ namespace Barotrauma.Abilities
characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
}
}
if (conditionsMatched)
{
timesTriggered++;
}
TimeSinceLastUpdate = 0;
}
}
@@ -34,7 +34,7 @@ namespace Barotrauma
if (string.IsNullOrEmpty(jobIdentifier))
{
DebugConsole.ThrowError("No job defined for talent tree!");
DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!");
return;
}
@@ -56,9 +56,10 @@
OnAllyGainMissionExperience,
OnGainMissionExperience,
OnGainMissionMoney,
OnLocationDiscovered,
OnItemDeconstructed,
OnItemDeconstructedMaterial,
OnItemDeconstructedRetainProbability,
OnItemDeconstructedInventory,
OnStopTinkering,
OnItemPicked,
AfterSubmarineAttacked,
@@ -96,7 +97,6 @@
// Utility
RepairSpeed,
DeconstructorSpeedMultiplier,
TinkeringDuration,
RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier,
@@ -105,6 +105,10 @@
GeneticMaterialRefineBonus,
GeneticMaterialTaintedProbabilityReductionOnCombine,
SkillGainSpeed,
// Tinker
TinkeringDuration,
TinkeringStrength,
TinkeringDamage,
// Misc
ReputationGainMultiplier,
MissionMoneyGainMultiplier,
@@ -114,6 +118,7 @@
Coauthor,
WarriorPoetMissionRuns,
WarriorPoetEnemiesKilled,
QuickfixRepairCount,
}
public enum AbilityFlags
@@ -868,6 +868,7 @@ namespace Barotrauma
{
if (level == null) { return 0.0f; }
var refEntity = GetRefEntity();
if (refEntity == null) { return 0.0f; }
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength))
@@ -9,10 +9,10 @@ namespace Barotrauma
{
partial class MineralMission : Mission
{
private Dictionary<string, Pair<int, float>> ResourceClusters { get; } = new Dictionary<string, Pair<int, float>>();
private Dictionary<string, List<Item>> SpawnedResources { get; } = new Dictionary<string, List<Item>>();
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>();
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>();
private readonly Dictionary<string, (int amount, float rotation)> resourceClusters = new Dictionary<string, (int amount, float rotation)>();
private readonly Dictionary<string, List<Item>> spawnedResources = new Dictionary<string, List<Item>>();
private readonly Dictionary<string, Item[]> relevantLevelResources = new Dictionary<string, Item[]>();
private readonly List<Tuple<string, Vector2>> missionClusterPositions = new List<Tuple<string, Vector2>>();
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
@@ -20,8 +20,8 @@ namespace Barotrauma
{
get
{
return MissionClusterPositions
.Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1]))
return missionClusterPositions
.Where(p => spawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(spawnedResources[p.Item1]))
.Select(p => p.Item2);
}
}
@@ -33,53 +33,53 @@ namespace Barotrauma
{
var identifier = c.GetAttributeString("identifier", null);
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
{
ResourceClusters.Add(identifier, new Pair<int, float>(1, 0.0f));
resourceClusters.Add(identifier, (1, 0.0f));
}
}
}
protected override void StartMissionSpecific(Level level)
{
if (SpawnedResources.Any())
if (spawnedResources.Any())
{
#if DEBUG
throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})");
throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})");
#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.");
SpawnedResources.Clear();
spawnedResources.Clear();
#endif
}
if (RelevantLevelResources.Any())
if (relevantLevelResources.Any())
{
#if DEBUG
throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})");
throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})");
#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.");
RelevantLevelResources.Clear();
relevantLevelResources.Clear();
#endif
}
if (MissionClusterPositions.Any())
if (missionClusterPositions.Any())
{
#if DEBUG
throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})");
throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})");
#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.");
MissionClusterPositions.Clear();
missionClusterPositions.Clear();
#endif
}
caves.Clear();
if (IsClient) { return; }
foreach (var kvp in ResourceClusters)
foreach (var kvp in resourceClusters)
{
var prefab = ItemPrefab.Find(null, kvp.Key);
if (prefab == null)
@@ -88,15 +88,14 @@ namespace Barotrauma
"couldn't find an item prefab with the identifier " + kvp.Key);
continue;
}
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation);
if (spawnedResources.Count < kvp.Value.First)
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.amount, out float rotation);
if (spawnedResources.Count < kvp.Value.amount)
{
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; }
SpawnedResources.Add(kvp.Key, spawnedResources);
kvp.Value.Second = rotation;
this.spawnedResources.Add(kvp.Key, spawnedResources);
foreach (Level.Cave cave in Level.Loaded.Caves)
{
@@ -142,7 +141,7 @@ namespace Barotrauma
GiveReward();
completed = true;
}
foreach (var kvp in SpawnedResources)
foreach (var kvp in spawnedResources)
{
foreach (var i in kvp.Value)
{
@@ -152,33 +151,33 @@ namespace Barotrauma
}
}
}
SpawnedResources.Clear();
RelevantLevelResources.Clear();
MissionClusterPositions.Clear();
spawnedResources.Clear();
relevantLevelResources.Clear();
missionClusterPositions.Clear();
failed = !completed && state > 0;
}
private void FindRelevantLevelResources()
{
RelevantLevelResources.Clear();
foreach (var identifier in ResourceClusters.Keys)
relevantLevelResources.Clear();
foreach (var identifier in resourceClusters.Keys)
{
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
i.Submarine == null && i.ParentInventory == null &&
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
.ToArray();
RelevantLevelResources.Add(identifier, items);
relevantLevelResources.Add(identifier, items);
}
}
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 needed = kvp.Value.First;
var needed = kvp.Value.amount;
if (collected < needed) { return false; }
}
else
@@ -210,8 +209,8 @@ namespace Barotrauma
private void CalculateMissionClusterPositions()
{
MissionClusterPositions.Clear();
foreach (var kvp in SpawnedResources)
missionClusterPositions.Clear();
foreach (var kvp in spawnedResources)
{
if (kvp.Value.None()) { continue; }
var pos = Vector2.Zero;
@@ -222,7 +221,7 @@ namespace Barotrauma
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));
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
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System;
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));
}
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)
{
return new Color(color.R, color.G, color.B, (byte)255);
@@ -659,16 +659,7 @@ namespace Barotrauma
}
foreach (Location location in Map.Locations)
{
if (location.Type != location.OriginalType)
{
location.ChangeType(location.OriginalType);
location.PendingLocationTypeChange = null;
}
location.CreateStore(force: true);
location.ClearMissions();
location.Discovered = false;
location.LevelData?.EventHistory?.Clear();
location.UnlockInitialMissions();
location.Reset();
}
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
@@ -452,9 +452,11 @@ namespace Barotrauma
StatusEffect.StopAll();
#if CLIENT
#if !DEBUG
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
#endif
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
LevelData = level?.LevelData;
Level = level;
@@ -661,6 +663,7 @@ namespace Barotrauma
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null);
#else
if (GameMain.GameSession == null) { return Enumerable.Empty<Character>(); }
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null);
#endif
}
@@ -143,14 +143,7 @@ namespace Barotrauma
return true;
}
public int CharacterHeadIndex { 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; }
internal CharacterInfo.HeadInfo PlayerCharacterCustomization { get; set; }
private float aimAssistAmount;
public float AimAssistAmount
@@ -855,171 +848,6 @@ namespace Barotrauma
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
public void LoadPlayerConfig()
{
@@ -1307,15 +1135,20 @@ namespace Barotrauma
gameplay.Add(jobPreferences);
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));
var playerElement = new XElement("player", new XAttribute("name", playerName ?? ""));
if (PlayerCharacterCustomization != null)
{
playerElement.SetAttributeValue("headindex", PlayerCharacterCustomization.HeadSpriteId);
playerElement.SetAttributeValue("gender", PlayerCharacterCustomization.gender);
playerElement.SetAttributeValue("race", PlayerCharacterCustomization.race);
playerElement.SetAttributeValue("hairindex", PlayerCharacterCustomization.HairIndex);
playerElement.SetAttributeValue("beardindex", PlayerCharacterCustomization.BeardIndex);
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);
#if CLIENT
@@ -1434,23 +1267,22 @@ namespace Barotrauma
if (playerElement != null)
{
playerName = playerElement.GetAttributeString("name", playerName);
CharacterHeadIndex = playerElement.GetAttributeInt("headindex", CharacterHeadIndex);
if (Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender g))
int head = playerElement.GetAttributeInt("headindex", -1);
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;
}
if (Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race r))
{
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);
SkinColor = skinColor,
HairColor = hairColor,
FacialHairColor = facialHairColor
};
}
}
@@ -1656,13 +1488,7 @@ namespace Barotrauma
UseSteamMatchmaking = true;
RequireSteamAuthentication = true;
QuickStartSubmarineName = string.Empty;
CharacterHeadIndex = 1;
CharacterHairIndex = -1;
CharacterBeardIndex = -1;
CharacterMoustacheIndex = -1;
CharacterFaceAttachmentIndex = -1;
CharacterGender = Gender.None;
CharacterRace = Race.White;
PlayerCharacterCustomization = null;
aimAssistAmount = 0.5f;
EnableMouseLook = true;
EnableRadialDistortion = true;
@@ -15,14 +15,14 @@ namespace Barotrauma.Items.Components
private Character targetCharacter;
private AfflictionPrefab selectedEffect, selectedTaintedEffect;
[Serialize("", false)]
[Serialize("", true)]
public string Effect
{
get;
set;
}
[Serialize("geneticmaterialdebuff", false)]
[Serialize("geneticmaterialdebuff", true)]
public string TaintedEffect
{
get;
@@ -30,7 +30,7 @@ namespace Barotrauma.Items.Components
}
private bool tainted;
[Serialize(false, false)]
[Serialize(false, true)]
public bool Tainted
{
get { return tainted; }
@@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components
}
//only for saving the selected tainted effect
[Serialize("", false)]
[Serialize("", true)]
public string SelectedTaintedEffect
{
get { return selectedTaintedEffect?.Identifier ?? string.Empty; }
@@ -100,6 +100,11 @@ namespace Barotrauma.Items.Components
{
float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
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;
#if SERVER
item.CreateServerEvent(this);
@@ -111,6 +116,11 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
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;
#if SERVER
item.CreateServerEvent(this);
@@ -197,5 +207,21 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
#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;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
return true;
}
private Vector2 GetAttachPosition(Character user, bool useWorldCoordinates = false)
{
if (user == null) { return useWorldCoordinates ? item.WorldPosition : item.Position; }
@@ -61,8 +61,10 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item);
Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent());
Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item)
{
DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent())
};
}
item.IsShootable = true;
// 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;
}
Character targetCharacter = null;
Limb targetLimb = null;
Structure targetStructure = null;
Item targetItem = null;
if (f2.Body.UserData is Limb)
if (f2.Body.UserData is Limb targetLimb)
{
targetLimb = (Limb)f2.Body.UserData;
if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; }
targetCharacter = targetLimb.character;
var targetCharacter = targetLimb.character;
if (targetCharacter == picker) { return false; }
if (AllowHitMultiple)
{
@@ -287,9 +283,8 @@ namespace Barotrauma.Items.Components
}
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; }
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
if (AllowHitMultiple)
@@ -302,9 +297,8 @@ namespace Barotrauma.Items.Components
}
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 (hitTargets.Contains(targetStructure)) { return true; }
@@ -315,9 +309,8 @@ namespace Barotrauma.Items.Components
}
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 (hitTargets.Contains(targetItem)) { return true; }
@@ -350,13 +343,11 @@ namespace Barotrauma.Items.Components
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
Structure targetStructure = target.UserData as Structure;
Item targetItem = target.UserData as Item;
if (Attack != null)
{
Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
if (targetLimb != null)
{
@@ -370,12 +361,12 @@ namespace Barotrauma.Items.Components
targetCharacter.LastDamageSource = item;
Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
}
else if (targetStructure != null)
else if (target.UserData is Structure targetStructure)
{
if (targetStructure.Removed) { return; }
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; }
Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
@@ -196,7 +196,8 @@ namespace Barotrauma.Items.Components
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
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);
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);
if (i == 0)
{
@@ -619,7 +619,7 @@ namespace Barotrauma.Items.Components
levelResource.requiredItems.Any() &&
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;
#if CLIENT
Character.Controlled?.UpdateHUDProgressBar(
@@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components
private float userDeconstructorSpeedMultiplier = 1.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
@@ -89,12 +91,20 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f) { 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)
{
float deconstructTime = 0.0f;
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);
@@ -126,7 +136,7 @@ namespace Barotrauma.Items.Components
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)));
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);
if (progressTimer > deconstructTime)
@@ -234,9 +244,13 @@ namespace Barotrauma.Items.Components
if (user != null && !user.Removed)
{
var itemsCreated = new AbilityValueItem(1f, targetItem.Prefab);
var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated);
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++)
@@ -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)
{
//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)
: base(item, element)
{
@@ -128,7 +130,7 @@ namespace Barotrauma.Items.Components
currForce *= maxForce * forceMultiplier;
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{
currForce *= 2.5f;
currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease;
}
//less effective when in a bad condition
@@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, true)]
public float SkillRequirementMultiplier { get; set; }
private const float TinkeringSpeedIncrease = 1.5f;
private enum FabricatorState
{
Active = 1,
@@ -279,7 +281,14 @@ namespace Barotrauma.Items.Components
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();
@@ -329,12 +338,7 @@ namespace Barotrauma.Items.Components
}
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
float floatQuality = 0.0f;
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
quality = GetFabricatedItemQuality(fabricatedItem, 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();
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 IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
private const float TinkeringSpeedIncrease = 1.5f;
public Pump(Item item, XElement element)
: base(item, element)
{
@@ -108,7 +110,7 @@ namespace Barotrauma.Items.Components
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{
currFlow *= 2.5f;
currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease;
}
//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.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");
}

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