Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions MiraAPI.Example/Achievements/AchievementCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Globalization;
using HarmonyLib;
using MiraAPI.Achievements;

namespace MiraAPI.Example.Achievements;

[HarmonyPatch]
public static class AchievementCommands
{
[HarmonyPrefix]
[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))]
private static bool SendChatPrefix(ChatController __instance)
{
var text = __instance.freeChatField.textArea.text.Trim();
if (!text.StartsWith("/achievement", true, CultureInfo.InvariantCulture))
{
return true;
}

var parts = text.Split(' ');
if (parts.Length < 2)
{
__instance.AddChat(PlayerControl.LocalPlayer, "Usage: /achievement <id> | /achievement unlock <id>");
__instance.freeChatField.textArea.Clear();
return false;
}

if (parts[1].Equals("unlock", System.StringComparison.OrdinalIgnoreCase))
{
if (parts.Length < 3)
{
__instance.AddChat(PlayerControl.LocalPlayer, "Usage: /achievement unlock <id>");
__instance.freeChatField.textArea.Clear();
return false;
}

var unlockId = parts[2];
if (!MiraAPI.Achievements.AchievementManager.TryGetAchievement(unlockId, out var unlockAch))
{
__instance.AddChat(PlayerControl.LocalPlayer, $"Achievement '{unlockId}' not found.");
__instance.freeChatField.textArea.Clear();
return false;
}

unlockAch.SetProgress(PlayerControl.LocalPlayer, unlockAch.Goal);
__instance.AddChat(PlayerControl.LocalPlayer, $"Force unlocked: [{unlockAch.Title}]");
__instance.freeChatField.textArea.Clear();
return false;
}

var achievementId = parts[1];
if (!MiraAPI.Achievements.AchievementManager.TryGetAchievement(achievementId, out var achievement))
{
__instance.AddChat(PlayerControl.LocalPlayer, $"Achievement '{achievementId}' not found.");
__instance.freeChatField.textArea.Clear();
return false;
}

if (!achievement.IsUnlocked(PlayerControl.LocalPlayer))
{
__instance.AddChat(PlayerControl.LocalPlayer, $"You haven't unlocked '{achievement.Title}' yet. Use /achievement unlock {achievementId}");
__instance.freeChatField.textArea.Clear();
return false;
}

MiraAPI.Achievements.AchievementManager.SetEquippedTitle(PlayerControl.LocalPlayer, achievementId);
TitleSyncRpc.Send(PlayerControl.LocalPlayer.PlayerId, achievementId);
__instance.AddChat(PlayerControl.LocalPlayer, $"Title equipped: [{achievement.Title}]");
__instance.freeChatField.textArea.Clear();
return false;
}
}
96 changes: 96 additions & 0 deletions MiraAPI.Example/Achievements/AchievementTitleComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Linq;
using Il2CppInterop.Runtime.Injection;
using Reactor.Utilities.Attributes;
using TMPro;
using UnityEngine;

namespace MiraAPI.Example.Achievements;

[RegisterInIl2Cpp]
public class AchievementTitleComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
private TextMeshPro? _titleText;
private PlayerControl? _player;
private PoolablePlayer? _poolablePlayer;
private string? _lastTitleId;

static AchievementTitleComponent()
{
ClassInjector.RegisterTypeInIl2Cpp<AchievementTitleComponent>();
}

private void Awake()
{
if (!TryGetComponent(out _player))
{
TryGetComponent(out _poolablePlayer);
}

var origText = _player
? _player.cosmetics.nameText
: _poolablePlayer?.cosmetics.nameText;

if (origText == null)
{
return;
}

var go = new GameObject("AchievementTitle");
go.layer = gameObject.layer;
go.transform.SetParent(origText.transform.parent, false);

_titleText = go.AddComponent<TextMeshPro>();
_titleText.fontMaterial = origText.fontMaterial;
_titleText.outlineWidth = 0.2f;
_titleText.outlineColor = Color.black;
_titleText.transform.localPosition = new Vector3(0, _player ? 1.1f : 0.75f, -0.01f);
_titleText.fontSize = _player ? 2.5f : 1.8f;
_titleText.alignment = TextAlignmentOptions.Center;
}

private void Update()
{
if (_titleText == null)
{
return;
}

var playerRef = _player ?? (_poolablePlayer
? PlayerControl.AllPlayerControls.ToArray().FirstOrDefault(p => p.PlayerId == (byte)_poolablePlayer.ColorId)
: null);

if (playerRef == null)
{
return;
}

var equipped = MiraAPI.Achievements.AchievementManager.GetEquippedTitle(playerRef);
var currentId = equipped?.Id;

if (currentId == _lastTitleId)
{
return;
}

_lastTitleId = currentId;

if (equipped != null)
{
_titleText.text = $"[{equipped.Title}]";
_titleText.color = equipped.TitleColor;
}
else
{
_titleText.text = string.Empty;
}
}

