revival/game/Assets/Scripts/Dialogue/DialogueSystem.cs

216 lines
6.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using FMOD;
using FMOD.Studio;
using FMODUnity;
using Ktyl.Util;
using UnityEngine;
using UnityEngine.Animations.Rigging;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Users;
using UnityEngine.Networking;
using Debug = UnityEngine.Debug;
using STOP_MODE = FMOD.Studio.STOP_MODE;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu(menuName = "KernelPanic/Dialogue/Dialogue System")]
public partial class DialogueSystem : ScriptableObject
{
[SerializeField] private DialogueSettings _settings;
// https://stackoverflow.com/questions/2282476/actiont-vs-delegate-event
public event EventHandler<DialogueLine> onDialogueLine;
// a list of dialogue keys that have already been spoken
private readonly List<string> _usedClips = new List<string>();
private EVENT_CALLBACK _dialogueCallback;
private EventInstance _currentInstance;
private string _currentControlScheme;
private void OnEnable()
{
_usedClips.Clear();
_dialogueCallback = DialogueEventCallback;
}
[AOT.MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
private static RESULT DialogueEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
{
var instance = new EventInstance(instancePtr);
// retrieve user data
instance.getUserData(out IntPtr stringPtr);
// get string obejct
var stringHandle = GCHandle.FromIntPtr(stringPtr);
var key = stringHandle.Target as string;
switch (type)
{
case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
{
MODE soundMode = MODE.LOOP_NORMAL | MODE.CREATECOMPRESSEDSAMPLE | MODE.NONBLOCKING;
var parameter =
(PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr,
typeof(PROGRAMMER_SOUND_PROPERTIES));
if (key.Contains("."))
{
Sound dialogueSound;
var soundResult = RuntimeManager.CoreSystem.createSound(Application.streamingAssetsPath + "/" + key,
soundMode, out dialogueSound);
if (soundResult == RESULT.OK)
{
parameter.sound = dialogueSound.handle;
parameter.subsoundIndex = -1;
Marshal.StructureToPtr(parameter, parameterPtr, false);
}
}
else
{
SOUND_INFO dialogueSoundInfo;
var keyResult = RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
if (keyResult != RESULT.OK)
{
break;
}
Sound dialogueSound;
var soundResult = RuntimeManager.CoreSystem.createSound(dialogueSoundInfo.name_or_data,
soundMode | dialogueSoundInfo.mode, ref dialogueSoundInfo.exinfo, out dialogueSound);
if (soundResult == RESULT.OK)
{
parameter.sound = dialogueSound.handle;
parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
Marshal.StructureToPtr(parameter, parameterPtr, false);
}
}
break;
}
case EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
{
var parameter =
(PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr,
typeof(PROGRAMMER_SOUND_PROPERTIES));
var sound = new Sound(parameter.sound);
sound.release();
break;
}
case EVENT_CALLBACK_TYPE.DESTROYED:
{
stringHandle.Free();
break;
}
}
return RESULT.OK;
}
// noRepeat locks this key off from further use. further attempts to use the key will be discarded
public void PlayLine(string key, bool noRepeat = true)
{
if (noRepeat)
{
if (_usedClips.Contains(key)) return;
_usedClips.Add(key);
}
if ( _currentInstance.isValid() )
{
_currentInstance.stop( STOP_MODE.IMMEDIATE );
_currentInstance.release();
}
_currentInstance = RuntimeManager.CreateInstance(_settings.RadioDialogueKey);
GCHandle stringHandle = GCHandle.Alloc(key, GCHandleType.Pinned);
_currentInstance.setUserData(GCHandle.ToIntPtr(stringHandle));
DialogueLine dl;
var line = DialogueDatabase.ReadDialogue(key);
// process templates in string
line = ProcessTemplates(line);
dl.text = line;
var clip = _settings.GetDialogueClip(key);
dl.duration = clip.length;
_currentInstance.setCallback(_dialogueCallback);
_currentInstance.start();
onDialogueLine?.Invoke(this, dl);
}
private string ProcessTemplates(string text)
{
var spriteIndices = _currentControlScheme == InputSchemes.GAMEPAD
? _settings.GamepadInputPrompts
: _settings.KeyboardInputPrompts;
text = text.Replace("[BLINK]", $"<sprite index={spriteIndices.blink}>");
text = text.Replace("[BOOST]", $"<sprite index={spriteIndices.boost}>");
text = text.Replace("[TIME_FREEZE]", $"<sprite index={spriteIndices.timeFreeze}>");
return text;
}
public void UpdateControlPrompts(PlayerInput playerInput)
{
var controlScheme = playerInput.currentControlScheme;
if (controlScheme != InputSchemes.PCMR && controlScheme != InputSchemes.GAMEPAD)
{
Debug.LogError($"could not set unknown control scheme {controlScheme}");
return;
}
_currentControlScheme = playerInput.currentControlScheme;
}
}
public struct DialogueLine
{
public string text;
public float duration;
}
#region Editor
#if UNITY_EDITOR
[CustomEditor(typeof(DialogueSystem))]
public class DialogueSystemEditor : Editor
{
private DialogueSystem _dialogue;
private string _key;
private void OnEnable()
{
_dialogue = target as DialogueSystem;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_key = EditorGUILayout.TextField("key", _key);
if (GUILayout.Button("Play Line"))
{
_dialogue.PlayLine(_key);
}
}
}
#endif
#endregion