Unstable 0.1500.1.0 (BaroDev edition)

This commit is contained in:
Markus Isberg
2021-09-03 21:56:31 +09:00
parent 501e02c026
commit e7b7c1a748
143 changed files with 2928 additions and 1356 deletions
@@ -623,6 +623,7 @@ namespace Barotrauma
bool removeOnDeath = inc.ReadBoolean(); bool removeOnDeath = inc.ReadBoolean();
ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath); ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath);
} }
ch.ExperiencePoints = inc.ReadUInt16();
return ch; return ch;
} }
@@ -1,10 +1,4 @@
using Microsoft.Xna.Framework; namespace Barotrauma
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{ {
partial class AfflictionHusk : Affliction partial class AfflictionHusk : Affliction
{ {
File diff suppressed because it is too large Load Diff
@@ -632,7 +632,7 @@ namespace Barotrauma
RefreshDeformations(); RefreshDeformations();
} }
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{ {
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f;
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
@@ -678,7 +678,7 @@ namespace Barotrauma
if (!hideLimb) if (!hideLimb)
{ {
var deformSprite = DeformSprite; var deformSprite = DeformSprite;
if (deformSprite != null) if (deformSprite != null && !disableDeformations)
{ {
if (ActiveDeformations.Any()) if (ActiveDeformations.Any())
{ {
@@ -999,10 +999,12 @@ namespace Barotrauma
} }
float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale; float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale;
float rotation = -body.DrawRotation - wearable.Rotation * Dir;
wearable.Sprite.Draw(spriteBatch, wearable.Sprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), 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)), 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, -body.DrawRotation, origin, rotation,
Scale * textureScale, spriteEffect, depth); Scale * textureScale, spriteEffect, depth);
} }
@@ -1320,11 +1320,13 @@ namespace Barotrauma
continue; continue;
} }
float avgOutCondition = (deconstructItem.OutConditionMin + deconstructItem.OutConditionMax) / 2;
int? deconstructProductPrice = targetItem.GetMinPrice(); int? deconstructProductPrice = targetItem.GetMinPrice();
if (deconstructProductPrice.HasValue) if (deconstructProductPrice.HasValue)
{ {
if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; } if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; }
deconstructProductCost += (int)(deconstructProductPrice * deconstructItem.OutCondition); deconstructProductCost += (int)(deconstructProductPrice * avgOutCondition);
} }
if (fabricationRecipe != null) if (fabricationRecipe != null)
@@ -1334,9 +1336,9 @@ namespace Barotrauma
{ {
NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red); NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red);
} }
else if (ingredient.UseCondition && ingredient.MinCondition < deconstructItem.OutCondition) else if (ingredient.UseCondition && ingredient.MinCondition < avgOutCondition)
{ {
NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(deconstructItem.OutCondition * 100)}%)", Color.Red); NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(avgOutCondition * 100)}%)", Color.Red);
} }
} }
} }
@@ -251,6 +251,11 @@ namespace Barotrauma
private readonly bool isHorizontal; private readonly bool isHorizontal;
/// <summary>
/// Setting this to true and CanBeFocused to false allows the list background to be unfocusable while the elements can still be interacted with.
/// </summary>
public bool CanInteractWhenUnfocusable { get; set; } = false;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param> /// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT)
{ {
@@ -570,7 +575,7 @@ namespace Barotrauma
if (child == null || !child.Visible) { continue; } if (child == null || !child.Visible) { continue; }
// selecting // selecting
if (Enabled && CanBeFocused && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child)) if (Enabled && (CanBeFocused || CanInteractWhenUnfocusable) && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child))
{ {
child.State = ComponentState.Hover; child.State = ComponentState.Hover;
@@ -79,14 +79,14 @@ namespace Barotrauma
new Rectangle( new Rectangle(
sliderArea.X + (int)sliceBorderSizes.X, sliderArea.X + (int)sliceBorderSizes.X,
sliderArea.Y, sliderArea.Y,
(int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount), (int)Math.Round((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
sliderArea.Height) sliderArea.Height)
: :
new Rectangle( new Rectangle(
sliderArea.X, sliderArea.X,
(int)(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W), (int)Math.Round(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W),
sliderArea.Width, sliderArea.Width,
(int)((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount)); (int)Math.Round((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount));
sliderRect.Width = Math.Max(sliderRect.Width, 1); sliderRect.Width = Math.Max(sliderRect.Width, 1);
sliderRect.Height = Math.Max(sliderRect.Height, 1); sliderRect.Height = Math.Max(sliderRect.Height, 1);
@@ -1219,7 +1219,11 @@ namespace Barotrauma
GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null); GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null);
GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true); GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true);
CreateCharacterSheet(characterInfoColumn); // move to a different tab menu
if (GameSettings.VerboseLogging)
{
CreateCharacterSheet(characterInfoColumn);
}
if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
@@ -1254,7 +1258,7 @@ namespace Barotrauma
Stretch = true, Stretch = true,
}; };
foreach (Talent talent in talentOption.Talents) foreach (TalentPrefab talent in talentOption.Talents)
{ {
int optionPadding = GUI.IntScale(10); int optionPadding = GUI.IntScale(10);
GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null) GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null)
@@ -1269,13 +1273,13 @@ namespace Barotrauma
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame") GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame")
{ {
ToolTip = $"{TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? talent.Identifier} \n\n{TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty}", ToolTip = $"{talent.DisplayName}\n\n{talent.Description}",
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
talentTitleText.Text = TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? string.Empty; talentTitleText.Text = talent.DisplayName;
talentDescriptionText.Text = TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty; talentDescriptionText.Text = talent.Description;
// deselect other buttons in tier by removing their selected talents from pool // deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>()) foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
@@ -1440,10 +1444,10 @@ namespace Barotrauma
private readonly StatTypes[] combatStats = new StatTypes[] private readonly StatTypes[] combatStats = new StatTypes[]
{ {
StatTypes.MaximumHealthMultiplier, StatTypes.MeleeAttackMultiplier,
StatTypes.MovementSpeed, StatTypes.MeleeAttackSpeed,
StatTypes.SwimmingSpeed, StatTypes.RangedAttackSpeed,
StatTypes.RepairSpeed, StatTypes.TurretAttackSpeed,
}; };
private readonly StatTypes[] miscStats = new StatTypes[] private readonly StatTypes[] miscStats = new StatTypes[]
@@ -99,7 +99,9 @@ namespace Barotrauma
crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false) crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false)
{ {
AutoHideScrollBar = false, AutoHideScrollBar = false,
CanBeFocused = false,
CanDragElements = true, CanDragElements = true,
CanInteractWhenUnfocusable = true,
OnSelected = (component, userData) => false, OnSelected = (component, userData) => false,
SelectMultiple = false, SelectMultiple = false,
Spacing = (int)(GUI.Scale * 10), Spacing = (int)(GUI.Scale * 10),
@@ -770,7 +772,7 @@ namespace Barotrauma
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
character.SetOrder(order, option, priority, orderGiver, speak: orderGiver != character); character.SetOrder(order, option, priority, orderGiver, speak: orderGiver != character);
string message = order?.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, priority: priority); string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, priority: priority);
orderGiver?.Speak(message); orderGiver?.Speak(message);
} }
else if (orderGiver != null) else if (orderGiver != null)
@@ -1905,13 +1907,23 @@ namespace Barotrauma
} }
} }
private bool CanSomeoneHearCharacter() private bool CanCharacterBeHeard()
{ {
#if DEBUG #if DEBUG
if (Character.Controlled == null) { return true; } if (Character.Controlled == null) { return true; }
#endif #endif
return Character.Controlled != null && if (Character.Controlled != null)
(characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled))); {
if (characterContext == null)
{
return characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
}
else
{
return characterContext.CanHearCharacter(Character.Controlled);
}
}
return false;
} }
private Entity FindEntityContext() private Entity FindEntityContext()
@@ -2580,7 +2592,7 @@ namespace Barotrauma
for (int i = 0; i < orders.Count; i++) for (int i = 0; i < orders.Count; i++)
{ {
order = orders[i]; order = orders[i];
disableNode = !CanSomeoneHearCharacter() || disableNode = !CanCharacterBeHeard() ||
(order.MustSetTarget && (order.ItemComponentType != null || order.TargetItems.Length > 0) && (order.MustSetTarget && (order.ItemComponentType != null || order.TargetItems.Length > 0) &&
order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None()); order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None());
optionNodes.Add(new Tuple<GUIComponent, Keys>( optionNodes.Add(new Tuple<GUIComponent, Keys>(
@@ -2728,7 +2740,7 @@ namespace Barotrauma
} }
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count)); var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count));
bool disableNode = !CanSomeoneHearCharacter(); bool disableNode = !CanCharacterBeHeard();
for (int i = 0; i < contextualOrders.Count; i++) for (int i = 0; i < contextualOrders.Count; i++)
{ {
optionNodes.Add(new Tuple<GUIComponent, Keys>( optionNodes.Add(new Tuple<GUIComponent, Keys>(
@@ -2766,7 +2778,7 @@ namespace Barotrauma
if (checkIfOrderCanBeHeard && !disableNode) if (checkIfOrderCanBeHeard && !disableNode)
{ {
disableNode = !CanSomeoneHearCharacter(); disableNode = !CanCharacterBeHeard();
} }
var mustSetOptionOrTarget = order.HasOptions; var mustSetOptionOrTarget = order.HasOptions;
@@ -2827,7 +2839,7 @@ namespace Barotrauma
if (disableNode) if (disableNode)
{ {
node.CanBeFocused = icon.CanBeFocused = false; node.CanBeFocused = icon.CanBeFocused = false;
CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get("nocharactercanhear")); CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext == null ? "nocharactercanhear" : "thischaractercanthear"));
} }
else if (hotkey >= 0) else if (hotkey >= 0)
{ {
@@ -2999,11 +3011,11 @@ namespace Barotrauma
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") + "\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip")); "\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip"));
} }
if (!CanSomeoneHearCharacter()) if (!CanCharacterBeHeard())
{ {
node.CanBeFocused = false; node.CanBeFocused = false;
if (icon != null) { icon.CanBeFocused = false; } if (icon != null) { icon.CanBeFocused = false; }
CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get("nocharactercanhear")); CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext == null ? "nocharactercanhear" : "thischaractercanthear"));
} }
else if (hotkey >= 0) else if (hotkey >= 0)
{ {
@@ -174,6 +174,12 @@ namespace Barotrauma
(int)SlotPositions[i].X, (int)SlotPositions[i].X,
(int)SlotPositions[i].Y, (int)SlotPositions[i].Y,
(int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier)); (int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
if (SlotTypes[i] == InvSlotType.HealthInterface &&
character.CharacterHealth?.InventorySlotContainer != null)
{
slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f);
}
ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>(); ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
if (itemContainer != null) if (itemContainer != null)
@@ -238,6 +244,8 @@ namespace Barotrauma
{ {
if (visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty())) { return true; } if (visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty())) { return true; }
if (CharacterHealth.OpenHealthWindow != Character.Controlled?.CharacterHealth && SlotTypes[i] == InvSlotType.HealthInterface) { return true; }
if (layout == Layout.Default) if (layout == Layout.Default)
{ {
if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(visualSlots[i].Rect.Center + visualSlots[i].DrawOffset.ToPoint())) { return true; } if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(visualSlots[i].Rect.Center + visualSlots[i].DrawOffset.ToPoint())) { return true; }
@@ -359,7 +367,7 @@ namespace Barotrauma
int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing; int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing;
for (int i = 0; i < visualSlots.Length; i++) for (int i = 0; i < visualSlots.Length; i++)
{ {
if (HideSlot(i)) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
@@ -383,7 +391,7 @@ namespace Barotrauma
continue; continue;
} }
if (HideSlot(i)) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
@@ -399,7 +407,7 @@ namespace Barotrauma
x = lowerX; x = lowerX;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (!HideSlot(i)) { continue; } if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
x -= visualSlots[i].Rect.Width + Spacing; x -= visualSlots[i].Rect.Width + Spacing;
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
@@ -414,7 +422,7 @@ namespace Barotrauma
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
@@ -436,7 +444,7 @@ namespace Barotrauma
SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY); SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY);
continue; continue;
} }
if (!HideSlot(i)) { continue; } if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + Spacing;
} }
@@ -450,7 +458,7 @@ namespace Barotrauma
int x = startX, y = startY; int x = startX, y = startY;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes) if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
@@ -462,7 +470,7 @@ namespace Barotrauma
int n = 0; int n = 0;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes) if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
@@ -479,13 +487,23 @@ namespace Barotrauma
} }
break; break;
} }
if (character.CharacterHealth?.UseHealthWindow ?? false)
{
Vector2 pos = character.CharacterHealth.InventorySlotContainer.Rect.Location.ToVector2();
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = pos;
pos.Y += visualSlots[i].Rect.Height + Spacing;
}
}
CreateSlots(); CreateSlots();
if (layout == Layout.Default) if (layout == Layout.Default)
{ {
HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale); HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale);
} }
} }
protected override void ControlInput(Camera cam) protected override void ControlInput(Camera cam)
@@ -679,7 +697,7 @@ namespace Barotrauma
if (item != null) if (item != null)
{ {
var slot = visualSlots[i]; var slot = visualSlots[i];
if (item.AllowedSlots.Any(a => a != InvSlotType.Any)) if (item.AllowedSlots.Any(a => a != InvSlotType.Any && a != InvSlotType.HealthInterface))
{ {
HandleButtonEquipStates(item, slot, deltaTime); HandleButtonEquipStates(item, slot, deltaTime);
} }
@@ -858,7 +876,9 @@ namespace Barotrauma
private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment) private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment)
{ {
if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null) if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null &&
//if the item can be equipped in the health interface slot, don't use it as a treatment but try to equip it
!item.AllowedSlots.Contains(InvSlotType.HealthInterface))
{ {
return QuickUseAction.UseTreatment; return QuickUseAction.UseTreatment;
} }
@@ -1153,7 +1173,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
if (HideSlot(i)) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
//don't draw the item if it's being dragged out of the slot //don't draw the item if it's being dragged out of the slot
bool drawItem = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn(); bool drawItem = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn();
@@ -1207,7 +1227,7 @@ namespace Barotrauma
highlightedQuickUseSlot = visualSlots[i]; highlightedQuickUseSlot = visualSlots[i];
} }
if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any)) if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any) || SlotTypes[i] == InvSlotType.HealthInterface)
{ {
continue; continue;
} }
@@ -0,0 +1,74 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Linq;
namespace Barotrauma.Items.Components
{
partial class GeneticMaterial : ItemComponent
{
[Serialize(0.0f, false)]
public float TooltipValueMin { get; set; }
[Serialize(0.0f, false)]
public float TooltipValueMax { get; set; }
public override void AddTooltipInfo(ref string name, ref string description)
{
if (!string.IsNullOrEmpty(materialName))
{
string mergedMaterialName = materialName;
foreach (Item containedItem in item.ContainedItems)
{
var containedMaterial = containedItem.GetComponent<GeneticMaterial>();
if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName;
}
name = TextManager.GetWithVariable("entityname.geneticmaterial", "[type]", mergedMaterialName);
}
if (Tainted)
{
name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name);
}
if (TextManager.ContainsTag("entitydescription."+Item.prefab.Identifier))
{
int value = (int)MathHelper.Lerp(TooltipValueMin, TooltipValueMax, item.ConditionPercentage / 100.0f);
description = TextManager.GetWithVariable("entitydescription." + Item.prefab.Identifier, "[value]", value.ToString());
}
}
public void ModifyDeconstructInfo(Deconstructor deconstructor, ref string buttonText, ref string infoText)
{
if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2)
{
if (!deconstructor.InputContainer.Inventory.AllItems.All(it => it.prefab == item.prefab))
{
buttonText = TextManager.Get("researchstation.combine");
infoText = TextManager.Get("researchstation.combine.infotext");
}
else
{
buttonText = TextManager.Get("researchstation.refine");
int taintedProbability = (int)(GetTaintedProbabilityOnRefine(Character.Controlled) * 100);
infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString());
}
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Tainted = msg.ReadBoolean();
if (Tainted)
{
uint selectedTaintedEffectId = msg.ReadUInt32();
selectedTaintedEffect = AfflictionPrefab.Prefabs.Find(a => a.UIntIdentifier == selectedTaintedEffectId);
}
else
{
uint selectedEffectId = msg.ReadUInt32();
selectedEffect = AfflictionPrefab.Prefabs.Find(a => a.UIntIdentifier == selectedEffectId);
}
}
}
}
@@ -621,6 +621,6 @@ namespace Barotrauma.Items.Components
} }
OnResolutionChanged(); OnResolutionChanged();
} }
public virtual void AddTooltipInfo(ref string description) { } public virtual void AddTooltipInfo(ref string name, ref string description) { }
} }
} }
@@ -58,6 +58,9 @@ namespace Barotrauma.Items.Components
[Serialize(null, false)] [Serialize(null, false)]
public string ContainedStateIndicatorStyle { get; set; } public string ContainedStateIndicatorStyle { get; set; }
[Serialize(-1, false, description: "Can be used to make the contained state indicator display the condition of the item in a specific slot even when the container's capacity is more than 1.")]
public int ContainedStateIndicatorSlot { get; set; }
[Serialize(true, false, description: "Should an indicator displaying the state of the contained items be displayed on this item's inventory slot. "+ [Serialize(true, false, description: "Should an indicator displaying the state of the contained items be displayed on this item's inventory slot. "+
"If this item can only contain one item, the indicator will display the condition of the contained item, otherwise it will indicate how full the item is.")] "If this item can only contain one item, the indicator will display the condition of the contained item, otherwise it will indicate how full the item is.")]
public bool ShowContainedStateIndicator { get; set; } public bool ShowContainedStateIndicator { get; set; }
@@ -14,12 +14,19 @@ namespace Barotrauma.Items.Components
} }
private GUIButton activateButton; private GUIButton activateButton;
private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUIComponent inputInventoryHolder, outputInventoryHolder;
private GUICustomComponent inputInventoryOverlay;
private GUIComponent inSufficientPowerWarning; private GUIComponent inSufficientPowerWarning;
private bool pendingState; private bool pendingState;
private GUITextBlock infoArea;
[Serialize("DeconstructorDeconstruct", true)]
public string ActivateButtonText { get; set; }
[Serialize(0.0f, true)]
public float InfoAreaWidth { get; set; }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
CreateGUI(); CreateGUI();
@@ -39,6 +46,12 @@ namespace Barotrauma.Items.Components
RelativeSpacing = 0.08f RelativeSpacing = 0.08f
}; };
new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform), item.Name, font: GUI.SubHeadingFont)
{
TextAlignment = Alignment.Center,
AutoScaleHorizontal = true
};
var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null);
// === INPUT LABEL === // // === INPUT LABEL === //
@@ -55,22 +68,23 @@ namespace Barotrauma.Items.Components
// === INPUT SLOTS === // // === INPUT SLOTS === //
inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null); inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
inputInventoryOverlay = new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false }; new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false };
// === ACTIVATE BUTTON === // // === ACTIVATE BUTTON === //
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.7f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft);
activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
{ {
TextBlock = { AutoScaleHorizontal = true }, TextBlock = { AutoScaleHorizontal = true },
OnClicked = ToggleActive OnClicked = ToggleActive
}; };
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
{ {
HoverColor = Color.Black, HoverColor = Color.Black,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false, Visible = false,
CanBeFocused = false CanBeFocused = false,
AutoScaleHorizontal = true
}; };
// === OUTPUT AREA === // // === OUTPUT AREA === //
@@ -86,8 +100,62 @@ namespace Barotrauma.Items.Components
outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height)); outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine");
var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
// === OUTPUT SLOTS === // // === OUTPUT SLOTS === //
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null); outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null);
if (InfoAreaWidth >= 0.0f)
{
var infoAreaContainer = new GUILayoutGroup(new RectTransform(new Vector2(InfoAreaWidth, 0.8f), outputArea.RectTransform), childAnchor: Anchor.CenterLeft);
infoArea = new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.95f), infoAreaContainer.RectTransform), string.Empty, wrap: true);
}
ActivateButton.OnAddedToGUIUpdateList += (GUIComponent component) =>
{
activateButton.Enabled = true;
infoArea.Text = string.Empty;
if (IsActive)
{
activateButton.Text = TextManager.Get("DeconstructorCancel");
return;
}
bool outputsFound = false;
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true))
{
outputsFound = true;
if (!string.IsNullOrEmpty(deconstructItem.ActivateButtonText))
{
string buttonText = TextManager.Get(deconstructItem.ActivateButtonText, returnNull: true) ?? deconstructItem.ActivateButtonText;
string infoText = string.Empty;
if (!string.IsNullOrEmpty(deconstructItem.InfoText))
{
infoText = TextManager.Get(deconstructItem.InfoText, returnNull: true) ?? deconstructItem.InfoText;
}
inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText);
activateButton.Text = buttonText;
if (infoArea != null)
{
infoArea.Text = infoText;
}
return;
}
}
//no valid outputs found: check if we're missing some required items from the input slots and display a message about it if possible
if (!outputsFound && infoArea != null)
{
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: false))
{
if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing))
{
string missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First(), returnNull: true);
infoArea.Text = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
}
}
}
activateButton.Enabled = inputContainer.Inventory.AllItems.Any();
activateButton.Text = TextManager.Get(ActivateButtonText);
};
} }
public override bool Select(Character character) public override bool Select(Character character)
@@ -126,14 +194,30 @@ namespace Barotrauma.Items.Components
private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{ {
overlayComponent.RectTransform.SetAsLastChild(); overlayComponent.RectTransform.SetAsLastChild();
if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
var lastSlot = visualSlots.Last();
GUI.DrawRectangle(spriteBatch, if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
new Rectangle(
lastSlot.Rect.X, lastSlot.Rect.Y + (int)(lastSlot.Rect.Height * (1.0f - progressState)), if (DeconstructItemsSimultaneously)
lastSlot.Rect.Width, (int)(lastSlot.Rect.Height * progressState)), {
GUI.Style.Green * 0.5f, isFilled: true); for (int i = 0; i < InputContainer.Inventory.Capacity; i++)
{
if (InputContainer.Inventory.GetItemAt(i) == null) { continue; }
DrawProgressBar(InputContainer.Inventory.visualSlots[i]);
}
}
else
{
DrawProgressBar(inputContainer.Inventory.visualSlots.Last());
}
void DrawProgressBar(VisualSlot slot)
{
GUI.DrawRectangle(spriteBatch,
new Rectangle(
slot.Rect.X, slot.Rect.Y + (int)(slot.Rect.Height * (1.0f - progressState)),
slot.Rect.Width, (int)(slot.Rect.Height * progressState)),
GUI.Style.Green * 0.5f, isFilled: true);
}
} }
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
@@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center)); scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false }; miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
miniMapFrame = CreateMiniMap(item.Submarine, miniMapContainer, MiniMapSettings.Default, null, out hullStatusComponents); miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, null, out hullStatusComponents);
IEnumerable<Item> pointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null); IEnumerable<Item> pointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), pointsOfInterest, out electricalMapComponents); electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), pointsOfInterest, out electricalMapComponents);
@@ -458,7 +458,7 @@ namespace Barotrauma.Items.Components
CreateHUD(); CreateHUD();
} }
if (PlayerInput.PrimaryMouseButtonDown()) if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus)
{ {
if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))
{ {
@@ -466,20 +466,16 @@ namespace Barotrauma.Items.Components
} }
} }
float newZoom = Zoom;
if (Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) if (currentMode != MiniMapMode.HullStatus && Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)))
{ {
newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); float newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom);
float distanceScale = newZoom / Zoom; float distanceScale = newZoom / Zoom;
mapOffset *= distanceScale; mapOffset *= distanceScale;
recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom);
Zoom = newZoom;
} }
recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom);
Zoom = newZoom;
Vector2 elementScale = new Vector2(Zoom);
if (dragMapStart is { } dragStart) if (dragMapStart is { } dragStart)
{ {
if (dragMap || Vector2.DistanceSquared(dragStart, PlayerInput.MousePosition) > GUI.IntScale(dragTreshold * dragTreshold)) if (dragMap || Vector2.DistanceSquared(dragStart, PlayerInput.MousePosition) > GUI.IntScale(dragTreshold * dragTreshold))
@@ -487,16 +483,11 @@ namespace Barotrauma.Items.Components
mapOffset.X += PlayerInput.MouseSpeed.X; mapOffset.X += PlayerInput.MouseSpeed.X;
mapOffset.Y += PlayerInput.MouseSpeed.Y; mapOffset.Y += PlayerInput.MouseSpeed.Y;
recalculate |= PlayerInput.MouseSpeed != Vector2.Zero; recalculate = true;
dragMap = true; dragMap = true;
} }
} }
var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f / Zoom;
mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth);
mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight);
if (!PlayerInput.PrimaryMouseButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
dragMapStart = null; dragMapStart = null;
@@ -505,10 +496,15 @@ namespace Barotrauma.Items.Components
if (recalculate) if (recalculate)
{ {
miniMapContainer.RectTransform.LocalScale = elementScale; miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom);
miniMapContainer.RectTransform.RecalculateChildren(true, true); miniMapContainer.RectTransform.RecalculateChildren(true, true);
miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint();
recalculate = false; recalculate = false;
var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f / Zoom;
mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth);
mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight);
} }
// is there a better way to do this? // is there a better way to do this?
@@ -583,6 +579,7 @@ namespace Barotrauma.Items.Components
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
{ {
// TODO remove // TODO remove
#warning remove
if (currentMode == MiniMapMode.HullCondition) if (currentMode == MiniMapMode.HullCondition)
{ {
const string wipText = "work in progress"; const string wipText = "work in progress";
@@ -757,7 +754,7 @@ namespace Barotrauma.Items.Components
foreach (Item foundItem in foundItems) foreach (Item foundItem in foundItems)
{ {
RelativeEntityRect scaledRect = new RelativeEntityRect(dockedBorders, foundItem.WorldRect); RelativeEntityRect scaledRect = new RelativeEntityRect(dockedBorders, foundItem.WorldRect);
Vector2 pos = (scaledRect.PositionRelativeTo(parentRect, skipOffset: true) + scaledRect.SizeRelativeTo(parentRect) / 2f) / Zoom; Vector2 pos = scaledRect.PositionRelativeTo(parentRect, skipOffset: true) + scaledRect.SizeRelativeTo(parentRect) / 2f;
positions.Add(pos); positions.Add(pos);
} }
@@ -873,10 +870,14 @@ namespace Barotrauma.Items.Components
string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty; string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty;
Color line1Color = GUI.Style.Red; Color line1Color = GUI.Style.Red;
string line2 = oxygenAmount == null ? TextManager.Get("MiniMapAirQualityUnavailable") : TextManager.AddPunctuation(':', TextManager.Get("MiniMapAirQuality"), +(int)oxygenAmount + " %"); string line2 = oxygenAmount == null ?
TextManager.Get("MiniMapAirQualityUnavailable") :
TextManager.AddPunctuation(':', TextManager.Get("MiniMapAirQuality"), (int)Math.Round(oxygenAmount.Value) + "%");
Color line2Color = oxygenAmount == null ? GUI.Style.Red : Color.Lerp(GUI.Style.Red, Color.LightGreen, (float)oxygenAmount / 100.0f); Color line2Color = oxygenAmount == null ? GUI.Style.Red : Color.Lerp(GUI.Style.Red, Color.LightGreen, (float)oxygenAmount / 100.0f);
string line3 = waterAmount == null ? TextManager.Get("MiniMapWaterLevelUnavailable") : TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)(waterAmount * 100.0f) + " %"); string line3 = waterAmount == null ?
TextManager.Get("MiniMapWaterLevelUnavailable") :
TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)Math.Round(waterAmount.Value * 100.0f) + "%");
Color line3Color = waterAmount == null ? GUI.Style.Red : Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)waterAmount); Color line3Color = waterAmount == null ? GUI.Style.Red : Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)waterAmount);
SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color); SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color);
@@ -958,7 +959,7 @@ namespace Barotrauma.Items.Components
Rectangle parentRect = container.Rect; Rectangle parentRect = container.Rect;
if (miniMapFrame is { } miniMap) { parentRect = miniMap.Rect; } if (miniMapFrame is { } miniMap) { parentRect = miniMap.Rect; }
DrawSubmarine(spriteBatch, parentRect); DrawSubmarine(spriteBatch);
} }
if (Voltage < MinVoltage) { return; } if (Voltage < MinVoltage) { return; }
@@ -979,7 +980,7 @@ namespace Barotrauma.Items.Components
Vector2 spriteScale = targetSize / pingCircle.size; Vector2 spriteScale = targetSize / pingCircle.size;
float scale = Math.Min(blipState, maxBlipState / 2f); float scale = Math.Min(blipState, maxBlipState / 2f);
float alpha = 1.0f - Math.Clamp((blipState - maxBlipState * 0.25f) * 2f, 0f, 1f); float alpha = 1.0f - Math.Clamp((blipState - maxBlipState * 0.25f) * 2f, 0f, 1f);
pingCircle.Draw(spriteBatch, miniMapFrame.Rect.Location.ToVector2() + (blip * Zoom), GUI.Style.Red * alpha, pingCircle.Origin, 0f, spriteScale * scale, SpriteEffects.None); pingCircle.Draw(spriteBatch, electricalFrame.Rect.Location.ToVector2() + blip * Zoom, GUI.Style.Red * alpha, pingCircle.Origin, 0f, spriteScale * scale, SpriteEffects.None);
} }
} }
} }
@@ -1002,8 +1003,15 @@ namespace Barotrauma.Items.Components
{ {
RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount); RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount);
const float width = 1f;
GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor); GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor);
GUI.DrawLine(spriteBatch, waterRect.Location, new Vector2(waterRect.Right, waterRect.Y), HullWaterLineColor);
if (!MathUtils.NearlyEqual(waterAmount, 1.0f))
{
Vector2 offset = new Vector2(0, width);
GUI.DrawLine(spriteBatch, waterRect.Location + offset, new Vector2(waterRect.Right, waterRect.Y) + offset, HullWaterLineColor, width: width);
}
} }
} }
@@ -1079,7 +1087,7 @@ namespace Barotrauma.Items.Components
submarinePreview = rt; submarinePreview = rt;
} }
private void DrawSubmarine(SpriteBatch spriteBatch, Rectangle parentRect) private void DrawSubmarine(SpriteBatch spriteBatch)
{ {
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End(); spriteBatch.End();
@@ -1094,7 +1102,8 @@ namespace Barotrauma.Items.Components
Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f }; Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f };
Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f); Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
spriteBatch.Draw(texture, parentRect.Center.ToVector2(), null, blueprintBlue, 0f, origin, Zoom, SpriteEffects.None, 0f); float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom;
spriteBatch.Draw(texture, miniMapContainer.Center, null, blueprintBlue, 0f, origin, scale, SpriteEffects.None, 0f);
spriteBatch.End(); spriteBatch.End();
} }
@@ -1281,12 +1290,12 @@ namespace Barotrauma.Items.Components
GUIFrame hullContainer = new GUIFrame(new RectTransform(containerScale * elementPadding, parent.RectTransform, Anchor.Center), style: null); GUIFrame hullContainer = new GUIFrame(new RectTransform(containerScale * elementPadding, parent.RectTransform, Anchor.Center), style: null);
ImmutableHashSet<Submarine> connectedSubs = sub.GetConnectedSubs().ToImmutableHashSet(); ImmutableHashSet<Submarine> connectedSubs = sub.GetConnectedSubs().ToImmutableHashSet();
ImmutableHashSet<Hull> hullList = ImmutableHashSet<Hull>.Empty; ImmutableArray<Hull> hullList = ImmutableArray<Hull>.Empty;
ImmutableDictionary<Hull, ImmutableHashSet<Hull>> combinedHulls = ImmutableDictionary<Hull, ImmutableHashSet<Hull>>.Empty; ImmutableDictionary<Hull, ImmutableArray<Hull>> combinedHulls = ImmutableDictionary<Hull, ImmutableArray<Hull>>.Empty;
if (settings.CreateHullElements) if (settings.CreateHullElements)
{ {
hullList = Hull.hullList.Where(IsPartofSub).ToImmutableHashSet(); hullList = Hull.hullList.Where(IsPartofSub).ToImmutableArray();
combinedHulls = CombinedHulls(hullList); combinedHulls = CombinedHulls(hullList);
} }
@@ -1436,7 +1445,7 @@ namespace Barotrauma.Items.Components
} }
} }
private static ImmutableDictionary<Hull, ImmutableHashSet<Hull>> CombinedHulls(ImmutableHashSet<Hull> hulls) private static ImmutableDictionary<Hull, ImmutableArray<Hull>> CombinedHulls(ImmutableArray<Hull> hulls)
{ {
Dictionary<Hull, HashSet<Hull>> combinedHulls = new Dictionary<Hull, HashSet<Hull>>(); Dictionary<Hull, HashSet<Hull>> combinedHulls = new Dictionary<Hull, HashSet<Hull>>();
@@ -1460,10 +1469,10 @@ namespace Barotrauma.Items.Components
} }
} }
return combinedHulls.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableHashSet()); return combinedHulls.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableArray());
} }
private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableHashSet<Hull> linkedHulls, GUIComponent parent, RectangleF worldBorders) private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableArray<Hull> linkedHulls, GUIComponent parent, RectangleF worldBorders)
{ {
Rectangle parentRect = parent.Rect; Rectangle parentRect = parent.Rect;
@@ -1500,6 +1509,8 @@ namespace Barotrauma.Items.Components
hullRefs.Add(hull); hullRefs.Add(hull);
} }
hullRefs.Reverse(); // I have no idea why this is required
ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1); ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1);
List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles); List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles);
@@ -1508,6 +1519,7 @@ namespace Barotrauma.Items.Components
foreach (List<Vector2> list in polygon) foreach (List<Vector2> list in polygon)
{ {
// scale down the polygon just a tiny bit
var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list); var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list);
float sizeX = polySizeX - 1f, float sizeX = polySizeX - 1f,
sizeY = polySizeY - 1f; sizeY = polySizeY - 1f;
@@ -0,0 +1,22 @@
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
partial class RemoteController : ItemComponent
{
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character);
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{
currentTarget?.UpdateHUD(cam, character,deltaTime);
}
public override void AddToGUIUpdateList()
{
currentTarget?.AddToGUIUpdateList();
}
}
}
@@ -54,6 +54,11 @@ namespace Barotrauma.Items.Components
if (wireComponent != null) if (wireComponent != null)
{ {
equippedWire = wireComponent; equippedWire = wireComponent;
var connectedEnd = equippedWire.OtherConnection(null);
if (connectedEnd?.Item.Submarine != null && panel.Item.Submarine != connectedEnd.Item.Submarine)
{
equippedWire = null;
}
} }
} }
} }
@@ -63,11 +63,11 @@ namespace Barotrauma.Items.Components
} }
OutputValue = input; OutputValue = input;
ShowOnDisplay(input); ShowOnDisplay(input, addToHistory: true);
item.SendSignal(input, "signal_out"); item.SendSignal(input, "signal_out");
} }
partial void ShowOnDisplay(string input, bool addToHistory = true) partial void ShowOnDisplay(string input, bool addToHistory)
{ {
if (addToHistory) if (addToHistory)
{ {
@@ -39,6 +39,34 @@ namespace Barotrauma.Items.Components
private set; private set;
} }
[Serialize(false, false)]
public bool SeeThroughWalls
{
get;
private set;
}
[Serialize(true, false)]
public bool ShowDeadCharacters
{
get;
private set;
}
[Serialize(true, false)]
public bool ShowTexts
{
get;
private set;
}
[Serialize("72,119,72,120", false)]
public Color OverlayColor
{
get;
private set;
}
private readonly List<Character> visibleCharacters = new List<Character>(); private readonly List<Character> visibleCharacters = new List<Character>();
private const float UpdateInterval = 0.5f; private const float UpdateInterval = 0.5f;
@@ -91,12 +119,13 @@ namespace Barotrauma.Items.Components
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c == equipper || !c.Enabled || c.Removed) { continue; } if (c == equipper || !c.Enabled || c.Removed) { continue; }
if (!ShowDeadCharacters && c.IsDead) { continue; }
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition); float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist < Range * Range) if (dist < Range * Range)
{ {
Vector2 diff = c.WorldPosition - refEntity.WorldPosition; Vector2 diff = c.WorldPosition - refEntity.WorldPosition;
if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) if (SeeThroughWalls || Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
{ {
visibleCharacters.Add(c); visibleCharacters.Add(c);
} }
@@ -123,27 +152,54 @@ namespace Barotrauma.Items.Components
{ {
if (character == null) { return; } if (character == null) { return; }
GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), if (OverlayColor.A > 0)
Color.LightGreen * 0.5f);
Character closestCharacter = null;
float closestDist = float.PositiveInfinity;
foreach (Character c in visibleCharacters)
{ {
if (c == character || !c.Enabled || c.Removed) { continue; } GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), OverlayColor);
float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition);
if (dist < closestDist)
{
closestCharacter = c;
closestDist = dist;
}
} }
if (closestCharacter != null) if (ShowTexts)
{ {
float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition); Character closestCharacter = null;
DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f)); float closestDist = float.PositiveInfinity;
foreach (Character c in visibleCharacters)
{
if (c == character || !c.Enabled || c.Removed) { continue; }
float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition);
if (dist < closestDist)
{
closestCharacter = c;
closestDist = dist;
}
}
if (closestCharacter != null)
{
float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition);
DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f));
}
}
if (SeeThroughWalls)
{
spriteBatch.End();
GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.35f + (float)Math.Sin(Timing.TotalTime * 1.6f) * 0.05f));
GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.03f + (float)Math.Sin(Timing.TotalTime) * 0.01f);
GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
foreach (Character c in visibleCharacters)
{
if (c == character || !c.Enabled || c.Removed) { continue; }
foreach (Limb limb in c.AnimController.Limbs)
{
limb.Draw(spriteBatch, Screen.Selected.Cam, disableDeformations: true);
}
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
} }
} }
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}"; description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}";
} }
public override void AddTooltipInfo(ref string description) public override void AddTooltipInfo(ref string name, ref string description)
{ {
if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any()) if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any())
{ {
@@ -297,9 +297,10 @@ namespace Barotrauma
} }
} }
string name = item.Name;
foreach (ItemComponent component in item.Components) foreach (ItemComponent component in item.Components)
{ {
component.AddTooltipInfo(ref description); component.AddTooltipInfo(ref name, ref description);
} }
if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null) if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null)
@@ -315,7 +316,7 @@ namespace Barotrauma
string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White);
toolTip = $"‖color:{colorStr}‖{item.Name}‖color:end‖"; toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))
{ {
toolTip += " " + TextManager.Get("connectionlocked"); toolTip += " " + TextManager.Get("connectionlocked");
@@ -719,7 +720,7 @@ namespace Barotrauma
spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale); spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale);
} }
int columns = (int)Math.Max(Math.Floor(Math.Sqrt(itemCapacity)), 1); int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow);
while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f) while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f)
{ {
columns++; columns++;
@@ -1543,13 +1544,13 @@ namespace Barotrauma
} }
else else
{ {
var containedItem = itemContainer.Inventory.slots[0].FirstOrDefault(); var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault();
containedState = itemContainer.Inventory.Capacity == 1 ? containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) : (containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity; itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && itemContainer.Inventory.Capacity == 1) if (containedItem != null && itemContainer.Inventory.Capacity == 1)
{ {
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.MaxStackSize); int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
if (maxStackSize > 1) if (maxStackSize > 1)
{ {
containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize; containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize;
@@ -1631,7 +1632,7 @@ namespace Barotrauma
int maxStackSize = item.Prefab.MaxStackSize; int maxStackSize = item.Prefab.MaxStackSize;
if (item.Container != null) if (item.Container != null)
{ {
maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent<ItemContainer>()?.MaxStackSize ?? maxStackSize); maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent<ItemContainer>()?.GetMaxStackSize(slotIndex) ?? maxStackSize);
} }
if (maxStackSize > 1 && inventory != null) if (maxStackSize > 1 && inventory != null)
{ {
@@ -1194,7 +1194,14 @@ namespace Barotrauma
} }
} }
if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) { return; } if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this)
{
if (Character.Controlled.SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem != this &&
!Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this))
{
return;
}
}
bool needsLayoutUpdate = false; bool needsLayoutUpdate = false;
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
@@ -423,20 +423,24 @@ namespace Barotrauma
//select wire if both items it's connected to are selected //select wire if both items it's connected to are selected
var selectedItems = SelectedList.Where(e => e is Item).Cast<Item>().ToList(); var selectedItems = SelectedList.Where(e => e is Item).Cast<Item>().ToList();
foreach (Item item in selectedItems) foreach (Item item in Item.ItemList)
{ {
if (item.Connections == null) continue; var wire = item.GetComponent<Wire>();
foreach (Connection c in item.Connections) if (wire == null) { continue; }
{ Item item0 = wire.Connections[0]?.Item;
foreach (Wire w in c.Wires) Item item1 = wire.Connections[1]?.Item;
{
if (w == null || SelectedList.Contains(w.Item)) continue;
if (w.OtherConnection(c) != null && SelectedList.Contains(w.OtherConnection(c).Item)) if (item0 == null && item1 != null)
{ {
SelectedList.Add(w.Item); item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
} }
} else if (item0 != null && item1 == null)
{
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
}
if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1))
{
SelectedList.Add(item);
} }
} }
@@ -326,7 +326,7 @@ namespace Barotrauma
depthSortedDamageable.Insert(i, structure); depthSortedDamageable.Insert(i, structure);
} }
} }
foreach (Structure s in depthSortedDamageable) foreach (Structure s in depthSortedDamageable)
{ {
s.DrawDamage(spriteBatch, damageEffect, editing); s.DrawDamage(spriteBatch, damageEffect, editing);
@@ -418,8 +418,8 @@ namespace Barotrauma
float scale = 0.9f; float scale = 0.9f;
GUIFrame hullContainer = new GUIFrame(new RectTransform( GUIFrame hullContainer = new GUIFrame(new RectTransform(
(parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale, (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
parent.RectTransform, Anchor.Center), parent.RectTransform, Anchor.Center),
style: null) style: null)
{ {
UserData = "hullcontainer" UserData = "hullcontainer"
@@ -444,9 +444,9 @@ namespace Barotrauma
{ {
if (!combinedHulls.ContainsKey(hull)) if (!combinedHulls.ContainsKey(hull))
{ {
combinedHulls.Add(hull, new HashSet<Hull>()); combinedHulls.Add(hull, new HashSet<Hull>());
} }
combinedHulls[hull].Add(linkedHull); combinedHulls[hull].Add(linkedHull);
} }
} }
@@ -454,14 +454,14 @@ namespace Barotrauma
foreach (Hull hull in hullList) foreach (Hull hull in hullList)
{ {
Vector2 relativeHullPos = new Vector2( Vector2 relativeHullPos = new Vector2(
(hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width,
(worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height); (worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height);
Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height); Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height);
bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull)); bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull));
if (hideHull) { continue; } if (hideHull) { continue; }
Color color = Color.DarkCyan * 0.8f; Color color = Color.DarkCyan * 0.8f;
var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: color) var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: color)
@@ -477,7 +477,7 @@ namespace Barotrauma
MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders); MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders);
Vector2 relativeHullPos = new Vector2( Vector2 relativeHullPos = new Vector2(
(data.Bounds.X - worldBorders.X) / worldBorders.Width, (data.Bounds.X - worldBorders.X) / worldBorders.Width,
(worldBorders.Y - data.Bounds.Y) / worldBorders.Height); (worldBorders.Y - data.Bounds.Y) / worldBorders.Height);
Vector2 relativeHullSize = new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height); Vector2 relativeHullSize = new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height);
@@ -507,9 +507,6 @@ namespace Barotrauma
var (parentW, parentH) = hullContainer.Rect.Size.ToVector2(); var (parentW, parentH) = hullContainer.Rect.Size.ToVector2();
Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH); Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH);
// TODO this won't be required if we some day switch RectTransform to use RectangleF // TODO this won't be required if we some day switch RectTransform to use RectangleF
const float padding = 0.001f;
size.X += padding;
size.Y += padding;
Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH); Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH);
GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: color) GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: color)
@@ -586,7 +583,7 @@ namespace Barotrauma
wRect.Y = -wRect.Y; wRect.Y = -wRect.Y;
var (posX, posY) = new Vector2( var (posX, posY) = new Vector2(
(wRect.X - worldBorders.X) / (float)worldBorders.Width, (wRect.X - worldBorders.X) / (float)worldBorders.Width,
(worldBorders.Y - wRect.Y) / (float)worldBorders.Height); (worldBorders.Y - wRect.Y) / (float)worldBorders.Height);
var (scaleX, scaleY) = new Vector2(wRect.Width / (float)worldBorders.Width, wRect.Height / (float)worldBorders.Height); var (scaleX, scaleY) = new Vector2(wRect.Width / (float)worldBorders.Width, wRect.Height / (float)worldBorders.Height);
@@ -629,7 +626,7 @@ namespace Barotrauma
} }
} }
if (Info.Type != SubmarineType.OutpostModule || if (Info.Type != SubmarineType.OutpostModule ||
(Info.OutpostModuleInfo?.ModuleFlags.Any(f => !f.Equals("hallwayvertical", StringComparison.OrdinalIgnoreCase) && !f.Equals("hallwayhorizontal", StringComparison.OrdinalIgnoreCase)) ?? true)) (Info.OutpostModuleInfo?.ModuleFlags.Any(f => !f.Equals("hallwayvertical", StringComparison.OrdinalIgnoreCase) && !f.Equals("hallwayhorizontal", StringComparison.OrdinalIgnoreCase)) ?? true))
{ {
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path))
@@ -697,7 +694,7 @@ namespace Barotrauma
int wireCount = item.Connections[i].Wires.Count(w => w != null); int wireCount = item.Connections[i].Wires.Count(w => w != null);
if (doorLinks + wireCount > item.Connections[i].MaxWires) if (doorLinks + wireCount > item.Connections[i].MaxWires)
{ {
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
new string[] { "[doorcount]", "[freeconnectioncount]" }, new string[] { "[doorcount]", "[freeconnectioncount]" },
new string[] { doorLinks.ToString(), (item.Connections[i].MaxWires - wireCount).ToString() })); new string[] { doorLinks.ToString(), (item.Connections[i].MaxWires - wireCount).ToString() }));
break; break;
@@ -794,7 +791,7 @@ namespace Barotrauma
return SubEditorScreen.SuppressedWarnings.Contains(type); return SubEditorScreen.SuppressedWarnings.Contains(type);
} }
} }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (type != ServerNetObject.ENTITY_POSITION) if (type != ServerNetObject.ENTITY_POSITION)
@@ -184,7 +184,7 @@ namespace Barotrauma.Particles
public ParticlePrefab FindPrefab(string prefabName) public ParticlePrefab FindPrefab(string prefabName)
{ {
return Prefabs.Find(p => p.Identifier == prefabName); return Prefabs.Find(p => p.Identifier.Equals(prefabName, StringComparison.OrdinalIgnoreCase));
} }
private void RemoveParticle(int index) private void RemoveParticle(int index)
@@ -1366,6 +1366,7 @@ namespace Barotrauma
LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible); roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible);
roundControlsHolder.Children.ForEach(c => c.RectTransform.RelativeSize = Vector2.One);
roundControlsHolder.Recalculate(); roundControlsHolder.Recalculate();
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted; ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted;
@@ -51,11 +51,16 @@ namespace Barotrauma
} }
// ???????? // ????????
submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Kastrull", StringComparison.OrdinalIgnoreCase))); submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Crescent", StringComparison.OrdinalIgnoreCase)));
miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine); miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine);
MiniMap miniMap = miniMapItem.GetComponent<MiniMap>(); MiniMap miniMap = miniMapItem.GetComponent<MiniMap>();
miniMap.PowerConsumption = 0; miniMap.PowerConsumption = 0;
foreach (var hull in Hull.hullList)
{
hull.WaterVolume = hull.Volume / 2f;
}
dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false);
dummyCharacter.Info.Name = "Galldren"; dummyCharacter.Info.Name = "Galldren";
dummyCharacter.Inventory.CreateSlots(); dummyCharacter.Inventory.CreateSlots();
@@ -1012,7 +1012,8 @@ namespace Barotrauma
Screen.Selected == GameMain.SpriteEditorScreen || Screen.Selected == GameMain.SpriteEditorScreen ||
Screen.Selected == GameMain.SubEditorScreen || Screen.Selected == GameMain.SubEditorScreen ||
Screen.Selected == GameMain.EventEditorScreen || Screen.Selected == GameMain.EventEditorScreen ||
(Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is TestGameMode)) (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is TestGameMode) ||
Screen.Selected == GameMain.NetLobbyScreen)
{ {
return "editor"; return "editor";
} }
@@ -110,14 +110,14 @@ namespace Barotrauma
foreach (StatusEffect statusEffect in successEffects) foreach (StatusEffect statusEffect in successEffects)
{ {
float duration = statusEffect.Duration; float duration = statusEffect.Duration;
onSuccessAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.First), -pair.Second, duration))); onSuccessAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.affliction), -pair.amount, duration)));
onSuccessAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => Tuple.Create(affliction.Prefab.Name, affliction.NonClampedStrength, duration))); onSuccessAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => Tuple.Create(affliction.Prefab.Name, affliction.NonClampedStrength, duration)));
} }
foreach (StatusEffect statusEffect in failureEffects) foreach (StatusEffect statusEffect in failureEffects)
{ {
float duration = statusEffect.Duration; float duration = statusEffect.Duration;
onFailureAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.First), -pair.Second, duration))); onFailureAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.affliction), -pair.amount, duration)));
onFailureAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => Tuple.Create(affliction.Prefab.Name, affliction.NonClampedStrength, duration))); onFailureAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => Tuple.Create(affliction.Prefab.Name, affliction.NonClampedStrength, duration)));
} }
} }
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.0.0</Version> <Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.0.0</Version> <Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.0.0</Version> <Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.0.0</Version> <Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.0.0</Version> <Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -28,6 +28,15 @@ namespace Barotrauma
} }
} }
if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter))
{
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
if (ownerClient != null)
{
(GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient);
}
}
healthUpdateTimer = 0.0f; healthUpdateTimer = 0.0f;
if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this) if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this)
@@ -73,6 +73,7 @@ namespace Barotrauma
msg.Write(savedStatValue.RemoveOnDeath); msg.Write(savedStatValue.RemoveOnDeath);
} }
} }
msg.Write((ushort)ExperiencePoints);
} }
} }
} }
@@ -27,6 +27,29 @@ namespace Barotrauma
public bool GameOver { get; private set; } public bool GameOver { get; private set; }
class SavedExperiencePoints
{
public readonly ulong SteamID;
public readonly string EndPoint;
public readonly int ExperiencePoints;
public SavedExperiencePoints(Client client)
{
SteamID = client.SteamID;
EndPoint = client.Connection.EndPointString;
ExperiencePoints = client.Character?.Info?.ExperiencePoints ?? 0;
}
public SavedExperiencePoints(XElement element)
{
SteamID = element.GetAttributeUInt64("steamid", 0);
EndPoint = element.GetAttributeString("endpoint", string.Empty);
ExperiencePoints = element.GetAttributeInt("points", 0);
}
}
private readonly List<SavedExperiencePoints> savedExperiencePoints = new List<SavedExperiencePoints>();
public override bool Paused public override bool Paused
{ {
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); } get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
@@ -155,6 +178,20 @@ namespace Barotrauma
c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign))); c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign)));
} }
public void SaveExperiencePoints(Client client)
{
ClearSavedExperiencePoints(client);
savedExperiencePoints.Add(new SavedExperiencePoints(client));
}
public int GetSavedExperiencePoints(Client client)
{
return savedExperiencePoints.Find(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint))?.ExperiencePoints ?? 0;
}
public void ClearSavedExperiencePoints(Client client)
{
savedExperiencePoints.RemoveAll(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint));
}
public void LoadPets() public void LoadPets()
{ {
if (petsElement != null) if (petsElement != null)
@@ -259,6 +296,16 @@ namespace Barotrauma
Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime)); Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
if (success)
{
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character?.HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter) ?? false)
{
(GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(c);
}
}
}
GameMain.GameSession.EndRound("", traitorResults, transitionType); GameMain.GameSession.EndRound("", traitorResults, transitionType);
@@ -965,6 +1012,15 @@ namespace Barotrauma
// save bots // save bots
CrewManager.SaveMultiplayer(modeElement); CrewManager.SaveMultiplayer(modeElement);
XElement savedExperiencePointsElement = new XElement("SavedExperiencePoints");
foreach (var savedExperiencePoint in savedExperiencePoints)
{
savedExperiencePointsElement.Add(new XElement("Point",
new XAttribute("steamid", savedExperiencePoint.SteamID),
new XAttribute("endpoint", savedExperiencePoint?.EndPoint ?? string.Empty),
new XAttribute("points", savedExperiencePoint.ExperiencePoints)));
}
// save available submarines // save available submarines
XElement availableSubsElement = new XElement("AvailableSubs"); XElement availableSubsElement = new XElement("AvailableSubs");
for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++)
@@ -0,0 +1,20 @@
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
partial class GeneticMaterial : ItemComponent
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(tainted);
if (tainted)
{
msg.Write(selectedTaintedEffect?.UIntIdentifier ?? 0);
}
else
{
msg.Write(selectedEffect?.UIntIdentifier ?? 0);
}
}
}
}
@@ -19,13 +19,13 @@ namespace Barotrauma.Items.Components
GameServer.Log(GameServer.CharacterLogName(c.Character) + " entered \"" + newOutputValue + "\" on " + item.Name, GameServer.Log(GameServer.CharacterLogName(c.Character) + " entered \"" + newOutputValue + "\" on " + item.Name,
ServerLog.MessageType.ItemInteraction); ServerLog.MessageType.ItemInteraction);
OutputValue = newOutputValue; OutputValue = newOutputValue;
ShowOnDisplay(newOutputValue); ShowOnDisplay(newOutputValue, addToHistory: true);
item.SendSignal(newOutputValue, "signal_out"); item.SendSignal(newOutputValue, "signal_out");
item.CreateServerEvent(this); item.CreateServerEvent(this);
} }
} }
partial void ShowOnDisplay(string input, bool addToHistory = true) partial void ShowOnDisplay(string input, bool addToHistory)
{ {
if (addToHistory) if (addToHistory)
{ {
@@ -2358,6 +2358,12 @@ namespace Barotrauma.Networking
characterData.HasSpawned = true; characterData.HasSpawned = true;
} }
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && spawnedCharacter.Info != null)
{
spawnedCharacter.Info.SetExperience(Math.Max(spawnedCharacter.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(teamClients[i])));
mpCampaign.ClearSavedExperiencePoints(teamClients[i]);
}
spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString; spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString;
spawnedCharacter.OwnerClientName = teamClients[i].Name; spawnedCharacter.OwnerClientName = teamClients[i].Name;
} }
@@ -379,6 +379,12 @@ namespace Barotrauma.Networking
} }
else else
{ {
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && character.Info != null)
{
character.Info.SetExperience(Math.Max(character.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(clients[i])));
mpCampaign.ClearSavedExperiencePoints(clients[i]);
}
//tell the respawning client they're no longer a traitor //tell the respawning client they're no longer a traitor
if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null) if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null)
{ {
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.0.0</Version> <Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -23,6 +23,7 @@
<Item file="Content/Items/Electricity/signalitems.xml" /> <Item file="Content/Items/Electricity/signalitems.xml" />
<Item file="Content/Items/Engine/engine.xml" /> <Item file="Content/Items/Engine/engine.xml" />
<Item file="Content/Items/Fabricators/fabricators.xml" /> <Item file="Content/Items/Fabricators/fabricators.xml" />
<Item file="Content/Items/Genetic/genetic.xml" />
<Item file="Content/Items/Labels/labels.xml" /> <Item file="Content/Items/Labels/labels.xml" />
<Item file="Content/Items/Ladder/ladder.xml" /> <Item file="Content/Items/Ladder/ladder.xml" />
<Item file="Content/Items/Materials/materials.xml" /> <Item file="Content/Items/Materials/materials.xml" />
@@ -63,6 +64,7 @@
<Item file="Content/Items/Gardening/gardeningtools.xml" /> <Item file="Content/Items/Gardening/gardeningtools.xml" />
<Item file="Content/Items/Gardening/plantproducts.xml" /> <Item file="Content/Items/Gardening/plantproducts.xml" />
<Item file="Content/Map/Pirates/PirateItems.xml" /> <Item file="Content/Map/Pirates/PirateItems.xml" />
<Item file="Content/Items/Jobgear/Assistant/assistant_talent_items.xml" />
<Item file="Content/Items/Jobgear/Mechanic/mechanic_talent_items.xml" /> <Item file="Content/Items/Jobgear/Mechanic/mechanic_talent_items.xml" />
<Item file="Content/Items/Jobgear/Security/securityofficer_talent_items.xml" /> <Item file="Content/Items/Jobgear/Security/securityofficer_talent_items.xml" />
<Item file="Content/Items/Jobgear/Engineer/engineer_talent_items.xml" /> <Item file="Content/Items/Jobgear/Engineer/engineer_talent_items.xml" />
@@ -152,6 +154,7 @@
<Text file="Content/Texts/Korean/KoreanVanilla.xml" /> <Text file="Content/Texts/Korean/KoreanVanilla.xml" />
<UIStyle file="Content/UI/style.xml" /> <UIStyle file="Content/UI/style.xml" />
<Afflictions file="Content/Afflictions.xml" /> <Afflictions file="Content/Afflictions.xml" />
<Afflictions file="Content/AfflictionsGeneticMaterial.xml" />
<Afflictions file="Content/Talents/Assistant/AfflictionsAssistant.xml" /> <Afflictions file="Content/Talents/Assistant/AfflictionsAssistant.xml" />
<Afflictions file="Content/Talents/Captain/AfflictionsCaptain.xml" /> <Afflictions file="Content/Talents/Captain/AfflictionsCaptain.xml" />
<Afflictions file="Content/Talents/Doctor/AfflictionsDoctor.xml" /> <Afflictions file="Content/Talents/Doctor/AfflictionsDoctor.xml" />
@@ -1915,12 +1915,12 @@ namespace Barotrauma
#if SERVER #if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{ {
Networking.NetEntityEvent.Type.SetAttackTarget, Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb, attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID, (damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0, damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X, SimPosition.X,
SimPosition.Y SimPosition.Y
}); });
#else #else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3); Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
@@ -588,8 +588,9 @@ namespace Barotrauma
// assume that it's required for the stun effect // assume that it's required for the stun effect
// as we can't check the status effect conditions here. // as we can't check the status effect conditions here.
var mobileBatteryTag = "mobilebattery"; var mobileBatteryTag = "mobilebattery";
var containers = weapon.Item.Components.Where(ic => ic is ItemContainer container && var containers = weapon.Item.Components.Where(ic =>
container.ContainableItems.Any(containable => containable.Identifiers.Any(id => id.Equals(mobileBatteryTag)))); ic is ItemContainer container &&
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
// If there's no such container, assume that the melee weapon can stun without a battery. // If there's no such container, assume that the melee weapon can stun without a battery.
return containers.None() || containers.Any(container => return containers.None() || containers.Any(container =>
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false); (container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
@@ -148,7 +148,7 @@ namespace Barotrauma
public virtual void UpdateAnim(float deltaTime) { } public virtual void UpdateAnim(float deltaTime) { }
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f) { } public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false) { }
public virtual void DragCharacter(Character target, float deltaTime) { } public virtual void DragCharacter(Character target, float deltaTime) { }
@@ -390,11 +390,17 @@ namespace Barotrauma
} }
if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
{ {
bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered; static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered;
//keep severing joints until there is only one limb left //keep severing joints until there is only one limb left
var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered); var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
if (nonSeveredJoints.None()) if (nonSeveredJoints.None())
{ {
//small monsters don't eat the contents of the character's inventory
if (Mass < target.AnimController.Mass)
{
target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null));
}
//only one limb left, the character is now full eaten //only one limb left, the character is now full eaten
Entity.Spawner?.AddToRemoveQueue(target); Entity.Spawner?.AddToRemoveQueue(target);
@@ -150,6 +150,13 @@ namespace Barotrauma
private float upperLegLength = 0.0f, lowerLegLength = 0.0f; private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
private bool aiming; private bool aiming;
private bool wasAiming;
private bool aimingMelee;
private bool wasAimingMelee;
public bool IsAiming => wasAiming;
public bool IsAimingMelee => wasAimingMelee;
private readonly float movementLerp; private readonly float movementLerp;
@@ -532,7 +539,10 @@ namespace Barotrauma
limb.Disabled = false; limb.Disabled = false;
} }
wasAiming = aiming;
aiming = false; aiming = false;
wasAimingMelee = aimingMelee;
aimingMelee = false;
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return; if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return;
} }
@@ -1718,7 +1728,7 @@ namespace Barotrauma
} }
//TODO: refactor this method, it's way too convoluted //TODO: refactor this method, it's way too convoluted
public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f) public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false)
{ {
if (character.Stun > 0.0f || character.IsIncapacitated) if (character.Stun > 0.0f || character.IsIncapacitated)
{ {
@@ -1748,6 +1758,8 @@ namespace Barotrauma
Holdable holdable = item.GetComponent<Holdable>(); Holdable holdable = item.GetComponent<Holdable>();
this.aimingMelee = aimingMelee;
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated) if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated)
{ {
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition); Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
@@ -1771,7 +1783,6 @@ namespace Barotrauma
aiming = true; aiming = true;
} }
} }
else else
{ {
@@ -395,12 +395,18 @@ namespace Barotrauma
if (character.IsHusk && character.Params.UseHuskAppendage) if (character.IsHusk && character.Params.UseHuskAppendage)
{ {
bool inEditor = false;
#if CLIENT
inEditor = Screen.Selected == GameMain.CharacterEditorScreen;
#endif
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath); var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
if (characterPrefab?.XDocument != null) if (characterPrefab?.XDocument != null)
{ {
var mainElement = characterPrefab.XDocument.Root.IsOverride() ? characterPrefab.XDocument.Root.FirstElement() : characterPrefab.XDocument.Root; var mainElement = characterPrefab.XDocument.Root.IsOverride() ? characterPrefab.XDocument.Root.FirstElement() : characterPrefab.XDocument.Root;
foreach (var huskAppendage in mainElement.GetChildElements("huskappendage")) foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
{ {
if (!inEditor && huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; }
AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeString("affliction", string.Empty), huskAppendage, ragdoll: this); AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeString("affliction", string.Empty), huskAppendage, ragdoll: this);
} }
} }
@@ -8,7 +8,8 @@ namespace Barotrauma
public enum HitDetection public enum HitDetection
{ {
Distance, Distance,
Contact Contact,
None
} }
public enum AttackContext public enum AttackContext
@@ -74,20 +75,6 @@ namespace Barotrauma
} }
} }
class AttackData
{
public float DamageMultiplier { get; set; } = 1f;
public float AddedPenetration { get; set; } = 0f;
public List<Affliction> Afflictions { get; set; }
public Attack SourceAttack { get; }
public AttackData(Attack sourceAttack)
{
SourceAttack = sourceAttack;
}
}
partial class Attack : ISerializableEntity partial class Attack : ISerializableEntity
{ {
[Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable] [Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
@@ -466,7 +453,7 @@ namespace Barotrauma
DamageParticles(deltaTime, worldPosition); DamageParticles(deltaTime, worldPosition);
var attackResult = target.AddDamage(attacker, worldPosition, this, deltaTime, playSound); var attackResult = target?.AddDamage(attacker, worldPosition, this, deltaTime, playSound) ?? new AttackResult();
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure; var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
if (targetCharacter != null && targetCharacter.IsDead) if (targetCharacter != null && targetCharacter.IsDead)
{ {
@@ -9,6 +9,7 @@ using System.Xml.Linq;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Abilities;
#if SERVER #if SERVER
using System.Text; using System.Text;
#endif #endif
@@ -1366,7 +1367,6 @@ namespace Barotrauma
} }
} }
} }
private List<Item> wearableItems = new List<Item>();
public float GetSkillLevel(string skillIdentifier) public float GetSkillLevel(string skillIdentifier)
{ {
@@ -2075,11 +2075,10 @@ namespace Barotrauma
return SelectedCharacter == owner && owner.CanInventoryBeAccessed; return SelectedCharacter == owner && owner.CanInventoryBeAccessed;
} }
if (inventory.Owner is Item) if (inventory.Owner is Item item)
{ {
var owner = (Item)inventory.Owner; if (!CanInteractWith(item) && !item.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; }
if (!CanInteractWith(owner) && !owner.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; } ItemContainer container = item.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
ItemContainer container = owner.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
if (container != null && !container.HasRequiredItems(this, addMessage: false)) { return false; } if (container != null && !container.HasRequiredItems(this, addMessage: false)) { return false; }
} }
return true; return true;
@@ -2218,6 +2217,12 @@ namespace Barotrauma
} }
} }
if (SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem == item ||
HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == item))
{
return true;
}
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; } if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; }
Pickable pickableComponent = item.GetComponent<Pickable>(); Pickable pickableComponent = item.GetComponent<Pickable>();
@@ -3355,10 +3360,18 @@ namespace Barotrauma
float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime; float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime;
AttackData attackData = new AttackData(attack); AbilityAttackData attackData = new AbilityAttackData(attack, this);
attacker.CheckTalents(AbilityEffectType.OnAttack, attackData); if (attacker != null)
CheckTalents(AbilityEffectType.OnAttacked, attackData); {
attackData.DamageMultiplier *= (1 + attacker.GetStatValue(StatTypes.AttackMultiplier)); attackData.Attacker = attacker;
attacker.CheckTalents(AbilityEffectType.OnAttack, attackData);
CheckTalents(AbilityEffectType.OnAttacked, attackData);
attackData.DamageMultiplier *= 1 + attacker.GetStatValue(StatTypes.AttackMultiplier);
if (attacker.TeamID == TeamID)
{
attackData.DamageMultiplier *= 1 + attacker.GetStatValue(StatTypes.TeamAttackMultiplier);
}
}
IEnumerable<Affliction> attackAfflictions; IEnumerable<Affliction> attackAfflictions;
@@ -3495,6 +3508,7 @@ namespace Barotrauma
{ {
attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target); attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target);
} }
CheckTalents(AbilityEffectType.OnKillCharacter, target);
if (!IsOnPlayerTeam) { return; } if (!IsOnPlayerTeam) { return; }
if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; } if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; }
@@ -3653,10 +3667,7 @@ namespace Barotrauma
if (statusEffect.type != actionType) { continue; } if (statusEffect.type != actionType) { continue; }
if (statusEffect.type == ActionType.OnDamaged) if (statusEffect.type == ActionType.OnDamaged)
{ {
if (statusEffect.AllowedAfflictions != null && (LastDamage.Afflictions == null || LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier)))) if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
{
continue;
}
if (statusEffect.OnlyPlayerTriggered) if (statusEffect.OnlyPlayerTriggered)
{ {
if (LastAttacker == null || !LastAttacker.IsPlayer) if (LastAttacker == null || !LastAttacker.IsPlayer)
@@ -3719,7 +3730,7 @@ namespace Barotrauma
} }
} }
private void Implode(bool isNetworkMessage = false) public void Implode(bool isNetworkMessage = false)
{ {
if (CharacterHealth.Unkillable || GodMode || IsDead) { return; } if (CharacterHealth.Unkillable || GodMode || IsDead) { return; }
@@ -3829,7 +3840,7 @@ namespace Barotrauma
if (info != null) if (info != null)
{ {
info.CauseOfDeath = CauseOfDeath; info.CauseOfDeath = CauseOfDeath;
info.ResetSavedStatValues(); info.MissionsCompletedSinceDeath = 0;
} }
AnimController.movement = Vector2.Zero; AnimController.movement = Vector2.Zero;
AnimController.TargetMovement = Vector2.Zero; AnimController.TargetMovement = Vector2.Zero;
@@ -4434,7 +4445,7 @@ namespace Barotrauma
} }
} }
private StatTypes GetSkillStatType(string skillIdentifier) public static StatTypes GetSkillStatType(string skillIdentifier)
{ {
// Using this method to translate between skill identifiers and stat types. Feel free to replace it if there's a better way // Using this method to translate between skill identifiers and stat types. Feel free to replace it if there's a better way
switch (skillIdentifier) switch (skillIdentifier)
@@ -462,6 +462,9 @@ namespace Barotrauma
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1; public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
// talent-relevant values
public int MissionsCompletedSinceDeath = 0;
// Used for creating the data // Used for creating the data
public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced, string npcIdentifier = "") public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced, string npcIdentifier = "")
{ {
@@ -605,7 +608,10 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(personalityName)) if (!string.IsNullOrEmpty(personalityName))
{ {
personalityTrait = NPCPersonalityTrait.List.Find(p => p.Name == personalityName); personalityTrait = NPCPersonalityTrait.List.Find(p => p.Name == personalityName);
} }
MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0);
foreach (XElement subElement in infoElement.Elements()) foreach (XElement subElement in infoElement.Elements())
{ {
bool jobCreated = false; bool jobCreated = false;
@@ -973,16 +979,17 @@ namespace Barotrauma
} }
float prevLevel = Job.GetSkillLevel(skillIdentifier); float prevLevel = Job.GetSkillLevel(skillIdentifier);
Job.IncreaseSkillLevel(skillIdentifier, increase); Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum));
float newLevel = Job.GetSkillLevel(skillIdentifier); float newLevel = Job.GetSkillLevel(skillIdentifier);
if ((int)newLevel > (int)prevLevel) if ((int)newLevel > (int)prevLevel)
{ {
Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier); Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier);
foreach (Character character in Character.GetFriendlyCrew(Character)) foreach (Character character in Character.GetFriendlyCrew(Character))
{ {
character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, (skillIdentifier, Character)); var abilityStringCharacter = new AbilityStringCharacter(skillIdentifier, Character);
character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, abilityStringCharacter);
} }
} }
@@ -1139,9 +1146,10 @@ namespace Barotrauma
new XAttribute("startitemsgiven", StartItemsGiven), new XAttribute("startitemsgiven", StartItemsGiven),
new XAttribute("ragdoll", ragdollFileName), new XAttribute("ragdoll", ragdollFileName),
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name)); new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
// TODO: animations? // TODO: animations?
charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath));
if (Character != null) if (Character != null)
{ {
if (Character.AnimController.CurrentHull != null) if (Character.AnimController.CurrentHull != null)
@@ -1158,6 +1166,7 @@ namespace Barotrauma
foreach (var savedStat in statValuePair.Value) foreach (var savedStat in statValuePair.Value)
{ {
if (savedStat.StatValue == 0f) { continue; } if (savedStat.StatValue == 0f) { continue; }
if (savedStat.RemoveAfterRound) { continue; }
savedStatElement.Add(new XElement("savedstatvalue", savedStatElement.Add(new XElement("savedstatvalue",
new XAttribute("stattype", statValuePair.Key.ToString()), new XAttribute("stattype", statValuePair.Key.ToString()),
@@ -1168,6 +1177,8 @@ namespace Barotrauma
} }
} }
charElement.Add(savedStatElement); charElement.Add(savedStatElement);
parentElement.Add(charElement); parentElement.Add(charElement);
@@ -1496,7 +1507,6 @@ namespace Barotrauma
} }
} }
} }
public void ResetSavedStatValue(string statIdentifier) public void ResetSavedStatValue(string statIdentifier)
{ {
savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f); savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f);
@@ -1514,7 +1524,7 @@ namespace Barotrauma
} }
} }
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath) public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue)
{ {
if (!savedStatValues.ContainsKey(statType)) if (!savedStatValues.ContainsKey(statType))
{ {
@@ -1523,12 +1533,11 @@ namespace Barotrauma
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{ {
savedStat.StatValue += value; savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue);
savedStat.RemoveOnDeath = removeOnDeath;
} }
else else
{ {
savedStatValues[statType].Add(new SavedStatValue(statIdentifier, value, removeOnDeath)); savedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound));
} }
} }
} }
@@ -1538,12 +1547,14 @@ namespace Barotrauma
public string StatIdentifier { get; set; } public string StatIdentifier { get; set; }
public float StatValue { get; set; } public float StatValue { get; set; }
public bool RemoveOnDeath { get; set; } public bool RemoveOnDeath { get; set; }
public bool RemoveAfterRound { get; set; }
public SavedStatValue(string statIdentifier, float value, bool removeOnDeath) public SavedStatValue(string statIdentifier, float value, bool removeOnDeath, bool retainAfterRound)
{ {
StatValue = value; StatValue = value;
RemoveOnDeath = removeOnDeath; RemoveOnDeath = removeOnDeath;
StatIdentifier = statIdentifier; StatIdentifier = statIdentifier;
RemoveAfterRound = retainAfterRound;
} }
} }
} }
@@ -17,6 +17,8 @@ namespace Barotrauma
public float PendingAdditionStrength { get; set; } public float PendingAdditionStrength { get; set; }
public float AdditionStrength { get; set; } public float AdditionStrength { get; set; }
private float fluctuationTimer;
protected float _strength; protected float _strength;
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
@@ -56,6 +58,8 @@ namespace Barotrauma
public readonly Dictionary<AfflictionPrefab.PeriodicEffect, float> PeriodicEffectTimers = new Dictionary<AfflictionPrefab.PeriodicEffect, float>(); public readonly Dictionary<AfflictionPrefab.PeriodicEffect, float> PeriodicEffectTimers = new Dictionary<AfflictionPrefab.PeriodicEffect, float>();
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
/// <summary> /// <summary>
/// Which character gave this affliction /// Which character gave this affliction
/// </summary> /// </summary>
@@ -123,7 +127,7 @@ namespace Barotrauma
float amount = MathHelper.Lerp( float amount = MathHelper.Lerp(
currentEffect.MinGrainStrength, currentEffect.MinGrainStrength,
currentEffect.MaxGrainStrength, currentEffect.MaxGrainStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
if (Prefab.GrainBurst > 0 && AdditionStrength > amount) if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
{ {
@@ -138,12 +142,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; } if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; } if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; } if (currentEffect.MaxScreenDistort - currentEffect.MinScreenDistort < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinScreenDistortStrength, currentEffect.MinScreenDistort,
currentEffect.MaxScreenDistortStrength, currentEffect.MaxScreenDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
} }
public float GetRadialDistortStrength() public float GetRadialDistortStrength()
@@ -151,12 +155,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; } if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; } if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; } if (currentEffect.MaxRadialDistort - currentEffect.MinRadialDistort < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinRadialDistortStrength, currentEffect.MinRadialDistort,
currentEffect.MaxRadialDistortStrength, currentEffect.MaxRadialDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
} }
public float GetChromaticAberrationStrength() public float GetChromaticAberrationStrength()
@@ -164,12 +168,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; } if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; } if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; } if (currentEffect.MaxChromaticAberration - currentEffect.MinChromaticAberration < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinChromaticAberrationStrength, currentEffect.MinChromaticAberration,
currentEffect.MaxChromaticAberrationStrength, currentEffect.MaxChromaticAberration,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
} }
public float GetScreenBlurStrength() public float GetScreenBlurStrength()
@@ -177,12 +181,18 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; } if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; } if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; } if (currentEffect.MaxScreenBlur - currentEffect.MinScreenBlur < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinScreenBlurStrength, currentEffect.MinScreenBlur,
currentEffect.MaxScreenBlurStrength, currentEffect.MaxScreenBlur,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
private float GetScreenEffectFluctuation(AfflictionPrefab.Effect currentEffect)
{
if (currentEffect == null || currentEffect.ScreenEffectFluctuationFrequency <= 0.0f) { return 1.0f; }
return ((float)Math.Sin(fluctuationTimer * MathHelper.TwoPi) + 1.0f) * 0.5f;
} }
public float GetSkillMultiplier() public float GetSkillMultiplier()
@@ -210,14 +220,17 @@ namespace Barotrauma
} }
} }
public float GetResistance(string afflictionId) public float GetResistance(AfflictionPrefab affliction)
{ {
if (Strength < Prefab.ActivationThreshold) { return 0.0f; } if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; } if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxResistance - currentEffect.MinResistance <= 0.0f) { return 0.0f; } if (!currentEffect.ResistanceFor.Any(r =>
if (afflictionId != null && afflictionId != currentEffect.ResistanceFor) { return 0.0f; } r.Equals(affliction.Identifier, StringComparison.OrdinalIgnoreCase) ||
r.Equals(affliction.AfflictionType, StringComparison.OrdinalIgnoreCase)))
{
return 0.0f;
}
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinResistance, currentEffect.MinResistance,
currentEffect.MaxResistance, currentEffect.MaxResistance,
@@ -229,8 +242,6 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 1.0f; } if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 1.0f; } if (currentEffect == null) { return 1.0f; }
if (currentEffect.MaxSpeedMultiplier - currentEffect.MinSpeedMultiplier <= 0.0f) { return 1.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinSpeedMultiplier, currentEffect.MinSpeedMultiplier,
currentEffect.MaxSpeedMultiplier, currentEffect.MaxSpeedMultiplier,
@@ -282,6 +293,9 @@ namespace Barotrauma
AfflictionPrefab.Effect currentEffect = GetActiveEffect(); AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return; } if (currentEffect == null) { return; }
fluctuationTimer += deltaTime * currentEffect.ScreenEffectFluctuationFrequency;
fluctuationTimer %= 1.0f;
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
{ {
float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier) float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier)
@@ -290,9 +304,9 @@ namespace Barotrauma
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier * durationMultiplier; _strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier * durationMultiplier;
} }
else // Reduce strengthening of afflictions if resistant else if (currentEffect.StrengthChange > 0) // Reduce strengthening of afflictions if resistant
{ {
_strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier)); _strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab));
} }
// Don't use the property, because it's virtual and some afflictions like husk overload it for external use. // Don't use the property, because it's virtual and some afflictions like husk overload it for external use.
_strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength); _strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength);
@@ -34,6 +34,10 @@ namespace Barotrauma
float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1; float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
float max = Math.Max(threshold, previousValue); float max = Math.Max(threshold, previousValue);
_strength = Math.Clamp(value, 0, max); _strength = Math.Clamp(value, 0, max);
if (previousValue > 0.0f && value <= 0.0f)
{
DeactivateHusk();
}
} }
} }
@@ -51,8 +55,10 @@ namespace Barotrauma
} }
} }
private float DormantThreshold => Prefab.MaxStrength * 0.5f; private float DormantThreshold => (Prefab as AfflictionPrefabHusk)?.DormantThreshold ?? Prefab.MaxStrength * 0.5f;
private float ActiveThreshold => Prefab.MaxStrength * 0.75f; private float ActiveThreshold => (Prefab as AfflictionPrefabHusk)?.ActiveThreshold ?? Prefab.MaxStrength * 0.75f;
private float TransitionThreshold => (Prefab as AfflictionPrefabHusk)?.TransitionThreshold ?? Prefab.MaxStrength * 0.75f;
public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { } public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { }
@@ -83,7 +89,7 @@ namespace Barotrauma
} }
State = InfectionState.Transition; State = InfectionState.Transition;
} }
else if (Strength < Prefab.MaxStrength) else if (Strength < TransitionThreshold)
{ {
if (State != InfectionState.Active) if (State != InfectionState.Active)
{ {
@@ -97,6 +97,10 @@ namespace Barotrauma
CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true); CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true);
NeedsAir = element.GetAttributeBool("needsair", false); NeedsAir = element.GetAttributeBool("needsair", false);
ControlHusk = element.GetAttributeBool("controlhusk", false); ControlHusk = element.GetAttributeBool("controlhusk", false);
DormantThreshold = element.GetAttributeFloat("dormantthreshold", MaxStrength * 0.5f);
ActiveThreshold = element.GetAttributeFloat("activethreshold", MaxStrength * 0.75f);
TransitionThreshold = element.GetAttributeFloat("transitionthreshold", MaxStrength);
} }
// Use any of these to define which limb the appendage is attached to. // Use any of these to define which limb the appendage is attached to.
@@ -105,6 +109,8 @@ namespace Barotrauma
public readonly string AttachLimbName; public readonly string AttachLimbName;
public readonly LimbType AttachLimbType; public readonly LimbType AttachLimbType;
public float ActiveThreshold, DormantThreshold, TransitionThreshold;
public readonly string HuskedSpeciesName; public readonly string HuskedSpeciesName;
public readonly string[] TargetSpecies; public readonly string[] TargetSpecies;
public const string Tag = "[speciesname]"; public const string Tag = "[speciesname]";
@@ -141,28 +147,31 @@ namespace Barotrauma
public bool MultiplyByMaxVitality { get; private set; } public bool MultiplyByMaxVitality { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MinScreenBlurStrength { get; private set; } public float MinScreenBlur { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MaxScreenBlurStrength { get; private set; } public float MaxScreenBlur { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MinScreenDistortStrength { get; private set; } public float MinScreenDistort { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MaxScreenDistortStrength { get; private set; } public float MaxScreenDistort { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MinRadialDistortStrength { get; private set; } public float MinRadialDistort { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MaxRadialDistortStrength { get; private set; } public float MaxRadialDistort { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MinChromaticAberrationStrength { get; private set; } public float MinChromaticAberration { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MaxChromaticAberrationStrength { get; private set; } public float MaxChromaticAberration { get; private set; }
[Serialize("255,255,255,255", false)]
public Color GrainColor { get; private set; }
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MinGrainStrength { get; private set; } public float MinGrainStrength { get; private set; }
@@ -170,6 +179,9 @@ namespace Barotrauma
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MaxGrainStrength { get; private set; } public float MaxGrainStrength { get; private set; }
[Serialize(0.0f, false)]
public float ScreenEffectFluctuationFrequency { get; private set; }
[Serialize(1.0f, false)] [Serialize(1.0f, false)]
public float MinBuffMultiplier { get; private set; } public float MinBuffMultiplier { get; private set; }
@@ -188,8 +200,11 @@ namespace Barotrauma
[Serialize(1.0f, false)] [Serialize(1.0f, false)]
public float MaxSkillMultiplier { get; private set; } public float MaxSkillMultiplier { get; private set; }
[Serialize("", false)] private readonly string[] resistanceFor;
public string ResistanceFor { get; private set; } public IEnumerable<string> ResistanceFor
{
get { return resistanceFor; }
}
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float MinResistance { get; private set; } public float MinResistance { get; private set; }
@@ -209,6 +224,8 @@ namespace Barotrauma
{ {
SerializableProperty.DeserializeProperties(this, element); SerializableProperty.DeserializeProperties(this, element);
resistanceFor = element.GetAttributeStringArray("resistancefor", new string[0], convertToLowerInvariant: true);
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -266,6 +266,12 @@ namespace Barotrauma
private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex]; private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex];
private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false)); private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false));
/// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary>
private IEnumerable<Affliction> GetMatchingAfflictions(LimbHealth limb)
=> limb.Afflictions.Union(afflictions.Where(a => GetMatchingLimbHealth(a) == limb));
/// <summary> /// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary> /// </summary>
@@ -426,18 +432,14 @@ namespace Barotrauma
} }
} }
public float GetResistance(string resistanceId) public float GetResistance(AfflictionPrefab affliction)
{ {
float resistance = 0.0f; float resistance = 0.0f;
for (int i = 0; i < afflictions.Count; i++) for (int i = 0; i < afflictions.Count; i++)
{ {
if (!afflictions[i].Prefab.IsBuff) continue; resistance += afflictions[i].GetResistance(affliction);
float temp = afflictions[i].GetResistance(resistanceId);
if (temp > resistance) resistance = temp;
} }
resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(resistanceId)); return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction.Identifier));
return resistance;
} }
public float GetStatValue(StatTypes statType) public float GetStatValue(StatTypes statType)
@@ -451,7 +453,7 @@ namespace Barotrauma
} }
private readonly List<Affliction> matchingAfflictions = new List<Affliction>(); private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
public void ReduceAffliction(Limb targetLimb, string affliction, float amount) public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null)
{ {
matchingAfflictions.Clear(); matchingAfflictions.Clear();
matchingAfflictions.AddRange(afflictions); matchingAfflictions.AddRange(afflictions);
@@ -499,6 +501,17 @@ namespace Barotrauma
{ {
matchingAffliction.Strength -= reduceAmount; matchingAffliction.Strength -= reduceAmount;
amount -= reduceAmount; amount -= reduceAmount;
if (treatmentAction != null)
{
if (treatmentAction.Value == ActionType.OnUse)
{
matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
}
else if (treatmentAction.Value == ActionType.OnFailure)
{
matchingAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
}
}
} }
} }
CalculateVitality(); CalculateVitality();
@@ -610,7 +623,7 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab == affliction.Prefab) if (newAffliction.Prefab == affliction.Prefab)
{ {
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)); float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab));
if (allowStacking) if (allowStacking)
{ {
// Add the existing strength // Add the existing strength
@@ -632,7 +645,7 @@ namespace Barotrauma
//create a new instance of the affliction to make sure we don't use the same instance for multiple characters //create a new instance of the affliction to make sure we don't use the same instance for multiple characters
//or modify the affliction instance of an Attack or a StatusEffect //or modify the affliction instance of an Attack or a StatusEffect
var copyAffliction = newAffliction.Prefab.Instantiate( var copyAffliction = newAffliction.Prefab.Instantiate(
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))), Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
newAffliction.Source); newAffliction.Source);
limbHealth.Afflictions.Add(copyAffliction); limbHealth.Afflictions.Add(copyAffliction);
@@ -666,7 +679,7 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab == affliction.Prefab) if (newAffliction.Prefab == affliction.Prefab)
{ {
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)); float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab));
if (allowStacking) if (allowStacking)
{ {
// Add the existing strength // Add the existing strength
@@ -688,7 +701,7 @@ namespace Barotrauma
//create a new instance of the affliction to make sure we don't use the same instance for multiple characters //create a new instance of the affliction to make sure we don't use the same instance for multiple characters
//or modify the affliction instance of an Attack or a StatusEffect //or modify the affliction instance of an Attack or a StatusEffect
afflictions.Add(newAffliction.Prefab.Instantiate( afflictions.Add(newAffliction.Prefab.Instantiate(
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))), Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
source: newAffliction.Source)); source: newAffliction.Source));
Character.HealthUpdateInterval = 0.0f; Character.HealthUpdateInterval = 0.0f;
@@ -700,8 +713,6 @@ namespace Barotrauma
} }
} }
partial void UpdateProjSpecific(float deltaTime);
partial void UpdateLimbAfflictionOverlays(); partial void UpdateLimbAfflictionOverlays();
public void Update(float deltaTime) public void Update(float deltaTime)
@@ -741,7 +752,7 @@ namespace Barotrauma
for (int i = afflictions.Count - 1; i >= 0; i--) for (int i = afflictions.Count - 1; i >= 0; i--)
{ {
var affliction = afflictions[i]; var affliction = afflictions[i];
if (irremovableAfflictions.Contains(affliction)) continue; if (irremovableAfflictions.Contains(affliction)) { continue; }
if (affliction.Strength <= 0.0f) if (affliction.Strength <= 0.0f)
{ {
SteamAchievementManager.OnAfflictionRemoved(affliction, Character); SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
@@ -763,6 +774,10 @@ namespace Barotrauma
{ {
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed)); Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed));
} }
else
{
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed));
}
UpdateLimbAfflictionOverlays(); UpdateLimbAfflictionOverlays();
@@ -786,7 +801,12 @@ namespace Barotrauma
} }
else else
{ {
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? -5.0f : 10.0f), -100.0f, 100.0f); float decreaseSpeed = -5.0f;
float increaseSpeed = 10.0f;
float oxygenlowResistance = GetResistance(oxygenLowAffliction.Prefab);
decreaseSpeed *= (1f - oxygenlowResistance);
increaseSpeed *= (1f + oxygenlowResistance);
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f);
} }
UpdateOxygenProjSpecific(prevOxygen, deltaTime); UpdateOxygenProjSpecific(prevOxygen, deltaTime);
@@ -807,8 +827,6 @@ namespace Barotrauma
Vitality = MaxVitality; Vitality = MaxVitality;
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
float damageResistanceMultiplier = 1f - GetResistance("damage");
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
foreach (Affliction affliction in limbHealth.Afflictions) foreach (Affliction affliction in limbHealth.Afflictions)
@@ -824,7 +842,6 @@ namespace Barotrauma
{ {
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type]; vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type];
} }
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease; Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease); affliction.CalculateDamagePerSecond(vitalityDecrease);
} }
@@ -833,7 +850,6 @@ namespace Barotrauma
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
{ {
float vitalityDecrease = affliction.GetVitalityDecrease(this); float vitalityDecrease = affliction.GetVitalityDecrease(this);
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease; Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease); affliction.CalculateDamagePerSecond(vitalityDecrease);
} }
@@ -951,13 +967,13 @@ namespace Barotrauma
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param> /// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param> /// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param> /// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f) public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, float randomization = 0.0f)
{ {
//key = item identifier //key = item identifier
//float = suitability //float = suitability
treatmentSuitability.Clear(); treatmentSuitability.Clear();
float minSuitability = -10, maxSuitability = 10; float minSuitability = -10, maxSuitability = 10;
foreach (Affliction affliction in GetAllAfflictions()) foreach (Affliction affliction in getAfflictions(limb))
{ {
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability) foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
@@ -990,6 +1006,18 @@ namespace Barotrauma
treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization; treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization;
} }
} }
IEnumerable<Affliction> getAfflictions(Limb limb)
{
if (limb == null)
{
return GetAllAfflictions();
}
else
{
return GetMatchingAfflictions(GetMatchingLimbHealth(limb));
}
}
} }
private readonly List<Affliction> activeAfflictions = new List<Affliction>(); private readonly List<Affliction> activeAfflictions = new List<Affliction>();
@@ -89,11 +89,11 @@ namespace Barotrauma
return (skill == null) ? 0.0f : skill.Level; return (skill == null) ? 0.0f : skill.Level;
} }
public void IncreaseSkillLevel(string skillIdentifier, float increase) public void IncreaseSkillLevel(string skillIdentifier, float increase, bool increasePastMax)
{ {
if (skills.TryGetValue(skillIdentifier, out Skill skill)) if (skills.TryGetValue(skillIdentifier, out Skill skill))
{ {
skill.Level += increase; skill.IncreaseSkill(increase, increasePastMax);
} }
else else
{ {
@@ -7,11 +7,18 @@ namespace Barotrauma
private float level; private float level;
public string Identifier { get; } public string Identifier { get; }
public const float MaximumSkill = 100.0f;
public float Level public float Level
{ {
get { return level; } get { return level; }
set { level = MathHelper.Clamp(value, 0.0f, 100.0f); } set { level = value; }
}
public void IncreaseSkill(float value, bool increasePastMax)
{
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? float.MaxValue : MaximumSkill);
} }
private Sprite icon; private Sprite icon;
@@ -868,7 +868,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null) public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
{ {
attackResult = default(AttackResult); attackResult = default;
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition; Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos)); float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
bool wasRunning = attack.IsRunning; bool wasRunning = attack.IsRunning;
@@ -971,7 +971,7 @@ namespace Barotrauma
wasHit = damageTarget != null; wasHit = damageTarget != null;
} }
if (wasHit) if (wasHit || attack.HitDetectionType == HitDetection.None)
{ {
if (character == Character.Controlled || GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) if (character == Character.Controlled || GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{ {
@@ -1132,10 +1132,7 @@ namespace Barotrauma
if (statusEffect.type != actionType) { continue; } if (statusEffect.type != actionType) { continue; }
if (statusEffect.type == ActionType.OnDamaged) if (statusEffect.type == ActionType.OnDamaged)
{ {
if (statusEffect.AllowedAfflictions != null && (character.LastDamage.Afflictions == null || character.LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier)))) if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; }
{
continue;
}
if (statusEffect.OnlyPlayerTriggered) if (statusEffect.OnlyPlayerTriggered)
{ {
if (character.LastAttacker == null || !character.LastAttacker.IsPlayer) if (character.LastAttacker == null || !character.LastAttacker.IsPlayer)
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly string itemIdentifier; private readonly string itemIdentifier;
private readonly string[] tags; private readonly string[] tags;
private WeaponType weapontype; private readonly WeaponType weapontype;
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{ {
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", ""); itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
@@ -33,7 +33,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData) protected override bool MatchesConditionSpecific(object abilityData)
{ {
if (abilityData is AttackData attackData) if (abilityData is AbilityAttackData attackData)
{ {
Item item = attackData?.SourceAttack?.SourceItem; Item item = attackData?.SourceAttack?.SourceItem;
@@ -71,7 +71,7 @@ namespace Barotrauma.Abilities
} }
else else
{ {
LogAbilityConditionError(abilityData, typeof(AttackData)); LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false; return false;
} }
} }
@@ -30,7 +30,7 @@ namespace Barotrauma.Abilities
} }
else else
{ {
LogAbilityConditionError(abilityData, typeof(AttackData)); LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false; return false;
} }
} }
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework; using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
@@ -1,27 +0,0 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHandsomeStranger : AbilityConditionData
{
string skillIdentifier;
public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is string skillIdentifier)
{
return this.skillIdentifier == skillIdentifier;
}
else
{
LogAbilityConditionError(abilityData, typeof(string));
return false;
}
}
}
}
@@ -0,0 +1,54 @@
using Barotrauma.Items.Components;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionIsAiming : AbilityConditionDataless
{
private enum WeaponType
{
Any = 0,
Melee = 1,
Ranged = 2
};
private WeaponType weapontype;
public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
switch (conditionElement.GetAttributeString("weapontype", ""))
{
case "melee":
weapontype = WeaponType.Melee;
break;
case "ranged":
weapontype = WeaponType.Ranged;
break;
}
}
protected override bool MatchesConditionSpecific()
{
bool aimingCorrectItem = false;
if (character.AnimController is HumanoidAnimController animController)
{
foreach (Item item in character.HeldItems)
{
switch (weapontype)
{
case WeaponType.Melee:
aimingCorrectItem |= item.GetComponent<MeleeWeapon>() != null && animController.IsAimingMelee;
break;
case WeaponType.Ranged:
aimingCorrectItem |= item.GetComponent<RangedWeapon>() != null && animController.IsAiming;
break;
default:
aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee;
break;
}
}
}
return aimingCorrectItem;
}
}
}
@@ -22,10 +22,9 @@ namespace Barotrauma.Abilities
{ {
item = tempItem.Prefab; item = tempItem.Prefab;
} }
// this and other instances of this type of casting will be refactored else if (abilityData is IAbilityItemPrefab abilityItemPrefab)
else if (abilityData is (ItemPrefab itemPrefab, object _))
{ {
item = itemPrefab; item = abilityItemPrefab.ItemPrefab;
} }
if (item != null) if (item != null)
@@ -15,17 +15,17 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData) protected override bool MatchesConditionSpecific(object abilityData)
{ {
if (abilityData is (Affliction affliction, float reduceAmount)) if (abilityData is IAbilityAffliction abilityAffliction)
{ {
if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; } if (allowedTypes.Find(c => c == abilityAffliction.Affliction.Prefab.AfflictionType) == null) { return false; }
if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; } if (!string.IsNullOrEmpty(identifier) && abilityAffliction.Affliction.Prefab.Identifier != identifier) { return false; }
return true; return true;
} }
else else
{ {
LogAbilityConditionError(abilityData, typeof((Affliction, float))); LogAbilityConditionError(abilityData, typeof(IAbilityAffliction));
return false; return false;
} }
} }
@@ -0,0 +1,32 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionSkill : AbilityConditionData
{
private readonly string skillIdentifier;
public AbilityConditionSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
}
private bool MatchesConditionSpecific(string skillIdentifier)
{
return this.skillIdentifier == skillIdentifier;
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if ((abilityData as string ?? (abilityData as IAbilityString)?.String) is string skillIdentifier)
{
return MatchesConditionSpecific(skillIdentifier);
}
else
{
LogAbilityConditionError(abilityData, typeof(string));
return false;
}
}
}
}
@@ -1,11 +1,10 @@
using System.Linq; using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
class AbilityConditionAboveVitality : AbilityConditionDataless class AbilityConditionAboveVitality : AbilityConditionDataless
{ {
float vitalityPercentage; private readonly float vitalityPercentage;
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{ {
@@ -0,0 +1,26 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionCoauthor : AbilityConditionDataless
{
private readonly string jobIdentifier;
public AbilityConditionCoauthor(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
jobIdentifier = conditionElement.GetAttributeString("jobidentifier", string.Empty);
}
protected override bool MatchesConditionSpecific()
{
if (character.SelectedCharacter is Character otherCharacter)
{
if (!otherCharacter.HasJob(jobIdentifier)) { return false; }
if (!(character.SelectedBy == otherCharacter)) { return false; }
return true;
}
return false;
}
}
}
@@ -1,8 +1,4 @@
using Microsoft.Xna.Framework; using System.Xml.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
@@ -0,0 +1,23 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasPermanentStat : AbilityConditionDataless
{
private readonly StatTypes statType;
private readonly float min;
public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
statType = CharacterAbilityGroup.ParseStatType(conditionElement.GetAttributeString("stattype", ""), characterTalent.DebugIdentifier);
min = conditionElement.GetAttributeFloat("min", 0f);
}
protected override bool MatchesConditionSpecific()
{
// should consider decoupling this from stat values entirely
return character.Info.GetSavedStatValue(statType) >= min;
}
}
}
@@ -0,0 +1,31 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasStatusTag : AbilityConditionDataless
{
private readonly string tag;
public AbilityConditionHasStatusTag(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
tag = conditionElement.GetAttributeString("tag", "");
if (string.IsNullOrEmpty(tag))
{
DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.");
}
}
protected override bool MatchesConditionSpecific()
{
if (!string.IsNullOrEmpty(tag))
{
return
StatusEffect.DurationList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag)) ||
DelayedEffect.DelayList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag));
}
return false;
}
}
}
@@ -0,0 +1,15 @@

