├── .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(familyNode.GetChildsAttributeNode(femininity)); 37 | Subspecies = new XmlAttribute(familyNode.GetChildsAttributeNode(subspecies)); 38 | } 39 | } -------------------------------------------------------------------------------- /Models/CharacterShortData.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | 4 | namespace LTSaveEd.Models; 5 | 6 | public class CharacterShortData 7 | { 8 | public string Id { get; set; } 9 | public string Name { get; set; } 10 | public string Femininity { get; set; } 11 | public string Subspecies { get; set; } 12 | 13 | public CharacterShortData(string id, string name, string femininity, string subspecies) 14 | { 15 | Id = id; 16 | Name = name; 17 | Femininity = femininity; 18 | Subspecies = subspecies; 19 | } 20 | 21 | public CharacterShortData(XElement characterNode) 22 | { 23 | var coreNode = characterNode.Element("core")!; 24 | var nameNode = coreNode.Element("name")!; 25 | var bodyCoreNode = characterNode.Element("body")!.Element("bodyCore")!; 26 | var femininity = int.Parse(bodyCoreNode.Attribute("femininity")!.Value); 27 | Id = coreNode.Element("id")!.Attribute("value")!.Value; 28 | Femininity = femininity switch 29 | { 30 | < 20 => "VERY_MASCULINE", 31 | < 40 => "MASCULINE", 32 | < 60 => "ANDROGYNOUS", 33 | < 80 => "FEMININE", 34 | >= 80 => "VERY_FEMININE" 35 | }; 36 | Subspecies = bodyCoreNode.Attribute("subspecies")!.Value; 37 | Name = femininity switch 38 | { 39 | < 40 => nameNode.Attribute("nameMasculine")!.Value, 40 | <= 60 => nameNode.Attribute("nameAndrogynous")!.Value, 41 | > 60 => nameNode.Attribute("nameFeminine")!.Value 42 | }; 43 | } 44 | } -------------------------------------------------------------------------------- /Models/Enums/ApplicationStateLocation.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums; 2 | 3 | public enum ApplicationStateLocation 4 | { 5 | SaveEditor, 6 | ModEditor, 7 | ModEditorHome, 8 | } -------------------------------------------------------------------------------- /Models/Enums/Game/ColorTag.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum ColorTag 4 | { 5 | // These tags will add the Colour to relevant covering lists. 6 | // Some covering lists only have natural colours, while some have both natural and dye colours (such as SLIME and FEATHER). 7 | Skin, 8 | SlimeNatural, 9 | SlimeDye, 10 | FeatherNatural, 11 | FeatherDye, 12 | Fur, 13 | Scale, 14 | Horn, 15 | Antler, 16 | Hair, 17 | 18 | // This tag will add the Colour to the 'all coverings' list: 19 | GenericCovering, 20 | 21 | // Enables the Colour to be used as a makeup colour 22 | Makeup, 23 | 24 | // Naturally-spawning iris colour, which is available to non-predatory races: 25 | IrisNatural, 26 | // Extra iris colour (only obtainable via transformation), which is available to non-predatory races: 27 | IrisDye, 28 | 29 | // Naturally-spawning iris colour, which is available to predatory races: 30 | IrisPredatorNatural, 31 | // Extra iris colour (only obtainable via transformation), which is available to predatory races: 32 | IrisPredatorDye, 33 | 34 | // Naturally-spawning pupil colour: 35 | PupilNatural, 36 | // Extra pupil colour (only obtainable via transformation): 37 | PupilDye, 38 | 39 | // Naturally-spawning sclera colour: 40 | ScleraNatural, 41 | // Extra sclera colour (only obtainable via transformation): 42 | ScleraDye, 43 | } 44 | 45 | public static class ColorTagExtensions 46 | { 47 | public static string GetValue(this ColorTag tag) 48 | { 49 | return tag switch 50 | { 51 | ColorTag.Skin => "SKIN", 52 | ColorTag.SlimeNatural => "SLIME_NATURAL", 53 | ColorTag.SlimeDye => "SLIME_DYE", 54 | ColorTag.FeatherNatural => "FEATHER_NATURAL", 55 | ColorTag.FeatherDye => "FEATHER_DYE", 56 | ColorTag.Fur => "FUR", 57 | ColorTag.Scale => "SCALE", 58 | ColorTag.Horn => "HORN", 59 | ColorTag.Antler => "ANTLER", 60 | ColorTag.Hair => "HAIR", 61 | ColorTag.GenericCovering => "GENERIC_COVERING", 62 | ColorTag.Makeup => "MAKEUP", 63 | ColorTag.IrisNatural => "IRIS_NATURAL", 64 | ColorTag.IrisDye => "IRIS_DYE", 65 | ColorTag.IrisPredatorNatural => "IRIS_PREDATOR_NATURAL", 66 | ColorTag.IrisPredatorDye => "IRIS_PREDATOR_DYE", 67 | ColorTag.PupilNatural => "PUPIL_NATURAL", 68 | ColorTag.PupilDye => "PUPIL_DYE", 69 | ColorTag.ScleraNatural => "SCLERA_NATURAL", 70 | ColorTag.ScleraDye => "SCLERA_DYE", 71 | _ => throw new ArgumentOutOfRangeException(nameof(tag), tag, null) 72 | }; 73 | } 74 | } -------------------------------------------------------------------------------- /Models/Enums/Game/CombatMoveCategory.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum CombatMoveCategory 4 | { 5 | /** Moves available to everyone by default. */ 6 | Basic, 7 | 8 | /** Spells. */ 9 | Spell, 10 | 11 | /** Any move derived from body parts, weapons, fetishes, etc. */ 12 | Special, 13 | } 14 | 15 | public static class CombatMoveCategoryExtensions 16 | { 17 | public static string GetValue(this CombatMoveCategory category) 18 | { 19 | return category switch 20 | { 21 | CombatMoveCategory.Basic => "BASIC", 22 | CombatMoveCategory.Spell => "SPELL", 23 | CombatMoveCategory.Special => "SPECIAL", 24 | _ => throw new ArgumentOutOfRangeException(nameof(category), category, null) 25 | }; 26 | } 27 | 28 | public static CombatMoveCategory FromValue(string value) 29 | { 30 | return value switch 31 | { 32 | "BASIC" => CombatMoveCategory.Basic, 33 | "SPELL" => CombatMoveCategory.Spell, 34 | "SPECIAL" => CombatMoveCategory.Special, 35 | _ => CombatMoveCategory.Basic, // Default to Basic if the value is unknown 36 | }; 37 | } 38 | } -------------------------------------------------------------------------------- /Models/Enums/Game/CombatMoveType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum CombatMoveType 4 | { 5 | Attack, 6 | Defend, 7 | Tease, 8 | Spell, 9 | Power, 10 | //Skill, 11 | AttackDefend, 12 | } 13 | 14 | public static class CombatMoveTypeExtensions 15 | { 16 | public static string GetValue(this CombatMoveType type) 17 | { 18 | return type switch 19 | { 20 | CombatMoveType.Attack => "ATTACK", 21 | CombatMoveType.Defend => "DEFEND", 22 | CombatMoveType.Tease => "TEASE", 23 | CombatMoveType.Spell => "SPELL", 24 | CombatMoveType.Power => "POWER", 25 | //CombatMoveType.SKILL => "SKILL", 26 | CombatMoveType.AttackDefend => "ATTACK_DEFEND", 27 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) 28 | }; 29 | } 30 | 31 | public static CombatMoveType FromValue(string value) 32 | { 33 | return value switch 34 | { 35 | "ATTACK" => CombatMoveType.Attack, 36 | "DEFEND" => CombatMoveType.Defend, 37 | "TEASE" => CombatMoveType.Tease, 38 | "SPELL" => CombatMoveType.Spell, 39 | "POWER" => CombatMoveType.Power, 40 | //"SKILL" => CombatMoveType.SKILL, 41 | "ATTACK_DEFEND" => CombatMoveType.AttackDefend, 42 | _ => CombatMoveType.Attack // Default to Attack if the value is unknown 43 | }; 44 | } 45 | } -------------------------------------------------------------------------------- /Models/Enums/Game/DamageType.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum DamageType 4 | { 5 | Health, 6 | Physical, 7 | Ice, 8 | Fire, 9 | Poison, 10 | Unarmed, 11 | Lust, 12 | Misc 13 | } 14 | 15 | public static class DamageTypeExtensions 16 | { 17 | public static string GetValue(this DamageType type) 18 | { 19 | return type switch 20 | { 21 | DamageType.Health => "HEALTH", 22 | DamageType.Physical => "PHYSICAL", 23 | DamageType.Ice => "ICE", 24 | DamageType.Fire => "FIRE", 25 | DamageType.Poison => "POISON", 26 | DamageType.Unarmed => "UNARMED", 27 | DamageType.Lust => "LUST", 28 | DamageType.Misc => "MISC", 29 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) 30 | }; 31 | } 32 | 33 | public static DamageType FromValue(string value) 34 | { 35 | return value switch 36 | { 37 | "HEALTH" => DamageType.Health, 38 | "PHYSICAL" => DamageType.Physical, 39 | "ICE" => DamageType.Ice, 40 | "FIRE" => DamageType.Fire, 41 | "POISON" => DamageType.Poison, 42 | "UNARMED" => DamageType.Unarmed, 43 | "LUST" => DamageType.Lust, 44 | "MISC" => DamageType.Misc, 45 | _ => DamageType.Health // Default case, can be adjusted as needed 46 | }; 47 | } 48 | } -------------------------------------------------------------------------------- /Models/Enums/Game/DamageVariance.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum DamageVariance 4 | { 5 | None, 6 | Low, 7 | Medium, 8 | High, 9 | } 10 | 11 | public static class DamageVarianceExtensions 12 | { 13 | public static string GetValue(this DamageVariance variance) 14 | { 15 | return variance switch 16 | { 17 | DamageVariance.None => "NONE", 18 | DamageVariance.Low => "LOW", 19 | DamageVariance.Medium => "MEDIUM", 20 | DamageVariance.High => "HIGH", 21 | _ => throw new ArgumentOutOfRangeException(nameof(variance), variance, null) 22 | }; 23 | } 24 | 25 | public static DamageVariance FromValue(string value) 26 | { 27 | return value switch 28 | { 29 | "NONE" => DamageVariance.None, 30 | "LOW" => DamageVariance.Low, 31 | "MEDIUM" => DamageVariance.Medium, 32 | "HIGH" => DamageVariance.High, 33 | _ => DamageVariance.None // Default case, can be adjusted as needed 34 | }; 35 | } 36 | } -------------------------------------------------------------------------------- /Models/Enums/Game/Femininity.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum Femininity 4 | { 5 | Feminine, 6 | Masculine, 7 | Androgynous, 8 | } 9 | 10 | public static class FemininityExtensions 11 | { 12 | public static string GetValue(this Femininity femininity) 13 | { 14 | return femininity switch 15 | { 16 | Femininity.Feminine => "FEMININE", 17 | Femininity.Masculine => "MASCULINE", 18 | Femininity.Androgynous => "ANDROGYNOUS", 19 | _ => throw new ArgumentOutOfRangeException(nameof(femininity), femininity, null) 20 | }; 21 | } 22 | 23 | public static Femininity FromValue(string value) 24 | { 25 | return value switch 26 | { 27 | "FEMININE" => Femininity.Feminine, 28 | "MASCULINE" => Femininity.Masculine, 29 | "ANDROGYNOUS" => Femininity.Androgynous, 30 | _ => Femininity.Androgynous, // Default to Androgynous if value is unknown 31 | }; 32 | } 33 | } -------------------------------------------------------------------------------- /Models/Enums/Game/Rarity.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models.Enums.Game; 2 | 3 | public enum Rarity 4 | { 5 | Common, 6 | Uncommon, 7 | Rare, 8 | Epic, 9 | Legendary, 10 | Quest, 11 | Jinxed, 12 | } 13 | 14 | public static class RarityExtensions 15 | { 16 | public static string GetValue(this Rarity rarity) 17 | { 18 | return rarity switch 19 | { 20 | Rarity.Common => "COMMON", 21 | Rarity.Uncommon => "UNCOMMON", 22 | Rarity.Rare => "RARE", 23 | Rarity.Epic => "EPIC", 24 | Rarity.Legendary => "LEGENDARY", 25 | Rarity.Quest => "QUEST", 26 | Rarity.Jinxed => "JINXED", 27 | _ => throw new ArgumentOutOfRangeException(nameof(rarity), rarity, null) 28 | }; 29 | } 30 | 31 | public static Rarity FromValue(string value) 32 | { 33 | return value switch 34 | { 35 | "COMMON" => Rarity.Common, 36 | "UNCOMMON" => Rarity.Uncommon, 37 | "RARE" => Rarity.Rare, 38 | "EPIC" => Rarity.Epic, 39 | "LEGENDARY" => Rarity.Legendary, 40 | "QUEST" => Rarity.Quest, 41 | "JINXED" => Rarity.Jinxed, 42 | _ => Rarity.Common // Default to Common if the value is unknown 43 | }; 44 | } 45 | } -------------------------------------------------------------------------------- /Models/JSWrappers/FileHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace LTSaveEd.Models.JSWrappers; 4 | 5 | public class FileHandler(IJSRuntime jsRuntime) : JsWrapper(jsRuntime) 6 | { 7 | protected override string JsFileName => "/js/FileHandler.js"; 8 | 9 | public async Task DownloadFileFromByteArrayAsync(byte[] data, string fileName) 10 | { 11 | await WaitForReference(); 12 | await AccessorJsRef.Value.InvokeVoidAsync("saveFile", data, fileName); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/JSWrappers/JsWrapper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace LTSaveEd.Models.JSWrappers; 4 | 5 | public abstract class JsWrapper(IJSRuntime jsRuntime) : IAsyncDisposable 6 | { 7 | protected abstract string JsFileName { get; } 8 | 9 | protected Lazy AccessorJsRef = new(); 10 | 11 | protected async Task WaitForReference() 12 | { 13 | if (AccessorJsRef.IsValueCreated is false) 14 | { 15 | AccessorJsRef = new Lazy(await jsRuntime.InvokeAsync("import", JsFileName)); 16 | } 17 | } 18 | 19 | public async ValueTask DisposeAsync() 20 | { 21 | GC.SuppressFinalize(this); 22 | if (AccessorJsRef.IsValueCreated) 23 | { 24 | await AccessorJsRef.Value.DisposeAsync(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Models/JSWrappers/LocalStorageAccessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace LTSaveEd.Models.JSWrappers; 4 | 5 | public class LocalStorageAccessor(IJSRuntime jsRuntime) : JsWrapper(jsRuntime) 6 | { 7 | protected override string JsFileName => "/js/LocalStorageAccessor.js"; 8 | 9 | public async Task GetValueAsync(string key) 10 | { 11 | await WaitForReference(); 12 | var result = await AccessorJsRef.Value.InvokeAsync("get", key); 13 | if (typeof(T) == typeof(int)) 14 | { 15 | return (T)(object)int.Parse(result); 16 | } 17 | if (typeof(T) == typeof(float)) 18 | { 19 | return (T)(object)float.Parse(result); 20 | } 21 | if (typeof(T) == typeof(bool)) 22 | { 23 | return (T)(object)bool.Parse(result); 24 | } 25 | return (T)(object)result; 26 | } 27 | 28 | public async Task CheckValueExistsAsync(string key) 29 | { 30 | await WaitForReference(); 31 | return await AccessorJsRef.Value.InvokeAsync("exists", key); 32 | } 33 | 34 | public async Task SetValueAsync(string key, T value) 35 | { 36 | await WaitForReference(); 37 | await AccessorJsRef.Value.InvokeVoidAsync("set", key, value); 38 | } 39 | 40 | public async Task Clear() 41 | { 42 | await WaitForReference(); 43 | await AccessorJsRef.Value.InvokeVoidAsync("clear"); 44 | } 45 | 46 | public async Task RemoveAsync(string key) 47 | { 48 | await WaitForReference(); 49 | await AccessorJsRef.Value.InvokeVoidAsync("remove", key); 50 | } 51 | } -------------------------------------------------------------------------------- /Models/ModEditor/ClothingMod.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.ModEditor.Xml; 3 | using LTSaveEd.Models.ModEditor.Xml.EnumXml; 4 | using LTSaveEd.Models.XmlData; 5 | 6 | namespace LTSaveEd.Models.ModEditor; 7 | 8 | public class ClothingMod : Mod 9 | { 10 | public XmlElement Value { get; set; } = null!; 11 | public XmlElement Determiner { get; set; } = null!; 12 | public XmlCData Name { get; set; } = null!; 13 | public XmlAttribute AppendColorName { get; set; } = null!; 14 | public XmlCData PluralName { get; set; } = null!; 15 | public XmlAttribute PluralByDefault { get; set; } = null!; 16 | public XmlCData Description { get; set; } = null!; 17 | public XmlElement PhysicalResistance { get; set; } = null!; 18 | public FemininityElement Femininity { get; set; } = null!; 19 | public EquipSlotElements EquipSlots { get; set; } = null!; 20 | public RarityElement Rarity { get; set; } = null!; 21 | // ClothingSet 22 | public XmlElement ImageName { get; set; } = null!; 23 | 24 | // Internal use only, do not use this constructor. 25 | public ClothingMod() 26 | { 27 | } 28 | 29 | protected override XDocument CreateNewModDocument() 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } -------------------------------------------------------------------------------- /Models/ModEditor/CombatMoveMod.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.ModEditor.Xml; 3 | using LTSaveEd.Models.ModEditor.Xml.EnumXml; 4 | using LTSaveEd.Models.XmlData; 5 | 6 | namespace LTSaveEd.Models.ModEditor; 7 | 8 | public class CombatMoveMod : Mod 9 | { 10 | public CombatMoveCategoryElement Category { get; set; } = null!; 11 | public CombatMoveTypeElement Type { get; set; } = null!; 12 | public XmlElement EquipWeighting { get; set; } = null!; 13 | public XmlCData Name { get; set; } = null!; 14 | public XmlCData Description { get; set; } = null!; 15 | public DamageTypeElement DamageType { get; set; } = null!; 16 | public XmlCData BaseDamage { get; set; } = null!; 17 | public DamageVarianceElement DamageVariance { get; set; } = null!; 18 | public XmlCData BlockAmount { get; set; } = null!; 19 | public XmlCData Cooldown { get; set; } = null!; 20 | public XmlCData ApCost { get; set; } = null!; 21 | public XmlElement CanTargetEnemies { get; set; } = null!; 22 | public XmlElement CanTargetAllies { get; set; } = null!; 23 | public XmlElement CanTargetSelf { get; set; } = null!; 24 | public XmlElement ImageName { get; set; } = null!; 25 | public PresetColorElement PrimaryColor { get; set; } = null!; 26 | public PresetColorElement SecondaryColor { get; set; } = null!; 27 | public PresetColorElement TertiaryColor { get; set; } = null!; 28 | // Status Effects 29 | public XmlCData AvailabilityCondition { get; set; } = null!; 30 | public XmlCData AvailabilityDescription { get; set; } = null!; 31 | public XmlCData Weighting { get; set; } = null!; 32 | public XmlCData CriticalCondition { get; set; } = null!; 33 | public XmlCData CriticalDescription { get; set; } = null!; 34 | public XmlCData MovePredictionDescriptionWithTarget { get; set; } = null!; 35 | public XmlCData MovePredictionDescriptionNoTarget { get; set; } = null!; 36 | // Perform move 37 | 38 | public CombatMoveMod() 39 | { 40 | 41 | } 42 | 43 | private CombatMoveMod(XDocument root) : base(root) 44 | { 45 | if (root.Root is null || root.Root.Name.LocalName != "combatMove") 46 | { 47 | throw new InvalidOperationException("Invalid XML structure: Root element must be 'combatMove'."); 48 | } 49 | 50 | var element = root.Root; 51 | } 52 | 53 | protected override XDocument CreateNewModDocument() 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | } -------------------------------------------------------------------------------- /Models/ModEditor/Mod.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.ModEditor; 4 | 5 | public abstract class Mod 6 | { 7 | public XDocument Root { get; private set; } = null!; 8 | 9 | // Internal use only, to create a new mod document. 10 | protected Mod() 11 | { 12 | 13 | } 14 | 15 | protected Mod(XDocument root) 16 | { 17 | Root = root; 18 | } 19 | 20 | protected abstract XDocument CreateNewModDocument(); 21 | 22 | public static T Load(XDocument root) where T : Mod 23 | { 24 | return (T)Activator.CreateInstance(typeof(T), root)!; 25 | } 26 | 27 | public static T New() where T : Mod, new() 28 | { 29 | var root = new T().CreateNewModDocument(); 30 | return (T)Activator.CreateInstance(typeof(T), root)!; 31 | } 32 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/ColorTagsElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml; 6 | 7 | public class ColorTagsElement : NullableXmlObject 8 | { 9 | private XElement? TagsNode => (XElement?)Node; 10 | 11 | public ColorTagsElement(XElement parent) : base(parent) 12 | { 13 | var tags = parent.Elements("tags").FirstOrDefault(); 14 | if (tags is not null) 15 | { 16 | Initialize(tags); 17 | } 18 | } 19 | 20 | protected override XObject CreateNode() 21 | { 22 | var tags = new XElement("tags"); 23 | Parent.Add(tags); // Probably should move child assignment up to the calling method 24 | return tags; 25 | } 26 | 27 | private int GetTagCount() 28 | { 29 | return TagsNode?.Elements("tag").Count() ?? 0; 30 | } 31 | 32 | private static XElement CreateColorTagNode(ColorTag tag) 33 | { 34 | return new XElement("tag", tag.GetValue()); 35 | } 36 | 37 | private XElement? GetTagNode(ColorTag tag) 38 | { 39 | return TagsNode?.Elements("tag").FirstOrDefault(t => t.Value == tag.GetValue()); 40 | } 41 | 42 | public void AddTag(ColorTag tag) 43 | { 44 | var tagNode = CreateColorTagNode(tag); 45 | Exists = true; 46 | if (GetTagNode(tag) is not null) 47 | { 48 | return; // Tag already exists, no need to add it again 49 | } 50 | 51 | TagsNode?.Add(tagNode); 52 | } 53 | 54 | public void RemoveTag(ColorTag tag) 55 | { 56 | var tagNode = GetTagNode(tag); 57 | tagNode?.Remove(); 58 | if (GetTagCount() == 0) 59 | { 60 | Exists = false; 61 | } 62 | } 63 | 64 | public bool HasTag(ColorTag tag) 65 | { 66 | return GetTagNode(tag) is not null; 67 | } 68 | 69 | public override string ToString() 70 | { 71 | return TagsNode is null 72 | ? "[]" 73 | : "[" + string.Join(", ", TagsNode.Elements("tag").Select(t => t.Value)) + "]"; 74 | } 75 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/CombatMoveCategoryElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class CombatMoveCategoryElement(XElement element) : XmlEnum(element) 8 | { 9 | public override CombatMoveCategory Value 10 | { 11 | get => CombatMoveCategoryExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/CombatMoveTypeElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class CombatMoveTypeElement(XElement element) : XmlEnum(element) 8 | { 9 | public override CombatMoveType Value 10 | { 11 | get => CombatMoveTypeExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/DamageTypeElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class DamageTypeElement(XCData element) : XmlEnum(element) 8 | { 9 | public override DamageType Value 10 | { 11 | get => DamageTypeExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/DamageVarianceElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class DamageVarianceElement(XElement element) : XmlEnum(element) 8 | { 9 | public override DamageVariance Value 10 | { 11 | get => DamageVarianceExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/FemininityElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class FemininityElement(XElement element) : XmlEnum(element) 8 | { 9 | public override Femininity Value 10 | { 11 | get => FemininityExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/InventorySlotElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class InventorySlotElement(XElement element) : XmlEnum(element) 8 | { 9 | public override InventorySlot Value 10 | { 11 | get => InventorySlotExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/PresetColorElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class PresetColorElement(XElement element) : XmlEnum(element) 8 | { 9 | public override PresetColor Value 10 | { 11 | get => PresetColorExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EnumXml/RarityElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.XmlData; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml.EnumXml; 6 | 7 | public class RarityElement(XElement element) : XmlEnum(element) 8 | { 9 | public override Rarity Value 10 | { 11 | get => RarityExtensions.FromValue(Element.Value); 12 | set => Element.Value = value.GetValue(); 13 | } 14 | } -------------------------------------------------------------------------------- /Models/ModEditor/Xml/EquipSlotElements.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.Models.Enums.Game; 3 | using LTSaveEd.Models.ModEditor.Xml.EnumXml; 4 | 5 | namespace LTSaveEd.Models.ModEditor.Xml; 6 | 7 | public class EquipSlotElements 8 | { 9 | private readonly XElement _parent; 10 | 11 | public List Slots { get; private set; } = []; 12 | 13 | public EquipSlotElements(XElement parent) 14 | { 15 | _parent = parent; 16 | var slots = parent.Elements("Slot") 17 | .Select(slot => new InventorySlotElement(slot)); 18 | Slots.AddRange(slots); 19 | } 20 | 21 | private static XElement CreateSlotNode(InventorySlot slot) 22 | { 23 | var node = new XElement("slot", slot.GetValue()); 24 | return node; 25 | } 26 | 27 | private XElement? GetSlotNode(InventorySlot slot) 28 | { 29 | return _parent.Elements("slot").FirstOrDefault(s => s.Value == slot.GetValue()); 30 | } 31 | 32 | private void AddSlot(InventorySlot slot) 33 | { 34 | var slotNode = CreateSlotNode(slot); 35 | if (GetSlotNode(slot) is not null) 36 | { 37 | return; // Slot already exists, no need to add it again 38 | } 39 | 40 | _parent.Add(slotNode); 41 | Slots.Add(new InventorySlotElement(slotNode)); 42 | } 43 | 44 | private void RemoveSlot(InventorySlot slot) 45 | { 46 | var slotNode = GetSlotNode(slot); 47 | var slotElement = Slots.FirstOrDefault(s => s.Value == slot); 48 | // Has a possibility to desync with the XML if the slot was not found in Slots 49 | slotNode?.Remove(); 50 | if (slotElement is not null) 51 | { 52 | Slots.Remove(slotElement); 53 | } 54 | } 55 | 56 | public bool HasSlot(InventorySlot slot) 57 | { 58 | return GetSlotNode(slot) is not null; 59 | } 60 | 61 | public override string ToString() 62 | { 63 | return "[" + string.Join(", ", _parent.Elements("slot").Select(s => s.Value)) + "]"; 64 | } 65 | } -------------------------------------------------------------------------------- /Models/Offspring.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | using LTSaveEd.Models.CharacterData; 4 | using LTSaveEd.Models.XmlData; 5 | 6 | namespace LTSaveEd.Models; 7 | 8 | public class Offspring 9 | { 10 | public XmlAttribute Id { get; } 11 | public XmlAttribute FromPlayer { get; } 12 | public XmlAttribute Born { get; } 13 | public Name Name { get; } 14 | public Family Family { get; } 15 | 16 | public Offspring(XElement offspringSeedNode) 17 | { 18 | var dataNode = offspringSeedNode.Element("data")!; 19 | Id = new XmlAttribute(dataNode.GetChildsAttributeNode("id")); 20 | FromPlayer = new XmlAttribute(dataNode.GetChildsAttributeNode("fromPlayer")); 21 | Born = new XmlAttribute(dataNode.GetChildsAttributeNode("born")); 22 | 23 | var nameNode = dataNode.Element("name")!; 24 | var surnameNode = dataNode.Element("surname")!; 25 | Name = new Name(nameNode, surnameNode); 26 | 27 | var familyNode = offspringSeedNode.Element("family")!; 28 | Family = new Family(familyNode); 29 | } 30 | } -------------------------------------------------------------------------------- /Models/Settings.cs: -------------------------------------------------------------------------------- 1 | using LTSaveEd.Models.JSWrappers; 2 | 3 | namespace LTSaveEd.Models; 4 | 5 | public class Settings 6 | { 7 | private LocalStorageAccessor _localStorageAccessor = null!; 8 | private bool _darkMode; 9 | 10 | public bool DarkMode 11 | { 12 | get => _darkMode; 13 | set 14 | { 15 | _darkMode = value; 16 | SaveSettingAsync(SettingsKey.DarkMode, value); 17 | } 18 | } 19 | 20 | public async Task InitializeAsync(LocalStorageAccessor localStorageAccessor) 21 | { 22 | _localStorageAccessor = localStorageAccessor; 23 | _darkMode = await ReadOrSetDefaultSettingAsync(SettingsKey.DarkMode, false); 24 | } 25 | 26 | // ReSharper disable once AsyncVoidMethod 27 | private async void SaveSettingAsync(SettingsKey key, T value) where T : struct 28 | { 29 | var keyString = key.ToString(); 30 | try 31 | { 32 | await _localStorageAccessor.SetValueAsync(keyString, value); 33 | } 34 | catch (Exception ex) 35 | { 36 | Console.WriteLine($"Error saving setting {keyString}: {ex}"); 37 | } 38 | } 39 | 40 | private async Task ReadOrSetDefaultSettingAsync(SettingsKey key, T defaultValue) 41 | { 42 | var keyString = key.ToString(); 43 | if (await _localStorageAccessor.CheckValueExistsAsync(keyString)) 44 | { 45 | return await _localStorageAccessor.GetValueAsync(keyString); 46 | } 47 | 48 | await _localStorageAccessor.SetValueAsync(keyString, defaultValue); 49 | return defaultValue; 50 | } 51 | } -------------------------------------------------------------------------------- /Models/SettingsKey.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models; 2 | 3 | public enum SettingsKey 4 | { 5 | DarkMode, 6 | SearchableCharacterSelect 7 | } -------------------------------------------------------------------------------- /Models/ValueDisplayPair.cs: -------------------------------------------------------------------------------- 1 | namespace LTSaveEd.Models; 2 | 3 | public class ValueDisplayPair(string displayValue, T value) 4 | { 5 | public string DisplayValue { get; } = displayValue; 6 | public T Value { get; } = value; 7 | 8 | public override string ToString() 9 | { 10 | return DisplayValue; 11 | } 12 | } -------------------------------------------------------------------------------- /Models/XmlData/LabeledXmlAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.XmlData; 4 | 5 | public class LabeledXmlAttribute(XAttribute attribute, Func labelGenerator) : XmlAttribute(attribute) 6 | { 7 | public string Label => labelGenerator(Value); 8 | } -------------------------------------------------------------------------------- /Models/XmlData/NullableXmlElement.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.XmlData; 4 | 5 | public class NullableXmlElement(XElement parent, string tagName) : NullableXmlObject(parent) 6 | { 7 | public T? Value 8 | { 9 | get 10 | { 11 | if (Node is null) 12 | { 13 | return default; 14 | } 15 | 16 | var element = (XElement)Node; 17 | if (typeof(T) == typeof(int)) 18 | { 19 | return (T)(object)int.Parse(element.Value); 20 | } 21 | if (typeof(T) == typeof(float)) 22 | { 23 | return (T)(object)float.Parse(element.Value); 24 | } 25 | if (typeof(T) == typeof(bool)) 26 | { 27 | return (T)(object)bool.Parse(element.Value); 28 | } 29 | return (T)(object)element.Value; 30 | } 31 | set 32 | { 33 | if (value is null) 34 | { 35 | Exists = false; 36 | return; 37 | } 38 | 39 | Exists = true; // Guarantee that the node exists before setting the value 40 | var element = (XElement)Node!; 41 | element.Value = value.ToString() ?? throw new InvalidOperationException(); 42 | } 43 | } 44 | 45 | protected override XElement CreateNode() 46 | { 47 | var element = new XElement(tagName); 48 | Parent.Add(element); 49 | return element; 50 | } 51 | } -------------------------------------------------------------------------------- /Models/XmlData/NullableXmlObject.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.XmlData; 4 | 5 | public abstract class NullableXmlObject(XElement parent) 6 | { 7 | protected XElement Parent { get; } = parent; 8 | protected XObject? Node { get; set; } 9 | 10 | public bool Exists 11 | { 12 | get => Node is not null; 13 | set 14 | { 15 | if (value) 16 | { 17 | Node ??= CreateNode(); 18 | } 19 | else 20 | { 21 | if (Node is null) 22 | { 23 | return; 24 | } 25 | 26 | DeleteNode(); 27 | } 28 | } 29 | } 30 | 31 | public void Initialize(XObject element) 32 | { 33 | Node = element; 34 | } 35 | 36 | protected abstract XObject CreateNode(); 37 | 38 | private void DeleteNode() 39 | { 40 | switch (Node) 41 | { 42 | case XElement element: 43 | element.Remove(); 44 | break; 45 | case XAttribute attribute: 46 | attribute.Remove(); 47 | break; 48 | } 49 | Node = null; 50 | } 51 | } -------------------------------------------------------------------------------- /Models/XmlData/XmlAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.XmlData; 4 | 5 | public class XmlAttribute 6 | { 7 | private readonly XAttribute _attribute; 8 | 9 | public T Value 10 | { 11 | get 12 | { 13 | if (typeof(T) == typeof(int)) 14 | { 15 | return (T)(object)int.Parse(_attribute.Value); 16 | } 17 | if (typeof(T) == typeof(float)) 18 | { 19 | return (T)(object)float.Parse(_attribute.Value); 20 | } 21 | if (typeof(T) == typeof(bool)) 22 | { 23 | return (T)(object)bool.Parse(_attribute.Value); 24 | } 25 | return (T)(object)_attribute.Value; 26 | } 27 | set => _attribute.Value = value?.ToString() ?? throw new InvalidOperationException(); 28 | } 29 | 30 | public XmlAttribute(XAttribute attribute) 31 | { 32 | _attribute = attribute ?? throw new ArgumentNullException(nameof(attribute)); 33 | 34 | // Validate the type at construction 35 | if (typeof(T) != typeof(int) && typeof(T) != typeof(float) && typeof(T) != typeof(bool) && typeof(T) != typeof(string)) 36 | { 37 | throw new ArgumentException("Invalid type. Only int, float, bool, and string are supported."); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Models/XmlData/XmlCData.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.XmlData; 4 | 5 | public class XmlCData 6 | { 7 | private readonly XCData _cdata; 8 | 9 | public T Value 10 | { 11 | get 12 | { 13 | if (typeof(T) == typeof(int)) 14 | { 15 | return (T)(object)int.Parse(_cdata.Value); 16 | } 17 | 18 | if (typeof(T) == typeof(float)) 19 | { 20 | return (T)(object)float.Parse(_cdata.Value); 21 | } 22 | 23 | if (typeof(T) == typeof(bool)) 24 | { 25 | return (T)(object)bool.Parse(_cdata.Value); 26 | } 27 | 28 | return (T)(object)_cdata.Value; 29 | } 30 | set => _cdata.Value = value?.ToString() ?? throw new InvalidOperationException(); 31 | } 32 | 33 | public XmlCData(XCData cdata) 34 | { 35 | _cdata = cdata; 36 | 37 | // Validate the type at construction 38 | if (typeof(T) != typeof(int) && typeof(T) != typeof(float) && typeof(T) != typeof(bool) && 39 | typeof(T) != typeof(string)) 40 | { 41 | throw new ArgumentException("Invalid type. Only int, float, bool, and string are supported."); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Models/XmlData/XmlDate.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using LTSaveEd.ExtensionMethods; 3 | 4 | namespace LTSaveEd.Models.XmlData; 5 | 6 | public class XmlDate 7 | { 8 | private XmlAttribute Year { get; } 9 | private XmlAttribute Month { get; } 10 | private XmlAttribute Day { get; } 11 | 12 | public DateTime? Value 13 | { 14 | get 15 | { 16 | var month = Month.Value.ToUpper().ToMonthInt(); 17 | return new DateTime(Year.Value, month, Day.Value); 18 | } 19 | set 20 | { 21 | if (value == null) 22 | { 23 | return; 24 | } 25 | 26 | var dateTime = value.Value; 27 | 28 | Year.Value = dateTime.Year; 29 | Month.Value = dateTime.Month.ToMonthString(); 30 | Day.Value = dateTime.Day; 31 | } 32 | } 33 | 34 | public XmlDate(XContainer coreNode) 35 | { 36 | Year = new XmlAttribute(coreNode.GetChildsAttributeNode("yearOfBirth")); 37 | Month = new XmlAttribute(coreNode.GetChildsAttributeNode("monthOfBirth")); 38 | Day = new XmlAttribute(coreNode.GetChildsAttributeNode("dayOfBirth")); 39 | } 40 | 41 | public XmlDate(XAttribute yearNode, XAttribute monthNode, XAttribute dayNode) 42 | { 43 | Year = new XmlAttribute(yearNode); 44 | Month = new XmlAttribute(monthNode); 45 | Day = new XmlAttribute(dayNode); 46 | } 47 | } -------------------------------------------------------------------------------- /Models/XmlData/XmlElement.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Xml.Linq; 3 | 4 | namespace LTSaveEd.Models.XmlData; 5 | 6 | public class XmlElement 7 | { 8 | private readonly XElement _element; 9 | 10 | public T Value 11 | { 12 | get 13 | { 14 | if (typeof(T) == typeof(int)) 15 | { 16 | return (T)(object)int.Parse(_element.Value); 17 | } 18 | if (typeof(T) == typeof(float)) 19 | { 20 | return (T)(object)float.Parse(_element.Value); 21 | } 22 | if (typeof(T) == typeof(bool)) 23 | { 24 | return (T)(object)bool.Parse(_element.Value); 25 | } 26 | return (T)(object)_element.Value; 27 | } 28 | set => _element.Value = GetValueAsString(value); 29 | } 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | private static string GetValueAsString(T value) 33 | { 34 | return value switch 35 | { 36 | bool boolValue => boolValue.ToString().ToLower(), 37 | _ => value?.ToString() ?? throw new InvalidOperationException("Value cannot be null.") 38 | }; 39 | } 40 | 41 | public XmlElement(XElement element) 42 | { 43 | _element = element; 44 | 45 | // Validate the type at construction 46 | if (typeof(T) != typeof(int) && typeof(T) != typeof(float) && typeof(T) != typeof(bool) && typeof(T) != typeof(string)) 47 | { 48 | throw new ArgumentException("Invalid type. Only int, float, bool, and string are supported."); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Models/XmlData/XmlEnum.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LTSaveEd.Models.XmlData; 4 | 5 | public abstract class XmlEnum where TElement : XObject where TEnum : Enum 6 | { 7 | protected readonly TElement Element; 8 | 9 | public abstract TEnum Value { get; set; } 10 | 11 | protected XmlEnum(TElement element) 12 | { 13 | if (element is not XElement && element is not XCData) 14 | { 15 | throw new ArgumentException("Element must be of type XElement or XCData.", nameof(element)); 16 | } 17 | 18 | Element = element; 19 | } 20 | } -------------------------------------------------------------------------------- /Pages/Body/BodyCore.razor: -------------------------------------------------------------------------------- 1 | @page "/body/bodycore" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Models.CharacterData.BodyData.AssData 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState 10 | 11 |

BodyCore

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | @code { 56 | private Models.CharacterData.BodyData.BodyCore BodyCoreData => SaveData.CurrentCharacter.Body.BodyCore; 57 | private Leg Leg => SaveData.CurrentCharacter.Body.Ass.Leg; 58 | } -------------------------------------------------------------------------------- /Pages/Body/Breasts.razor: -------------------------------------------------------------------------------- 1 | @page "/body/breasts" 2 | 3 | @using LTSaveEd.Models 4 | 5 | @inherits BaseSaveEditorPage 6 | 7 | @inject ApplicationState ApplicationState 8 | 9 |

Breasts

10 | 11 | 12 | 13 | @code { 14 | private Models.CharacterData.BodyData.Breasts BreastsData => SaveData.CurrentCharacter.Body.Breasts; 15 | } -------------------------------------------------------------------------------- /Pages/Body/BreastsCrotch.razor: -------------------------------------------------------------------------------- 1 | @page "/body/breastscrotch" 2 | 3 | @using LTSaveEd.Models 4 | 5 | @inherits BaseSaveEditorPage 6 | 7 | @inject ApplicationState ApplicationState 8 | 9 |

BreastsCrotch

10 | 11 | 12 | 13 | @code { 14 | private Models.CharacterData.BodyData.Breasts BreastsCrotchData => SaveData.CurrentCharacter.Body.BreastsCrotch; 15 | } -------------------------------------------------------------------------------- /Pages/Extra.razor: -------------------------------------------------------------------------------- 1 | @page "/extra" 2 | 3 | @using LTSaveEd.Models 4 | 5 | @inherits BaseSaveEditorPage 6 | 7 | @inject ApplicationState ApplicationState 8 | 9 |

Extra

10 | 11 | 12 | 13 | 14 | 15 | Delete All Offsprings 17 | Reveal All Map Tiles 19 | 20 | 21 | 22 | 23 | 24 | @code { 25 | private Models.Offsprings Offsprings => SaveData.Offsprings; 26 | 27 | private void RevealMap() { 28 | var mapsNode = SaveData.GetElement("maps"); 29 | var worlds = mapsNode.Elements(); 30 | foreach (var world in worlds) 31 | { 32 | var grid = world.Element("grid")!; 33 | var cells = grid.Elements(); 34 | foreach (var cell in cells) 35 | { 36 | cell.Attribute("discovered")!.Value = "true"; 37 | cell.Attribute("travelledTo")!.Value = "true"; 38 | } 39 | } 40 | Console.WriteLine("Revealed all map tiles"); 41 | } 42 | } -------------------------------------------------------------------------------- /Pages/Family.razor: -------------------------------------------------------------------------------- 1 | @page "/family" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Components 5 | 6 | @inherits BaseSaveEditorPage 7 | 8 | @inject ApplicationState ApplicationState 9 | 10 |

Family

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @code { 34 | private Models.CharacterData.Family FamilyData => SaveData.CurrentCharacter.Family; 35 | private ValueDisplayPair[] _availableParents = []; 36 | 37 | protected override void OnInitialized() 38 | { 39 | _availableParents = [new ValueDisplayPair("Unknown", ""), ..SaveData.CharacterIds]; 40 | } 41 | 42 | private void LoadCharacterAsParent(string characterId, bool mother) 43 | { 44 | var characterData = SaveData.GetCharacterShortData(characterId); 45 | if (mother) 46 | { 47 | FamilyData.Mother.Id.Value = characterData.Id; 48 | FamilyData.Mother.Name.Value = characterData.Name; 49 | FamilyData.Mother.Femininity.Value = characterData.Femininity; 50 | FamilyData.Mother.Subspecies.Value = characterData.Subspecies; 51 | } 52 | else 53 | { 54 | FamilyData.Father.Id.Value = characterData.Id; 55 | FamilyData.Father.Name.Value = characterData.Name; 56 | FamilyData.Father.Femininity.Value = characterData.Femininity; 57 | FamilyData.Father.Subspecies.Value = characterData.Subspecies; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/home" 2 | 3 | @using LTSaveEd.Models 4 | 5 | @inherits BaseSaveEditorPage 6 | 7 | @inject ApplicationState ApplicationState 8 | @inject NavigationManager NavigationManager 9 | 10 | Home 11 | 12 |

Welcome

13 | 14 |
visitor badge
15 |
Load a save file to get started. Once you load your save file, you will need to wait for the save loaded 16 | notification. Once the save has been loaded, click the three horizontal bars in the top-left corner to access the 17 | save editor menu which shows the various components of the save that can be edited.
18 | 19 | @code 20 | { 21 | protected override void OnInitialized() 22 | { 23 | // Do not call base.OnInitialized() here, as it will cause an infinite redirect loop to the home page 24 | //base.OnInitialized(); 25 | ApplicationState.UpdateLocation(NavigationManager); 26 | StateHasChanged(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | @inject NavigationManager NavigationManager 4 | 5 |

Loading...

6 | 7 | @code { 8 | protected override void OnInitialized() 9 | { 10 | NavigationManager.NavigateTo("home", replace: true); 11 | } 12 | } -------------------------------------------------------------------------------- /Pages/Inventory/Clothing.razor: -------------------------------------------------------------------------------- 1 | @page "/inventory/clothing" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Models.CharacterData 5 | @using LTSaveEd.Models.CharacterData.InventoryData 6 | @using LTSaveEd.Components 7 | @using LTSaveEd.Models.CharacterData.InventoryData.Clothes 8 | 9 | @inherits BaseSaveEditorPage 10 | 11 | @inject ApplicationState ApplicationState 12 | @inject ISnackbar Snackbar 13 | @inject IDialogService DialogService 14 | 15 |

Clothing

16 | 17 | 18 | 19 | 20 | @for (var i = 0; i < Inventory.Clothes.Count; i++) 21 | { 22 | var localIndex = i; 23 | var clothing = Inventory.Clothes[localIndex]; 24 | 25 | 26 | 27 | 28 | 30 | 32 | Delete 34 | 35 | } 36 | Add 37 | 38 | 39 | 40 | 41 | @code { 42 | private Inventory Inventory => SaveData.CurrentCharacter.Inventory; 43 | 44 | private async Task Add() 45 | { 46 | if (Inventory.Full) 47 | { 48 | Snackbar.Add("Inventory Full", Severity.Error); 49 | return; 50 | } 51 | 52 | var options = new DialogOptions { CloseOnEscapeKey = true }; 53 | var parameters = new DialogParameters 54 | { 55 | ["Types"] = Collections.ClothingTypes, 56 | ["TypeMap"] = Collections.ClothingMap 57 | }; 58 | 59 | var dialog = await DialogService.ShowAsync>("Add Clothing", options: options, parameters: parameters); 60 | var result = await dialog.Result; 61 | if (!result!.Canceled) 62 | { 63 | Inventory.Add((ClothingData)result.Data!); 64 | StateHasChanged(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Pages/Inventory/Items.razor: -------------------------------------------------------------------------------- 1 | @page "/inventory/items" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Models.CharacterData 5 | @using LTSaveEd.Models.CharacterData.InventoryData 6 | @using LTSaveEd.Components 7 | @using LTSaveEd.Models.CharacterData.InventoryData.Items 8 | 9 | @inherits BaseSaveEditorPage 10 | 11 | @inject ApplicationState ApplicationState 12 | @inject ISnackbar Snackbar 13 | @inject IDialogService DialogService 14 | 15 |

Items

16 | 17 | 18 | 19 | 20 | @for (var i = 0; i < Inventory.Items.Count; i++) 21 | { 22 | var localIndex = i; 23 | var item = Inventory.Items[localIndex]; 24 | 25 | 26 | 27 | 28 | Delete 30 | 31 | } 32 | Add 33 | 34 | 35 | 36 | 37 | @code { 38 | private Inventory Inventory => SaveData.CurrentCharacter.Inventory; 39 | 40 | private async Task Add() 41 | { 42 | if (Inventory.Full) 43 | { 44 | Snackbar.Add("Inventory Full", Severity.Error); 45 | return; 46 | } 47 | 48 | var options = new DialogOptions { CloseOnEscapeKey = true }; 49 | var parameters = new DialogParameters 50 | { 51 | ["Types"] = Collections.ItemTypes, 52 | ["TypeMap"] = Collections.ItemsMap 53 | }; 54 | 55 | var dialog = await DialogService.ShowAsync>("Add Item", options: options, parameters: parameters); 56 | var result = await dialog.Result; 57 | if (!result!.Canceled) 58 | { 59 | Inventory.Add((ItemData)result.Data!); 60 | StateHasChanged(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Pages/Inventory/Weapons.razor: -------------------------------------------------------------------------------- 1 | @page "/inventory/weapons" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Models.CharacterData 5 | @using LTSaveEd.Models.CharacterData.InventoryData 6 | @using LTSaveEd.Components 7 | @using LTSaveEd.Models.CharacterData.InventoryData.Weapons 8 | 9 | @inherits BaseSaveEditorPage 10 | 11 | @inject ApplicationState ApplicationState 12 | @inject ISnackbar Snackbar 13 | @inject IDialogService DialogService 14 | 15 |

Weapons

16 | 17 | 18 | 19 | 20 | @for (var i = 0; i < Inventory.Weapons.Count; i++) 21 | { 22 | var localIndex = i; 23 | var weapon = Inventory.Weapons[localIndex]; 24 | 25 | 26 | 27 | 28 | 31 | Delete 33 | 34 | } 35 | Add 36 | 37 | 38 | 39 | 40 | @code { 41 | private Inventory Inventory => SaveData.CurrentCharacter.Inventory; 42 | 43 | private async void Add() 44 | { 45 | if (Inventory.Full) 46 | { 47 | Snackbar.Add("Inventory Full", Severity.Error); 48 | return; 49 | } 50 | 51 | var options = new DialogOptions { CloseOnEscapeKey = true }; 52 | var parameters = new DialogParameters 53 | { 54 | ["Types"] = Collections.WeaponTypes, 55 | ["TypeMap"] = Collections.WeaponsMap 56 | }; 57 | 58 | var dialog = await DialogService.ShowAsync>("Add Weapon", options: options, parameters: parameters); 59 | var result = await dialog.Result; 60 | if (!result!.Canceled) 61 | { 62 | Inventory.Add((WeaponData)result.Data!); 63 | StateHasChanged(); 64 | } 65 | } 66 | 67 | private static void UpdateCoreEnchantment(Weapon weapon, ValueDisplayPair newValue) 68 | { 69 | var coreEnchantment = weapon.CoreEnchantment.Value; 70 | if (coreEnchantment.StartsWith("DAMAGE_")) 71 | { 72 | weapon.CoreEnchantment.Value = $"DAMAGE_{newValue.Value}"; 73 | } 74 | else if (coreEnchantment.StartsWith("RESISTANCE_")) 75 | { 76 | weapon.CoreEnchantment.Value = $"RESISTANCE_{newValue.Value}"; 77 | } 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /Pages/ModEditor/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/mod-editor" 2 | 3 | @using System.Xml.Linq 4 | @using LTSaveEd.Models.ModEditor 5 | 6 | @inherits BaseModEditorPage 7 | 8 | Mod Editor 9 | 10 |

Welcome

11 | 12 | @*
visitor badge
*@ 13 |
Click the three horizontal bars in the top-left corner to access the 14 | mod editor menu which shows the various mods that can be created or edited.
15 | 16 | @code { 17 | protected override void OnInitialized() 18 | { 19 | base.OnInitialized(); 20 | StateHasChanged(); 21 | } 22 | 23 | protected override bool LoadModDataHandler(XDocument doc) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | protected override Mod GetMod() 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /Pages/Offsprings.razor: -------------------------------------------------------------------------------- 1 | @page "/offsprings" 2 | 3 | @using LTSaveEd.Models 4 | 5 | @inherits BaseSaveEditorPage 6 | 7 | @inject ApplicationState ApplicationState 8 | 9 |

Offsprings

10 | 11 | 12 | 13 | 14 | @for (var i = 0; i < OffspringsData.NumOffsprings; i++) 15 | { 16 | var localIndex = i; 17 | var offspring = OffspringsData.OffspringsList[localIndex]; 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 29 | Delete 31 | 32 | } 33 | 34 | 35 | 36 | 37 | @code { 38 | private Models.Offsprings OffspringsData => SaveData.Offsprings; 39 | } -------------------------------------------------------------------------------- /Pages/Relationships.razor: -------------------------------------------------------------------------------- 1 | @page "/relationships" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Components 5 | 6 | @inherits BaseSaveEditorPage 7 | 8 | @inject NavigationManager NavigationManager 9 | @inject ApplicationState ApplicationState 10 | 11 |

Relationships

12 | 13 | 14 | 15 | 16 | @for (var i = 0; i < RelationshipsData.RelationshipsData.Count; i++) 17 | { 18 | var localIndex = i; 19 | var relationShip = RelationshipsData.RelationshipsData[localIndex]; 20 | 21 | 22 | 23 | 24 | Delete 26 | 27 | } 28 | 29 | 30 | Add 31 | 32 | 33 | 34 | 35 | 36 | @code { 37 | private Models.CharacterData.Relationships RelationshipsData => SaveData.CurrentCharacter.Relationships; 38 | private ValueDisplayPair SelectedCharacter { get; set; } = null!; 39 | 40 | protected override void OnInitialized() 41 | { 42 | SelectedCharacter = SaveData.CharacterIds[0]; 43 | } 44 | 45 | private void AddRelationship() 46 | { 47 | RelationshipsData.AddRelationship(SaveData.GetCharacterName(SelectedCharacter.Value), SelectedCharacter.Value); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /Pages/Spells/Air.razor: -------------------------------------------------------------------------------- 1 | @page "/spells/air" 2 | 3 | @using LTSaveEd.Models.CharacterData.SpellData 4 | @using LTSaveEd.Models 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState 10 | 11 |

Air

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @code { 34 | private AirSpells Spells => SaveData.CurrentCharacter.Spells.Air; 35 | } -------------------------------------------------------------------------------- /Pages/Spells/Arcane.razor: -------------------------------------------------------------------------------- 1 | @page "/spells/arcane" 2 | 3 | @using LTSaveEd.Models.CharacterData.SpellData 4 | @using LTSaveEd.Models 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState 10 | 11 |

Arcane

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | @code { 46 | private ArcaneSpells Spells => SaveData.CurrentCharacter.Spells.Arcane; 47 | } -------------------------------------------------------------------------------- /Pages/Spells/Earth.razor: -------------------------------------------------------------------------------- 1 | @page "/spells/earth" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Models.CharacterData.SpellData 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState; 10 | 11 |

Earth

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @code { 34 | private EarthSpells Spells => SaveData.CurrentCharacter.Spells.Earth; 35 | } -------------------------------------------------------------------------------- /Pages/Spells/Fire.razor: -------------------------------------------------------------------------------- 1 | @page "/spells/fire" 2 | 3 | @using LTSaveEd.Models.CharacterData.SpellData 4 | @using LTSaveEd.Models 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState 10 | 11 |

Fire

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @code { 34 | private FireSpells Spells => SaveData.CurrentCharacter.Spells.Fire; 35 | } -------------------------------------------------------------------------------- /Pages/Spells/Misc.razor: -------------------------------------------------------------------------------- 1 | @page "/spells/misc" 2 | 3 | @using LTSaveEd.Models.CharacterData.SpellData 4 | @using LTSaveEd.Models 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState 10 | 11 |

Misc

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | @code { 40 | private MiscSpells Spells => SaveData.CurrentCharacter.Spells.Misc; 41 | } -------------------------------------------------------------------------------- /Pages/Spells/Water.razor: -------------------------------------------------------------------------------- 1 | @page "/spells/water" 2 | 3 | @using LTSaveEd.Models.CharacterData.SpellData 4 | @using LTSaveEd.Models 5 | @using LTSaveEd.Components 6 | 7 | @inherits BaseSaveEditorPage 8 | 9 | @inject ApplicationState ApplicationState 10 | 11 |

Water

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Soothing Waters Optional Spells: 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | @code { 50 | private WaterSpells Spells => SaveData.CurrentCharacter.Spells.Water; 51 | } -------------------------------------------------------------------------------- /Pages/World.razor: -------------------------------------------------------------------------------- 1 | @page "/world" 2 | 3 | @using LTSaveEd.Models 4 | @using LTSaveEd.Components 5 | 6 | @inherits BaseSaveEditorPage 7 | 8 | @inject ApplicationState ApplicationState 9 | 10 |

World

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | @code { 39 | private Models.World WorldData => SaveData.WorldData; 40 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using LTSaveEd; 4 | using LTSaveEd.Models; 5 | using LTSaveEd.Models.JSWrappers; 6 | using MudBlazor.Services; 7 | 8 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 9 | builder.RootComponents.Add("#app"); 10 | builder.RootComponents.Add("head::after"); 11 | 12 | builder.Services.AddScoped(_ => new HttpClient 13 | { 14 | BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) 15 | }); 16 | builder.Services.AddMudServices(); 17 | builder.Services.AddSingleton(); 18 | builder.Services.AddSingleton(); 19 | builder.Services.AddScoped(); 20 | builder.Services.AddScoped(); 21 | 22 | var host = builder.Build(); 23 | 24 | var localStorageAccessor = host.Services.GetRequiredService(); 25 | var settings = host.Services.GetRequiredService(); 26 | await settings.InitializeAsync(localStorageAccessor); 27 | 28 | await host.RunAsync(); -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:38525", 8 | "sslPort": 44389 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5139", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 27 | "applicationUrl": "https://localhost:7194;http://localhost:5139", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LTSaveEd [![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Exiua/LTSaveEd/blob/master/LICENSE) 2 | [![Release Version](https://img.shields.io/github/v/release/Exiua/LTSaveEd?include_prereleases)](https://github.com/Exiua/LTSaveEd/releases) ![GitHub all releases](https://img.shields.io/github/downloads/Exiua/LTSaveEd/total) ![Visitor Badge](https://visitor-badge.laobi.icu/badge?page_id=Exiua.LTSaveEd&format=true&query_only=true) 3 | 4 | LTSaveEd is a save editor for the game _Lilith's Throne_ built using Blazor WebAssembly (WASM) and .NET Core 9.0. 5 | 6 | ## Requirements 7 | 8 | Requires a browser that supports WASM (most modern browsers do) if you want to run the save editor. Requires .NET Core 9.0 9 | if you want to compile and run the program yourself. 10 | 11 | ## How To Use 12 | 13 | Go to [https://exiua.github.io/LTSaveEd/](https://exiua.github.io/LTSaveEd/) to use the save editor 14 | 15 | ## Features 16 | - Edit both player and NPCs 17 | - Change name and description of player/NPCs 18 | - Adjust stats and attributes (level, money, health, etc.) 19 | - Modify body configuration 20 | - Adjust Fetishes, Perks, and Spells 21 | - Add/Remove/Modify items in inventory 22 | - Modify relationships and family 23 | - Adjust global values (ingame date, stamps, etc.) 24 | - Remove all non-encountered offsprings 25 | - Reveal the map 26 | 27 | ## Bugs 28 | 29 | If you encounter a problem with the save editor, please open an issue on the GitHub page. Provide as much information as 30 | you can about the issue and how to replicate the issue. Also, please provide the save file that you are trying to edit 31 | when the issue occurred. 32 | -------------------------------------------------------------------------------- /Scripts/EnumToArray.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | with open("in.txt", "r") as f: 3 | data = f.readlines() 4 | 5 | enum_type = "ClothingType" 6 | data = [dat.split(",")[0] for dat in data] 7 | data = [f'new ValueDisplayPair<{enum_type}>("{dat}", {enum_type}.{dat})' for dat in data] 8 | data = "[\n" + ", ".join(data) + "\n]" 9 | data = [data] 10 | 11 | with open("out.txt", "w") as f: 12 | f.writelines(data) 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using MudBlazor 10 | @using LTSaveEd 11 | @using LTSaveEd.Layout -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exiua/LTSaveEd/3bc2c5b07923abd7067a63dfcd6392d0a3675463/requirements.txt -------------------------------------------------------------------------------- /wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exiua/LTSaveEd/3bc2c5b07923abd7067a63dfcd6392d0a3675463/wwwroot/favicon.png -------------------------------------------------------------------------------- /wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exiua/LTSaveEd/3bc2c5b07923abd7067a63dfcd6392d0a3675463/wwwroot/icon-192.png -------------------------------------------------------------------------------- /wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | LTSaveEd 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 | An unhandled error has occurred. 28 | Reload 29 | 🗙 30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /wwwroot/js/FileHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Save a file to the computer 3 | * @param data {Uint8Array} The Uint8Array data that makes up the file (automatically converted from a byte[] to a Uint8Array when sent from C#) 4 | * @param filename {string} The name of the file to save 5 | */ 6 | export function saveFile(data, filename) { 7 | // Create a Blob from the byte array (Uint8Array) 8 | const blob = new Blob([data], { type: 'application/xml' }); 9 | 10 | // Create an anchor element and download the file 11 | const url = URL.createObjectURL(blob); 12 | const anchor = document.createElement('a'); 13 | anchor.href = url; 14 | anchor.download = filename; 15 | document.body.appendChild(anchor); // Required for Firefox 16 | anchor.click(); 17 | document.body.removeChild(anchor); 18 | URL.revokeObjectURL(url); 19 | } -------------------------------------------------------------------------------- /wwwroot/js/LocalStorageAccessor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the value of a local storage item 3 | * @param key {string} The key of the item to get 4 | * @returns {string} The value of the item 5 | */ 6 | export function get(key) 7 | { 8 | return window.localStorage.getItem(key); 9 | } 10 | 11 | /** 12 | * Check if a local storage item exists 13 | * @param key {string} The key of the item to check 14 | * @returns {boolean} True if the item exists, false otherwise 15 | */ 16 | export function exists(key) 17 | { 18 | return window.localStorage.getItem(key) !== null; 19 | } 20 | 21 | /** 22 | * Set the value of a local storage item 23 | * @param key {string} The key of the item to set 24 | * @param value {any} The value to set (values are coerced to strings) 25 | */ 26 | export function set(key, value) 27 | { 28 | window.localStorage.setItem(key, value); 29 | } 30 | 31 | /** 32 | * Clear all local storage items 33 | */ 34 | export function clear() 35 | { 36 | window.localStorage.clear(); 37 | } 38 | 39 | /** 40 | * Remove a local storage item 41 | * @param key {string} The key of the item to remove 42 | */ 43 | export function remove(key) 44 | { 45 | window.localStorage.removeItem(key); 46 | } --------------------------------------------------------------------------------