HeavenStudio/Assets/Scripts/Minigames.cs
minenice55 534b06301d
Squashed commit of the following: (#474)
commit a6ce193ce5
Merge: 6105bb4f 9b24ac48
Author: minenice55 <star.elementa@gmail.com>
Date:   Wed Jun 14 01:23:31 2023 +0000

    Merge pull request #473 from minenice55/easings-fix

    Enum Parsing Fix

commit 9b24ac4838
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 21:21:13 2023 -0400

    fix all parsing of enums, removed the "special case" for eases

    update to latest Jukebox

commit 6105bb4f64
Merge: 576b4a00 8df235a8
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 21:39:15 2023 +0000

    Merge pull request #469 from minenice55/jukebox-update

    Jukebox Package Update

commit 576b4a0017
Merge: 99804457 10f8fa7f
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 21:39:08 2023 +0000

    Merge pull request #458 from AstrlJelly/MiscAdditions_4

    Bug Fixes + Feature Additions

commit 10f8fa7f5a
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 17:38:24 2023 -0400

    add "updater" for the old marching entity

commit 118cd4993d
Merge: 1c1c731d 99804457
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 17:22:01 2023 -0400

    Merge branch 'release_1' into pr/458

commit 8df235a85b
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 16:55:02 2023 -0400

    let play mode start if no song file is loaded

    fix issue with loading large audio files

commit 9980445723
Merge: aff1e3d8 2f595824
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 18:32:40 2023 +0000

    Merge pull request #471 from Rapandrasmus/FixWorkingDoughCurve

    Fixed weird curve stuff on game switch in working dough

commit 2f59582460
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 19:24:13 2023 +0200

    Fixed weird curve stuff on game switch in working dough

commit aff1e3d889
Merge: b40ec128 c9accfa5
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 16:09:21 2023 +0000

    Merge pull request #470 from Rapandrasmus/WorkingDoughFixes

    Fixed small balls not working in working dough

commit c9accfa52d
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 18:07:13 2023 +0200

    Fixed small balls not working in working dough

commit b40ec128a5
Merge: 9f953d50 95c9b346
Author: minenice55 <star.elementa@gmail.com>
Date:   Tue Jun 13 14:56:58 2023 +0000

    Merge pull request #467 from Rapandrasmus/WorkingDoughRework

    Working Dough rework

commit 95c9b34695
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 16:13:08 2023 +0200

    modernised rockers pass turn

commit 0debf55391
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 16:03:08 2023 +0200

    Rhythm tweezers pass turn now works like working dough

commit 6eac745eae
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 15:51:35 2023 +0200

    gandw on balls has been added

commit 349867d983
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 14:59:56 2023 +0200

    Proper inactive handling now

commit d469c6c3fa
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 13:52:15 2023 +0200

    OnSpawnBall reimplemented

commit dd290b3331
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Tue Jun 13 13:37:09 2023 +0200

    new sounds

commit 757e1c2c5e
Author: minenice55 <star.elementa@gmail.com>
Date:   Mon Jun 12 17:18:37 2023 -0400

    update Jukebox to latest version

    fixes for inferred entity loading

commit 8ce1d8276b
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Mon Jun 12 21:36:57 2023 +0200

    ball transporter anims for pass turn

commit af0d9bae19
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Mon Jun 12 20:38:48 2023 +0200

    working dough converted, need to fix eveerything though

commit db21d51673
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Mon Jun 12 18:36:41 2023 +0200

    Converted everything to new curves and made playerballs handle themselves input-wise

commit 41157a038c
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Mon Jun 12 14:04:32 2023 +0200

    Some new curves

commit 7d754f5706
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Mon Jun 12 12:03:21 2023 +0200

    Rockers can do it too now

commit c196947251
Author: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Date:   Mon Jun 12 11:58:31 2023 +0200

    You can now place inputs on top of pass turn for rhythm tweezers

commit 9f953d505f
Merge: 2a8d8d6f df120f08
Author: minenice55 <star.elementa@gmail.com>
Date:   Sun Jun 11 15:55:40 2023 -0400

    Merge branch 'master' into release_1

commit 2a8d8d6fd2
Merge: 9e3e5942 13adf5b0
Author: minenice55 <star.elementa@gmail.com>
Date:   Sun Jun 11 19:53:47 2023 +0000

    Merge pull request #465 from minenice55/dont_infer_track

    Don't infer the track field when importing converted v0 riq coming from unknown origin

commit 13adf5b07b
Author: minenice55 <star.elementa@gmail.com>
Date:   Sun Jun 11 15:52:14 2023 -0400

    don't infer track when importing a v0 riq from another program

commit 9e3e594209
Author: minenice55 <star.elementa@gmail.com>
Date:   Sun Jun 11 12:12:25 2023 -0400

    make base datamodels for special entity reading (#463)

    * make base datamodels for special entity reading

    * fix crop stomp breaking when no game switch or remix end is set

    * fix save shortcut

    fix loading charts with no music

commit 1c1c731dd1
Author: minenice55 <star.elementa@gmail.com>
Date:   Sat Jun 10 23:00:30 2023 -0400

    add updater for marching orders turn

commit 210be54ba3
Merge: 67b6658c af8395fb
Author: minenice55 <star.elementa@gmail.com>
Date:   Sat Jun 10 22:36:10 2023 -0400

    Merge branch 'release_1' into pr/458

commit af8395fb6a
Merge: 81eafec9 b1fab52a
Author: minenice55 <star.elementa@gmail.com>
Date:   Sat Jun 10 22:30:32 2023 -0400

    Merge branch 'master' into release_1

commit 81eafec986
Author: minenice55 <star.elementa@gmail.com>
Date:   Sat Jun 10 22:27:58 2023 -0400

    editor fixes (#459)

    * ditch loading dialog

    doesn't show up when it's supposed to

    * format song offset in editor

    * remove VorbisPlugin

    * Update Editor.cs

commit 67b6658cd3
Author: AstrlJelly <bdlawson115@gmail.com>
Date:   Sat Jun 10 16:55:10 2023 -0400

    convert float to double and all that

commit a4b66367fd
Merge: 08a664ba b1fab52a
Author: AstrlJelly <bdlawson115@gmail.com>
Date:   Sat Jun 10 16:01:44 2023 -0400

    Merge remote-tracking branch 'upstream/master' into MiscAdditions_4

commit 08a664ba2a
Merge: faf3fbf9 b7afd697
Author: AstrlJelly <bdlawson115@gmail.com>
Date:   Sat Jun 10 15:21:22 2023 -0400

    Merge remote-tracking branch 'upstream/master' into MiscAdditions_4

commit faf3fbf97c
Author: AstrlJelly <bdlawson115@gmail.com>
Date:   Sat Jun 10 15:18:45 2023 -0400

    a lot

    * munchy monk input + mustache fixes
    * fork lifter and pajama party bopping
    * meat grinder miss bop fix
    * cloud monkey Real
    * marching orders Go! was broken
    * force march doesn't break when it's too early from a game switch
    * you can use the March! block without the marching now

commit bb2ae74339
Author: minenice55 <star.elementa@gmail.com>
Date:   Sat Jun 10 15:13:29 2023 -0400

    Integration of Jukebox Library (#451)

    * add Jukebox library

    todo:
    - saving / loading of new format
    - inferrence of unknown data like past versions
    - move the temporary float casts to proper use of double
    - make sound related functions take double for timing
    - inform people that the Jukebox sound player was renamed to SoundByte lol

    * make sound, input scheduling, and super curve use double precision

    * successfully load charts

    * editor works again

    v1 riqs can be saved and loaded

    * first tempo and volume markers are unmovable

    fix loading of charts' easing values

    * use gsync / freesync

    * update Jukebox refs to SoundByte

    * game events use double part 1

    Air Rally - Glee Club converted

    * don't load song if chart load fails

    * finish conversion of all minigames

    * remove editor waveform toggle

    * timeline now respects added song offset length

    clear cache files on app close
    prepped notes for dsp sync

    * update timeline length when offset changed

    * update to latest Jukebox

    * make error panel object in global game manager

    * improve conductor music scheduling

    * added error message box

    fix first game events sometimes not playing
2023-06-13 21:28:21 -04:00

754 lines
41 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using HeavenStudio.Util;
using HeavenStudio.Editor.Track;
using HeavenStudio.Games;
using Jukebox;
using Jukebox.Legacy;
using System;
using System.Linq;
using System.Reflection;
using System.IO;
namespace HeavenStudio
{
public class Minigames
{
public static void InitPreprocessor()
{
RiqBeatmap.OnUpdateBeatmap += PreProcessBeatmap;
}
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)
{"iconurl", ""}, // custom icon location (future)
{"challengetype", 0}, // perfect challenge type
// 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, or second line for single-type)
{"resultcommon_ok", "Eh. Passable."}, // generic "OK" message (one-liner, or second line for single-type)
{"resultcommon_ng", "Try harder next time."}, // generic "Try Again" message (one-liner, or second line for single-type)
// the following are shown / hidden in-editor depending on the tags of the games used
{"resultnormal_hi", "You show strong fundamentals."}, // "Superb" message for normal games (two-liner)
{"resultnormal_ng", "Work on your fundamentals."}, // "Try Again" message for normal games (two-liner)
{"resultkeep_hi", "You kept the beat well."}, // "Superb" message for keep-the-beat games (two-liner)
{"resultkeep_ng", "You had trouble keeping the beat."}, // "Try Again" message for keep-the-beat games (two-liner)
{"resultaim_hi", "You had great aim."}, // "Superb" message for aim games (two-liner)
{"resultaim_ng", "Your aim was a little shaky."}, // "Try Again" message for aim games (two-liner)
{"resultrepeat_hi", "You followed the example well."}, // "Superb" message for call-and-response games (two-liner)
{"resultrepeat_ng", "Next time, follow the example better."}, // "Try Again" message for call-and-response games (two-liner)
};
static Dictionary<string, object> tempoChangeModel = new()
{
{"tempo", 120f},
{"swing", 0f},
{"timeSignature", new Vector2(4, 4)},
};
static Dictionary<string, object> volumeChangeModel = new()
{
{"volume", 1f},
{"fade", Util.EasingFunction.Ease.Instant},
};
static Dictionary<string, object> sectionMarkModel = new()
{
{"sectionName", ""},
{"isCheckpoint", false},
{"startPerfect", false},
{"breakSection", false},
{"extendsPrevious", false},
{"sectionWeight", 1f},
};
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);
}
}
}
}
/// <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;
foreach (var e in data.entities)
{
var gameName = e.datamodel.Split(0);
var actionName = e.datamodel.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(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.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))
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.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.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)
{
if (!data.properties.ContainsKey(prop.Key))
{
data.properties.Add(prop.Key, prop.Value);
}
}
return data;
}
public class Minigame
{
public string name;
public string displayName;
public string color;
public GameObject holder;
public bool hidden;
public bool fxOnly;
public List<GameAction> actions = new List<GameAction>();
public List<string> tags;
public string defaultLocale = "en";
public string wantAssetBundle = "";
public List<string> supportedLocales;
public bool inferred;
public bool usesAssetBundle => (wantAssetBundle != "");
public bool hasLocales => (supportedLocales.Count > 0);
public bool AssetsLoaded => (((hasLocales && localeLoaded && currentLoadedLocale == defaultLocale) || (!hasLocales)) && commonLoaded);
public bool SequencesPreloaded => soundSequences != null;
public string LoadableName => inferred ? "noGame" : name;
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 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 assetBundle = "", string defaultLocale = "en", List<string> supportedLocales = null, bool inferred = false)
{
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 = assetBundle;
this.defaultLocale = defaultLocale;
this.supportedLocales = supportedLocales ?? new List<string>();
this.inferred = inferred;
}
public AssetBundle GetLocalizedAssetBundle()
{
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 (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;
}
public IEnumerator LoadCommonAssetBundleAsync()
{
if (commonPreloaded || commonLoaded) yield break;
commonPreloaded = true;
if (!usesAssetBundle) yield break;
if (bundleCommon != null) yield break;
AssetBundleCreateRequest asyncBundleRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, wantAssetBundle + "/common"));
if (bundleCommon != null) yield break;
yield return asyncBundleRequest;
AssetBundle localAssetBundle = asyncBundleRequest.assetBundle;
if (bundleCommon != null) yield break;
yield return localAssetBundle;
if (localAssetBundle == null) yield break;
bundleCommon = localAssetBundle;
commonLoaded = true;
}
public IEnumerator LoadLocalizedAssetBundleAsync()
{
if (localePreloaded) yield break;
localePreloaded = true;
if (!hasLocales) yield break;
if (!usesAssetBundle) yield break;
if (localeLoaded && bundleLocalized != null && currentLoadedLocale == defaultLocale) yield break;
AssetBundleCreateRequest asyncBundleRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, wantAssetBundle + "/locale." + defaultLocale));
if (localeLoaded && bundleLocalized != null && currentLoadedLocale == defaultLocale) yield break;
yield return asyncBundleRequest;
AssetBundle localAssetBundle = asyncBundleRequest.assetBundle;
if (localeLoaded && bundleLocalized != null && currentLoadedLocale == defaultLocale) yield break;
yield return localAssetBundle;
if (localAssetBundle == null) yield break;
bundleLocalized = localAssetBundle;
currentLoadedLocale = defaultLocale;
localeLoaded = true;
}
}
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="prescheduleFunction">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="preFunction">Runs two beats before this event is reached.</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 prescheduleFunction = null, bool hidden = false, EventCallback preFunction = null, 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 = prescheduleFunction ?? delegate { };
this.priority = priority;
this.preFunctionLength = preFunctionLength;
//todo: converting to new versions of GameActions
}
/// <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 propertyCaption;
public string tooltip;
/// <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="propertyCaption">The name shown in the editor. Can be anything you want.</param>
public Param(string propertyName, object parameter, string propertyCaption, string tooltip = "")
{
this.propertyName = propertyName;
this.parameter = parameter;
this.propertyCaption = propertyCaption;
this.tooltip = tooltip;
}
}
public delegate void EventCallback();
public delegate void ParamChangeCallback(string paramName, object paramValue, RiqEntity entity);
// overengineered af but it's a modified version of
// https://stackoverflow.com/a/19877141
static List<Func<EventCaller, Minigame>> loadRunners;
static void BuildLoadRunnerList() {
loadRunners = System.Reflection.Assembly.GetExecutingAssembly()
.GetTypes()
.Where(x => x.Namespace == "HeavenStudio.Games.Loaders" && x.GetMethod("AddGame", BindingFlags.Public | BindingFlags.Static) != null)
.Select(t => (Func<EventCaller, Minigame>) Delegate.CreateDelegate(
typeof(Func<EventCaller, Minigame>),
null,
t.GetMethod("AddGame", BindingFlags.Public | BindingFlags.Static),
false
))
.ToList();
}
public static void Init(EventCaller eventCaller)
{
eventCaller.minigames = new List<Minigame>()
{
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", "Enable or disable the black screen for this Game Switch")
},
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(0);
else
GameManager.instance.Stop(0);
}
),
new GameAction("skill star", "Skill Star", 1f, true)
{
//temp for testing
function = delegate {
var e = eventCaller.currentEntity;
HeavenStudio.Common.SkillStarManager.instance.DoStarIn(e.beat, e.length);
// BeatAction.New(HeavenStudio.Common.SkillStarManager.instance.gameObject, new List<BeatAction.Action>(){
// new BeatAction.Action(e.beat + e.length, delegate {
// HeavenStudio.Common.SkillStarManager.instance.DoStarJust();
// })
// });
}
},
new GameAction("toggle inputs", "Toggle Inputs", 0.5f, true,
new List<Param>()
{
new Param("toggle", true, "Enable Inputs")
},
delegate
{
GameManager.instance.ToggleInputs(eventCaller.currentEntity["toggle"]);
}
),
// These are still here for backwards-compatibility but are hidden in the editor
new GameAction("flash", "", 1f, true,
new List<Param>()
{
new Param("colorA", Color.white, "Start Color"),
new Param("colorB", Color.white, "End Color"),
new Param("valA", new EntityTypes.Float(0, 1, 1), "Start Opacity"),
new Param("valB", new EntityTypes.Float(0, 1, 0), "End Opacity"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease")
},
hidden: true
),
new GameAction("move camera", "", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-50, 50, 0), "Right / Left"),
new Param("valB", new EntityTypes.Float(-50, 50, 0), "Up / Down"),
new Param("valC", new EntityTypes.Float(-0, 250, 10), "In / Out"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type")
},
hidden: true ),
new GameAction("rotate camera", "", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Integer(-360, 360, 0), "Pitch"),
new Param("valB", new EntityTypes.Integer(-360, 360, 0), "Yaw"),
new Param("valC", new EntityTypes.Integer(-360, 360, 0), "Roll"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type")
},
hidden: true ),
}),
new Minigame("countIn", "Count-Ins", "", false, true, new List<GameAction>()
{
new GameAction("4 beat count-in", "4 Beat Count-In", 4f, true,
new List<Param>()
{
new Param("type", SoundEffects.CountInType.Normal, "Type", "The sounds to play 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", "The sounds to play 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, "Number", "The sound to play"),
new Param("toggle", false, "Alt", "Whether or not the alternate version should be played")
},
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", "Whether or not the alternate version should be played")
},
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", 1f, true,
new List<Param>()
{
new Param("colorA", Color.white, "Start Color"),
new Param("colorB", Color.white, "End Color"),
new Param("valA", new EntityTypes.Float(0, 1, 1), "Start Opacity"),
new Param("valB", new EntityTypes.Float(0, 1, 0), "End Opacity"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease")
}
),
new GameAction("filter", "Filter", 1f, true,
new List<Param>()
{
new Param("filter", Games.Global.Filter.FilterType.grayscale, "Filter"),
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 GameAction("move camera", "Move Camera", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-50, 50, 0), "Right / Left", "Next position on the X axis"),
new Param("valB", new EntityTypes.Float(-50, 50, 0), "Up / Down", "Next position on the Y axis"),
new Param("valC", new EntityTypes.Float(-0, 250, 10), "In / Out", "Next position on the Z axis"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type"),
new Param("axis", GameCamera.CameraAxis.All, "Axis", "The axis to move the camera on" )
}
),
new GameAction("rotate camera", "Rotate Camera", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Integer(-360, 360, 0), "Pitch", "Next rotation on the X axis"),
new Param("valB", new EntityTypes.Integer(-360, 360, 0), "Yaw", "Next rotation on the Y axis"),
new Param("valC", new EntityTypes.Integer(-360, 360, 0), "Roll", "Next rotation on the Z axis"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type"),
new Param("axis", GameCamera.CameraAxis.All, "Axis", "The axis to move the camera on" )
}
),
new GameAction("pan view", "Pan Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-50, 50, 0), "Right / Left", "Next position on the X axis"),
new Param("valB", new EntityTypes.Float(-50, 50, 0), "Up / Down", "Next position on the Y axis"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type"),
new Param("axis", StaticCamera.ViewAxis.All, "Axis", "The axis to pan the viewport in" )
}
),
new GameAction("rotate view", "Rotate Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(-360, 360, 0), "Rotation", "Next viewport rotation"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type"),
}
),
new GameAction("scale view", "Scale Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(0, 50, 1), "Width", "Next viewport width"),
new Param("valB", new EntityTypes.Float(0, 50, 1), "Height", "Next viewport height"),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease Type"),
new Param("axis", StaticCamera.ViewAxis.All, "Axis", "The axis to scale the viewport in" )
}
),
new GameAction("screen shake", "Screen Shake", 1f, true,
new List<Param>()
{
new Param("valA", new EntityTypes.Float(0, 10, 0), "Horizontal Intensity"),
new Param("valB", new EntityTypes.Float(0, 10, 1), "Vertical Intensity")
}
),
new GameAction("display textbox", "Display Textbox", 1f, true, new List<Param>()
{
new Param("text1", "", "Text", "The text to display in the textbox (Rich Text is supported!)"),
new Param("type", Games.Global.Textbox.TextboxAnchor.TopMiddle, "Anchor", "Where to anchor the textbox"),
new Param("valA", new EntityTypes.Float(0.25f, 4, 1), "Textbox Width", "Textbox width multiplier"),
new Param("valB", new EntityTypes.Float(0.5f, 8, 1), "Textbox Height", "Textbox height multiplier")
}
),
new GameAction("display open captions", "Display Open Captions", 1f, true,
new List<Param>()
{
new Param("text1", "", "Text", "The text to display in the captions (Rich Text is supported!)"),
new Param("type", Games.Global.Textbox.TextboxAnchor.BottomMiddle, "Anchor", "Where to anchor the captions"),
new Param("valA", new EntityTypes.Float(0.25f, 4, 1), "Captions Width", "Captions width multiplier"),
new Param("valB", new EntityTypes.Float(0.5f, 8, 1), "Captions Height", "Captions height multiplier")
}
),
new GameAction("display closed captions", "Display Closed Captions", 1f, true,
new List<Param>()
{
new Param("text1", "", "Text", "The text to display in the captions (Rich Text is supported!)"),
new Param("type", Games.Global.Textbox.ClosedCaptionsAnchor.Top, "Anchor", "Where to anchor the captions"),
new Param("valA", new EntityTypes.Float(0.5f, 4, 1), "Captions Height", "Captions height multiplier")
}
),
new GameAction("display song artist", "Display Song Info", 1f, true,
new List<Param>()
{
new Param("text1", "", "Title", "Text to display in the upper label (Rich Text is supported!)"),
new Param("text2", "", "Artist", "Text to display in the lower label (Rich Text is supported!)"),
}
),
}),
};
BuildLoadRunnerList();
foreach(var load in loadRunners)
{
Debug.Log("Running game loader " + RuntimeReflectionExtensions.GetMethodInfo(load).DeclaringType.Name);
eventCaller.minigames.Add(load(eventCaller));
}
}
}
}