From fc5b486d4cfae97e48391040431ebfcafbaf6fd0 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Sun, 24 Apr 2022 17:50:22 -0700 Subject: [PATCH 1/7] Add SfxPlayer and loading of SFX files in content provider --- EOLib/misc.cs | 2 + EndlessClient/Audio/SfxPlayer.cs | 62 ++++++++++++++++++++++++ EndlessClient/Audio/WAVFileValidator.cs | 3 +- EndlessClient/Content/ContentProvider.cs | 60 +++++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 EndlessClient/Audio/SfxPlayer.cs diff --git a/EOLib/misc.cs b/EOLib/misc.cs index aaef9e8b1..3bb3a9d12 100644 --- a/EOLib/misc.cs +++ b/EOLib/misc.cs @@ -43,6 +43,8 @@ public static class Constants public const string LogFilePath = "log/debug.log"; public const string LogFileFmt = "log/{0}-debug.log"; + public const string SfxDirectory = "sfx"; + public const string FriendListFile = "config/friends.ini"; public const string IgnoreListFile = "config/ignore.ini"; diff --git a/EndlessClient/Audio/SfxPlayer.cs b/EndlessClient/Audio/SfxPlayer.cs new file mode 100644 index 000000000..a33da05e2 --- /dev/null +++ b/EndlessClient/Audio/SfxPlayer.cs @@ -0,0 +1,62 @@ +using AutomaticTypeMapper; +using EndlessClient.Content; +using Microsoft.Xna.Framework.Audio; +using System.Collections.Generic; + +namespace EndlessClient.Audio +{ + [AutoMappedType(IsSingleton = true)] + public class SfxPlayer : ISfxPlayer + { + private readonly IContentProvider _contentProvider; + private readonly Dictionary _activeSfx; + + public SfxPlayer(IContentProvider contentProvider) + { + _contentProvider = contentProvider; + _activeSfx = new Dictionary(); + } + + public void PlaySfx(SoundEffectID id) + { + _contentProvider.SFX[id].Play(); + } + + public void PlayHarpNote(int index) + { + if (index < 0 || index >= _contentProvider.HarpNotes.Count) + return; + + _contentProvider.HarpNotes[index].Play(); + } + + public void PlayGuitarNote(int index) + { + if (index < 0 || index >= _contentProvider.GuitarNotes.Count) + return; + + _contentProvider.GuitarNotes[index].Play(); + } + + public void PlayLoopingSfx(SoundEffectID id) + { + // todo: SFX + + //var res = _activeSfx.TryGetValue(id, out var sfxInstance); + //if (res && sfxInstance.State != SoundState.Stopped) + // return; + + //if (res) + // _activeSfx[id].Dispose(); + //_activeSfx[id] = _contentProvider.SFX[id].CreateInstance(); + //_activeSfx[id] + } + } + + public interface ISfxPlayer + { + void PlayHarpNote(int index); + + void PlayGuitarNote(int index); + } +} diff --git a/EndlessClient/Audio/WAVFileValidator.cs b/EndlessClient/Audio/WAVFileValidator.cs index 777e68416..28dce4884 100644 --- a/EndlessClient/Audio/WAVFileValidator.cs +++ b/EndlessClient/Audio/WAVFileValidator.cs @@ -1,5 +1,4 @@ - -using System; +using System; using System.IO; using System.Text; using EOLib; diff --git a/EndlessClient/Content/ContentProvider.cs b/EndlessClient/Content/ContentProvider.cs index 99080f5fd..4b80e7e5a 100644 --- a/EndlessClient/Content/ContentProvider.cs +++ b/EndlessClient/Content/ContentProvider.cs @@ -1,8 +1,12 @@ using AutomaticTypeMapper; +using EndlessClient.Audio; using EOLib; +using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using System.Collections.Generic; +using System.IO; +using System.Linq; namespace EndlessClient.Content { @@ -12,6 +16,12 @@ public interface IContentProvider IReadOnlyDictionary Fonts { get; } + IReadOnlyDictionary SFX { get; } + + IReadOnlyList HarpNotes { get; } + + IReadOnlyList GuitarNotes { get; } + void SetContentManager(ContentManager content); void Load(); @@ -22,6 +32,9 @@ public class ContentProvider : IContentProvider { private readonly Dictionary _textures; private readonly Dictionary _fonts; + private readonly Dictionary _sfx; + private readonly List _harpNotes; + private readonly List _guitarNotes; private ContentManager _content; @@ -46,10 +59,19 @@ public class ContentProvider : IContentProvider public IReadOnlyDictionary Fonts => _fonts; + public IReadOnlyDictionary SFX => _sfx; + + public IReadOnlyList HarpNotes => _harpNotes; + + public IReadOnlyList GuitarNotes => _guitarNotes; + public ContentProvider() { _textures = new Dictionary(); _fonts = new Dictionary(); + _sfx = new Dictionary(); + _harpNotes = new List(); + _guitarNotes = new List(); } public void SetContentManager(ContentManager content) @@ -61,6 +83,9 @@ public void Load() { RefreshTextures(); RefreshFonts(); + LoadSFX(); + LoadHarp(); + LoadGuitar(); } private void RefreshTextures() @@ -91,5 +116,40 @@ private void RefreshFonts() _fonts[Constants.FontSize08] = _content.Load(Constants.FontSize08); _fonts[Constants.FontSize09] = _content.Load(Constants.FontSize09); } + + private void LoadSFX() + { + var id = (SoundEffectID)0; + foreach (var sfxFile in GetSoundEffects("sfx*.wav")) + _sfx[id++] = sfxFile; + if (_sfx.Count != 81) + throw new FileNotFoundException("Unexpected number of SFX"); + } + + private void LoadHarp() + { + _harpNotes.AddRange(GetSoundEffects("har*.wav")); + if (_harpNotes.Count != 36) + throw new FileNotFoundException("Unexpected number of harp SFX"); + } + + private void LoadGuitar() + { + _guitarNotes.AddRange(GetSoundEffects("gui*.wav")); + if (_guitarNotes.Count != 36) + throw new FileNotFoundException("Unexpected number of guitar SFX"); + } + + private static IEnumerable GetSoundEffects(string filter) + { + var sfxFiles = Directory.GetFiles(Constants.SfxDirectory, filter).ToList(); + sfxFiles.Sort(); + + foreach (var file in sfxFiles) + { + using var wavStream = WAVFileValidator.GetStreamWithCorrectLengthHeader(file); + yield return SoundEffect.FromStream(wavStream); + } + } } } From 3b8b042d68dfd268fcf10a6ea3e2289da41a14d0 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Mon, 25 Apr 2022 16:34:08 -0700 Subject: [PATCH 2/7] Show bard dialog when using the bard spell --- .../Spells/SpellCastValidationActions.cs | 11 +++ EOLib/misc.cs | 5 +- EndlessClient/Controllers/BardController.cs | 17 ++++ .../Controllers/FunctionKeyController.cs | 10 ++- .../Dialogs/Actions/InGameDialogActions.cs | 22 ++++- .../Dialogs/ActiveDialogRepository.cs | 10 ++- EndlessClient/Dialogs/BardDialog.cs | 82 +++++++++++++++++++ .../Dialogs/Factories/BardDialogFactory.cs | 36 ++++++++ 8 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 EndlessClient/Controllers/BardController.cs create mode 100644 EndlessClient/Dialogs/BardDialog.cs create mode 100644 EndlessClient/Dialogs/Factories/BardDialogFactory.cs diff --git a/EOLib/Domain/Spells/SpellCastValidationActions.cs b/EOLib/Domain/Spells/SpellCastValidationActions.cs index 329b91cdb..8e62212a6 100644 --- a/EOLib/Domain/Spells/SpellCastValidationActions.cs +++ b/EOLib/Domain/Spells/SpellCastValidationActions.cs @@ -4,6 +4,8 @@ using EOLib.Domain.NPC; using EOLib.IO; using EOLib.IO.Repositories; +using Optional.Collections; +using System.Linq; namespace EOLib.Domain.Spells { @@ -72,6 +74,13 @@ public SpellCastValidationResult ValidateSpellCast(int spellId, ISpellTargetable return SpellCastValidationResult.Ok; } + + public bool ValidateBard() + { + var weapon = _characterProvider.MainCharacter.RenderProperties.WeaponGraphic; + return _pubFileProvider.EIFFile.SingleOrNone(x => x.DollGraphic == weapon && x.Type == ItemType.Weapon) + .Match(some => Constants.InstrumentIDs.Any(x => x == some.ID), () => false); + } } public interface ISpellCastValidationActions @@ -79,5 +88,7 @@ public interface ISpellCastValidationActions SpellCastValidationResult ValidateSpellCast(int spellId); SpellCastValidationResult ValidateSpellCast(int spellId, ISpellTargetable spellTarget); + + bool ValidateBard(); } } diff --git a/EOLib/misc.cs b/EOLib/misc.cs index 3bb3a9d12..a979a6a25 100644 --- a/EOLib/misc.cs +++ b/EOLib/misc.cs @@ -53,7 +53,10 @@ public static class Constants //Should be easily customizable between different clients (based on graphics) //not a config option because this shouldn't be exposed at the user level - public static readonly int[] TrapSpikeGFXObjectIDs = {449, 450, 451, 452}; + public static readonly int[] TrapSpikeGFXObjectIDs = { 449, 450, 451, 452 }; + + // Item IDs of instruments (there is no pub flag for this) + public static readonly int[] InstrumentIDs = { 349, 350 }; public const string FontSize07 = @"Fonts/InGame_Main_07"; public const string FontSize08 = @"Fonts/InGame_Main_08"; diff --git a/EndlessClient/Controllers/BardController.cs b/EndlessClient/Controllers/BardController.cs new file mode 100644 index 000000000..8f1b47583 --- /dev/null +++ b/EndlessClient/Controllers/BardController.cs @@ -0,0 +1,17 @@ +using AutomaticTypeMapper; + +namespace EndlessClient.Controllers +{ + [AutoMappedType] + public class BardController : IBardController + { + public void PlayInstrumentNote(int noteIndex) + { + } + } + + public interface IBardController + { + void PlayInstrumentNote(int noteIndex); + } +} diff --git a/EndlessClient/Controllers/FunctionKeyController.cs b/EndlessClient/Controllers/FunctionKeyController.cs index ec66f9f24..d31f59ed1 100644 --- a/EndlessClient/Controllers/FunctionKeyController.cs +++ b/EndlessClient/Controllers/FunctionKeyController.cs @@ -1,4 +1,5 @@ using AutomaticTypeMapper; +using EndlessClient.Dialogs.Actions; using EndlessClient.HUD; using EndlessClient.HUD.Panels; using EndlessClient.HUD.Spells; @@ -21,6 +22,7 @@ public class FunctionKeyController : IFunctionKeyController private readonly ISpellSelectActions _spellSelectActions; private readonly ICharacterAnimationActions _characterAnimationActions; private readonly ISpellCastValidationActions _spellCastValidationActions; + private readonly IInGameDialogActions _inGameDialogActions; private readonly IStatusLabelSetter _statusLabelSetter; private readonly ICharacterProvider _characterProvider; private readonly IESFFileProvider _esfFileProvider; @@ -31,6 +33,7 @@ public FunctionKeyController(IMapActions mapActions, ISpellSelectActions spellSelectActions, ICharacterAnimationActions characterAnimationActions, ISpellCastValidationActions spellCastValidationActions, + IInGameDialogActions inGameDialogActions, IStatusLabelSetter statusLabelSetter, ICharacterProvider characterProvider, IESFFileProvider esfFileProvider, @@ -41,6 +44,7 @@ public FunctionKeyController(IMapActions mapActions, _spellSelectActions = spellSelectActions; _characterAnimationActions = characterAnimationActions; _spellCastValidationActions = spellCastValidationActions; + _inGameDialogActions = inGameDialogActions; _statusLabelSetter = statusLabelSetter; _characterProvider = characterProvider; _esfFileProvider = esfFileProvider; @@ -56,7 +60,11 @@ public bool SelectSpell(int index, bool isAlternate) _spellSlotDataProvider.SelectedSpellInfo.MatchSome(x => { var spellData = _esfFileProvider.ESFFile[x.ID]; - if (spellData.Target == SpellTarget.Self || spellData.Target == SpellTarget.Group) + if (spellData.Type == SpellType.Bard && _spellCastValidationActions.ValidateBard()) + { + _inGameDialogActions.ShowBardDialog(); + } + else if (spellData.Target == SpellTarget.Self || spellData.Target == SpellTarget.Group) { var castResult = _spellCastValidationActions.ValidateSpellCast(x.ID); diff --git a/EndlessClient/Dialogs/Actions/InGameDialogActions.cs b/EndlessClient/Dialogs/Actions/InGameDialogActions.cs index 62d140419..e3367c6ec 100644 --- a/EndlessClient/Dialogs/Actions/InGameDialogActions.cs +++ b/EndlessClient/Dialogs/Actions/InGameDialogActions.cs @@ -23,6 +23,7 @@ public class InGameDialogActions : IInGameDialogActions private readonly ILockerDialogFactory _lockerDialogFactory; private readonly IBankAccountDialogFactory _bankAccountDialogFactory; private readonly ISkillmasterDialogFactory _skillmasterDialogFactory; + private readonly IBardDialogFactory _bardDialogFactory; private readonly IShopDialogFactory _shopDialogFactory; private readonly IQuestDialogFactory _questDialogFactory; @@ -39,7 +40,8 @@ public InGameDialogActions(IFriendIgnoreListDialogFactory friendIgnoreListDialog IChestDialogFactory chestDialogFactory, ILockerDialogFactory lockerDialogFactory, IBankAccountDialogFactory bankAccountDialogFactory, - ISkillmasterDialogFactory skillmasterDialogFactory) + ISkillmasterDialogFactory skillmasterDialogFactory, + IBardDialogFactory bardDialogFactory) { _friendIgnoreListDialogFactory = friendIgnoreListDialogFactory; _paperdollDialogFactory = paperdollDialogFactory; @@ -53,6 +55,7 @@ public InGameDialogActions(IFriendIgnoreListDialogFactory friendIgnoreListDialog _lockerDialogFactory = lockerDialogFactory; _bankAccountDialogFactory = bankAccountDialogFactory; _skillmasterDialogFactory = skillmasterDialogFactory; + _bardDialogFactory = bardDialogFactory; _shopDialogFactory = shopDialogFactory; _questDialogFactory = questDialogFactory; } @@ -197,6 +200,21 @@ public void ShowSkillmasterDialog() dlg.Show(); } + + public void ShowBardDialog() + { + _activeDialogRepository.BardDialog.MatchNone(() => + { + var dlg = _bardDialogFactory.Create(); + dlg.DialogClosed += (_, _) => + { + _activeDialogRepository.BardDialog = Option.None(); + }; + _activeDialogRepository.BardDialog = Option.Some(dlg); + + dlg.Show(); + }); + } } public interface IInGameDialogActions @@ -222,5 +240,7 @@ public interface IInGameDialogActions void ShowBankAccountDialog(); void ShowSkillmasterDialog(); + + void ShowBardDialog(); } } diff --git a/EndlessClient/Dialogs/ActiveDialogRepository.cs b/EndlessClient/Dialogs/ActiveDialogRepository.cs index 4cebfbb1b..babf4ea77 100644 --- a/EndlessClient/Dialogs/ActiveDialogRepository.cs +++ b/EndlessClient/Dialogs/ActiveDialogRepository.cs @@ -29,6 +29,8 @@ public interface IActiveDialogProvider : IDisposable Option SkillmasterDialog { get; } + Option BardDialog { get; } + IReadOnlyList> ActiveDialogs { get; } } @@ -52,7 +54,9 @@ public interface IActiveDialogRepository : IDisposable Option BankAccountDialog { get; set; } - Option SkillmasterDialog { get; set; } + Option SkillmasterDialog { get; set; } + + Option BardDialog { get; set; } IReadOnlyList> ActiveDialogs { get; } } @@ -80,6 +84,8 @@ public class ActiveDialogRepository : IActiveDialogRepository, IActiveDialogProv public Option SkillmasterDialog { get; set; } + public Option BardDialog { get; set; } + IReadOnlyList> ActiveDialogs { get @@ -96,6 +102,7 @@ IReadOnlyList> ActiveDialogs LockerDialog.Map(d => (IXNADialog)d), BankAccountDialog.Map(d => (IXNADialog)d), SkillmasterDialog.Map(d => (IXNADialog)d), + BardDialog.Map(d => (IXNADialog)d), }.ToList(); } } @@ -119,6 +126,7 @@ public void Dispose() LockerDialog = Option.None(); BankAccountDialog = Option.None(); SkillmasterDialog = Option.None(); + BardDialog = Option.None(); } } } diff --git a/EndlessClient/Dialogs/BardDialog.cs b/EndlessClient/Dialogs/BardDialog.cs new file mode 100644 index 000000000..7c8fd9044 --- /dev/null +++ b/EndlessClient/Dialogs/BardDialog.cs @@ -0,0 +1,82 @@ +using EndlessClient.Controllers; +using EndlessClient.Dialogs.Services; +using EOLib.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Optional; +using System; +using XNAControls; + +namespace EndlessClient.Dialogs +{ + public class BardDialog : BaseEODialog + { + private readonly INativeGraphicsManager _nativeGraphicsManager; + private readonly IBardController _bardController; + private readonly Texture2D _noteHighlight; + private readonly Rectangle _noteRectangleArea; + + private Vector2 _highlightDrawPosition; + private Option _highlightSource; + + public BardDialog(INativeGraphicsManager nativeGraphicsManager, + IBardController bardController, + IEODialogButtonService dialogButtonService) + : base(isInGame: true) + { + _nativeGraphicsManager = nativeGraphicsManager; + _bardController = bardController; + BackgroundTexture = _nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 65); + _noteHighlight = _nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 66); + _noteRectangleArea = new Rectangle(15, 15, 240, 60); + + var cancel = new XNAButton(dialogButtonService.SmallButtonSheet, new Vector2(92, 83), + dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Cancel), + dialogButtonService.GetSmallDialogButtonOverSource(SmallButton.Cancel)); + cancel.Initialize(); + cancel.SetParentControl(this); + cancel.OnClick += (_, _) => Close(XNADialogResult.Cancel); + + CenterInGameView(); + } + + protected override void OnUpdateControl(GameTime gameTime) + { + var relativeMousePosition = CurrentMouseState.Position.ToVector2() - DrawPositionWithParentOffset; + if (_noteRectangleArea.Contains(relativeMousePosition)) + { + var relativePosition = relativeMousePosition - _noteRectangleArea.Location.ToVector2(); + var highlightDrawPosition = new Vector2( + relativePosition.X - (relativePosition.X % 20), + relativePosition.Y - (relativePosition.Y % 20)); + _highlightDrawPosition = highlightDrawPosition + DrawPositionWithParentOffset + _noteRectangleArea.Location.ToVector2(); + _highlightSource = Option.Some(new Rectangle(highlightDrawPosition.ToPoint(), new Point(20, 20))); + + if (PreviousMouseState.LeftButton == ButtonState.Pressed && CurrentMouseState.LeftButton == ButtonState.Released) + { + var noteIndex = (int)Math.Floor(highlightDrawPosition.X / 20 + (12 * (highlightDrawPosition.Y / 20))); + _bardController.PlayInstrumentNote(noteIndex); + } + } + else + { + _highlightSource = Option.None(); + } + + base.OnUpdateControl(gameTime); + } + + protected override void OnDrawControl(GameTime gameTime) + { + base.OnDrawControl(gameTime); + + _highlightSource.MatchSome(src => + { + _spriteBatch.Begin(); + _spriteBatch.Draw(_noteHighlight, _highlightDrawPosition, src, Color.White); + _spriteBatch.End(); + }); + } + } +} diff --git a/EndlessClient/Dialogs/Factories/BardDialogFactory.cs b/EndlessClient/Dialogs/Factories/BardDialogFactory.cs new file mode 100644 index 000000000..64e81da46 --- /dev/null +++ b/EndlessClient/Dialogs/Factories/BardDialogFactory.cs @@ -0,0 +1,36 @@ +using AutomaticTypeMapper; +using EndlessClient.Controllers; +using EndlessClient.Dialogs.Services; +using EOLib.Graphics; + +namespace EndlessClient.Dialogs.Factories +{ + [AutoMappedType] + public class BardDialogFactory : IBardDialogFactory + { + private readonly INativeGraphicsManager _nativeGraphicsManager; + private readonly IBardController _bardController; + private readonly IEODialogButtonService _dialogButtonService; + + public BardDialogFactory(INativeGraphicsManager nativeGraphicsManager, + IBardController bardController, + IEODialogButtonService dialogButtonService) + { + _nativeGraphicsManager = nativeGraphicsManager; + _bardController = bardController; + _dialogButtonService = dialogButtonService; + } + + public BardDialog Create() + { + return new BardDialog(_nativeGraphicsManager, + _bardController, + _dialogButtonService); + } + } + + public interface IBardDialogFactory + { + BardDialog Create(); + } +} From bea0e27189c264969729d1fd1a2d3d3237f77698 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 26 Apr 2022 17:41:39 -0700 Subject: [PATCH 3/7] Update instrument attacks to render as ranged weapons instead of melee weapons --- BatchMap/BatchMap.csproj | 1 + BatchMap/Program.cs | 4 +++- EOLib.IO/Actions/IPubFileLoadActions.cs | 8 +++++--- EOLib.IO/Actions/PubFileLoadActions.cs | 21 +++++++++++++++++---- EOLib.IO/Extensions/EIFFileExtensions.cs | 8 ++------ EOLib/misc.cs | 2 ++ EndlessClient/GameExecution/EndlessGame.cs | 3 ++- 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/BatchMap/BatchMap.csproj b/BatchMap/BatchMap.csproj index c6f2f6122..8cd3c35ca 100644 --- a/BatchMap/BatchMap.csproj +++ b/BatchMap/BatchMap.csproj @@ -13,5 +13,6 @@ + \ No newline at end of file diff --git a/BatchMap/Program.cs b/BatchMap/Program.cs index 56c3ee086..b2db35c26 100644 --- a/BatchMap/Program.cs +++ b/BatchMap/Program.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.Linq; using AutomaticTypeMapper; +using EOLib; using EOLib.IO.Actions; using EOLib.IO.Map; using EOLib.IO.Repositories; @@ -101,7 +103,7 @@ private static void Main(string[] args) { var actions = _typeRegistry.Resolve(); - actions.LoadItemFileByName(Path.Combine(pubFilePath, "dat001.eif")); + actions.LoadItemFileByName(Path.Combine(pubFilePath, "dat001.eif"), rangedWeaponIds: Constants.RangedWeaponIDs.Concat(Constants.InstrumentIDs)); actions.LoadNPCFileByName(Path.Combine(pubFilePath, "dtn001.enf")); } catch diff --git a/EOLib.IO/Actions/IPubFileLoadActions.cs b/EOLib.IO/Actions/IPubFileLoadActions.cs index 9ea8d1505..15af14a08 100644 --- a/EOLib.IO/Actions/IPubFileLoadActions.cs +++ b/EOLib.IO/Actions/IPubFileLoadActions.cs @@ -1,10 +1,12 @@ -namespace EOLib.IO.Actions +using System.Collections.Generic; + +namespace EOLib.IO.Actions { public interface IPubFileLoadActions { - void LoadItemFile(); + void LoadItemFile(IEnumerable rangedWeaponIds); - void LoadItemFileByName(string fileName); + void LoadItemFileByName(string fileName, IEnumerable rangedWeaponIds); void LoadNPCFile(); diff --git a/EOLib.IO/Actions/PubFileLoadActions.cs b/EOLib.IO/Actions/PubFileLoadActions.cs index de494833a..1bb934731 100644 --- a/EOLib.IO/Actions/PubFileLoadActions.cs +++ b/EOLib.IO/Actions/PubFileLoadActions.cs @@ -2,6 +2,8 @@ using EOLib.IO.Pub; using EOLib.IO.Repositories; using EOLib.IO.Services; +using System.Collections.Generic; +using System.Linq; namespace EOLib.IO.Actions { @@ -27,16 +29,16 @@ public PubFileLoadActions(IPubFileRepository pubFileRepository, _classFileLoadService = classFileLoadService; } - public void LoadItemFile() + public void LoadItemFile(IEnumerable rangedWeaponIds) { var itemFile = _itemFileLoadService.LoadPubFromDefaultFile(); - _pubFileRepository.EIFFile = itemFile; + _pubFileRepository.EIFFile = OverrideRangedWeapons(itemFile, rangedWeaponIds); } - public void LoadItemFileByName(string fileName) + public void LoadItemFileByName(string fileName, IEnumerable rangedWeaponIds) { var itemFile = _itemFileLoadService.LoadPubFromExplicitFile(fileName); - _pubFileRepository.EIFFile = itemFile; + _pubFileRepository.EIFFile = OverrideRangedWeapons(itemFile, rangedWeaponIds); } public void LoadNPCFile() @@ -74,5 +76,16 @@ public void LoadClassFileByName(string fileName) var classFile = _classFileLoadService.LoadPubFromExplicitFile(fileName); _pubFileRepository.ECFFile = classFile; } + + private static IPubFile OverrideRangedWeapons(IPubFile inputFile, IEnumerable rangedWeaponIds) + { + var rangedItemOverrides = inputFile.Where(x => x.Type == ItemType.Weapon && rangedWeaponIds.Contains(x.ID)).ToList(); + + var retFile = inputFile; + foreach (var item in rangedItemOverrides) + retFile = retFile.WithUpdatedRecord((EIFRecord)item.WithProperty(PubRecordProperty.ItemSubType, (int)ItemSubType.Ranged)); + + return retFile; + } } } \ No newline at end of file diff --git a/EOLib.IO/Extensions/EIFFileExtensions.cs b/EOLib.IO/Extensions/EIFFileExtensions.cs index ce6221bbd..0699c965f 100644 --- a/EOLib.IO/Extensions/EIFFileExtensions.cs +++ b/EOLib.IO/Extensions/EIFFileExtensions.cs @@ -20,12 +20,8 @@ public static bool IsShieldOnBack(this IPubFile itemFile, short graph public static bool IsRangedWeapon(this IPubFile itemFile, short graphic) { - if (itemFile == null) - return false; - - var weaponInfo = itemFile.FirstOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == graphic); - - return weaponInfo != null && (weaponInfo.Name == "Gun" || weaponInfo.SubType == ItemSubType.Ranged); + var weaponInfo = itemFile?.FirstOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == graphic); + return weaponInfo?.SubType == ItemSubType.Ranged; } } } diff --git a/EOLib/misc.cs b/EOLib/misc.cs index a979a6a25..9aca718bb 100644 --- a/EOLib/misc.cs +++ b/EOLib/misc.cs @@ -57,6 +57,8 @@ public static class Constants // Item IDs of instruments (there is no pub flag for this) public static readonly int[] InstrumentIDs = { 349, 350 }; + // Item IDs of ranged weapons (overrides pub value) + public static readonly int[] RangedWeaponIDs = { 365 }; public const string FontSize07 = @"Fonts/InGame_Main_07"; public const string FontSize08 = @"Fonts/InGame_Main_08"; diff --git a/EndlessClient/GameExecution/EndlessGame.cs b/EndlessClient/GameExecution/EndlessGame.cs index 866fbd304..190b75f4c 100644 --- a/EndlessClient/GameExecution/EndlessGame.cs +++ b/EndlessClient/GameExecution/EndlessGame.cs @@ -10,6 +10,7 @@ using EndlessClient.Rendering.Chat; using EndlessClient.Test; using EndlessClient.UIControls; +using EOLib; using EOLib.Domain.Character; using EOLib.Graphics; using EOLib.IO; @@ -159,7 +160,7 @@ private void AttemptToLoadPubFiles() try { - _pubFileLoadActions.LoadItemFile(); + _pubFileLoadActions.LoadItemFile(rangedWeaponIds: Constants.RangedWeaponIDs.Concat(Constants.InstrumentIDs)); } catch (IOException ioe) { From bfb045fac7aed771e29a103b5e789d1649879cc0 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 27 Apr 2022 07:35:01 -0700 Subject: [PATCH 4/7] Rate limit bard dialog input based on update tick --- EndlessClient/Dialogs/BardDialog.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/EndlessClient/Dialogs/BardDialog.cs b/EndlessClient/Dialogs/BardDialog.cs index 7c8fd9044..2aa1d2f0b 100644 --- a/EndlessClient/Dialogs/BardDialog.cs +++ b/EndlessClient/Dialogs/BardDialog.cs @@ -20,6 +20,8 @@ public class BardDialog : BaseEODialog private Vector2 _highlightDrawPosition; private Option _highlightSource; + private ulong _currentTick; + public BardDialog(INativeGraphicsManager nativeGraphicsManager, IBardController bardController, IEODialogButtonService dialogButtonService) @@ -43,6 +45,8 @@ public BardDialog(INativeGraphicsManager nativeGraphicsManager, protected override void OnUpdateControl(GameTime gameTime) { + _currentTick++; + var relativeMousePosition = CurrentMouseState.Position.ToVector2() - DrawPositionWithParentOffset; if (_noteRectangleArea.Contains(relativeMousePosition)) { @@ -53,10 +57,11 @@ protected override void OnUpdateControl(GameTime gameTime) _highlightDrawPosition = highlightDrawPosition + DrawPositionWithParentOffset + _noteRectangleArea.Location.ToVector2(); _highlightSource = Option.Some(new Rectangle(highlightDrawPosition.ToPoint(), new Point(20, 20))); - if (PreviousMouseState.LeftButton == ButtonState.Pressed && CurrentMouseState.LeftButton == ButtonState.Released) + if (PreviousMouseState.LeftButton == ButtonState.Pressed && CurrentMouseState.LeftButton == ButtonState.Released && _currentTick > 8) { var noteIndex = (int)Math.Floor(highlightDrawPosition.X / 20 + (12 * (highlightDrawPosition.Y / 20))); _bardController.PlayInstrumentNote(noteIndex); + _currentTick = 0; } } else From aafaa13e7b80cb10317b99df3df3bf0339755974 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 27 Apr 2022 07:41:32 -0700 Subject: [PATCH 5/7] Add support for bard music. Player can click to play harp/guitar notes, and notes played by other players will be played. --- EOBot/Program.cs | 2 +- .../Character/CharacterRenderProperties.cs | 6 +- EOLib/Domain/Character/Emote.cs | 3 +- EOLib/Domain/Jukebox/JukeboxActions.cs | 51 ++++++++++++++ .../IOtherCharacterAnimationNotifier.cs | 4 +- .../Jukebox/JukeboxMessageHandler.cs | 61 +++++++++++++++++ EndlessClient/Controllers/BardController.cs | 26 +++++++ EndlessClient/Controllers/NumPadController.cs | 28 +++----- .../Character/CharacterAnimationActions.cs | 67 +++++++++++++++---- .../Rendering/Character/CharacterAnimator.cs | 26 +++++-- .../CharacterProperties/EmoteRenderer.cs | 3 +- .../CharacterProperties/FaceRenderer.cs | 3 +- .../Rendering/Sprites/EmoteSpriteType.cs | 3 +- 13 files changed, 237 insertions(+), 46 deletions(-) create mode 100644 EOLib/Domain/Jukebox/JukeboxActions.cs create mode 100644 EOLib/PacketHandlers/Jukebox/JukeboxMessageHandler.cs diff --git a/EOBot/Program.cs b/EOBot/Program.cs index 6dc93d8a8..778a0c7cb 100644 --- a/EOBot/Program.cs +++ b/EOBot/Program.cs @@ -158,7 +158,7 @@ public void NotifySelfSpellCast(short playerId, short spellId, int spellHp, byte public void NotifyStartSpellCast(short playerId, short spellId) { } public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerID, short spellId, int recoveredHP, byte targetPercentHealth) { } - public void StartOtherCharacterAttackAnimation(int characterID) { } + public void StartOtherCharacterAttackAnimation(int characterID, int noteIndex) { } public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, byte destinationY, EODirection direction) { } } diff --git a/EOLib/Domain/Character/CharacterRenderProperties.cs b/EOLib/Domain/Character/CharacterRenderProperties.cs index 21ec5279b..e78481884 100644 --- a/EOLib/Domain/Character/CharacterRenderProperties.cs +++ b/EOLib/Domain/Character/CharacterRenderProperties.cs @@ -174,7 +174,11 @@ public ICharacterRenderProperties WithNextEmoteFrame() { var props = MakeCopy(this); props.EmoteFrame = (props.EmoteFrame + 1) % MAX_NUMBER_OF_EMOTE_FRAMES; - props.CurrentAction = props.EmoteFrame == 0 ? CharacterActionState.Standing : CharacterActionState.Emote; + props.CurrentAction = props.EmoteFrame == 0 + ? CharacterActionState.Standing + : props.CurrentAction == CharacterActionState.Attacking // when using an instrument keep the current state as "Attacking" + ? CharacterActionState.Attacking + : CharacterActionState.Emote; return props; } diff --git a/EOLib/Domain/Character/Emote.cs b/EOLib/Domain/Character/Emote.cs index 955c4787e..2dfe99597 100644 --- a/EOLib/Domain/Character/Emote.cs +++ b/EOLib/Domain/Character/Emote.cs @@ -21,6 +21,7 @@ public enum Emote /// /// 0 key /// - Playful = 14 + Playful = 14, + MusicNotes = 15, } } diff --git a/EOLib/Domain/Jukebox/JukeboxActions.cs b/EOLib/Domain/Jukebox/JukeboxActions.cs new file mode 100644 index 000000000..04e8ef6af --- /dev/null +++ b/EOLib/Domain/Jukebox/JukeboxActions.cs @@ -0,0 +1,51 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.IO; +using EOLib.IO.Repositories; +using EOLib.Net; +using EOLib.Net.Communication; +using Optional.Collections; +using System; + +namespace EOLib.Domain.Jukebox +{ + [AutoMappedType] + public class JukeboxActions : IJukeboxActions + { + private readonly IPacketSendService _packetSendService; + private readonly ICharacterProvider _characterProvider; + private readonly IEIFFileProvider _eifFileProvider; + + public JukeboxActions(IPacketSendService packetSendService, + ICharacterProvider characterProvider, + IEIFFileProvider eifFileProvider) + { + _packetSendService = packetSendService; + _characterProvider = characterProvider; + _eifFileProvider = eifFileProvider; + } + + public void PlayNote(int noteIndex) + { + if (noteIndex < 0 || noteIndex >= 36) + throw new ArgumentOutOfRangeException(nameof(noteIndex)); + + var weapon = _characterProvider.MainCharacter.RenderProperties.WeaponGraphic; + _eifFileProvider.EIFFile.SingleOrNone(x => x.Type == ItemType.Weapon && x.DollGraphic == weapon) + .MatchSome(rec => + { + var packet = new PacketBuilder(PacketFamily.JukeBox, PacketAction.Use) + .AddChar((byte)rec.DollGraphic) // todo: determine what GameServer expects; eoserv sends DollGraphic as a response in Character::PlayBard + .AddChar((byte)(noteIndex + 1)) + .Build(); + + _packetSendService.SendPacket(packet); + }); + } + } + + public interface IJukeboxActions + { + void PlayNote(int noteIndex); + } +} diff --git a/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs b/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs index a096be63f..827343f5e 100644 --- a/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs +++ b/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs @@ -6,7 +6,7 @@ public interface IOtherCharacterAnimationNotifier { void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, byte destinationY, EODirection direction); - void StartOtherCharacterAttackAnimation(int characterID); + void StartOtherCharacterAttackAnimation(int characterID, int noteIndex = -1); void NotifyStartSpellCast(short playerId, short spellId); @@ -20,7 +20,7 @@ public class NoOpOtherCharacterAnimationNotifier : IOtherCharacterAnimationNotif { public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, byte destinationY, EODirection direction) { } - public void StartOtherCharacterAttackAnimation(int characterID) { } + public void StartOtherCharacterAttackAnimation(int characterID, int noteIndex = -1) { } public void NotifyStartSpellCast(short playerId, short spellId) { } diff --git a/EOLib/PacketHandlers/Jukebox/JukeboxMessageHandler.cs b/EOLib/PacketHandlers/Jukebox/JukeboxMessageHandler.cs new file mode 100644 index 000000000..c12e8d921 --- /dev/null +++ b/EOLib/PacketHandlers/Jukebox/JukeboxMessageHandler.cs @@ -0,0 +1,61 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Domain.Map; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Jukebox +{ + [AutoMappedType] + public class JukeboxMessageHandler : InGameOnlyPacketHandler + { + private readonly ICharacterRepository _characterRepository; + private readonly ICurrentMapStateRepository _currentMapStateRepository; + private readonly IEnumerable _mainCharacterEventNotifiers; + private readonly IEnumerable _otherCharacterAnimationNotifiers; + + public override PacketFamily Family => PacketFamily.JukeBox; + + public override PacketAction Action => PacketAction.Message; + + public JukeboxMessageHandler(IPlayerInfoProvider playerInfoProvider, + ICurrentMapStateRepository currentMapStateRepository, + IEnumerable otherCharacterAnimationNotifiers) + : base(playerInfoProvider) + { + _currentMapStateRepository = currentMapStateRepository; + _otherCharacterAnimationNotifiers = otherCharacterAnimationNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var playerId = packet.ReadShort(); + var direction = (EODirection)packet.ReadChar(); + var instrument = packet.ReadChar(); + var note = packet.ReadChar(); + + if (_currentMapStateRepository.Characters.ContainsKey(playerId)) + { + var c = _currentMapStateRepository.Characters[playerId]; + + if (c.RenderProperties.WeaponGraphic == instrument) + { + c = c.WithRenderProperties(c.RenderProperties.WithDirection(direction)); + _currentMapStateRepository.Characters[playerId] = c; + + foreach (var notifier in _otherCharacterAnimationNotifiers) + notifier.StartOtherCharacterAttackAnimation(playerId, note - 1); + } + } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(playerId); + } + + return true; + } + } +} diff --git a/EndlessClient/Controllers/BardController.cs b/EndlessClient/Controllers/BardController.cs index 8f1b47583..dd84763a9 100644 --- a/EndlessClient/Controllers/BardController.cs +++ b/EndlessClient/Controllers/BardController.cs @@ -1,12 +1,38 @@ using AutomaticTypeMapper; +using EndlessClient.Rendering.Character; +using EOLib.Domain.Character; +using EOLib.Domain.Extensions; +using EOLib.Domain.Jukebox; namespace EndlessClient.Controllers { [AutoMappedType] public class BardController : IBardController { + private readonly ICharacterAnimationActions _characterAnimationActions; + private readonly IJukeboxActions _jukeboxActions; + private readonly ICharacterProvider _characterProvider; + + public BardController(ICharacterAnimationActions characterAnimationActions, + IJukeboxActions jukeboxActions, + ICharacterProvider characterProvider) + { + _characterAnimationActions = characterAnimationActions; + _jukeboxActions = jukeboxActions; + _characterProvider = characterProvider; + } + public void PlayInstrumentNote(int noteIndex) { + _characterAnimationActions.StartAttacking(noteIndex); + _jukeboxActions.PlayNote(noteIndex); + } + + private bool CanAttackAgain() + { + var rp = _characterProvider.MainCharacter.RenderProperties; + return rp.IsActing(CharacterActionState.Standing) || + rp.RenderAttackFrame == CharacterRenderProperties.MAX_NUMBER_OF_ATTACK_FRAMES; } } diff --git a/EndlessClient/Controllers/NumPadController.cs b/EndlessClient/Controllers/NumPadController.cs index 467bf5f3d..be501e11a 100644 --- a/EndlessClient/Controllers/NumPadController.cs +++ b/EndlessClient/Controllers/NumPadController.cs @@ -1,6 +1,4 @@ using AutomaticTypeMapper; -using EndlessClient.ControlSets; -using EndlessClient.HUD.Controls; using EndlessClient.Rendering.Character; using EOLib.Domain.Character; using EOLib.Domain.Extensions; @@ -11,31 +9,25 @@ namespace EndlessClient.Controllers public class NumPadController : INumPadController { private readonly ICharacterActions _characterActions; - private readonly IHudControlProvider _hudControlProvider; - private readonly ICharacterRendererProvider _characterRendererProvider; + private readonly ICharacterAnimationActions _characterAnimationActions; + private readonly ICharacterProvider _characterProvider; public NumPadController(ICharacterActions characterActions, - IHudControlProvider hudControlProvider, - ICharacterRendererProvider characterRendererProvider) + ICharacterAnimationActions characterAnimationActions, + ICharacterProvider characterProvider) { _characterActions = characterActions; - _hudControlProvider = hudControlProvider; - _characterRendererProvider = characterRendererProvider; + _characterAnimationActions = characterAnimationActions; + _characterProvider = characterProvider; } public void Emote(Emote whichEmote) { - var mainRenderer = _characterRendererProvider.MainCharacterRenderer; - mainRenderer.MatchSome(renderer => - { - if (!renderer.Character.RenderProperties.IsActing(CharacterActionState.Standing)) - return; + if (!_characterProvider.MainCharacter.RenderProperties.IsActing(CharacterActionState.Standing)) + return; - _characterActions.Emote(whichEmote); - - var animator = _hudControlProvider.GetComponent(HudControlIdentifier.CharacterAnimator); - animator.Emote(renderer.Character.ID, whichEmote); - }); + _characterActions.Emote(whichEmote); + _characterAnimationActions.Emote(whichEmote); } } diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index 38ac910d1..d0fbdd7de 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -1,4 +1,5 @@ using AutomaticTypeMapper; +using EndlessClient.Audio; using EndlessClient.ControlSets; using EndlessClient.HUD; using EndlessClient.HUD.Controls; @@ -9,10 +10,13 @@ using EOLib.Domain.Map; using EOLib.Domain.Notifiers; using EOLib.Domain.Spells; +using EOLib.IO; using EOLib.IO.Map; using EOLib.IO.Repositories; using EOLib.Localization; using Optional; +using Optional.Collections; +using System.Linq; namespace EndlessClient.Rendering.Character { @@ -25,8 +29,9 @@ public class CharacterAnimationActions : ICharacterAnimationActions, IOtherChara private readonly ICharacterRendererProvider _characterRendererProvider; private readonly ICurrentMapProvider _currentMapProvider; private readonly ISpikeTrapActions _spikeTrapActions; - private readonly IESFFileProvider _esfFileProvider; + private readonly IPubFileProvider _pubFileProvider; private readonly IStatusLabelSetter _statusLabelSetter; + private readonly ISfxPlayer _sfxPlayer; public CharacterAnimationActions(IHudControlProvider hudControlProvider, ICharacterRepository characterRepository, @@ -34,8 +39,9 @@ public CharacterAnimationActions(IHudControlProvider hudControlProvider, ICharacterRendererProvider characterRendererProvider, ICurrentMapProvider currentMapProvider, ISpikeTrapActions spikeTrapActions, - IESFFileProvider esfFileProvider, - IStatusLabelSetter statusLabelSetter) + IPubFileProvider pubFileProvider, + IStatusLabelSetter statusLabelSetter, + ISfxPlayer sfxPlayer) { _hudControlProvider = hudControlProvider; _characterRepository = characterRepository; @@ -43,8 +49,9 @@ public CharacterAnimationActions(IHudControlProvider hudControlProvider, _characterRendererProvider = characterRendererProvider; _currentMapProvider = currentMapProvider; _spikeTrapActions = spikeTrapActions; - _esfFileProvider = esfFileProvider; + _pubFileProvider = pubFileProvider; _statusLabelSetter = statusLabelSetter; + _sfxPlayer = sfxPlayer; } public void Face(EODirection direction) @@ -66,14 +73,16 @@ public void StartWalking() ShowWaterSplashiesIfNeeded(CharacterActionState.Walking, _characterRepository.MainCharacter.ID); } - public void StartAttacking() + public void StartAttacking(int noteIndex = -1) { if (!_hudControlProvider.IsInGame) return; CancelSpellPrep(); - Animator.StartMainCharacterAttackAnimation(); + Animator.StartMainCharacterAttackAnimation(isBard: noteIndex >= 0); ShowWaterSplashiesIfNeeded(CharacterActionState.Attacking, _characterRepository.MainCharacter.ID); + + PlayWeaponSound(_characterRepository.MainCharacter, noteIndex); } public bool PrepareMainCharacterSpell(int spellId, ISpellTargetable spellTarget) @@ -81,11 +90,19 @@ public bool PrepareMainCharacterSpell(int spellId, ISpellTargetable spellTarget) if (!_hudControlProvider.IsInGame) return false; - var spellData = _esfFileProvider.ESFFile[spellId]; + var spellData = _pubFileProvider.ESFFile[spellId]; _characterRendererProvider.MainCharacterRenderer.MatchSome(r => r.ShoutSpellPrep(spellData.Shout)); return Animator.MainCharacterShoutSpellPrep(spellData, spellTarget); } + public void Emote(Emote whichEmote) + { + if (!_hudControlProvider.IsInGame) + return; + + Animator.Emote(_characterRepository.MainCharacter.ID, whichEmote); + } + public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, byte destinationY, EODirection direction) { if (!_hudControlProvider.IsInGame) @@ -98,13 +115,16 @@ public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, _spikeTrapActions.ShowSpikeTrap(characterID); } - public void StartOtherCharacterAttackAnimation(int characterID) + public void StartOtherCharacterAttackAnimation(int characterID, int noteIndex = -1) { if (!_hudControlProvider.IsInGame) return; - Animator.StartOtherCharacterAttackAnimation(characterID); + Animator.StartOtherCharacterAttackAnimation(characterID, noteIndex >= 0); ShowWaterSplashiesIfNeeded(CharacterActionState.Attacking, characterID); + + if (_currentMapStateProvider.Characters.ContainsKey(characterID)) + PlayWeaponSound(_currentMapStateProvider.Characters[characterID], noteIndex); } public void NotifyWarpLeaveEffect(short characterId, WarpAnimation anim) @@ -134,13 +154,13 @@ public void NotifyPotionEffect(short playerId, int effectId) public void NotifyStartSpellCast(short playerId, short spellId) { - var shoutName = _esfFileProvider.ESFFile[spellId].Shout; + var shoutName = _pubFileProvider.ESFFile[spellId].Shout; _characterRendererProvider.CharacterRenderers[playerId].ShoutSpellPrep(shoutName.ToLower()); } public void NotifySelfSpellCast(short playerId, short spellId, int spellHp, byte percentHealth) { - var spellGraphic = _esfFileProvider.ESFFile[spellId].Graphic; + var spellGraphic = _pubFileProvider.ESFFile[spellId].Graphic; if (playerId == _characterRepository.MainCharacter.ID) { @@ -172,7 +192,7 @@ public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerI _characterRendererProvider.CharacterRenderers[sourcePlayerID].ShoutSpellCast(); } - var spellData = _esfFileProvider.ESFFile[spellId]; + var spellData = _pubFileProvider.ESFFile[spellId]; if (targetPlayerID == _characterRepository.MainCharacter.ID) { @@ -246,6 +266,25 @@ private void CancelSpellPrep() _characterRendererProvider.MainCharacterRenderer.MatchSome(r => r.StopShout()); } + private void PlayWeaponSound(ICharacter character, int noteIndex = -1) + { + _pubFileProvider.EIFFile.SingleOrNone(x => x.Type == ItemType.Weapon && x.DollGraphic == character.RenderProperties.WeaponGraphic) + .MatchSome(x => + { + var ndx = Constants.InstrumentIDs.ToList().FindIndex(y => y == x.ID); + + if (ndx >= 0 && (noteIndex < 0 || noteIndex >= 36)) + return; + + switch (ndx) + { + case 0: _sfxPlayer.PlayHarpNote(noteIndex); break; + case 1: _sfxPlayer.PlayGuitarNote(noteIndex); break; + default: break; // todo: melee/bow/gun sounds + } + }); + } + private ICharacterAnimator Animator => _hudControlProvider.GetComponent(HudControlIdentifier.CharacterAnimator); } @@ -255,8 +294,10 @@ public interface ICharacterAnimationActions void StartWalking(); - void StartAttacking(); + void StartAttacking(int noteIndex = -1); bool PrepareMainCharacterSpell(int spellId, ISpellTargetable spellTarget); + + void Emote(Emote whichEmote); } } diff --git a/EndlessClient/Rendering/Character/CharacterAnimator.cs b/EndlessClient/Rendering/Character/CharacterAnimator.cs index 09dbc3a48..a0832fabf 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimator.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimator.cs @@ -129,7 +129,7 @@ public void StartMainCharacterWalkAnimation(Option targetCoordina _characterActions.Walk(); } - public void StartMainCharacterAttackAnimation() + public void StartMainCharacterAttackAnimation(bool isBard = false) { if (_otherPlayerStartAttackingTimes.ContainsKey(_characterRepository.MainCharacter.ID)) { @@ -139,6 +139,13 @@ public void StartMainCharacterAttackAnimation() var startAttackingTime = new RenderFrameActionTime(_characterRepository.MainCharacter.ID); _otherPlayerStartAttackingTimes.Add(_characterRepository.MainCharacter.ID, startAttackingTime); + + if (isBard) + { + var rp = _characterRepository.MainCharacter.RenderProperties.WithEmote(EOLib.Domain.Character.Emote.MusicNotes); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithRenderProperties(rp); + _startEmoteTimes[_characterRepository.MainCharacter.ID] = new RenderFrameActionTime(_characterRepository.MainCharacter.ID); + } } public bool MainCharacterShoutSpellPrep(ESFRecord spellData, ISpellTargetable target) @@ -176,7 +183,7 @@ public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, _otherPlayerStartWalkingTimes.Add(characterID, startWalkingTimeAndID); } - public void StartOtherCharacterAttackAnimation(int characterID) + public void StartOtherCharacterAttackAnimation(int characterID, bool isBard = false) { if (_otherPlayerStartAttackingTimes.TryGetValue(characterID, out var _)) { @@ -184,8 +191,15 @@ public void StartOtherCharacterAttackAnimation(int characterID) return; } - var startAttackingTimeAndID = new RenderFrameActionTime(characterID); - _otherPlayerStartAttackingTimes.Add(characterID, startAttackingTimeAndID); + var startAttackingTime = new RenderFrameActionTime(characterID); + _otherPlayerStartAttackingTimes.Add(characterID, startAttackingTime); + + if (isBard && _currentMapStateRepository.Characters.TryGetValue(characterID, out var otherCharacter)) + { + var rp = otherCharacter.RenderProperties.WithEmote(EOLib.Domain.Character.Emote.MusicNotes); + _currentMapStateRepository.Characters[characterID] = otherCharacter.WithRenderProperties(rp); + _startEmoteTimes[characterID] = new RenderFrameActionTime(characterID); + } } public void StartOtherCharacterSpellCast(int characterID) @@ -547,7 +561,7 @@ public interface ICharacterAnimator : IGameComponent void StartMainCharacterWalkAnimation(Option targetCoordinate); - void StartMainCharacterAttackAnimation(); + void StartMainCharacterAttackAnimation(bool isBard = false); bool MainCharacterShoutSpellPrep(ESFRecord spellData, ISpellTargetable spellTarget); @@ -555,7 +569,7 @@ public interface ICharacterAnimator : IGameComponent void StartOtherCharacterWalkAnimation(int characterID, byte targetX, byte targetY, EODirection direction); - void StartOtherCharacterAttackAnimation(int characterID); + void StartOtherCharacterAttackAnimation(int characterID, bool isBard = false); void StartOtherCharacterSpellCast(int characterID); diff --git a/EndlessClient/Rendering/CharacterProperties/EmoteRenderer.cs b/EndlessClient/Rendering/CharacterProperties/EmoteRenderer.cs index ec2309d43..a1cf5a5d6 100644 --- a/EndlessClient/Rendering/CharacterProperties/EmoteRenderer.cs +++ b/EndlessClient/Rendering/CharacterProperties/EmoteRenderer.cs @@ -12,8 +12,7 @@ public class EmoteRenderer : BaseCharacterPropertyRenderer private readonly ISpriteSheet _skinSheet; private readonly SkinRenderLocationCalculator _skinRenderLocationCalculator; - public override bool CanRender => _renderProperties.IsActing(CharacterActionState.Emote) && - _renderProperties.EmoteFrame > 0; + public override bool CanRender => _renderProperties.EmoteFrame > 0; protected override bool ShouldFlip => false; diff --git a/EndlessClient/Rendering/CharacterProperties/FaceRenderer.cs b/EndlessClient/Rendering/CharacterProperties/FaceRenderer.cs index e34a708f4..97ef88c5d 100644 --- a/EndlessClient/Rendering/CharacterProperties/FaceRenderer.cs +++ b/EndlessClient/Rendering/CharacterProperties/FaceRenderer.cs @@ -16,7 +16,8 @@ public class FaceRenderer : BaseCharacterPropertyRenderer public override bool CanRender => _renderProperties.IsActing(CharacterActionState.Emote) && _renderProperties.EmoteFrame > 0 && _renderProperties.Emote != Emote.Trade && - _renderProperties.Emote != Emote.LevelUp; + _renderProperties.Emote != Emote.LevelUp && + _renderProperties.Emote != Emote.MusicNotes; public FaceRenderer(ICharacterRenderProperties renderProperties, ISpriteSheet faceSheet, diff --git a/EndlessClient/Rendering/Sprites/EmoteSpriteType.cs b/EndlessClient/Rendering/Sprites/EmoteSpriteType.cs index 94043173c..aad9b755c 100644 --- a/EndlessClient/Rendering/Sprites/EmoteSpriteType.cs +++ b/EndlessClient/Rendering/Sprites/EmoteSpriteType.cs @@ -15,6 +15,7 @@ public enum EmoteSpriteType Drunk = 10, Trade = 11, LevelUp = 12, - Playful = 13 + Playful = 13, + MusicNotes = 14, } } From 512ed1aec3b6db7562bd64a07fee39590aa2414b Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 27 Apr 2022 07:41:47 -0700 Subject: [PATCH 6/7] Ensure CharacterRenderer is not updating textures every single frame --- EndlessClient/Rendering/Character/CharacterRenderer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/EndlessClient/Rendering/Character/CharacterRenderer.cs b/EndlessClient/Rendering/Character/CharacterRenderer.cs index 1cb14ecb4..9cb1fba9d 100644 --- a/EndlessClient/Rendering/Character/CharacterRenderer.cs +++ b/EndlessClient/Rendering/Character/CharacterRenderer.cs @@ -319,9 +319,12 @@ private void SetGridCoordinatePosition() private void SetScreenCoordinates(int xPosition, int yPosition) { - // size of standing still skin texture - DrawArea = new Rectangle(xPosition, yPosition, 18, 58); - _textureUpdateRequired = true; + if (DrawArea.X != xPosition || DrawArea.Y != yPosition) + { + // size of standing still skin texture + DrawArea = new Rectangle(xPosition, yPosition, 18, 58); + _textureUpdateRequired = true; + } } private int GetMainCharacterOffsetX() From 1335b1c874fc1a41ef95f4b6b8e5bea89e34ca87 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 27 Apr 2022 07:46:08 -0700 Subject: [PATCH 7/7] Separate Emote from Attack in CharacterAnimator --- .../Character/CharacterAnimationActions.cs | 10 +++++++-- .../Rendering/Character/CharacterAnimator.cs | 22 ++++--------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index d0fbdd7de..9afbd076f 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -79,7 +79,10 @@ public void StartAttacking(int noteIndex = -1) return; CancelSpellPrep(); - Animator.StartMainCharacterAttackAnimation(isBard: noteIndex >= 0); + + if (noteIndex >= 0) + Animator.Emote(_characterRepository.MainCharacter.ID, EOLib.Domain.Character.Emote.MusicNotes); + Animator.StartMainCharacterAttackAnimation(); ShowWaterSplashiesIfNeeded(CharacterActionState.Attacking, _characterRepository.MainCharacter.ID); PlayWeaponSound(_characterRepository.MainCharacter, noteIndex); @@ -120,7 +123,10 @@ public void StartOtherCharacterAttackAnimation(int characterID, int noteIndex = if (!_hudControlProvider.IsInGame) return; - Animator.StartOtherCharacterAttackAnimation(characterID, noteIndex >= 0); + if (noteIndex >= 0) + Animator.Emote(characterID, EOLib.Domain.Character.Emote.MusicNotes); + + Animator.StartOtherCharacterAttackAnimation(characterID); ShowWaterSplashiesIfNeeded(CharacterActionState.Attacking, characterID); if (_currentMapStateProvider.Characters.ContainsKey(characterID)) diff --git a/EndlessClient/Rendering/Character/CharacterAnimator.cs b/EndlessClient/Rendering/Character/CharacterAnimator.cs index a0832fabf..913013451 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimator.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimator.cs @@ -129,7 +129,7 @@ public void StartMainCharacterWalkAnimation(Option targetCoordina _characterActions.Walk(); } - public void StartMainCharacterAttackAnimation(bool isBard = false) + public void StartMainCharacterAttackAnimation() { if (_otherPlayerStartAttackingTimes.ContainsKey(_characterRepository.MainCharacter.ID)) { @@ -139,13 +139,6 @@ public void StartMainCharacterAttackAnimation(bool isBard = false) var startAttackingTime = new RenderFrameActionTime(_characterRepository.MainCharacter.ID); _otherPlayerStartAttackingTimes.Add(_characterRepository.MainCharacter.ID, startAttackingTime); - - if (isBard) - { - var rp = _characterRepository.MainCharacter.RenderProperties.WithEmote(EOLib.Domain.Character.Emote.MusicNotes); - _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithRenderProperties(rp); - _startEmoteTimes[_characterRepository.MainCharacter.ID] = new RenderFrameActionTime(_characterRepository.MainCharacter.ID); - } } public bool MainCharacterShoutSpellPrep(ESFRecord spellData, ISpellTargetable target) @@ -183,7 +176,7 @@ public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, _otherPlayerStartWalkingTimes.Add(characterID, startWalkingTimeAndID); } - public void StartOtherCharacterAttackAnimation(int characterID, bool isBard = false) + public void StartOtherCharacterAttackAnimation(int characterID) { if (_otherPlayerStartAttackingTimes.TryGetValue(characterID, out var _)) { @@ -193,13 +186,6 @@ public void StartOtherCharacterAttackAnimation(int characterID, bool isBard = fa var startAttackingTime = new RenderFrameActionTime(characterID); _otherPlayerStartAttackingTimes.Add(characterID, startAttackingTime); - - if (isBard && _currentMapStateRepository.Characters.TryGetValue(characterID, out var otherCharacter)) - { - var rp = otherCharacter.RenderProperties.WithEmote(EOLib.Domain.Character.Emote.MusicNotes); - _currentMapStateRepository.Characters[characterID] = otherCharacter.WithRenderProperties(rp); - _startEmoteTimes[characterID] = new RenderFrameActionTime(characterID); - } } public void StartOtherCharacterSpellCast(int characterID) @@ -561,7 +547,7 @@ public interface ICharacterAnimator : IGameComponent void StartMainCharacterWalkAnimation(Option targetCoordinate); - void StartMainCharacterAttackAnimation(bool isBard = false); + void StartMainCharacterAttackAnimation(); bool MainCharacterShoutSpellPrep(ESFRecord spellData, ISpellTargetable spellTarget); @@ -569,7 +555,7 @@ public interface ICharacterAnimator : IGameComponent void StartOtherCharacterWalkAnimation(int characterID, byte targetX, byte targetY, EODirection direction); - void StartOtherCharacterAttackAnimation(int characterID, bool isBard = false); + void StartOtherCharacterAttackAnimation(int characterID); void StartOtherCharacterSpellCast(int characterID);