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

View File

@@ -623,6 +623,7 @@ namespace Barotrauma
bool removeOnDeath = inc.ReadBoolean();
ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath);
}
ch.ExperiencePoints = inc.ReadUInt16();
return ch;
}

View File

@@ -1,10 +1,4 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
namespace Barotrauma
{
partial class AfflictionHusk : Affliction
{

View File

@@ -632,7 +632,7 @@ namespace Barotrauma
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;
var spriteParams = Params.GetSprite();
@@ -678,7 +678,7 @@ namespace Barotrauma
if (!hideLimb)
{
var deformSprite = DeformSprite;
if (deformSprite != null)
if (deformSprite != null && !disableDeformations)
{
if (ActiveDeformations.Any())
{
@@ -999,10 +999,12 @@ namespace Barotrauma
}
float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale;
float rotation = -body.DrawRotation - wearable.Rotation * Dir;
wearable.Sprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)),
origin, -body.DrawRotation,
origin, rotation,
Scale * textureScale, spriteEffect, depth);
}

View File

@@ -1320,11 +1320,13 @@ namespace Barotrauma
continue;
}
float avgOutCondition = (deconstructItem.OutConditionMin + deconstructItem.OutConditionMax) / 2;
int? deconstructProductPrice = targetItem.GetMinPrice();
if (deconstructProductPrice.HasValue)
{
if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; }
deconstructProductCost += (int)(deconstructProductPrice * deconstructItem.OutCondition);
deconstructProductCost += (int)(deconstructProductPrice * avgOutCondition);
}
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);
}
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);
}
}
}

View File

@@ -251,6 +251,11 @@ namespace Barotrauma
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>
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; }
// 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;

View File

@@ -79,14 +79,14 @@ namespace Barotrauma
new Rectangle(
sliderArea.X + (int)sliceBorderSizes.X,
sliderArea.Y,
(int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
(int)Math.Round((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
sliderArea.Height)
:
new Rectangle(
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,
(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.Height = Math.Max(sliderRect.Height, 1);

View File

@@ -1219,7 +1219,11 @@ namespace Barotrauma
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);
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; }
@@ -1254,7 +1258,7 @@ namespace Barotrauma
Stretch = true,
};
foreach (Talent talent in talentOption.Talents)
foreach (TalentPrefab talent in talentOption.Talents)
{
int optionPadding = GUI.IntScale(10);
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")
{
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,
PressedColor = pressedColor,
OnClicked = (button, userData) =>
{
talentTitleText.Text = TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? string.Empty;
talentDescriptionText.Text = TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty;
talentTitleText.Text = talent.DisplayName;
talentDescriptionText.Text = talent.Description;
// deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
@@ -1440,10 +1444,10 @@ namespace Barotrauma
private readonly StatTypes[] combatStats = new StatTypes[]
{
StatTypes.MaximumHealthMultiplier,
StatTypes.MovementSpeed,
StatTypes.SwimmingSpeed,
StatTypes.RepairSpeed,
StatTypes.MeleeAttackMultiplier,
StatTypes.MeleeAttackSpeed,
StatTypes.RangedAttackSpeed,
StatTypes.TurretAttackSpeed,
};
private readonly StatTypes[] miscStats = new StatTypes[]

View File

@@ -99,7 +99,9 @@ namespace Barotrauma
crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false)
{
AutoHideScrollBar = false,
CanBeFocused = false,
CanDragElements = true,
CanInteractWhenUnfocusable = true,
OnSelected = (component, userData) => false,
SelectMultiple = false,
Spacing = (int)(GUI.Scale * 10),
@@ -770,7 +772,7 @@ namespace Barotrauma
if (IsSinglePlayer)
{
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);
}
else if (orderGiver != null)
@@ -1905,13 +1907,23 @@ namespace Barotrauma
}
}
private bool CanSomeoneHearCharacter()
private bool CanCharacterBeHeard()
{
#if DEBUG
if (Character.Controlled == null) { return true; }
#endif
return Character.Controlled != null &&
(characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)));
if (Character.Controlled != null)
{
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()
@@ -2580,7 +2592,7 @@ namespace Barotrauma
for (int i = 0; i < orders.Count; i++)
{
order = orders[i];
disableNode = !CanSomeoneHearCharacter() ||
disableNode = !CanCharacterBeHeard() ||
(order.MustSetTarget && (order.ItemComponentType != null || order.TargetItems.Length > 0) &&
order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None());
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));
bool disableNode = !CanSomeoneHearCharacter();
bool disableNode = !CanCharacterBeHeard();
for (int i = 0; i < contextualOrders.Count; i++)
{
optionNodes.Add(new Tuple<GUIComponent, Keys>(
@@ -2766,7 +2778,7 @@ namespace Barotrauma
if (checkIfOrderCanBeHeard && !disableNode)
{
disableNode = !CanSomeoneHearCharacter();
disableNode = !CanCharacterBeHeard();
}
var mustSetOptionOrTarget = order.HasOptions;
@@ -2827,7 +2839,7 @@ namespace Barotrauma
if (disableNode)
{
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)
{
@@ -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.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip"));
}
if (!CanSomeoneHearCharacter())
if (!CanCharacterBeHeard())
{
node.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)
{

View File

@@ -174,6 +174,12 @@ namespace Barotrauma
(int)SlotPositions[i].X,
(int)SlotPositions[i].Y,
(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>();
if (itemContainer != null)
@@ -238,6 +244,8 @@ namespace Barotrauma
{
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 (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;
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 (PersonalSlots.HasFlag(SlotTypes[i]))
{
@@ -383,7 +391,7 @@ namespace Barotrauma
continue;
}
if (HideSlot(i)) { continue; }
if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i]))
{
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
@@ -399,7 +407,7 @@ namespace Barotrauma
x = lowerX;
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; }
x -= visualSlots[i].Rect.Width + Spacing;
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
@@ -414,7 +422,7 @@ namespace Barotrauma
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 (PersonalSlots.HasFlag(SlotTypes[i]))
{
@@ -436,7 +444,7 @@ namespace Barotrauma
SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY);
continue;
}
if (!HideSlot(i)) { continue; }
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing;
}
@@ -450,7 +458,7 @@ namespace Barotrauma
int x = startX, y = startY;
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)
{
SlotPositions[i] = new Vector2(x, y);
@@ -462,7 +470,7 @@ namespace Barotrauma
int n = 0;
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)
{
SlotPositions[i] = new Vector2(x, y);
@@ -479,13 +487,23 @@ namespace Barotrauma
}
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();
if (layout == Layout.Default)
{
HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale);
}
}
protected override void ControlInput(Camera cam)
@@ -679,7 +697,7 @@ namespace Barotrauma
if (item != null)
{
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);
}
@@ -858,7 +876,9 @@ namespace Barotrauma
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;
}
@@ -1153,7 +1173,7 @@ namespace Barotrauma
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
bool drawItem = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn();
@@ -1207,7 +1227,7 @@ namespace Barotrauma
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;
}

