Skip to content

Commit

Permalink
Added custom Action Icons (GameAPI.AddActionIcon)
Browse files Browse the repository at this point in the history
  • Loading branch information
ManlyMarco committed Mar 16, 2021
1 parent 424e0d8 commit 320a096
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 6 deletions.
2 changes: 2 additions & 0 deletions doc/KKAPI.MainGame.md
Expand Up @@ -17,6 +17,7 @@ Static Methods

| Type | Name | Summary |
| --- | --- | --- |
| `void` | AddActionIcon(`Int32` mapNo, `Vector3` position, `Sprite` iconOn, `Sprite` iconOff, `Action` onOpen, `Action<TriggerEnterExitEvent>` onCreated = null) | Register a new action icon in roaming mode (like the icons for training/studying, club report screen, peeping). |
| `IEnumerable<GameCustomFunctionController>` | GetBehaviours() | Get all registered behaviours for the game. |
| `GameCustomFunctionController` | GetRegisteredBehaviour(`String` extendedDataId) | Get the first controller that was registered with the specified extendedDataId. |
| `GameCustomFunctionController` | GetRegisteredBehaviour(`Type` controllerType) | Get the first controller that was registered with the specified extendedDataId. |
Expand Down Expand Up @@ -93,6 +94,7 @@ Static Methods
| `IEnumerable<ChaFileControl>` | GetRelatedChaFiles(this `Heroine` heroine) | Get ChaFiles that are related to this heroine. Warning: It might not return some copies. |
| `IEnumerable<ChaFileControl>` | GetRelatedChaFiles(this `Player` player) | Get ChaFiles that are related to this heroine. Warning: It might not return some copies. |
| `Boolean` | IsShowerPeeping(this `HFlag` hFlag) | Returns true if the H scene is peeping in the shower. Use `HFlag.mode` to get info on what mode the H scene is in. |
| `void` | SetIsCursorLock(this `ActionScene` actScene, `Boolean` value) | Set the value of isCursorLock (setter is private by default). Used to regain mouse cursor during roaming mode. Best used together with setting `UnityEngine.Time.timeScale` to 0 to pause the game. |


