Skip to content

Commit

Permalink
Add support for bard music. Player can click to play harp/guitar note…
Browse files Browse the repository at this point in the history
…s, and notes played by other players will be played.
  • Loading branch information
ethanmoffat committed Apr 27, 2022
1 parent bfb045f commit aafaa13
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 46 deletions.
2 changes: 1 addition & 1 deletion EOBot/Program.cs
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
6 changes: 5 additions & 1 deletion EOLib/Domain/Character/CharacterRenderProperties.cs
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
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
@@ -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
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
61 changes: 61 additions & 0 deletions 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<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;
}
}
}
26 changes: 26 additions & 0 deletions 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;
}
}

Expand Down
28 changes: 10 additions & 18 deletions 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;
Expand All @@ -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<ICharacterAnimator>(HudControlIdentifier.CharacterAnimator);
animator.Emote(renderer.Character.ID, whichEmote);
});
_characterActions.Emote(whichEmote);
_characterAnimationActions.Emote(whichEmote);
}
}

Expand Down

0 comments on commit aafaa13

Please sign in to comment.