View File

@@ -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);
}
}
}
}

View File

@@ -621,6 +621,6 @@ namespace Barotrauma.Items.Components
}
OnResolutionChanged();
}
public virtual void AddTooltipInfo(ref string description) { }
public virtual void AddTooltipInfo(ref string name, ref string description) { }
}
}

View File

@@ -58,6 +58,9 @@ namespace Barotrauma.Items.Components
[Serialize(null, false)]
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. "+
"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; }

View File

@@ -14,12 +14,19 @@ namespace Barotrauma.Items.Components
}
private GUIButton activateButton;
private GUIComponent inputInventoryHolder, outputInventoryHolder;
private GUICustomComponent inputInventoryOverlay;
private GUIComponent inSufficientPowerWarning;
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)
{
CreateGUI();
@@ -39,6 +46,12 @@ namespace Barotrauma.Items.Components
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);
// === INPUT LABEL === //
@@ -55,22 +68,23 @@ namespace Barotrauma.Items.Components
// === INPUT SLOTS === //
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 === //
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")
{
TextBlock = { AutoScaleHorizontal = true },
OnClicked = ToggleActive
};
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,
IgnoreLayoutGroups = true,
Visible = false,
CanBeFocused = false
CanBeFocused = false,
AutoScaleHorizontal = true
};
// === 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));
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 === //
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)
@@ -126,14 +194,30 @@ namespace Barotrauma.Items.Components
private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{
overlayComponent.RectTransform.SetAsLastChild();
if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
var lastSlot = visualSlots.Last();
GUI.DrawRectangle(spriteBatch,
new Rectangle(
lastSlot.Rect.X, lastSlot.Rect.Y + (int)(lastSlot.Rect.Height * (1.0f - progressState)),
lastSlot.Rect.Width, (int)(lastSlot.Rect.Height * progressState)),
GUI.Style.Green * 0.5f, isFilled: true);
if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
if (DeconstructItemsSimultaneously)
{
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)

View File

@@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components
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 };
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);
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), pointsOfInterest, out electricalMapComponents);
@@ -458,7 +458,7 @@ namespace Barotrauma.Items.Components
CreateHUD();
}
if (PlayerInput.PrimaryMouseButtonDown())
if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus)
{
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;
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 (dragMap || Vector2.DistanceSquared(dragStart, PlayerInput.MousePosition) > GUI.IntScale(dragTreshold * dragTreshold))
@@ -487,16 +483,11 @@ namespace Barotrauma.Items.Components
mapOffset.X += PlayerInput.MouseSpeed.X;
mapOffset.Y += PlayerInput.MouseSpeed.Y;
recalculate |= PlayerInput.MouseSpeed != Vector2.Zero;
recalculate = 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())
{
dragMapStart = null;
@@ -505,10 +496,15 @@ namespace Barotrauma.Items.Components
if (recalculate)
{
miniMapContainer.RectTransform.LocalScale = elementScale;
miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom);
miniMapContainer.RectTransform.RecalculateChildren(true, true);
miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint();
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?
@@ -583,6 +579,7 @@ namespace Barotrauma.Items.Components
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
{
// TODO remove
#warning remove
if (currentMode == MiniMapMode.HullCondition)
{
const string wipText = "work in progress";
@@ -757,7 +754,7 @@ namespace Barotrauma.Items.Components
foreach (Item foundItem in foundItems)
{
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);
}
@@ -873,10 +870,14 @@ namespace Barotrauma.Items.Components
string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty;
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);
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);
SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color);
@@ -958,7 +959,7 @@ namespace Barotrauma.Items.Components
Rectangle parentRect = container.Rect;
if (miniMapFrame is { } miniMap) { parentRect = miniMap.Rect; }
DrawSubmarine(spriteBatch, parentRect);
DrawSubmarine(spriteBatch);
}
if (Voltage < MinVoltage) { return; }
@@ -979,7 +980,7 @@ namespace Barotrauma.Items.Components
Vector2 spriteScale = targetSize / pingCircle.size;
float scale = Math.Min(blipState, maxBlipState / 2f);
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);
const float width = 1f;
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;
}
private void DrawSubmarine(SpriteBatch spriteBatch, Rectangle parentRect)
private void DrawSubmarine(SpriteBatch spriteBatch)
{
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
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 };
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();
}
@@ -1281,12 +1290,12 @@ namespace Barotrauma.Items.Components
GUIFrame hullContainer = new GUIFrame(new RectTransform(containerScale * elementPadding, parent.RectTransform, Anchor.Center), style: null);
ImmutableHashSet<Submarine> connectedSubs = sub.GetConnectedSubs().ToImmutableHashSet();
ImmutableHashSet<Hull> hullList = ImmutableHashSet<Hull>.Empty;
ImmutableDictionary<Hull, ImmutableHashSet<Hull>> combinedHulls = ImmutableDictionary<Hull, ImmutableHashSet<Hull>>.Empty;
ImmutableArray<Hull> hullList = ImmutableArray<Hull>.Empty;
ImmutableDictionary<Hull, ImmutableArray<Hull>> combinedHulls = ImmutableDictionary<Hull, ImmutableArray<Hull>>.Empty;
if (settings.CreateHullElements)
{
hullList = Hull.hullList.Where(IsPartofSub).ToImmutableHashSet();
hullList = Hull.hullList.Where(IsPartofSub).ToImmutableArray();
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>>();
@@ -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;
@@ -1500,6 +1509,8 @@ namespace Barotrauma.Items.Components
hullRefs.Add(hull);
}
hullRefs.Reverse(); // I have no idea why this is required
ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1);
List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles);
@@ -1508,6 +1519,7 @@ namespace Barotrauma.Items.Components
foreach (List<Vector2> list in polygon)
{
// scale down the polygon just a tiny bit
var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list);
float sizeX = polySizeX - 1f,
sizeY = polySizeY - 1f;