## `GameSaveLoadEventArgs`
Expand Down
5 changes: 3 additions & 2 deletions src/KKAPI/KKAPI.csproj
Expand Up @@ -81,6 +81,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="MainGame\CustomActionIcon.cs" />
<Compile Include="Maker\AccessoryCopyEventArgs.cs" />
<Compile Include="MainGame\GameApi.cs" />
<Compile Include="MainGame\GameAPI.Hooks.cs" />
Expand Down Expand Up @@ -114,14 +115,14 @@ IF EXIST $(SolutionDir)PostBuild.bat CALL "$(SolutionDir)PostBuild.bat" $(Target
</PropertyGroup>
<Error Condition="!Exists('..\packages\IllusionLibs.BepInEx.Harmony.2.2.0.1\build\IllusionLibs.BepInEx.Harmony.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.BepInEx.Harmony.2.2.0.1\build\IllusionLibs.BepInEx.Harmony.targets'))" />
<Error Condition="!Exists('..\packages\IllusionLibs.Koikatu.Assembly-CSharp.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.Koikatu.Assembly-CSharp.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp.targets'))" />
<Error Condition="!Exists('..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets'))" />
<Error Condition="!Exists('..\packages\IllusionLibs.Koikatu.TextMeshPro.2019.4.27.2\build\IllusionLibs.Koikatu.TextMeshPro.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.Koikatu.TextMeshPro.2019.4.27.2\build\IllusionLibs.Koikatu.TextMeshPro.targets'))" />
<Error Condition="!Exists('..\packages\IllusionLibs.Koikatu.UnityEngine.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.Koikatu.UnityEngine.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.targets'))" />
<Error Condition="!Exists('..\packages\IllusionLibs.Koikatu.UnityEngine.UI.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.UI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.Koikatu.UnityEngine.UI.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.UI.targets'))" />
<Error Condition="!Exists('..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets'))" />
</Target>
<Import Project="..\packages\IllusionLibs.Koikatu.Assembly-CSharp.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp.targets" Condition="Exists('..\packages\IllusionLibs.Koikatu.Assembly-CSharp.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp.targets')" />
<Import Project="..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets" Condition="Exists('..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets')" />
<Import Project="..\packages\IllusionLibs.Koikatu.TextMeshPro.2019.4.27.2\build\IllusionLibs.Koikatu.TextMeshPro.targets" Condition="Exists('..\packages\IllusionLibs.Koikatu.TextMeshPro.2019.4.27.2\build\IllusionLibs.Koikatu.TextMeshPro.targets')" />
<Import Project="..\packages\IllusionLibs.Koikatu.UnityEngine.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.targets" Condition="Exists('..\packages\IllusionLibs.Koikatu.UnityEngine.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.targets')" />
<Import Project="..\packages\IllusionLibs.Koikatu.UnityEngine.UI.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.UI.targets" Condition="Exists('..\packages\IllusionLibs.Koikatu.UnityEngine.UI.5.6.2.2\build\IllusionLibs.Koikatu.UnityEngine.UI.targets')" />
<Import Project="..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets" Condition="Exists('..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.2\build\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.targets')" />
</Project>
132 changes: 132 additions & 0 deletions src/KKAPI/MainGame/CustomActionIcon.cs
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using ActionGame;
using ActionGame.Chara;
using HarmonyLib;
using Illusion.Component;
using Manager;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using Object = UnityEngine.Object;

namespace KKAPI.MainGame
{
internal static class CustomActionIcon
{
private sealed class ActionIconEntry
{
public readonly Sprite IconOff, IconOn;
public readonly int MapNo;
public readonly Vector3 Position;
public readonly Action OnOpen;
public readonly Action<TriggerEnterExitEvent> OnCreated;

public ActionIconEntry(int mapNo, Vector3 position, Sprite iconOn, Sprite iconOff, Action onOpen, Action<TriggerEnterExitEvent> onCreated)
{
IconOff = iconOff;
OnOpen = onOpen;
OnCreated = onCreated;
IconOn = iconOn;
MapNo = mapNo;
Position = position;
}
}

private static readonly List<ActionIconEntry> _entries = new List<ActionIconEntry>();

public static void AddActionIcon(int mapNo, Vector3 position, Sprite iconOn, Sprite iconOff, Action onOpen, Action<TriggerEnterExitEvent> onCreated = null)
{
if (iconOn == null) throw new ArgumentNullException(nameof(iconOn));
if (iconOff == null) throw new ArgumentNullException(nameof(iconOff));
if (onOpen == null) throw new ArgumentNullException(nameof(onOpen));

Object.DontDestroyOnLoad(iconOn);
Object.DontDestroyOnLoad(iconOff);

var entry = new ActionIconEntry(mapNo, position, iconOn, iconOff, onOpen, onCreated);
_entries.Add(entry);
}

[HarmonyPostfix]
[HarmonyPatch(typeof(ActionMap), "Reserve")]
private static void OnMapChangedHook(ActionMap __instance)
{
if (__instance.mapRoot == null || __instance.isMapLoading) return;

var created = 0;

foreach (var iconEntry in _entries)
{
if (iconEntry.MapNo == __instance.no)
{
try
{
SpawnActionPoint(iconEntry);
created++;
}
catch (Exception e)
{
KoikatuAPI.Logger.LogError($"Failed to created custom action point on map no {__instance.no} at {iconEntry.Position}\n{e}");
}
}
}

if (created > 0)
KoikatuAPI.Logger.LogDebug($"Created {created} custom action points on map no {__instance.no}");
}

private static void SpawnActionPoint(ActionIconEntry iconEntry)
{
var inst = CommonLib.LoadAsset<GameObject>("map/playeractionpoint/00.unity3d", "PlayerActionPoint_05", true);
var parent = GameObject.Find("Map/ActionPoints");
inst.transform.SetParent(parent.transform, true);

var pap = inst.GetComponentInChildren<PlayerActionPoint>();
var iconRootObject = pap.gameObject;
var iconRootTransform = pap.transform;
Object.DestroyImmediate(pap, false);

iconRootTransform.position = iconEntry.Position;

var evt = iconRootObject.AddComponent<TriggerEnterExitEvent>();
var animator = iconRootObject.GetComponentInChildren<Animator>();
var rendererIcon = iconRootObject.GetComponentInChildren<SpriteRenderer>();
rendererIcon.sprite = iconEntry.IconOff;
var playerInRange = false;
evt.onTriggerEnter += c =>
{
if (!c.CompareTag("Player")) return;
playerInRange = true;
animator.Play("icon_action");
rendererIcon.sprite = iconEntry.IconOn;
c.GetComponent<Player>().actionPointList.Add(evt);
};
evt.onTriggerExit += c =>
{
if (!c.CompareTag("Player")) return;
playerInRange = false;
animator.Play("icon_stop");
rendererIcon.sprite = iconEntry.IconOff;
c.GetComponent<Player>().actionPointList.Remove(evt);
};

var player = Singleton<Game>.Instance.actScene.Player;
evt.UpdateAsObservable()
.Subscribe(_ =>
{
// Hide in H scenes and other places
var isVisible = Singleton<Game>.IsInstance() && !Singleton<Game>.Instance.IsRegulate(true);
if (rendererIcon.enabled != isVisible)
rendererIcon.enabled = isVisible;
// Check if player clicked this point
if (isVisible && playerInRange && ActionInput.isAction && !player.isActionNow)
iconEntry.OnOpen();
})
.AddTo(evt);

iconEntry.OnCreated?.Invoke(evt);
}
}
}
4 changes: 2 additions & 2 deletions src/KKAPI/MainGame/GameAPI.Hooks.cs
Expand Up @@ -9,9 +9,9 @@ public static partial class GameAPI
{
private class Hooks
{
public static void SetupHooks()
public static void SetupHooks(Harmony hi)
{
Harmony.CreateAndPatchAll(typeof(Hooks));
hi.PatchAll(typeof(Hooks));
}

[HarmonyPostfix]
Expand Down
27 changes: 25 additions & 2 deletions src/KKAPI/MainGame/GameApi.cs
Expand Up @@ -4,7 +4,10 @@
using System.Linq;
using ActionGame;
using BepInEx.Bootstrap;
using HarmonyLib;
using Illusion.Component;
using KKAPI.Studio;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.SceneManagement;

Expand All @@ -22,7 +25,7 @@ public static partial class GameAPI

// navigating these scenes in order triggers OnNewGame
private static readonly IList<string> _newGameDetectionScenes =
new List<string>(new[] {"Title", "EntryPlayer", "ClassRoomSelect", "Action"});
new List<string>(new[] { "Title", "EntryPlayer", "ClassRoomSelect", "Action" });

private static int _newGameDetectionIndex = -1;

Expand Down Expand Up @@ -111,11 +114,31 @@ public static void RegisterExtraBehaviour<T>(string extendedDataId) where T : Ga
_registeredHandlers.Add(newBehaviour, extendedDataId);
}

/// <summary>
/// Register a new action icon in roaming mode (like the icons for training/studying, club report screen, peeping).
/// </summary>
/// <param name="mapNo">Identification number of the map the icon should be spawned on</param>
/// <param name="position">Position of the icon. All default icons are spawned at y=0, but different heights work fine to a degree.
/// You can figure out the position by walking to it and getting the player position with RUE.</param>
/// <param name="iconOn">Icon shown when player is in range to click it.</param>
/// <param name="iconOff">Icon shown when player is out of range.</param>
/// <param name="onOpen">Action triggered when player clicks the icon (If you want to open your own menu, use <see cref="GameExtensions.SetIsCursorLock"/>
/// to enable mouse cursor and hide the action icon to prevent it from being clicked again.).</param>
/// <param name="onCreated">Optional action to run after the icon is created.
/// Use to attach extra code to the icon, e.g. by using <see cref="ObservableTriggerExtensions.UpdateAsObservable(Component)"/> and similar methods.</param>
public static void AddActionIcon(int mapNo, Vector3 position, Sprite iconOn, Sprite iconOff, Action onOpen, Action<TriggerEnterExitEvent> onCreated = null)
{
if (StudioAPI.InsideStudio) return;
CustomActionIcon.AddActionIcon(mapNo, position, iconOn, iconOff, onOpen, onCreated);
}

internal static void Init(bool insideStudio)
{
if (insideStudio) return;

Hooks.SetupHooks();
var hi = new Harmony(typeof(GameAPI).FullName);
hi.PatchAll(typeof(Hooks));
hi.PatchAll(typeof(CustomActionIcon));

_functionControllerContainer = new GameObject("GameCustomFunctionController Zoo");
_functionControllerContainer.transform.SetParent(Chainloader.ManagerObject.transform, false);
Expand Down

0 comments on commit 320a096

Please sign in to comment.