Skip to content

Commit

Permalink
Merge pull request #48 from Extremelyd1/hkmp-api
Browse files Browse the repository at this point in the history
HKMP API with addon system
  • Loading branch information
Extremelyd1 authored Mar 7, 2022
2 parents 70ff212 + 6c175dd commit 8fbc057
Show file tree
Hide file tree
Showing 201 changed files with 8,632 additions and 2,916 deletions.
7 changes: 2 additions & 5 deletions HKMP/Animation/AnimationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Hkmp.Fsm;
using Hkmp.Game;
using Hkmp.Game.Client;
using Hkmp.Networking;
using Hkmp.Networking.Client;
using Hkmp.Networking.Packet;
using Hkmp.Networking.Packet.Data;
Expand All @@ -15,8 +14,6 @@
using Modding;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;

namespace Hkmp.Animation {
/**
Expand Down Expand Up @@ -553,12 +550,12 @@ public class AnimationManager {
private bool _lastWallSlideActive;

public AnimationManager(
NetworkManager networkManager,
NetClient netClient,
PlayerManager playerManager,
PacketManager packetManager,
Game.Settings.GameSettings gameSettings
) {
_netClient = networkManager.GetNetClient();
_netClient = netClient;
_playerManager = playerManager;

// Register packet handler
Expand Down
5 changes: 1 addition & 4 deletions HKMP/Animation/Effects/FireballBase.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System;
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using Hkmp.Util;
using HutongGames.PlayMaker.Actions;
using UnityEngine;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;

// TODO: (dung)flukes are still client sided, perhaps find a efficient way to sync them?
namespace Hkmp.Animation.Effects {
Expand Down
2 changes: 1 addition & 1 deletion HKMP/Animation/Effects/FocusBurst.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Hkmp.Util;
using HutongGames.PlayMaker.Actions;
using Modding;
using UnityEngine;
using ReflectionHelper = Modding.ReflectionHelper;

namespace Hkmp.Animation.Effects {
public class FocusBurst : DamageAnimationEffect {
Expand Down
2 changes: 1 addition & 1 deletion HKMP/Animation/Effects/FocusEnd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Play(GameObject playerObject) {
var audioSource = chargeAudio.GetComponent<AudioSource>();

// Instantiate a custom fade audio object
var fadeAudio = new Fsm.FadeAudio(
var fadeAudio = new FadeAudio(
audioSource,
1,
0,
Expand Down
29 changes: 29 additions & 0 deletions HKMP/Api/Addon/Addon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Hkmp.Api.Addon {
/// <summary>
/// Abstract base class for addons.
/// </summary>
public abstract class Addon {
/// <summary>
/// The maximum length of the name string for an addon.
/// </summary>
public const int MaxNameLength = 20;
/// <summary>
/// The maximum length of the version string for an addon.
/// </summary>
public const int MaxVersionLength = 10;

/// <summary>
/// The internal ID assigned to this addon.
/// </summary>
internal byte Id { get; set; }

/// <summary>
/// The network sender object if it has been registered.
/// </summary>
internal object NetworkSender;
/// <summary>
/// The network receiver object if it has been registered.
/// </summary>
internal object NetworkReceiver;
}
}
107 changes: 107 additions & 0 deletions HKMP/Api/Addon/AddonLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Hkmp.Api.Addon {
/// <summary>
/// Abstract base class for loading addons from file.
/// </summary>
public abstract class AddonLoader {
/// <summary>
/// The file pattern to look for when obtaining candidate files to load.
/// </summary>
private const string AssemblyFilePattern = "*.dll";

/// <summary>
/// The directory in which to look for assembly files.
/// </summary>
/// <returns>A string denoting the path of the current directory.</returns>
protected abstract string GetCurrentDirectoryPath();

/// <summary>
/// Get the paths for all assembly files in the HKMP directory.
/// </summary>
/// <returns>A string array containing file paths.</returns>
private string[] GetAssemblyPaths() {
return Directory.GetFiles(GetCurrentDirectoryPath(), AssemblyFilePattern);
}

/// <summary>
/// Get all loadable types from the given assembly.
/// </summary>
/// <param name="assembly">The assembly instance to get the types from.</param>
/// <returns>An enumerator that traverses the loadable types.</returns>
private static IEnumerable<Type> GetLoadableTypes(Assembly assembly) {
try {
return assembly.GetTypes();
} catch (ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}

/// <summary>
/// Load all addons given their type and given an API interface instance.
/// </summary>
/// <param name="apiObject">The API interface instance in object form.</param>
/// <typeparam name="TAddon">The type of the addon.</typeparam>
/// <typeparam name="TApiInterface">The type of the API interface.</typeparam>
/// <returns>A list of addon instance of type TAddon.</returns>
protected List<TAddon> LoadAddons<TAddon, TApiInterface>(object apiObject) {
var addons = new List<TAddon>();

var assemblyPaths = GetAssemblyPaths();

foreach (var assemblyPath in assemblyPaths) {
Logger.Get().Info(this, $"Trying to load assembly at: {assemblyPath}");

Assembly assembly;
try {
assembly = Assembly.LoadFrom(assemblyPath);
} catch (Exception e) {
Logger.Get().Warn(this,
$" Could not load assembly, exception: {e.GetType()}, message: {e.Message}");
continue;
}

foreach (var type in GetLoadableTypes(assembly)) {
if (!type.IsClass
|| type.IsAbstract
|| type.IsInterface
|| !type.IsSubclassOf(typeof(TAddon))
) {
continue;
}

Logger.Get().Info(this, $" Found {typeof(TAddon)} extending class, constructing addon");

var constructor = type.GetConstructor(new[] {typeof(TApiInterface)});
if (constructor == null) {
Logger.Get().Warn(this, " Could not find constructor for addon");
continue;
}

object addonObject;
try {
addonObject = constructor.Invoke(new[] {apiObject});
} catch (Exception e) {
Logger.Get().Warn(this, $" Could not invoke constructor for addon, exception: {e.GetType()}, message: {e.Message}");
continue;
}

if (!(addonObject is TAddon addon)) {
Logger.Get().Warn(this, $" Addon is not of type {typeof(TAddon).Name}");
continue;
}

addons.Add(addon);
// We only allow a single class extending the addon subclass
break;
}
}

return addons;
}
}
}
68 changes: 68 additions & 0 deletions HKMP/Api/Client/ClientAddon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using JetBrains.Annotations;

namespace Hkmp.Api.Client {
/// <summary>
/// Abstract base class for a client addon. Inheriting this will allow the addon class to be loaded.
/// </summary>
[PublicAPI]
public abstract class ClientAddon : Addon.Addon {

/// <summary>
/// The client API interface.
/// </summary>
protected IClientApi ClientApi { get; }

/// <summary>
/// The logger for logging information.
/// </summary>
protected ILogger Logger => Hkmp.Logger.Get();

/// <summary>
/// The name (and also identifier) of the addon.
/// </summary>
protected abstract string Name { get; }

/// <summary>
/// The version (also identifying) of the addon.
/// </summary>
protected abstract string Version { get; }

/// <summary>
/// Whether this addon requires network access.
/// </summary>
public abstract bool NeedsNetwork { get; }

/// <summary>
/// Called when the addon is loaded and can be initialized.
/// </summary>
public abstract void Initialize();

public ClientAddon(IClientApi clientApi) {
ClientApi = clientApi;
}

/// <summary>
/// Internal method for obtaining the length-valid addon name.
/// </summary>
/// <returns>The name of the addon or a substring of the first valid characters of its name.</returns>
public string GetName() {
if (Name.Length > MaxNameLength) {
return Name.Substring(0, MaxNameLength);
}

return Name;
}

/// <summary>
/// Internal method for obtaining the length-valid addon version.
/// </summary>
/// <returns>The version of the addon or a substring of the first valid characters of its version.</returns>
public string GetVersion() {
if (Version.Length > MaxVersionLength) {
return Version.Substring(0, MaxVersionLength);
}

return Version;
}
}
}
32 changes: 32 additions & 0 deletions HKMP/Api/Client/ClientAddonLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Hkmp.Api.Addon;

namespace Hkmp.Api.Client {
/// <summary>
/// Addon loader for the client-side.
/// </summary>
public class ClientAddonLoader : AddonLoader {
/// <summary>
/// The client API instance to pass onto newly loaded addons.
/// </summary>
private readonly ClientApi _clientApi;

public ClientAddonLoader(ClientApi clientApi) {
_clientApi = clientApi;
}

/// <summary>
/// Loads all client addons.
/// </summary>
/// <returns>A list of ClientAddon instances.</returns>
public List<ClientAddon> LoadAddons() {
return LoadAddons<ClientAddon, IClientApi>(_clientApi);
}

protected override string GetCurrentDirectoryPath() {
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
}
}
83 changes: 83 additions & 0 deletions HKMP/Api/Client/ClientAddonManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using Hkmp.Networking.Packet.Data;

namespace Hkmp.Api.Client {
/// <summary>
/// Manager class for client addons.
/// </summary>
public class ClientAddonManager {
/// <summary>
/// A list of all loaded addons, the order is important as it is the exact order
/// in which we sent it to the server and are expected to act on when receiving a response.
/// </summary>
private readonly List<ClientAddon> _addons;

public ClientAddonManager(ClientApi clientApi) {
_addons = new List<ClientAddon>();

var addonLoader = new ClientAddonLoader(clientApi);

var addons = addonLoader.LoadAddons();
foreach (var addon in addons) {
Logger.Get().Info(this,
$"Initializing client addon: {addon.GetName()} {addon.GetVersion()}");

try {
addon.Initialize();
} catch (Exception e) {
Logger.Get().Warn(this, $"Could not initialize addon {addon.GetName()}, exception: {e.GetType()}, {e.Message}, {e.StackTrace}");
continue;
}

_addons.Add(addon);
}
}

/// <summary>
/// Get a list of addon data for all networked addons.
/// </summary>
/// <returns>A list of AddonData instances.</returns>
public List<AddonData> GetNetworkedAddonData() {
var addonData = new List<AddonData>();

foreach (var addon in _addons) {
if (!addon.NeedsNetwork) {
continue;
}

addonData.Add(new AddonData {
Identifier = addon.GetName(),
Version = addon.GetVersion()
});
}

return addonData;
}

/// <summary>
/// Updates the order of all networked addons according to the given order.
/// </summary>
/// <param name="addonOrder">A byte array containing the IDs the addons should have.</param>
public void UpdateNetworkedAddonOrder(byte[] addonOrder) {
var index = 0;

// The order of the addons in our local list should stay the same
// between connection and obtaining the addon order from the server
foreach (var addon in _addons) {
// Skip all non-networked addons
if (!addon.NeedsNetwork) {
continue;
}

// Retrieve the ID that this networked addon should have
var id = addonOrder[index++];

// Set the internal ID of the addon
addon.Id = id;

Logger.Get().Info(this, $"Retrieved addon {addon.GetName()} v{addon.GetVersion()} ID: {id}");
}
}
}
}
Loading

0 comments on commit 8fbc057

Please sign in to comment.