View File

@@ -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();
}
}
}

View File

@@ -54,6 +54,11 @@ namespace Barotrauma.Items.Components
if (wireComponent != null)
{
equippedWire = wireComponent;
var connectedEnd = equippedWire.OtherConnection(null);
if (connectedEnd?.Item.Submarine != null && panel.Item.Submarine != connectedEnd.Item.Submarine)
{
equippedWire = null;
}
}
}
}

View File

@@ -63,11 +63,11 @@ namespace Barotrauma.Items.Components
}
OutputValue = input;
ShowOnDisplay(input);
ShowOnDisplay(input, addToHistory: true);
item.SendSignal(input, "signal_out");
}
partial void ShowOnDisplay(string input, bool addToHistory = true)
partial void ShowOnDisplay(string input, bool addToHistory)
{
if (addToHistory)
{

View File

@@ -39,6 +39,34 @@ namespace Barotrauma.Items.Components
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 const float UpdateInterval = 0.5f;
@@ -91,12 +119,13 @@ namespace Barotrauma.Items.Components
foreach (Character c in Character.CharacterList)
{
if (c == equipper || !c.Enabled || c.Removed) { continue; }
if (!ShowDeadCharacters && c.IsDead) { continue; }
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist < Range * Range)
{
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);
}
@@ -123,27 +152,54 @@ namespace Barotrauma.Items.Components
{
if (character == null) { return; }
GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
Color.LightGreen * 0.5f);
Character closestCharacter = null;
float closestDist = float.PositiveInfinity;
foreach (Character c in visibleCharacters)
if (OverlayColor.A > 0)
{
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;
}
GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), OverlayColor);
}
if (closestCharacter != null)
if (ShowTexts)
{
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));
Character closestCharacter = null;
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);
}
}

View File

@@ -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}";
}
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())
{

View File

@@ -297,9 +297,10 @@ namespace Barotrauma
}
}
string name = item.Name;
foreach (ItemComponent component in item.Components)
{
component.AddTooltipInfo(ref description);
component.AddTooltipInfo(ref name, ref description);
}
if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null)
@@ -315,7 +316,7 @@ namespace Barotrauma
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))
{
toolTip += " " + TextManager.Get("connectionlocked");
@@ -719,7 +720,7 @@ namespace Barotrauma
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)
{
columns++;
@@ -1543,13 +1544,13 @@ namespace Barotrauma
}
else
{
var containedItem = itemContainer.Inventory.slots[0].FirstOrDefault();
containedState = itemContainer.Inventory.Capacity == 1 ?
var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault();
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && itemContainer.Inventory.Capacity == 1)
{
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.MaxStackSize);
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
if (maxStackSize > 1)
{
containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize;
@@ -1631,7 +1632,7 @@ namespace Barotrauma
int maxStackSize = item.Prefab.MaxStackSize;
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)
{

View File

@@ -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;
foreach (ItemComponent ic in activeHUDs)

View File

@@ -423,20 +423,24 @@ namespace Barotrauma
//select wire if both items it's connected to are selected
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;
foreach (Connection c in item.Connections)
{
foreach (Wire w in c.Wires)
{
if (w == null || SelectedList.Contains(w.Item)) continue;
var wire = item.GetComponent<Wire>();
if (wire == null) { continue; }
Item item0 = wire.Connections[0]?.Item;
Item item1 = wire.Connections[1]?.Item;
if (w.OtherConnection(c) != null && SelectedList.Contains(w.OtherConnection(c).Item))
{
SelectedList.Add(w.Item);
}
}
if (item0 == null && item1 != null)
{
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);
}
}

View File

@@ -326,7 +326,7 @@ namespace Barotrauma
depthSortedDamageable.Insert(i, structure);
}
}
foreach (Structure s in depthSortedDamageable)
{
s.DrawDamage(spriteBatch, damageEffect, editing);
@@ -418,8 +418,8 @@ namespace Barotrauma
float scale = 0.9f;
GUIFrame hullContainer = new GUIFrame(new RectTransform(
(parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
parent.RectTransform, Anchor.Center),
(parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
parent.RectTransform, Anchor.Center),
style: null)
{
UserData = "hullcontainer"
@@ -444,9 +444,9 @@ namespace Barotrauma
{
if (!combinedHulls.ContainsKey(hull))
{
combinedHulls.Add(hull, new HashSet<Hull>());
combinedHulls.Add(hull, new HashSet<Hull>());
}
combinedHulls[hull].Add(linkedHull);
}
}
@@ -454,14 +454,14 @@ namespace Barotrauma
foreach (Hull hull in hullList)
{
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);
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));
if (hideHull) { continue; }
Color color = Color.DarkCyan * 0.8f;
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);
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);
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();
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
const float padding = 0.001f;
size.X += padding;
size.Y += padding;
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)
@@ -586,7 +583,7 @@ namespace Barotrauma
wRect.Y = -wRect.Y;
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);
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))
{
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);
if (doorLinks + wireCount > item.Connections[i].MaxWires)
{
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
new string[] { "[doorcount]", "[freeconnectioncount]" },
new string[] { doorLinks.ToString(), (item.Connections[i].MaxWires - wireCount).ToString() }));
break;
@@ -794,7 +791,7 @@ namespace Barotrauma
return SubEditorScreen.SuppressedWarnings.Contains(type);
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
if (type != ServerNetObject.ENTITY_POSITION)

