HeavenStudioPlus/Assets/Scripts/Minigames.cs
minenice55 fd1df6687e Auto-Swing (#827)
* BurstLinq

make BGM resync when changing pitch (to test)

* autoswing

some game implementations, most games already work fine

* more game tweaks

* 16th note swing

more game fixes
make pitch change resync optional in the API

* suppress some common warnings

* Update Credits.txt
2024-04-07 04:54:06 +00:00

1419 lines
No EOL
86 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using HeavenStudio.Util;
using HeavenStudio.Editor.Track;
using HeavenStudio.Games;
using Jukebox;
using SatorImaging.UnitySourceGenerator;
using System;
using System.IO;
using System.Linq;
using BurstLinq;
using UnityEngine.Assertions.Must;
using Newtonsoft.Json.Linq;
namespace HeavenStudio
{
[UnitySourceGenerator(typeof(MinigameLoaderGenerator), OverwriteIfFileExists = true)]
public partial class Minigames
{
public enum RecommendedControlStyle
{
Any,
Pad,
Touch,
Baton,
}
public static void InitPreprocessor()
{
RiqBeatmap.OnUpdateBeatmap += PreProcessBeatmap;
RiqFileHandler.AudioConverter = JukeboxAudioConverter;
}
readonly public static Dictionary<string, object> propertiesModel = new()
{
// mapper set properties? (future: use this to flash the button)
{"propertiesmodified", false},
////// CATEGORY 1: SONG INFO
// general chart info
{"remixtitle", "New Remix"}, // chart name
{"remixauthor", "Your Name"}, // charter's name
{"remixdesc", "Remix Description"}, // chart description
{"remixlevel", 1}, // chart difficulty (maybe offer a suggestion but still have the mapper determine it)
{"remixtempo", 120f}, // avg. chart tempo
{"remixtags", ""}, // chart tags
{"icontype", 0}, // chart icon (presets, custom - future)
{"iconres", new EntityTypes.Resource(EntityTypes.Resource.ResourceType.Image, "Images/Select/", "Icon")}, // custom icon location (future)
{"challengetype", 0}, // perfect challenge type
{"accessiblewarning", false}, // epilepsy warning
{"playstyle", RecommendedControlStyle.Any}, // recommended control style
// chart song info
{"idolgenre", "Song Genre"}, // song genre
{"idolsong", "Song Name"}, // song name
{"idolcredit", "Artist"}, // song artist
////// CATEGORY 2: PROLOGUE AND EPILOGUE
// chart prologue
{"prologuetype", 0}, // prologue card animation (future)
{"prologuecaption", "Remix"}, // prologue card sub-title (future)
// chart results screen messages
{"resultcaption", "Rhythm League Notes"}, // result screen header
{"resultcommon_hi", "Good rhythm."}, // generic "Superb" message (one-liner)
{"resultcommon_ok", "Eh. Passable."}, // generic "OK" message (one-liner)
{"resultcommon_ng", "Try harder next time."}, // generic "Try Again" message (one-liner)
{"resultcat0_hi", "You show strong fundamentals."}, // "Superb" message for input category 0 "normal" (two-liner)
{"resultcat0_ng", "Work on your fundamentals."}, // "Try Again" message for input category 0 "normal" (two-liner)
{"resultcat1_hi", "You kept the beat well."}, // "Superb" message for input category 1 "keep" (two-liner)
{"resultcat1_ng", "You had trouble keeping the beat."}, // "Try Again" message for input category 1 "keep" (two-liner)
{"resultcat2_hi", "You had great aim."}, // "Superb" message for input category 2 "aim" (two-liner)
{"resultcat2_ng", "Your aim was a little shaky."}, // "Try Again" message for input category 2 "aim" (two-liner)
{"resultcat3_hi", "You followed the example well."}, // "Superb" message for input category 3 "repeat" (two-liner)
{"resultcat3_ng", "Next time, follow the example better."}, // "Try Again" message for input category 3 "repeat" (two-liner)
{"epilogue_hi", "Superb"}, // epilogue "Superb" message
{"epilogue_ok", "OK"}, // epilogue "OK" message
{"epilogue_ng", "Try Again"}, // epilogue "Try Again" message
{"epilogue_hi_res", new EntityTypes.Resource(EntityTypes.Resource.ResourceType.Image, "Images/Epilogue/", "Hi")}, // epilogue "Superb" image resource path
{"epilogue_ok_res", new EntityTypes.Resource(EntityTypes.Resource.ResourceType.Image, "Images/Epilogue/", "Ok")}, // epilogue "OK" image resource path
{"epilogue_ng_res", new EntityTypes.Resource(EntityTypes.Resource.ResourceType.Image, "Images/Epilogue/", "Ng")}, // epilogue "Try Again" image resource path
};
readonly static Dictionary<string, object> tempoChangeModel = new()
{
{"tempo", 120f},
{"swing", 0f},
{"swingDivision", 1f},
{"timeSignature", new Vector2(4, 4)},
};
readonly static Dictionary<string, object> volumeChangeModel = new()
{
{"volume", 1f},
{"fade", Util.EasingFunction.Ease.Instant},
};
readonly static Dictionary<string, object> sectionMarkModel = new()
{
{"sectionName", ""},
{"startPerfect", false},
{"weight", 1f},
{"category", 0},
};
static void PreProcessSpecialEntity(RiqEntity e, Dictionary<string, object> model)
{
foreach (var t in model)
{
string propertyName = t.Key;
Type type = t.Value.GetType();
if (!e.dynamicData.ContainsKey(propertyName))
{
e.CreateProperty(propertyName, t.Value);
}
Type pType = e[propertyName].GetType();
if (pType != type)
{
try
{
if (type.IsEnum)
{
if (pType == typeof(string))
e.dynamicData[propertyName] = (int)Enum.Parse(type, (string)e[propertyName]);
else
e.dynamicData[propertyName] = (int)e[propertyName];
}
else if (pType == typeof(Newtonsoft.Json.Linq.JObject))
e[propertyName] = e[propertyName].ToObject(type);
else
e[propertyName] = Convert.ChangeType(e[propertyName], type);
}
catch
{
Debug.LogWarning($"Could not convert {propertyName} to {type}! Using default value...");
// use default value
e.CreateProperty(propertyName, t.Value);
}
}
}
}
public static string JukeboxAudioConverter(string filePath, AudioType audioType, string specificType)
{
string wavCachePath = Path.Combine(Application.temporaryCachePath, "savewav");
if (Directory.Exists(wavCachePath))
{
Directory.Delete(wavCachePath, true);
}
if (!Directory.Exists(wavCachePath))
{
Directory.CreateDirectory(wavCachePath);
}
if (audioType == AudioType.MPEG)
{
Debug.Log($"mp3 loaded, Converting {filePath} to wav...");
// convert mp3 to wav
// import the mp3 as an audioclip
string url = "file://" + filePath;
using (var www = UnityWebRequestMultimedia.GetAudioClip(url, audioType))
{
www.SendWebRequest();
while (!www.isDone) { }
if (www.result == UnityWebRequest.Result.ConnectionError)
{
Debug.LogError($"Could not load audio file {filePath}! Error: {www.error}");
return filePath;
}
AudioClip clip = DownloadHandlerAudioClip.GetContent(www);
string fileName = Path.GetFileNameWithoutExtension(filePath);
SavWav.Save(fileName, clip, true);
filePath = Path.Combine(wavCachePath, $"{fileName}.wav");
clip = null;
}
}
return filePath;
}
/// <summary>
/// processes an riq beatmap after it is loaded
/// </summary>
public static RiqBeatmapData? PreProcessBeatmap(string version, RiqBeatmapData data)
{
Debug.Log("Preprocessing beatmap...");
Minigames.Minigame game;
Minigames.GameAction action;
System.Type type, pType;
if (EventCaller.instance != null)
{
string[] split;
foreach (var e in data.entities)
{
split = e.datamodel.Split('/');
var gameName = split[0];
var actionName = split[1];
game = EventCaller.instance.GetMinigame(gameName);
if (game == null)
{
Debug.LogWarning($"Unknown game {gameName} found in remix.json! Adding game...");
game = new Minigames.Minigame(gameName, gameName.DisplayName() + " \n<color=#eb5454>[inferred from remix.json]</color>", "", false, false, new List<Minigames.GameAction>(), inferred: true);
EventCaller.instance.minigames.Add(gameName, game);
if (Editor.Editor.instance != null)
Editor.Editor.instance.AddIcon(game);
}
action = EventCaller.instance.GetGameAction(game, actionName);
if (action == null)
{
Debug.LogWarning($"Unknown action {gameName}/{actionName} found in remix.json! Adding action...");
var parameters = new List<Minigames.Param>();
foreach (var item in e.dynamicData)
{
Debug.Log($"k: {item.Key}, v: {item.Value}");
if (item.Key == "track")
continue;
if (item.Value == null)
continue;
var value = item.Value;
if (value.GetType() == typeof(long))
value = new EntityTypes.Integer(int.MinValue, int.MaxValue, (int)value);
else if (value.GetType() == typeof(double))
value = new EntityTypes.Float(float.NegativeInfinity, float.PositiveInfinity, (float)value);
parameters.Add(new Minigames.Param(item.Key, value, item.Key.DisplayName(), "[inferred from remix.json]"));
}
action = new Minigames.GameAction(actionName, actionName.DisplayName(), e.length, true, parameters);
game.actions.Add(action);
}
//check each param of the action
if (action.parameters != null)
{
foreach (var param in action.parameters)
{
type = param.parameter.GetType();
//add property if it doesn't exist
if (!e.dynamicData.ContainsKey(param.propertyName))
{
Debug.LogWarning($"Property {param.propertyName} does not exist in the entity's dynamic data! Adding...");
if (type == typeof(EntityTypes.Integer))
e.dynamicData.Add(param.propertyName, ((EntityTypes.Integer)param.parameter).val);
else if (type == typeof(EntityTypes.Float))
e.dynamicData.Add(param.propertyName, ((EntityTypes.Float)param.parameter).val);
else if (type == typeof(EntityTypes.Note))
e.dynamicData.Add(param.propertyName, ((EntityTypes.Note)param.parameter).val);
else if (type.IsEnum)
e.dynamicData.Add(param.propertyName, (int)param.parameter);
else
e.dynamicData.Add(param.propertyName, Convert.ChangeType(param.parameter, type));
continue;
}
pType = e[param.propertyName].GetType();
if (pType != type)
{
try
{
if (type == typeof(EntityTypes.Integer) || type == typeof(EntityTypes.Note))
e.dynamicData[param.propertyName] = (int)e[param.propertyName];
else if (type == typeof(EntityTypes.Float))
e.dynamicData[param.propertyName] = (float)e[param.propertyName];
else if (type == typeof(EntityTypes.Button))
e.dynamicData[param.propertyName] = (string)e[param.propertyName];
else if (type == typeof(EntityTypes.Dropdown)) {
JValue value = e[param.propertyName]["value"];
JArray values = e[param.propertyName]["Values"];
e.dynamicData[param.propertyName] = new EntityTypes.DropdownObj((int)value, values.Select(x => (string)x).ToList());
}
else if (type == typeof(EntityTypes.Resource))
e.dynamicData[param.propertyName] = (EntityTypes.Resource)e[param.propertyName];
else if (type.IsEnum)
{
if (pType == typeof(string))
e.dynamicData[param.propertyName] = (int)Enum.Parse(type, (string)e[param.propertyName]);
else
e.dynamicData[param.propertyName] = (int)e[param.propertyName];
}
else if (pType == typeof(Newtonsoft.Json.Linq.JObject))
e.dynamicData[param.propertyName] = e[param.propertyName].ToObject(type);
else
e.dynamicData[param.propertyName] = Convert.ChangeType(e[param.propertyName], type);
}
catch
{
Debug.LogWarning($"Could not convert {param.propertyName} to {type}! Using default value...");
// GlobalGameManager.ShowErrorMessage("Warning", $"Could not convert {e.datamodel}/{param.propertyName} to {type}! This will be loaded using the default value, so chart may be unstable.");
// use default value
if (type == typeof(EntityTypes.Integer))
e.dynamicData[param.propertyName] = ((EntityTypes.Integer)param.parameter).val;
else if (type == typeof(EntityTypes.Note))
e.dynamicData[param.propertyName] = ((EntityTypes.Note)param.parameter).val;
else if (type == typeof(EntityTypes.Float))
e.dynamicData[param.propertyName] = ((EntityTypes.Float)param.parameter).val;
else if (type.IsEnum && param.propertyName != "ease")
e.dynamicData[param.propertyName] = (int)param.parameter;
else
e.dynamicData[param.propertyName] = Convert.ChangeType(param.parameter, type);
}
}
}
}
}
}
foreach (var tempo in data.tempoChanges)
{
PreProcessSpecialEntity(tempo, tempoChangeModel);
}
if (data.tempoChanges[0]["tempo"] <= 0)
{
data.tempoChanges[0]["tempo"] = 120;
}
foreach (var vol in data.volumeChanges)
{
PreProcessSpecialEntity(vol, volumeChangeModel);
}
foreach (var section in data.beatmapSections)
{
PreProcessSpecialEntity(section, sectionMarkModel);
}
//go thru each property of the model beatmap and add any missing keyvalue pair
foreach (var prop in propertiesModel)
{
var mType = propertiesModel[prop.Key].GetType();
if (!data.properties.ContainsKey(prop.Key))
{
data.properties.Add(prop.Key, prop.Value);
}
else
{
// convert enums to the intended enum type
if (mType.IsEnum)
{
if (data.properties[prop.Key].GetType() == typeof(string))
data.properties[prop.Key] = Enum.Parse(mType, (string)data.properties[prop.Key]);
else
data.properties[prop.Key] = Enum.ToObject(mType, data.properties[prop.Key]);
}
// convert all JObjects to their respective types
else if (data.properties[prop.Key].GetType() == typeof(Newtonsoft.Json.Linq.JObject))
{
data.properties[prop.Key] = (data.properties[prop.Key] as Newtonsoft.Json.Linq.JObject).ToObject(mType);
}
}
}
return data;
}
public class Minigame
{
public string name;
public string displayName;
public string color;
public string splitColorL;
public string splitColorR;
public bool hidden;
public bool fxOnly;
public List<GameAction> actions = new List<GameAction>();
public uint? chronologicalSortKey;
// Yin: basically i figure this should just be whatever index number the minigame is
// in its game of origin. So, basically, like, if we're talking Rhythm Heaven DS, Built to
// Scale would be 1, then Fillbots would be 2, and so on. If it's an endless game, add 100.
// If it's a rhythm toy, add 200. If it's a 2-Player endless game, add 300. If it's a credits
// game... IDK, I guess just set it to 99. It works. If the game isn't a RH original then just
// put the date in YYYYMMDD format. Oh, and if it's a practice game set it to 0.
public List<string> tags;
public string defaultLocale = "en";
public string wantAssetBundle = null;
public List<string> supportedLocales;
public bool inferred;
public bool usesAssetBundle => wantAssetBundle is not null or "";
public bool hasLocales => supportedLocales.Count > 0;
public bool AssetsLoaded => ((hasLocales && localeLoaded && currentLoadedLocale == defaultLocale) || (!hasLocales)) && commonLoaded && loadComplete;
public bool AlreadyLoading => alreadyLoading;
public bool SequencesPreloaded => soundSequences != null;
public string LoadableName => inferred ? "noGame" : name;
public GameObject LoadedPrefab => loadedPrefab;
private AssetBundle bundleCommon = null;
private bool commonLoaded = false;
private bool commonPreloaded = false;
private string currentLoadedLocale = "";
private AssetBundle bundleLocalized = null;
private bool localeLoaded = false;
private bool localePreloaded = false;
private GameObject loadedPrefab = null;
bool loadComplete = false;
private SoundSequence.SequenceKeyValue[] soundSequences = null;
public SoundSequence.SequenceKeyValue[] LoadedSoundSequences
{
get => soundSequences;
set => soundSequences = value;
}
public Minigame(string name, string displayName, string color, bool hidden, bool fxOnly, List<GameAction> actions, List<string> tags = null, string wantAssetBundle = null, string defaultLocale = "en", List<string> supportedLocales = null, bool inferred = false, uint? chronologicalSortKey = null)
{
this.name = name;
this.displayName = displayName;
this.color = color;
this.actions = actions;
this.hidden = hidden;
this.fxOnly = fxOnly;
this.tags = tags ?? new List<string>();
this.wantAssetBundle = wantAssetBundle;
this.defaultLocale = defaultLocale;
this.supportedLocales = supportedLocales ?? new List<string>();
this.inferred = inferred;
this.splitColorL = null;
this.splitColorR = null;
this.chronologicalSortKey = chronologicalSortKey;
}
public Minigame(string name, string displayName, string color, string splitColorL, string splitColorR, bool hidden, bool fxOnly, List<GameAction> actions, List<string> tags = null, string wantAssetBundle = null, string defaultLocale = "en", List<string> supportedLocales = null, bool inferred = false, uint? chronologicalSortKey = null)
{
this.name = name;
this.displayName = displayName;
this.color = color;
this.actions = actions;
this.hidden = hidden;
this.fxOnly = fxOnly;
this.tags = tags ?? new List<string>();
this.wantAssetBundle = wantAssetBundle;
this.defaultLocale = defaultLocale;
this.supportedLocales = supportedLocales ?? new List<string>();
this.inferred = inferred;
this.splitColorL = splitColorL;
this.splitColorR = splitColorR;
this.chronologicalSortKey = chronologicalSortKey;
}
public AssetBundle GetLocalizedAssetBundle()
{
if (bundleLocalized != null && !localeLoaded)
{
bundleLocalized.Unload(true);
bundleLocalized = null;
localeLoaded = false;
localePreloaded = false;
}
if (!hasLocales) return null;
if (!usesAssetBundle) return null;
if (bundleLocalized == null || currentLoadedLocale != defaultLocale) //TEMPORARY: use the game's default locale until we add localization support
{
if (localeLoaded) return bundleLocalized;
// TODO: try/catch for missing assetbundles
currentLoadedLocale = defaultLocale;
bundleLocalized = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, wantAssetBundle + "/locale." + defaultLocale));
localeLoaded = true;
}
return bundleLocalized;
}
public AssetBundle GetCommonAssetBundle()
{
if (bundleCommon != null && !commonLoaded)
{
bundleCommon.Unload(true);
bundleCommon = null;
commonLoaded = false;
commonPreloaded = false;
}
if (commonLoaded) return bundleCommon;
if (!usesAssetBundle) return null;
if (bundleCommon == null)
{
// TODO: try/catch for missing assetbundles
bundleCommon = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, wantAssetBundle + "/common"));
commonLoaded = true;
}
return bundleCommon;
}
bool alreadyLoading = false;
public async UniTaskVoid LoadAssetsAsync()
{
if (alreadyLoading || AssetsLoaded || !usesAssetBundle) return;
loadComplete = false;
alreadyLoading = true;
await UniTask.WhenAll(LoadCommonAssetBundleAsync(), LoadLocalizedAssetBundleAsync());
await UniTask.WhenAll(LoadGamePrefabAsync(), LoadCommonAudioClips(), LoadLocalizedAudioClips());
SoundByte.PreloadGameAudioClips(this);
alreadyLoading = false;
loadComplete = true;
}
public async UniTask LoadCommonAssetBundleAsync()
{
if (bundleCommon != null && !commonLoaded)
{
await bundleCommon.UnloadAsync(true);
bundleCommon = null;
commonLoaded = false;
commonPreloaded = false;
}
if (commonPreloaded || commonLoaded) return;
commonPreloaded = true;
if (!usesAssetBundle) return;
if (bundleCommon != null) return;
AssetBundle bundle = await AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, wantAssetBundle + "/common")).ToUniTask(timing: PlayerLoopTiming.PreLateUpdate);
bundleCommon = bundle;
commonLoaded = true;
}
public async UniTask LoadLocalizedAssetBundleAsync()
{
if (bundleLocalized != null && !localeLoaded)
{
await bundleLocalized.UnloadAsync(true);
bundleLocalized = null;
localeLoaded = false;
localePreloaded = false;
}
if (!hasLocales) return;
if (localePreloaded) return;
localePreloaded = true;
if (!usesAssetBundle) return;
if (localeLoaded && bundleLocalized != null && currentLoadedLocale == defaultLocale) return;
AssetBundle bundle = await AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, wantAssetBundle + "/locale." + defaultLocale)).ToUniTask(timing: PlayerLoopTiming.PreLateUpdate);
if (localeLoaded && bundleLocalized != null && currentLoadedLocale == defaultLocale) return;
bundleLocalized = bundle;
currentLoadedLocale = defaultLocale;
localeLoaded = true;
}
public async UniTask LoadGamePrefabAsync()
{
if (!usesAssetBundle) return;
if (!commonLoaded) return;
if (bundleCommon == null) return;
AssetBundleRequest request = bundleCommon.LoadAssetAsync<GameObject>(name);
request.completed += (op) => OnPrefabLoaded(op as AssetBundleRequest);
await request;
loadedPrefab = request.asset as GameObject;
}
void OnPrefabLoaded(AssetBundleRequest request)
{
GameObject prefab = request.asset as GameObject;
// // load sound sequences here for now
// // this is taxing and is still done synchronously
// // move sequences to their own assets so that we don't have to look up a component
if (prefab.TryGetComponent<Games.Minigame>(out Games.Minigame minigame))
{
soundSequences = minigame.SoundSequences;
}
loadedPrefab = prefab;
}
public GameObject LoadGamePrefab()
{
if (!usesAssetBundle) return null;
loadedPrefab = GetCommonAssetBundle().LoadAsset<GameObject>(name);
return loadedPrefab;
}
public async UniTask LoadCommonAudioClips()
{
if (!commonLoaded) return;
if (bundleCommon == null) return;
var assets = bundleCommon.LoadAllAssetsAsync();
await assets;
}
public async UniTask LoadLocalizedAudioClips()
{
if (!localeLoaded) return;
if (bundleLocalized == null) return;
var assets = bundleLocalized.LoadAllAssetsAsync();
await assets;
}
public async UniTask UnloadAllAssets()
{
if (!usesAssetBundle) return;
if (loadedPrefab != null)
{
loadedPrefab = null;
}
if (bundleCommon != null)
{
await bundleCommon.UnloadAsync(true);
bundleCommon = null;
commonLoaded = false;
commonPreloaded = false;
}
if (bundleLocalized != null)
{
await bundleLocalized.UnloadAsync(true);
bundleLocalized = null;
localeLoaded = false;
localePreloaded = false;
}
SoundByte.UnloadAudioClips(name);
loadComplete = false;
}
}
public class GameAction
{
public string actionName;
public string displayName;
public EventCallback function = delegate { };
public float defaultLength = 1;
public bool resizable = false;
public List<Param> parameters = null;
public bool hidden = false;
public int priority = 0;
public EventCallback inactiveFunction = delegate { };
public EventCallback preFunction = delegate { };
public float preFunctionLength = 2.0f;
/// <summary>
/// <para>Creates a block that can be used in the editor. The block's function and attributes are defined in the parentheses.</para>
/// <para>Note: Every parameter after the second one is an optional parameter. You can change optional parameters by adding (name): (value) after the second parameter.</para>
/// </summary>
/// <param name="actionName">Entity model name</param>
/// <param name="displayName">Name of the block used in the UI</param>
/// <param name="defaultLength">How long the block appears in the editor</param>
/// <param name="resizable">Allows the user to resize the block</param>
/// <param name="parameters">Extra parameters for this block that change how it functions.</param>
/// <param name="function"><para>What the block does when read during playback</para>
/// <para>Only does this if the game that it is associated with is loaded.</para></param>
/// <param name="inactiveFunction">What the block does when read while the game it's associated with isn't loaded.</param>
/// <param name="preFunction">What the block does when the GameManager seeks to this cue for pre-scheduling.</param>
/// <param name="hidden">Prevents the block from being shown in the game list. Block will still function normally if it is in the timeline.</param>
/// <param name="priority">Priority of this event. Higher priority events will be run first.</param>
public GameAction(string actionName, string displayName, float defaultLength = 1, bool resizable = false, List<Param> parameters = null, EventCallback function = null, EventCallback inactiveFunction = null, EventCallback preFunction = null, bool hidden = false, int priority = 0, float preFunctionLength = 2.0f)
{
this.actionName = actionName;
if (displayName == String.Empty) this.displayName = actionName;
else this.displayName = displayName;
this.defaultLength = defaultLength;
this.resizable = resizable;
this.parameters = parameters;
this.hidden = hidden;
this.function = function ?? delegate { };
this.inactiveFunction = inactiveFunction ?? delegate { };
this.preFunction = preFunction ?? delegate { };
this.priority = priority;
this.preFunctionLength = preFunctionLength;
}
/// <summary>
/// <para>Shorthand constructor for a GameAction with only required data</para>
/// </summary>
/// <param name="actionName">Entity model name</param>
/// <param name="displayName">Name of the block used in the UI</param>
public GameAction(string actionName, string displayName)
{
this.actionName = actionName;
if (displayName == String.Empty) this.displayName = actionName;
else this.displayName = displayName;
}
}
[System.Serializable]
public class Param
{
public string propertyName;
public object parameter;
public string caption;
public string tooltip;
public List<CollapseParam> collapseParams;
/// <summary>
/// A parameter that changes the function of a GameAction.
/// </summary>
/// <param name="propertyName">The name of the variable that's being changed.</param>
/// <param name="parameter">The value of the parameter</param>
/// <param name="caption">The name shown in the editor. Can be anything you want.</param>
public Param(string propertyName, object parameter, string caption, string tooltip = "", List<CollapseParam> collapseParams = null)
{
this.propertyName = propertyName;
this.parameter = parameter;
this.caption = caption;
this.tooltip = tooltip;
this.collapseParams = collapseParams;
}
public class CollapseParam
{
public Func<object, RiqEntity, bool> CollapseOn;
public string[] collapseables;
/// <summary>
/// Class that decides how other parameters will be collapsed
/// </summary>
/// <param name="collapseOn">What values should make it collapse/uncollapse?</param>
/// <param name="collapseables">IDs of the parameters to collapse</param>
public CollapseParam(Func<object, RiqEntity, bool> collapseOn, params string[] collapseables)
{
CollapseOn = collapseOn;
this.collapseables = collapseables;
}
}
}
public delegate void EventCallback();
public delegate void ParamChangeCallback(string paramName, object paramValue, RiqEntity entity);
public static void Init(EventCaller eventCaller)
{
List<Minigame> defaultGames = new()
{
new Minigame("gameManager", "Game Manager", "", false, true, new List<GameAction>()
{
new GameAction("switchGame", "Switch Game", 0.5f, false,
function: delegate { var e = eventCaller.currentEntity; GameManager.instance.SwitchGame(eventCaller.currentSwitchGame, eventCaller.currentEntity.beat, e["toggle"]); },
parameters: new List<Param>()
{
new Param("toggle", true, "Black Flash", "Toggle if there should be a blck flash before the game is switched. You should only disable this if you know what you're doing.")
},
inactiveFunction: delegate { var e = eventCaller.currentEntity; GameManager.instance.SwitchGame(eventCaller.currentSwitchGame, eventCaller.currentEntity.beat, e["toggle"]); }
),
new GameAction("end", "End Remix",
function: delegate {
Debug.Log("end");
if (Timeline.instance != null)
Timeline.instance?.Stop(Timeline.instance.PlaybackBeat);
else
GameManager.instance.Stop(eventCaller.currentEntity.beat);
}
),
new GameAction("skill star", "Skill Star", 1f, true)
{
//temp for testing
function = delegate {
var e = eventCaller.currentEntity;
Common.SkillStarManager.instance.DoStarIn(e.beat, e.length);
}
},
new GameAction("toggle inputs", "Toggle Inputs", 0.5f, true,
new List<Param>()
{
new Param("toggle", true, "Allow Inputs", "Toggle if the player is able to input. Any missed cues while this is disabled will not be counted as a miss and will not break a perfect.")
},
delegate
{
GameManager.instance.ToggleInputs(eventCaller.currentEntity["toggle"]);
}
),
}),
new Minigame("countIn", "Count-Ins", "", false, true, new List<GameAction>()
{
new GameAction("count-in", "Count-In", 4f, true,
new List<Param>()
{
new Param("alt", false, "Alt", "Set the type of sounds to use for the count-in."),
new Param("go", false, "Go!", "Toggle to end the count-in with \"Go!\""),
},
preFunction : delegate {
var e = eventCaller.currentEntity;
SoundEffects.CountIn(e.beat, e.length, e["alt"], e["go"]);
}
),
new GameAction("4 beat count-in", "4 Beat Count-In", 4f, true,
new List<Param>()
{
new Param("type", SoundEffects.CountInType.Normal, "Type", "Set the type of sounds to use for the count-in.")
},
delegate {
var e = eventCaller.currentEntity;
SoundEffects.FourBeatCountIn(e.beat, e.length / 4f, e["type"]);
}
),
new GameAction("8 beat count-in", "8 Beat Count-In", 8f, true,
new List<Param>()
{
new Param("type", SoundEffects.CountInType.Normal, "Type", "Set the type of sounds to use for the count-in.")
},
delegate {
var e = eventCaller.currentEntity;
SoundEffects.EightBeatCountIn(e.beat, e.length / 8f, e["type"]);
}
),
new GameAction("count", "Count", 1f, false,
new List<Param>()
{
new Param("type", SoundEffects.CountNumbers.One, "Type", "Set the number to say."),
new Param("toggle", false, "Alt", "Toggle if the alternate version of this voice line should be used.")
},
delegate {
var e = eventCaller.currentEntity;
SoundEffects.Count(e["type"], e["toggle"]);
}
),
new GameAction("cowbell", "Cowbell",
function: delegate { SoundEffects.Cowbell(); }
),
new GameAction("ready!", "Ready!", 2f, true,
function: delegate { var e = eventCaller.currentEntity; SoundEffects.Ready(e.beat, (e.length / 2f)); }
),
new GameAction("and", "And", 0.5f,
function: delegate { SoundEffects.And(); }
),
new GameAction("go!", "Go!", 1f, false,
new List<Param>()
{
new Param("toggle", false, "Alt", "Toggle if the alternate version of this voice line should be used.")
},
function: delegate { SoundEffects.Go(eventCaller.currentEntity["toggle"]); }
),
// These are still here for backwards-compatibility but are hidden in the editor
new GameAction("4 beat count-in (alt)", "", 4f, function: delegate { var e = eventCaller.currentEntity; SoundEffects.FourBeatCountIn(e.beat, e.length, 1); }, hidden: true),
new GameAction("4 beat count-in (cowbell)", "", 4f, function: delegate { var e = eventCaller.currentEntity; SoundEffects.FourBeatCountIn(e.beat, e.length, 2); }, hidden: true),
new GameAction("8 beat count-in (alt)", "", 8f, function: delegate { var e = eventCaller.currentEntity; SoundEffects.EightBeatCountIn(e.beat, e.length, 1); }, hidden: true),
new GameAction("8 beat count-in (cowbell)", "", 8f, function: delegate { var e = eventCaller.currentEntity; SoundEffects.EightBeatCountIn(e.beat, e.length, 2); }, hidden: true),
new GameAction("one", "", function: delegate { SoundEffects.Count(0, false); }, hidden: true),
new GameAction("two", "", function: delegate { SoundEffects.Count(1, false); }, hidden: true),
new GameAction("three", "", function: delegate { SoundEffects.Count(2, false); }, hidden: true),
new GameAction("four", "", function: delegate { SoundEffects.Count(3, false); }, hidden: true),
new GameAction("one (alt)", "", function: delegate { SoundEffects.Count(0, true); }, hidden: true),
new GameAction("two (alt)", "", function: delegate { SoundEffects.Count(1, true); }, hidden: true),
new GameAction("three (alt)", "", function: delegate { SoundEffects.Count(2, true); }, hidden: true),
new GameAction("four (alt)", "", function: delegate { SoundEffects.Count(3, true); }, hidden: true),
new GameAction("go! (alt)", "", function: delegate { SoundEffects.Go(true); }, hidden: true),
}),
new Minigame("vfx", "Visual Effects", "", false, true, new List<GameAction>()
{
new GameAction("flash", "Flash/Fade", 1f, true,
new List<Param>()
{
new Param("colorA", Color.white, "Start Color", "Set the color at the start of the event."),
new Param("colorB", Color.white, "End Color", "Set the color at the end of the event."),
new Param("valA", new EntityTypes.Float(0, 1, 1), "Start Opacity", "Set the opacity at the start of the event."),
new Param("valB", new EntityTypes.Float(0, 1, 0), "End Opacity", "Set the opacity at the end of the event."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "colorA", "valA" })
})
}
),
new GameAction("filter", "Filter", 1f, true,
new List<Param>()
{
new Param("filter", Games.Global.Filter.FilterType.grayscale, "Type", "Set the type of filter to use."),
// old
/*new Param("inten", new EntityTypes.Float(0, 100, 100), "Intensity"),
new Param("fadein", new EntityTypes.Float(0, 100, 0), "Fade In"),
new Param("fadeout", new EntityTypes.Float(0, 100, 0), "Fade Out")*/
// new
new Param("slot", new EntityTypes.Integer(1, 10, 1), "Slot", "Set the slot for the filter. Higher slots are applied first. There can only be one filter per slot."),
new Param("start", new EntityTypes.Float(0, 1, 1), "Start Intensity", "Set the intensity at the start of the event."),
new Param("end", new EntityTypes.Float(0, 1, 1), "End Intensity", "Set the intensity at the end of the event."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "start" })
}),
}
),
new GameAction("move camera", "Move Camera", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-50, 50, 0), "Right / Left", "Set the position on the X axis."),
new Param("valB", new EntityTypes.Float(-50, 50, 0), "Up / Down", "Set the position on the Y axis."),
new Param("valC", new EntityTypes.Float(-250, 250, 10), "In / Out", "Set the position on the Z axis."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
new Param("axis", GameCamera.CameraAxis.All, "Axis", "Set if only a specific axis should be modified." )
}
),
new GameAction("rotate camera", "Rotate Camera", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Integer(-360, 360, 0), "Pitch", "Set the up/down rotation."),
new Param("valB", new EntityTypes.Integer(-360, 360, 0), "Yaw", "Set the left/right rotation."),
new Param("valC", new EntityTypes.Integer(-360, 360, 0), "Roll", "Set the clockwise/counterclockwise rotation."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
new Param("axis", GameCamera.CameraAxis.All, "Axis", "Set if only a specific axis should be modified." )
}
),
new GameAction("pan view", "Pan Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-50, 50, 0), "Right / Left", "Set the position on the X axis."),
new Param("valB", new EntityTypes.Float(-50, 50, 0), "Up / Down", "Set the position on the Y axis."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
new Param("axis", StaticCamera.ViewAxis.All, "Axis", "Set if only a specific axis should be modified." )
}
),
new GameAction("rotate view", "Rotate Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-360, 360, 0), "Rotation", "Set the clockwise/counterclockwise rotation."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
}
),
new GameAction("scale view", "Scale Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-50f, 50, 1), "Width", "Set the width of the viewport."),
new Param("valB", new EntityTypes.Float(-50f, 50, 1), "Height", "Set the height of the viewport."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
new Param("axis", StaticCamera.ViewAxis.All, "Axis", "Set if only a specific axis should be modified." )
}
),
new("stretch camera", "Stretch Camera")
{
resizable = true,
parameters = new()
{
new("x1", new EntityTypes.Float(-50f, 50f, 1f), "Start Width", "Set the width at the start of the event."),
new("x2", new EntityTypes.Float(-50f, 50f, 1f), "End Width", "Set the width at the end of the event."),
new("y1", new EntityTypes.Float(-50f, 50f, 1f), "Start Height", "Set the height at the start of the event."),
new("y2", new EntityTypes.Float(-50f, 50f, 1f), "End Height", "Set the height at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "x1", "y1" })
}),
new Param("axis", GameCamera.CameraAxis.All, "Axis", "Set if only a specific axis should be modified.")
}
},
new GameAction("fitScreen", "Force Game Stretching To Window")
{
defaultLength = 0.5f,
parameters = new()
{
new("enable", true, "Enabled", "Toggle if the game should be forced to stretch to the window size, removing the letterbox.")
}
},
new GameAction("screen shake", "Screen Shake", 1f, true,
new List<Param>()
{
new Param("easedA", new EntityTypes.Float(0, 10, 0), "Start Horizontal Intensity", "Set the horizontal intensity of the screen shake at the start of the event."),
new Param("valA", new EntityTypes.Float(0, 10, 0), "End Horizontal Intensity", "Set the horizontal intensity of the screen shake at the end of the event."),
new Param("easedB", new EntityTypes.Float(0, 10, 0.5f), "Start Vertical Intensity", "Set the vertical intensity of the screen shake at the start of the event."),
new Param("valB", new EntityTypes.Float(0, 10, 0.5f), "End Vertical Intensity", "Set the vertical intensity of the screen shake at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "easedA", "easedB" })
}),
}
),
new GameAction("display textbox", "Display Textbox", 1f, true, new List<Param>()
{
new Param("text1", "", "Text", "Set the text to display in the textbox. Rich text is supported."),
new Param("type", Games.Global.Textbox.TextboxAnchor.TopMiddle, "Anchor", "Set where to anchor the textbox."),
new Param("valA", new EntityTypes.Float(0.25f, 4, 1), "Width", "Set the width of the textbox."),
new Param("valB", new EntityTypes.Float(0.5f, 8, 1), "Height", "Set the height of the textbox.")
}
),
new GameAction("display open captions", "Display Open Captions", 1f, true,
new List<Param>()
{
new Param("text1", "", "Text", "Set the text to display in the captions. Rich text is supported."),
new Param("type", Games.Global.Textbox.TextboxAnchor.BottomMiddle, "Anchor", "Set where to anchor the captions."),
new Param("valA", new EntityTypes.Float(0.25f, 4, 1), "Width", "Set the width of the captions."),
new Param("valB", new EntityTypes.Float(0.5f, 8, 1), "Height", "Set the height of the captions.")
}
),
new GameAction("display closed captions", "Display Closed Captions", 1f, true,
new List<Param>()
{
new Param("text1", "", "Text", "Set the text to display in the captions. Rich text is supported."),
new Param("type", Games.Global.Textbox.ClosedCaptionsAnchor.Top, "Anchor", "Set where to anchor the captions."),
new Param("valA", new EntityTypes.Float(0.5f, 4, 1), "Height", "Set the height of the captions.")
}
),
new GameAction("display song artist", "Display Song Info", 1f, true,
new List<Param>()
{
new Param("text1", "", "Title", "Set the text to display in the upper label. Rich text is supported."),
new Param("text2", "", "Artist", "Set the text to display in the lower label. Rich text is supported."),
new Param("instantOn", false, "Instant Show", "Toggle if the slide-in animation should be skipped."),
new Param("instantOff", false, "Instant Hide", "Toggle if the slide-out animation should be skipped."),
}
),
new GameAction("camera background color", "Camera Background Color", 1, true, new List<Param>()
{
new Param("color", Color.black, "Start Color", "Set the color at the start of the event."),
new Param("color2", Color.black, "End Color", "Set the color at the end of the event."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.")
}
),
// Post Processing VFX
new GameAction("vignette", "Vignette")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 1f), "End Intensity", "Set the intensity at the end of the event."),
new("colorStart", Color.black, "Start Color", "Set the color at the start of the event."),
new("colorEnd", Color.black, "End Color", "Set the color at the end of the event."),
new("smoothStart", new EntityTypes.Float(0.01f, 1f, 0.2f), "Start Smoothness", "Set the smoothness at the start of the event."),
new("smoothEnd", new EntityTypes.Float(0.01f, 1f, 0.2f), "End Smoothness", "Set the smoothness at the end of the event."),
new("roundStart", new EntityTypes.Float(0f, 1f, 1f), "Start Roundness", "Set the roundness at the start of the event."),
new("roundEnd", new EntityTypes.Float(0f, 1f, 1f), "End Roundness", "Set the roundness at the end of the event."),
new("rounded", false, "Rounded", "Toggle if the vignette should be equal on all sides."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "colorStart", "smoothStart", "roundStart" })
}),
}
},
new GameAction("cabb", "Chromatic Abberation")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 1f), "End Intensity", "Set the intensity at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart" })
}),
}
},
new GameAction("bloom", "Bloom")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 100f, 0f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 100f, 1f), "End Intensity", "Set the intensity at the end of the event."),
new("colorStart", Color.white, "Start Tint", "Set the tint at the start of the event."),
new("colorEnd", Color.white, "End Tint", "Set the tint at the end of the event."),
new("thresholdStart", new EntityTypes.Float(0f, 100f, 1f), "Start Threshold", "Set the threshold at the start of the event."),
new("thresholdEnd", new EntityTypes.Float(0f, 100f, 1f), "End Threshold", "Set the threshold at the end of the event."),
new("softKneeStart", new EntityTypes.Float(0f, 1f, 0.5f), "Start Soft Knee", "Set the soft knee at the start of the event."),
new("softKneeEnd", new EntityTypes.Float(0f, 1f, 0.5f), "End Soft Knee", "Set the soft knee at the end of the event."),
new("anaStart", new EntityTypes.Float(-1f, 1f, 0f), "Start Anamorphic Ratio", "Set the anamorphic ratio at the start of the event."),
new("anaEnd", new EntityTypes.Float(-1f, 1f, 0f), "End Anamorphic Ratio", "Set the anamorphic ratio at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "colorStart", "thresholdStart", "softKneeStart", "anaStart" })
}),
}
},
new GameAction("lensD", "Lens Distortion")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(-100f, 100f, 0f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(-100f, 100f, 1f), "End Intensity", "Set the intensity at the end of the event."),
new("xStart", new EntityTypes.Float(0f, 1f, 1f), "Start X Multiplier", "Set the X multiplier at the start of the event."),
new("xEnd", new EntityTypes.Float(0f, 1f, 1f), "End X Multiplier", "Set the X multiplier at the end of the event."),
new("yStart", new EntityTypes.Float(0f, 1f, 1f), "Start Y Multiplier", "Set the Y multiplier at the start of the event."),
new("yEnd", new EntityTypes.Float(0f, 1f, 1f), "End Y Multiplier", "Set the Y multiplier at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "xStart", "yStart" })
}),
}
},
new GameAction("grain", "Grain")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 1f), "End Intensity", "Set the intensity at the end of the event."),
new("sizeStart", new EntityTypes.Float(0.3f, 3f, 1f), "Start Size", "Set the size at the start of the event."),
new("sizeEnd", new EntityTypes.Float(0.3f, 3f, 1f), "End Size", "Set the size at the end of the event."),
new("colored", true, "Colored", "Toggle if the grain will be colored."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "sizeStart" })
}),
}
},
new GameAction("colorGrading", "Color Grading")
{
resizable = true,
parameters = new()
{
new("tempStart", new EntityTypes.Float(-100f, 100f), "Start Temperature", "Set the temperature at the start of the event."),
new("tempEnd", new EntityTypes.Float(-100f, 100f), "End Temperature", "Set the temperature at the end of the event."),
new("tintStart", new EntityTypes.Float(-100f, 100f), "Start Tint", "Set the tint at the start of the event."),
new("tintEnd", new EntityTypes.Float(-100f, 100f), "End Tint", "Set the tint at the end of the event."),
new("colorStart", Color.white, "Start Color Filter", "Set the color filter at the start of the event."),
new("colorEnd", Color.white, "End Color Filter", "Set the color filter at the end of the event."),
new("hueShiftStart", new EntityTypes.Float(-180f, 180f), "Start Hue Shift", "Set the hue shift at the start of the event."),
new("hueShiftEnd", new EntityTypes.Float(-180f, 180f), "End Hue Shift", "Set the hue shift at the end of the event."),
new("satStart", new EntityTypes.Float(-100f, 100f), "Start Saturation", "Set the saturation at the start of the event."),
new("satEnd", new EntityTypes.Float(-100f, 100f), "End Saturation", "Set the saturation at the end of the event."),
new("brightStart", new EntityTypes.Float(-100f, 100f), "Start Brightness", "Set the brightness at the start of the event."),
new("brightEnd", new EntityTypes.Float(-100f, 100f), "End Brightness", "Set the brightness at the end of the event."),
new("conStart", new EntityTypes.Float(-100f, 100f), "Start Contrast", "Set the contrast at the start of the event."),
new("conEnd", new EntityTypes.Float(-100f, 100f), "End Contrast", "Set the contrast at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "tempStart", "tintStart", "colorStart", "hueShiftStart", "satStart", "brightStart", "conStart" })
}),
}
},
new GameAction("gaussBlur", "Gaussian Blur")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 5f, 0f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 5f, 1f), "End Intensity", "Set the intensity at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Linear, new string[] { "intenStart" })
}),
}
},
new GameAction("retroTv", "Retro TV")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f, 0f), "Start Distortion", "Set the distortion at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 0.2f), "End Distortion", "Set the distortion at the end of the event."),
new("rgbStart", new EntityTypes.Float(0f, 1f, 1f), "Start RGB Blend", "Set the RGB blend at the start of the event."),
new("rgbEnd", new EntityTypes.Float(0f, 1f, 1f), "End RGB Blend", "Set the RGB blend at the end of the event."),
new("bottomStart", new EntityTypes.Float(0f, 1f, 0f), "Start Bottom Collapse", "Set the bottom collapse at the start of the event."),
new("bottomEnd", new EntityTypes.Float(0f, 1f, 0.02f), "End Bottom Collapse", "Set the bottom collapse at the end of the event."),
new("noiseStart", new EntityTypes.Float(0f, 1f, 0f), "Start Noise", "Set the noise at the start of the event."),
new("noiseEnd", new EntityTypes.Float(0f, 1f, 0.3f), "End Noise", "Set the noise knee at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "rgbStart", "bottomStart", "noiseStart"})
}),
}
},
new GameAction("scanJitter", "Scan Line Jitter")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f, 0f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 0.1f), "End Intensity", "Set the intensity at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart" })
}),
}
},
new GameAction("analogNoise", "Analog Noise")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f, 0f), "Start Speed", "Set the speed at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 0.5f), "End Speed", "Set the speed at the end of the event."),
new("fadingStart", new EntityTypes.Float(0f, 1f, 0f), "Start Fading", "Set the fading at the start of the event."),
new("fadingEnd", new EntityTypes.Float(0f, 1f, 0.1f), "End Fading", "Set the fading at the end of the event."),
new("thresholdStart", new EntityTypes.Float(0f, 1f, 0f), "Start Threshold", "Set the threshold at the start of the event."),
new("thresholdEnd", new EntityTypes.Float(0f, 1f, 0.8f), "End Threshold", "Set the threshold at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "fadingStart", "thresholdStart"})
}),
}
},
new GameAction("screenJump", "Screen Jump")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0f, 1f, 0f), "Start Intensity", "Set the intensity at the start of the event."),
new("intenEnd", new EntityTypes.Float(0f, 1f, 0.01f), "End Intensity", "Set the intensity at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart" })
}),
}
},
new GameAction("sobelNeon", "Neon")
{
resizable = true,
parameters = new()
{
new("intenStart", new EntityTypes.Float(0.1f, 1f, 0.1f), "Start Intensity", "Set the edge fade at the start of the event."),
new("intenEnd", new EntityTypes.Float(0.1f, 1f, 1f), "End Intensity", "Set the edge fade at the end of the event."),
new("edgeWidthStart", new EntityTypes.Float(0.05f, 5f, 0.05f), "Start Edge Width", "Set the edge width at the start of the event."),
new("edgeWidthEnd", new EntityTypes.Float(0.05f, 5f, 2f), "End Edge Width", "Set the edge width at the end of the event."),
new("bgFadeStart", new EntityTypes.Float(0f, 1f, 0f), "Start Background Presence", "Set the background presence at the start of the event."),
new("bgFadeEnd", new EntityTypes.Float(0f, 1f, 0f), "End Background Presence", "Set the background presence at the end of the event."),
new("brightnessStart", new EntityTypes.Float(0f, 2f, 0f), "Start Brightness", "Set the brightness at the start of the event."),
new("brightnessEnd", new EntityTypes.Float(0f, 2f, 1f), "End Brightness", "Set the brightness at the end of the event."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "intenStart", "edgeWidthStart", "bgFadeStart", "brightnessStart" })
}),
}
},
new GameAction("screenTiling", "Tile Screen")
{
resizable = true,
parameters = new()
{
new("xStart", new EntityTypes.Float(1, 100, 1), "Start Horizontal Tiles", "Set the amount of horizontal tiles at the start of the event."),
new("xEnd", new EntityTypes.Float(1, 100, 1), "End Horizontal Tiles", "Set the amount of horizontal tiles at the end of the event."),
new("yStart", new EntityTypes.Float(1, 100, 1), "Start Vertical Tiles", "Set the amount of vertical tiles at the start of the event."),
new("yEnd", new EntityTypes.Float(1, 100, 1), "End Vertical Tiles", "Set the amount of vertical tiles at the end of the event."),
new("axis", StaticCamera.ViewAxis.All, "Axis", "Set if only a specific axis should be modified."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "xStart", "yStart" })
}),
}
},
new GameAction("scrollTiles", "Scroll Tiles")
{
resizable = true,
parameters = new()
{
new("xScrollStart", new EntityTypes.Float(-100, 100, 0), "Start Horizontal Scroll", "Set the horizontal scroll at the start of the event."),
new("xScrollEnd", new EntityTypes.Float(-100, 100, 0), "End Horizontal Scroll", "Set the horizontal scroll at the end of the event."),
new("yScrollStart", new EntityTypes.Float(-100, 100, 0), "Start Vertical Scroll", "Set the vertical scroll at the start of the event."),
new("yScrollEnd", new EntityTypes.Float(-100, 100, 0), "End Vertical Scroll", "Set the vertical scroll at the end of the event."),
new("axis", StaticCamera.ViewAxis.All, "Axis", "Set if only a specific axis should be modified."),
new("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action.", new()
{
new((x, y) => (Util.EasingFunction.Ease)x != Util.EasingFunction.Ease.Instant, new string[] { "xScrollStart", "yScrollStart" })
}),
}
},
}),
new Minigame("advanced", "Advanced", "", false, true, new List<GameAction>()
{
new GameAction("play animation", "Play Animation", 0.5f, false,
new List<Param>()
{
new Param("getAnimators", new EntityTypes.Button("No Game", e => {
var gm = GameManager.instance;
Minigame game = gm.GetGameInfo(gm.currentGame);
if (game != null) {
Animator[] animators = gm.minigameObj.transform.GetComponentsInChildren<Animator>();
// not in an update loop so it's fine :3
((EntityTypes.DropdownObj)e["animator"]).SetValues(animators.Select(anim => {
Transform obj = anim.transform;
List<string> path = new() { obj.name };
for (int i = 0; i < 10; i++) // not a while loop because i don't trust myself
{
if (obj.parent.name == game.name || obj.parent == null) break;
obj = obj.parent;
path.Insert(0, obj.name);
}
return string.Join('/', path);
}).ToList());
}
return game?.displayName ?? "No Game";
}), "Get Animators", "Get all the animators in the current minigame scene. (Make sure to have the minigame you want loaded!)", new() {
new((x, _) => (string)x != "No Game", "animator", "getAnimations")
}),
new Param("animator", new EntityTypes.Dropdown(), "Animator", "Specify which animator in the scene to play an animation on."),
new Param("getAnimations", new EntityTypes.Button("", e => {
var gm = GameManager.instance;
Minigame game = gm.GetGameInfo(gm.currentGame);
string animPath = ((EntityTypes.DropdownObj)e["animator"]).CurrentValue;
Animator animator = null;
if (!string.IsNullOrEmpty(animPath)) {
var animObj = gm.minigameObj.transform.Find(animPath);
if (animObj != null && animObj.TryGetComponent(out animator) && animator != null) {
List<string> animationClips = new();
foreach (var clip in animator.runtimeAnimatorController.animationClips) {
if (clip != null) {
animationClips.Add(clip.name);
}
}
((EntityTypes.DropdownObj)e["animation"]).SetValues(animationClips);
}
}
return animator != null ? animator.name : "";
}), "Get Animations", "Get all the animations in the selected animator.", new() {
new((x, _) => (string)x != "", "animation", "scale")
}),
new Param("animation", new EntityTypes.Dropdown(), "Animation", "Specify the name of the animation to play."),
new Param("scale", new EntityTypes.Float(0, 5, 0.5f), "Animation Scale", "The time scale of the animation. Higher values are faster."),
},
delegate {
var e = eventCaller.currentEntity;
GameManager.instance.PlayAnimationArbitrary(e["animator"].CurrentValue, e["animation"].CurrentValue, e["scale"]);
}
),
new GameAction("play sfx", "Play SFX", 0.5f, true,
new List<Param>()
{
new Param("game", new EntityTypes.Dropdown(), "Which Game", "Specify the game's sfx to play. An empty input will play global sfx."),
new Param("getSfx", new EntityTypes.Button("", e => {
string gameName = ((EntityTypes.DropdownObj)e["game"]).CurrentValue;
List<string> clips;
if (eventCaller.minigames.TryGetValue(gameName, out Minigame game) && game != null) {
IEnumerable<AudioClip> audioClips = game.GetCommonAssetBundle().LoadAllAssets<AudioClip>();
var localAssBun = game.GetLocalizedAssetBundle();
if (localAssBun != null) {
audioClips = audioClips.Concat(localAssBun.LoadAllAssets<AudioClip>());
}
clips = audioClips.Select(x => x.name).ToList();
} else {
// this is probably the best way to do it?
clips = new() { "applause", "metronome", "miss", "nearMiss", "perfectMiss", "skillStar" };
}
clips.Sort((s1, s2) => s1.CompareTo(s2));
EntityTypes.DropdownObj sfxDD = e["sfxName"];
sfxDD.SetValues(clips);
return clips.Count > 0 ? (game != null ? game.displayName : "Common") : "Empty!";
}), "Get SFX", "Get all the sfx in the selected minigame."),
new Param("sfxName", new EntityTypes.Dropdown(), "SFX Name", "The name of the sfx to play."),
new Param("useSemitones", false, "Use Semitones", "Toggle to use semitones instead of straight pitch.", new() {
new((x, e) => (bool)x, "semitones", "cents"),
new((x, e) => !(bool)x, "pitch"),
}),
new Param("semitones", new EntityTypes.Integer(-EntityTypes.Note.maxSemitones, EntityTypes.Note.maxSemitones, 0), "Semitones", "The semitones of the sfx."),
new Param("cents", new EntityTypes.Integer(-100, 100, 0), "Cents", "The cents of the sfx."),
new Param("pitch", new EntityTypes.Float(0, 5, 1), "Pitch", "The pitch of the sfx."),
new Param("volume", new EntityTypes.Float(0, 2, 1), "Volume", "The volume of the sfx."),
new Param("offset", new EntityTypes.Integer(-500, 500), "Offset (ms)", "The offset of the sfx in milliseconds."),
new Param("loop", false, "Loop", "Loop the sfx for the length of the block."),
},
preFunction : delegate {
var e = eventCaller.currentEntity;
float pitch = e["pitch"];
if (e["useSemitones"]) pitch = SoundByte.GetPitchFromCents((e["semitones"] * 100) + e["cents"], false);
GameManager.PlaySFXArbitrary(e.beat, e.length, e["game"].CurrentValue, e["sfxName"].CurrentValue, pitch, e["volume"], e["loop"], e["offset"]);
}
),
}),
};
foreach (var game in defaultGames)
{
eventCaller.minigames.Add(game.name, game);
}
LoadMinigames(eventCaller);
// im so sorry
eventCaller.minigames["advanced"].actions
.Find(a => a.actionName == "play sfx").parameters[0].parameter = new EntityTypes.Dropdown(0, new string[] { "common" }.Concat(eventCaller.minigames.Keys.Skip(defaultGames.Count)).ToArray());
}
}
}