433 lines
20 KiB
C#
433 lines
20 KiB
C#
using Barotrauma.Extensions;
|
|
using Barotrauma.Items.Components;
|
|
using FarseerPhysics;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class BrokenItemSprite
|
|
{
|
|
//sprite will be rendered if the condition of the item is below this
|
|
public readonly float MaxConditionPercentage;
|
|
public readonly Sprite Sprite;
|
|
public readonly bool FadeIn;
|
|
public readonly Point Offset;
|
|
|
|
public BrokenItemSprite(Sprite sprite, float maxCondition, bool fadeIn, Point offset)
|
|
{
|
|
Sprite = sprite;
|
|
MaxConditionPercentage = MathHelper.Clamp(maxCondition, 0.0f, 100.0f);
|
|
FadeIn = fadeIn;
|
|
Offset = offset;
|
|
}
|
|
}
|
|
|
|
class ContainedItemSprite
|
|
{
|
|
public enum DecorativeSpriteBehaviorType
|
|
{
|
|
None, HideWhenVisible, HideWhenNotVisible
|
|
}
|
|
|
|
public readonly Sprite Sprite;
|
|
public readonly bool UseWhenAttached;
|
|
public readonly DecorativeSpriteBehaviorType DecorativeSpriteBehavior;
|
|
public readonly ImmutableHashSet<Identifier> AllowedContainerIdentifiers;
|
|
public readonly ImmutableHashSet<Identifier> AllowedContainerTags;
|
|
|
|
public ContainedItemSprite(ContentXElement element, string path = "", bool lazyLoad = false)
|
|
{
|
|
Sprite = new Sprite(element, path, lazyLoad: lazyLoad);
|
|
UseWhenAttached = element.GetAttributeBool("usewhenattached", false);
|
|
Enum.TryParse(element.GetAttributeString("decorativespritebehavior", "None"), ignoreCase: true, out DecorativeSpriteBehavior);
|
|
AllowedContainerIdentifiers = element.GetAttributeIdentifierArray("allowedcontaineridentifiers", Array.Empty<Identifier>()).ToImmutableHashSet();
|
|
AllowedContainerTags = element.GetAttributeIdentifierArray("allowedcontainertags", Array.Empty<Identifier>()).ToImmutableHashSet();
|
|
}
|
|
|
|
public bool MatchesContainer(Item container)
|
|
{
|
|
if (container == null) { return false; }
|
|
return AllowedContainerIdentifiers.Contains(container.Prefab.Identifier) ||
|
|
AllowedContainerTags.Any(t => container.Prefab.Tags.Contains(t));
|
|
}
|
|
}
|
|
|
|
partial class ItemPrefab : MapEntityPrefab, IImplementsVariants<ItemPrefab>
|
|
{
|
|
public ImmutableDictionary<Identifier, ImmutableArray<DecorativeSprite>> UpgradeOverrideSprites { get; private set; }
|
|
public ImmutableArray<BrokenItemSprite> BrokenSprites { get; private set; }
|
|
public ImmutableArray<DecorativeSprite> DecorativeSprites { get; private set; }
|
|
public ImmutableArray<ContainedItemSprite> ContainedSprites { get; private set; }
|
|
public ImmutableDictionary<int, ImmutableArray<DecorativeSprite>> DecorativeSpriteGroups { get; private set; }
|
|
public Sprite InventoryIcon { get; private set; }
|
|
public Sprite MinimapIcon { get; private set; }
|
|
public Sprite UpgradePreviewSprite { get; private set; }
|
|
public Sprite InfectedSprite { get; private set; }
|
|
public Sprite DamagedInfectedSprite { get; private set; }
|
|
|
|
public float UpgradePreviewScale = 1.0f;
|
|
|
|
private IReadOnlyList<DamageModifier> wearableDamageModifiers;
|
|
private IReadOnlyDictionary<Identifier, float> wearableSkillModifiers;
|
|
|
|
//only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis
|
|
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)]
|
|
public Color InventoryIconColor { get; protected set; }
|
|
|
|
[Serialize("", IsPropertySaveable.No)]
|
|
public string ImpactSoundTag { get; private set; }
|
|
|
|
[Serialize(true, IsPropertySaveable.No)]
|
|
public bool ShowInStatusMonitor
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
private void ParseSubElementsClient(ContentXElement element, ItemPrefab variantOf)
|
|
{
|
|
UpgradePreviewSprite = null;
|
|
UpgradePreviewScale = 1f;
|
|
InventoryIcon = null;
|
|
MinimapIcon = null;
|
|
InfectedSprite = null;
|
|
DamagedInfectedSprite = null;
|
|
|
|
var upgradeOverrideSprites = new Dictionary<Identifier, List<DecorativeSprite>>();
|
|
var brokenSprites = new List<BrokenItemSprite>();
|
|
var decorativeSprites = new List<DecorativeSprite>();
|
|
var containedSprites = new List<ContainedItemSprite>();
|
|
var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
|
|
|
|
var wearableDamageModifiers = new List<DamageModifier>();
|
|
var wearableSkillModifiers = new Dictionary<Identifier, float>();
|
|
|
|
foreach (var subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.LocalName.ToLowerInvariant())
|
|
{
|
|
case "upgradeoverride":
|
|
{
|
|
|
|
var sprites = new List<DecorativeSprite>();
|
|
foreach (var decorSprite in subElement.Elements())
|
|
{
|
|
if (decorSprite.NameAsIdentifier() == "decorativesprite")
|
|
{
|
|
sprites.Add(new DecorativeSprite(decorSprite));
|
|
}
|
|
}
|
|
upgradeOverrideSprites.Add(subElement.GetAttributeIdentifier("identifier", Identifier.Empty), sprites);
|
|
break;
|
|
}
|
|
case "upgradepreviewsprite":
|
|
{
|
|
string iconFolder = GetTexturePath(subElement, variantOf);
|
|
UpgradePreviewSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
|
|
UpgradePreviewScale = subElement.GetAttributeFloat("scale", 1.0f);
|
|
}
|
|
break;
|
|
case "inventoryicon":
|
|
{
|
|
string iconFolder = GetTexturePath(subElement, variantOf);
|
|
InventoryIcon = new Sprite(subElement, iconFolder, lazyLoad: true);
|
|
}
|
|
break;
|
|
case "minimapicon":
|
|
{
|
|
string iconFolder = GetTexturePath(subElement, variantOf);
|
|
MinimapIcon = new Sprite(subElement, iconFolder, lazyLoad: true);
|
|
}
|
|
break;
|
|
case "infectedsprite":
|
|
{
|
|
string iconFolder = GetTexturePath(subElement, variantOf);
|
|
|
|
InfectedSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
|
|
}
|
|
break;
|
|
case "damagedinfectedsprite":
|
|
{
|
|
string iconFolder = GetTexturePath(subElement, variantOf);
|
|
|
|
DamagedInfectedSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
|
|
}
|
|
break;
|
|
case "brokensprite":
|
|
string brokenSpriteFolder = GetTexturePath(subElement, variantOf);
|
|
|
|
var brokenSprite = new BrokenItemSprite(
|
|
new Sprite(subElement, brokenSpriteFolder, lazyLoad: true),
|
|
subElement.GetAttributeFloat("maxcondition", 0.0f),
|
|
subElement.GetAttributeBool("fadein", false),
|
|
subElement.GetAttributePoint("offset", Point.Zero));
|
|
|
|
if (brokenSprite.FadeIn && brokenSprite.MaxConditionPercentage <= 0.0f)
|
|
{
|
|
DebugConsole.AddWarning(
|
|
$"Potential error in item {Identifier}: a broken sprite that's set to fade in despite the max condition being 0."+
|
|
" The sprite cannot fade in if it's set to only appear when the item is fully broken.",
|
|
ContentPackage);
|
|
}
|
|
|
|
int spriteIndex = 0;
|
|
for (int i = 0; i < brokenSprites.Count && brokenSprites[i].MaxConditionPercentage < brokenSprite.MaxConditionPercentage; i++)
|
|
{
|
|
spriteIndex = i;
|
|
}
|
|
brokenSprites.Insert(spriteIndex, brokenSprite);
|
|
break;
|
|
case "decorativesprite":
|
|
string decorativeSpriteFolder = GetTexturePath(subElement, variantOf);
|
|
|
|
int groupID = 0;
|
|
DecorativeSprite decorativeSprite = null;
|
|
if (subElement.GetAttribute("texture") == null)
|
|
{
|
|
groupID = subElement.GetAttributeInt("randomgroupid", 0);
|
|
}
|
|
else
|
|
{
|
|
decorativeSprite = new DecorativeSprite(subElement, decorativeSpriteFolder, lazyLoad: true);
|
|
decorativeSprites.Add(decorativeSprite);
|
|
groupID = decorativeSprite.RandomGroupID;
|
|
}
|
|
if (!decorativeSpriteGroups.ContainsKey(groupID))
|
|
{
|
|
decorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
|
|
}
|
|
decorativeSpriteGroups[groupID].Add(decorativeSprite);
|
|
|
|
break;
|
|
case "containedsprite":
|
|
string containedSpriteFolder = GetTexturePath(subElement, variantOf);
|
|
var containedSprite = new ContainedItemSprite(subElement, containedSpriteFolder, lazyLoad: true);
|
|
if (containedSprite.Sprite != null)
|
|
{
|
|
containedSprites.Add(containedSprite);
|
|
}
|
|
break;
|
|
case "wearable":
|
|
foreach (ContentXElement wearableSubElement in subElement.Elements())
|
|
{
|
|
switch (wearableSubElement.Name.LocalName.ToLowerInvariant())
|
|
{
|
|
case "damagemodifier":
|
|
wearableDamageModifiers.Add(new DamageModifier(wearableSubElement, Name.Value + ", Wearable", checkErrors: false));
|
|
break;
|
|
case "skillmodifier":
|
|
Identifier skillIdentifier = wearableSubElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
|
|
float skillValue = wearableSubElement.GetAttributeFloat("skillvalue", 0f);
|
|
if (wearableSkillModifiers.ContainsKey(skillIdentifier))
|
|
{
|
|
wearableSkillModifiers[skillIdentifier] += skillValue;
|
|
}
|
|
else
|
|
{
|
|
wearableSkillModifiers.TryAdd(skillIdentifier, skillValue);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
this.wearableDamageModifiers = wearableDamageModifiers.ToImmutableList();
|
|
this.wearableSkillModifiers = wearableSkillModifiers.ToImmutableDictionary();
|
|
|
|
UpgradeOverrideSprites = upgradeOverrideSprites.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
|
BrokenSprites = brokenSprites.ToImmutableArray();
|
|
DecorativeSprites = decorativeSprites.ToImmutableArray();
|
|
ContainedSprites = containedSprites.ToImmutableArray();
|
|
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
|
|
|
#if CLIENT
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.Prefab == this)
|
|
{
|
|
item.InitSpriteStates();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public bool CanCharacterBuy()
|
|
{
|
|
if (DefaultPrice == null) { return false; }
|
|
if (!DefaultPrice.RequiresUnlock) { return true; }
|
|
return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
|
|
}
|
|
public LocalizedString GetTooltip(Character character)
|
|
{
|
|
LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖";
|
|
if (!Description.IsNullOrEmpty())
|
|
{
|
|
tooltip += $"\n{Description}";
|
|
}
|
|
if (wearableDamageModifiers.Any() || wearableSkillModifiers.Any())
|
|
{
|
|
Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
|
|
}
|
|
if (SkillRequirementHints != null && SkillRequirementHints.Any())
|
|
{
|
|
tooltip += GetSkillRequirementHints(character);
|
|
}
|
|
return tooltip;
|
|
}
|
|
|
|
public override void UpdatePlacing(Camera cam)
|
|
{
|
|
|
|
if (PlayerInput.SecondaryMouseButtonClicked())
|
|
{
|
|
Selected = null;
|
|
return;
|
|
}
|
|
|
|
var potentialContainer = MapEntity.GetPotentialContainer(cam.ScreenToWorld(PlayerInput.MousePosition));
|
|
|
|
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
|
if (!ResizeHorizontal && !ResizeVertical)
|
|
{
|
|
if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
|
|
{
|
|
var item = new Item(new Rectangle((int)position.X, (int)position.Y, (int)(Sprite.size.X * Scale), (int)(Sprite.size.Y * Scale)), this, Submarine.MainSub)
|
|
{
|
|
Submarine = Submarine.MainSub
|
|
};
|
|
item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
|
|
item.GetComponent<Items.Components.Door>()?.RefreshLinkedGap();
|
|
item.FindHull();
|
|
item.Submarine = Submarine.MainSub;
|
|
|
|
if (PlayerInput.IsShiftDown())
|
|
{
|
|
if (potentialContainer?.OwnInventory?.TryPutItem(item, Character.Controlled) ?? false)
|
|
{
|
|
SoundPlayer.PlayUISound(GUISoundType.PickItem);
|
|
}
|
|
}
|
|
|
|
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> {item}, false));
|
|
|
|
placePosition = Vector2.Zero;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector2 placeSize = Size * Scale;
|
|
|
|
if (placePosition == Vector2.Zero)
|
|
{
|
|
if (PlayerInput.PrimaryMouseButtonHeld() && GUI.MouseOn == null) { placePosition = position; }
|
|
}
|
|
else
|
|
{
|
|
if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, Size.X); }
|
|
if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, Size.Y); }
|
|
|
|
if (PlayerInput.PrimaryMouseButtonReleased())
|
|
{
|
|
var item = new Item(new Rectangle((int)placePosition.X, (int)placePosition.Y, (int)placeSize.X, (int)placeSize.Y), this, Submarine.MainSub);
|
|
placePosition = Vector2.Zero;
|
|
|
|
item.Submarine = Submarine.MainSub;
|
|
item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
|
|
item.FindHull();
|
|
|
|
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { item }, false));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (potentialContainer != null)
|
|
{
|
|
potentialContainer.IsHighlighted = true;
|
|
}
|
|
}
|
|
|
|
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
|
|
{
|
|
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
|
|
|
if (!ResizeHorizontal && !ResizeVertical)
|
|
{
|
|
Sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y) + Sprite.size / 2.0f * Scale, SpriteColor, scale: Scale);
|
|
}
|
|
else
|
|
{
|
|
Vector2 placeSize = Size * Scale;
|
|
if (placePosition != Vector2.Zero)
|
|
{
|
|
if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); }
|
|
if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); }
|
|
position = placePosition;
|
|
}
|
|
Sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
|
|
}
|
|
}
|
|
|
|
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
|
|
{
|
|
if (!ResizeHorizontal && !ResizeVertical)
|
|
{
|
|
sprite.Draw(
|
|
spriteBatch: spriteBatch,
|
|
pos: new Vector2(placeRect.Center.X,
|
|
-(placeRect.Y - placeRect.Height / 2)),
|
|
color: SpriteColor * 0.8f,
|
|
scale: scale,
|
|
rotate: rotation,
|
|
spriteEffect: spriteEffects ^ sprite.effects);
|
|
}
|
|
else
|
|
{
|
|
Vector2 position = placeRect.Location.ToVector2();
|
|
Vector2 placeSize = placeRect.Size.ToVector2();
|
|
sprite?.DrawTiled(
|
|
spriteBatch: spriteBatch,
|
|
position: new Vector2(position.X, -position.Y),
|
|
targetSize: placeSize,
|
|
rotation: rotation,
|
|
textureScale: Vector2.One * scale,
|
|
color: SpriteColor * 0.8f,
|
|
spriteEffects: spriteEffects ^ sprite.effects);
|
|
}
|
|
}
|
|
|
|
public LocalizedString GetSkillRequirementHints(Character character)
|
|
{
|
|
LocalizedString text = "";
|
|
if (SkillRequirementHints != null && SkillRequirementHints.Any() && character != null)
|
|
{
|
|
Color orange = GUIStyle.Orange;
|
|
// Reuse an existing, localized, text because it's what we want here: "Required skills:"
|
|
text = "\n\n" + $"‖color:{orange.ToStringHex()}‖{TextManager.Get("requiredrepairskills")}‖color:end‖";
|
|
foreach (var hint in SkillRequirementHints)
|
|
{
|
|
int skillLevel = (int)character.GetSkillLevel(hint.Skill);
|
|
Color levelColor = GUIStyle.Yellow;
|
|
if (skillLevel >= hint.Level)
|
|
{
|
|
levelColor = GUIStyle.Green;
|
|
}
|
|
else if (skillLevel < hint.Level / 2)
|
|
{
|
|
levelColor = GUIStyle.Red;
|
|
}
|
|
text += "\n" + hint.GetFormattedText(skillLevel, levelColor.ToStringHex());
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
}
|
|
}
|