View File

@@ -184,7 +184,7 @@ namespace Barotrauma.Particles
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)

View File

@@ -1366,6 +1366,7 @@ namespace Barotrauma
LogButtons.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.RectTransform.RelativeSize = Vector2.One);
roundControlsHolder.Recalculate();
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted;

View File

@@ -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);
MiniMap miniMap = miniMapItem.GetComponent<MiniMap>();
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.Info.Name = "Galldren";
dummyCharacter.Inventory.CreateSlots();

View File

@@ -1012,7 +1012,8 @@ namespace Barotrauma
Screen.Selected == GameMain.SpriteEditorScreen ||
Screen.Selected == GameMain.SubEditorScreen ||
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";
}

View File

@@ -110,14 +110,14 @@ namespace Barotrauma
foreach (StatusEffect statusEffect in successEffects)
{
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)));
}
foreach (StatusEffect statusEffect in failureEffects)
{
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)));
}
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.0.0</Version>
<Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.0.0</Version>
<Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.1500.0.0</Version>
<Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.0.0</Version>
<Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.0.0</Version>
<Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -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;
if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this)

View File

@@ -73,6 +73,7 @@ namespace Barotrauma
msg.Write(savedStatValue.RemoveOnDeath);
}
}
msg.Write((ushort)ExperiencePoints);
}
}
}

View File

@@ -27,6 +27,29 @@ namespace Barotrauma
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
{
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
@@ -155,6 +178,20 @@ namespace Barotrauma
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()
{
if (petsElement != null)
@@ -259,6 +296,16 @@ namespace Barotrauma
Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
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);
@@ -965,6 +1012,15 @@ namespace Barotrauma
// save bots
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
XElement availableSubsElement = new XElement("AvailableSubs");
for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++)

View File

@@ -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);
}
}
}
}

View File

@@ -19,13 +19,13 @@ namespace Barotrauma.Items.Components
GameServer.Log(GameServer.CharacterLogName(c.Character) + " entered \"" + newOutputValue + "\" on " + item.Name,
ServerLog.MessageType.ItemInteraction);
OutputValue = newOutputValue;
ShowOnDisplay(newOutputValue);
ShowOnDisplay(newOutputValue, addToHistory: true);
item.SendSignal(newOutputValue, "signal_out");
item.CreateServerEvent(this);
}
}
partial void ShowOnDisplay(string input, bool addToHistory = true)
partial void ShowOnDisplay(string input, bool addToHistory)
{
if (addToHistory)
{

View File

@@ -2358,6 +2358,12 @@ namespace Barotrauma.Networking
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.OwnerClientName = teamClients[i].Name;
}

View File

@@ -379,6 +379,12 @@ namespace Barotrauma.Networking
}
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
if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null)
{

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.0.0</Version>
<Version>0.1500.1.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -23,6 +23,7 @@
<Item file="Content/Items/Electricity/signalitems.xml" />
<Item file="Content/Items/Engine/engine.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/Ladder/ladder.xml" />
<Item file="Content/Items/Materials/materials.xml" />
@@ -63,6 +64,7 @@
<Item file="Content/Items/Gardening/gardeningtools.xml" />
<Item file="Content/Items/Gardening/plantproducts.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/Security/securityofficer_talent_items.xml" />
<Item file="Content/Items/Jobgear/Engineer/engineer_talent_items.xml" />
@@ -152,6 +154,7 @@
<Text file="Content/Texts/Korean/KoreanVanilla.xml" />
<UIStyle file="Content/UI/style.xml" />
<Afflictions file="Content/Afflictions.xml" />
<Afflictions file="Content/AfflictionsGeneticMaterial.xml" />
<Afflictions file="Content/Talents/Assistant/AfflictionsAssistant.xml" />
<Afflictions file="Content/Talents/Captain/AfflictionsCaptain.xml" />
<Afflictions file="Content/Talents/Doctor/AfflictionsDoctor.xml" />

View File

@@ -1915,12 +1915,12 @@ namespace Barotrauma
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
});
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);

View File

@@ -588,8 +588,9 @@ namespace Barotrauma
// assume that it's required for the stun effect
// as we can't check the status effect conditions here.
var mobileBatteryTag = "mobilebattery";
var containers = weapon.Item.Components.Where(ic => ic is ItemContainer container &&
container.ContainableItems.Any(containable => containable.Identifiers.Any(id => id.Equals(mobileBatteryTag))));
var containers = weapon.Item.Components.Where(ic =>
ic is ItemContainer container &&
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
// If there's no such container, assume that the melee weapon can stun without a battery.
return containers.None() || containers.Any(container =>
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);

View File

@@ -148,7 +148,7 @@ namespace Barotrauma
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) { }

View File

@@ -390,11 +390,17 @@ namespace Barotrauma
}
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
var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
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
Entity.Spawner?.AddToRemoveQueue(target);

View File

