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;
393 Primary = XMLExtensions.GetAttributeIdentifierArray(element,
"primary", Array.Empty<Identifier>()).ToImmutableHashSet();
394 Secondary = XMLExtensions.GetAttributeIdentifierArray(element,
"secondary", Array.Empty<Identifier>()).ToImmutableHashSet();
396 MinAmount = element.GetAttributeInt(
"minamount", 0);
398 Amount = element.GetAttributeInt(
"amount", 0);
399 MaxCondition = element.GetAttributeFloat(
"maxcondition", 100f);
400 MinCondition = element.GetAttributeFloat(
"mincondition", 0f);
408 MaxLevelDifficulty = element.GetAttributeFloat(nameof(MaxLevelDifficulty),
float.MaxValue);
410 if (element.GetAttribute(
"spawnprobability") ==
null)
418 else if (element.GetAttribute(
"minamount") ==
null && element.GetAttribute(
"maxamount") ==
null && element.GetAttribute(
"amount") ==
null)
441 public List<(Identifier requiredTag, Identifier swapTo)>
ConnectedItemsToSwap =
new List<(Identifier requiredTag, Identifier swapTo)>();
450 price = (int)(price * campaign.Settings.ShipyardPriceMultiplier);
464 foreach (var subElement
in element.Elements())
466 switch (subElement.Name.ToString().ToLowerInvariant())
468 case "schematicsprite":
471 case "swapconnecteditem":
473 (subElement.GetAttributeIdentifier(
"tag",
""),
474 subElement.GetAttributeIdentifier(
"swapto",
"")));
488 public Vector2
Size {
get;
private set; }
492 private ImmutableDictionary<Identifier, PriceInfo> StorePrices {
get;
set; }
494 (StorePrices !=
null && StorePrices.Any(p => p.Value.CanBeBought));
504 public ImmutableArray<Rectangle>
Triggers {
get;
private set; }
506 private ImmutableDictionary<Identifier, float> treatmentSuitability;
575 this.commonness = Math.Max(element?.GetAttributeFloat(
"commonness", 0.0f) ?? 0.0f, 0.0f);
578 XAttribute abyssCommonnessAttribute = element?.GetAttribute(
"abysscommonness") ?? element?.GetAttribute(
"abyss");
579 if (abyssCommonnessAttribute !=
null)
581 abyssCommonness = Math.Max(abyssCommonnessAttribute.GetAttributeFloat(0.0f), 0.0f);
586 XAttribute caveCommonnessAttribute = element?.GetAttribute(
"cavecommonness") ?? element?.GetAttribute(
"cave");
587 if (caveCommonnessAttribute !=
null)
589 caveCommonness = Math.Max(caveCommonnessAttribute.GetAttributeFloat(0.0f), 0.0f);
611 foreach (var parentInfo
in parentInfos)
634 private ImmutableDictionary<Identifier, CommonnessInfo> LevelCommonness {
get;
set; }
652 public ImmutableDictionary<Identifier, FixedQuantityResourceInfo>
LevelQuantity {
get;
private set; }
654 private bool canSpriteFlipX;
657 private bool canSpriteFlipY;
677 private ImmutableHashSet<Identifier> tags;
678 public override ImmutableHashSet<Identifier>
Tags => tags;
680 private ImmutableHashSet<Identifier> allowedLinks;
681 public override ImmutableHashSet<Identifier>
AllowedLinks => allowedLinks;
686 private ImmutableHashSet<string> aliases;
687 public override ImmutableHashSet<string>
Aliases => aliases;
700 [
Serialize(
false,
IsPropertySaveable.No, description:
"Hides the condition bar displayed at the bottom of the inventory slot the item is in.")]
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.")]
734 [
Serialize(
true,
IsPropertySaveable.No, description:
"Are AI characters allowed to deselect the item when they're idling (and wander off?).")]
737 private float health;
742 get {
return health; }
747 health = Math.Min(value, 1000000.0f);
784 private float impactTolerance;
788 get {
return impactTolerance; }
789 set { impactTolerance = Math.Max(value, 0.0f); }
862 private int maxStackSize;
866 get {
return maxStackSize; }
870 private int maxStackSizeCharacterInventory;
874 get {
return maxStackSizeCharacterInventory; }
878 private int maxStackSizeHoldableOrWearableInventory;
880 "Maximum stack size when the item is inside a holdable or wearable item. "+
881 "If not set, defaults to MaxStackSizeCharacterInventory.")]
884 get {
return maxStackSizeHoldableOrWearableInventory; }
890 int extraStackSize = inventory
switch
894 i.ExtraStackSize + EnumExtensions.GetIndividualFlags(
Category).Sum(c => (
int)info.GetSavedStatValueWithAll(
StatTypes.InventoryExtraStackSize, c.ToIdentifier())),
901 return MaxStackWithExtra(maxStackSizeCharacterInventory, extraStackSize);
903 else if (inventory?.Owner is
Item item &&
904 (item.GetComponent<
Holdable>() is { Attachable: false } || item.GetComponent<
Wearable>() !=
null))
906 if (maxStackSizeHoldableOrWearableInventory > 0)
908 return MaxStackWithExtra(maxStackSizeHoldableOrWearableInventory, extraStackSize);
910 else if (maxStackSizeCharacterInventory > 0)
913 return MaxStackWithExtra(maxStackSizeCharacterInventory, extraStackSize);
917 return MaxStackWithExtra(maxStackSize, extraStackSize);
919 static int MaxStackWithExtra(
int maxStackSize,
int extraStackSize)
921 extraStackSize = Math.Max(extraStackSize, 0);
922 if (maxStackSize == 1)
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.")]
947 [
Serialize(
false,
IsPropertySaveable.No, description:
"Should the bots shoot at this item with turret or not? Disabled by default.")]
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.")]
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.")]
956 [
Serialize(
float.PositiveInfinity,
IsPropertySaveable.No, description:
"The max distance at which the bots are allowed to target the items. Defaults to infinity.")]
959 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"If enabled, taking items from this container is never considered stealing.")]
965 [
Serialize(
false,
IsPropertySaveable.No, description:
"If enabled, the player is unable to open the middle click menu when this item is selected.")]
970 Identifier identifier = base.DetermineIdentifier(element);
971 string originalName = element.GetAttributeString(
"name",
"");
972 if (identifier.IsEmpty && !
string.IsNullOrEmpty(originalName))
974 string categoryStr = element.GetAttributeString(
"category",
"Misc");
985 return ($
"legacyitem_{name.Replace(" ", "")}").ToIdentifier();
990 originalElement = element;
1000 ParseConfigElement(variantOf:
null);
1004 => subElement.DoesAttributeReferenceFileNameAlone(
"texture")
1008 private void ParseConfigElement(
ItemPrefab variantOf)
1011 this.category = Enum.TryParse(categoryStr,
true, out
MapEntityCategory category)
1021 name = TextManager.Get(nameIdentifier.IsEmpty
1022 ? $
"EntityName.{Identifier}"
1023 : $
"EntityName.{nameIdentifier}",
1024 $
"EntityName.{fallbackNameIdentifier}");
1032 name = TextManager.GetWithVariable(
"wreckeditemformat",
"[name]", name);
1040 .ToImmutableHashSet()
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>();
1060 tags.Add(
"light".ToIdentifier());
1062 this.tags = tags.ToImmutableHashSet();
1066 DebugConsole.ThrowError($
"Error in item prefab \"{ToString()}\" - cargo container should be configured using the item's identifier, not the name.",
1070 SerializableProperty.DeserializeProperties(
this,
ConfigElement);
1073 var skillRequirementHints =
new List<SkillRequirementHint>();
1076 skillRequirementHints.Add(
new SkillRequirementHint(skillRequirementHintElement));
1078 if (skillRequirementHints.Any())
1087 var levelCommonness =
new Dictionary<Identifier, CommonnessInfo>();
1088 var levelQuantity =
new Dictionary<Identifier, FixedQuantityResourceInfo>();
1090 List<FabricationRecipe> loadedRecipes =
new List<FabricationRecipe>();
1093 switch (subElement.Name.ToString().ToLowerInvariant())
1096 string spriteFolder = GetTexturePath(subElement, variantOf);
1098 canSpriteFlipX = subElement.GetAttributeBool(
"canflipx",
true);
1099 canSpriteFlipY = subElement.GetAttributeBool(
"canflipy",
true);
1101 sprite =
new Sprite(subElement, spriteFolder, lazyLoad:
true);
1102 if (subElement.GetAttribute(
"sourcerect") ==
null &&
1103 subElement.GetAttribute(
"sheetindex") ==
null)
1105 DebugConsole.ThrowError($
"Warning - sprite sourcerect not configured for item \"{ToString()}\"!",
1110 if (subElement.GetAttribute(
"name") ==
null && !
Name.IsNullOrWhiteSpace())
1117 if (subElement.GetAttribute(
"baseprice") !=
null)
1119 foreach (var priceInfo
in PriceInfo.CreatePriceInfos(subElement, out defaultPrice))
1121 if (priceInfo.StoreIdentifier.IsEmpty) {
continue; }
1122 if (storePrices.ContainsKey(priceInfo.StoreIdentifier))
1124 DebugConsole.AddWarning($
"Error in item prefab \"{this}\": price for the store \"{priceInfo.StoreIdentifier}\" defined more than once.",
1126 storePrices[priceInfo.StoreIdentifier] = priceInfo;
1130 storePrices.Add(priceInfo.StoreIdentifier, priceInfo);
1134 else if (subElement.GetAttribute(
"buyprice") !=
null && subElement.GetAttributeIdentifier(
"locationtype",
"") is { IsEmpty: false } locationType)
1136 if (storePrices.ContainsKey(locationType))
1138 DebugConsole.AddWarning($
"Error in item prefab \"{this}\": price for the location type \"{locationType}\" defined more than once.",
1140 storePrices[locationType] =
new PriceInfo(subElement);
1144 storePrices.Add(locationType,
new PriceInfo(subElement));
1153 foreach (XElement itemElement
in subElement.Elements())
1155 if (itemElement.Attribute(
"name") !=
null)
1157 DebugConsole.ThrowError($
"Error in item config \"{ToString()}\" - use item identifiers instead of names to configure the deconstruct items.",
1161 var deconstructItem =
new DeconstructItem(itemElement,
Identifier);
1162 if (deconstructItem.ItemIdentifier.IsEmpty)
1164 DebugConsole.ThrowError($
"Error in item config \"{ToString()}\" - deconstruction output contains an item with no identifier.",
1168 deconstructItems.Add(deconstructItem);
1174 case "fabricableitem":
1175 var newRecipe =
new FabricationRecipe(subElement,
Identifier);
1176 if (fabricationRecipes.TryGetValue(newRecipe.RecipeHash, out var prevRecipe))
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);
1193 fabricationRecipes.Add(newRecipe.RecipeHash, newRecipe);
1195 loadedRecipes.Add(newRecipe);
1197 case "preferredcontainer":
1198 var preferredContainer =
new PreferredContainer(subElement);
1199 if (preferredContainer.Primary.Count == 0 && preferredContainer.Secondary.Count == 0)
1202 if (variantOf ==
null)
1204 DebugConsole.ThrowError($
"Error in item prefab \"{ToString()}\": preferred container has no preferences defined ({subElement}).",
1210 preferredContainers.Add(preferredContainer);
1213 case "swappableitem":
1217 Rectangle trigger =
new Rectangle(0, 0, 10, 10)
1219 X = subElement.GetAttributeInt(
"x", 0),
1220 Y = subElement.GetAttributeInt(
"y", 0),
1221 Width = subElement.GetAttributeInt(
"width", 0),
1222 Height = subElement.GetAttributeInt(
"height", 0)
1225 triggers.Add(trigger);
1228 case "levelresource":
1229 foreach (XElement levelCommonnessElement
in subElement.GetChildElements(
"commonness"))
1231 Identifier levelName = levelCommonnessElement.GetAttributeIdentifier(
"leveltype",
"");
1232 if (!levelCommonnessElement.GetAttributeBool(
"fixedquantity",
false))
1234 if (!levelCommonness.ContainsKey(levelName))
1236 levelCommonness.Add(levelName,
new CommonnessInfo(levelCommonnessElement));
1241 if (!levelQuantity.ContainsKey(levelName))
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)));
1252 case "suitabletreatment":
1253 if (subElement.GetAttribute(
"name") !=
null)
1255 DebugConsole.ThrowError($
"Error in item prefab \"{ToString()}\" - suitable treatments should be defined using item identifiers, not item names.",
1258 Identifier treatmentIdentifier = subElement.GetAttributeIdentifier(
"identifier", subElement.GetAttributeIdentifier(
"type",
Identifier.Empty));
1259 float suitability = subElement.GetAttributeFloat(
"suitability", 0.0f);
1260 treatmentSuitability.Add(treatmentIdentifier, suitability);
1271 this.
Triggers = triggers.ToImmutableArray();
1274 this.treatmentSuitability = treatmentSuitability.ToImmutableDictionary();
1275 StorePrices = storePrices.ToImmutableDictionary();
1277 this.LevelCommonness = levelCommonness.ToImmutableDictionary();
1281 if (storePrices.Any())
1283 defaultPrice ??=
new PriceInfo(
GetMinPrice() ?? 0,
false);
1289 if (categoryStr.Equals(
"Thalamus", StringComparison.OrdinalIgnoreCase))
1299 this.sprite =
new Sprite(
"", Vector2.Zero);
1300 this.sprite.
SourceRect =
new Rectangle(0, 0, 32, 32);
1302 this.sprite =
new Sprite(TextureLoader.PlaceHolderTexture,
null,
null)
1304 Origin = TextureLoader.PlaceHolderTexture.Bounds.Size.ToVector2() / 2
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.",
1323 DebugConsole.AddWarning($
"Item \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.",
1343 if (levelCommonnessInfo.HasValue)
1347 else if (biomeCommonnessInfo.HasValue)
1351 else if (defaultCommonnessInfo.HasValue)
1353 return defaultCommonnessInfo;
1360 if (LevelCommonness.TryGetValue(identifier, out
CommonnessInfo info))
1373 return treatmentSuitability.TryGetValue(treatmentIdentifier, out
float suitability) ? suitability : 0.0f;
1382 string message = $
"Tried to get price info for \"{Identifier}\" with a null store parameter!\n{Environment.StackTrace.CleanupStackTrace()}";
1387 GameAnalyticsManager.AddErrorEventOnce(
"ItemPrefab.GetPriceInfo:StoreParameterNull", GameAnalyticsManager.ErrorSeverity.Error, message);
1391 else if (!store.Identifier.IsEmpty && StorePrices !=
null && StorePrices.TryGetValue(store.Identifier, out var storePriceInfo))
1393 return storePriceInfo;
1404 Identifier? faction = store?.Location.Faction?.Prefab.Identifier;
1405 Identifier? secondaryFaction = store?.Location.SecondaryFaction?.Prefab.Identifier;
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));
1416 if (location?.Stores ==
null) {
return false; }
1417 foreach (var store
in location.Stores)
1420 if (priceInfo ==
null) {
continue; }
1421 if (!priceInfo.CanBeBought) {
continue; }
1422 if (location.LevelData.Difficulty < priceInfo.MinLevelDifficulty) {
continue; }
1423 if (priceInfo.MinReputation.Any())
1425 if (!priceInfo.MinReputation.Any(p =>
1426 location?.Faction?.Prefab.Identifier == p.Key ||
1427 location?.SecondaryFaction?.Prefab.Identifier == p.Key))
1439 int? minPrice =
null;
1440 if (StorePrices !=
null && StorePrices.Any())
1442 minPrice = StorePrices.Values.Min(p => p.Price);
1444 if (minPrice.HasValue)
1452 return minPrice.Value;
1463 var prices =
new Dictionary<Identifier, PriceInfo>();
1464 if (StorePrices !=
null)
1466 foreach (var storePrice
in StorePrices)
1468 var priceInfo = storePrice.Value;
1469 if (priceInfo ==
null)
1473 if (!priceInfo.CanBeBought)
1477 if (priceInfo.Price < maxCost || maxCost == 0)
1479 prices.Add(storePrice.Key, priceInfo);
1483 return prices.ToImmutableDictionary();
1486 public ImmutableDictionary<Identifier, PriceInfo>
GetSellPricesOver(
int minCost = 0,
bool sellingImportant =
true)
1488 var prices =
new Dictionary<Identifier, PriceInfo>();
1491 return prices.ToImmutableDictionary();
1493 foreach (var storePrice
in StorePrices)
1495 var priceInfo = storePrice.Value;
1496 if (priceInfo ==
null)
1500 if (priceInfo.Price > minCost)
1502 prices.Add(storePrice.Key, priceInfo);
1505 return prices.ToImmutableDictionary();
1512 if (
string.IsNullOrEmpty(name) && identifier.IsEmpty)
1514 throw new ArgumentException(
"Both name and identifier cannot be null.");
1517 if (identifier.IsEmpty)
1525 if (prefab ==
null && !
string.IsNullOrEmpty(name))
1527 string lowerCaseName = name.ToLowerInvariant();
1528 prefab =
Prefabs.Find(me => me.Aliases !=
null && me.Aliases.Contains(lowerCaseName));
1532 prefab =
Prefabs.Find(me => me.Aliases !=
null && me.Aliases.Contains(identifier.Value));
1537 DebugConsole.ThrowError($
"Error loading item - item prefab \"{name}\" (identifier \"{identifier}\") not found.");
1542 public bool IsContainerPreferred(
Item item,
ItemContainer targetContainer, out
bool isPreferencesDefined, out
bool isSecondary,
bool requireConditionRequirement =
false,
bool checkTransferConditions =
false)
1545 isSecondary =
false;
1546 if (!isPreferencesDefined) {
return true; }
1547 if (
PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
1553 return PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
IsContainerPreferred(pc.Secondary, targetContainer));
1560 isSecondary =
false;
1561 if (!isPreferencesDefined) {
return true; }
1570 private static bool IsItemConditionAcceptable(
Item item,
PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
1572 pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item));
1575 public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, IEnumerable<Identifier> ids) => ids.Any(
id => preferences.Contains(
id));
1579 throw new InvalidOperationException(
"Can't call ItemPrefab.CreateInstance");
1593 ParseConfigElement(parent);
1595 void CheckXML(XElement originalElement, XElement variantElement, XElement result)
1600 if (result ==
null) {
return; }
1601 if (result.Name.ToIdentifier() ==
"RequiredItem" &&
1602 result.Parent?.Name.ToIdentifier() ==
"Fabricate")
1604 int originalAmount = originalElement.GetAttributeInt(
"amount", 1);
1605 Identifier originalIdentifier = originalElement.GetAttributeIdentifier(
"identifier",
Identifier.Empty);
1606 if (variantElement ==
null)
1609 if (this.originalElement.GetChildElement(
"Fabricate")?.GetChildElement(
"RequiredItem") !=
null)
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.",
1620 if (originalAmount > 1 && variantElement.GetAttribute(
"amount") ==
null)
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.",
1629 if (originalElement?.
Name.ToIdentifier() ==
"Deconstruct" &&
1630 variantElement?.Name.ToIdentifier() ==
"Deconstruct")
1632 if (originalElement.Elements().Any(e => e.Name.ToIdentifier() ==
"Item") &&
1633 variantElement.Elements().Any(e => e.Name.ToIdentifier() ==
"RequiredItem"))
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.",
1639 if (variantElement.Elements().Any(e => e.Name.ToIdentifier() ==
"Item") &&
1640 originalElement.Elements().Any(e => e.Name.ToIdentifier() ==
"RequiredItem"))
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.",
1667 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)
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)
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 ContentPackage VanillaContent
static GameSession GameSession
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.
bool DamagedByContainedItemExplosions
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 AllowDeselectWhenIdling
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)