using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInFriendlySubmarine : AbilityConditionDataless
{
public AbilityConditionInFriendlySubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.Submarine?.TeamID == character.TeamID;
}
}
}
@@ -0,0 +1,15 @@

using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInHull : AbilityConditionDataless
{
public AbilityConditionInHull(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.CurrentHull != null;
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionLevelsBehindHighest : AbilityConditionDataless
{
private readonly int levelsBehind;
public AbilityConditionLevelsBehindHighest(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
levelsBehind = conditionElement.GetAttributeInt("levelsbehind", 0);
}
protected override bool MatchesConditionSpecific()
{
return Character.GetFriendlyCrew(character).Where(c => c.Info != null && (c.Info.GetCurrentLevel() - character.Info.GetCurrentLevel() >= levelsBehind)).Any();
}
}
}
@@ -24,13 +24,13 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData) protected override bool MatchesConditionSpecific(object abilityData)
{ {
if (abilityData is (Mission mission, AbilityValue missionAbilityValue)) if (abilityData is IAbilityMission abilityMission)
{ {
return mission.Prefab.Type == missionType; return abilityMission.Mission.Prefab.Type == missionType;
} }
else else
{ {
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue))); LogAbilityConditionError(abilityData, typeof(IAbilityMission));
return false; return false;
} }
} }
@@ -1,14 +1,10 @@
using Microsoft.Xna.Framework; using System.Xml.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
class AbilityConditionServerRandom : AbilityConditionDataless class AbilityConditionServerRandom : AbilityConditionDataless
{ {
private float randomChance = 0f; private readonly float randomChance = 0f;
public override bool AllowClientSimulation => false; public override bool AllowClientSimulation => false;
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
@@ -0,0 +1,32 @@
namespace Barotrauma.Abilities
{
interface IAbilityItemPrefab
{
public ItemPrefab ItemPrefab { get; set; }
}
interface IAbilityValue
{
public float Value { get; set; }
}
interface IAbilityMission
{
public Mission Mission { get; set; }
}
interface IAbilityCharacter
{
public Character Character { get; set; }
}
interface IAbilityString
{
public string String { get; set; }
}
interface IAbilityAffliction
{
public Affliction Affliction { get; set; }
}
}
@@ -0,0 +1,85 @@
using System.Collections.Generic;
namespace Barotrauma.Abilities
{
class AbilityValue : IAbilityValue
{
public AbilityValue(float value)
{
Value = value;
}
public float Value { get; set; }
}
class AbilityValueItem : IAbilityValue, IAbilityItemPrefab
{
public AbilityValueItem(float value, ItemPrefab itemPrefab)
{
Value = value;
ItemPrefab = itemPrefab;
}
public float Value { get; set; }
public ItemPrefab ItemPrefab { get; set; }
}
class AbilityValueString : IAbilityValue, IAbilityString
{
public AbilityValueString(float value, string abilityString)
{
Value = value;
String = abilityString;
}
public float Value { get; set; }
public string String { get; set; }
}
class AbilityStringCharacter : IAbilityCharacter, IAbilityString
{
public AbilityStringCharacter(string abilityString, Character character)
{
String = abilityString;
Character = character;
}
public Character Character { get; set; }
public string String { get; set; }
}
class AbilityValueAffliction : IAbilityValue, IAbilityAffliction
{
public AbilityValueAffliction(float value, Affliction affliction)
{
Value = value;
Affliction = affliction;
}
public float Value { get; set; }
public Affliction Affliction { get; set; }
}
class AbilityValueMission : IAbilityValue, IAbilityMission
{
public AbilityValueMission(float value, Mission mission)
{
Value = value;
Mission = mission;
}
public float Value { get; set; }
public Mission Mission { get; set; }
}
class AbilityAttackData : IAbilityCharacter
{
public float DamageMultiplier { get; set; } = 1f;
public float AddedPenetration { get; set; } = 0f;
public List<Affliction> Afflictions { get; set; }
public Attack SourceAttack { get; }
public Character Character { get; set; }
public Character Attacker { get; set; }
public AbilityAttackData(Attack sourceAttack, Character character)
{
SourceAttack = sourceAttack;
Character = character;
}
}
}
@@ -12,12 +12,16 @@ namespace Barotrauma.Abilities
public CharacterTalent CharacterTalent { get; } public CharacterTalent CharacterTalent { get; }
public Character Character { get; } public Character Character { get; }
public virtual bool RequiresAlive => true; public bool RequiresAlive { get; }
public virtual bool AllowClientSimulation => false; public virtual bool AllowClientSimulation => false;
public virtual bool AppliesEffectOnIntervalUpdate => false; public virtual bool AppliesEffectOnIntervalUpdate => false;
private const float DefaultEffectTime = 1.0f; private const float DefaultEffectTime = 1.0f;
// currently resets if the character dies. would need to be stored in a dictionary of sorts to maintain through death
/// <summary> /// <summary>
/// Used primarily for StatusEffects. Default to constant outside interval abilities. /// Used primarily for StatusEffects. Default to constant outside interval abilities.
/// </summary> /// </summary>
@@ -28,6 +32,7 @@ namespace Barotrauma.Abilities
CharacterAbilityGroup = characterAbilityGroup; CharacterAbilityGroup = characterAbilityGroup;
CharacterTalent = characterAbilityGroup.CharacterTalent; CharacterTalent = characterAbilityGroup.CharacterTalent;
Character = CharacterTalent.Character; Character = CharacterTalent.Character;
RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true);
} }
public bool IsViable() public bool IsViable()
@@ -132,11 +137,5 @@ namespace Barotrauma.Abilities
} }
return flagType; return flagType;
} }
public static float DistanceToSquaredDistance(float distance)
{
return distance * distance;
}
} }
} }
@@ -10,16 +10,41 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects; protected readonly List<StatusEffect> statusEffects;
private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
} }
protected void ApplyEffectSpecific(Character targetCharacter) protected void ApplyEffectSpecific(Character targetCharacter)
{ {
foreach (var statusEffect in statusEffects) foreach (var statusEffect in statusEffects)
{ {
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
// currently used this to spawn items on the targeted character
statusEffect.SetUser(targetCharacter);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.This))
{
statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character);
}
else
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
} }
} }
@@ -30,13 +55,17 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(object abilityData) protected override void ApplyEffect(object abilityData)
{ {
if (abilityData is Character targetCharacter) if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter)
{
ApplyEffectSpecific(selectedCharacter);
}
else if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter)
{ {
ApplyEffectSpecific(targetCharacter); ApplyEffectSpecific(targetCharacter);
} }
else else
{ {
ApplyEffect(); ApplyEffect();
} }
} }
} }
@@ -0,0 +1,30 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects
{
private readonly bool allowSelf;
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
protected override void ApplyEffect()
{
IEnumerable<Character> chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character);
foreach (Character character in chosenCharacters)
{
ApplyEffectSpecific(character);
}
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToAttacker : CharacterAbilityApplyStatusEffects
{
public CharacterAbilityApplyStatusEffectsToAttacker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect(object abilityData)
{
if ((abilityData as AbilityAttackData)?.Attacker is Character attacker)
{
ApplyEffectSpecific(attacker);
}
}
}
}
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
@@ -10,7 +9,7 @@ namespace Barotrauma.Abilities
protected float squaredMaxDistance; protected float squaredMaxDistance;
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue)); squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
} }
protected override void ApplyEffect() protected override void ApplyEffect()
@@ -20,7 +19,7 @@ namespace Barotrauma.Abilities
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character)) foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
{ {
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance) if (crewCharacter != Character && Vector2.DistanceSquared(Character.WorldPosition, crewCharacter.WorldPosition) is float tempDistance && tempDistance < closestDistance)
{ {
closestCharacter = crewCharacter; closestCharacter = crewCharacter;
closestDistance = tempDistance; closestDistance = tempDistance;
@@ -1,5 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -16,7 +17,7 @@ namespace Barotrauma.Abilities
public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue)); squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true); allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true);
allowSelf = abilityElement.GetAttributeBool("allowself", true); allowSelf = abilityElement.GetAttributeBool("allowself", true);
} }
@@ -26,9 +27,9 @@ namespace Barotrauma.Abilities
Character chosenCharacter = null; Character chosenCharacter = null;
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c => chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
(allowSelf ||c != Character) && (allowSelf || c != Character) &&
(allowDifferentSub || c.Submarine == Character.Submarine) && (allowDifferentSub || c.Submarine == Character.Submarine) &&
Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) is float tempDistance &&
tempDistance < squaredMaxDistance).GetRandom(); tempDistance < squaredMaxDistance).GetRandom();
if (chosenCharacter == null) { return; } if (chosenCharacter == null) { return; }
@@ -7,16 +7,41 @@ namespace Barotrauma.Abilities
{ {
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
private int amount; private readonly int amount;
private StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
amount = abilityElement.GetAttributeInt("amount", 0); amount = abilityElement.GetAttributeInt("amount", 0);
scalingStatType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("scalingstattype", "None"), CharacterTalent.DebugIdentifier);
}
private void ApplyEffectSpecific(Character targetCharacter)
{
float multiplier = 1f;
if (scalingStatType != StatTypes.None)
{
multiplier = 0 + Character.Info.GetSavedStatValue(scalingStatType);
}
targetCharacter.GiveMoney((int)(multiplier * amount));
}
protected override void ApplyEffect(object abilityData)
{
if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
else
{
ApplyEffectSpecific(Character);
}
} }
protected override void ApplyEffect() protected override void ApplyEffect()
{ {
Character.GiveMoney(amount); ApplyEffectSpecific(Character);
} }
} }
} }
@@ -8,8 +8,10 @@ namespace Barotrauma.Abilities
private readonly string statIdentifier; private readonly string statIdentifier;
private readonly StatTypes statType; private readonly StatTypes statType;
private readonly float value; private readonly float value;
private readonly float maxValue;
private readonly bool targetAllies; private readonly bool targetAllies;
private readonly bool removeOnDeath; private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
//private readonly float maximumValue; //private readonly float maximumValue;
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
@@ -19,8 +21,10 @@ namespace Barotrauma.Abilities
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f); value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue);
targetAllies = abilityElement.GetAttributeBool("targetallies", false); targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
} }
@@ -38,11 +42,11 @@ namespace Barotrauma.Abilities
{ {
if (targetAllies) if (targetAllies)
{ {
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath)); Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue));
} }
else else
{ {
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath); Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue);
} }
} }
} }
@@ -4,18 +4,23 @@ namespace Barotrauma.Abilities
{ {
class CharacterAbilityGiveResistance : CharacterAbility class CharacterAbilityGiveResistance : CharacterAbility
{ {
private string resistanceId; private readonly string resistanceId;
private float resistance; private readonly float multiplier;
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
resistanceId = abilityElement.GetAttributeString("resistanceid", ""); resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f); multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (string.IsNullOrEmpty(resistanceId))
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
}
} }
public override void InitializeAbility(bool addingFirstTime) public override void InitializeAbility(bool addingFirstTime)
{ {
Character.ChangeAbilityResistance(resistanceId, resistance); Character.ChangeAbilityResistance(resistanceId, multiplier);
} }
} }
} }
@@ -4,10 +4,9 @@ namespace Barotrauma.Abilities
{ {
class CharacterAbilityGiveStat : CharacterAbility class CharacterAbilityGiveStat : CharacterAbility
{ {
private StatTypes statType; private readonly StatTypes statType;
private float value; private readonly float value;
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
@@ -7,8 +7,9 @@ namespace Barotrauma.Abilities
{ {
private readonly List<Affliction> afflictions; private readonly List<Affliction> afflictions;
float addedDamageMultiplier; private readonly float addedDamageMultiplier;
float addedPenetration; private readonly float addedPenetration;
private readonly bool implode;
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
@@ -18,11 +19,12 @@ namespace Barotrauma.Abilities
} }
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f); addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f); addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
implode = abilityElement.GetAttributeBool("implode", false);
} }
protected override void ApplyEffect(object abilityData) protected override void ApplyEffect(object abilityData)
{ {
if (abilityData is AttackData attackData) if (abilityData is AbilityAttackData attackData)
{ {
if (attackData.Afflictions == null) if (attackData.Afflictions == null)
{ {
@@ -34,6 +36,13 @@ namespace Barotrauma.Abilities
} }
attackData.DamageMultiplier += addedDamageMultiplier; attackData.DamageMultiplier += addedDamageMultiplier;
attackData.AddedPenetration += addedPenetration; attackData.AddedPenetration += addedPenetration;
if (implode)
{
// might have issues, as the method used to be private and only used for pressure death
attackData.Character?.Implode();
}
} }
else else
{ {
@@ -4,8 +4,8 @@ namespace Barotrauma.Abilities
{ {
class CharacterAbilityModifyResistance : CharacterAbility class CharacterAbilityModifyResistance : CharacterAbility
{ {
private string resistanceId; private readonly string resistanceId;
private float resistance; private readonly float resistance;
bool lastState; bool lastState;
// should probably be split to different classes // should probably be split to different classes
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
{ {
resistanceId = abilityElement.GetAttributeString("resistanceid", ""); resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f); resistance = abilityElement.GetAttributeFloat("resistance", 1f);
if (string.IsNullOrEmpty(resistanceId))
{
DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set.");
}
} }
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
@@ -0,0 +1,52 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToSkill : CharacterAbility
{
private readonly StatTypes statType;
private readonly float maxValue;
private readonly string skillIdentifier;
private readonly bool useAll;
private float lastValue = 0f;
public CharacterAbilityModifyStatToSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", string.Empty);
useAll = skillIdentifier == "all";
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched)
{
float skillTotal = 0f;
if (useAll && Character.Info?.Job != null)
{
foreach (Skill skill in Character.Info.Job.Skills)
{
skillTotal += Character.GetSkillLevel(skill.Identifier);
}
skillTotal /= Character.Info.Job.Skills.Count;
}
else
{
skillTotal = Character.GetSkillLevel(skillIdentifier);
}
lastValue = skillTotal / 100f * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -5,42 +5,21 @@ namespace Barotrauma.Abilities
class CharacterAbilityModifyValue : CharacterAbility class CharacterAbilityModifyValue : CharacterAbility
{ {
private float addedValue; private float addedValue;
private float multiplierValue; private float multiplyValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f); multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
} }
protected override void ApplyEffect(object abilityData) protected override void ApplyEffect(object abilityData)
{ {
if (abilityData is AbilityValue abilityValue) if (abilityData is IAbilityValue abilityValue)
{ {
ApplyEffectSpecific(abilityValue); abilityValue.Value += addedValue;
abilityValue.Value *= multiplyValue;
} }
else if (abilityData is (object _, AbilityValue tupleAbilityValue))
{
ApplyEffectSpecific(tupleAbilityValue);
}
}
private void ApplyEffectSpecific(AbilityValue abilityValue)
{
abilityValue.Value += addedValue;
abilityValue.Value *= multiplierValue;
}
}
// this seems like a real silly way to have to pass values by reference into these same interfaces
// if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead
class AbilityValue
{
public float Value { get; set; }
public AbilityValue(float value)
{
Value = value;
} }
} }
} }
@@ -5,7 +5,7 @@ namespace Barotrauma.Abilities
class CharacterAbilityResetPermanentStat : CharacterAbility class CharacterAbilityResetPermanentStat : CharacterAbility
{ {
private readonly string statIdentifier; private readonly string statIdentifier;
public override bool RequiresAlive => false; public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityRevive : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityRevive(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
private void ApplyEffectSpecific()
{
Character.Revive();
}
protected override void ApplyEffect()
{
ApplyEffectSpecific();
}
protected override void ApplyEffect(object abilityData)
{
ApplyEffectSpecific();
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityAlienHoarder : CharacterAbility
{
private readonly float addedDamageMultiplierPerItem;
private readonly int maxAmount;
private readonly string[] tags;
public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f);
maxAmount = abilityElement.GetAttributeInt("maxamount", 0);
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AbilityAttackData attackData)
{
float totalAddedDamageMultiplier = 0f;
foreach (Item item in Character.Inventory.AllItems)
{
if (tags.Any(t => item.Prefab.Tags.Any(p => t == p)))
{
totalAddedDamageMultiplier += addedDamageMultiplierPerItem;
}
}
attackData.DamageMultiplier += addedDamageMultiplierPerItem;
}
else
{
LogAbilityDataMismatch();
}
}
}
}
@@ -12,9 +12,9 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(object abilityData) protected override void ApplyEffect(object abilityData)
{ {
if (abilityData is (string skillIdentifier, Character character) && character != Character) if (abilityData is AbilityStringCharacter abilityStringCharacter && abilityStringCharacter.Character != Character)
{ {
character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); Character.Info?.IncreaseSkillLevel(abilityStringCharacter.String, 1.0f, abilityStringCharacter.Character.Position + Vector2.UnitY * 175.0f);
} }
} }
} }
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityByTheBook : CharacterAbility
{
private int moneyAmount;
private int max;
public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0);
max = abilityElement.GetAttributeInt("max", 0);
}
protected override void ApplyEffect()
{
IEnumerable<Character> enemyCharacters = Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.None);
int timesGiven = 0;
foreach (Character enemyCharacter in enemyCharacters)
{
if (!enemyCharacter.IsHuman) { continue; }
if (enemyCharacter.Submarine == null || enemyCharacter.Submarine != Submarine.MainSub) { continue; }
if (enemyCharacter.IsDead) { continue; }
if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount);
timesGiven++;
}
}
}
}
@@ -9,45 +9,22 @@ namespace Barotrauma.Abilities
class CharacterAbilityInsurancePolicy : CharacterAbility class CharacterAbilityInsurancePolicy : CharacterAbility
{ {
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
public override bool RequiresAlive => false;
private readonly int moneyPerLevel; private readonly int moneyPerMission;
private bool hasOccurred = false;
private static List<Client> clientsAlreadyUsed = new List<Client>(); private static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0); moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
} }
protected override void ApplyEffect() protected override void ApplyEffect()
{ {
if (Character?.Info is CharacterInfo info && !hasOccurred) if (Character?.Info is CharacterInfo info)
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
{
if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; }
}
}
Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel()); Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
hasOccurred = true;
// this is an ugly way to do this, but this effect should not occur more than once per round for a client
// this seemed like the simplest way to do it since characters are instantiated from scratch each time
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
{
if (client.Character == Character)
{
clientsAlreadyUsed.Add(client);
}
}
}
} }
} }
} }
@@ -5,14 +5,14 @@ namespace Barotrauma.Abilities
class CharacterAbilityPsychoClown : CharacterAbility class CharacterAbilityPsychoClown : CharacterAbility
{ {
private StatTypes statType; private StatTypes statType;
private float value; private float maxValue;
private string afflictionIdentifier; private string afflictionIdentifier;
private float lastValue = 0f; private float lastValue = 0f;
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f); maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", ""); afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
} }
@@ -32,7 +32,7 @@ namespace Barotrauma.Abilities
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength; afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
} }
lastValue = afflictionStrength * value; lastValue = afflictionStrength * maxValue;
Character.ChangeStat(statType, lastValue); Character.ChangeStat(statType, lastValue);
} }
else else

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