@@ -150,6 +150,13 @@ namespace Barotrauma
private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
private bool aiming;
private bool wasAiming;
private bool aimingMelee;
private bool wasAimingMelee;
public bool IsAiming => wasAiming;
public bool IsAimingMelee => wasAimingMelee;
private readonly float movementLerp;
@@ -532,7 +539,10 @@ namespace Barotrauma
limb.Disabled = false;
}
wasAiming = aiming;
aiming = false;
wasAimingMelee = aimingMelee;
aimingMelee = false;
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return;
}
@@ -1718,7 +1728,7 @@ namespace Barotrauma
}
//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)
{
@@ -1748,6 +1758,8 @@ namespace Barotrauma
Holdable holdable = item.GetComponent<Holdable>();
this.aimingMelee = aimingMelee;
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated)
{
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
@@ -1771,7 +1783,6 @@ namespace Barotrauma
aiming = true;
}
}
else
{

View File

@@ -395,12 +395,18 @@ namespace Barotrauma
if (character.IsHusk && character.Params.UseHuskAppendage)
{
bool inEditor = false;
#if CLIENT
inEditor = Screen.Selected == GameMain.CharacterEditorScreen;
#endif
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
if (characterPrefab?.XDocument != null)
{
var mainElement = characterPrefab.XDocument.Root.IsOverride() ? characterPrefab.XDocument.Root.FirstElement() : characterPrefab.XDocument.Root;
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);
}
}

View File

@@ -8,7 +8,8 @@ namespace Barotrauma
public enum HitDetection
{
Distance,
Contact
Contact,
None
}
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
{
[Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
@@ -466,7 +453,7 @@ namespace Barotrauma
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;
if (targetCharacter != null && targetCharacter.IsDead)
{

View File

@@ -9,6 +9,7 @@ using System.Xml.Linq;
using Barotrauma.Items.Components;
using FarseerPhysics.Dynamics;
using Barotrauma.Extensions;
using Barotrauma.Abilities;
#if SERVER
using System.Text;
#endif
@@ -1366,7 +1367,6 @@ namespace Barotrauma
}
}
}
private List<Item> wearableItems = new List<Item>();
public float GetSkillLevel(string skillIdentifier)
{
@@ -2075,11 +2075,10 @@ namespace Barotrauma
return SelectedCharacter == owner && owner.CanInventoryBeAccessed;
}
if (inventory.Owner is Item)
if (inventory.Owner is Item item)
{
var owner = (Item)inventory.Owner;
if (!CanInteractWith(owner) && !owner.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; }
ItemContainer container = owner.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
if (!CanInteractWith(item) && !item.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; }
ItemContainer container = item.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
if (container != null && !container.HasRequiredItems(this, addMessage: false)) { return false; }
}
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; }
Pickable pickableComponent = item.GetComponent<Pickable>();
@@ -3355,10 +3360,18 @@ namespace Barotrauma
float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime;
AttackData attackData = new AttackData(attack);
attacker.CheckTalents(AbilityEffectType.OnAttack, attackData);
CheckTalents(AbilityEffectType.OnAttacked, attackData);
attackData.DamageMultiplier *= (1 + attacker.GetStatValue(StatTypes.AttackMultiplier));
AbilityAttackData attackData = new AbilityAttackData(attack, this);
if (attacker != null)
{
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;
@@ -3495,6 +3508,7 @@ namespace Barotrauma
{
attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target);
}
CheckTalents(AbilityEffectType.OnKillCharacter, target);
if (!IsOnPlayerTeam) { 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.OnDamaged)
{
if (statusEffect.AllowedAfflictions != null && (LastDamage.Afflictions == null || LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier))))
{
continue;
}
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
if (statusEffect.OnlyPlayerTriggered)
{
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; }
@@ -3829,7 +3840,7 @@ namespace Barotrauma
if (info != null)
{
info.CauseOfDeath = CauseOfDeath;
info.ResetSavedStatValues();
info.MissionsCompletedSinceDeath = 0;
}
AnimController.movement = 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
switch (skillIdentifier)

View File

@@ -462,6 +462,9 @@ namespace Barotrauma
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
// talent-relevant values
public int MissionsCompletedSinceDeath = 0;
// 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 = "")
{
@@ -605,7 +608,10 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(personalityName))
{
personalityTrait = NPCPersonalityTrait.List.Find(p => p.Name == personalityName);
}
}
MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0);
foreach (XElement subElement in infoElement.Elements())
{
bool jobCreated = false;
@@ -973,16 +979,17 @@ namespace Barotrauma
}
float prevLevel = Job.GetSkillLevel(skillIdentifier);
Job.IncreaseSkillLevel(skillIdentifier, increase);
Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum));
float newLevel = Job.GetSkillLevel(skillIdentifier);
if ((int)newLevel > (int)prevLevel)
{
Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier);
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("ragdoll", ragdollFileName),
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
// TODO: animations?
charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath));
if (Character != null)
{
if (Character.AnimController.CurrentHull != null)
@@ -1158,6 +1166,7 @@ namespace Barotrauma
foreach (var savedStat in statValuePair.Value)
{
if (savedStat.StatValue == 0f) { continue; }
if (savedStat.RemoveAfterRound) { continue; }
savedStatElement.Add(new XElement("savedstatvalue",
new XAttribute("stattype", statValuePair.Key.ToString()),
@@ -1168,6 +1177,8 @@ namespace Barotrauma
}
}
charElement.Add(savedStatElement);
parentElement.Add(charElement);
@@ -1496,7 +1507,6 @@ namespace Barotrauma
}
}
}
public void ResetSavedStatValue(string statIdentifier)
{
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))
{
@@ -1523,12 +1533,11 @@ namespace Barotrauma
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{
savedStat.StatValue += value;
savedStat.RemoveOnDeath = removeOnDeath;
savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue);
}
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 float StatValue { 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;
RemoveOnDeath = removeOnDeath;
StatIdentifier = statIdentifier;
RemoveAfterRound = retainAfterRound;
}
}
}

View File