private void OnDestroy()
{
if (_titleText != null)
{
Destroy(_titleText.gameObject);
}
}
}
38 changes: 38 additions & 0 deletions MiraAPI.Example/Achievements/ExampleAchievements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Linq;
using MiraAPI.Achievements;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Events.Vanilla.Player;

namespace MiraAPI.Example.Achievements;

public static class ExampleAchievements
{
public static readonly MadKillerAchievement MadKiller = new();
public static readonly FirstVictoryAchievement FirstWin = new();

public static void Register()
{
MiraAPI.Achievements.AchievementManager.RegisterAchievement(MadKiller);
MiraAPI.Achievements.AchievementManager.RegisterAchievement(FirstWin);

MiraEventManager.RegisterEventHandler<AfterMurderEvent>(OnMurder);
MiraEventManager.RegisterEventHandler<GameEndEvent>(OnGameEnd);
}

private static void OnMurder(AfterMurderEvent @event)
{
if (@event.Source.AmOwner)
{
MiraAPI.Achievements.AchievementManager.AddProgress(@event.Source, MadKiller.Id, 1);
}
}

private static void OnGameEnd(GameEndEvent @event)
{
foreach (var plr in PlayerControl.AllPlayerControls.ToArray().Where(p => !p.Data.IsDead && p.AmOwner))
{
MiraAPI.Achievements.AchievementManager.AddProgress(plr, FirstWin.Id, 1);
}
}
}
14 changes: 14 additions & 0 deletions MiraAPI.Example/Achievements/FirstVictoryAchievement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using MiraAPI.Achievements;
using UnityEngine;

namespace MiraAPI.Example.Achievements;

public class FirstVictoryAchievement : BaseAchievement
{
public override string Id => "first-win";
public override string Title => "First Victory";
public override string Description => "Win your first game.";
public override AchievementTier Tier => AchievementTier.Common;
public override int Goal => 1;
public override Color TitleColor => Color.green;
}
14 changes: 14 additions & 0 deletions MiraAPI.Example/Achievements/MadKillerAchievement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using MiraAPI.Achievements;
using UnityEngine;

namespace MiraAPI.Example.Achievements;

public class MadKillerAchievement : BaseAchievement
{
public override string Id => "mad-killer";
public override string Title => "Mad Killer";
public override string Description => "Kill 5 players in a single game.";
public override AchievementTier Tier => AchievementTier.Rare;
public override int Goal => 5;
public override Color TitleColor => Color.red;
}
12 changes: 12 additions & 0 deletions MiraAPI.Example/Achievements/TitlePatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using HarmonyLib;

namespace MiraAPI.Example.Achievements;

[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))]
public static class TitlePatch
{
public static void Postfix(PlayerControl __instance)
{
__instance.gameObject.AddComponent<AchievementTitleComponent>();
}
}
46 changes: 46 additions & 0 deletions MiraAPI.Example/Achievements/TitleSyncRpc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Hazel;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;

namespace MiraAPI.Example.Achievements;

[RegisterCustomRpc(100)]
public class TitleSyncRpc(ExamplePlugin plugin, uint id) : PlayerCustomRpc<ExamplePlugin, TitleSyncRpc.Data>(plugin, id)
{
public class Data
{
public byte PlayerId { get; set; }
public string AchievementId { get; set; } = "-";
}

public override RpcLocalHandling LocalHandling => RpcLocalHandling.After;

public override void Write(MessageWriter writer, Data data)
{
writer.Write(data.PlayerId);
writer.Write(data.AchievementId);
}

public override Data Read(MessageReader reader)
{
return new Data
{
PlayerId = reader.ReadByte(),
AchievementId = reader.ReadString(),
};
}

public override void Handle(PlayerControl innerNetObject, Data data)
{
MiraAPI.Achievements.AchievementManager.SetEquippedTitleById(data.PlayerId, data.AchievementId);
}

public static void Send(byte playerId, string? achievementId)
{
Rpc<TitleSyncRpc>.Instance.SendTo(PlayerControl.LocalPlayer, -1, new Data
{
PlayerId = playerId,
AchievementId = achievementId ?? "-",
});
}
}
6 changes: 5 additions & 1 deletion MiraAPI.Example/ExamplePlugin.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Linq;
using System.Linq;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.Achievements;
using MiraAPI.Example.Achievements;
using MiraAPI.PluginLoading;
using Reactor;
using Reactor.Networking;
Expand All @@ -22,6 +24,8 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
public ConfigFile GetConfigFile() => Config;
public override void Load()
{
MiraAPI.Achievements.AchievementManager.Initialize(Id);
ExampleAchievements.Register();
ExampleEventHandlers.Initialize();
Harmony.PatchAll();
}
Expand Down
Loading