262 lines
12 KiB
C#
262 lines
12 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Barotrauma.IO;
|
|
using Barotrauma.Items.Components;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
public class SpreadsheetExport
|
|
{
|
|
private const char separator = ',';
|
|
private const string debugIdentifier = "_spreadsheet";
|
|
|
|
public static void Export()
|
|
{
|
|
XDocument doc = new XDocument();
|
|
if (doc.Root == null)
|
|
{
|
|
doc.Add(new XElement("Content"));
|
|
}
|
|
|
|
XElement root = doc.Root!;
|
|
|
|
foreach (ItemPrefab prefab in ItemPrefab.Prefabs)
|
|
{
|
|
XElement itemElement = new XElement("Item",
|
|
new XAttribute("identifier", prefab.Identifier),
|
|
new XAttribute("name", prefab.Name),
|
|
new XAttribute("tags", FormatArray(prefab.Tags)),
|
|
new XAttribute("value", prefab.DefaultPrice?.Price ?? 0)
|
|
);
|
|
|
|
itemElement.Add(ParseRecipe(prefab));
|
|
itemElement.Add(ParseDecon(prefab));
|
|
itemElement.Add(ParseMedical(prefab));
|
|
itemElement.Add(ParseWeapon(prefab));
|
|
|
|
root.Add(itemElement);
|
|
}
|
|
|
|
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
|
|
{
|
|
Indent = false,
|
|
NewLineOnAttributes = false
|
|
};
|
|
|
|
using XmlWriter? writer = XmlWriter.Create("spreadsheetdata.xml", settings);
|
|
doc.SaveSafe(writer);
|
|
}
|
|
|
|
private static XElement ParseRecipe(ItemPrefab prefab)
|
|
{
|
|
FabricationRecipe? recipe = prefab.FabricationRecipes.Values.FirstOrDefault();
|
|
|
|
List<ItemPrefab> ingredients = recipe?.RequiredItems.SelectMany(ri => ri.ItemPrefabs).Distinct().ToList() ?? new List<ItemPrefab>();
|
|
Skill? skill = recipe?.RequiredSkills.FirstOrDefault();
|
|
|
|
return new XElement("Recipe",
|
|
new XAttribute("amount", recipe?.Amount ?? 0),
|
|
new XAttribute("time", recipe?.RequiredTime ?? 0),
|
|
new XAttribute("skillname", skill?.Identifier.Value ?? ""),
|
|
new XAttribute("skillamount", (int?) skill?.Level ?? 0),
|
|
new XAttribute("ingredients", FormatArray(ingredients.Select(ip => ip.Name))),
|
|
new XAttribute("values", FormatArray(ingredients.Select(ip => ip.DefaultPrice?.Price ?? 0)))
|
|
);
|
|
}
|
|
|
|
private static XElement ParseDecon(ItemPrefab prefab)
|
|
{
|
|
List<ItemPrefab> deconOutput = prefab.DeconstructItems.Select(item => ItemPrefab.Find(null, item.ItemIdentifier)).Where(outputPrefab => outputPrefab != null).ToList();
|
|
return new XElement("Deconstruct",
|
|
new XAttribute("time", prefab.DeconstructTime),
|
|
new XAttribute("outputs", FormatArray(deconOutput.Select(ip => ip.Name))),
|
|
new XAttribute("values", FormatArray(deconOutput.Select(ip => ip.DefaultPrice?.Price ?? 0)))
|
|
);
|
|
}
|
|
|
|
private static XElement ParseMedical(ItemPrefab prefab)
|
|
{
|
|
ContentXElement? itemMeleeWeapon = prefab.ConfigElement.GetChildElement(nameof(MeleeWeapon));
|
|
// affliction, amount, duration
|
|
List<(LocalizedString Name, float Amount, float Duration)> onSuccessAfflictions = new List<(LocalizedString Name, float Amount, float Duration)>();
|
|
List<(LocalizedString Name, float Amount, float Duration)> onFailureAfflictions = new List<(LocalizedString Name, float Amount, float Duration)>();
|
|
int medicalRequiredSkill = 0;
|
|
if (itemMeleeWeapon != null)
|
|
{
|
|
List<StatusEffect> statusEffects = new List<StatusEffect>();
|
|
foreach (var subElement in itemMeleeWeapon.Elements())
|
|
{
|
|
string name = subElement.Name.ToString();
|
|
if (name.Equals(nameof(StatusEffect), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
StatusEffect statusEffect = StatusEffect.Load(subElement, debugIdentifier);
|
|
if (statusEffect == null || !statusEffect.HasTag("medical")) { continue; }
|
|
|
|
statusEffects.Add(statusEffect);
|
|
}
|
|
else if (IsRequiredSkill(subElement, out Skill? skill) && skill != null)
|
|
{
|
|
medicalRequiredSkill = (int) skill.Level;
|
|
}
|
|
}
|
|
|
|
List<StatusEffect> successEffects = statusEffects.Where(se => se.type == ActionType.OnSuccess).ToList();
|
|
List<StatusEffect> failureEffects = statusEffects.Where(se => se.type == ActionType.OnFailure).ToList();
|
|
|
|
foreach (StatusEffect statusEffect in successEffects)
|
|
{
|
|
float duration = statusEffect.Duration;
|
|
onSuccessAfflictions.AddRange(statusEffect.ReduceAffliction.Select(ra => (GetAfflictionName(ra.AfflictionIdentifier), -ra.ReduceAmount, duration)));
|
|
onSuccessAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => (affliction.Prefab.Name, affliction.NonClampedStrength, duration)));
|
|
}
|
|
|
|
foreach (StatusEffect statusEffect in failureEffects)
|
|
{
|
|
float duration = statusEffect.Duration;
|
|
onFailureAfflictions.AddRange(statusEffect.ReduceAffliction.Select(ra => (GetAfflictionName(ra.AfflictionIdentifier), -ra.ReduceAmount, duration)));
|
|
onFailureAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => (affliction.Prefab.Name, affliction.NonClampedStrength, duration)));
|
|
}
|
|
}
|
|
|
|
return new XElement("Medical",
|
|
new XAttribute("skillamount", medicalRequiredSkill),
|
|
new XAttribute("successafflictions", FormatArray(onSuccessAfflictions.Select(tpl => tpl.Item1))),
|
|
new XAttribute("successamounts", FormatArray(onSuccessAfflictions.Select(tpl => FormatFloat(tpl.Item2)))),
|
|
new XAttribute("successdurations", FormatArray(onSuccessAfflictions.Select(tpl => FormatFloat(tpl.Item3)))),
|
|
new XAttribute("failureafflictions", FormatArray(onFailureAfflictions.Select(tpl => tpl.Item1))),
|
|
new XAttribute("failureamounts", FormatArray(onFailureAfflictions.Select(tpl => FormatFloat(tpl.Item2)))),
|
|
new XAttribute("failuredurations", FormatArray(onFailureAfflictions.Select(tpl => FormatFloat(tpl.Item3))))
|
|
);
|
|
}
|
|
|
|
private static XElement ParseWeapon(ItemPrefab prefab)
|
|
{
|
|
float stun = 0;
|
|
bool isAoE = false;
|
|
float? structDamage = null;
|
|
int skillRequirement = 0;
|
|
|
|
// affliction, amount
|
|
List<(LocalizedString Name, float Amount)> damages = new List<(LocalizedString Name, float Amount)>();
|
|
|
|
string[] validNames = { nameof(Projectile), nameof(MeleeWeapon), nameof(RepairTool), nameof(ItemComponent), nameof(RangedWeapon) };
|
|
foreach (var icElement in prefab.ConfigElement.Elements())
|
|
{
|
|
string icName = icElement.Name.ToString();
|
|
if (!validNames.Any(name => icName.Equals(name, StringComparison.OrdinalIgnoreCase))) { continue; }
|
|
|
|
foreach (var icChildElement in icElement.Elements())
|
|
{
|
|
string name = icChildElement.Name.ToString();
|
|
if (IsRequiredSkill(icChildElement, out Skill? skill) && skill != null)
|
|
{
|
|
skillRequirement = (int) skill.Level;
|
|
}
|
|
else if (name.Equals(nameof(Attack), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ParseAttack(new Attack(icChildElement, debugIdentifier));
|
|
}
|
|
else if (name.Equals(nameof(Explosion), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ParseExplosion(new[] { new Explosion(icChildElement, debugIdentifier) });
|
|
}
|
|
else if (name.Equals(nameof(StatusEffect), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ParseStatusEffect(new[] { StatusEffect.Load(icChildElement, debugIdentifier) });
|
|
}
|
|
|
|
void ParseStatusEffect(IEnumerable<StatusEffect> statusEffects)
|
|
{
|
|
foreach (StatusEffect effect in statusEffects)
|
|
{
|
|
if (effect.HasTargetType(StatusEffect.TargetType.Character)) { continue; }
|
|
|
|
ParseAfflictions(effect.Afflictions);
|
|
ParseExplosion(effect.Explosions);
|
|
}
|
|
}
|
|
|
|
void ParseExplosion(IEnumerable<Explosion> explosions)
|
|
{
|
|
foreach (Explosion explosion in explosions)
|
|
{
|
|
isAoE = true;
|
|
ParseAttack(explosion.Attack);
|
|
ParseStatusEffect(explosion.Attack.StatusEffects);
|
|
}
|
|
}
|
|
|
|
void ParseAttack(Attack attack)
|
|
{
|
|
structDamage ??= attack.StructureDamage;
|
|
ParseAfflictions(attack.Afflictions.Keys);
|
|
ParseStatusEffect(attack.StatusEffects);
|
|
}
|
|
|
|
void ParseAfflictions(IEnumerable<Affliction> afflictions)
|
|
{
|
|
foreach (Affliction affliction in afflictions)
|
|
{
|
|
// Exclude stuns
|
|
if (affliction.Prefab == AfflictionPrefab.Stun)
|
|
{
|
|
stun += affliction.NonClampedStrength;
|
|
continue;
|
|
}
|
|
|
|
damages.Add((affliction.Prefab.Name, affliction.NonClampedStrength));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return new XElement("Weapon",
|
|
new XAttribute("damagenames", FormatArray(damages.Select(tpl => tpl.Item1))),
|
|
new XAttribute("damageamounts", FormatArray(damages.Select(tpl => FormatFloat(tpl.Item2)))),
|
|
new XAttribute("isaoe", isAoE),
|
|
new XAttribute("structuredamage", structDamage ?? 0),
|
|
new XAttribute("stun", FormatFloat(stun)),
|
|
new XAttribute("skillrequirement", skillRequirement)
|
|
);
|
|
}
|
|
|
|
private static LocalizedString GetAfflictionName(Identifier identifier)
|
|
{
|
|
return AfflictionPrefab.Prefabs.Find(prefab => prefab.Identifier == identifier)?.Name ?? CultureInfo.CurrentCulture.TextInfo.ToTitleCase(identifier.Value!.ToLower());
|
|
}
|
|
|
|
private static string FormatFloat(float value)
|
|
{
|
|
return value.ToString("0.00", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static string FormatArray<T>(IEnumerable<T> array)
|
|
{
|
|
return string.Join(separator, array);
|
|
}
|
|
|
|
private static bool IsRequiredSkill(ContentXElement element, out Skill? skill)
|
|
{
|
|
string name = element.Name.ToString();
|
|
bool isSkill = name.Equals("RequiredSkill", StringComparison.OrdinalIgnoreCase) ||
|
|
name.Equals("RequiredSkills", StringComparison.OrdinalIgnoreCase);
|
|
|
|
if (isSkill)
|
|
{
|
|
Identifier identifier = element.GetAttributeIdentifier(nameof(Skill.Identifier), Identifier.Empty);
|
|
float level = element.GetAttributeFloat(nameof(Skill.Level).ToLowerInvariant(), 0f);
|
|
skill = new Skill(identifier, level);
|
|
}
|
|
else
|
|
{
|
|
skill = null;
|
|
}
|
|
|
|
return isSkill;
|
|
}
|
|
}
|
|
} |