@@ -17,6 +17,8 @@ namespace Barotrauma
public float PendingAdditionStrength { get; set; }
public float AdditionStrength { get; set; }
private float fluctuationTimer;
protected float _strength;
[Serialize(0f, true), Editable]
@@ -56,6 +58,8 @@ namespace Barotrauma
public readonly Dictionary<AfflictionPrefab.PeriodicEffect, float> PeriodicEffectTimers = new Dictionary<AfflictionPrefab.PeriodicEffect, float>();
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
/// <summary>
/// Which character gave this affliction
/// </summary>
@@ -123,7 +127,7 @@ namespace Barotrauma
float amount = MathHelper.Lerp(
currentEffect.MinGrainStrength,
currentEffect.MaxGrainStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
{
@@ -138,12 +142,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
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(
currentEffect.MinScreenDistortStrength,
currentEffect.MaxScreenDistortStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinScreenDistort,
currentEffect.MaxScreenDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetRadialDistortStrength()
@@ -151,12 +155,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
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(
currentEffect.MinRadialDistortStrength,
currentEffect.MaxRadialDistortStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinRadialDistort,
currentEffect.MaxRadialDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetChromaticAberrationStrength()
@@ -164,12 +168,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
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(
currentEffect.MinChromaticAberrationStrength,
currentEffect.MaxChromaticAberrationStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinChromaticAberration,
currentEffect.MaxChromaticAberration,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetScreenBlurStrength()
@@ -177,12 +181,18 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
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(
currentEffect.MinScreenBlurStrength,
currentEffect.MaxScreenBlurStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinScreenBlur,
currentEffect.MaxScreenBlur,
(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()
@@ -210,14 +220,17 @@ namespace Barotrauma
}
}
public float GetResistance(string afflictionId)
public float GetResistance(AfflictionPrefab affliction)
{
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxResistance - currentEffect.MinResistance <= 0.0f) { return 0.0f; }
if (afflictionId != null && afflictionId != currentEffect.ResistanceFor) { return 0.0f; }
if (!currentEffect.ResistanceFor.Any(r =>
r.Equals(affliction.Identifier, StringComparison.OrdinalIgnoreCase) ||
r.Equals(affliction.AfflictionType, StringComparison.OrdinalIgnoreCase)))
{
return 0.0f;
}
return MathHelper.Lerp(
currentEffect.MinResistance,
currentEffect.MaxResistance,
@@ -229,8 +242,6 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 1.0f; }
if (currentEffect.MaxSpeedMultiplier - currentEffect.MinSpeedMultiplier <= 0.0f) { return 1.0f; }
return MathHelper.Lerp(
currentEffect.MinSpeedMultiplier,
currentEffect.MaxSpeedMultiplier,
@@ -282,6 +293,9 @@ namespace Barotrauma
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return; }
fluctuationTimer += deltaTime * currentEffect.ScreenEffectFluctuationFrequency;
fluctuationTimer %= 1.0f;
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
{
float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier)
@@ -290,9 +304,9 @@ namespace Barotrauma
_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.
_strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength);

View File

@@ -34,6 +34,10 @@ namespace Barotrauma
float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
float max = Math.Max(threshold, previousValue);
_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 ActiveThreshold => Prefab.MaxStrength * 0.75f;
private float DormantThreshold => (Prefab as AfflictionPrefabHusk)?.DormantThreshold ?? Prefab.MaxStrength * 0.5f;
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) { }
@@ -83,7 +89,7 @@ namespace Barotrauma
}
State = InfectionState.Transition;
}
else if (Strength < Prefab.MaxStrength)
else if (Strength < TransitionThreshold)
{
if (State != InfectionState.Active)
{

View File

@@ -97,6 +97,10 @@ namespace Barotrauma
CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true);
NeedsAir = element.GetAttributeBool("needsair", 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.
@@ -105,6 +109,8 @@ namespace Barotrauma
public readonly string AttachLimbName;
public readonly LimbType AttachLimbType;
public float ActiveThreshold, DormantThreshold, TransitionThreshold;
public readonly string HuskedSpeciesName;
public readonly string[] TargetSpecies;
public const string Tag = "[speciesname]";
@@ -141,28 +147,31 @@ namespace Barotrauma
public bool MultiplyByMaxVitality { get; private set; }
[Serialize(0.0f, false)]
public float MinScreenBlurStrength { get; private set; }
public float MinScreenBlur { get; private set; }
[Serialize(0.0f, false)]
public float MaxScreenBlurStrength { get; private set; }
public float MaxScreenBlur { get; private set; }
[Serialize(0.0f, false)]
public float MinScreenDistortStrength { get; private set; }
public float MinScreenDistort { get; private set; }
[Serialize(0.0f, false)]
public float MaxScreenDistortStrength { get; private set; }
public float MaxScreenDistort { get; private set; }
[Serialize(0.0f, false)]
public float MinRadialDistortStrength { get; private set; }
public float MinRadialDistort { get; private set; }
[Serialize(0.0f, false)]
public float MaxRadialDistortStrength { get; private set; }
public float MaxRadialDistort { get; private set; }
[Serialize(0.0f, false)]
public float MinChromaticAberrationStrength { get; private set; }
public float MinChromaticAberration { get; private set; }
[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)]
public float MinGrainStrength { get; private set; }
@@ -170,6 +179,9 @@ namespace Barotrauma
[Serialize(0.0f, false)]
public float MaxGrainStrength { get; private set; }
[Serialize(0.0f, false)]
public float ScreenEffectFluctuationFrequency { get; private set; }
[Serialize(1.0f, false)]
public float MinBuffMultiplier { get; private set; }
@@ -188,8 +200,11 @@ namespace Barotrauma
[Serialize(1.0f, false)]
public float MaxSkillMultiplier { get; private set; }
[Serialize("", false)]
public string ResistanceFor { get; private set; }
private readonly string[] resistanceFor;
public IEnumerable<string> ResistanceFor
{
get { return resistanceFor; }
}
[Serialize(0.0f, false)]
public float MinResistance { get; private set; }
@@ -209,6 +224,8 @@ namespace Barotrauma
{
SerializableProperty.DeserializeProperties(this, element);
resistanceFor = element.GetAttributeStringArray("resistancefor", new string[0], convertToLowerInvariant: true);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())

View File

@@ -266,6 +266,12 @@ namespace Barotrauma
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));
/// <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>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary>
@@ -426,18 +432,14 @@ namespace Barotrauma
}
}
public float GetResistance(string resistanceId)
public float GetResistance(AfflictionPrefab affliction)
{
float resistance = 0.0f;
for (int i = 0; i < afflictions.Count; i++)
{
if (!afflictions[i].Prefab.IsBuff) continue;
float temp = afflictions[i].GetResistance(resistanceId);
if (temp > resistance) resistance = temp;
resistance += afflictions[i].GetResistance(affliction);
}
resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(resistanceId));
return resistance;
return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction.Identifier));
}
public float GetStatValue(StatTypes statType)
@@ -451,7 +453,7 @@ namespace Barotrauma
}
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.AddRange(afflictions);
@@ -499,6 +501,17 @@ namespace Barotrauma
{
matchingAffliction.Strength -= 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();
@@ -610,7 +623,7 @@ namespace Barotrauma
{
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)
{
// 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
//or modify the affliction instance of an Attack or a StatusEffect
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);
limbHealth.Afflictions.Add(copyAffliction);
@@ -666,7 +679,7 @@ namespace Barotrauma
{
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)
{
// 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
//or modify the affliction instance of an Attack or a StatusEffect
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));
Character.HealthUpdateInterval = 0.0f;
@@ -700,8 +713,6 @@ namespace Barotrauma
}
}
partial void UpdateProjSpecific(float deltaTime);
partial void UpdateLimbAfflictionOverlays();
public void Update(float deltaTime)
@@ -741,7 +752,7 @@ namespace Barotrauma
for (int i = afflictions.Count - 1; i >= 0; i--)
{
var affliction = afflictions[i];
if (irremovableAfflictions.Contains(affliction)) continue;
if (irremovableAfflictions.Contains(affliction)) { continue; }
if (affliction.Strength <= 0.0f)
{
SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
@@ -763,6 +774,10 @@ namespace Barotrauma
{
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed));
}
else
{
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed));
}
UpdateLimbAfflictionOverlays();
@@ -786,7 +801,12 @@ namespace Barotrauma
}
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);
@@ -807,8 +827,6 @@ namespace Barotrauma
Vitality = MaxVitality;
if (Unkillable || Character.GodMode) { return; }
float damageResistanceMultiplier = 1f - GetResistance("damage");
foreach (LimbHealth limbHealth in limbHealths)
{
foreach (Affliction affliction in limbHealth.Afflictions)
@@ -824,7 +842,6 @@ namespace Barotrauma
{
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type];
}
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease);
}
@@ -833,7 +850,6 @@ namespace Barotrauma
foreach (Affliction affliction in afflictions)
{
float vitalityDecrease = affliction.GetVitalityDecrease(this);
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= 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="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>
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
//float = suitability
treatmentSuitability.Clear();
float minSuitability = -10, maxSuitability = 10;
foreach (Affliction affliction in GetAllAfflictions())
foreach (Affliction affliction in getAfflictions(limb))
{
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
@@ -990,6 +1006,18 @@ namespace Barotrauma
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>();

View File

@@ -89,11 +89,11 @@ namespace Barotrauma
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))
{
skill.Level += increase;
skill.IncreaseSkill(increase, increasePastMax);
}
else
{

View File

@@ -7,11 +7,18 @@ namespace Barotrauma
private float level;
public string Identifier { get; }
public const float MaximumSkill = 100.0f;
public float 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;

View File

@@ -868,7 +868,7 @@ namespace Barotrauma
/// </summary>
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;
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
bool wasRunning = attack.IsRunning;
@@ -971,7 +971,7 @@ namespace Barotrauma
wasHit = damageTarget != null;
}
if (wasHit)
if (wasHit || attack.HitDetectionType == HitDetection.None)
{
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.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))))
{
continue;
}
if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; }
if (statusEffect.OnlyPlayerTriggered)
{
if (character.LastAttacker == null || !character.LastAttacker.IsPlayer)

View File

@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly string itemIdentifier;
private readonly string[] tags;
private WeaponType weapontype;
private readonly WeaponType weapontype;
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
@@ -33,7 +33,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is AttackData attackData)
if (abilityData is AbilityAttackData attackData)
{
Item item = attackData?.SourceAttack?.SourceItem;
@@ -71,7 +71,7 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false;
}
}

