Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bard dialog and first-pass SFX playing #188

Merged
merged 7 commits into from
Apr 27, 2022
1 change: 1 addition & 0 deletions BatchMap/BatchMap.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\EOLib.IO\EOLib.IO.csproj" />
<ProjectReference Include="..\EOLib\EOLib.csproj" />
</ItemGroup>
</Project>
4 changes: 3 additions & 1 deletion BatchMap/Program.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -101,7 +103,7 @@ private static void Main(string[] args)
{
var actions = _typeRegistry.Resolve<IPubFileLoadActions>();

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
Expand Down
2 changes: 1 addition & 1 deletion EOBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }
}

Expand Down
8 changes: 5 additions & 3 deletions EOLib.IO/Actions/IPubFileLoadActions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
namespace EOLib.IO.Actions
using System.Collections.Generic;

namespace EOLib.IO.Actions
{
public interface IPubFileLoadActions
{
void LoadItemFile();
void LoadItemFile(IEnumerable<int> rangedWeaponIds);

void LoadItemFileByName(string fileName);
void LoadItemFileByName(string fileName, IEnumerable<int> rangedWeaponIds);

void LoadNPCFile();

Expand Down
21 changes: 17 additions & 4 deletions EOLib.IO/Actions/PubFileLoadActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -27,16 +29,16 @@ public PubFileLoadActions(IPubFileRepository pubFileRepository,
_classFileLoadService = classFileLoadService;
}

public void LoadItemFile()
public void LoadItemFile(IEnumerable<int> rangedWeaponIds)
{
var itemFile = _itemFileLoadService.LoadPubFromDefaultFile();
_pubFileRepository.EIFFile = itemFile;
_pubFileRepository.EIFFile = OverrideRangedWeapons(itemFile, rangedWeaponIds);
}

public void LoadItemFileByName(string fileName)
public void LoadItemFileByName(string fileName, IEnumerable<int> rangedWeaponIds)
{
var itemFile = _itemFileLoadService.LoadPubFromExplicitFile(fileName);
_pubFileRepository.EIFFile = itemFile;
_pubFileRepository.EIFFile = OverrideRangedWeapons(itemFile, rangedWeaponIds);
}

public void LoadNPCFile()
Expand Down Expand Up @@ -74,5 +76,16 @@ public void LoadClassFileByName(string fileName)
var classFile = _classFileLoadService.LoadPubFromExplicitFile(fileName);
_pubFileRepository.ECFFile = classFile;
}

private static IPubFile<EIFRecord> OverrideRangedWeapons(IPubFile<EIFRecord> inputFile, IEnumerable<int> 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;
}
}
}
8 changes: 2 additions & 6 deletions EOLib.IO/Extensions/EIFFileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ public static bool IsShieldOnBack(this IPubFile<EIFRecord> itemFile, short graph

public static bool IsRangedWeapon(this IPubFile<EIFRecord> 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;
}
}
}
6 changes: 5 additions & 1 deletion EOLib/Domain/Character/CharacterRenderProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion EOLib/Domain/Character/Emote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum Emote
/// <summary>
/// 0 key
/// </summary>
Playful = 14
Playful = 14,
MusicNotes = 15,
}
}
51 changes: 51 additions & 0 deletions EOLib/Domain/Jukebox/JukeboxActions.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 2 additions & 2 deletions EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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) { }

Expand Down
11 changes: 11 additions & 0 deletions EOLib/Domain/Spells/SpellCastValidationActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -72,12 +74,21 @@ 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
{
SpellCastValidationResult ValidateSpellCast(int spellId);

SpellCastValidationResult ValidateSpellCast(int spellId, ISpellTargetable spellTarget);

bool ValidateBard();
}
}
61 changes: 61 additions & 0 deletions EOLib/PacketHandlers/Jukebox/JukeboxMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -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<IMainCharacterEventNotifier> _mainCharacterEventNotifiers;
private readonly IEnumerable<IOtherCharacterAnimationNotifier> _otherCharacterAnimationNotifiers;

public override PacketFamily Family => PacketFamily.JukeBox;

public override PacketAction Action => PacketAction.Message;

public JukeboxMessageHandler(IPlayerInfoProvider playerInfoProvider,
ICurrentMapStateRepository currentMapStateRepository,
IEnumerable<IOtherCharacterAnimationNotifier> 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;
}
}
}
9 changes: 8 additions & 1 deletion EOLib/misc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -51,7 +53,12 @@ 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 };
// 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";
Expand Down
62 changes: 62 additions & 0 deletions EndlessClient/Audio/SfxPlayer.cs
Original file line number Diff line number Diff line change
@@ -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<SoundEffectID, SoundEffectInstance> _activeSfx;

public SfxPlayer(IContentProvider contentProvider)
{
_contentProvider = contentProvider;
_activeSfx = new Dictionary<SoundEffectID, SoundEffectInstance>();
}

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);
}
}
3 changes: 1 addition & 2 deletions EndlessClient/Audio/WAVFileValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

using System;
using System;
using System.IO;
using System.Text;
using EOLib;
Expand Down
Loading