From 797c2389f1ce017a28ad7c2f1b77f60e064a581b Mon Sep 17 00:00:00 2001
From: Extremelyd1 <10898310+Extremelyd1@users.noreply.github.com>
Date: Sat, 20 Jul 2024 20:37:54 +0200
Subject: [PATCH] Initial work for sound/music networking
---
.../Client/Entity/Component/MusicComponent.cs | 148 ++++++++++++++++++
HKMP/HKMP.csproj | 1 +
HKMP/Resource/music-data.json | 104 ++++++++++++
3 files changed, 253 insertions(+)
create mode 100644 HKMP/Game/Client/Entity/Component/MusicComponent.cs
create mode 100644 HKMP/Resource/music-data.json
diff --git a/HKMP/Game/Client/Entity/Component/MusicComponent.cs b/HKMP/Game/Client/Entity/Component/MusicComponent.cs
new file mode 100644
index 0000000..dc1cfbc
--- /dev/null
+++ b/HKMP/Game/Client/Entity/Component/MusicComponent.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using Hkmp.Networking.Client;
+using Hkmp.Networking.Packet.Data;
+using Hkmp.Util;
+using HutongGames.PlayMaker.Actions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using UnityEngine;
+using UnityEngine.Audio;
+using Logger = Hkmp.Logging.Logger;
+
+namespace Hkmp.Game.Client.Entity.Component;
+
+// TODO: document all fields and methods
+///
+/// This component manages the music that plays for boss fights.
+internal class MusicComponent : EntityComponent {
+ ///
+ /// The file path of the embedded resource file for music data.
+ ///
+ private const string MusicDataFilePath = "Hkmp.Resource.music-data.json";
+
+ private static readonly List MusicCueDataList;
+ private static readonly List SnapshotDataList;
+
+ static MusicComponent() {
+ var dataPair = FileUtil.LoadObjectFromEmbeddedJson<
+ (List, List)
+ >(MusicDataFilePath);
+
+ MusicCueDataList = dataPair.Item1;
+ SnapshotDataList = dataPair.Item2;
+
+ On.PlayMakerFSM.OnEnable += OnFsmEnable;
+ }
+
+ public MusicComponent(
+ NetClient netClient,
+ ushort entityId,
+ HostClientPair gameObject
+ ) : base(netClient, entityId, gameObject) {
+ // TODO: register hooks for entering ApplyMusicCue and TransitionToAudioSnapshot actions
+ // TODO: these hooks should network changes in Music and Audio to the server if the player is scene host
+ }
+
+ ///
+ public override void InitializeHost() {
+ }
+
+ ///
+ public override void Update(EntityNetworkData data) {
+ // TODO: handle receiving Music and Audio updates from the server and applying them
+ }
+
+ ///
+ public override void Destroy() {
+ }
+
+ private static void OnFsmEnable(On.PlayMakerFSM.orig_OnEnable orig, PlayMakerFSM self) {
+ orig(self);
+
+ foreach (var state in self.FsmStates) {
+ foreach (var action in state.Actions) {
+ if (action is ApplyMusicCue applyMusicCue) {
+ var musicCue = applyMusicCue.musicCue.Value as MusicCue;
+ if (musicCue == null) {
+ continue;
+ }
+
+ Logger.Debug($"Found music cue '{musicCue.name}' in FSM '{self.Fsm.Name}', '{state.Name}'");
+
+ if (GetMusicCueData(
+ data => data.Name.Equals(musicCue.name),
+ out var musicCueData
+ )) {
+ Logger.Debug($" Adding to data with type: {musicCueData.Type}");
+ musicCueData.MusicCue = musicCue;
+ }
+ } else if (action is TransitionToAudioSnapshot snapshotAction) {
+ var snapshot = snapshotAction.snapshot.Value as AudioMixerSnapshot;
+ if (snapshot == null) {
+ continue;
+ }
+
+ Logger.Debug($"Found audio mixer snapshot '{snapshot.name}' in FSM '{self.Fsm.Name}', '{state.Name}'");
+ }
+ }
+ }
+ }
+
+ private static bool GetMusicCueData(Func predicate, out MusicCueData musicCueData) {
+ foreach (var data in MusicCueDataList) {
+ if (predicate.Invoke(data)) {
+ musicCueData = data;
+ return true;
+ }
+ }
+
+ musicCueData = null;
+ return false;
+ }
+
+ private class MusicCueData {
+ public MusicCueType Type { get; set; }
+ public string Name { get; set; }
+ public byte Index { get; set; }
+ [JsonIgnore]
+ public MusicCue MusicCue { get; set; }
+ }
+
+ private class AudioMixerSnapshotData {
+ public AudioMixerSnapshotType Type { get; set; }
+ public string Name { get; set; }
+ public byte Index { get; set; }
+ [JsonIgnore]
+ public AudioMixerSnapshot Snapshot { get; set; }
+ }
+
+ [JsonConverter(typeof(StringEnumConverter))]
+ private enum MusicCueType {
+ None,
+ FalseKnight,
+ Hornet,
+ GGHornet,
+ MantisLords,
+ SoulMaster,
+ SoulMaster2,
+ GGHeavy,
+ EnemyBattle,
+ DreamFight,
+ Hive,
+ HiveKnight,
+ DungDefender,
+ BrokenVessel,
+ Nosk,
+ TheHollowKnight,
+ Greenpath,
+ Waterways
+ }
+
+ [JsonConverter(typeof(StringEnumConverter))]
+ private enum AudioMixerSnapshotType {
+ Silent,
+ None,
+ Off,
+ }
+}
diff --git a/HKMP/HKMP.csproj b/HKMP/HKMP.csproj
index 76902f0..dc66cd4 100644
--- a/HKMP/HKMP.csproj
+++ b/HKMP/HKMP.csproj
@@ -23,6 +23,7 @@
+
diff --git a/HKMP/Resource/music-data.json b/HKMP/Resource/music-data.json
new file mode 100644
index 0000000..e9669f4
--- /dev/null
+++ b/HKMP/Resource/music-data.json
@@ -0,0 +1,104 @@
+{
+ "Item1": [
+ {
+ "Type": "None",
+ "Name": "None",
+ "Index": 0
+ },
+ {
+ "Type": "FalseKnight",
+ "Name": "Boss1",
+ "Index": 1
+ },
+ {
+ "Type": "Hornet",
+ "Name": "BossHornet",
+ "Index": 2
+ },
+ {
+ "Type": "GGHornet",
+ "Name": "GGHornet",
+ "Index": 3
+ },
+ {
+ "Type": "MantisLords",
+ "Name": "BossMantisLords",
+ "Index": 4
+ },
+ {
+ "Type": "SoulMaster",
+ "Name": "BossMageLord",
+ "Index": 5
+ },
+ {
+ "Type": "SoulMaster2",
+ "Name": "MageLord2",
+ "Index": 6
+ },
+ {
+ "Type": "GGHeavy",
+ "Name": "GG Heavy",
+ "Index": 7
+ },{
+ "Type": "DreamFight",
+ "Name": "DreamFight",
+ "Index": 8
+ },
+ {
+ "Type": "Hive",
+ "Name": "Hive",
+ "Index": 9
+ },
+ {
+ "Type": "HiveKnight",
+ "Name": "HiveKnight",
+ "Index": 10
+ },
+ {
+ "Type": "DungDefender",
+ "Name": "DungDefender",
+ "Index": 11
+ },
+ {
+ "Type": "BrokenVessel",
+ "Name": "BossIK",
+ "Index": 12
+ },
+ {
+ "Type": "Nosk",
+ "Name": "MimicSpider",
+ "Index": 13
+ },{
+ "Type": "TheHollowKnight",
+ "Name": "HKBattle",
+ "Index": 14
+ },
+ {
+ "Type": "Greenpath",
+ "Name": "Greenpath",
+ "Index": 15
+ },
+ {
+ "Type": "Waterways",
+ "Name": "Waterways",
+ "Index": 16
+ }
+ ],
+ "Item2": [
+ {
+ "Type": "Silent",
+ "Name": "Silent",
+ "Index": 0
+ },
+ {
+ "Type": "None",
+ "Name": "None",
+ "Index": 1
+ },
+ {
+ "Type": "Off",
+ "Name": "Off",
+ "Index": 2
+ }
+ ]
+}