View File

@@ -30,7 +30,7 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false;
}
}

View File

@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -22,10 +22,9 @@ namespace Barotrauma.Abilities
{
item = tempItem.Prefab;
}
// this and other instances of this type of casting will be refactored
else if (abilityData is (ItemPrefab itemPrefab, object _))
else if (abilityData is IAbilityItemPrefab abilityItemPrefab)
{
item = itemPrefab;
item = abilityItemPrefab.ItemPrefab;
}
if (item != null)

View File

@@ -15,17 +15,17 @@ namespace Barotrauma.Abilities
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;
}
else
{
LogAbilityConditionError(abilityData, typeof((Affliction, float)));
LogAbilityConditionError(abilityData, typeof(IAbilityAffliction));
return false;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -1,11 +1,10 @@
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAboveVitality : AbilityConditionDataless
{
float vitalityPercentage;
private readonly float vitalityPercentage;
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{

View File

@@ -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;
}
}
}

View File

@@ -1,8 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -24,13 +24,13 @@ namespace Barotrauma.Abilities
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
{
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue)));
LogAbilityConditionError(abilityData, typeof(IAbilityMission));
return false;
}
}

View File

@@ -1,14 +1,10 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionServerRandom : AbilityConditionDataless
{
private float randomChance = 0f;
private readonly float randomChance = 0f;
public override bool AllowClientSimulation => false;
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -12,12 +12,16 @@ namespace Barotrauma.Abilities
public CharacterTalent CharacterTalent { get; }
public Character Character { get; }
public virtual bool RequiresAlive => true;
public bool RequiresAlive { get; }
public virtual bool AllowClientSimulation => false;
public virtual bool AppliesEffectOnIntervalUpdate => false;
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>
/// Used primarily for StatusEffects. Default to constant outside interval abilities.
/// </summary>
@@ -28,6 +32,7 @@ namespace Barotrauma.Abilities
CharacterAbilityGroup = characterAbilityGroup;
CharacterTalent = characterAbilityGroup.CharacterTalent;
Character = CharacterTalent.Character;
RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true);
}
public bool IsViable()
@@ -132,11 +137,5 @@ namespace Barotrauma.Abilities
}
return flagType;
}
public static float DistanceToSquaredDistance(float distance)
{
return distance * distance;
}
}
}

