Server LuaCsForBarotrauma
ItemPrefab.cs
2 using Barotrauma.IO;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
8 using System.Linq;
9 using System.Security.Cryptography;
10 using System.Xml.Linq;
11 
12 namespace Barotrauma
13 {
14  readonly struct SkillRequirementHint
15  {
16  public readonly Identifier Skill;
17  public readonly float Level;
18  public readonly LocalizedString SkillName;
19 
20  public LocalizedString GetFormattedText(int skillLevel, string levelColorTag) =>
21  $"{SkillName} {Level} (‖color:{levelColorTag}‖{skillLevel}‖color:end‖)";
22 
24  {
25  Skill = element.GetAttributeIdentifier("identifier", Identifier.Empty);
26  Level = element.GetAttributeFloat("level", 0);
27  SkillName = TextManager.Get("skillname." + Skill);
28  }
29  }
30 
31  readonly struct DeconstructItem
32  {
33  public readonly Identifier ItemIdentifier;
34  //number of items to output
35  public readonly int Amount;
36  //minCondition does <= check, meaning that below or equal to min condition will be skipped.
37  public readonly float MinCondition;
38  //maxCondition does > check, meaning that above this max the deconstruct item will be skipped.
39  public readonly float MaxCondition;
40  //Condition of item on creation
41  public readonly float OutConditionMin, OutConditionMax;
42  //should the condition of the deconstructed item be copied to the output items
43  public readonly bool CopyCondition;
44 
45  //tag/identifier of the deconstructor(s) that can be used to deconstruct the item into this
46  public readonly Identifier[] RequiredDeconstructor;
47  //tag/identifier of other item(s) that that need to be present in the deconstructor to deconstruct the item into this
48  public readonly Identifier[] RequiredOtherItem;
49  //text to display on the deconstructor's activate button when this output is available
50  public readonly string ActivateButtonText;
51  public readonly string InfoText;
52  public readonly string InfoTextOnOtherItemMissing;
53 
54  public readonly float Commonness;
55 
56  public DeconstructItem(XElement element, Identifier parentDebugName)
57  {
58  ItemIdentifier = element.GetAttributeIdentifier("identifier", "");
59  Amount = element.GetAttributeInt("amount", 1);
60  MinCondition = element.GetAttributeFloat("mincondition", -0.1f);
61  MaxCondition = element.GetAttributeFloat("maxcondition", 1.0f);
62  OutConditionMin = element.GetAttributeFloat("outconditionmin", element.GetAttributeFloat("outcondition", 1.0f));
63  OutConditionMax = element.GetAttributeFloat("outconditionmax", element.GetAttributeFloat("outcondition", 1.0f));
64  CopyCondition = element.GetAttributeBool("copycondition", false);
65  Commonness = element.GetAttributeFloat("commonness", 1.0f);
66  RequiredDeconstructor = element.GetAttributeIdentifierArray("requireddeconstructor",
67  element.Parent?.GetAttributeIdentifierArray("requireddeconstructor", Array.Empty<Identifier>()) ?? Array.Empty<Identifier>());
68  RequiredOtherItem = element.GetAttributeIdentifierArray("requiredotheritem", Array.Empty<Identifier>());
69  ActivateButtonText = element.GetAttributeString("activatebuttontext", string.Empty);
70  InfoText = element.GetAttributeString("infotext", string.Empty);
71  InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty);
72  }
73 
74  public bool IsValidDeconstructor(Item deconstructor)
75  {
76  return RequiredDeconstructor.Length == 0 || RequiredDeconstructor.Any(r => deconstructor.HasTag(r) || deconstructor.Prefab.Identifier == r);
77  }
78  }
79 
81  {
82  public abstract class RequiredItem
83  {
84  public abstract IEnumerable<ItemPrefab> ItemPrefabs { get; }
85  public abstract UInt32 UintIdentifier { get; }
86 
87  public abstract bool MatchesItem(Item item);
88 
89  public abstract ItemPrefab FirstMatchingPrefab { get; }
90 
93 
94  public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader, Identifier defaultItem)
95  {
96  Amount = amount;
97  MinCondition = minCondition;
98  MaxCondition = maxCondition;
99  UseCondition = useCondition;
100  OverrideHeader = overrideHeader;
101  OverrideDescription = overrideDescription;
102  DefaultItem = defaultItem;
103  }
104  public readonly int Amount;
105  public readonly float MinCondition;
106  public readonly float MaxCondition;
107  public readonly bool UseCondition;
111  public readonly Identifier DefaultItem;
112 
113  public bool IsConditionSuitable(float conditionPercentage)
114  {
115  float normalizedCondition = conditionPercentage / 100.0f;
116  if (MathUtils.NearlyEqual(normalizedCondition, MinCondition) || MathUtils.NearlyEqual(normalizedCondition, MaxCondition))
117  {
118  return true;
119  }
120  else if (normalizedCondition >= MinCondition && normalizedCondition <= MaxCondition)
121  {
122  return true;
123  }
124  return false;
125  }
126  }
127 
129  {
130  public readonly Identifier ItemPrefabIdentifier;
131 
133  ItemPrefab.Prefabs.TryGet(ItemPrefabIdentifier, out var prefab) ? prefab
135 
136  public override UInt32 UintIdentifier { get; }
137 
138  public override IEnumerable<ItemPrefab> ItemPrefabs => ItemPrefab == null ? Enumerable.Empty<ItemPrefab>() : ItemPrefab.ToEnumerable();
139 
141 
142 
143  public override bool MatchesItem(Item item)
144  {
145  return item?.Prefab.Identifier == ItemPrefabIdentifier;
146  }
147 
148  public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader) :
149  base(amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader, defaultItem: Identifier.Empty)
150  {
151  ItemPrefabIdentifier = itemPrefab;
152  using MD5 md5 = MD5.Create();
153  UintIdentifier = ToolBoxCore.IdentifierToUint32Hash(itemPrefab, md5);
154  }
155 
156  public override string ToString()
157  {
158  return $"{base.ToString()} ({ItemPrefabIdentifier})";
159  }
160  }
161 
163  {
164  public readonly Identifier Tag;
165 
166  public override UInt32 UintIdentifier { get; }
167 
168  private readonly List<ItemPrefab> cachedPrefabs = new List<ItemPrefab>();
169 
170  private Md5Hash prevContentPackagesHash;
171 
172  public override IEnumerable<ItemPrefab> ItemPrefabs
173  {
174  get
175  {
176  if (prevContentPackagesHash == null ||
177  !prevContentPackagesHash.Equals(ContentPackageManager.EnabledPackages.MergedHash))
178  {
179  cachedPrefabs.Clear();
180  cachedPrefabs.AddRange(ItemPrefab.Prefabs.Where(p => p.Tags.Contains(Tag)));
181  prevContentPackagesHash = ContentPackageManager.EnabledPackages.MergedHash;
182  }
183  return cachedPrefabs;
184  }
185  }
186 
187  public override ItemPrefab FirstMatchingPrefab => ItemPrefabs.FirstOrDefault();
188 
189  public override bool MatchesItem(Item item)
190  {
191  if (item == null) { return false; }
192  return item.HasTag(Tag);
193  }
194 
195  public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader, Identifier defaultItem)
196  : base(amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader, defaultItem)
197  {
198  Tag = tag;
199  using MD5 md5 = MD5.Create();
200  UintIdentifier = ToolBoxCore.IdentifierToUint32Hash(tag, md5);
201  }
202 
203  public override string ToString()
204  {
205  return $"{base.ToString()} ({Tag})";
206  }
207  }
208 
209  public readonly Identifier TargetItemPrefabIdentifier;
211 
212  private readonly Lazy<LocalizedString> displayName;
214  => ItemPrefab.Prefabs.ContainsKey(TargetItemPrefabIdentifier) ? displayName.Value : "";
215  public readonly ImmutableArray<RequiredItem> RequiredItems;
216  public readonly ImmutableArray<Identifier> SuitableFabricatorIdentifiers;
217  public readonly float RequiredTime;
218  public readonly int RequiredMoney;
219  public readonly bool RequiresRecipe;
220  public readonly float OutCondition; //Percentage-based from 0 to 1
221  public readonly ImmutableArray<Skill> RequiredSkills;
222  public readonly uint RecipeHash;
223  public readonly int Amount;
224  public readonly int? Quality;
225  public readonly bool HideForNonTraitors;
226 
230  public readonly int FabricationLimitMin, FabricationLimitMax;
231 
232  public FabricationRecipe(ContentXElement element, Identifier itemPrefab)
233  {
234  TargetItemPrefabIdentifier = itemPrefab;
235  var displayNameIdentifier = element.GetAttributeIdentifier("displayname", "");
236  displayName = new Lazy<LocalizedString>(() => displayNameIdentifier.IsEmpty
237  ? TargetItem.Name
238  : TextManager.GetWithVariable($"DisplayName.{displayNameIdentifier}", "[itemname]", TargetItem.Name));
239 
240  SuitableFabricatorIdentifiers = element.GetAttributeIdentifierArray("suitablefabricators", Array.Empty<Identifier>()).ToImmutableArray();
241 
242  var requiredSkills = new List<Skill>();
243  RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
244  RequiredMoney = element.GetAttributeInt("requiredmoney", 0);
245  OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
246  if (OutCondition > 1.0f)
247  {
248  DebugConsole.AddWarning($"Error in \"{itemPrefab}\"'s fabrication recipe: out condition is above 100% ({OutCondition * 100}).",
249  element.ContentPackage);
250  }
251  var requiredItems = new List<RequiredItem>();
252  RequiresRecipe = element.GetAttributeBool("requiresrecipe", false);
253  Amount = element.GetAttributeInt("amount", 1);
254 
255  int limitDefault = element.GetAttributeInt("fabricationlimit", -1);
256  FabricationLimitMin = element.GetAttributeInt(nameof(FabricationLimitMin), limitDefault);
257  FabricationLimitMax = element.GetAttributeInt(nameof(FabricationLimitMax), limitDefault);
258 
259  HideForNonTraitors = element.GetAttributeBool(nameof(HideForNonTraitors), false);
260 
261  if (element.GetAttribute(nameof(Quality)) != null)
262  {
263  Quality = element.GetAttributeInt(nameof(Quality), 0);
264  }
265 
266  foreach (var subElement in element.Elements())
267  {
268  switch (subElement.Name.ToString().ToLowerInvariant())
269  {
270  case "requiredskill":
271  if (subElement.GetAttribute("name") != null)
272  {
273  DebugConsole.ThrowError("Error in fabricable item " + itemPrefab + "! Use skill identifiers instead of names.",
274  contentPackage: element.ContentPackage);
275  continue;
276  }
277 
278  requiredSkills.Add(new Skill(
279  subElement.GetAttributeIdentifier("identifier", ""),
280  subElement.GetAttributeInt("level", 0)));
281  break;
282  case "item":
283  case "requireditem":
284  Identifier requiredItemIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
285  Identifier requiredItemTag = subElement.GetAttributeIdentifier("tag", Identifier.Empty);
286  if (requiredItemIdentifier == Identifier.Empty && requiredItemTag == Identifier.Empty)
287  {
288  DebugConsole.ThrowError("Error in fabricable item " + itemPrefab + "! One of the required items has no identifier or tag.",
289  contentPackage: element.ContentPackage);
290  continue;
291  }
292 
293  float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
294  float maxCondition = subElement.GetAttributeFloat("maxcondition", 1.0f);
295  //Substract mincondition from required item's condition or delete it regardless?
296  bool useCondition = subElement.GetAttributeBool("usecondition", true);
297  int amount = subElement.GetAttributeInt("count", subElement.GetAttributeInt("amount", 1));
298 
299  LocalizedString overrideDescription = string.Empty;
300  if (subElement.GetAttributeString("description", string.Empty) is string descriptionTag && !descriptionTag.IsNullOrEmpty())
301  {
302  overrideDescription = TextManager.Get(descriptionTag);
303  }
304  LocalizedString overrideHeader = string.Empty;
305  if (subElement.GetAttributeString("header", string.Empty) is string headerTag && !headerTag.IsNullOrEmpty())
306  {
307  overrideHeader = TextManager.Get(headerTag);
308  }
309 
310  if (requiredItemIdentifier != Identifier.Empty)
311  {
312  var existing = requiredItems.FindIndex(r =>
313  r is RequiredItemByIdentifier ri &&
314  ri.ItemPrefabIdentifier == requiredItemIdentifier &&
315  MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
316  MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
317  if (existing >= 0)
318  {
319  amount += requiredItems[existing].Amount;
320  requiredItems.RemoveAt(existing);
321  }
322  requiredItems.Add(new RequiredItemByIdentifier(requiredItemIdentifier, amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader));
323  }
324  else
325  {
326  var existing = requiredItems.FindIndex(r =>
327  r is RequiredItemByTag rt &&
328  rt.Tag == requiredItemTag &&
329  MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
330  MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
331  if (existing >= 0)
332  {
333  amount += requiredItems[existing].Amount;
334  requiredItems.RemoveAt(existing);
335  }
336  Identifier defaultItem = subElement.GetAttributeIdentifier("defaultitem", Identifier.Empty);
337  requiredItems.Add(new RequiredItemByTag(requiredItemTag, amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader, defaultItem));
338  }
339  break;
340  }
341  }
342 
343  RequiredSkills = requiredSkills.ToImmutableArray();
344  RequiredItems = requiredItems
345  /*Put the items required by identifier first - since we must use specific items for those, we should check them before the ones that accept multiple items.
346  Otherwise we might end up choosing the "specific item" as the multi-option ingredient, and not have enough left for the "specific item" requirement */
347  .OrderBy(requiredItem => requiredItem is RequiredItemByIdentifier ? 0 : 1)
348  .ToImmutableArray();
349 
350  RecipeHash = GenerateHash();
351  }
352 
353  private uint GenerateHash()
354  {
355  using var md5 = MD5.Create();
356  uint outputId = ToolBoxCore.IdentifierToUint32Hash(TargetItemPrefabIdentifier, md5);
357 
358  var requiredItems = string.Join(':', RequiredItems
359  .Select(static i => $"{i.UintIdentifier}:{i.Amount}")
360  .Select(static i => string.Join(',', i)));
361  // above but include the item amount
362 
363  var requiredSkills = string.Join(':', RequiredSkills.Select(s => $"{s.Identifier}:{s.Level}"));
364 
365  uint retVal = ToolBoxCore.StringToUInt32Hash($"{Amount}|{outputId}|{RequiredTime}|{RequiresRecipe}|{requiredItems}|{requiredSkills}", md5);
366  if (retVal == 0) { retVal = 1; }
367  return retVal;
368  }
369  }
370 
372  {
373  public readonly ImmutableHashSet<Identifier> Primary;
374  public readonly ImmutableHashSet<Identifier> Secondary;
375 
376  public readonly float SpawnProbability;
377  public readonly float MaxCondition;
378  public readonly float MinCondition;
379  public readonly int MinAmount;
380  public readonly int MaxAmount;
381  // Overrides min and max, if defined.
382  public readonly int Amount;
383  public readonly bool CampaignOnly;
384  public readonly bool NotCampaign;
385  public readonly bool NotPvP;
386  public readonly bool TransferOnlyOnePerContainer;
387  public readonly bool AllowTransfersHere = true;
388 
389  public readonly float MinLevelDifficulty, MaxLevelDifficulty;
390 
391  public PreferredContainer(XElement element)
392  {
393  Primary = XMLExtensions.GetAttributeIdentifierArray(element, "primary", Array.Empty<Identifier>()).ToImmutableHashSet();
394  Secondary = XMLExtensions.GetAttributeIdentifierArray(element, "secondary", Array.Empty<Identifier>()).ToImmutableHashSet();
395  SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
396  MinAmount = element.GetAttributeInt("minamount", 0);
397  MaxAmount = Math.Max(MinAmount, element.GetAttributeInt("maxamount", 0));
398  Amount = element.GetAttributeInt("amount", 0);
399  MaxCondition = element.GetAttributeFloat("maxcondition", 100f);
400  MinCondition = element.GetAttributeFloat("mincondition", 0f);
401  CampaignOnly = element.GetAttributeBool("campaignonly", CampaignOnly);
402  NotCampaign = element.GetAttributeBool("notcampaign", NotCampaign);
403  NotPvP = element.GetAttributeBool("notpvp", NotPvP);
404  TransferOnlyOnePerContainer = element.GetAttributeBool("TransferOnlyOnePerContainer", TransferOnlyOnePerContainer);
405  AllowTransfersHere = element.GetAttributeBool("AllowTransfersHere", AllowTransfersHere);
406 
407  MinLevelDifficulty = element.GetAttributeFloat(nameof(MinLevelDifficulty), float.MinValue);
408  MaxLevelDifficulty = element.GetAttributeFloat(nameof(MaxLevelDifficulty), float.MaxValue);
409 
410  if (element.GetAttribute("spawnprobability") == null)
411  {
412  //if spawn probability is not defined but amount is, assume the probability is 1
413  if (MaxAmount > 0 || Amount > 0)
414  {
415  SpawnProbability = 1.0f;
416  }
417  }
418  else if (element.GetAttribute("minamount") == null && element.GetAttribute("maxamount") == null && element.GetAttribute("amount") == null)
419  {
420  //spawn probability defined but amount isn't, assume amount is 1
421  MinAmount = MaxAmount = Amount = 1;
422  SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
423  }
424  }
425  }
426 
428  {
429  public int BasePrice { get; }
430 
431  public readonly bool CanBeBought;
432 
433  public readonly Identifier ReplacementOnUninstall;
434 
435  public string SpawnWithId;
436 
437  public string SwapIdentifier;
438 
439  public readonly Vector2 SwapOrigin;
440 
441  public List<(Identifier requiredTag, Identifier swapTo)> ConnectedItemsToSwap = new List<(Identifier requiredTag, Identifier swapTo)>();
442 
443  public readonly Sprite SchematicSprite;
444 
445  public int GetPrice(Location location = null)
446  {
447  int price = location?.GetAdjustedMechanicalCost(BasePrice) ?? BasePrice;
448  if (GameMain.GameSession?.Campaign is CampaignMode campaign)
449  {
450  price = (int)(price * campaign.Settings.ShipyardPriceMultiplier);
451  }
452  return price;
453  }
454 
456  {
457  BasePrice = Math.Max(element.GetAttributeInt("price", 0), 0);
458  SwapIdentifier = element.GetAttributeString("swapidentifier", string.Empty);
459  CanBeBought = element.GetAttributeBool("canbebought", BasePrice != 0);
460  ReplacementOnUninstall = element.GetAttributeIdentifier("replacementonuninstall", "");
461  SwapOrigin = element.GetAttributeVector2("origin", Vector2.One);
462  SpawnWithId = element.GetAttributeString("spawnwithid", string.Empty);
463 
464  foreach (var subElement in element.Elements())
465  {
466  switch (subElement.Name.ToString().ToLowerInvariant())
467  {
468  case "schematicsprite":
469  SchematicSprite = new Sprite(subElement);
470  break;
471  case "swapconnecteditem":
473  (subElement.GetAttributeIdentifier("tag", ""),
474  subElement.GetAttributeIdentifier("swapto", "")));
475  break;
476  }
477  }
478  }
479  }
480 
481  partial class ItemPrefab : MapEntityPrefab, IImplementsVariants<ItemPrefab>
482  {
484 
485  public const float DefaultInteractDistance = 120.0f;
486 
487  //default size
488  public Vector2 Size { get; private set; }
489 
490  private PriceInfo defaultPrice;
491  public PriceInfo DefaultPrice => defaultPrice;
492  private ImmutableDictionary<Identifier, PriceInfo> StorePrices { get; set; }
493  public bool CanBeBought => (DefaultPrice != null && DefaultPrice.CanBeBought) ||
494  (StorePrices != null && StorePrices.Any(p => p.Value.CanBeBought));
498  public bool CanBeSold => DefaultPrice != null;
499 
504  public ImmutableArray<Rectangle> Triggers { get; private set; }
505 
506  private ImmutableDictionary<Identifier, float> treatmentSuitability;
507 
511  public bool IsOverride => Prefabs.IsOverride(this);
512 
513  private readonly ContentXElement originalElement;
514  public ContentXElement ConfigElement { get; private set; }
515 
516  public ImmutableArray<DeconstructItem> DeconstructItems { get; private set; }
517 
518  public ImmutableDictionary<uint, FabricationRecipe> FabricationRecipes { get; private set; }
519 
520  public float DeconstructTime { get; private set; }
521 
522  public bool AllowDeconstruct { get; private set; }
523 
524  //Containers (by identifiers or tags) that this item should be placed in. These are preferences, which are not enforced.
525  public ImmutableArray<PreferredContainer> PreferredContainers { get; private set; }
526 
527  public ImmutableArray<SkillRequirementHint> SkillRequirementHints { get; private set; }
528 
530  {
531  get;
532  private set;
533  }
534 
535  public readonly struct CommonnessInfo
536  {
537  public float Commonness
538  {
539  get
540  {
541  return commonness;
542  }
543  }
544  public float AbyssCommonness
545  {
546  get
547  {
548  return abyssCommonness ?? 0.0f;
549  }
550  }
551  public float CaveCommonness
552  {
553  get
554  {
555  return caveCommonness ?? Commonness;
556  }
557  }
558  public bool CanAppear
559  {
560  get
561  {
562  if (Commonness > 0.0f) { return true; }
563  if (AbyssCommonness > 0.0f) { return true; }
564  if (CaveCommonness > 0.0f) { return true; }
565  return false;
566  }
567  }
568 
569  public readonly float commonness;
570  public readonly float? abyssCommonness;
571  public readonly float? caveCommonness;
572 
573  public CommonnessInfo(XElement element)
574  {
575  this.commonness = Math.Max(element?.GetAttributeFloat("commonness", 0.0f) ?? 0.0f, 0.0f);
576 
577  float? abyssCommonness = null;
578  XAttribute abyssCommonnessAttribute = element?.GetAttribute("abysscommonness") ?? element?.GetAttribute("abyss");
579  if (abyssCommonnessAttribute != null)
580  {
581  abyssCommonness = Math.Max(abyssCommonnessAttribute.GetAttributeFloat(0.0f), 0.0f);
582  }
583  this.abyssCommonness = abyssCommonness;
584 
585  float? caveCommonness = null;
586  XAttribute caveCommonnessAttribute = element?.GetAttribute("cavecommonness") ?? element?.GetAttribute("cave");
587  if (caveCommonnessAttribute != null)
588  {
589  caveCommonness = Math.Max(caveCommonnessAttribute.GetAttributeFloat(0.0f), 0.0f);
590  }
591  this.caveCommonness = caveCommonness;
592  }
593 
595  {
596  this.commonness = commonness;
597  this.abyssCommonness = abyssCommonness != null ? (float?)Math.Max(abyssCommonness.Value, 0.0f) : null;
598  this.caveCommonness = caveCommonness != null ? (float?)Math.Max(caveCommonness.Value, 0.0f) : null;
599  }
600 
602  {
603  return new CommonnessInfo(commonness,
604  abyssCommonness ?? parentInfo?.abyssCommonness,
605  caveCommonness ?? parentInfo?.caveCommonness);
606  }
607 
609  {
610  CommonnessInfo info = this;
611  foreach (var parentInfo in parentInfos)
612  {
613  info = info.WithInheritedCommonness(parentInfo);
614  }
615  return info;
616  }
617 
618  public float GetCommonness(Level.TunnelType tunnelType)
619  {
620  if (tunnelType == Level.TunnelType.Cave)
621  {
622  return CaveCommonness;
623  }
624  else
625  {
626  return Commonness;
627  }
628  }
629  }
630 
634  private ImmutableDictionary<Identifier, CommonnessInfo> LevelCommonness { get; set; }
635 
636  public readonly struct FixedQuantityResourceInfo
637  {
638  public readonly int ClusterQuantity;
639  public readonly int ClusterSize;
640  public readonly bool IsIslandSpecific;
641  public readonly bool AllowAtStart;
642 
643  public FixedQuantityResourceInfo(int clusterQuantity, int clusterSize, bool isIslandSpecific, bool allowAtStart)
644  {
645  ClusterQuantity = clusterQuantity;
646  ClusterSize = clusterSize;
647  IsIslandSpecific = isIslandSpecific;
648  AllowAtStart = allowAtStart;
649  }
650  }
651 
652  public ImmutableDictionary<Identifier, FixedQuantityResourceInfo> LevelQuantity { get; private set; }
653 
654  private bool canSpriteFlipX;
655  public override bool CanSpriteFlipX => canSpriteFlipX;
656 
657  private bool canSpriteFlipY;
658  public override bool CanSpriteFlipY => canSpriteFlipY;
659 
663  public bool? AllowAsExtraCargo { get; private set; }
664 
665  public bool RandomDeconstructionOutput { get; private set; }
666 
667  public int RandomDeconstructionOutputAmount { get; private set; }
668 
669  private Sprite sprite;
670  public override Sprite Sprite => sprite;
671 
672  public override string OriginalName { get; }
673 
674  private LocalizedString name;
675  public override LocalizedString Name => name;
676 
677  private ImmutableHashSet<Identifier> tags;
678  public override ImmutableHashSet<Identifier> Tags => tags;
679 
680  private ImmutableHashSet<Identifier> allowedLinks;
681  public override ImmutableHashSet<Identifier> AllowedLinks => allowedLinks;
682 
683  private MapEntityCategory category;
684  public override MapEntityCategory Category => category;
685 
686  private ImmutableHashSet<string> aliases;
687  public override ImmutableHashSet<string> Aliases => aliases;
688 
689  //how close the Character has to be to the item to pick it up
691  public float InteractDistance { get; private set; }
692 
693  // this can be used to allow items which are behind other items tp
694  [Serialize(0.0f, IsPropertySaveable.No)]
695  public float InteractPriority { get; private set; }
696 
697  [Serialize(false, IsPropertySaveable.No)]
698  public bool InteractThroughWalls { get; private set; }
699 
700  [Serialize(false, IsPropertySaveable.No, description: "Hides the condition bar displayed at the bottom of the inventory slot the item is in.")]
701  public bool HideConditionBar { get; set; }
702 
703  [Serialize(false, IsPropertySaveable.No, description: "Hides the condition displayed in the item's tooltip.")]
704  public bool HideConditionInTooltip { get; set; }
705 
706  //if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item
707  //if false, trigger areas define areas that can be used to highlight the item
708  [Serialize(true, IsPropertySaveable.No)]
709  public bool RequireBodyInsideTrigger { get; private set; }
710 
711  //if true and the item has trigger areas defined, players can only highlight the item when the cursor is on the trigger
712  [Serialize(false, IsPropertySaveable.No)]
713  public bool RequireCursorInsideTrigger { get; private set; }
714 
715  //if true then players can only highlight the item if its targeted for interaction by a campaign event
716  [Serialize(false, IsPropertySaveable.No)]
718  {
719  get;
720  private set;
721  }
722 
723  //should the camera focus on the item when selected
724  [Serialize(false, IsPropertySaveable.No)]
725  public bool FocusOnSelected { get; private set; }
726 
727  //the amount of "camera offset" when selecting the construction
728  [Serialize(0.0f, IsPropertySaveable.No)]
729  public float OffsetOnSelected { get; private set; }
730 
731  [Serialize(false, IsPropertySaveable.No, description: "Should the character who's selected the item grab it (hold their hand on it, the same way as they do when repairing)? Defaults to true on items that have an ItemContainer component.")]
732  public bool GrabWhenSelected { get; set; }
733 
734  [Serialize(true, IsPropertySaveable.No, description: "Are AI characters allowed to deselect the item when they're idling (and wander off?).")]
735  public bool AllowDeselectWhenIdling { get; private set; }
736 
737  private float health;
738 
739  [Serialize(100.0f, IsPropertySaveable.No)]
740  public float Health
741  {
742  get { return health; }
743  private set
744  {
745  //don't allow health values higher than this, because they lead to various issues:
746  //e.g. integer overflows when we're casting to int to display a health value, value being set to float.Infinity if it's high enough
747  health = Math.Min(value, 1000000.0f);
748  }
749  }
750 
751  [Serialize(false, IsPropertySaveable.No)]
752  public bool AllowSellingWhenBroken { get; private set; }
753 
754  [Serialize(false, IsPropertySaveable.No)]
755  public bool AllowStealingAlways { get; private set; }
756 
757  [Serialize(false, IsPropertySaveable.No)]
758  public bool Indestructible { get; private set; }
759 
760  [Serialize(false, IsPropertySaveable.No)]
761  public bool DamagedByExplosions { get; private set; }
762 
763  [Serialize(false, IsPropertySaveable.No)]
764  public bool DamagedByContainedItemExplosions { get; private set; }
765 
766  [Serialize(1f, IsPropertySaveable.No)]
767  public float ExplosionDamageMultiplier { get; private set; }
768 
769  [Serialize(1f, IsPropertySaveable.No)]
770  public float ItemDamageMultiplier { get; private set; }
771 
772  [Serialize(false, IsPropertySaveable.No)]
773  public bool DamagedByProjectiles { get; private set; }
774 
775  [Serialize(false, IsPropertySaveable.No)]
776  public bool DamagedByMeleeWeapons { get; private set; }
777 
778  [Serialize(false, IsPropertySaveable.No)]
779  public bool DamagedByRepairTools { get; private set; }
780 
781  [Serialize(false, IsPropertySaveable.No)]
782  public bool DamagedByMonsters { get; private set; }
783 
784  private float impactTolerance;
785  [Serialize(0.0f, IsPropertySaveable.No)]
786  public float ImpactTolerance
787  {
788  get { return impactTolerance; }
789  set { impactTolerance = Math.Max(value, 0.0f); }
790  }
791 
792  [Serialize(0.0f, IsPropertySaveable.No)]
793  public float OnDamagedThreshold { get; set; }
794 
795  [Serialize(0.0f, IsPropertySaveable.No)]
796  public float SonarSize
797  {
798  get;
799  private set;
800  }
801 
802  [Serialize(false, IsPropertySaveable.No)]
803  public bool UseInHealthInterface { get; private set; }
804 
805  [Serialize(false, IsPropertySaveable.No)]
806  public bool DisableItemUsageWhenSelected { get; private set; }
807 
808  [Serialize("metalcrate", IsPropertySaveable.No)]
809  public string CargoContainerIdentifier { get; private set; }
810 
811  [Serialize(false, IsPropertySaveable.No)]
812  public bool UseContainedSpriteColor { get; private set; }
813 
814  [Serialize(false, IsPropertySaveable.No)]
815  public bool UseContainedInventoryIconColor { get; private set; }
816 
817  [Serialize(0.0f, IsPropertySaveable.No)]
819  {
820  get;
821  private set;
822  }
823 
824  [Serialize(0.0f, IsPropertySaveable.No)]
826  {
827  get;
828  private set;
829  }
830 
831  [Serialize(false, IsPropertySaveable.No)]
832  public bool CannotRepairFail
833  {
834  get;
835  private set;
836  }
837 
838  [Serialize(null, IsPropertySaveable.No)]
839  public string EquipConfirmationText { get; set; }
840 
841  [Serialize(true, IsPropertySaveable.No, description: "Can the item be rotated in the submarine editor?")]
842  public bool AllowRotatingInEditor { get; set; }
843 
844  [Serialize(false, IsPropertySaveable.No)]
845  public bool ShowContentsInTooltip { get; private set; }
846 
847  [Serialize(true, IsPropertySaveable.No)]
848  public bool CanFlipX { get; private set; }
849 
850  [Serialize(true, IsPropertySaveable.No)]
851  public bool CanFlipY { get; private set; }
852 
853  [Serialize(0.01f, IsPropertySaveable.No)]
854  public float MinScale { get; private set; }
855 
856  [Serialize(10.0f, IsPropertySaveable.No)]
857  public float MaxScale { get; private set; }
858 
859  [Serialize(false, IsPropertySaveable.No)]
860  public bool IsDangerous { get; private set; }
861 
862  private int maxStackSize;
864  public int MaxStackSize
865  {
866  get { return maxStackSize; }
867  private set { maxStackSize = MathHelper.Clamp(value, 1, Inventory.MaxPossibleStackSize); }
868  }
869 
870  private int maxStackSizeCharacterInventory;
871  [Serialize(-1, IsPropertySaveable.No, description: "Maximum stack size when the item is in a character inventory.")]
873  {
874  get { return maxStackSizeCharacterInventory; }
875  private set { maxStackSizeCharacterInventory = Math.Min(value, Inventory.MaxPossibleStackSize); }
876  }
877 
878  private int maxStackSizeHoldableOrWearableInventory;
879  [Serialize(-1, IsPropertySaveable.No, description:
880  "Maximum stack size when the item is inside a holdable or wearable item. "+
881  "If not set, defaults to MaxStackSizeCharacterInventory.")]
883  {
884  get { return maxStackSizeHoldableOrWearableInventory; }
885  private set { maxStackSizeHoldableOrWearableInventory = Math.Min(value, Inventory.MaxPossibleStackSize); }
886  }
887 
888  public int GetMaxStackSize(Inventory inventory)
889  {
890  int extraStackSize = inventory switch
891  {
892  ItemInventory { Owner: Item it } i => (int)it.StatManager.GetAdjustedValueAdditive(ItemTalentStats.ExtraStackSize, i.ExtraStackSize),
893  CharacterInventory { Owner: Character { Info: { } info } } i =>
894  i.ExtraStackSize + EnumExtensions.GetIndividualFlags(Category).Sum(c => (int)info.GetSavedStatValueWithAll(StatTypes.InventoryExtraStackSize, c.ToIdentifier())),
895  not null => inventory.ExtraStackSize,
896  null => 0
897  };
898 
899  if (inventory is CharacterInventory && maxStackSizeCharacterInventory > 0)
900  {
901  return MaxStackWithExtra(maxStackSizeCharacterInventory, extraStackSize);
902  }
903  else if (inventory?.Owner is Item item &&
904  (item.GetComponent<Holdable>() is { Attachable: false } || item.GetComponent<Wearable>() != null))
905  {
906  if (maxStackSizeHoldableOrWearableInventory > 0)
907  {
908  return MaxStackWithExtra(maxStackSizeHoldableOrWearableInventory, extraStackSize);
909  }
910  else if (maxStackSizeCharacterInventory > 0)
911  {
912  //if maxStackSizeHoldableOrWearableInventory is not set, it defaults to maxStackSizeCharacterInventory
913  return MaxStackWithExtra(maxStackSizeCharacterInventory, extraStackSize);
914  }
915  }
916 
917  return MaxStackWithExtra(maxStackSize, extraStackSize);
918 
919  static int MaxStackWithExtra(int maxStackSize, int extraStackSize)
920  {
921  extraStackSize = Math.Max(extraStackSize, 0);
922  if (maxStackSize == 1)
923  {
924  return Math.Min(maxStackSize, Inventory.MaxPossibleStackSize);
925  }
926  return Math.Min(maxStackSize + extraStackSize, Inventory.MaxPossibleStackSize);
927  }
928  }
929 
930  [Serialize(false, IsPropertySaveable.No)]
931  public bool AllowDroppingOnSwap { get; private set; }
932 
933  public ImmutableHashSet<Identifier> AllowDroppingOnSwapWith { get; private set; }
934 
935  [Serialize(false, IsPropertySaveable.No, "If enabled, the item is not transferred when the player transfers items between subs.")]
936  public bool DontTransferBetweenSubs { get; private set; }
937 
938  [Serialize(true, IsPropertySaveable.No)]
939  public bool ShowHealthBar { get; private set; }
940 
941  [Serialize(1f, IsPropertySaveable.No, description: "How much the bots prioritize this item when they seek for items. For example, bots prioritize less exosuit than the other diving suits. Defaults to 1. Note that there's also a specific CombatPriority for items that can be used as weapons.")]
942  public float BotPriority { get; private set; }
943 
944  [Serialize(true, IsPropertySaveable.No)]
945  public bool ShowNameInHealthBar { get; private set; }
946 
947  [Serialize(false, IsPropertySaveable.No, description:"Should the bots shoot at this item with turret or not? Disabled by default.")]
948  public bool IsAITurretTarget { get; private set; }
949 
950  [Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with turrets? Defaults to 1. Distance to the target affects the decision making.")]
951  public float AITurretPriority { get; private set; }
952 
953  [Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with slow turrets, like railguns? Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making.")]
954  public float AISlowTurretPriority { get; private set; }
955 
956  [Serialize(float.PositiveInfinity, IsPropertySaveable.No, description: "The max distance at which the bots are allowed to target the items. Defaults to infinity.")]
957  public float AITurretTargetingMaxDistance { get; private set; }
958 
959  [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, taking items from this container is never considered stealing.")]
960  public bool AllowStealingContainedItems { get; private set; }
961 
962  [Serialize("255,255,255,255", IsPropertySaveable.No, description: "Used in circuit box to set the color of the nodes.")]
963  public Color SignalComponentColor { get; private set; }
964 
965  [Serialize(false, IsPropertySaveable.No, description: "If enabled, the player is unable to open the middle click menu when this item is selected.")]
966  public bool DisableCommandMenuWhenSelected { get; set; }
967 
968  protected override Identifier DetermineIdentifier(XElement element)
969  {
970  Identifier identifier = base.DetermineIdentifier(element);
971  string originalName = element.GetAttributeString("name", "");
972  if (identifier.IsEmpty && !string.IsNullOrEmpty(originalName))
973  {
974  string categoryStr = element.GetAttributeString("category", "Misc");
975  if (Enum.TryParse(categoryStr, true, out MapEntityCategory category) && category.HasFlag(MapEntityCategory.Legacy))
976  {
977  identifier = GenerateLegacyIdentifier(originalName);
978  }
979  }
980  return identifier;
981  }
982 
983  public static Identifier GenerateLegacyIdentifier(string name)
984  {
985  return ($"legacyitem_{name.Replace(" ", "")}").ToIdentifier();
986  }
987 
988  public ItemPrefab(ContentXElement element, ItemFile file) : base(element, file)
989  {
990  originalElement = element;
991  ConfigElement = element;
992 
993  OriginalName = element.GetAttributeString("name", "");
994  name = OriginalName;
995 
996  VariantOf = element.VariantOf();
997 
998  if (!VariantOf.IsEmpty) { return; } //don't even attempt to read the XML until the PrefabCollection readies up the parent to inherit from
999 
1000  ParseConfigElement(variantOf: null);
1001  }
1002 
1003  private string GetTexturePath(ContentXElement subElement, ItemPrefab variantOf)
1004  => subElement.DoesAttributeReferenceFileNameAlone("texture")
1005  ? Path.GetDirectoryName(variantOf?.ContentFile.Path ?? ContentFile.Path)
1006  : "";
1007 
1008  private void ParseConfigElement(ItemPrefab variantOf)
1009  {
1010  string categoryStr = ConfigElement.GetAttributeString("category", "Misc");
1011  this.category = Enum.TryParse(categoryStr, true, out MapEntityCategory category)
1012  ? category
1013  : MapEntityCategory.Misc;
1014 
1015  //nameidentifier can be used to make multiple items use the same names and descriptions
1016  Identifier nameIdentifier = ConfigElement.GetAttributeIdentifier("nameidentifier", "");
1017 
1018  //only used if the item doesn't have a name/description defined in the currently selected language
1019  string fallbackNameIdentifier = ConfigElement.GetAttributeString("fallbacknameidentifier", "");
1020 
1021  name = TextManager.Get(nameIdentifier.IsEmpty
1022  ? $"EntityName.{Identifier}"
1023  : $"EntityName.{nameIdentifier}",
1024  $"EntityName.{fallbackNameIdentifier}");
1025  if (!string.IsNullOrEmpty(OriginalName))
1026  {
1027  name = name.Fallback(OriginalName);
1028  }
1029 
1030  if (category == MapEntityCategory.Wrecked)
1031  {
1032  name = TextManager.GetWithVariable("wreckeditemformat", "[name]", name);
1033  }
1034 
1036 
1037  this.aliases =
1038  (ConfigElement.GetAttributeStringArray("aliases", null, convertToLowerInvariant: true) ??
1039  ConfigElement.GetAttributeStringArray("Aliases", Array.Empty<string>(), convertToLowerInvariant: true))
1040  .ToImmutableHashSet()
1041  .Add(OriginalName.ToLowerInvariant());
1042 
1043  var triggers = new List<Rectangle>();
1044  var deconstructItems = new List<DeconstructItem>();
1045  var fabricationRecipes = new Dictionary<uint, FabricationRecipe>();
1046  var treatmentSuitability = new Dictionary<Identifier, float>();
1047  var storePrices = new Dictionary<Identifier, PriceInfo>();
1048  var preferredContainers = new List<PreferredContainer>();
1049  DeconstructTime = 1.0f;
1050 
1051  if (ConfigElement.GetAttribute("allowasextracargo") != null)
1052  {
1053  AllowAsExtraCargo = ConfigElement.GetAttributeBool("allowasextracargo", false);
1054  }
1055 
1056  List<Identifier> tags = ConfigElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToList();
1057  //this was previously handled in ItemComponent, moved here to make it part of the immutable tags of the item
1058  if (ConfigElement.Descendants().Any(e => e.NameAsIdentifier() == "lightcomponent"))
1059  {
1060  tags.Add("light".ToIdentifier());
1061  }
1062  this.tags = tags.ToImmutableHashSet();
1063 
1064  if (ConfigElement.GetAttribute("cargocontainername") != null)
1065  {
1066  DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - cargo container should be configured using the item's identifier, not the name.",
1067  contentPackage: ConfigElement.ContentPackage);
1068  }
1069 
1070  SerializableProperty.DeserializeProperties(this, ConfigElement);
1071 
1073  var skillRequirementHints = new List<SkillRequirementHint>();
1074  foreach (var skillRequirementHintElement in ConfigElement.GetChildElements("SkillRequirementHint"))
1075  {
1076  skillRequirementHints.Add(new SkillRequirementHint(skillRequirementHintElement));
1077  }
1078  if (skillRequirementHints.Any())
1079  {
1080  SkillRequirementHints = skillRequirementHints.ToImmutableArray();
1081  }
1082 
1083  var allowDroppingOnSwapWith = ConfigElement.GetAttributeIdentifierArray("allowdroppingonswapwith", Array.Empty<Identifier>());
1084  AllowDroppingOnSwapWith = allowDroppingOnSwapWith.ToImmutableHashSet();
1085  AllowDroppingOnSwap = allowDroppingOnSwapWith.Any();
1086 
1087  var levelCommonness = new Dictionary<Identifier, CommonnessInfo>();
1088  var levelQuantity = new Dictionary<Identifier, FixedQuantityResourceInfo>();
1089 
1090  List<FabricationRecipe> loadedRecipes = new List<FabricationRecipe>();
1091  foreach (ContentXElement subElement in ConfigElement.Elements())
1092  {
1093  switch (subElement.Name.ToString().ToLowerInvariant())
1094  {
1095  case "sprite":
1096  string spriteFolder = GetTexturePath(subElement, variantOf);
1097 
1098  canSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
1099  canSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
1100 
1101  sprite = new Sprite(subElement, spriteFolder, lazyLoad: true);
1102  if (subElement.GetAttribute("sourcerect") == null &&
1103  subElement.GetAttribute("sheetindex") == null)
1104  {
1105  DebugConsole.ThrowError($"Warning - sprite sourcerect not configured for item \"{ToString()}\"!",
1106  contentPackage: ConfigElement.ContentPackage);
1107  }
1108  Size = Sprite.size;
1109 
1110  if (subElement.GetAttribute("name") == null && !Name.IsNullOrWhiteSpace())
1111  {
1112  Sprite.Name = Name.Value;
1113  }
1115  break;
1116  case "price":
1117  if (subElement.GetAttribute("baseprice") != null)
1118  {
1119  foreach (var priceInfo in PriceInfo.CreatePriceInfos(subElement, out defaultPrice))
1120  {
1121  if (priceInfo.StoreIdentifier.IsEmpty) { continue; }
1122  if (storePrices.ContainsKey(priceInfo.StoreIdentifier))
1123  {
1124  DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the store \"{priceInfo.StoreIdentifier}\" defined more than once.",
1125  ContentPackage);
1126  storePrices[priceInfo.StoreIdentifier] = priceInfo;
1127  }
1128  else
1129  {
1130  storePrices.Add(priceInfo.StoreIdentifier, priceInfo);
1131  }
1132  }
1133  }
1134  else if (subElement.GetAttribute("buyprice") != null && subElement.GetAttributeIdentifier("locationtype", "") is { IsEmpty: false } locationType) // Backwards compatibility
1135  {
1136  if (storePrices.ContainsKey(locationType))
1137  {
1138  DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the location type \"{locationType}\" defined more than once.",
1139  ContentPackage);
1140  storePrices[locationType] = new PriceInfo(subElement);
1141  }
1142  else
1143  {
1144  storePrices.Add(locationType, new PriceInfo(subElement));
1145  }
1146  }
1147  break;
1148  case "deconstruct":
1149  DeconstructTime = subElement.GetAttributeFloat("time", 1.0f);
1150  AllowDeconstruct = true;
1151  RandomDeconstructionOutput = subElement.GetAttributeBool("chooserandom", false);
1152  RandomDeconstructionOutputAmount = subElement.GetAttributeInt("amount", 1);
1153  foreach (XElement itemElement in subElement.Elements())
1154  {
1155  if (itemElement.Attribute("name") != null)
1156  {
1157  DebugConsole.ThrowError($"Error in item config \"{ToString()}\" - use item identifiers instead of names to configure the deconstruct items.",
1158  contentPackage: ConfigElement.ContentPackage);
1159  continue;
1160  }
1161  var deconstructItem = new DeconstructItem(itemElement, Identifier);
1162  if (deconstructItem.ItemIdentifier.IsEmpty)
1163  {
1164  DebugConsole.ThrowError($"Error in item config \"{ToString()}\" - deconstruction output contains an item with no identifier.",
1165  contentPackage: ConfigElement.ContentPackage);
1166  continue;
1167  }
1168  deconstructItems.Add(deconstructItem);
1169  }
1170  RandomDeconstructionOutputAmount = Math.Min(RandomDeconstructionOutputAmount, deconstructItems.Count);
1171  break;
1172  case "fabricate":
1173  case "fabricable":
1174  case "fabricableitem":
1175  var newRecipe = new FabricationRecipe(subElement, Identifier);
1176  if (fabricationRecipes.TryGetValue(newRecipe.RecipeHash, out var prevRecipe))
1177  {
1178  //the errors below may be caused by a mod overriding a base item instead of this one, log the package of the base item in that case
1179  var packageToLog =
1180  (variantOf.ContentPackage != null && variantOf.ContentPackage != ContentPackageManager.VanillaCorePackage) ?
1181  variantOf.ContentPackage :
1183 
1184  int prevRecipeIndex = loadedRecipes.IndexOf(prevRecipe);
1185  DebugConsole.AddWarning(
1186  $"Error in item prefab \"{ToString()}\": " +
1187  $"Fabrication recipe #{loadedRecipes.Count + 1} has the same hash as recipe #{prevRecipeIndex + 1}. This is most likely caused by identical, duplicate recipes. " +
1188  $"This will cause issues with fabrication.",
1189  contentPackage: packageToLog);
1190  }
1191  else
1192  {
1193  fabricationRecipes.Add(newRecipe.RecipeHash, newRecipe);
1194  }
1195  loadedRecipes.Add(newRecipe);
1196  break;
1197  case "preferredcontainer":
1198  var preferredContainer = new PreferredContainer(subElement);
1199  if (preferredContainer.Primary.Count == 0 && preferredContainer.Secondary.Count == 0)
1200  {
1201  //it's ok for variants to clear the primary and secondary containers to disable the PreferredContainer element
1202  if (variantOf == null)
1203  {
1204  DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\": preferred container has no preferences defined ({subElement}).",
1205  contentPackage: ConfigElement.ContentPackage);
1206  }
1207  }
1208  else
1209  {
1210  preferredContainers.Add(preferredContainer);
1211  }
1212  break;
1213  case "swappableitem":
1214  SwappableItem = new SwappableItem(subElement);
1215  break;
1216  case "trigger":
1217  Rectangle trigger = new Rectangle(0, 0, 10, 10)
1218  {
1219  X = subElement.GetAttributeInt("x", 0),
1220  Y = subElement.GetAttributeInt("y", 0),
1221  Width = subElement.GetAttributeInt("width", 0),
1222  Height = subElement.GetAttributeInt("height", 0)
1223  };
1224 
1225  triggers.Add(trigger);
1226 
1227  break;
1228  case "levelresource":
1229  foreach (XElement levelCommonnessElement in subElement.GetChildElements("commonness"))
1230  {
1231  Identifier levelName = levelCommonnessElement.GetAttributeIdentifier("leveltype", "");
1232  if (!levelCommonnessElement.GetAttributeBool("fixedquantity", false))
1233  {
1234  if (!levelCommonness.ContainsKey(levelName))
1235  {
1236  levelCommonness.Add(levelName, new CommonnessInfo(levelCommonnessElement));
1237  }
1238  }
1239  else
1240  {
1241  if (!levelQuantity.ContainsKey(levelName))
1242  {
1243  levelQuantity.Add(levelName, new FixedQuantityResourceInfo(
1244  levelCommonnessElement.GetAttributeInt("clusterquantity", 0),
1245  levelCommonnessElement.GetAttributeInt("clustersize", 0),
1246  levelCommonnessElement.GetAttributeBool("isislandspecific", false),
1247  levelCommonnessElement.GetAttributeBool("allowatstart", true)));
1248  }
1249  }
1250  }
1251  break;
1252  case "suitabletreatment":
1253  if (subElement.GetAttribute("name") != null)
1254  {
1255  DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - suitable treatments should be defined using item identifiers, not item names.",
1256  contentPackage: ConfigElement.ContentPackage);
1257  }
1258  Identifier treatmentIdentifier = subElement.GetAttributeIdentifier("identifier", subElement.GetAttributeIdentifier("type", Identifier.Empty));
1259  float suitability = subElement.GetAttributeFloat("suitability", 0.0f);
1260  treatmentSuitability.Add(treatmentIdentifier, suitability);
1261  break;
1262  }
1263  }
1264 
1266 
1267 #if CLIENT
1268  ParseSubElementsClient(ConfigElement, variantOf);
1269 #endif
1270 
1271  this.Triggers = triggers.ToImmutableArray();
1272  this.DeconstructItems = deconstructItems.ToImmutableArray();
1273  this.FabricationRecipes = fabricationRecipes.ToImmutableDictionary();
1274  this.treatmentSuitability = treatmentSuitability.ToImmutableDictionary();
1275  StorePrices = storePrices.ToImmutableDictionary();
1276  this.PreferredContainers = preferredContainers.ToImmutableArray();
1277  this.LevelCommonness = levelCommonness.ToImmutableDictionary();
1278  this.LevelQuantity = levelQuantity.ToImmutableDictionary();
1279 
1280  // Backwards compatibility
1281  if (storePrices.Any())
1282  {
1283  defaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false);
1284  }
1285 
1287 
1288  //backwards compatibility
1289  if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
1290  {
1291  this.category = MapEntityCategory.Wrecked;
1292  Subcategory = "Thalamus";
1293  }
1294 
1295  if (Sprite == null)
1296  {
1297  DebugConsole.ThrowError($"Item \"{ToString()}\" has no sprite!", contentPackage: ConfigElement.ContentPackage);
1298 #if SERVER
1299  this.sprite = new Sprite("", Vector2.Zero);
1300  this.sprite.SourceRect = new Rectangle(0, 0, 32, 32);
1301 #else
1302  this.sprite = new Sprite(TextureLoader.PlaceHolderTexture, null, null)
1303  {
1304  Origin = TextureLoader.PlaceHolderTexture.Bounds.Size.ToVector2() / 2
1305  };
1306 #endif
1307  Size = Sprite.size;
1309  }
1310 
1311  if (Identifier == Identifier.Empty)
1312  {
1313  DebugConsole.ThrowError(
1314  $"Item prefab \"{ToString()}\" has no identifier. All item prefabs have a unique identifier string that's used to differentiate between items during saving and loading.",
1315  contentPackage: ConfigElement.ContentPackage);
1316  }
1317 
1318 #if DEBUG
1319  if (!Category.HasFlag(MapEntityCategory.Legacy) && !HideInMenus)
1320  {
1321  if (!string.IsNullOrEmpty(OriginalName))
1322  {
1323  DebugConsole.AddWarning($"Item \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.",
1324  ContentPackage);
1325  }
1326  }
1327 #endif
1328 
1329  this.allowedLinks = ConfigElement.GetAttributeIdentifierArray("allowedlinks", Array.Empty<Identifier>()).ToImmutableHashSet();
1330 
1332  nameof(GrabWhenSelected),
1333  ConfigElement.GetChildElement(nameof(ItemContainer)) != null &&
1334  ConfigElement.GetChildElement("Body") == null);
1335  }
1336 
1338  {
1339  CommonnessInfo? levelCommonnessInfo = GetValueOrNull(level.GenerationParams.Identifier);
1340  CommonnessInfo? biomeCommonnessInfo = GetValueOrNull(level.LevelData.Biome.Identifier);
1341  CommonnessInfo? defaultCommonnessInfo = GetValueOrNull(Identifier.Empty);
1342 
1343  if (levelCommonnessInfo.HasValue)
1344  {
1345  return levelCommonnessInfo?.WithInheritedCommonness(biomeCommonnessInfo, defaultCommonnessInfo);
1346  }
1347  else if (biomeCommonnessInfo.HasValue)
1348  {
1349  return biomeCommonnessInfo?.WithInheritedCommonness(defaultCommonnessInfo);
1350  }
1351  else if (defaultCommonnessInfo.HasValue)
1352  {
1353  return defaultCommonnessInfo;
1354  }
1355 
1356  return null;
1357 
1358  CommonnessInfo? GetValueOrNull(Identifier identifier)
1359  {
1360  if (LevelCommonness.TryGetValue(identifier, out CommonnessInfo info))
1361  {
1362  return info;
1363  }
1364  else
1365  {
1366  return null;
1367  }
1368  }
1369  }
1370 
1371  public float GetTreatmentSuitability(Identifier treatmentIdentifier)
1372  {
1373  return treatmentSuitability.TryGetValue(treatmentIdentifier, out float suitability) ? suitability : 0.0f;
1374  }
1375 
1376  #region Pricing
1377 
1379  {
1380  if (store == null)
1381  {
1382  string message = $"Tried to get price info for \"{Identifier}\" with a null store parameter!\n{Environment.StackTrace.CleanupStackTrace()}";
1383 #if DEBUG
1384  DebugConsole.LogError(message, contentPackage: ContentPackage);
1385 #else
1386  DebugConsole.AddWarning(message, ContentPackage);
1387  GameAnalyticsManager.AddErrorEventOnce("ItemPrefab.GetPriceInfo:StoreParameterNull", GameAnalyticsManager.ErrorSeverity.Error, message);
1388 #endif
1389  return null;
1390  }
1391  else if (!store.Identifier.IsEmpty && StorePrices != null && StorePrices.TryGetValue(store.Identifier, out var storePriceInfo))
1392  {
1393  return storePriceInfo;
1394  }
1395  else
1396  {
1397  return DefaultPrice;
1398  }
1399  }
1400 
1401  public bool CanBeBoughtFrom(Location.StoreInfo store, out PriceInfo priceInfo)
1402  {
1403  priceInfo = GetPriceInfo(store);
1404  Identifier? faction = store?.Location.Faction?.Prefab.Identifier;
1405  Identifier? secondaryFaction = store?.Location.SecondaryFaction?.Prefab.Identifier;
1406 
1407  return
1408  priceInfo is { CanBeBought: true } &&
1409  (store?.Location.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty &&
1410  (priceInfo.RequiredFaction.IsEmpty || faction == priceInfo.RequiredFaction || secondaryFaction == priceInfo.RequiredFaction) &&
1411  (!priceInfo.MinReputation.Any() || priceInfo.MinReputation.Any(p => faction == p.Key || secondaryFaction == p.Key));
1412  }
1413 
1414  public bool CanBeBoughtFrom(Location location)
1415  {
1416  if (location?.Stores == null) { return false; }
1417  foreach (var store in location.Stores)
1418  {
1419  var priceInfo = GetPriceInfo(store.Value);
1420  if (priceInfo == null) { continue; }
1421  if (!priceInfo.CanBeBought) { continue; }
1422  if (location.LevelData.Difficulty < priceInfo.MinLevelDifficulty) { continue; }
1423  if (priceInfo.MinReputation.Any())
1424  {
1425  if (!priceInfo.MinReputation.Any(p =>
1426  location?.Faction?.Prefab.Identifier == p.Key ||
1427  location?.SecondaryFaction?.Prefab.Identifier == p.Key))
1428  {
1429  continue;
1430  }
1431  }
1432  return true;
1433  }
1434  return false;
1435  }
1436 
1437  public int? GetMinPrice()
1438  {
1439  int? minPrice = null;
1440  if (StorePrices != null && StorePrices.Any())
1441  {
1442  minPrice = StorePrices.Values.Min(p => p.Price);
1443  }
1444  if (minPrice.HasValue)
1445  {
1446  if (DefaultPrice != null)
1447  {
1448  return minPrice < DefaultPrice.Price ? minPrice : DefaultPrice.Price;
1449  }
1450  else
1451  {
1452  return minPrice.Value;
1453  }
1454  }
1455  else
1456  {
1457  return DefaultPrice?.Price;
1458  }
1459  }
1460 
1461  public ImmutableDictionary<Identifier, PriceInfo> GetBuyPricesUnder(int maxCost = 0)
1462  {
1463  var prices = new Dictionary<Identifier, PriceInfo>();
1464  if (StorePrices != null)
1465  {
1466  foreach (var storePrice in StorePrices)
1467  {
1468  var priceInfo = storePrice.Value;
1469  if (priceInfo == null)
1470  {
1471  continue;
1472  }
1473  if (!priceInfo.CanBeBought)
1474  {
1475  continue;
1476  }
1477  if (priceInfo.Price < maxCost || maxCost == 0)
1478  {
1479  prices.Add(storePrice.Key, priceInfo);
1480  }
1481  }
1482  }
1483  return prices.ToImmutableDictionary();
1484  }
1485 
1486  public ImmutableDictionary<Identifier, PriceInfo> GetSellPricesOver(int minCost = 0, bool sellingImportant = true)
1487  {
1488  var prices = new Dictionary<Identifier, PriceInfo>();
1489  if (!CanBeSold && sellingImportant)
1490  {
1491  return prices.ToImmutableDictionary();
1492  }
1493  foreach (var storePrice in StorePrices)
1494  {
1495  var priceInfo = storePrice.Value;
1496  if (priceInfo == null)
1497  {
1498  continue;
1499  }
1500  if (priceInfo.Price > minCost)
1501  {
1502  prices.Add(storePrice.Key, priceInfo);
1503  }
1504  }
1505  return prices.ToImmutableDictionary();
1506  }
1507 
1508  #endregion
1509 
1510  public static ItemPrefab Find(string name, Identifier identifier)
1511  {
1512  if (string.IsNullOrEmpty(name) && identifier.IsEmpty)
1513  {
1514  throw new ArgumentException("Both name and identifier cannot be null.");
1515  }
1516 
1517  if (identifier.IsEmpty)
1518  {
1519  //legacy support
1520  identifier = GenerateLegacyIdentifier(name);
1521  }
1522  Prefabs.TryGet(identifier, out ItemPrefab prefab);
1523 
1524  //not found, see if we can find a prefab with a matching alias
1525  if (prefab == null && !string.IsNullOrEmpty(name))
1526  {
1527  string lowerCaseName = name.ToLowerInvariant();
1528  prefab = Prefabs.Find(me => me.Aliases != null && me.Aliases.Contains(lowerCaseName));
1529  }
1530  if (prefab == null)
1531  {
1532  prefab = Prefabs.Find(me => me.Aliases != null && me.Aliases.Contains(identifier.Value));
1533  }
1534 
1535  if (prefab == null)
1536  {
1537  DebugConsole.ThrowError($"Error loading item - item prefab \"{name}\" (identifier \"{identifier}\") not found.");
1538  }
1539  return prefab;
1540  }
1541 
1542  public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false, bool checkTransferConditions = false)
1543  {
1544  isPreferencesDefined = PreferredContainers.Any();
1545  isSecondary = false;
1546  if (!isPreferencesDefined) { return true; }
1547  if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
1548  IsContainerPreferred(pc.Primary, targetContainer) && (!checkTransferConditions || CanBeTransferred(item.Prefab.Identifier, pc, targetContainer))))
1549  {
1550  return true;
1551  }
1552  isSecondary = true;
1553  return PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, targetContainer));
1554  static bool HasConditionRequirement(PreferredContainer pc) => pc.MinCondition > 0 || pc.MaxCondition < 100;
1555  }
1556 
1557  public bool IsContainerPreferred(Item item, Identifier[] identifiersOrTags, out bool isPreferencesDefined, out bool isSecondary)
1558  {
1559  isPreferencesDefined = PreferredContainers.Any();
1560  isSecondary = false;
1561  if (!isPreferencesDefined) { return true; }
1562  if (PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, identifiersOrTags)))
1563  {
1564  return true;
1565  }
1566  isSecondary = true;
1567  return PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, identifiersOrTags));
1568  }
1569 
1570  private static bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
1571  private static bool CanBeTransferred(Identifier item, PreferredContainer pc, ItemContainer targetContainer) =>
1572  pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item));
1573 
1574  public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, ItemContainer c) => preferences.Any(id => c.Item.Prefab.Identifier == id || c.Item.HasTag(id));
1575  public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, IEnumerable<Identifier> ids) => ids.Any(id => preferences.Contains(id));
1576 
1577  protected override void CreateInstance(Rectangle rect)
1578  {
1579  throw new InvalidOperationException("Can't call ItemPrefab.CreateInstance");
1580  }
1581 
1582  public override void Dispose()
1583  {
1584  Item.RemoveByPrefab(this);
1585  }
1586 
1587  public Identifier VariantOf { get; }
1588  public ItemPrefab ParentPrefab { get; set; }
1589 
1590  public void InheritFrom(ItemPrefab parent)
1591  {
1592  ConfigElement = originalElement.CreateVariantXML(parent.ConfigElement, CheckXML);
1593  ParseConfigElement(parent);
1594 
1595  void CheckXML(XElement originalElement, XElement variantElement, XElement result)
1596  {
1597  //if either the parent or the variant are non-vanilla, assume the error is coming from that package
1598  var packageToLog = parent.ContentPackage != GameMain.VanillaContent ? parent.ContentPackage : ContentPackage;
1599 
1600  if (result == null) { return; }
1601  if (result.Name.ToIdentifier() == "RequiredItem" &&
1602  result.Parent?.Name.ToIdentifier() == "Fabricate")
1603  {
1604  int originalAmount = originalElement.GetAttributeInt("amount", 1);
1605  Identifier originalIdentifier = originalElement.GetAttributeIdentifier("identifier", Identifier.Empty);
1606  if (variantElement == null)
1607  {
1608  //if the variant defines some fabrication requirements, we probably don't want to inherit anything extra from the base item?
1609  if (this.originalElement.GetChildElement("Fabricate")?.GetChildElement("RequiredItem") != null)
1610  {
1611  DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
1612  $"the item inherits the fabrication requirement of x{originalAmount} \"{originalIdentifier}\" from the base item \"{parent.Identifier}\". " +
1613  $"If this is not intentional, you can use empty <RequiredItem /> elements in the item variant to remove any excess inherited fabrication requirements.",
1614  packageToLog);
1615  }
1616  return;
1617  }
1618 
1619  Identifier resultIdentifier = result.GetAttributeIdentifier("identifier", Identifier.Empty);
1620  if (originalAmount > 1 && variantElement.GetAttribute("amount") == null)
1621  {
1622  DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
1623  $"the base item \"{parent.Identifier}\" requires x{originalAmount} \"{originalIdentifier}\" to fabricate. " +
1624  $"The variant only overrides the required item, not the amount, resulting in a requirement of x{originalAmount} \"{resultIdentifier}\". "+
1625  "Specify the amount in the variant to fix this.",
1626  packageToLog);
1627  }
1628  }
1629  if (originalElement?.Name.ToIdentifier() == "Deconstruct" &&
1630  variantElement?.Name.ToIdentifier() == "Deconstruct")
1631  {
1632  if (originalElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") &&
1633  variantElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem"))
1634  {
1635  DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
1636  $"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. Overriding the base recipe may not work correctly.",
1637  packageToLog);
1638  }
1639  if (variantElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") &&
1640  originalElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem"))
1641  {
1642  DebugConsole.AddWarning($"Potential error in item \"{parent.Identifier}\": " +
1643  $"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. The item variant \"{Identifier}\" may not override the base recipe correctly.",
1644  packageToLog);
1645  }
1646  }
1647  }
1648  }
1649 
1656  {
1657  if (ParentPrefab != null &&
1658  ParentPrefab.ContentPackage != ContentPackageManager.VanillaCorePackage)
1659  {
1661  }
1662  return ContentPackage;
1663  }
1664 
1665  public override string ToString()
1666  {
1667  return $"{Name} (identifier: {Identifier})";
1668  }
1669  }
1670 }
Base class for content file types, which are loaded from filelist.xml via reflection....
Definition: ContentFile.cs:23
readonly ContentPath Path
Definition: ContentFile.cs:137
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
IEnumerable< ContentXElement > Descendants()
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
IEnumerable< ContentXElement > GetChildElements(string name)
IEnumerable< ContentXElement > Elements()
Vector2 GetAttributeVector2(string key, in Vector2 def)
ContentXElement? GetChildElement(string name)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
string?[] GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant=false)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader)
Definition: ItemPrefab.cs:148
override IEnumerable< ItemPrefab > ItemPrefabs
Definition: ItemPrefab.cs:138
RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader, Identifier defaultItem)
Definition: ItemPrefab.cs:195
override IEnumerable< ItemPrefab > ItemPrefabs
Definition: ItemPrefab.cs:173
bool IsConditionSuitable(float conditionPercentage)
Definition: ItemPrefab.cs:113
abstract bool MatchesItem(Item item)
RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader, Identifier defaultItem)
Definition: ItemPrefab.cs:94
abstract IEnumerable< ItemPrefab > ItemPrefabs
Definition: ItemPrefab.cs:84
readonly Identifier DefaultItem
Used only when there's multiple optional items.
Definition: ItemPrefab.cs:111
readonly ImmutableArray< Identifier > SuitableFabricatorIdentifiers
Definition: ItemPrefab.cs:216
readonly float OutCondition
Definition: ItemPrefab.cs:220
LocalizedString DisplayName
Definition: ItemPrefab.cs:214
FabricationRecipe(ContentXElement element, Identifier itemPrefab)
Definition: ItemPrefab.cs:232
readonly ImmutableArray< RequiredItem > RequiredItems
Definition: ItemPrefab.cs:215
readonly ImmutableArray< Skill > RequiredSkills
Definition: ItemPrefab.cs:221
readonly float RequiredTime
Definition: ItemPrefab.cs:217
readonly bool RequiresRecipe
Definition: ItemPrefab.cs:219
readonly Identifier TargetItemPrefabIdentifier
Definition: ItemPrefab.cs:209
readonly int FabricationLimitMin
How many of this item the fabricator can create (< 0 = unlimited)
Definition: ItemPrefab.cs:230
readonly bool HideForNonTraitors
Definition: ItemPrefab.cs:225
static ContentPackage VanillaContent
Definition: GameMain.cs:71
static GameSession GameSession
Definition: GameMain.cs:45
CampaignMode? Campaign
Definition: GameSession.cs:128
static void RemoveByPrefab(ItemPrefab prefab)
static readonly PrefabCollection< ItemPrefab > Prefabs
Definition: ItemPrefab.cs:483
override ImmutableHashSet< Identifier > AllowedLinks
Definition: ItemPrefab.cs:681
override string OriginalName
Definition: ItemPrefab.cs:672
const float DefaultInteractDistance
Definition: ItemPrefab.cs:485
bool CanBeSold
Any item with a Price element in the definition can be sold everywhere.
Definition: ItemPrefab.cs:498
bool DamagedByContainedItemExplosions
Definition: ItemPrefab.cs:764
ImmutableHashSet< Identifier > AllowDroppingOnSwapWith
Definition: ItemPrefab.cs:933
bool AllowStealingContainedItems
Definition: ItemPrefab.cs:960
bool CanBeBoughtFrom(Location location)
Definition: ItemPrefab.cs:1414
bool UseContainedInventoryIconColor
Definition: ItemPrefab.cs:815
float GetTreatmentSuitability(Identifier treatmentIdentifier)
Definition: ItemPrefab.cs:1371
static ItemPrefab Find(string name, Identifier identifier)
Definition: ItemPrefab.cs:1510
ImmutableArray< SkillRequirementHint > SkillRequirementHints
Definition: ItemPrefab.cs:527
ImmutableDictionary< Identifier, FixedQuantityResourceInfo > LevelQuantity
Definition: ItemPrefab.cs:652
CommonnessInfo? GetCommonnessInfo(Level level)
Definition: ItemPrefab.cs:1337
void InheritFrom(ItemPrefab parent)
Definition: ItemPrefab.cs:1590
ImmutableArray< PreferredContainer > PreferredContainers
Definition: ItemPrefab.cs:525
static Identifier GenerateLegacyIdentifier(string name)
Definition: ItemPrefab.cs:983
float ExplosionDamageMultiplier
Definition: ItemPrefab.cs:767
ItemPrefab(ContentXElement element, ItemFile file)
Definition: ItemPrefab.cs:988
ImmutableDictionary< Identifier, PriceInfo > GetBuyPricesUnder(int maxCost=0)
Definition: ItemPrefab.cs:1461
override Identifier DetermineIdentifier(XElement element)
Definition: ItemPrefab.cs:968
int GetMaxStackSize(Inventory inventory)
Definition: ItemPrefab.cs:888
float AITurretTargetingMaxDistance
Definition: ItemPrefab.cs:957
bool IsOverride
Is this prefab overriding a prefab in another content package
Definition: ItemPrefab.cs:511
override bool CanSpriteFlipX
Definition: ItemPrefab.cs:655
ImmutableArray< Rectangle > Triggers
Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true,...
Definition: ItemPrefab.cs:504
override Sprite Sprite
Definition: ItemPrefab.cs:670
PriceInfo GetPriceInfo(Location.StoreInfo store)
Definition: ItemPrefab.cs:1378
ItemPrefab ParentPrefab
Definition: ItemPrefab.cs:1588
ImmutableDictionary< Identifier, PriceInfo > GetSellPricesOver(int minCost=0, bool sellingImportant=true)
Definition: ItemPrefab.cs:1486
ImmutableDictionary< uint, FabricationRecipe > FabricationRecipes
Definition: ItemPrefab.cs:518
string CargoContainerIdentifier
Definition: ItemPrefab.cs:809
override void Dispose()
Definition: ItemPrefab.cs:1582
PriceInfo DefaultPrice
Definition: ItemPrefab.cs:491
bool CanBeBoughtFrom(Location.StoreInfo store, out PriceInfo priceInfo)
Definition: ItemPrefab.cs:1401
ContentPackage GetParentModPackageOrThisPackage()
If the base prefab this one is a variant of is defined in a non-vanilla package, returns that non-van...
Definition: ItemPrefab.cs:1655
override LocalizedString Name
Definition: ItemPrefab.cs:675
override ImmutableHashSet< string > Aliases
Definition: ItemPrefab.cs:687
override void CreateInstance(Rectangle rect)
Definition: ItemPrefab.cs:1577
string EquipConfirmationText
Definition: ItemPrefab.cs:839
ImmutableArray< DeconstructItem > DeconstructItems
Definition: ItemPrefab.cs:516
bool IsContainerPreferred(Item item, Identifier[] identifiersOrTags, out bool isPreferencesDefined, out bool isSecondary)
Definition: ItemPrefab.cs:1557
override ImmutableHashSet< Identifier > Tags
Definition: ItemPrefab.cs:678
bool DisableCommandMenuWhenSelected
Definition: ItemPrefab.cs:966
int MaxStackSizeCharacterInventory
Definition: ItemPrefab.cs:873
static bool IsContainerPreferred(IEnumerable< Identifier > preferences, ItemContainer c)
override string ToString()
Definition: ItemPrefab.cs:1665
SwappableItem SwappableItem
Definition: ItemPrefab.cs:530
float AddedPickingSpeedMultiplier
Definition: ItemPrefab.cs:826
override MapEntityCategory Category
Definition: ItemPrefab.cs:684
bool? AllowAsExtraCargo
Can the item be chosen as extra cargo in multiplayer. If not set, the item is available if it can be ...
Definition: ItemPrefab.cs:663
bool DisableItemUsageWhenSelected
Definition: ItemPrefab.cs:806
static bool IsContainerPreferred(IEnumerable< Identifier > preferences, IEnumerable< Identifier > ids)
float AddedRepairSpeedMultiplier
Definition: ItemPrefab.cs:819
int RandomDeconstructionOutputAmount
Definition: ItemPrefab.cs:667
bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement=false, bool checkTransferConditions=false)
Definition: ItemPrefab.cs:1542
override bool CanSpriteFlipY
Definition: ItemPrefab.cs:658
int MaxStackSizeHoldableOrWearableInventory
Definition: ItemPrefab.cs:883
ContentXElement ConfigElement
Definition: ItemPrefab.cs:514
static LocalizedString TryCreateName(ItemPrefab prefab, XElement element)
readonly Biome Biome
Definition: LevelData.cs:26
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
static MapEntityPrefab FindByName(string name)
void LoadDescription(ContentXElement element)
override bool Equals(object? obj)
Definition: Md5Hash.cs:147
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
readonly bool TransferOnlyOnePerContainer
Definition: ItemPrefab.cs:386
PreferredContainer(XElement element)
Definition: ItemPrefab.cs:391
readonly float MinLevelDifficulty
Definition: ItemPrefab.cs:389
readonly ImmutableHashSet< Identifier > Secondary
Definition: ItemPrefab.cs:374
readonly ImmutableHashSet< Identifier > Primary
Definition: ItemPrefab.cs:373
readonly bool AllowTransfersHere
Definition: ItemPrefab.cs:387
readonly float SpawnProbability
Definition: ItemPrefab.cs:376
Vector2 size
Definition: Sprite.cs:36
Identifier EntityIdentifier
Identifier of the Map Entity so that we can link the sprite to its owner.
Definition: Sprite.cs:101
Rectangle SourceRect
Definition: Sprite.cs:47
readonly Vector2 SwapOrigin
Definition: ItemPrefab.cs:439
SwappableItem(ContentXElement element)
Definition: ItemPrefab.cs:455
readonly bool CanBeBought
Definition: ItemPrefab.cs:431
List<(Identifier requiredTag, Identifier swapTo)> ConnectedItemsToSwap
Definition: ItemPrefab.cs:441
readonly Sprite SchematicSprite
Definition: ItemPrefab.cs:443
int GetPrice(Location location=null)
Definition: ItemPrefab.cs:445
readonly Identifier ReplacementOnUninstall
Definition: ItemPrefab.cs:433
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:195
DeconstructItem(XElement element, Identifier parentDebugName)
Definition: ItemPrefab.cs:56
readonly float Commonness
Definition: ItemPrefab.cs:54
readonly Identifier[] RequiredDeconstructor
Definition: ItemPrefab.cs:46
readonly float MinCondition
Definition: ItemPrefab.cs:37
readonly float OutConditionMax
Definition: ItemPrefab.cs:41
readonly Identifier[] RequiredOtherItem
Definition: ItemPrefab.cs:48
readonly Identifier ItemIdentifier
Definition: ItemPrefab.cs:33
readonly bool CopyCondition
Definition: ItemPrefab.cs:43
bool IsValidDeconstructor(Item deconstructor)
Definition: ItemPrefab.cs:74
readonly float OutConditionMin
Definition: ItemPrefab.cs:41
readonly string InfoText
Definition: ItemPrefab.cs:51
readonly string InfoTextOnOtherItemMissing
Definition: ItemPrefab.cs:52
readonly float MaxCondition
Definition: ItemPrefab.cs:39
readonly string ActivateButtonText
Definition: ItemPrefab.cs:50
float GetCommonness(Level.TunnelType tunnelType)
Definition: ItemPrefab.cs:618
CommonnessInfo WithInheritedCommonness(CommonnessInfo? parentInfo)
Definition: ItemPrefab.cs:601
CommonnessInfo WithInheritedCommonness(params CommonnessInfo?[] parentInfos)
Definition: ItemPrefab.cs:608
CommonnessInfo(float commonness, float? abyssCommonness, float? caveCommonness)
Definition: ItemPrefab.cs:594
FixedQuantityResourceInfo(int clusterQuantity, int clusterSize, bool isIslandSpecific, bool allowAtStart)
Definition: ItemPrefab.cs:643
readonly Identifier Skill
Definition: ItemPrefab.cs:16
SkillRequirementHint(ContentXElement element)
Definition: ItemPrefab.cs:23
readonly LocalizedString SkillName
Definition: ItemPrefab.cs:18
LocalizedString GetFormattedText(int skillLevel, string levelColorTag)