using AcDream.Core.Spells; using AcDream.Runtime.Gameplay; namespace AcDream.Runtime.Tests.Gameplay; public sealed class RuntimeCharacterStateTests { [Fact] public void OwnsOneCoupledSpellbookAndLocalPlayerGraph() { SpellTable table = SpellTable.Create( [ new SpellMetadata( SpellId: 1u, Name: "Test", School: "Life", Family: 0u, IconId: 0u, SpellWords: "", Duration: 60f, ManaCost: 0, IsDebuff: false, IsFellowship: false, Description: "", SortKey: 0, Difficulty: 0, Flags: 0u, Generation: 1, IsFastWindup: false, IsOffensive: false, IsUntargeted: false, Speed: 0f, CasterEffect: 0u, TargetEffect: 0u, TargetMask: 0u, SpellType: 0) ]); using var state = new RuntimeCharacterState(table); state.LocalPlayer.OnAttributeUpdate( atType: 2u, ranks: 90u, start: 10u, xp: 0u); state.LocalPlayer.OnVitalUpdate( vitalId: 7u, ranks: 50u, start: 50u, xp: 0u, current: 150u); state.Spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord( SpellId: 1u, LayerId: 1u, Duration: 60f, CasterGuid: 2u, Bucket: 2u, StatModType: 0u, StatModKey: EnchantmentMath.StatKey.MaxHealth, StatModValue: 25f)); state.Spellbook.SetDesiredComponent(0x68000001u, 12u); Assert.Equal(175u, state.LocalPlayer.GetMaxApprox( AcDream.Core.Player.LocalPlayerState.VitalKind.Health)); Assert.Equal(12u, state.Spellbook.DesiredComponents[0x68000001u]); } [Fact] public void InstallsImmutableMetadataOnceOnTheCanonicalSpellbook() { using var state = new RuntimeCharacterState(); SpellTable table = SpellTable.Create(Array.Empty()); state.InstallSpellMetadata(table); state.InstallSpellMetadata(table); Assert.Same(table, state.Spellbook.Metadata); Assert.Throws( () => state.InstallSpellMetadata(SpellTable.Empty)); } [Fact] public void ResetMethodsPreserveTheOtherHalfOfTheLifetimeGroup() { using var state = new RuntimeCharacterState(); state.Spellbook.OnSpellLearned(7u); state.LocalPlayer.OnVitalUpdate(7u, 1u, 9u, 0u, 10u); state.ResetSpellbook(); Assert.False(state.Spellbook.Knows(7u)); Assert.NotNull(state.LocalPlayer.Get( AcDream.Core.Player.LocalPlayerState.VitalKind.Health)); state.Spellbook.OnSpellLearned(8u); state.ResetLocalPlayer(); Assert.True(state.Spellbook.Knows(8u)); Assert.Null(state.LocalPlayer.Get( AcDream.Core.Player.LocalPlayerState.VitalKind.Health)); } [Fact] public void IndependentRuntimeInstancesNeverShareCharacterState() { using var first = new RuntimeCharacterState(); using var second = new RuntimeCharacterState(); first.Spellbook.OnSpellLearned(7u); first.LocalPlayer.OnVitalUpdate(7u, 1u, 9u, 0u, 10u); Assert.False(second.Spellbook.Knows(7u)); Assert.Null(second.LocalPlayer.Get( AcDream.Core.Player.LocalPlayerState.VitalKind.Health)); } [Fact] public void DisposalRetriesObserverFailuresAndClearsBothOwners() { var state = new RuntimeCharacterState(); state.Spellbook.OnSpellLearned(7u); state.LocalPlayer.OnVitalUpdate(7u, 1u, 9u, 0u, 10u); bool failSpellbook = true; bool failCharacter = true; state.Spellbook.SpellbookChanged += () => { if (failSpellbook) { failSpellbook = false; throw new InvalidOperationException("spellbook"); } }; state.LocalPlayer.CharacterChanged += () => { if (failCharacter) { failCharacter = false; throw new InvalidOperationException("character"); } }; AggregateException error = Assert.Throws( state.Dispose); Assert.Equal(2, error.InnerExceptions.Count); Assert.False(state.IsDisposed); Assert.False(state.Spellbook.Knows(7u)); Assert.Null(state.LocalPlayer.Get( AcDream.Core.Player.LocalPlayerState.VitalKind.Health)); state.Dispose(); state.Dispose(); Assert.True(state.IsDisposed); } }