View File

@@ -10,16 +10,41 @@ namespace Barotrauma.Abilities
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)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
}
protected void ApplyEffectSpecific(Character targetCharacter)
{
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)
{
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);
}
else
else
{
ApplyEffect();
ApplyEffect();
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -10,7 +9,7 @@ namespace Barotrauma.Abilities
protected float squaredMaxDistance;
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()
@@ -20,7 +19,7 @@ namespace Barotrauma.Abilities
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;
closestDistance = tempDistance;

View File

@@ -1,5 +1,6 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -16,7 +17,7 @@ namespace Barotrauma.Abilities
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);
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
@@ -26,9 +27,9 @@ namespace Barotrauma.Abilities
Character chosenCharacter = null;
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
(allowSelf ||c != Character) &&
(allowSelf || c != Character) &&
(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();
if (chosenCharacter == null) { return; }

View File

@@ -7,16 +7,41 @@ namespace Barotrauma.Abilities
{
public override bool AppliesEffectOnIntervalUpdate => true;
private int amount;
private readonly int amount;
private StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
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()
{
Character.GiveMoney(amount);
ApplyEffectSpecific(Character);
}
}
}

View File

@@ -8,8 +8,10 @@ namespace Barotrauma.Abilities
private readonly string statIdentifier;
private readonly StatTypes statType;
private readonly float value;
private readonly float maxValue;
private readonly bool targetAllies;
private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
//private readonly float maximumValue;
public override bool AppliesEffectOnIntervalUpdate => true;
@@ -19,8 +21,10 @@ namespace Barotrauma.Abilities
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue);
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
}
@@ -38,11 +42,11 @@ namespace Barotrauma.Abilities
{
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
{
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue);
}
}
}

View File

@@ -4,18 +4,23 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
private readonly string resistanceId;
private readonly float multiplier;
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
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)
{
Character.ChangeAbilityResistance(resistanceId, resistance);
Character.ChangeAbilityResistance(resistanceId, multiplier);
}
}
}

View File

@@ -4,10 +4,9 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveStat : CharacterAbility
{
private StatTypes statType;
private float value;
private readonly StatTypes statType;
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)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);

View File

@@ -7,8 +7,9 @@ namespace Barotrauma.Abilities
{
private readonly List<Affliction> afflictions;
float addedDamageMultiplier;
float addedPenetration;
private readonly float addedDamageMultiplier;
private readonly float addedPenetration;
private readonly bool implode;
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -18,11 +19,12 @@ namespace Barotrauma.Abilities
}
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
implode = abilityElement.GetAttributeBool("implode", false);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AttackData attackData)
if (abilityData is AbilityAttackData attackData)
{
if (attackData.Afflictions == null)
{
@@ -34,6 +36,13 @@ namespace Barotrauma.Abilities
}
attackData.DamageMultiplier += addedDamageMultiplier;
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
{

View File

@@ -4,8 +4,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityModifyResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
private readonly string resistanceId;
private readonly float resistance;
bool lastState;
// should probably be split to different classes
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
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)

View File

@@ -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;
}
}
}
}

View File

@@ -5,42 +5,21 @@ namespace Barotrauma.Abilities
class CharacterAbilityModifyValue : CharacterAbility
{
private float addedValue;
private float multiplierValue;
private float multiplyValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
}
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;
}
}
}

View File

@@ -5,7 +5,7 @@ namespace Barotrauma.Abilities
class CharacterAbilityResetPermanentStat : CharacterAbility
{
private readonly string statIdentifier;
public override bool RequiresAlive => false;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -12,9 +12,9 @@ namespace Barotrauma.Abilities
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);
}
}
}

View File

@@ -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++;
}
}
}
}

View File

@@ -9,45 +9,22 @@ namespace Barotrauma.Abilities
class CharacterAbilityInsurancePolicy : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public override bool RequiresAlive => false;
private readonly int moneyPerLevel;
private bool hasOccurred = false;
private readonly int moneyPerMission;
private static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0);
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
}
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());
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);
}
}
}
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
}
}
}

View File

@@ -5,14 +5,14 @@ namespace Barotrauma.Abilities
class CharacterAbilityPsychoClown : CharacterAbility
{
private StatTypes statType;
private float value;
private float maxValue;
private string afflictionIdentifier;
private float lastValue = 0f;
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
}
@@ -32,7 +32,7 @@ namespace Barotrauma.Abilities
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
}
lastValue = afflictionStrength * value;
lastValue = afflictionStrength * maxValue;
Character.ChangeStat(statType, lastValue);
}
else

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