├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── main.yml ├── .gitignore ├── App.razor ├── Components ├── AddInventoryElementDialog.razor ├── CharacterSelect.razor ├── ColorPicker.razor ├── ColorTagElement.razor ├── FetishField.razor ├── FetishNumericField.razor ├── FloatField.razor ├── IntField.razor ├── InventoryElementSelect.razor ├── LegRelatedValueDisplayPairSelect.razor ├── LegTypeValueDisplaySelect.razor ├── ParentField.razor ├── PerkCheckbox.razor ├── ProxyValueDisplayPairSelect.razor ├── SearchableCharacterSelect.razor ├── SpellSelect.razor └── ValueDisplayPairSelect.razor ├── ExtensionMethods ├── DateExtensionMethods.cs ├── GeneralExtensionMethods.cs └── XDocumentExtensionMethods.cs ├── LICENSE ├── LTSaveEd.csproj ├── LTSaveEd.sln ├── Layout ├── BaseModEditorPage.cs ├── BaseSaveEditorPage.razor ├── BreastsLayout.razor ├── MainLayout.razor └── MainLayout.razor.css ├── Models ├── ApplicationState.cs ├── Character.cs ├── CharacterData │ ├── Attributes.cs │ ├── Body.cs │ ├── BodyData │ │ ├── Ass.cs │ │ ├── AssData │ │ │ ├── Anus.cs │ │ │ ├── AssComponent.cs │ │ │ ├── Leg.cs │ │ │ └── LegComponentType.cs │ │ ├── BodyComponent.cs │ │ ├── BodyComponentModifier.cs │ │ ├── BodyCore.cs │ │ ├── Breasts.cs │ │ ├── BreastsData │ │ │ ├── BreastsComponent.cs │ │ │ ├── Milk.cs │ │ │ └── Nipples.cs │ │ ├── Face.cs │ │ ├── FaceData │ │ │ ├── Eye.cs │ │ │ ├── FaceComponent.cs │ │ │ ├── Mouth.cs │ │ │ └── Tongue.cs │ │ ├── Head.cs │ │ ├── HeadData │ │ │ ├── Antennae.cs │ │ │ ├── Ear.cs │ │ │ ├── Hair.cs │ │ │ └── Horn.cs │ │ ├── LegTypeValueDisplayPair.cs │ │ ├── Penis.cs │ │ ├── PenisData │ │ │ ├── Cum.cs │ │ │ ├── PenisComponent.cs │ │ │ └── Testicles.cs │ │ ├── Torso.cs │ │ ├── TorsoData │ │ │ ├── Arm.cs │ │ │ ├── Spinneret.cs │ │ │ ├── Tail.cs │ │ │ ├── Tentacle.cs │ │ │ ├── TorsoComponent.cs │ │ │ └── Wing.cs │ │ ├── Vagina.cs │ │ └── VaginaData │ │ │ ├── GirlCum.cs │ │ │ └── VaginaComponent.cs │ ├── Collections.cs │ ├── Core.cs │ ├── Family.cs │ ├── Fetish.cs │ ├── FetishOwnedAttribute.cs │ ├── Fetishes.cs │ ├── Inventory.cs │ ├── InventoryData │ │ ├── Clothes │ │ │ ├── Clothing.cs │ │ │ ├── ClothingData.cs │ │ │ └── ClothingType.cs │ │ ├── InventoryElement.cs │ │ ├── InventoryElementData.cs │ │ ├── InventoryElementType.cs │ │ ├── Items │ │ │ ├── Item.cs │ │ │ ├── ItemData.cs │ │ │ └── ItemType.cs │ │ └── Weapons │ │ │ ├── Weapon.cs │ │ │ ├── WeaponData.cs │ │ │ └── WeaponType.cs │ ├── Name.cs │ ├── Parent.cs │ ├── PerkNode.cs │ ├── Perks.cs │ ├── Personality.cs │ ├── PersonalityTrait.cs │ ├── Relationship.cs │ ├── Relationships.cs │ ├── SpellData │ │ ├── AirSpells.cs │ │ ├── ArcaneSpells.cs │ │ ├── EarthSpells.cs │ │ ├── ElementalSpells.cs │ │ ├── FireSpells.cs │ │ ├── MiscSpells.cs │ │ ├── NullableSpell.cs │ │ ├── Spell.cs │ │ ├── SpellTier.cs │ │ └── WaterSpells.cs │ └── Spells.cs ├── CharacterShort.cs ├── CharacterShortData.cs ├── Enums │ ├── ApplicationStateLocation.cs │ └── Game │ │ ├── ColorTag.cs │ │ ├── CombatMoveCategory.cs │ │ ├── CombatMoveType.cs │ │ ├── DamageType.cs │ │ ├── DamageVariance.cs │ │ ├── Femininity.cs │ │ ├── InventorySlot.cs │ │ ├── PresetColor.cs │ │ └── Rarity.cs ├── JSWrappers │ ├── FileHandler.cs │ ├── JsWrapper.cs │ └── LocalStorageAccessor.cs ├── ModEditor │ ├── ClothingMod.cs │ ├── ColorMod.cs │ ├── CombatMoveMod.cs │ ├── Mod.cs │ └── Xml │ │ ├── ColorTagsElement.cs │ │ ├── EnumXml │ │ ├── CombatMoveCategoryElement.cs │ │ ├── CombatMoveTypeElement.cs │ │ ├── DamageTypeElement.cs │ │ ├── DamageVarianceElement.cs │ │ ├── FemininityElement.cs │ │ ├── InventorySlotElement.cs │ │ ├── PresetColorElement.cs │ │ └── RarityElement.cs │ │ └── EquipSlotElements.cs ├── Offspring.cs ├── Offsprings.cs ├── SaveData.cs ├── Settings.cs ├── SettingsKey.cs ├── ValueDisplayPair.cs ├── World.cs └── XmlData │ ├── LabeledXmlAttribute.cs │ ├── NullableXmlElement.cs │ ├── NullableXmlObject.cs │ ├── XmlAttribute.cs │ ├── XmlCData.cs │ ├── XmlDate.cs │ ├── XmlElement.cs │ └── XmlEnum.cs ├── Pages ├── Attributes.razor ├── Body │ ├── Ass.razor │ ├── BodyCore.razor │ ├── Breasts.razor │ ├── BreastsCrotch.razor │ ├── Face.razor │ ├── Head.razor │ ├── Penis.razor │ ├── Torso.razor │ └── Vagina.razor ├── Core.razor ├── Extra.razor ├── Family.razor ├── Fetishes.razor ├── Home.razor ├── Index.razor ├── Inventory │ ├── Clothing.razor │ ├── Items.razor │ └── Weapons.razor ├── ModEditor │ ├── Color.razor │ └── Home.razor ├── Offsprings.razor ├── Perks.razor ├── Relationships.razor ├── Spells │ ├── Air.razor │ ├── Arcane.razor │ ├── Earth.razor │ ├── Fire.razor │ ├── Misc.razor │ └── Water.razor └── World.razor ├── Program.cs ├── Properties └── launchSettings.json ├── README.md ├── Scripts ├── EnumToArray.py └── GenerateInventoryElementMaps.py ├── _Imports.razor ├── requirements.txt └── wwwroot ├── css ├── app.css └── bootstrap │ ├── bootstrap.min.css │ └── bootstrap.min.css.map ├── favicon.png ├── icon-192.png ├── index.html └── js ├── FileHandler.js └── LocalStorageAccessor.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: Exiua 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional Information (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser: [e.g. chrome, safari] 26 | - Version: [e.g. 22] 27 | - Upload a copy of the save file in which this issue occurred 28 | - Whether the save file is modded 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | # Run workflow on every push to the master branch 3 | on: 4 | push: 5 | branches: [ master ] 6 | jobs: 7 | deploy-to-github-pages: 8 | # use ubuntu-latest image to run steps on 9 | runs-on: ubuntu-latest 10 | steps: 11 | # uses GitHub's checkout action to checkout code form the Blazor branch 12 | - uses: actions/checkout@v4 13 | 14 | # fix js interop paths 15 | - name: Fix JS Filepaths 16 | run: | 17 | sed -i 's|/js/LocalStorageAccessor.js|/LTSaveEd/js/LocalStorageAccessor.js|' ./Models/JSWrappers/LocalStorageAccessor.cs 18 | sed -i 's|/js/FileHandler.js|/LTSaveEd/js/FileHandler.js|' ./Models/JSWrappers/FileHandler.cs 19 | 20 | # sets up .NET Core SDK 9.0 21 | - name: Setup .NET Core SDK 22 | uses: actions/setup-dotnet@v4 23 | with: 24 | dotnet-version: 9.0 25 | 26 | # publishes Blazor project to the release-folder 27 | - name: Publish .NET Core Project 28 | run: dotnet publish LTSaveEd.csproj -c Release -o release --nologo 29 | 30 | # changes the base-tag in index.html from '/' to 'LTSaveEd' to match GitHub Pages repository subdirectory 31 | - name: Change base-tag in index.html from / to LTSaveEd 32 | run: sed -i 's///g' release/wwwroot/index.html 33 | 34 | # copy index.html to 404.html to serve the same file when a file is not found 35 | - name: Copy index.html to 404.html 36 | run: cp release/wwwroot/index.html release/wwwroot/404.html 37 | 38 | # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) 39 | - name: Add .nojekyll file 40 | run: touch release/wwwroot/.nojekyll 41 | - name: Commit wwwroot to GitHub Pages 42 | uses: JamesIves/github-pages-deploy-action@v4 43 | with: 44 | branch: gh-pages 45 | folder: release/wwwroot 46 | -------------------------------------------------------------------------------- /App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 | Sorry, there's nothing at this address. 10 | 11 | 12 | -------------------------------------------------------------------------------- /Components/AddInventoryElementDialog.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | @using LTSaveEd.Models.CharacterData.InventoryData 3 | 4 | @typeparam TEnum where TEnum : Enum 5 | @typeparam TElement where TElement : InventoryElementData 6 | 7 | 8 | 9 | 10 | @foreach(var type in Types) 11 | { 12 | 13 | } 14 | 15 | 16 | @foreach(var item in TypeMap[SelectedType.Value]) 17 | { 18 | 19 | } 20 | 21 | 22 | 23 | Cancel 24 | Ok 25 | 26 | 27 | 28 | @code { 29 | private ValueDisplayPair _selectedType = null!; 30 | 31 | [CascadingParameter] public required IMudDialogInstance MudDialog { get; set; } 32 | [Parameter] public required ValueDisplayPair[] Types { get; set; } 33 | [Parameter] public required Dictionary TypeMap { get; set; } 34 | 35 | private ValueDisplayPair SelectedType 36 | { 37 | get => _selectedType; 38 | set 39 | { 40 | _selectedType = value; 41 | SelectedElement = TypeMap[_selectedType.Value][0]; 42 | } 43 | } 44 | 45 | private TElement SelectedElement { get; set; } = null!; 46 | 47 | protected override void OnInitialized() 48 | { 49 | _selectedType = Types[0]; 50 | SelectedElement = TypeMap[SelectedType.Value][0]; 51 | } 52 | 53 | private void Submit() => MudDialog.Close(DialogResult.Ok(SelectedElement)); 54 | private void Cancel() => MudDialog.Cancel(); 55 | } -------------------------------------------------------------------------------- /Components/CharacterSelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | 3 | 4 | @foreach (var value in Values) 5 | { 6 | 7 | } 8 | 9 | 10 | @code { 11 | // Used as a intermediary to allow proper two-way binding. 12 | private ValueDisplayPair _currentValue = null!; 13 | 14 | /// 15 | /// The list of character Name-Id pairs to display in the select. 16 | /// 17 | [Parameter] public required IEnumerable> Values { get; set; } 18 | 19 | /// 20 | /// The currently selected character Name-Id pair. 21 | /// 22 | [Parameter] public required ValueDisplayPair CharacterId { get; set; } 23 | 24 | /// 25 | /// The event that is fired when the selected character Name-Id pair changes. 26 | /// 27 | [Parameter] public EventCallback> CharacterIdChanged { get; set; } 28 | 29 | private ValueDisplayPair CurrentValue 30 | { 31 | get => _currentValue; 32 | set 33 | { 34 | // Update the internal value and fire the event to update the bound value. 35 | _currentValue = value; 36 | CharacterIdChanged.InvokeAsync(value); 37 | } 38 | } 39 | 40 | protected override void OnInitialized() 41 | { 42 | // Set the initial value to the parameter value. 43 | _currentValue = CharacterId; 44 | } 45 | } -------------------------------------------------------------------------------- /Components/ColorPicker.razor: -------------------------------------------------------------------------------- 1 | @using MudBlazor.Utilities 2 | 3 | 4 | 5 | @code { 6 | private MudColor _color = MudColor.Parse("#594ae2"); 7 | 8 | [Parameter] public required string Label { get; set; } 9 | [Parameter] public required string Value { get; set; } 10 | [Parameter] public EventCallback ValueChanged { get; set; } 11 | [Parameter] public bool Disabled { get; set; } 12 | 13 | private MudColor Color 14 | { 15 | get => _color; 16 | set 17 | { 18 | _color = value; 19 | var newValue = _color.ToString(MudColorOutputFormats.Hex)[1..]; // Remove the '#' character 20 | ValueChanged.InvokeAsync(newValue); 21 | } 22 | } 23 | 24 | protected override void OnInitialized() 25 | { 26 | if (string.IsNullOrEmpty(Value)) 27 | { 28 | Value = "594ae2"; // Default color 29 | } 30 | 31 | Color = MudColor.Parse($"#" + Value); 32 | } 33 | 34 | protected override void OnParametersSet() 35 | { 36 | if (MudColor.TryParse("#" + Value, out var parsedColor)) 37 | { 38 | _color = parsedColor; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Components/ColorTagElement.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models.Enums.Game 2 | @using LTSaveEd.Models.ModEditor 3 | @using LTSaveEd.Models.ModEditor.Xml 4 | 5 | 6 | 7 | @code { 8 | private bool _checked; 9 | 10 | [Parameter] public required string Label { get; set; } 11 | [Parameter] public required ColorTag ColorTag { get; set; } 12 | [Parameter] public required ColorTagsElement ColorTagsElement { get; set; } 13 | 14 | public bool Checked 15 | { 16 | get => _checked; 17 | set 18 | { 19 | _checked = value; 20 | if (value) 21 | { 22 | ColorTagsElement.AddTag(ColorTag); 23 | } 24 | else 25 | { 26 | ColorTagsElement.RemoveTag(ColorTag); 27 | } 28 | } 29 | } 30 | 31 | protected override void OnInitialized() 32 | { 33 | base.OnInitialized(); 34 | Checked = ColorTagsElement.HasTag(ColorTag); 35 | } 36 | 37 | protected override void OnParametersSet() 38 | { 39 | base.OnParametersSet(); 40 | // Ensure the checkbox reflects the current state of the ColorTagsElement 41 | Checked = ColorTagsElement.HasTag(ColorTag); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /Components/FetishField.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models.CharacterData 2 | 3 | 4 | 5 | 6 | @Label 7 | 8 | 9 | 10 | 11 | Desire: 12 | 13 | 14 | 15 | Love 16 | Like 17 | Indifferent 18 | Dislike 19 | Hate 20 | 21 | 22 | 23 | Exp: 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | @code { 35 | /// 36 | /// The fetish component of the character being edited. 37 | /// 38 | [Parameter] public required Fetish Fetish { get; set; } 39 | 40 | /// 41 | /// The label to display for this component. 42 | /// 43 | [Parameter] public required string Label { get; set; } 44 | } -------------------------------------------------------------------------------- /Components/FetishNumericField.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | @using LTSaveEd.Models.CharacterData 3 | 4 | 5 | 6 | @Label 7 | 8 | 9 | 10 | 11 | Exp: 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @code { 20 | /// 21 | /// The fetish component of the character being edited. 22 | /// 23 | [Parameter] public required Fetish Fetish { get; set; } 24 | 25 | /// 26 | /// The label to display for the component. 27 | /// 28 | [Parameter] public required string Label { get; set; } 29 | } -------------------------------------------------------------------------------- /Components/FloatField.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | @using LTSaveEd.Models.XmlData 3 | 4 | 6 | 7 | @code { 8 | /// 9 | /// The float XmlAttribute to bind to. 10 | /// 11 | [Parameter] public required XmlAttribute Attribute { get; set; } 12 | 13 | /// 14 | /// The label of this component to display. 15 | /// 16 | [Parameter] public required string Label { get; set; } 17 | 18 | /// 19 | /// Whether to only allow values >= 0. 20 | /// 21 | [Parameter] public bool PositiveOnly { get; set; } = true; 22 | 23 | /// 24 | /// The maximum value allowed. 25 | /// 26 | [Parameter] public float Max { get; set; } = float.MaxValue; 27 | 28 | /// 29 | /// The minimum value allowed. 30 | /// 31 | [Parameter] public float Min { get; set; } = float.MinValue; 32 | 33 | private bool _generateHelperLabels; 34 | 35 | private float Value 36 | { 37 | get => Attribute.Value; 38 | set 39 | { 40 | if (PositiveOnly && value < 0) 41 | { 42 | return; 43 | } 44 | 45 | Attribute.Value = value; 46 | } 47 | } 48 | 49 | private string HelperLabel => _generateHelperLabels ? ((LabeledXmlAttribute)Attribute).Label : ""; 50 | 51 | protected override void OnInitialized() 52 | { 53 | // Label is only generated if the attribute is a LabeledXmlAttribute 54 | _generateHelperLabels = Attribute is LabeledXmlAttribute; 55 | } 56 | } -------------------------------------------------------------------------------- /Components/IntField.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | @using LTSaveEd.Models.XmlData 3 | 4 | 6 | 7 | @code { 8 | 9 | /// 10 | /// The int XmlAttribute to bind to. 11 | /// 12 | [Parameter] public required XmlAttribute Attribute { get; set; } 13 | 14 | /// 15 | /// The label of this component to display. 16 | /// 17 | [Parameter] public required string Label { get; set; } 18 | 19 | /// 20 | /// Whether or not to only allow values >= 0. 21 | /// 22 | [Parameter] public bool PositiveOnly { get; set; } = true; 23 | 24 | /// 25 | /// The maximum value allowed. 26 | /// 27 | [Parameter] public int Max { get; set; } = int.MaxValue; 28 | 29 | /// 30 | /// The minimum value allowed. 31 | /// 32 | [Parameter] public int Min { get; set; } = int.MinValue; 33 | 34 | private bool _generateHelperLabels; 35 | 36 | private float Value 37 | { 38 | get => Attribute.Value; 39 | set 40 | { 41 | if (PositiveOnly && value < 0) 42 | { 43 | return; 44 | } 45 | 46 | Attribute.Value = (int)value; 47 | } 48 | } 49 | 50 | private string HelperLabel => _generateHelperLabels ? ((LabeledXmlAttribute)Attribute).Label : ""; 51 | 52 | protected override void OnInitialized() 53 | { 54 | // Label is only generated if the attribute is a LabeledXmlAttribute 55 | _generateHelperLabels = Attribute is LabeledXmlAttribute; 56 | } 57 | } -------------------------------------------------------------------------------- /Components/InventoryElementSelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models.CharacterData.InventoryData 2 | 3 | 5 | 6 | @code { 7 | private InventoryElementData _currentValue = null!; 8 | 9 | [Parameter] public required string Label { get; set; } 10 | [Parameter] public required IEnumerable Values { get; set; } 11 | [Parameter] public required string ElementId { get; set; } 12 | [Parameter] public EventCallback ElementIdChanged { get; set; } 13 | [Parameter] public int? MaxDisplayedItems { get; set; } = 20; 14 | 15 | private InventoryElementData CurrentValue 16 | { 17 | get => _currentValue; 18 | set 19 | { 20 | _currentValue = value; 21 | ElementIdChanged.InvokeAsync(_currentValue.Value); 22 | } 23 | } 24 | 25 | protected override void OnInitialized() 26 | { 27 | foreach (var value in Values) 28 | { 29 | if (value.Value != ElementId) 30 | { 31 | continue; 32 | } 33 | 34 | _currentValue = value; 35 | break; 36 | } 37 | } 38 | 39 | private Task> Search(string value, CancellationToken cancellationToken) 40 | { 41 | if (string.IsNullOrEmpty(value)) 42 | { 43 | return Task.FromResult(Values); 44 | } 45 | 46 | // Search for similar values to the search term in the list of values. 47 | return Task.FromResult(Values.Where(x => x.DisplayValue.Contains(value, StringComparison.InvariantCultureIgnoreCase))); 48 | } 49 | } -------------------------------------------------------------------------------- /Components/LegTypeValueDisplaySelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models.CharacterData.BodyData 2 | @using System.Diagnostics.CodeAnalysis 3 | @using LTSaveEd.Models.CharacterData.BodyData.AssData 4 | 5 | 6 | @foreach (var value in Values) 7 | { 8 | 9 | } 10 | 11 | 12 | @code { 13 | private LegTypeValueDisplayPair _currentValue = null!; 14 | 15 | /// 16 | /// List of possible values for the leg type. 17 | /// 18 | [Parameter] public required IEnumerable Values { get; set; } 19 | 20 | /// 21 | /// Label for the select. 22 | /// 23 | [Parameter] public required string Label { get; set; } 24 | 25 | /// 26 | /// The attribute of the leg type to bind to. 27 | /// 28 | [Parameter] public required LegTypeValueDisplayPair Attribute { get; set; } 29 | 30 | /// 31 | /// Callback for when the leg type is changed to update the bound attribute. 32 | /// 33 | [Parameter] public EventCallback AttributeChanged { get; set; } 34 | 35 | /// 36 | /// Callback for when the leg type is changed. 37 | /// 38 | [Parameter] public Action? OnAttributeChanged { get; set; } 39 | 40 | private LegTypeValueDisplayPair CurrentValue // Proxy property to prevent infinite Attribute update 41 | { 42 | get => _currentValue; 43 | set 44 | { 45 | _currentValue = value; 46 | AttributeChanged.InvokeAsync(value); 47 | OnAttributeChanged?.Invoke(); 48 | } 49 | } 50 | 51 | protected override void OnInitialized() 52 | { 53 | _currentValue = Attribute; 54 | } 55 | } -------------------------------------------------------------------------------- /Components/ParentField.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | @using LTSaveEd.Models.CharacterData 3 | @using LTSaveEd.Models.CharacterData.BodyData 4 | 5 | 6 | 7 | 8 | 10 | 12 | @*TODO: Fix bug where ValueDisplayPairSelect doesn't update when Parent changes*@ 13 | 14 | @code { 15 | private string _currentId = null!; 16 | private static ValueDisplayPair[] FemininityValues => Collections.FemininityValues; 17 | private static ValueDisplayPair[] Subspecies => Collections.SubspeciesOverrides; 18 | 19 | /// 20 | /// The list of parents to choose from. 21 | /// 22 | [Parameter] public required ValueDisplayPair[] Parents { get; set; } 23 | 24 | /// 25 | /// The parent data to display. 26 | /// 27 | [Parameter] public required CharacterShort ParentData { get; set; } 28 | 29 | /// 30 | /// Whether this is the mother or father. 31 | /// 32 | [Parameter] public bool Mother { get; set; } 33 | 34 | /// 35 | /// The action to call when a new character is loaded. 36 | /// 37 | [Parameter] public required Action LoadCharacterAction { get; set; } 38 | 39 | /// 40 | /// Whether the fields are read only. 41 | /// 42 | private bool ReadOnly { get; set; } 43 | 44 | private string CurrentId 45 | { 46 | get => _currentId; 47 | set 48 | { 49 | _currentId = value; 50 | if (value != "") // If parent is not unknown 51 | { 52 | LoadCharacterAction(value, Mother); 53 | ReadOnly = true; 54 | } 55 | else 56 | { 57 | // Unknown parents don't exist, so we can edit their properties 58 | ReadOnly = false; 59 | } 60 | StateHasChanged(); 61 | } 62 | } 63 | 64 | protected override void OnInitialized() 65 | { 66 | // Sets initial state 67 | _currentId = ParentData.Id.Value; 68 | ReadOnly = _currentId != ""; 69 | } 70 | } -------------------------------------------------------------------------------- /Components/PerkCheckbox.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models.CharacterData 2 | 3 | 4 | 5 | @code { 6 | 7 | /// 8 | /// The PerkNode to bind to. 9 | /// 10 | [Parameter] public required PerkNode Perk { get; set; } 11 | } -------------------------------------------------------------------------------- /Components/ProxyValueDisplayPairSelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | 3 | 4 | @foreach (var value in Values) 5 | { 6 | 7 | } 8 | 9 | 10 | @code { 11 | private ValueDisplayPair _currentValue = null!; 12 | 13 | /// 14 | /// The values to display in the select. 15 | /// 16 | [Parameter] public required IEnumerable> Values { get; set; } 17 | 18 | /// 19 | /// The label to display above the select. 20 | /// 21 | [Parameter] public required string Label { get; set; } 22 | 23 | /// 24 | /// The attribute to bind to. 25 | /// 26 | [Parameter] public required string Attribute { get; set; } 27 | 28 | /// 29 | /// The callback to invoke when the attribute changes. 30 | /// 31 | [Parameter] public EventCallback AttributeChanged { get; set; } 32 | 33 | protected ValueDisplayPair CurrentValue 34 | { 35 | get => _currentValue; 36 | set 37 | { 38 | _currentValue = value; 39 | Attribute = _currentValue.Value; 40 | AttributeChanged.InvokeAsync(_currentValue.Value); 41 | } 42 | } 43 | 44 | protected override void OnInitialized() 45 | { 46 | // Set the current value to the value of the attribute. 47 | foreach (var value in Values) 48 | { 49 | if (value.Value != Attribute) 50 | { 51 | continue; 52 | } 53 | 54 | _currentValue = value; 55 | break; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Components/SearchableCharacterSelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | 3 | 5 | 6 | @code{ 7 | private ValueDisplayPair _currentValue = null!; 8 | 9 | /// 10 | /// The list of values to search from. 11 | /// 12 | [Parameter] public required IEnumerable> Values { get; set; } 13 | 14 | /// 15 | /// The currently selected character. 16 | /// 17 | [Parameter] public required ValueDisplayPair CharacterId { get; set; } 18 | 19 | /// 20 | /// The event that is called when the selected character changes. 21 | /// 22 | [Parameter] public EventCallback> CharacterIdChanged { get; set; } 23 | 24 | private ValueDisplayPair CurrentValue 25 | { 26 | get => _currentValue; 27 | set 28 | { 29 | _currentValue = value; 30 | CharacterIdChanged.InvokeAsync(value); 31 | } 32 | } 33 | 34 | protected override void OnInitialized() 35 | { 36 | _currentValue = CharacterId; 37 | } 38 | 39 | private Task>> Search(string value, CancellationToken cancellationToken) 40 | { 41 | if (string.IsNullOrEmpty(value)) 42 | { 43 | return Task.FromResult(Values); 44 | } 45 | 46 | // Search for similar values to the search term in the list of values. 47 | return Task.FromResult(Values.Where(x => x.DisplayValue.Contains(value, StringComparison.InvariantCultureIgnoreCase))); 48 | } 49 | } -------------------------------------------------------------------------------- /Components/SpellSelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models.CharacterData.SpellData 2 | 3 | 4 | @foreach (var spellTier in Spell.Tiers) 5 | { 6 | 7 | } 8 | 9 | 10 | @code { 11 | 12 | /// 13 | /// The spell component of the character being edited. 14 | /// 15 | [Parameter] public required Spell Spell { get; set; } 16 | 17 | /// 18 | /// The label to display for the current spell. 19 | /// 20 | [Parameter] public required string Label { get; set; } 21 | } -------------------------------------------------------------------------------- /Components/ValueDisplayPairSelect.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | @using LTSaveEd.Models.XmlData 3 | 4 | 5 | @foreach (var value in Values) 6 | { 7 | 8 | } 9 | 10 | 11 | @code { 12 | private ValueDisplayPair _currentValue = null!; 13 | 14 | /// 15 | /// The values to display in the select. 16 | /// 17 | [Parameter] public required IEnumerable> Values { get; set; } 18 | 19 | /// 20 | /// The label to display above the select. 21 | /// 22 | [Parameter] public required string Label { get; set; } 23 | 24 | /// 25 | /// The XmlAttribute to bind to. 26 | /// 27 | [Parameter] public required XmlAttribute Attribute { get; set; } 28 | 29 | /// 30 | /// Whether the select is read only. 31 | /// 32 | [Parameter] public bool ReadOnly { get; set; } 33 | 34 | [Parameter] public Action>? CurrentValueChanged { get; set; } 35 | 36 | protected ValueDisplayPair CurrentValue 37 | { 38 | get => _currentValue; 39 | set 40 | { 41 | _currentValue = value; 42 | Attribute.Value = _currentValue.Value; 43 | CurrentValueChanged?.Invoke(_currentValue); 44 | } 45 | } 46 | 47 | protected override void OnInitialized() 48 | { 49 | // Set the current value to the value of the attribute. 50 | foreach (var value in Values) 51 | { 52 | if (value.Value != Attribute.Value) 53 | { 54 | continue; 55 | } 56 | 57 | _currentValue = value; 58 | break; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /ExtensionMethods/DateExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.ExtensionMethods; 2 | 3 | /// 4 | /// Extension methods for related functionality. 5 | /// 6 | public static class DateExtensionMethods 7 | { 8 | /// 9 | /// Converts a month's numerical value to its full-uppercase string representation. 10 | /// 11 | /// Numerical value of the month 12 | /// String name of the corresponding month 13 | /// Thrown when value is out of the range [1, 12] 14 | public static string ToMonthString(this int value) 15 | { 16 | return value switch 17 | { 18 | 1 => "JANUARY", 19 | 2 => "FEBRUARY", 20 | 3 => "MARCH", 21 | 4 => "APRIL", 22 | 5 => "MAY", 23 | 6 => "JUNE", 24 | 7 => "JULY", 25 | 8 => "AUGUST", 26 | 9 => "SEPTEMBER", 27 | 10 => "OCTOBER", 28 | 11 => "NOVEMBER", 29 | 12 => "DECEMBER", 30 | _ => throw new Exception($"Invalid month number: {value}") 31 | }; 32 | } 33 | 34 | /// 35 | /// Converts a month's full-uppercase string representation to its numerical value. 36 | /// 37 | /// String name of the month 38 | /// Numerical value of the corresponding month 39 | /// Thrown when an invalid (or improperly formatted) month name is provided 40 | public static int ToMonthInt(this string value) 41 | { 42 | return value switch 43 | { 44 | "JANUARY" => 1, 45 | "FEBRUARY" => 2, 46 | "MARCH" => 3, 47 | "APRIL" => 4, 48 | "MAY" => 5, 49 | "JUNE" => 6, 50 | "JULY" => 7, 51 | "AUGUST" => 8, 52 | "SEPTEMBER" => 9, 53 | "OCTOBER" => 10, 54 | "NOVEMBER" => 11, 55 | "DECEMBER" => 12, 56 | _ => throw new Exception($"Invalid month: {value}") 57 | }; 58 | } 59 | } -------------------------------------------------------------------------------- /ExtensionMethods/GeneralExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.ExtensionMethods; 2 | 3 | /// 4 | /// General extension methods 5 | /// 6 | public static class GeneralExtensionMethods 7 | { 8 | /// 9 | /// Convert an enumerable collection to a formatted string in the form of [item1, item2, item3, ...] 10 | /// 11 | /// Enumerable collection to format 12 | /// Type of the elements 13 | /// Formatted string using values from the provided collection 14 | public static string ToFormattedString(this IEnumerable list) 15 | { 16 | var output = string.Join(", ", list); 17 | return $"[{output}]"; 18 | } 19 | 20 | public static void Pop(this List list) 21 | { 22 | list.RemoveAt(list.Count - 1); 23 | } 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Exiua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LTSaveEd.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 2.7.1 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LTSaveEd.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LTSaveEd", "LTSaveEd.csproj", "{E57111AB-2BD4-4479-91B5-2F3C4AF562CC}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {E57111AB-2BD4-4479-91B5-2F3C4AF562CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {E57111AB-2BD4-4479-91B5-2F3C4AF562CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {E57111AB-2BD4-4479-91B5-2F3C4AF562CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {E57111AB-2BD4-4479-91B5-2F3C4AF562CC}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /Layout/BaseModEditorPage.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models; 3 | using LTSaveEd.Models.ModEditor; 4 | using Microsoft.AspNetCore.Components; 5 | 6 | namespace LTSaveEd.Layout; 7 | 8 | public abstract class BaseModEditorPage : ComponentBase 9 | { 10 | [Inject] protected NavigationManager NavigationManager { get; set; } = null!; 11 | [Inject] protected ApplicationState ApplicationState { get; set; } = null!; 12 | 13 | protected override void OnInitialized() 14 | { 15 | ApplicationState.UpdateLocation(NavigationManager); 16 | ApplicationState.SaveModDataHandler = SaveModData; 17 | ApplicationState.LoadModDataHandler = LoadModDataHandler; 18 | } 19 | 20 | private async Task SaveModData() 21 | { 22 | var mod = GetMod(); 23 | var memoryStream = new MemoryStream(); 24 | // TODO: Figure out if the added whitespace around CDATA values cause issues 25 | await mod.Root.SaveAsync(memoryStream, SaveOptions.None, CancellationToken.None); 26 | memoryStream.Position = 0; 27 | return memoryStream; 28 | } 29 | 30 | protected abstract bool LoadModDataHandler(XDocument doc); 31 | 32 | protected abstract Mod GetMod(); 33 | } -------------------------------------------------------------------------------- /Layout/BaseSaveEditorPage.razor: -------------------------------------------------------------------------------- 1 | @using LTSaveEd.Models 2 | 3 | @inject NavigationManager NavigationManager 4 | @inject ApplicationState ApplicationState 5 | 6 | @code { 7 | protected SaveData SaveData => ApplicationState.SaveData; 8 | 9 | protected override void OnInitialized() 10 | { 11 | ApplicationState.UpdateLocation(NavigationManager); 12 | if (!SaveData.Initialized) 13 | { 14 | NavigationManager.NavigateTo("home", replace: true); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Models/ApplicationState.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums; 3 | using Microsoft.AspNetCore.Components; 4 | 5 | namespace LTSaveEd.Models; 6 | 7 | public class ApplicationState 8 | { 9 | public SaveData SaveData { get; set; } = new(); 10 | public ApplicationStateLocation Location { get; set; } = ApplicationStateLocation.SaveEditor; 11 | public Func>? SaveModDataHandler { get; set; } 12 | public Func? LoadModDataHandler { get; set; } 13 | 14 | public event Action? LocationChanged; 15 | 16 | public void UpdateLocation(NavigationManager navigationManager) 17 | { 18 | var path = navigationManager.Uri; 19 | if (path.Contains("/mod-editor/")) 20 | { 21 | Location = ApplicationStateLocation.ModEditor; 22 | } 23 | else if (path.EndsWith("/mod-editor")) 24 | { 25 | Location = ApplicationStateLocation.ModEditorHome; 26 | } 27 | else 28 | { 29 | Location = ApplicationStateLocation.SaveEditor; 30 | } 31 | 32 | LocationChanged?.Invoke(); 33 | } 34 | 35 | public async Task SaveMod() 36 | { 37 | if (SaveModDataHandler is null) 38 | { 39 | throw new InvalidOperationException("SaveModDataHandler is not set."); 40 | } 41 | 42 | var memoryStream = await SaveModDataHandler(); 43 | memoryStream.Position = 0; 44 | return memoryStream; 45 | } 46 | 47 | public async Task LoadMod(Stream stream) 48 | { 49 | try 50 | { 51 | var doc = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); 52 | if (LoadModDataHandler is null) 53 | { 54 | throw new InvalidOperationException("LoadModDataHandler is not set."); 55 | } 56 | 57 | var initialized = LoadModDataHandler(doc); 58 | return initialized; 59 | } 60 | catch (Exception e) 61 | { 62 | #if DEBUG 63 | Console.WriteLine(e); 64 | #endif 65 | return false; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Models/Character.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData; 3 | 4 | namespace LTSaveEd.Models; 5 | 6 | public class Character(XContainer characterNode, Dictionary idNameLookup) 7 | { 8 | public Core Core { get; } = new(characterNode.Element("core")!); 9 | public Attributes Attributes { get; } = new(characterNode.Element("attributes")!); 10 | public Body Body { get; } = new(characterNode.Element("body")!); 11 | public Fetishes Fetishes { get; } = new(characterNode.Element("fetishes")!); 12 | public Perks Perks { get; } = new(characterNode.Element("perks")!); 13 | public Spells Spells { get; } = new(characterNode.Element("knownSpells")!, characterNode.Element("spellUpgrades")!, characterNode.Element("spellUpgradePoints")!); 14 | public Inventory Inventory { get; } = new(characterNode.Element("characterInventory")!); 15 | public Relationships Relationships { get; } = new(characterNode.Element("characterRelationships")!, idNameLookup); 16 | public Family Family { get; } = new(characterNode.Element("family")!); 17 | 18 | public bool IsPlayer => Core.Id.Value == "PlayerCharacter"; 19 | 20 | public CharacterShortData Shorten() 21 | { 22 | var id = Core.Id.Value; 23 | var femininityValue = Body.BodyCore.Femininity.Value; 24 | var names = Core.Name; 25 | var subspecies = Body.BodyCore.SubspeciesOverride.Value; 26 | string femininity; 27 | string name; 28 | switch (femininityValue) 29 | { 30 | case < 20: 31 | femininity = "VERY_MASCULINE"; 32 | name = names.Masculine.Value; 33 | break; 34 | case < 40: 35 | femininity = "MASCULINE"; 36 | name = names.Masculine.Value; 37 | break; 38 | case < 60: 39 | femininity = "ANDROGYNOUS"; 40 | name = names.Androgynous.Value; 41 | break; 42 | case < 80: 43 | femininity = "FEMININE"; 44 | name = names.Feminine.Value; 45 | break; 46 | case >= 80: 47 | femininity = "VERY_FEMININE"; 48 | name = names.Feminine.Value; 49 | break; 50 | } 51 | return new CharacterShortData(id, name, femininity, subspecies); 52 | } 53 | } -------------------------------------------------------------------------------- /Models/CharacterData/Body.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class Body 7 | { 8 | public BodyCore BodyCore { get; } 9 | public Head Head { get; } 10 | public Face Face { get; } 11 | public Breasts Breasts { get; } 12 | public Breasts BreastsCrotch { get; } 13 | public Torso Torso { get; } 14 | public Penis Penis { get; } 15 | public Vagina Vagina { get; } 16 | public Ass Ass { get; } 17 | 18 | public Body(XElement bodyNode) 19 | { 20 | BodyCore = new BodyCore(bodyNode.Element("bodyCore")!, this); 21 | Head = new Head(bodyNode, this); 22 | Face = new Face(bodyNode, this); 23 | Breasts = new Breasts(bodyNode.Element("breasts")!, bodyNode.Element("nipples")!, bodyNode.Element("milk")!, this, false); 24 | BreastsCrotch = new Breasts(bodyNode.Element("breastsCrotch")!, bodyNode.Element("nipplesCrotch")!, bodyNode.Element("milkCrotch")!, this, true); 25 | Torso = new Torso(bodyNode, this); 26 | Penis = new Penis(bodyNode, this); 27 | Vagina = new Vagina(bodyNode, this); 28 | Ass = new Ass(bodyNode, this); 29 | } 30 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Ass.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.AssData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Ass : BodyComponent 7 | { 8 | public Leg Leg { get; } 9 | public AssComponent AssComponent { get; } 10 | public Anus Anus { get; } 11 | 12 | public Ass(XElement bodyNode, Body body) : base(body) 13 | { 14 | Leg = new Leg(bodyNode.Element("leg")!); 15 | AssComponent = new AssComponent(bodyNode.Element("ass")!); 16 | Anus = new Anus(bodyNode.Element("anus")!); 17 | } 18 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/AssData/Anus.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.AssData; 5 | 6 | /// 7 | /// Class models the anus node of the character's body data. Part of the model. 8 | /// 9 | public class Anus 10 | { 11 | public static ValueDisplayPair[] AssHairTypes => Collections.HairTypes; 12 | 13 | public LabeledXmlAttribute Capacity { get; } 14 | public LabeledXmlAttribute Depth { get; } 15 | public LabeledXmlAttribute Elasticity { get; } 16 | public LabeledXmlAttribute Plasticity { get; } 17 | public LabeledXmlAttribute StretchedCapacity { get; } 18 | public LabeledXmlAttribute Wetness { get; } 19 | public XmlAttribute AssHair { get; } 20 | public XmlAttribute Bleached { get; } 21 | public XmlAttribute Virgin { get; } 22 | 23 | #region Modifiers 24 | 25 | public BodyComponentModifier Puffy { get; } 26 | public BodyComponentModifier InternallyRibbed { get; } 27 | public BodyComponentModifier Tentacled { get; } 28 | public BodyComponentModifier InternallyMuscled { get; } 29 | 30 | #endregion 31 | 32 | public Anus(XElement anusNode) 33 | { 34 | Capacity = new LabeledXmlAttribute(anusNode.Attribute("capacity")!, Collections.GetCapacityLabel); 35 | Depth = new LabeledXmlAttribute(anusNode.Attribute("depth")!, Collections.GetDepthLabel); 36 | Elasticity = new LabeledXmlAttribute(anusNode.Attribute("elasticity")!, Collections.GetElasticityLabel); 37 | Plasticity = new LabeledXmlAttribute(anusNode.Attribute("plasticity")!, Collections.GetPlasticityLabel); 38 | StretchedCapacity = new LabeledXmlAttribute(anusNode.Attribute("stretchedCapacity")!, Collections.GetCapacityLabel); 39 | Wetness = new LabeledXmlAttribute(anusNode.Attribute("wetness")!, Collections.GetWetnessLabel); 40 | AssHair = new XmlAttribute(anusNode.Attribute("assHair")!); 41 | Bleached = new XmlAttribute(anusNode.Attribute("bleached")!); 42 | Virgin = new XmlAttribute(anusNode.Attribute("virgin")!); 43 | 44 | Puffy = new BodyComponentModifier(anusNode, "PUFFY"); 45 | InternallyRibbed = new BodyComponentModifier(anusNode, "RIBBED"); 46 | Tentacled = new BodyComponentModifier(anusNode, "TENTACLED"); 47 | InternallyMuscled = new BodyComponentModifier(anusNode, "MUSCLE_CONTROL"); 48 | 49 | var modifiers = anusNode.Elements(); 50 | foreach (var modifier in modifiers) 51 | { 52 | switch (modifier.Value) 53 | { 54 | case "PUFFY": 55 | Puffy.Initialize(modifier); 56 | break; 57 | case "RIBBED": 58 | InternallyRibbed.Initialize(modifier); 59 | break; 60 | case "TENTACLED": 61 | Tentacled.Initialize(modifier); 62 | break; 63 | case "MUSCLE_CONTROL": 64 | InternallyMuscled.Initialize(modifier); 65 | break; 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/AssData/AssComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.AssData; 5 | 6 | /// 7 | /// Class models the ass node of the character's body data. Part of the model. 8 | /// 9 | public class AssComponent 10 | { 11 | public ValueDisplayPair[] AssTypes { get; } = 12 | [ 13 | new("Alligator", "ALLIGATOR_MORPH"), new("Angel", "ANGEL"), 14 | new("Badger", "innoxia_badger_ass"), new("Bat", "BAT_MORPH"), 15 | new("Bear", "dsg_bear_ass"), 16 | new("Capybara", "NoStepOnSnek_capybara_ass"), 17 | new("Cat", "CAT_MORPH"), new("Cow", "COW_MORPH"), 18 | new("Demonic", "DEMON_COMMON"), new("Dog", "DOG_MORPH"), 19 | new("Dragon", "dsg_dragon_ass"), new("Ferret", "dsg_ferret_ass"), 20 | new("Fox", "FOX_MORPH"), new("Goat", "innoxia_goat_ass"), 21 | new("Gryphon", "dsg_gryphon_ass"), new("Harpy", "HARPY"), 22 | new("Horse", "HORSE_MORPH"), new("Human", "HUMAN"), 23 | new("Hyena", "innoxia_hyena_ass"), 24 | new("Octopus", "NoStepOnSnek_octopus_ass"), 25 | new("Otter", "dsg_otter_ass"), new("Panther", "innoxia_panther_ass"), 26 | new("Pig", "innoxia_pig_ass"), new("Rabbit", "RABBIT_MORPH"), 27 | new("Racoon", "dsg_raccoon_ass"), new("Rat", "RAT_MORPH"), 28 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_ass"), 29 | new("Sheep", "innoxia_sheep_ass"), 30 | new("Snake", "NoStepOnSnek_snake_ass"), 31 | new("Spider", "charisma_spider_ass"), 32 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH") 33 | ]; 34 | 35 | public LabeledXmlAttribute AssSize { get; } 36 | public LabeledXmlAttribute HipSize { get; } 37 | public XmlAttribute Type { get; } 38 | 39 | public AssComponent(XElement assNode) 40 | { 41 | AssSize = new LabeledXmlAttribute(assNode.Attribute("assSize")!, GetAssSizeLabel); 42 | HipSize = new LabeledXmlAttribute(assNode.Attribute("hipSize")!, GetHipSizeLabel); 43 | Type = new XmlAttribute(assNode.Attribute("type")!); 44 | } 45 | 46 | private static string GetAssSizeLabel(int value) 47 | { 48 | return value switch 49 | { 50 | <= 0 => "Flat", 51 | 1 => "Tiny", 52 | 2 => "Small", 53 | 3 => "Round", 54 | 4 => "Large", 55 | 5 => "Huge", 56 | 6 => "Massive", 57 | >= 7 => "Gigantic" 58 | }; 59 | } 60 | 61 | private static string GetHipSizeLabel(int value) 62 | { 63 | return value switch 64 | { 65 | <= 0 => "Completely Straight", 66 | 1 => "Very Narrow", 67 | 2 => "Narrow", 68 | 3 => "Girly", 69 | 4 => "Womanly", 70 | 5 => "Very Wide", 71 | 6 => "Extremely Wide", 72 | >= 7 => "Absurdly Wide" 73 | }; 74 | } 75 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/AssData/Leg.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.AssData; 5 | 6 | /// 7 | /// Class models the leg node of the character's body data. Part of the model. 8 | /// 9 | public class Leg 10 | { 11 | public static LegTypeValueDisplayPair[] LegTypes => Collections.LegTypes; 12 | 13 | private LegTypeValueDisplayPair _legType = null!; 14 | 15 | public ValueDisplayPair[] LegConfigurations => LegType.LegConfigurations; 16 | public ValueDisplayPair[] FootStructures => LegType.FootStructures; 17 | public ValueDisplayPair[] GenitalArrangements => LegType.GenitalArrangements; 18 | 19 | public XmlAttribute Configuration { get; } 20 | public XmlAttribute FootStructure { get; } 21 | //public XmlAttribute TailLength { get; } // TODO: Figure out if all saves have this attribute 22 | private XmlAttribute Type { get; } 23 | 24 | public LegTypeValueDisplayPair LegType 25 | { 26 | get => _legType; 27 | set 28 | { 29 | var currentLegConfigurations = LegConfigurations; 30 | var currentFootStructures = FootStructures; 31 | var currentGenitalArrangements = GenitalArrangements; 32 | _legType = value; 33 | Type.Value = _legType.Value; 34 | 35 | // Check if leg configurations, foot structures, or genital arrangements are valid for the new leg type. 36 | ValueDisplayPair? newLegConfiguration = null; 37 | ValueDisplayPair? newFootStructure = null; 38 | ValueDisplayPair? newGenitalArrangement = null; 39 | if (!ReferenceEquals(currentLegConfigurations, LegConfigurations)) 40 | { 41 | newLegConfiguration = _legType.DefaultLegConfiguration; 42 | } 43 | if (!ReferenceEquals(currentFootStructures, FootStructures)) 44 | { 45 | newFootStructure = _legType.DefaultFootStructure; 46 | } 47 | if (!ReferenceEquals(currentGenitalArrangements, GenitalArrangements)) 48 | { 49 | newGenitalArrangement = _legType.DefaultGenitalArrangement; 50 | } 51 | 52 | OnLegTypeChanged?.Invoke(newLegConfiguration, newFootStructure, newGenitalArrangement); 53 | } 54 | } 55 | 56 | public event Action?, ValueDisplayPair?, ValueDisplayPair?>? OnLegTypeChanged; 57 | 58 | public Leg(XElement legNode) 59 | { 60 | Configuration = new XmlAttribute(legNode.Attribute("configuration")!); 61 | FootStructure = new XmlAttribute(legNode.Attribute("footStructure")!); 62 | //TailLength = new XmlAttribute(legNode.Attribute("tailLength")!); 63 | Type = new XmlAttribute(legNode.Attribute("type")!); 64 | 65 | foreach (var legType in LegTypes) 66 | { 67 | if (Type.Value != legType.Value) 68 | { 69 | continue; 70 | } 71 | 72 | _legType = legType; 73 | break; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/AssData/LegComponentType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.BodyData.AssData; 2 | 3 | public enum LegComponentType 4 | { 5 | None = 0, 6 | LegConfiguration = 1, 7 | FootStructure = 2, 8 | GenitalArrangement = 3 9 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/BodyComponent.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.BodyData; 2 | 3 | public abstract class BodyComponent(Body body) 4 | { 5 | protected Body Body { get; } = body; 6 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/BodyComponentModifier.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class BodyComponentModifier : NullableXmlObject 7 | { 8 | private string AttributeName { get; } 9 | private string ModifierName { get; } 10 | 11 | public BodyComponentModifier(XElement parent, string attributeName, string modifierName = "mod") : base(parent) 12 | { 13 | AttributeName = attributeName; 14 | ModifierName = modifierName; 15 | } 16 | 17 | protected override XElement CreateNode() 18 | { 19 | var attribute = new XElement(ModifierName, AttributeName); 20 | Parent.Add(attribute); 21 | return attribute; 22 | } 23 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Breasts.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.BreastsData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Breasts : BodyComponent 7 | { 8 | public BreastsComponent BreastsComponent { get; } 9 | public Nipples Nipples { get; } 10 | public Milk Milk { get; } 11 | public bool BreastsCrotch { get; } 12 | 13 | public Breasts(XElement breastsNode, XElement nipplesNode, XElement milkNode, Body body, bool breastsCrotch) : base(body) 14 | { 15 | BreastsCrotch = breastsCrotch; 16 | BreastsComponent = new BreastsComponent(breastsNode, breastsCrotch); 17 | Nipples = new Nipples(nipplesNode); 18 | Milk = new Milk(milkNode); 19 | } 20 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/BreastsData/Milk.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.BreastsData; 5 | 6 | /// 7 | /// Class models the milk(Crotch) node of the character's body data. Part of the model. 8 | /// 9 | public class Milk 10 | { 11 | public static ValueDisplayPair[] MilkFlavours => Collections.BodyFluidFlavours; 12 | 13 | public XmlAttribute Flavour { get; } 14 | 15 | #region Modifiers 16 | 17 | public BodyComponentModifier Musky { get; } 18 | public BodyComponentModifier Viscous { get; } 19 | public BodyComponentModifier Sticky { get; } 20 | public BodyComponentModifier Slimy { get; } 21 | public BodyComponentModifier Bubbling { get; } 22 | public BodyComponentModifier MineralOil { get; } 23 | public BodyComponentModifier Alcoholic { get; } 24 | public BodyComponentModifier Addictive { get; } 25 | public BodyComponentModifier Psychoactive { get; } 26 | 27 | #endregion 28 | 29 | public Milk(XElement milkNode) 30 | { 31 | Flavour = new XmlAttribute(milkNode.Attribute("flavour")!); 32 | 33 | Musky = new BodyComponentModifier(milkNode, "MUSKY"); 34 | Viscous = new BodyComponentModifier(milkNode, "VISCOUS"); 35 | Sticky = new BodyComponentModifier(milkNode, "STICKY"); 36 | Slimy = new BodyComponentModifier(milkNode, "SLIMY"); 37 | Bubbling = new BodyComponentModifier(milkNode, "BUBBLING"); 38 | MineralOil = new BodyComponentModifier(milkNode, "MINERAL_OIL"); 39 | Alcoholic = new BodyComponentModifier(milkNode, "ALCOHOLIC"); 40 | Addictive = new BodyComponentModifier(milkNode, "ADDICTIVE"); 41 | Psychoactive = new BodyComponentModifier(milkNode, "HALLUCINOGENIC"); 42 | 43 | var modifiers = milkNode.Attributes(); 44 | foreach (var modifier in modifiers) 45 | { 46 | switch (modifier.Name.LocalName) 47 | { 48 | case "MUSKY": 49 | Musky.Initialize(modifier); 50 | break; 51 | case "VISCOUS": 52 | Viscous.Initialize(modifier); 53 | break; 54 | case "STICKY": 55 | Sticky.Initialize(modifier); 56 | break; 57 | case "SLIMY": 58 | Slimy.Initialize(modifier); 59 | break; 60 | case "BUBBLING": 61 | Bubbling.Initialize(modifier); 62 | break; 63 | case "MINERAL_OIL": 64 | MineralOil.Initialize(modifier); 65 | break; 66 | case "ALCOHOLIC": 67 | Alcoholic.Initialize(modifier); 68 | break; 69 | case "ADDICTIVE": 70 | Addictive.Initialize(modifier); 71 | break; 72 | case "HALLUCINOGENIC": 73 | Psychoactive.Initialize(modifier); 74 | break; 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Face.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.FaceData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Face : BodyComponent 7 | { 8 | public FaceComponent FaceComponent { get; } 9 | public Mouth Mouth { get; } 10 | public Tongue Tongue { get; } 11 | public Eye Eye { get; } 12 | 13 | public Face(XElement bodyNode, Body body) : base(body) 14 | { 15 | FaceComponent = new FaceComponent(bodyNode.Element("face")!); 16 | Mouth = new Mouth(bodyNode.Element("mouth")!); 17 | Tongue = new Tongue(bodyNode.Element("tongue")!); 18 | Eye = new Eye(bodyNode.Element("eye")!); 19 | } 20 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/FaceData/Eye.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.FaceData; 5 | 6 | /// 7 | /// Class models the eye node of the character's body data. Part of the model. 8 | /// 9 | public class Eye 10 | { 11 | public ValueDisplayPair[] IrisShapes { get; } = 12 | [ 13 | new("Round", "ROUND"), new("Horizontal", "HORIZONTAL"), 14 | new("Vertical", "VERTICAL"), new("Heart-shaped", "HEART"), 15 | new("Star-shaped", "STAR") 16 | ]; 17 | 18 | public ValueDisplayPair[] PupilShapes { get; } 19 | 20 | public ValueDisplayPair[] EyeTypes { get; } = 21 | [ 22 | new("Alligator", "ALLIGATOR_MORPH"), new("Angel", "ANGEL"), 23 | new("Badger", "innoxia_badger_eye"), new("Bat", "BAT_MORPH"), 24 | new("Bear", "dsg_bear_eye"), 25 | new("Capybara", "NoStepOnSnek_capybara_eye"), 26 | new("Cat", "CAT_MORPH"), new("Cow", "COW_MORPH"), 27 | new("Demonic", "DEMON_COMMON"), new("Deer", "innoxia_deer_eye"), 28 | new("Dog", "DOG_MORPH"), 29 | new("Dragon", "dsg_dragon_eye"), new("Ferret", "dsg_ferret_eye"), 30 | new("Fox", "FOX_MORPH"), new("Goat", "innoxia_goat_eye"), 31 | new("Gryphon", "dsg_gryphon_eye"), new("Harpy", "HARPY"), 32 | new("Horse", "HORSE_MORPH"), new("Human", "HUMAN"), 33 | new("Hyena", "innoxia_hyena_eye"), 34 | new("Octopus", "NoStepOnSnek_octopus_eye"), 35 | new("Otter", "dsg_otter_eye"), new("Panther", "innoxia_panther_eye"), 36 | new("Pig", "innoxia_pig_eye"), new("Rabbit", "RABBIT_MORPH"), 37 | new("Racoon", "dsg_raccoon_eye"), new("Rat", "RAT_MORPH"), 38 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_eye"), 39 | new("Sheep", "innoxia_sheep_eye"), 40 | new("Snake", "NoStepOnSnek_snake_eye"), 41 | new("Spider", "charisma_spider_eye"), 42 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH") 43 | ]; 44 | 45 | public XmlAttribute EyePairs { get; } 46 | public XmlAttribute IrisShape { get; } 47 | public XmlAttribute PupilShape { get; } 48 | public XmlAttribute Type { get; } 49 | 50 | public Eye(XElement eyeNode) 51 | { 52 | EyePairs = new XmlAttribute(eyeNode.Attribute("eyePairs")!); 53 | IrisShape = new XmlAttribute(eyeNode.Attribute("irisShape")!); 54 | PupilShape = new XmlAttribute(eyeNode.Attribute("pupilShape")!); 55 | Type = new XmlAttribute(eyeNode.Attribute("type")!); 56 | 57 | PupilShapes = IrisShapes; 58 | } 59 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/FaceData/FaceComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.FaceData; 5 | 6 | /// 7 | /// Class models the face node of the character's body data. Part of the model. 8 | /// 9 | /// XElement of the face node 10 | public class FaceComponent(XElement faceNode) 11 | { 12 | public static ValueDisplayPair[] FacialHairTypes => Collections.HairTypes; 13 | 14 | public ValueDisplayPair[] FaceTypes { get; } = 15 | [ 16 | new("Alligator", "ALLIGATOR_MORPH"), new("Angel", "ANGEL"), 17 | new("Badger", "innoxia_badger_face"), new("Bat", "BAT_MORPH"), 18 | new("Bear", "dsg_bear_face"), 19 | new("Capybara", "NoStepOnSnek_capybara_face"), 20 | new("Cat", "CAT_MORPH"), new("Cow", "COW_MORPH"), 21 | new("Demonic", "DEMON_COMMON"), new("Dog", "DOG_MORPH"), 22 | new("Coatl", "dsg_dragon_faceCoatl"), 23 | new("Ryu", "dsg_dragon_faceRyu"), new("Dragon", "dsg_dragon_face"), 24 | new("Ferret", "dsg_ferret_face"), new("Fox", "FOX_MORPH"), 25 | new("Goat", "innoxia_goat_face"), new("Gryphon", "dsg_gryphon_face"), 26 | new("Harpy", "HARPY"), new("Horse", "HORSE_MORPH"), 27 | new("Human", "HUMAN"), new("Hyena", "innoxia_hyena_face"), 28 | new("Octopus", "NoStepOnSnek_octopus_face"), 29 | new("Otter", "dsg_otter_face"), 30 | new("Panther", "innoxia_panther_face"), 31 | new("Pig", "innoxia_pig_face"), new("Rabbit", "RABBIT_MORPH"), 32 | new("Racoon", "dsg_raccoon_face"), new("Rat", "RAT_MORPH"), 33 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_face"), 34 | new("Sheep", "innoxia_sheep_face"), 35 | new("Snake", "NoStepOnSnek_snake_face"), 36 | new("Hooded Snake", "NoStepOnSnek_snake_face_h"), 37 | new("Spider", "charisma_spider_face"), 38 | new("Furred Spider", "charisma_spider_faceFluffy"), 39 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH") 40 | ]; 41 | 42 | public XmlAttribute FacialHair { get; } = new(faceNode.Attribute("facialHair")!); 43 | public XmlAttribute PiercedNose { get; } = new(faceNode.Attribute("piercedNose")!); 44 | public XmlAttribute Type { get; } = new(faceNode.Attribute("type")!); 45 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/FaceData/Tongue.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.FaceData; 5 | 6 | /// 7 | /// Class models the tongue node of the character's body data. Part of the model. 8 | /// 9 | public class Tongue 10 | { 11 | public XmlAttribute PiercedTongue { get; } 12 | public LabeledXmlAttribute Length { get; } 13 | 14 | #region Modifiers 15 | 16 | public BodyComponentModifier Ribbed { get; } 17 | public BodyComponentModifier Tentacled { get; } 18 | public BodyComponentModifier Bifurcated { get; } 19 | public BodyComponentModifier Wide { get; } 20 | public BodyComponentModifier Flat { get; } 21 | public BodyComponentModifier Strong { get; } 22 | public BodyComponentModifier Tapered { get; } 23 | 24 | #endregion 25 | 26 | public Tongue(XElement tongueNode) 27 | { 28 | PiercedTongue = new XmlAttribute(tongueNode.Attribute("piercedTongue")!); 29 | Length = new LabeledXmlAttribute(tongueNode.Attribute("tongueLength")!, GetTongueLengthLabel); 30 | 31 | Ribbed = new BodyComponentModifier(tongueNode, "RIBBED"); 32 | Tentacled = new BodyComponentModifier(tongueNode, "TENTACLED"); 33 | Bifurcated = new BodyComponentModifier(tongueNode, "BIFURCATED"); 34 | Wide = new BodyComponentModifier(tongueNode, "WIDE"); 35 | Flat = new BodyComponentModifier(tongueNode, "FLAT"); 36 | Strong = new BodyComponentModifier(tongueNode, "STRONG"); 37 | Tapered = new BodyComponentModifier(tongueNode, "TAPERED"); 38 | 39 | var modifiers = tongueNode.Elements(); 40 | foreach (var modifier in modifiers) 41 | { 42 | switch (modifier.Value) 43 | { 44 | case "RIBBED": 45 | Ribbed.Initialize(modifier); 46 | break; 47 | case "TENTACLED": 48 | Tentacled.Initialize(modifier); 49 | break; 50 | case "BIFURCATED": 51 | Bifurcated.Initialize(modifier); 52 | break; 53 | case "WIDE": 54 | Wide.Initialize(modifier); 55 | break; 56 | case "FLAT": 57 | Flat.Initialize(modifier); 58 | break; 59 | case "STRONG": 60 | Strong.Initialize(modifier); 61 | break; 62 | case "TAPERED": 63 | Tapered.Initialize(modifier); 64 | break; 65 | } 66 | } 67 | } 68 | 69 | /// 70 | /// Returns the tongue length label based on the tongue length value. 71 | /// 72 | /// Value to get the corresponding label of 73 | /// Label corresponding to the provided value 74 | private static string GetTongueLengthLabel(int value) 75 | { 76 | return value switch 77 | { 78 | < 7 => "Normal-sized", 79 | < 15 => "Long", 80 | < 25 => "Very Long", 81 | < 45 => "Extremely Long", 82 | >= 45 => "Absurdly Long" 83 | }; 84 | } 85 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Head.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.HeadData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Head(XElement bodyNode, Body body) : BodyComponent(body) 7 | { 8 | public Antennae Antennae { get; } = new(bodyNode.Element("antennae")!); 9 | public Ear Ear { get; } = new(bodyNode.Element("ear")!); 10 | public Hair Hair { get; } = new(bodyNode.Element("hair")!); 11 | public Horn Horn { get; } = new(bodyNode.Element("horn")!); 12 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/HeadData/Antennae.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.HeadData; 5 | 6 | /// 7 | /// Class models the antennae node of the character's body data. Part of the model. 8 | /// 9 | /// XElement of the antennae node 10 | public class Antennae(XElement antennaeNode) 11 | { 12 | public ValueDisplayPair[] AntennaeTypes { get; } = 13 | [ 14 | new("None", "NONE") 15 | ]; // TODO: Find antennae types 16 | 17 | public XmlAttribute AntennaePerRow { get; } = new(antennaeNode.Attribute("antennaePerRow")!); 18 | public LabeledXmlAttribute Length { get; } = new(antennaeNode.Attribute("length")!, Collections.GetExtremityLabel); 19 | public XmlAttribute Rows { get; } = new(antennaeNode.Attribute("rows")!); 20 | public XmlAttribute Type { get; } = new(antennaeNode.Attribute("type")!); 21 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/HeadData/Ear.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.HeadData; 5 | 6 | /// 7 | /// Class models the ear node of the character's body data. Part of the model. 8 | /// 9 | /// XElement of the ear node 10 | public class Ear(XElement earNode) 11 | { 12 | public ValueDisplayPair[] EarTypes { get; } = 13 | [ 14 | new("Alligator", "ALLIGATOR_MORPH"), new("Angel", "ANGEL"), 15 | new("Badger", "innoxia_badger_ear"), new("Bat", "BAT_MORPH"), 16 | new("Bear", "dsg_bear_ear"), new("Capybara", "NoStepOnSnek_capybara_ear"), 17 | new("Cat", "CAT_MORPH"), new("Tufted Cat", "CAT_MORPH_TUFTED"), 18 | new("Cow", "COW_MORPH"), new("Demonic", "DEMON_COMMON"), 19 | new("Floppy Dog", "DOG_MORPH"), 20 | new("Pointed Dog", "DOG_MORPH_POINTED"), 21 | new("Folded Dog", "DOG_MORPH_FOLDED"), new("Deer", "innoxia_deer_ear"), 22 | new("Dragon", "dsg_dragon_ear"), 23 | new("Dragon external", "dsg_dragon_earExternal"), 24 | new("Ferret", "dsg_ferret_ear"), new("Fox", "FOX_MORPH"), 25 | new("Fennec Fox", "FOX_MORPH_BIG"), new("Goat", "innoxia_goat_ear"), 26 | new("Gryphon", "dsg_gryphon_ear"), new("Harpy", "HARPY"), 27 | new("Horse", "HORSE_MORPH"), new("Human", "HUMAN"), 28 | new("Hyena", "innoxia_hyena_ear"), 29 | new("Octopus", "NoStepOnSnek_octopus_ear"), 30 | new("Otter", "dsg_otter_ear"), 31 | new("Panther", "innoxia_panther_ear"), new("Pig", "innoxia_pig_ear"), 32 | new("Upright Rabbit", "RABBIT_MORPH"), 33 | new("Floppy Rabbit", "RABBIT_MORPH_FLOPPY"), 34 | new("Racoon", "dsg_raccoon_ear"), new("Rat", "RAT_MORPH"), 35 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_ear"), 36 | new("Shark finned", "dsg_shark_earFin"), 37 | new("Sheep", "innoxia_sheep_ear"), 38 | new("Pointed Snake", "NoStepOnSnek_snake_ear_1"), 39 | new("Snake", "NoStepOnSnek_snake_ear"), 40 | new("Spider", "charisma_spider_ear"), 41 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH") 42 | ]; 43 | 44 | public XmlAttribute Pierced { get; } = new(earNode.Attribute("pierced")!); 45 | public XmlAttribute Type { get; } = new(earNode.Attribute("type")!); 46 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/HeadData/Horn.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.HeadData; 5 | 6 | /// 7 | /// Class models the horn node of the character's body data. Part of the model. 8 | /// 9 | /// XElement of the horn node 10 | public class Horn(XElement hornNode) 11 | { 12 | public ValueDisplayPair[] HornTypes { get; } = 13 | [ 14 | new("None", "NONE"), new("Antlers", "ANTLERS"), 15 | new("Dragon", "dsg_dragon_horn"), 16 | new("Dragon antler", "dsg_dragon_hornAntlers"), 17 | new("Dragon curled", "dsg_dragon_hornCurled"), 18 | new("Dragon curved", "dsg_dragon_hornCurved"), 19 | new("Unicorn", "HORSE_STRAIGHT"), new("Curled", "CURLED"), 20 | new("Curved", "CURVED"), new("Spiral", "SPIRAL"), 21 | new("Straight", "STRAIGHT"), new("Swept-back", "SWEPT_BACK"), 22 | new("Multi-branched", "REINDEER_RACK") 23 | ]; 24 | 25 | public XmlAttribute HornsPerRow { get; } = new(hornNode.Attribute("hornsPerRow")!); 26 | public LabeledXmlAttributeLength { get; } = new(hornNode.Attribute("length")!, Collections.GetExtremityLabel); 27 | public XmlAttribute Rows { get; } = new(hornNode.Attribute("rows")!); 28 | public XmlAttribute Type { get; } = new(hornNode.Attribute("type")!); 29 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/LegTypeValueDisplayPair.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.BodyData; 2 | 3 | public class LegTypeValueDisplayPair(string displayValue, string value, ValueDisplayPair[] legConfigurations, ValueDisplayPair[] footStructures, 4 | ValueDisplayPair[] genitalArrangements) : ValueDisplayPair(displayValue, value) 5 | { 6 | public ValueDisplayPair[] LegConfigurations { get; } = legConfigurations; 7 | public ValueDisplayPair[] FootStructures { get; } = footStructures; 8 | public ValueDisplayPair[] GenitalArrangements { get; } = genitalArrangements; 9 | public ValueDisplayPair DefaultLegConfiguration { get; } = legConfigurations[0]; 10 | public ValueDisplayPair DefaultFootStructure { get; } = footStructures[0]; 11 | public ValueDisplayPair DefaultGenitalArrangement { get; } = genitalArrangements[0]; 12 | 13 | public LegTypeValueDisplayPair(string displayValue, string value, ValueDisplayPair[] legConfigurations, ValueDisplayPair[] footStructures, 14 | ValueDisplayPair[] genitalArrangements, string defaultFootStructure) : this(displayValue, value, legConfigurations, footStructures, genitalArrangements) 15 | { 16 | foreach (var footStructure in footStructures) 17 | { 18 | if (footStructure.Value != defaultFootStructure) 19 | { 20 | continue; 21 | } 22 | 23 | DefaultFootStructure = footStructure; 24 | break; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Penis.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.PenisData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Penis : BodyComponent 7 | { 8 | public PenisComponent PenisComponent { get; } 9 | public Testicles Testicles { get; } 10 | public Cum Cum { get; } 11 | 12 | public Penis(XElement bodyNode, Body body) : base(body) 13 | { 14 | PenisComponent = new PenisComponent(bodyNode.Element("penis")!); 15 | Testicles = new Testicles(bodyNode.Element("testicles")!); 16 | Cum = new Cum(bodyNode.Element("cum")!); 17 | } 18 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/PenisData/Cum.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.PenisData; 5 | 6 | /// 7 | /// Class models the cum node of the character's body data. Part of the model. 8 | /// 9 | public class Cum 10 | { 11 | public static ValueDisplayPair[] CumFlavours => Collections.BodyFluidFlavours; 12 | 13 | public XmlAttribute Flavour { get; } 14 | 15 | #region Modifiers 16 | 17 | public BodyComponentModifier Musky { get; } 18 | public BodyComponentModifier Viscous { get; } 19 | public BodyComponentModifier Sticky { get; } 20 | public BodyComponentModifier Slimy { get; } 21 | public BodyComponentModifier Bubbling { get; } 22 | public BodyComponentModifier MineralOil { get; } 23 | public BodyComponentModifier Alcoholic { get; } 24 | public BodyComponentModifier Addictive { get; } 25 | public BodyComponentModifier Psychoactive { get; } 26 | 27 | #endregion 28 | 29 | public Cum(XElement cumNode) 30 | { 31 | Flavour = new XmlAttribute(cumNode.Attribute("flavour")!); 32 | 33 | Musky = new BodyComponentModifier(cumNode, "MUSKY"); 34 | Viscous = new BodyComponentModifier(cumNode, "VISCOUS"); 35 | Sticky = new BodyComponentModifier(cumNode, "STICKY"); 36 | Slimy = new BodyComponentModifier(cumNode, "SLIMY"); 37 | Bubbling = new BodyComponentModifier(cumNode, "BUBBLING"); 38 | MineralOil = new BodyComponentModifier(cumNode, "MINERAL_OIL"); 39 | Alcoholic = new BodyComponentModifier(cumNode, "ALCOHOLIC"); 40 | Addictive = new BodyComponentModifier(cumNode, "ADDICTIVE"); 41 | Psychoactive = new BodyComponentModifier(cumNode, "HALLUCINOGENIC"); 42 | 43 | var modifiers = cumNode.Elements(); 44 | foreach (var modifier in modifiers) 45 | { 46 | switch (modifier.Value) 47 | { 48 | case "MUSKY": 49 | Musky.Initialize(modifier); 50 | break; 51 | case "VISCOUS": 52 | Viscous.Initialize(modifier); 53 | break; 54 | case "STICKY": 55 | Sticky.Initialize(modifier); 56 | break; 57 | case "SLIMY": 58 | Slimy.Initialize(modifier); 59 | break; 60 | case "BUBBLING": 61 | Bubbling.Initialize(modifier); 62 | break; 63 | case "MINERAL_OIL": 64 | MineralOil.Initialize(modifier); 65 | break; 66 | case "ALCOHOLIC": 67 | Alcoholic.Initialize(modifier); 68 | break; 69 | case "ADDICTIVE": 70 | Addictive.Initialize(modifier); 71 | break; 72 | case "HALLUCINOGENIC": 73 | Psychoactive.Initialize(modifier); 74 | break; 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/PenisData/Testicles.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.PenisData; 5 | 6 | /// 7 | /// Class models the testicles node of the character's body data. Part of the model. 8 | /// 9 | /// XElement of the testicles node 10 | public class Testicles(XElement testiclesNode) 11 | { 12 | public XmlAttribute CumExpulsion { get; } = new(testiclesNode.Attribute("cumExpulsion")!); 13 | public LabeledXmlAttribute CumRegeneration { get; } = new(testiclesNode.Attribute("cumRegeneration")!, Collections.GetFluidRegenerationLabel); 14 | public LabeledXmlAttribute CumStorage { get; } = new(testiclesNode.Attribute("cumStorage")!, GetCumStorageLabel); 15 | public XmlAttribute NumberOfTesticles { get; } = new(testiclesNode.Attribute("numberOfTesticles")!); 16 | public XmlAttribute StoredCum { get; } = new(testiclesNode.Attribute("storedCum")!); 17 | public LabeledXmlAttribute TesticleSize { get; } = new(testiclesNode.Attribute("testicleSize")!, GetTesticleSizeLabel); 18 | public XmlAttribute Internal { get; } = new(testiclesNode.Attribute("internal")!); 19 | 20 | /// 21 | /// Get label based on the given cum storage value. 22 | /// 23 | /// Value to get the corresponding label of 24 | /// Label corresponding to the provided value 25 | private static string GetCumStorageLabel(int value) 26 | { 27 | return value switch 28 | { 29 | <= 0 => "None", 30 | < 10 => "Trickle", 31 | < 20 => "Average", 32 | < 30 => "Large", 33 | < 100 => "Huge", 34 | < 1000 => "Extreme", 35 | >= 1000 => "Monstrous" 36 | }; 37 | } 38 | 39 | /// 40 | /// Get label based on the given testicle size value. 41 | /// 42 | /// Value to get the corresponding label of 43 | /// Label corresponding to the provided value 44 | private static string GetTesticleSizeLabel(int value) 45 | { 46 | return value switch 47 | { 48 | <= 0 => "Vestigial", 49 | 1 => "Tiny", 50 | 2 => "Average-sized", 51 | 3 => "Large", 52 | 4 => "Huge", 53 | 5 => "Massive", 54 | 6 => "Gigantic", 55 | >= 7 => "Absurdly Enormous" 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Torso.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.TorsoData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Torso : BodyComponent 7 | { 8 | public TorsoComponent TorsoComponent { get; } 9 | public Tail Tail { get; } 10 | public Tentacle Tentacle { get; } 11 | public Wing Wing { get; } 12 | public Spinneret Spinneret { get; } 13 | public Arm Arm { get; } 14 | 15 | public Torso(XElement bodyNode, Body body) : base(body) 16 | { 17 | TorsoComponent = new TorsoComponent(bodyNode.Element("torso")!); 18 | Tail = new Tail(bodyNode.Element("tail")!); 19 | Tentacle = new Tentacle(bodyNode.Element("tentacle")!); 20 | Wing = new Wing(bodyNode.Element("wing")!); 21 | Spinneret = new Spinneret(bodyNode.Element("spinneret")!); 22 | Arm = new Arm(bodyNode.Element("arm")!); 23 | } 24 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/TorsoData/Arm.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.TorsoData; 5 | 6 | /// 7 | /// Class models the arm node of the character's body data. Part of the model. 8 | /// 9 | /// XElement of the arm node 10 | public class Arm(XElement armNode) 11 | { 12 | public ValueDisplayPair[] ArmTypes { get; } = 13 | [ 14 | new("Alligator", "ALLIGATOR_MORPH"), new("Angel", "ANGEL"), 15 | new("Badger", "innoxia_badger_arm"), new("Bat", "BAT_MORPH"), 16 | new("Bear", "dsg_bear_arm"), 17 | new("Capybara", "NoStepOnSnek_capybara_arm"), 18 | new("Cat", "CAT_MORPH"), new("Cow", "COW_MORPH"), 19 | new("Demonic", "DEMON_COMMON"), new("Dog", "DOG_MORPH"), 20 | new("Dragon", "dsg_dragon_arm"), 21 | new("Dragon winged", "dsg_dragon_armWings"), 22 | new("Ferret", "dsg_ferret_arm"), new("Fox", "FOX_MORPH"), 23 | new("Goat", "innoxia_goat_arm"), new("Gryphon", "dsg_gryphon_arm"), 24 | new("Harpy", "HARPY"), new("Horse", "HORSE_MORPH"), 25 | new("Human", "HUMAN"), new("Hyena", "innoxia_hyena_arm"), 26 | new("Octopus", "NoStepOnSnek_octopus_arm"), 27 | new("Otter", "dsg_otter_arm"), new("Panther", "innoxia_panther_arm"), 28 | new("Pig", "innoxia_pig_arm"), new("Rabbit", "RABBIT_MORPH"), 29 | new("Racoon", "dsg_raccoon_arm"), new("Rat", "RAT_MORPH"), 30 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_arm"), 31 | new("Shark finned", "dsg_shark_armFin"), 32 | new("Sheep", "innoxia_sheep_arm"), 33 | new("Snake", "NoStepOnSnek_snake_arm"), 34 | new("Spider", "charisma_spider_arm"), 35 | new("Furred Spider", "charisma_spider_armFluffy"), 36 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH") 37 | ]; 38 | 39 | public static ValueDisplayPair[] UnderArmHairTypes => Collections.HairTypes; 40 | 41 | public XmlAttribute Rows { get; } = new(armNode.Attribute("rows")!); 42 | public XmlAttribute Type { get; } = new(armNode.Attribute("type")!); 43 | public XmlAttribute UnderarmHair { get; } = new(armNode.Attribute("underarmHair")!); 44 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/TorsoData/Spinneret.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.TorsoData; 5 | 6 | public class Spinneret(XElement spinneretNode) 7 | { 8 | public LabeledXmlAttribute Capacity { get; } = new(spinneretNode.Attribute("capacity")!, Collections.GetCapacityLabel); 9 | public LabeledXmlAttribute Depth { get; } = new(spinneretNode.Attribute("depth")!, Collections.GetDepthLabel); 10 | public LabeledXmlAttribute Elasticity { get; } = new(spinneretNode.Attribute("elasticity")!, Collections.GetElasticityLabel); 11 | public LabeledXmlAttribute Plasticity { get; } = new(spinneretNode.Attribute("plasticity")!, Collections.GetPlasticityLabel); 12 | public LabeledXmlAttribute StretchedCapacity { get; } = new(spinneretNode.Attribute("stretchedCapacity")!, Collections.GetCapacityLabel); 13 | public LabeledXmlAttribute Wetness { get; } = new(spinneretNode.Attribute("wetness")!, Collections.GetWetnessLabel); 14 | public XmlAttribute Virgin { get; } = new(spinneretNode.Attribute("virgin")!); 15 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/TorsoData/Tail.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.TorsoData; 5 | 6 | public class Tail 7 | { 8 | public ValueDisplayPair[] TailTypes { get; } = 9 | [ 10 | new("None", "NONE"), new("Alligator", "ALLIGATOR_MORPH"), 11 | new("Badger", "innoxia_badger_tail"), new("Bat", "BAT_MORPH"), 12 | new("Bear", "dsg_bear_tail"), 13 | new("Capybara", "NoStepOnSnek_capybara_arm"), 14 | new("Feline", "CAT_MORPH"), new("Short Feline", "CAT_MORPH_SHORT"), 15 | new("Tufted Feline", "CAT_MORPH_TUFTED"), new("Cow", "COW_MORPH"), 16 | new("Demonic spaded", "DEMON_COMMON"), 17 | new("Demonic hair-tipped", "DEMON_HAIR_TIP"), 18 | new("Demonic tapered", "DEMON_TAPERED"), 19 | new("Demonic horse", "DEMON_HORSE"), new("Dog", "DOG_MORPH"), 20 | new("Stubby Dog", "DOG_MORPH_STUBBY"), 21 | new("Dragon tufted", "dsg_dragon_tailTufted"), 22 | new("Dragon", "dsg_dragon_tail"), 23 | new("Dragon feathered", "dsg_dragon_tailFeathered"), 24 | new("Dragon spaded", "dsg_dragon_tailSpaded"), 25 | new("Ferret", "dsg_ferret_tail"), new("Fox", "FOX_MORPH"), 26 | new("Goat", "innoxia_goat_tail"), 27 | new("Gryphon feathered", "dsg_gryphon_tailFeathers"), 28 | new("Gryphon", "dsg_gryphon_tail"), new("Harpy", "HARPY"), 29 | new("Horse", "HORSE_MORPH"), new("Zebra", "HORSE_MORPH_ZEBRA"), 30 | new("Hyena", "innoxia_hyena_tail"), new("Otter", "dsg_otter_tail"), 31 | new("Panther", "innoxia_panther_tail"), 32 | new("Pig", "innoxia_pig_tail"), new("Rabbit", "RABBIT_MORPH"), 33 | new("Racoon", "dsg_raccoon_tail"), new("Rat", "RAT_MORPH"), 34 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_tail"), 35 | new("Sheep", "innoxia_sheep_tail"), 36 | new("Snake", "NoStepOnSnek_snake_tail"), 37 | new("Furred Spider", "charisma_spider_tailFluffy"), 38 | new("Spider", "charisma_spider_tail"), 39 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH") 40 | ]; 41 | 42 | public XmlAttribute Count { get; } 43 | public LabeledXmlAttribute Girth { get; } 44 | public XmlAttribute Length { get; } 45 | public XmlAttribute Type { get; } 46 | 47 | public Tail(XElement tailNode) 48 | { 49 | Count = new XmlAttribute(tailNode.Attribute("count")!); 50 | Girth = new LabeledXmlAttribute(tailNode.Attribute("girth")!, Collections.GetAppendageGirthLabel); 51 | Length = new XmlAttribute(tailNode.Attribute("length")!); 52 | Type = new XmlAttribute(tailNode.Attribute("type")!); 53 | } 54 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/TorsoData/Tentacle.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.TorsoData; 5 | 6 | public class Tentacle 7 | { 8 | public ValueDisplayPair[] TentacleTypes { get; } = 9 | [ 10 | new("None", "NONE") 11 | ]; // TODO: Find tentacle types 12 | 13 | public XmlAttribute Count { get; } 14 | public LabeledXmlAttribute Girth { get; } 15 | public XmlAttribute Length { get; } 16 | public XmlAttribute Type { get; } 17 | 18 | public Tentacle(XElement tentacleNode) 19 | { 20 | Count = new XmlAttribute(tentacleNode.Attribute("count")!); 21 | Girth = new LabeledXmlAttribute(tentacleNode.Attribute("girth")!, Collections.GetAppendageGirthLabel); 22 | Length = new XmlAttribute(tentacleNode.Attribute("length")!); 23 | Type = new XmlAttribute(tentacleNode.Attribute("type")!); 24 | } 25 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/TorsoData/TorsoComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.TorsoData; 5 | 6 | public class TorsoComponent(XElement torsoNode) 7 | { 8 | public ValueDisplayPair[] TorsoTypes { get; } = [ 9 | new("Alligator", "ALLIGATOR_MORPH"), new("Angel", "ANGEL"), 10 | new("Badger", "innoxia_badger_torso"), new("Bat", "BAT_MORPH"), 11 | new("Bear", "dsg_bear_torso"), 12 | new("Capybara", "NoStepOnSnek_capybara_torso"), 13 | new("Cat", "CAT_MORPH"), new("Cow", "COW_MORPH"), 14 | new("Demonic", "DEMON_COMMON"), new("Dog", "DOG_MORPH"), 15 | new("Ryu", "dsg_dragon_torsoRyu"), new("Dragon", "dsg_dragon_torso"), 16 | new("Ferret", "dsg_ferret_torso"), new("Fox", "FOX_MORPH"), 17 | new("Goat", "innoxia_goat_torso"), 18 | new("Gryphon", "dsg_gryphon_torso"), new("Harpy", "HARPY"), 19 | new("Horse", "HORSE_MORPH"), new("Human", "HUMAN"), 20 | new("Hyena", "innoxia_hyena_torso"), 21 | new("Octopus", "NoStepOnSnek_octopus_torso"), 22 | new("Otter", "dsg_otter_torso"), 23 | new("Panther", "innoxia_panther_torso"), 24 | new("Pig", "innoxia_pig_torso"), new("Rabbit", "RABBIT_MORPH"), 25 | new("Racoon", "dsg_raccoon_torso"), new("Rat", "RAT_MORPH"), 26 | new("Reindeer", "REINDEER_MORPH"), new("Shark", "dsg_shark_torso"), 27 | new("Shark finned", "dsg_shark_torsoDorsalFin"), 28 | new("Sheep", "innoxia_sheep_torso"), 29 | new("Snake", "NoStepOnSnek_snake_torso"), 30 | new("Furred Spider", "charisma_spider_torsoFluffy"), 31 | new("Spider", "charisma_spider_torso"), 32 | new("Squirrel", "SQUIRREL_MORPH"), new("Wolf", "WOLF_MORPH")]; 33 | 34 | public XmlAttribute Type { get; } = new(torsoNode.Attribute("type")!); 35 | 36 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/TorsoData/Wing.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.TorsoData; 5 | 6 | public class Wing 7 | { 8 | public ValueDisplayPair[] WingTypes { get; } = 9 | [ 10 | new("None", "NONE"), new("Angel", "ANGEL"), 11 | new("Demonic feathered", "DEMON_FEATHERED"), new("Demonic leathery", "DEMON_COMMON"), 12 | new("Dragon", "dsg_dragon_wing"), new("Dragon feathered", "dsg_dragon_wingFeathered"), 13 | new("Gryphon", "dsg_gryphon_wing"), 14 | new("Feathered", "FEATHERED"), new("Insect", "INSECT"), 15 | new("Leathery", "LEATHERY") 16 | ]; 17 | 18 | public XmlAttribute Size { get; } 19 | public XmlAttribute Type { get; } 20 | 21 | public Wing(XElement wingNode) 22 | { 23 | Size = new XmlAttribute(wingNode.Attribute("size")!); 24 | Type = new XmlAttribute(wingNode.Attribute("type")!); 25 | } 26 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/Vagina.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.BodyData.VaginaData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData; 5 | 6 | public class Vagina : BodyComponent 7 | { 8 | public VaginaComponent VaginaComponent { get; } 9 | public GirlCum GirlCum { get; } 10 | 11 | public Vagina(XElement bodyNode, Body body) : base(body) 12 | { 13 | VaginaComponent = new VaginaComponent(bodyNode.Element("vagina")!); 14 | GirlCum = new GirlCum(bodyNode.Element("girlcum")!); 15 | } 16 | } -------------------------------------------------------------------------------- /Models/CharacterData/BodyData/VaginaData/GirlCum.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.BodyData.VaginaData; 5 | 6 | public class GirlCum 7 | { 8 | public static ValueDisplayPair[] GirlCumFlavours => Collections.BodyFluidFlavours; 9 | 10 | public XmlAttribute Flavour { get; } 11 | 12 | #region Modifiers 13 | 14 | public BodyComponentModifier Musky { get; } 15 | public BodyComponentModifier Viscous { get; } 16 | public BodyComponentModifier Sticky { get; } 17 | public BodyComponentModifier Slimy { get; } 18 | public BodyComponentModifier Bubbling { get; } 19 | public BodyComponentModifier MineralOil { get; } 20 | public BodyComponentModifier Alcoholic { get; } 21 | public BodyComponentModifier Addictive { get; } 22 | public BodyComponentModifier Psychoactive { get; } 23 | 24 | #endregion 25 | 26 | public GirlCum(XElement girlCumNode) 27 | { 28 | Flavour = new XmlAttribute(girlCumNode.Attribute("flavour")!); 29 | 30 | Musky = new BodyComponentModifier(girlCumNode, "MUSKY"); 31 | Viscous = new BodyComponentModifier(girlCumNode, "VISCOUS"); 32 | Sticky = new BodyComponentModifier(girlCumNode, "STICKY"); 33 | Slimy = new BodyComponentModifier(girlCumNode, "SLIMY"); 34 | Bubbling = new BodyComponentModifier(girlCumNode, "BUBBLING"); 35 | MineralOil = new BodyComponentModifier(girlCumNode, "MINERAL_OIL"); 36 | Alcoholic = new BodyComponentModifier(girlCumNode, "ALCOHOLIC"); 37 | Addictive = new BodyComponentModifier(girlCumNode, "ADDICTIVE"); 38 | Psychoactive = new BodyComponentModifier(girlCumNode, "HALLUCINOGENIC"); 39 | 40 | var modifiers = girlCumNode.Attributes(); 41 | foreach (var modifier in modifiers) 42 | { 43 | switch (modifier.Value) 44 | { 45 | case "MUSKY": 46 | Musky.Initialize(modifier); 47 | break; 48 | case "VISCOUS": 49 | Viscous.Initialize(modifier); 50 | break; 51 | case "STICKY": 52 | Sticky.Initialize(modifier); 53 | break; 54 | case "SLIMY": 55 | Slimy.Initialize(modifier); 56 | break; 57 | case "BUBBLING": 58 | Bubbling.Initialize(modifier); 59 | break; 60 | case "MINERAL_OIL": 61 | MineralOil.Initialize(modifier); 62 | break; 63 | case "ALCOHOLIC": 64 | Alcoholic.Initialize(modifier); 65 | break; 66 | case "ADDICTIVE": 67 | Addictive.Initialize(modifier); 68 | break; 69 | case "HALLUCINOGENIC": 70 | Psychoactive.Initialize(modifier); 71 | break; 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Models/CharacterData/Family.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.CharacterData; 6 | 7 | public class Family 8 | { 9 | public CharacterShort Mother { get; set; } 10 | public CharacterShort Father { get; set; } 11 | public XmlDate DateOfConception { get; } 12 | 13 | public Family(XElement familyNode) 14 | { 15 | Mother = new CharacterShort(familyNode, true); 16 | Father = new CharacterShort(familyNode, false); 17 | var yearNode = familyNode.GetChildsAttributeNode("yearOfConception"); 18 | var monthNode = familyNode.GetChildsAttributeNode("monthOfConception"); 19 | var dayNode = familyNode.GetChildsAttributeNode("dayOfConception"); 20 | DateOfConception = new XmlDate(yearNode, monthNode, dayNode); 21 | } 22 | } -------------------------------------------------------------------------------- /Models/CharacterData/Fetish.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class Fetish 7 | { 8 | private XmlAttribute Desire { get; } 9 | public XmlAttribute Xp { get; } 10 | public FetishOwnedAttribute Owned { get; } 11 | 12 | public string DesireValue 13 | { 14 | get 15 | { 16 | return Desire.Value switch 17 | { 18 | 0 => "Hate", 19 | 1 => "Dislike", 20 | 2 => "Indifferent", 21 | 3 => "Like", 22 | 4 => "Love", 23 | _ => throw new Exception($"Invalid Desire Value: {Desire.Value}") 24 | }; 25 | } 26 | set 27 | { 28 | Desire.Value = value switch 29 | { 30 | "Hate" => 0, 31 | "Dislike" => 1, 32 | "Indifferent" => 2, 33 | "Like" => 3, 34 | "Love" => 4, 35 | _ => throw new Exception($"Invalid Desire Type: {value}") 36 | }; 37 | } 38 | } 39 | 40 | public Fetish(XElement fetishNode, bool suppressDesire = false) 41 | { 42 | var desireNode = fetishNode.Attribute("desire"); 43 | if (desireNode is null) 44 | { 45 | desireNode = new XAttribute("desire", 2); 46 | if(!suppressDesire) 47 | { 48 | fetishNode.Add(desireNode); 49 | } 50 | } 51 | Desire = new XmlAttribute(desireNode); 52 | 53 | var xpNode = fetishNode.Attribute("xp"); 54 | if (xpNode is null) 55 | { 56 | xpNode = new XAttribute("xp", 0); 57 | fetishNode.Add(xpNode); 58 | } 59 | Xp = new XmlAttribute(xpNode); 60 | 61 | Owned = new FetishOwnedAttribute(fetishNode); 62 | var ownedNode = fetishNode.Attribute("o"); 63 | if (ownedNode is not null) 64 | { 65 | Owned.Initialize(ownedNode); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Models/CharacterData/FetishOwnedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class FetishOwnedAttribute(XElement parent) : NullableXmlObject(parent) 7 | { 8 | protected override XAttribute CreateNode() 9 | { 10 | var ownedAttribute = new XAttribute("o", true); 11 | Parent.Add(ownedAttribute); 12 | return ownedAttribute; 13 | } 14 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Clothes/Clothing.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.InventoryData.Clothes; 5 | 6 | public class Clothing : InventoryElement 7 | { 8 | public XmlAttribute EnchantmentKnown { get; } 9 | public XmlAttribute IsDirty { get; } 10 | public XmlElement[] Colors { get; } 11 | 12 | public Clothing(XElement clothingNode) : base(clothingNode) 13 | { 14 | EnchantmentKnown = new XmlAttribute(clothingNode.Attribute("enchantmentKnown")!); 15 | IsDirty = new XmlAttribute(clothingNode.Attribute("isDirty")!); 16 | var colorsNode = clothingNode.Element("colours")!; 17 | var colors = colorsNode.Elements().ToArray(); 18 | Colors = new XmlElement[colors.Length]; 19 | for (var i = 0; i < colors.Length; i++) 20 | { 21 | var color = colors[i]; 22 | Colors[i] = new XmlElement(color); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Clothes/ClothingData.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData.Clothes; 2 | 3 | public class ClothingData(string displayValue, string value, int colorCount) : InventoryElementData(displayValue, value) 4 | { 5 | public int ColorCount { get; } = colorCount; 6 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Clothes/ClothingType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData.Clothes; 2 | 3 | public enum ClothingType 4 | { 5 | Head, 6 | Eyes, 7 | Hair, 8 | Mouth, 9 | Neck, 10 | TorsoOver, // OVER_TORSO 11 | TorsoUnder, // TORSO 12 | Chest, 13 | Nipple, // NIPPLES 14 | Stomach, 15 | Hand, // HANDS 16 | Wrist, // WRISTS 17 | Finger, // FINGERS 18 | Hips, 19 | Anus, 20 | Leg, // LEGS 21 | Groin, 22 | Foot, // FEET 23 | Sock, // CALVES 24 | Ankle, // ANKLES 25 | Horns, 26 | Wings, 27 | Tail, 28 | Penis, 29 | Vagina, 30 | PiercingEar, 31 | PiercingNose, 32 | PiercingTongue, 33 | PiercingLip, 34 | PiercingStomach, 35 | PiercingNipple, 36 | PiercingVagina, 37 | PiercingPenis 38 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/InventoryElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.InventoryData; 5 | 6 | public abstract class InventoryElement 7 | { 8 | protected readonly int MaxEffects = 100; 9 | 10 | public XmlAttribute Id { get; } 11 | public XmlAttribute Name { get; } 12 | public XmlAttribute Count { get; } 13 | 14 | // TODO: Effects 15 | 16 | protected InventoryElement(XElement inventoryElementNode) 17 | { 18 | Id = new XmlAttribute(inventoryElementNode.Attribute("id")!); 19 | var nameNode = inventoryElementNode.Attribute("name"); // Apparently, used items don't have a name attribute 20 | if (nameNode is null) 21 | { 22 | nameNode = new XAttribute("name", Id); 23 | inventoryElementNode.Add(nameNode); 24 | } 25 | 26 | Name = new XmlAttribute(nameNode); 27 | Count = new XmlAttribute(inventoryElementNode.Attribute("count")!); 28 | } 29 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/InventoryElementData.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData; 2 | 3 | public abstract class InventoryElementData(string displayValue, string value) : ValueDisplayPair(displayValue, value) 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/InventoryElementType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData; 2 | 3 | public enum InventoryElementType 4 | { 5 | None = 0, 6 | Clothing = 1, 7 | Item = 2, 8 | Weapon = 3 9 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Items/Item.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.InventoryData.Items; 5 | 6 | public class Item : InventoryElement 7 | { 8 | public XmlAttribute Color { get; } 9 | 10 | public Item(XElement itemNode) : base(itemNode) 11 | { 12 | Color = new XmlAttribute(itemNode.Attribute("colour")!); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Items/ItemData.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData.Items; 2 | 3 | public class ItemData(string displayValue, string value) : InventoryElementData(displayValue, value); -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Items/ItemType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData.Items; 2 | 3 | public enum ItemType { 4 | Item, 5 | Essence, 6 | Book, 7 | Spell 8 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Weapons/Weapon.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.InventoryData.Weapons; 5 | 6 | public class Weapon : InventoryElement 7 | { 8 | private readonly ValueDisplayPair[] _damageTypes = 9 | [ 10 | new("Physical", "PHYSICAL"), new("Fire", "FIRE"), 11 | new("Ice", "ICE"), new("Poison", "POISON") 12 | ]; 13 | 14 | private readonly Dictionary[]> _damageTypeLookup = new() 15 | { 16 | {"PHYSICAL", [new ValueDisplayPair("Physical", "PHYSICAL")]}, 17 | {"FIRE", [new ValueDisplayPair("Fire", "FIRE")]}, 18 | {"ICE", [new ValueDisplayPair("Ice", "ICE")]}, 19 | {"POISON", [new ValueDisplayPair("Poison", "POISON")]}, 20 | {"LUST", [new ValueDisplayPair("Lust", "LUST")]} 21 | }; 22 | 23 | public ValueDisplayPair[] AvailableDamageTypes => 24 | CoreEnchantment.Value switch 25 | { 26 | "null" => _damageTypeLookup[DamageType.Value], 27 | "DAMAGE_LUST" => _damageTypeLookup["LUST"], 28 | _ => _damageTypes 29 | }; 30 | 31 | public XmlAttribute CoreEnchantment { get; } 32 | public XmlAttribute DamageType { get; } 33 | 34 | public Weapon(XElement weaponNode) : base(weaponNode) 35 | { 36 | CoreEnchantment = new XmlAttribute(weaponNode.Attribute("coreEnchantment")!); 37 | DamageType = new XmlAttribute(weaponNode.Attribute("damageType")!); 38 | } 39 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Weapons/WeaponData.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData.Weapons; 2 | 3 | public class WeaponData(string displayValue, string value, int colorCount, string coreEnchantment, 4 | string defaultEnchantment) : InventoryElementData(displayValue, value) 5 | { 6 | public int ColorCount { get; } = colorCount; 7 | public string CoreEnchantment { get; } = coreEnchantment; 8 | public string DefaultEnchantment { get; } = defaultEnchantment; 9 | } -------------------------------------------------------------------------------- /Models/CharacterData/InventoryData/Weapons/WeaponType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.InventoryData.Weapons; 2 | 3 | public enum WeaponType { 4 | Melee, 5 | Ranged 6 | } -------------------------------------------------------------------------------- /Models/CharacterData/Name.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class Name(XElement nameNode, XElement surnameNode) 7 | { 8 | public XmlAttribute Androgynous { get; } = new(nameNode.Attribute("nameAndrogynous")!); 9 | public XmlAttribute Feminine { get; } = new(nameNode.Attribute("nameFeminine")!); 10 | public XmlAttribute Masculine { get; } = new(nameNode.Attribute("nameMasculine")!); 11 | public XmlAttribute Surname { get; } = new(surnameNode.Attribute("value")!); 12 | } -------------------------------------------------------------------------------- /Models/CharacterData/Parent.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData; 2 | 3 | public class Parent(string id, string name, string femininity, string subspecies) 4 | { 5 | public string Id { get; set; } = id; 6 | public string Name { get; set; } = name; 7 | public string Femininity { get; set; } = femininity; 8 | public string Subspecies { get; set; } = subspecies; 9 | } -------------------------------------------------------------------------------- /Models/CharacterData/PersonalityTrait.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class PersonalityTrait : NullableXmlObject 7 | { 8 | private readonly string _value; 9 | private readonly List _incompatibleTraits; 10 | 11 | public PersonalityTrait(XElement personalityNode, string trait) : base(personalityNode) 12 | { 13 | _value = trait; 14 | _incompatibleTraits = []; 15 | } 16 | 17 | public void AddIncompatibleTraits(params PersonalityTrait[] personalityTraits) 18 | { 19 | foreach (var personalityTrait in personalityTraits) 20 | { 21 | if (_incompatibleTraits.Contains(personalityTrait)) 22 | { 23 | continue; 24 | } 25 | 26 | _incompatibleTraits.Add(personalityTrait); 27 | personalityTrait.AddIncompatibleTraits(this); 28 | } 29 | } 30 | 31 | private void CheckIncompatibleTraits() 32 | { 33 | foreach (var trait in _incompatibleTraits) 34 | { 35 | trait.Exists = false; 36 | } 37 | } 38 | 39 | protected override XElement CreateNode() 40 | { 41 | var trait = new XElement("trait") 42 | { 43 | Value = _value 44 | }; 45 | Parent.Add(trait); 46 | CheckIncompatibleTraits(); 47 | return trait; 48 | } 49 | } -------------------------------------------------------------------------------- /Models/CharacterData/Relationship.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class Relationship 7 | { 8 | public string CharacterName { get; set; } 9 | public XmlAttribute CharacterId { get; } 10 | public XmlAttribute Value { get; } 11 | 12 | 13 | public Relationship(XElement relationshipNode, Dictionary idNameLookup) 14 | { 15 | var idAttribute = relationshipNode.Attribute("character")!; 16 | CharacterName = idNameLookup[idAttribute.Value]; 17 | CharacterId = new XmlAttribute(idAttribute); 18 | Value = new XmlAttribute(relationshipNode.Attribute("value")!); 19 | } 20 | 21 | public Relationship(XElement relationshipNode, string characterName) 22 | { 23 | CharacterName = characterName; 24 | CharacterId = new XmlAttribute(relationshipNode.Attribute("character")!); 25 | Value = new XmlAttribute(relationshipNode.Attribute("value")!); 26 | } 27 | } -------------------------------------------------------------------------------- /Models/CharacterData/Relationships.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.CharacterData; 4 | 5 | public class Relationships 6 | { 7 | public List RelationshipsData { get; } = []; 8 | 9 | private readonly List _relationshipNodes = []; 10 | private readonly XElement _relationshipsNode; 11 | 12 | public Relationships(XElement relationshipsNode, Dictionary idNameLookup) 13 | { 14 | _relationshipsNode = relationshipsNode; 15 | var relationships = relationshipsNode.Elements(); 16 | foreach (var relationshipElement in relationships) 17 | { 18 | var relationship = new Relationship(relationshipElement, idNameLookup); 19 | RelationshipsData.Add(relationship); 20 | _relationshipNodes.Add(relationshipElement); 21 | } 22 | } 23 | 24 | public void RemoveRelationship(int index) 25 | { 26 | _relationshipNodes[index].Remove(); 27 | _relationshipNodes.RemoveAt(index); 28 | RelationshipsData.RemoveAt(index); 29 | } 30 | 31 | public void AddRelationship(string characterName, string characterId) 32 | { 33 | if (RelationshipsData.Any(relationship => relationship.CharacterId.Value == characterId)) // Prevent duplicate relationship entries 34 | { 35 | return; 36 | } 37 | 38 | var relationshipElement = new XElement("relationship", new XAttribute("character", characterId), new XAttribute("value", 0)); 39 | var relationship = new Relationship(relationshipElement, characterName); 40 | _relationshipsNode.Add(relationshipElement); 41 | RelationshipsData.Add(relationship); 42 | _relationshipNodes.Add(relationshipElement); 43 | } 44 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/AirSpells.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.CharacterData.SpellData; 4 | 5 | public class AirSpells : ElementalSpells 6 | { 7 | private SpellTier[] PoisonVapoursSpellTiers { get; } = 8 | [ 9 | new("Unowned", "POISON_VAPOURS_UNOWNED"), new("Base", "POISON_VAPOURS"), 10 | new("Choking Haze", "POISON_VAPOURS_1"), 11 | new("Arcane Sickness", "POISON_VAPOURS_2"), 12 | new("Weakening Cloud", "POISON_VAPOURS_3") 13 | ]; 14 | 15 | private SpellTier[] VacuumSpellTiers { get; } = 16 | [ 17 | new("Unowned", "VACUUM_UNOWNED"), new("Base", "VACUUM"), 18 | new("Secondary Voids", "VACUUM_1"), new("Suction", "VACUUM_2"), 19 | new("Total Void", "VACUUM_3") 20 | ]; 21 | 22 | private SpellTier[] ProtectiveGustsSpellTiers { get; } = 23 | [ 24 | new("Unowned", "PROTECTIVE_GUSTS_UNOWNED"), 25 | new("Base", "PROTECTIVE_GUSTS"), 26 | new("Guiding Wind", "PROTECTIVE_GUSTS_1"), 27 | new("Focused Blast", "PROTECTIVE_GUSTS_2"), 28 | new("Lingering Presence", "PROTECTIVE_GUSTS_3") 29 | ]; 30 | 31 | private SpellTier[] ElementalAirSpellTiers { get; } = 32 | [ 33 | new("Unowned", "ELEMENTAL_AIR_UNOWNED"), 34 | new("Base", "ELEMENTAL_AIR"), 35 | new("Whirlwind", "ELEMENTAL_AIR_1"), 36 | new("Vitalising Scents", "ELEMENTAL_AIR_2"), 37 | new("Servant of Air", "ELEMENTAL_AIR_3A"), 38 | new("Binding of Air", "ELEMENTAL_AIR_3B") 39 | ]; 40 | 41 | public Spell PoisonVapours { get; } 42 | public Spell Vacuum { get; } 43 | public Spell ProtectiveGusts { get; } 44 | public Spell AirElemental { get; } 45 | 46 | public AirSpells(XElement knownSpellsNode, XElement spellUpgradesNode, XElement spellUpgradePointsNode) : base(knownSpellsNode, spellUpgradesNode) 47 | { 48 | PoisonVapours = new Spell(PoisonVapoursSpellTiers, knownSpellsNode, spellUpgradesNode); 49 | Vacuum = new Spell(VacuumSpellTiers, knownSpellsNode, spellUpgradesNode); 50 | ProtectiveGusts = new Spell(ProtectiveGustsSpellTiers, knownSpellsNode, spellUpgradesNode); 51 | AirElemental = new Spell(ElementalAirSpellTiers, knownSpellsNode, spellUpgradesNode); 52 | UpgradePoints = GetUpgradePointNode(spellUpgradePointsNode, "AIR"); 53 | } 54 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/EarthSpells.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.CharacterData.SpellData; 4 | 5 | public class EarthSpells : ElementalSpells 6 | { 7 | 8 | private static SpellTier[] SlamSpellTiers { get; } = 9 | [ 10 | new("Unowned", "SLAM_UNOWNED"), new("Base", "SLAM"), 11 | new("Ground Shake", "SLAM_1"), new("Aftershock", "SLAM_2"), 12 | new("Earthquake", "SLAM_3") 13 | ]; 14 | 15 | private static SpellTier[] TelekineticShowerSpellTiers { get; } = 16 | [ 17 | // Yes, that is how telekinetic is spelt in the save file 18 | new("Unowned", "TELEKENETIC_SHOWER_UNOWNED"), 19 | new("Base", "TELEKENETIC_SHOWER"), 20 | new("Mind Over Matter", "TELEKENETIC_SHOWER_1"), 21 | new("Precision Strikes", "TELEKENETIC_SHOWER_2"), 22 | new("Unseen Force", "TELEKENETIC_SHOWER_3") 23 | ]; 24 | 25 | private static SpellTier[] StoneShellSpellTiers { get; } = 26 | [ 27 | new("Unowned", "STONE_SHELL_UNOWNED"), new("Base", "STONE_SHELL"), 28 | new("Shifting Sands", "STONE_SHELL_1"), 29 | new("Hardened Carapace", "STONE_SHELL_2"), 30 | new("Explosive Finish", "STONE_SHELL_3") 31 | ]; 32 | 33 | private static SpellTier[] ElementalEarthSpellTiers { get; } = 34 | [ 35 | new("Unowned", "ELEMENTAL_EARTH_UNOWNED"), 36 | new("Base", "ELEMENTAL_EARTH"), 37 | new("Rolling Stone", "ELEMENTAL_EARTH_1"), 38 | new("Hardening", "ELEMENTAL_EARTH_2"), 39 | new("Servant of Earth", "ELEMENTAL_EARTH_3A"), 40 | new("Binding of Earth", "ELEMENTAL_EARTH_3B") 41 | ]; 42 | 43 | public Spell Slam { get; } 44 | public Spell TelekineticShower { get; } 45 | public Spell StoneShell { get; } 46 | public Spell EarthElemental { get; } 47 | 48 | 49 | public EarthSpells(XElement knownSpellsNode, XElement spellUpgradesNode, XElement spellUpgradePointsNode) : base(knownSpellsNode, spellUpgradesNode) 50 | { 51 | Slam = new Spell(SlamSpellTiers, knownSpellsNode, spellUpgradesNode); 52 | TelekineticShower = new Spell(TelekineticShowerSpellTiers, knownSpellsNode, spellUpgradesNode); 53 | StoneShell = new Spell(StoneShellSpellTiers, knownSpellsNode, spellUpgradesNode); 54 | EarthElemental = new Spell(ElementalEarthSpellTiers, knownSpellsNode, spellUpgradesNode); 55 | UpgradePoints = GetUpgradePointNode(spellUpgradePointsNode, "EARTH"); 56 | } 57 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/ElementalSpells.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.CharacterData.SpellData; 6 | 7 | public abstract class ElementalSpells(XElement knownSpellsNode, XElement spellUpgradesNode) 8 | { 9 | protected XElement KnownSpellsNode { get; } = knownSpellsNode; 10 | protected XElement SpellUpgradesNode { get; } = spellUpgradesNode; 11 | public XmlAttribute UpgradePoints { get; protected init; } = null!; 12 | 13 | protected static XmlAttribute GetUpgradePointNode(XElement spellUpgradePointsNode, string schoolName) 14 | { 15 | var spellUpgradePoints = spellUpgradePointsNode.Elements(); 16 | foreach (var upgradePoint in spellUpgradePoints) 17 | { 18 | if (upgradePoint.GetAttributeValue("school") == schoolName) 19 | { 20 | return new XmlAttribute(upgradePoint.Attribute("points")!); 21 | } 22 | } 23 | 24 | var pointAttribute = new XAttribute("points", 0); 25 | var upgradeEntry = new XElement("upgradeEntry", pointAttribute, new XAttribute("school", schoolName)); 26 | spellUpgradePointsNode.Add(upgradeEntry); 27 | return new XmlAttribute(pointAttribute); 28 | } 29 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/FireSpells.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.CharacterData.SpellData; 4 | 5 | public class FireSpells : ElementalSpells 6 | { 7 | private SpellTier[] FireballSpellTiers { get; } = 8 | [ 9 | new("Unowned", "FIREBALL_UNOWNED"), new("Base", "FIREBALL"), 10 | new("Lingering Flames", "FIREBALL_1"), new("Twin Comets", "FIREBALL_2"), 11 | new("Burning Fury", "FIREBALL_3") 12 | ]; 13 | 14 | private SpellTier[] FlashSpellTiers { get; } = 15 | [ 16 | new("Unowned", "FLASH_UNOWNED"), new("Base", "FLASH"), 17 | new("Secondary Sparks", "FLASH_1"), new("Arcing Flash", "FLASH_2"), 18 | new("Efficient Burn", "FLASH_3") 19 | ]; 20 | 21 | 22 | private SpellTier[] CloakOfFlamesSpellTiers { get; } = 23 | [ 24 | new("Unowned", "CLOAK_OF_FLAMES_UNOWNED"), 25 | new("Base", "CLOAK_OF_FLAMES"), 26 | new("Incendiary", "CLOAK_OF_FLAMES_1"), 27 | new("Inferno", "CLOAK_OF_FLAMES_2"), 28 | new("Ring of Fire", "CLOAK_OF_FLAMES_3") 29 | ]; 30 | 31 | private SpellTier[] ElementalFireSpellTiers { get; } = 32 | [ 33 | new("Unowned", "ELEMENTAL_FIRE_UNOWNED"), 34 | new("Base", "ELEMENTAL_FIRE"), 35 | new("Wildfire", "ELEMENTAL_FIRE_1"), 36 | new("Burning Desire", "ELEMENTAL_FIRE_2"), 37 | new("Servant of Fire", "ELEMENTAL_FIRE_3A"), 38 | new("Binding of Fire", "ELEMENTAL_FIRE_3B") 39 | ]; 40 | 41 | public Spell Fireball { get; } 42 | public Spell Flash { get; } 43 | public Spell CloakOfFlames { get; } 44 | public Spell FireElemental { get; } 45 | 46 | public FireSpells(XElement knownSpellsNode, XElement spellUpgradesNode, XElement spellUpgradePointsNode) : base(knownSpellsNode, spellUpgradesNode) 47 | { 48 | Fireball = new Spell(FireballSpellTiers, knownSpellsNode, spellUpgradesNode); 49 | Flash = new Spell(FlashSpellTiers, knownSpellsNode, spellUpgradesNode); 50 | CloakOfFlames = new Spell(CloakOfFlamesSpellTiers, knownSpellsNode, spellUpgradesNode); 51 | FireElemental = new Spell(ElementalFireSpellTiers, knownSpellsNode, spellUpgradesNode); 52 | UpgradePoints = GetUpgradePointNode(spellUpgradePointsNode, "FIRE"); 53 | } 54 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/MiscSpells.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | 4 | namespace LTSaveEd.Models.CharacterData.SpellData; 5 | 6 | public class MiscSpells : ElementalSpells 7 | { 8 | public NullableSpell WitchsSeal { get; } 9 | public NullableSpell WitchsCharm { get; } 10 | public NullableSpell SirensCall { get; } 11 | public NullableSpell LightningDischarge { get; } 12 | public NullableSpell LightningOvercharge { get; } 13 | public NullableSpell ChainLightning { get; } 14 | public NullableSpell LightningSuperbolt { get; } 15 | 16 | public MiscSpells(XElement knownSpellsNode, XElement spellUpgradesNode) : base(knownSpellsNode, spellUpgradesNode) 17 | { 18 | WitchsSeal = new NullableSpell(knownSpellsNode, "WITCH_SEAL", nodeName: "spell"); 19 | WitchsCharm = new NullableSpell(knownSpellsNode, "WITCH_CHARM", nodeName: "spell"); 20 | SirensCall = new NullableSpell(knownSpellsNode, "DARK_SIREN_SIRENS_CALL", nodeName: "spell"); 21 | LightningDischarge = new NullableSpell(knownSpellsNode, "LIGHTNING_SPHERE_DISCHARGE", nodeName: "spell"); 22 | LightningOvercharge = new NullableSpell(knownSpellsNode, "LIGHTNING_SPHERE_OVERCHARGE", nodeName: "spell"); 23 | ChainLightning = new NullableSpell(knownSpellsNode, "ARCANE_CHAIN_LIGHTNING", nodeName: "spell"); 24 | LightningSuperbolt = new NullableSpell(knownSpellsNode, "ARCANE_LIGHTNING_SUPERBOLT", nodeName: "spell"); 25 | 26 | var spells = knownSpellsNode.Elements(); 27 | foreach (var spell in spells) 28 | { 29 | var spellType = spell.GetAttributeValue("type"); 30 | switch (spellType) 31 | { 32 | case "WITCH_SEAL": 33 | WitchsSeal.Initialize(spell); 34 | break; 35 | case "WITCH_CHARM": 36 | WitchsCharm.Initialize(spell); 37 | break; 38 | case "DARK_SIREN_SIRENS_CALL": 39 | SirensCall.Initialize(spell); 40 | break; 41 | case "LIGHTNING_SPHERE_DISCHARGE": 42 | LightningDischarge.Initialize(spell); 43 | break; 44 | case "LIGHTNING_SPHERE_OVERCHARGE": 45 | LightningOvercharge.Initialize(spell); 46 | break; 47 | case "ARCANE_CHAIN_LIGHTNING": 48 | ChainLightning.Initialize(spell); 49 | break; 50 | case "ARCANE_LIGHTNING_SUPERBOLT": 51 | LightningSuperbolt.Initialize(spell); 52 | break; 53 | default: 54 | Console.WriteLine($"Unknown Misc Spell Encountered: {spellType}"); 55 | break; 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/NullableSpell.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.XmlData; 3 | 4 | namespace LTSaveEd.Models.CharacterData.SpellData; 5 | 6 | public class NullableSpell(XElement spellUpgradesNode, string type, string nodeName = "upgrade") : NullableXmlObject(spellUpgradesNode) 7 | { 8 | private string Type { get; } = type; 9 | private string NodeName { get; } = nodeName; 10 | 11 | private XElement SpellUpgradesNode => Parent; // Makes the relationship a bit more clear 12 | 13 | protected override XElement CreateNode() 14 | { 15 | var upgrade = new XElement(NodeName, new XAttribute("type", Type)); 16 | SpellUpgradesNode.Add(upgrade); 17 | return upgrade; 18 | } 19 | } -------------------------------------------------------------------------------- /Models/CharacterData/SpellData/SpellTier.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.CharacterData.SpellData; 2 | 3 | public class SpellTier(string name, string value) : ValueDisplayPair(name, value) 4 | { 5 | private static string[] Endings { get; } = ["_UNOWNED", "_3A", "_3B", "_3", "_2", "_1"]; 6 | 7 | public string Type { get; } = ReadType(value); 8 | public int Tier { get; } = ReadTier(value); 9 | 10 | private static char GetLastNum(string str){ 11 | var num = str[^1]; 12 | if(num is 'A' or 'B'){ 13 | num = '3'; 14 | } 15 | return num; 16 | } 17 | 18 | public static string ReadType(string str){ 19 | return Endings.Aggregate(str, (current, replacement) => current.Replace(replacement, "")); 20 | } 21 | 22 | public static int ReadTier(string value) 23 | { 24 | var valueType = ReadType(value); 25 | if(value.EndsWith("UNOWNED")){ 26 | return -1; 27 | } 28 | 29 | if(value == valueType){ 30 | return 0; 31 | } 32 | 33 | if (valueType != "STEAL") 34 | { 35 | return (int)char.GetNumericValue(GetLastNum(value)); 36 | } 37 | 38 | if(value.EndsWith("3A")){ 39 | return 3; 40 | } 41 | 42 | if(value.EndsWith("3B")){ 43 | return 4; 44 | } 45 | 46 | return (int)char.GetNumericValue(GetLastNum(value)); 47 | } 48 | } -------------------------------------------------------------------------------- /Models/CharacterData/Spells.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.CharacterData.SpellData; 3 | 4 | namespace LTSaveEd.Models.CharacterData; 5 | 6 | public class Spells 7 | { 8 | public EarthSpells Earth { get; } 9 | public WaterSpells Water { get; } 10 | public FireSpells Fire { get; } 11 | public AirSpells Air { get; } 12 | public ArcaneSpells Arcane { get; } 13 | public MiscSpells Misc { get; } 14 | 15 | public Spells(XElement knownSpellsNode, XElement spellUpgradesNode, XElement spellUpgradePointsNode) 16 | { 17 | Earth = new EarthSpells(knownSpellsNode, spellUpgradesNode, spellUpgradePointsNode); 18 | Water = new WaterSpells(knownSpellsNode, spellUpgradesNode, spellUpgradePointsNode); 19 | Fire = new FireSpells(knownSpellsNode, spellUpgradesNode, spellUpgradePointsNode); 20 | Air = new AirSpells(knownSpellsNode, spellUpgradesNode, spellUpgradePointsNode); 21 | Arcane = new ArcaneSpells(knownSpellsNode, spellUpgradesNode, spellUpgradePointsNode); 22 | Misc = new MiscSpells(knownSpellsNode, spellUpgradesNode); 23 | } 24 | } -------------------------------------------------------------------------------- /Models/CharacterShort.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models; 6 | 7 | public class CharacterShort 8 | { 9 | public XmlAttribute Id { get; set; } 10 | public XmlAttribute Name { get; set; } 11 | public XmlAttribute Femininity { get; set; } 12 | public XmlAttribute Subspecies { get; set; } 13 | 14 | public CharacterShort(XElement familyNode, bool mother) 15 | { 16 | string id; 17 | string name; 18 | string femininity; 19 | string subspecies; 20 | if (mother) 21 | { 22 | id = "motherId"; 23 | name = "motherName"; 24 | femininity = "motherFemininity"; 25 | subspecies = "motherSubspecies"; 26 | } 27 | else 28 | { 29 | id = "fatherId"; 30 | name = "fatherName"; 31 | femininity = "fatherFemininity"; 32 | subspecies = "fatherSubspecies"; 33 | } 34 | Id = new XmlAttribute(familyNode.GetChildsAttributeNode(id)); 35 | Name = new XmlAttribute(familyNode.GetChildsAttributeNode(name)); 36 | Femininity = new XmlAttribute
Sorry, there's nothing at this address.