4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
9 using System.Security.Cryptography;
10 using System.Xml.Linq;
16 public readonly Identifier
Skill;
21 $
"{SkillName} {Level} (‖color:{levelColorTag}‖{skillLevel}‖color:end‖)";
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);
67 element.Parent?.GetAttributeIdentifierArray(
"requireddeconstructor", Array.Empty<Identifier>()) ?? Array.Empty<Identifier>());
68 RequiredOtherItem = element.GetAttributeIdentifierArray(
"requiredotheritem", Array.Empty<Identifier>());
70 InfoText = element.GetAttributeString(
"infotext",
string.Empty);
115 float normalizedCondition = conditionPercentage / 100.0f;
116 if (MathUtils.NearlyEqual(normalizedCondition,
MinCondition) || MathUtils.NearlyEqual(normalizedCondition,
MaxCondition))
149 base(amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader, defaultItem: Identifier.Empty)
152 using MD5 md5 = MD5.Create();
153 UintIdentifier = ToolBoxCore.IdentifierToUint32Hash(itemPrefab, md5);
158 return $
"{base.ToString()} ({ItemPrefabIdentifier})";
164 public readonly Identifier
Tag;
168 private readonly List<ItemPrefab> cachedPrefabs =
new List<ItemPrefab>();
170 private Md5Hash prevContentPackagesHash;
176 if (prevContentPackagesHash ==
null ||
177 !prevContentPackagesHash.
Equals(ContentPackageManager.EnabledPackages.MergedHash))
179 cachedPrefabs.Clear();
181 prevContentPackagesHash = ContentPackageManager.EnabledPackages.MergedHash;
183 return cachedPrefabs;
191 if (item ==
null) {
return false; }
196 : base(amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader, defaultItem)
199 using MD5 md5 = MD5.Create();
205 return $
"{base.ToString()} ({Tag})";
212 private readonly Lazy<LocalizedString> displayName;
236 displayName =
new Lazy<LocalizedString>(() => displayNameIdentifier.IsEmpty
238 : TextManager.GetWithVariable($
"DisplayName.{displayNameIdentifier}",
"[itemname]",
TargetItem.
Name));
242 var requiredSkills =
new List<Skill>();
248 DebugConsole.AddWarning($
"Error in \"{itemPrefab}\"'s fabrication recipe: out condition is above 100% ({OutCondition * 100}).",
251 var requiredItems =
new List<RequiredItem>();
257 FabricationLimitMax = element.
GetAttributeInt(nameof(FabricationLimitMax), limitDefault);
266 foreach (var subElement
in element.Elements())
268 switch (subElement.Name.ToString().ToLowerInvariant())
270 case "requiredskill":
271 if (subElement.GetAttribute(
"name") !=
null)
273 DebugConsole.ThrowError(
"Error in fabricable item " + itemPrefab +
"! Use skill identifiers instead of names.",
274 contentPackage: element.ContentPackage);
278 requiredSkills.Add(
new Skill(
279 subElement.GetAttributeIdentifier(
"identifier",
""),
280 subElement.GetAttributeInt(
"level", 0)));
284 Identifier requiredItemIdentifier = subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
285 Identifier requiredItemTag = subElement.GetAttributeIdentifier(
"tag", Identifier.Empty);
286 if (requiredItemIdentifier == Identifier.Empty && requiredItemTag == Identifier.Empty)
288 DebugConsole.ThrowError(
"Error in fabricable item " + itemPrefab +
"! One of the required items has no identifier or tag.",
289 contentPackage: element.ContentPackage);
293 float minCondition = subElement.GetAttributeFloat(
"mincondition", 1.0f);
294 float maxCondition = subElement.GetAttributeFloat(
"maxcondition", 1.0f);
296 bool useCondition = subElement.GetAttributeBool(
"usecondition",
true);
297 int amount = subElement.GetAttributeInt(
"count", subElement.GetAttributeInt(
"amount", 1));
300 if (subElement.GetAttributeString(
"description",
string.Empty) is
string descriptionTag && !descriptionTag.IsNullOrEmpty())
302 overrideDescription = TextManager.Get(descriptionTag);
305 if (subElement.GetAttributeString(
"header",
string.Empty) is
string headerTag && !headerTag.IsNullOrEmpty())
307 overrideHeader = TextManager.Get(headerTag);
310 if (requiredItemIdentifier != Identifier.Empty)
312 var existing = requiredItems.FindIndex(r =>
315 MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
316 MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
319 amount += requiredItems[existing].Amount;
320 requiredItems.RemoveAt(existing);
322 requiredItems.Add(
new RequiredItemByIdentifier(requiredItemIdentifier, amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader));
326 var existing = requiredItems.FindIndex(r =>
328 rt.
Tag == requiredItemTag &&
329 MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
330 MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
333 amount += requiredItems[existing].Amount;
334 requiredItems.RemoveAt(existing);
336 Identifier defaultItem = subElement.GetAttributeIdentifier(
"defaultitem", Identifier.Empty);
337 requiredItems.Add(
new RequiredItemByTag(requiredItemTag, amount, minCondition, maxCondition, useCondition, overrideDescription, overrideHeader, defaultItem));
353 private uint GenerateHash()
355 using var md5 = MD5.Create();
359 .
Select(
static i => $
"{i.UintIdentifier}:{i.Amount}")
360 .
Select(
static i =>
string.Join(
',', i)));
363 var requiredSkills =
string.Join(
':',
RequiredSkills.Select(s => $
"{s.Identifier}:{s.Level}"));
365 uint retVal = ToolBoxCore.StringToUInt32Hash($
"{Amount}|{outputId}|{RequiredTime}|{RequiresRecipe}|{requiredItems}|{requiredSkills}", md5);
366 if (retVal == 0) { retVal = 1; }
373 public readonly ImmutableHashSet<Identifier>
Primary;
392 Primary = XMLExtensions.GetAttributeIdentifierArray(element,
"primary", Array.Empty<Identifier>()).ToImmutableHashSet();
393 Secondary = XMLExtensions.GetAttributeIdentifierArray(element,
"secondary", Array.Empty<Identifier>()).ToImmutableHashSet();
395 MinAmount = element.GetAttributeInt(
"minamount", 0);
397 Amount = element.GetAttributeInt(
"amount", 0);
398 MaxCondition = element.GetAttributeFloat(
"maxcondition", 100f);
399 MinCondition = element.GetAttributeFloat(
"mincondition", 0f);
406 MaxLevelDifficulty = element.GetAttributeFloat(nameof(MaxLevelDifficulty),
float.MaxValue);
408 if (element.GetAttribute(
"spawnprobability") ==
null)
416 else if (element.GetAttribute(
"minamount") ==
null && element.GetAttribute(
"maxamount") ==
null && element.GetAttribute(
"amount") ==
null)
439 public List<(Identifier requiredTag, Identifier swapTo)>
ConnectedItemsToSwap =
new List<(Identifier requiredTag, Identifier swapTo)>();
448 price = (int)(price * campaign.Settings.ShipyardPriceMultiplier);
462 foreach (var subElement
in element.Elements())
464 switch (subElement.Name.ToString().ToLowerInvariant())
466 case "schematicsprite":
469 case "swapconnecteditem":
471 (subElement.GetAttributeIdentifier(
"tag",
""),
472 subElement.GetAttributeIdentifier(
"swapto",
"")));
486 public Vector2
Size {
get;
private set; }
490 private ImmutableDictionary<Identifier, PriceInfo> StorePrices {
get;
set; }
492 (StorePrices !=
null && StorePrices.Any(p => p.Value.CanBeBought));
502 public ImmutableArray<Rectangle>
Triggers {
get;
private set; }
504 private ImmutableDictionary<Identifier, float> treatmentSuitability;
511 private readonly XElement originalElement;
573 this.commonness = Math.Max(element?.GetAttributeFloat(
"commonness", 0.0f) ?? 0.0f, 0.0f);
576 XAttribute abyssCommonnessAttribute = element?.GetAttribute(
"abysscommonness") ?? element?.GetAttribute(
"abyss");
577 if (abyssCommonnessAttribute !=
null)
579 abyssCommonness = Math.Max(abyssCommonnessAttribute.GetAttributeFloat(0.0f), 0.0f);
584 XAttribute caveCommonnessAttribute = element?.GetAttribute(
"cavecommonness") ?? element?.GetAttribute(
"cave");
585 if (caveCommonnessAttribute !=
null)
587 caveCommonness = Math.Max(caveCommonnessAttribute.GetAttributeFloat(0.0f), 0.0f);
609 foreach (var parentInfo
in parentInfos)
632 private ImmutableDictionary<Identifier, CommonnessInfo> LevelCommonness {
get;
set; }
650 public ImmutableDictionary<Identifier, FixedQuantityResourceInfo>
LevelQuantity {
get;
private set; }
652 private bool canSpriteFlipX;
655 private bool canSpriteFlipY;
675 private ImmutableHashSet<Identifier> tags;
676 public override ImmutableHashSet<Identifier>
Tags => tags;
678 private ImmutableHashSet<Identifier> allowedLinks;
679 public override ImmutableHashSet<Identifier>
AllowedLinks => allowedLinks;
684 private ImmutableHashSet<string> aliases;
685 public override ImmutableHashSet<string>
Aliases => aliases;
698 [
Serialize(
false,
IsPropertySaveable.No, description:
"Hides the condition bar displayed at the bottom of the inventory slot the item is in.")]
729 [
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 private float health;
737 get {
return health; }
742 health = Math.Min(value, 1000000.0f);
776 private float impactTolerance;
780 get {
return impactTolerance; }
781 set { impactTolerance = Math.Max(value, 0.0f); }
854 private int maxStackSize;
858 get {
return maxStackSize; }
862 private int maxStackSizeCharacterInventory;
866 get {
return maxStackSizeCharacterInventory; }
870 private int maxStackSizeHoldableOrWearableInventory;
872 "Maximum stack size when the item is inside a holdable or wearable item. "+
873 "If not set, defaults to MaxStackSizeCharacterInventory.")]
876 get {
return maxStackSizeHoldableOrWearableInventory; }
882 int extraStackSize = inventory
switch
892 return MaxStackWithExtra(maxStackSizeCharacterInventory, extraStackSize);
894 else if (inventory?.Owner is
Item item &&
895 (item.GetComponent<
Holdable>() is { Attachable: false } || item.GetComponent<
Wearable>() !=
null))
897 if (maxStackSizeHoldableOrWearableInventory > 0)
899 return MaxStackWithExtra(maxStackSizeHoldableOrWearableInventory, extraStackSize);
901 else if (maxStackSizeCharacterInventory > 0)
904 return MaxStackWithExtra(maxStackSizeCharacterInventory, extraStackSize);
908 return MaxStackWithExtra(maxStackSize, extraStackSize);
910 static int MaxStackWithExtra(
int maxStackSize,
int extraStackSize)
912 extraStackSize = Math.Max(extraStackSize, 0);
913 if (maxStackSize == 1)
932 [
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.")]
938 [
Serialize(
false,
IsPropertySaveable.No, description:
"Should the bots shoot at this item with turret or not? Disabled by default.")]
941 [
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.")]
944 [
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.")]
947 [
Serialize(
float.PositiveInfinity,
IsPropertySaveable.No, description:
"The max distance at which the bots are allowed to target the items. Defaults to infinity.")]
950 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"If enabled, taking items from this container is never considered stealing.")]
956 [
Serialize(
false,
IsPropertySaveable.No, description:
"If enabled, the player is unable to open the middle click menu when this item is selected.")]
961 Identifier identifier = base.DetermineIdentifier(element);
962 string originalName = element.GetAttributeString(
"name",
"");
963 if (identifier.IsEmpty && !
string.IsNullOrEmpty(originalName))
965 string categoryStr = element.GetAttributeString(
"category",
"Misc");
976 return ($
"legacyitem_{name.Replace(" ", "")}").ToIdentifier();
981 originalElement = element;
991 ParseConfigElement(variantOf:
null);
995 => subElement.DoesAttributeReferenceFileNameAlone(
"texture")
999 private void ParseConfigElement(
ItemPrefab variantOf)
1002 this.category = Enum.TryParse(categoryStr,
true, out
MapEntityCategory category)
1012 name = TextManager.Get(nameIdentifier.IsEmpty
1013 ? $
"EntityName.{Identifier}"
1014 : $
"EntityName.{nameIdentifier}",
1015 $
"EntityName.{fallbackNameIdentifier}");
1023 name = TextManager.GetWithVariable(
"wreckeditemformat",
"[name]", name);
1031 .ToImmutableHashSet()
1034 var triggers =
new List<Rectangle>();
1035 var deconstructItems =
new List<DeconstructItem>();
1036 var fabricationRecipes =
new Dictionary<uint, FabricationRecipe>();
1037 var treatmentSuitability =
new Dictionary<Identifier, float>();
1038 var storePrices =
new Dictionary<Identifier, PriceInfo>();
1039 var preferredContainers =
new List<PreferredContainer>();
1055 DebugConsole.ThrowError($
"Error in item prefab \"{ToString()}\" - cargo container should be configured using the item's identifier, not the name.",
1059 SerializableProperty.DeserializeProperties(
this,
ConfigElement);
1062 var skillRequirementHints =
new List<SkillRequirementHint>();
1065 skillRequirementHints.Add(
new SkillRequirementHint(skillRequirementHintElement));
1067 if (skillRequirementHints.Any())
1076 var levelCommonness =
new Dictionary<Identifier, CommonnessInfo>();
1077 var levelQuantity =
new Dictionary<Identifier, FixedQuantityResourceInfo>();
1079 List<FabricationRecipe> loadedRecipes =
new List<FabricationRecipe>();
1082 switch (subElement.Name.ToString().ToLowerInvariant())
1085 string spriteFolder = GetTexturePath(subElement, variantOf);
1087 canSpriteFlipX = subElement.GetAttributeBool(
"canflipx",
true);
1088 canSpriteFlipY = subElement.GetAttributeBool(
"canflipy",
true);
1090 sprite =
new Sprite(subElement, spriteFolder, lazyLoad:
true);
1091 if (subElement.GetAttribute(
"sourcerect") ==
null &&
1092 subElement.GetAttribute(
"sheetindex") ==
null)
1094 DebugConsole.ThrowError($
"Warning - sprite sourcerect not configured for item \"{ToString()}\"!",
1099 if (subElement.GetAttribute(
"name") ==
null && !
Name.IsNullOrWhiteSpace())
1106 if (subElement.GetAttribute(
"baseprice") !=
null)
1108 foreach (var priceInfo
in PriceInfo.CreatePriceInfos(subElement, out defaultPrice))
1110 if (priceInfo.StoreIdentifier.IsEmpty) {
continue; }
1111 if (storePrices.ContainsKey(priceInfo.StoreIdentifier))
1113 DebugConsole.AddWarning($
"Error in item prefab \"{this}\": price for the store \"{priceInfo.StoreIdentifier}\" defined more than once.",
1115 storePrices[priceInfo.StoreIdentifier] = priceInfo;
1119 storePrices.Add(priceInfo.StoreIdentifier, priceInfo);
1123 else if (subElement.GetAttribute(
"buyprice") !=
null && subElement.GetAttributeIdentifier(
"locationtype",
"") is { IsEmpty: false } locationType)
1125 if (storePrices.ContainsKey(locationType))
1127 DebugConsole.AddWarning($
"Error in item prefab \"{this}\": price for the location type \"{locationType}\" defined more than once.",
1129 storePrices[locationType] =
new PriceInfo(subElement);
1133 storePrices.Add(locationType,
new PriceInfo(subElement));
1142 foreach (XElement itemElement
in subElement.Elements())
1144 if (itemElement.Attribute(
"name") !=
null)
1146 DebugConsole.ThrowError($
"Error in item config \"{ToString()}\" - use item identifiers instead of names to configure the deconstruct items.",
1150 var deconstructItem =
new DeconstructItem(itemElement,
Identifier);
1151 if (deconstructItem.ItemIdentifier.IsEmpty)
1153 DebugConsole.ThrowError($
"Error in item config \"{ToString()}\" - deconstruction output contains an item with no identifier.",
1157 deconstructItems.Add(deconstructItem);
1163 case "fabricableitem":
1164 var newRecipe =
new FabricationRecipe(subElement,
Identifier);
1165 if (fabricationRecipes.TryGetValue(newRecipe.RecipeHash, out var prevRecipe))
1173 int prevRecipeIndex = loadedRecipes.IndexOf(prevRecipe);
1174 DebugConsole.AddWarning(
1175 $
"Error in item prefab \"{ToString()}\": " +
1176 $
"Fabrication recipe #{loadedRecipes.Count + 1} has the same hash as recipe #{prevRecipeIndex + 1}. This is most likely caused by identical, duplicate recipes. " +
1177 $
"This will cause issues with fabrication.",
1178 contentPackage: packageToLog);
1182 fabricationRecipes.Add(newRecipe.RecipeHash, newRecipe);
1184 loadedRecipes.Add(newRecipe);
1186 case "preferredcontainer":
1187 var preferredContainer =
new PreferredContainer(subElement);
1188 if (preferredContainer.Primary.Count == 0 && preferredContainer.Secondary.Count == 0)
1191 if (variantOf ==
null)
1193 DebugConsole.ThrowError($
"Error in item prefab \"{ToString()}\": preferred container has no preferences defined ({subElement}).",
1199 preferredContainers.Add(preferredContainer);
1202 case "swappableitem":
1208 X = subElement.GetAttributeInt(
"x", 0),
1209 Y = subElement.GetAttributeInt(
"y", 0),
1210 Width = subElement.GetAttributeInt(
"width", 0),
1211 Height = subElement.GetAttributeInt(
"height", 0)
1214 triggers.Add(trigger);
1217 case "levelresource":
1218 foreach (XElement levelCommonnessElement
in subElement.GetChildElements(
"commonness"))
1220 Identifier levelName = levelCommonnessElement.GetAttributeIdentifier(
"leveltype",
"");
1221 if (!levelCommonnessElement.GetAttributeBool(
"fixedquantity",
false))
1223 if (!levelCommonness.ContainsKey(levelName))
1225 levelCommonness.Add(levelName,
new CommonnessInfo(levelCommonnessElement));
1230 if (!levelQuantity.ContainsKey(levelName))
1232 levelQuantity.Add(levelName,
new FixedQuantityResourceInfo(
1233 levelCommonnessElement.GetAttributeInt(
"clusterquantity", 0),
1234 levelCommonnessElement.GetAttributeInt(
"clustersize", 0),
1235 levelCommonnessElement.GetAttributeBool(
"isislandspecific",
false),
1236 levelCommonnessElement.GetAttributeBool(
"allowatstart",
true)));
1241 case "suitabletreatment":
1242 if (subElement.GetAttribute(
"name") !=
null)
1244 DebugConsole.ThrowError($
"Error in item prefab \"{ToString()}\" - suitable treatments should be defined using item identifiers, not item names.",
1247 Identifier treatmentIdentifier = subElement.GetAttributeIdentifier(
"identifier", subElement.GetAttributeIdentifier(
"type",
Identifier.Empty));
1248 float suitability = subElement.GetAttributeFloat(
"suitability", 0.0f);
1249 treatmentSuitability.Add(treatmentIdentifier, suitability);
1260 this.
Triggers = triggers.ToImmutableArray();
1263 this.treatmentSuitability = treatmentSuitability.ToImmutableDictionary();
1264 StorePrices = storePrices.ToImmutableDictionary();
1266 this.LevelCommonness = levelCommonness.ToImmutableDictionary();
1270 if (storePrices.Any())
1272 defaultPrice ??=
new PriceInfo(
GetMinPrice() ?? 0,
false);
1278 if (categoryStr.Equals(
"Thalamus", StringComparison.OrdinalIgnoreCase))
1288 this.sprite =
new Sprite(
"", Vector2.Zero);
1291 this.sprite =
new Sprite(TextureLoader.PlaceHolderTexture,
null,
null)
1293 Origin = TextureLoader.PlaceHolderTexture.Bounds.Size.ToVector2() / 2
1302 DebugConsole.ThrowError(
1303 $
"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.",
1312 DebugConsole.AddWarning($
"Item \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.",
1332 if (levelCommonnessInfo.HasValue)
1336 else if (biomeCommonnessInfo.HasValue)
1340 else if (defaultCommonnessInfo.HasValue)
1342 return defaultCommonnessInfo;
1349 if (LevelCommonness.TryGetValue(identifier, out
CommonnessInfo info))
1362 return treatmentSuitability.TryGetValue(treatmentIdentifier, out
float suitability) ? suitability : 0.0f;
1371 string message = $
"Tried to get price info for \"{Identifier}\" with a null store parameter!\n{Environment.StackTrace.CleanupStackTrace()}";
1376 GameAnalyticsManager.AddErrorEventOnce(
"ItemPrefab.GetPriceInfo:StoreParameterNull", GameAnalyticsManager.ErrorSeverity.Error, message);
1380 else if (!store.Identifier.IsEmpty && StorePrices !=
null && StorePrices.TryGetValue(store.Identifier, out var storePriceInfo))
1382 return storePriceInfo;
1395 (store?.Location.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty &&
1396 (!priceInfo.MinReputation.Any() || priceInfo.MinReputation.Any(p => store?.
Location.
Faction?.
Prefab.Identifier == p.Key || store?.Location.SecondaryFaction?.Prefab.Identifier == p.Key));
1401 if (location?.Stores ==
null) {
return false; }
1402 foreach (var store
in location.Stores)
1405 if (priceInfo ==
null) {
continue; }
1406 if (!priceInfo.CanBeBought) {
continue; }
1407 if (location.LevelData.Difficulty < priceInfo.MinLevelDifficulty) {
continue; }
1408 if (priceInfo.MinReputation.Any())
1410 if (!priceInfo.MinReputation.Any(p =>
1411 location?.Faction?.Prefab.Identifier == p.Key ||
1412 location?.SecondaryFaction?.Prefab.Identifier == p.Key))
1424 int? minPrice =
null;
1425 if (StorePrices !=
null && StorePrices.Any())
1427 minPrice = StorePrices.Values.Min(p => p.Price);
1429 if (minPrice.HasValue)
1437 return minPrice.Value;
1448 var prices =
new Dictionary<Identifier, PriceInfo>();
1449 if (StorePrices !=
null)
1451 foreach (var storePrice
in StorePrices)
1453 var priceInfo = storePrice.Value;
1454 if (priceInfo ==
null)
1458 if (!priceInfo.CanBeBought)
1462 if (priceInfo.Price < maxCost || maxCost == 0)
1464 prices.Add(storePrice.Key, priceInfo);
1468 return prices.ToImmutableDictionary();
1471 public ImmutableDictionary<Identifier, PriceInfo>
GetSellPricesOver(
int minCost = 0,
bool sellingImportant =
true)
1473 var prices =
new Dictionary<Identifier, PriceInfo>();
1476 return prices.ToImmutableDictionary();
1478 foreach (var storePrice
in StorePrices)
1480 var priceInfo = storePrice.Value;
1481 if (priceInfo ==
null)
1485 if (priceInfo.Price > minCost)
1487 prices.Add(storePrice.Key, priceInfo);
1490 return prices.ToImmutableDictionary();
1497 if (
string.IsNullOrEmpty(name) && identifier.IsEmpty)
1499 throw new ArgumentException(
"Both name and identifier cannot be null.");
1502 if (identifier.IsEmpty)
1510 if (prefab ==
null && !
string.IsNullOrEmpty(name))
1512 string lowerCaseName = name.ToLowerInvariant();
1513 prefab =
Prefabs.Find(me => me.Aliases !=
null && me.Aliases.Contains(lowerCaseName));
1517 prefab =
Prefabs.Find(me => me.Aliases !=
null && me.Aliases.Contains(identifier.Value));
1522 DebugConsole.ThrowError($
"Error loading item - item prefab \"{name}\" (identifier \"{identifier}\") not found.");
1527 public bool IsContainerPreferred(
Item item,
ItemContainer targetContainer, out
bool isPreferencesDefined, out
bool isSecondary,
bool requireConditionRequirement =
false,
bool checkTransferConditions =
false)
1530 isSecondary =
false;
1531 if (!isPreferencesDefined) {
return true; }
1532 if (
PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
1538 return PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
IsContainerPreferred(pc.Secondary, targetContainer));
1545 isSecondary =
false;
1546 if (!isPreferencesDefined) {
return true; }
1555 private static bool IsItemConditionAcceptable(
Item item,
PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
1557 pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item));
1560 public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, IEnumerable<Identifier> ids) => ids.Any(
id => preferences.Contains(
id));
1564 throw new InvalidOperationException(
"Can't call ItemPrefab.CreateInstance");
1578 ParseConfigElement(parent);
1580 void CheckXML(XElement originalElement, XElement variantElement, XElement result)
1585 if (result ==
null) {
return; }
1586 if (result.Name.ToIdentifier() ==
"RequiredItem" &&
1587 result.Parent?.Name.ToIdentifier() ==
"Fabricate")
1589 int originalAmount = originalElement.GetAttributeInt(
"amount", 1);
1590 Identifier originalIdentifier = originalElement.GetAttributeIdentifier(
"identifier",
Identifier.Empty);
1591 if (variantElement ==
null)
1594 if (this.originalElement.GetChildElement(
"Fabricate")?.GetChildElement(
"RequiredItem") !=
null)
1596 DebugConsole.AddWarning($
"Potential error in item variant \"{Identifier}\": " +
1597 $
"the item inherits the fabrication requirement of x{originalAmount} \"{originalIdentifier}\" from the base item \"{parent.Identifier}\". " +
1598 $
"If this is not intentional, you can use empty <RequiredItem /> elements in the item variant to remove any excess inherited fabrication requirements.",
1605 if (originalAmount > 1 && variantElement.GetAttribute(
"amount") ==
null)
1607 DebugConsole.AddWarning($
"Potential error in item variant \"{Identifier}\": " +
1608 $
"the base item \"{parent.Identifier}\" requires x{originalAmount} \"{originalIdentifier}\" to fabricate. " +
1609 $
"The variant only overrides the required item, not the amount, resulting in a requirement of x{originalAmount} \"{resultIdentifier}\". "+
1610 "Specify the amount in the variant to fix this.",
1614 if (originalElement?.
Name.ToIdentifier() ==
"Deconstruct" &&
1615 variantElement?.Name.ToIdentifier() ==
"Deconstruct")
1617 if (originalElement.Elements().Any(e => e.Name.ToIdentifier() ==
"Item") &&
1618 variantElement.Elements().Any(e => e.Name.ToIdentifier() ==
"RequiredItem"))
1620 DebugConsole.AddWarning($
"Potential error in item variant \"{Identifier}\": " +
1621 $
"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. Overriding the base recipe may not work correctly.",
1624 if (variantElement.Elements().Any(e => e.Name.ToIdentifier() ==
"Item") &&
1625 originalElement.Elements().Any(e => e.Name.ToIdentifier() ==
"RequiredItem"))
1627 DebugConsole.AddWarning($
"Potential error in item \"{parent.Identifier}\": " +
1628 $
"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. The item variant \"{Identifier}\" may not override the base recipe correctly.",
1652 return $
"{Name} (identifier: {Identifier})";
Base class for content file types, which are loaded from filelist.xml via reflection....
readonly ContentPath Path
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
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)
readonly Identifier ItemPrefabIdentifier
RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader)
override bool MatchesItem(Item item)
override UInt32 UintIdentifier
override string ToString()
override IEnumerable< ItemPrefab > ItemPrefabs
override ItemPrefab FirstMatchingPrefab
RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader, Identifier defaultItem)
override ItemPrefab FirstMatchingPrefab
override bool MatchesItem(Item item)
override string ToString()
override IEnumerable< ItemPrefab > ItemPrefabs
override UInt32 UintIdentifier
bool IsConditionSuitable(float conditionPercentage)
abstract bool MatchesItem(Item item)
LocalizedString OverrideDescription
RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription, LocalizedString overrideHeader, Identifier defaultItem)
LocalizedString OverrideHeader
abstract IEnumerable< ItemPrefab > ItemPrefabs
readonly float MinCondition
readonly bool UseCondition
readonly float MaxCondition
abstract ItemPrefab FirstMatchingPrefab
readonly Identifier DefaultItem
Used only when there's multiple optional items.
abstract UInt32 UintIdentifier
readonly ImmutableArray< Identifier > SuitableFabricatorIdentifiers
readonly float OutCondition
readonly int RequiredMoney
LocalizedString DisplayName
FabricationRecipe(ContentXElement element, Identifier itemPrefab)
readonly ImmutableArray< RequiredItem > RequiredItems
readonly ImmutableArray< Skill > RequiredSkills
readonly float RequiredTime
readonly bool RequiresRecipe
readonly Identifier TargetItemPrefabIdentifier
readonly int FabricationLimitMin
How many of this item the fabricator can create (< 0 = unlimited)
readonly bool HideForNonTraitors
static GameSession?? GameSession
static ContentPackage VanillaContent
const int MaxPossibleStackSize
static void RemoveByPrefab(ItemPrefab prefab)
ItemStatManager StatManager
bool HasTag(Identifier tag)
static readonly PrefabCollection< ItemPrefab > Prefabs
override ImmutableHashSet< Identifier > AllowedLinks
override string OriginalName
float ItemDamageMultiplier
const float DefaultInteractDistance
bool CanBeSold
Any item with a Price element in the definition can be sold everywhere.
ImmutableHashSet< Identifier > AllowDroppingOnSwapWith
bool AllowStealingContainedItems
bool RandomDeconstructionOutput
bool CanBeBoughtFrom(Location location)
bool UseContainedInventoryIconColor
float GetTreatmentSuitability(Identifier treatmentIdentifier)
bool AllowRotatingInEditor
bool UseInHealthInterface
static ItemPrefab Find(string name, Identifier identifier)
ImmutableArray< SkillRequirementHint > SkillRequirementHints
ImmutableDictionary< Identifier, FixedQuantityResourceInfo > LevelQuantity
CommonnessInfo? GetCommonnessInfo(Level level)
bool InteractThroughWalls
void InheritFrom(ItemPrefab parent)
bool RequireCampaignInteract
ImmutableArray< PreferredContainer > PreferredContainers
static Identifier GenerateLegacyIdentifier(string name)
bool DamagedByMeleeWeapons
bool UseContainedSpriteColor
float ExplosionDamageMultiplier
ItemPrefab(ContentXElement element, ItemFile file)
ImmutableDictionary< Identifier, PriceInfo > GetBuyPricesUnder(int maxCost=0)
override Identifier DetermineIdentifier(XElement element)
int GetMaxStackSize(Inventory inventory)
float AITurretTargetingMaxDistance
bool IsOverride
Is this prefab overriding a prefab in another content package
override bool CanSpriteFlipX
ImmutableArray< Rectangle > Triggers
Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true,...
PriceInfo GetPriceInfo(Location.StoreInfo store)
ImmutableDictionary< Identifier, PriceInfo > GetSellPricesOver(int minCost=0, bool sellingImportant=true)
ImmutableDictionary< uint, FabricationRecipe > FabricationRecipes
string CargoContainerIdentifier
bool CanBeBoughtFrom(Location.StoreInfo store, out PriceInfo priceInfo)
ContentPackage GetParentModPackageOrThisPackage()
If the base prefab this one is a variant of is defined in a non-vanilla package, returns that non-van...
bool RequireCursorInsideTrigger
override LocalizedString Name
override ImmutableHashSet< string > Aliases
override void CreateInstance(Rectangle rect)
string EquipConfirmationText
bool HideConditionInTooltip
ImmutableArray< DeconstructItem > DeconstructItems
float AISlowTurretPriority
bool DamagedByRepairTools
bool ShowContentsInTooltip
bool IsContainerPreferred(Item item, Identifier[] identifiersOrTags, out bool isPreferencesDefined, out bool isSecondary)
override ImmutableHashSet< Identifier > Tags
bool DisableCommandMenuWhenSelected
int MaxStackSizeCharacterInventory
bool DontTransferBetweenSubs
bool AllowSellingWhenBroken
static bool IsContainerPreferred(IEnumerable< Identifier > preferences, ItemContainer c)
override string ToString()
SwappableItem SwappableItem
float AddedPickingSpeedMultiplier
override MapEntityCategory Category
bool? AllowAsExtraCargo
Can the item be chosen as extra cargo in multiplayer. If not set, the item is available if it can be ...
bool DisableItemUsageWhenSelected
static bool IsContainerPreferred(IEnumerable< Identifier > preferences, IEnumerable< Identifier > ids)
bool DamagedByProjectiles
float AddedRepairSpeedMultiplier
bool RequireBodyInsideTrigger
int RandomDeconstructionOutputAmount
Color SignalComponentColor
bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement=false, bool checkTransferConditions=false)
override bool CanSpriteFlipY
int MaxStackSizeHoldableOrWearableInventory
ContentXElement ConfigElement
static LocalizedString TryCreateName(ItemPrefab prefab, XElement element)
LevelGenerationParams GenerationParams
readonly LevelData LevelData
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)
ContentPackage? ContentPackage
readonly Identifier Identifier
readonly bool TransferOnlyOnePerContainer
readonly float MaxCondition
PreferredContainer(XElement element)
readonly float MinLevelDifficulty
readonly ImmutableHashSet< Identifier > Secondary
readonly ImmutableHashSet< Identifier > Primary
readonly bool AllowTransfersHere
readonly float SpawnProbability
readonly float MinCondition
readonly bool NotCampaign
readonly bool CampaignOnly
Identifier EntityIdentifier
Identifier of the Map Entity so that we can link the sprite to its owner.
readonly Vector2 SwapOrigin
SwappableItem(ContentXElement element)
readonly bool CanBeBought
List<(Identifier requiredTag, Identifier swapTo)> ConnectedItemsToSwap
readonly Sprite SchematicSprite
int GetPrice(Location location=null)
readonly Identifier ReplacementOnUninstall
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
DeconstructItem(XElement element, Identifier parentDebugName)
readonly float Commonness
readonly Identifier[] RequiredDeconstructor
readonly float MinCondition
readonly float OutConditionMax
readonly Identifier[] RequiredOtherItem
readonly Identifier ItemIdentifier
readonly bool CopyCondition
bool IsValidDeconstructor(Item deconstructor)
readonly float OutConditionMin
readonly string InfoTextOnOtherItemMissing
readonly float MaxCondition
readonly string ActivateButtonText
readonly? float caveCommonness
float GetCommonness(Level.TunnelType tunnelType)
CommonnessInfo WithInheritedCommonness(CommonnessInfo? parentInfo)
CommonnessInfo WithInheritedCommonness(params CommonnessInfo?[] parentInfos)
readonly? float abyssCommonness
readonly float commonness
CommonnessInfo(float commonness, float? abyssCommonness, float? caveCommonness)
CommonnessInfo(XElement element)
readonly bool AllowAtStart
FixedQuantityResourceInfo(int clusterQuantity, int clusterSize, bool isIslandSpecific, bool allowAtStart)
readonly int ClusterQuantity
readonly bool IsIslandSpecific
readonly Identifier Skill
SkillRequirementHint(ContentXElement element)
readonly LocalizedString SkillName
LocalizedString GetFormattedText(int skillLevel, string levelColorTag)