Advanced Blocks (#720)

* play sfx and play animation blocks

i also changed prescheduleFunction to preFunction, and removed the unused preFunction argument in GameAction
i can revert this if need be but it just seemed vestigial

* count in rework + preloading, multisound addition

multisound was using an array that was converted to a list..?
very silly when you consider it's a list first so sometimes it's list -> array -> list lol

new Count-In and Play SFX block preloads sfx now!! epic.

* prefab-ify event properties, Button EntityType

* things are very nearly working!

however i just hit an insane hurdle. how do i modify a dropdown while still being able to access the index/int value of that param directly. UGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH

* okay it's WORKING now

i just need to do some better dropdown stuff

* ITS WORKING ITS WORKING ITS WORKING

arbitrary animations, now accessible to those without prefab knowledge! and it's piss easy to use!!

* about to make a struct + class, tooltip improvements

gonna make the struct define it, then the class will actually be the dropdown
this is gonna make things so so so so much easier to comprehend

* finishing up, probably one more commit after this

* split up Dropdown into Dropdown and DropdownObj, which basically fixed all of my problems lol
* fixed a count bug
* added param tooltip toggle

* grah it's ALMOST DONE

* it's 99.9% finished.

just some touch ups, i don't think i even know of any bugs

* alright, looks like that's all the bugs gone

* EVERYTHING IS FINISHED!!
This commit is contained in:
AstrlJelly
2024-02-25 20:46:23 -05:00
committed by GitHub
parent bdbe5c9c9c
commit caf124a8cd
45 changed files with 7877 additions and 6212 deletions

View File

@ -5,7 +5,6 @@ using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using DG.Tweening;
using HeavenStudio.Util;
using HeavenStudio.Editor.Track;
@ -15,9 +14,10 @@ using Jukebox;
using SatorImaging.UnitySourceGenerator;
using System;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Linq;
using UnityEngine.Assertions.Must;
using Newtonsoft.Json.Linq;
namespace HeavenStudio
{
@ -267,6 +267,13 @@ namespace HeavenStudio
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)
@ -635,11 +642,10 @@ namespace HeavenStudio
/// <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="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="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)
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;
@ -651,7 +657,7 @@ namespace HeavenStudio
this.function = function ?? delegate { };
this.inactiveFunction = inactiveFunction ?? delegate { };
this.preFunction = prescheduleFunction ?? delegate { };
this.preFunction = preFunction ?? delegate { };
this.priority = priority;
this.preFunctionLength = preFunctionLength;
}
@ -674,7 +680,7 @@ namespace HeavenStudio
{
public string propertyName;
public object parameter;
public string propertyCaption;
public string caption;
public string tooltip;
public List<CollapseParam> collapseParams;
@ -683,12 +689,12 @@ namespace HeavenStudio
/// </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 = "", List<CollapseParam> collapseParams = null)
/// <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.propertyCaption = propertyCaption;
this.caption = caption;
this.tooltip = tooltip;
this.collapseParams = collapseParams;
}
@ -702,7 +708,7 @@ namespace HeavenStudio
/// </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, string[] collapseables)
public CollapseParam(Func<object, RiqEntity, bool> collapseOn, params string[] collapseables)
{
CollapseOn = collapseOn;
this.collapseables = collapseables;
@ -758,19 +764,36 @@ namespace HeavenStudio
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"]); }
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"]); }
delegate {
var e = eventCaller.currentEntity;
SoundEffects.EightBeatCountIn(e.beat, e.length / 8f, e["type"]);
}
),
new GameAction("count", "Count", 1f, false,
new List<Param>()
@ -778,13 +801,16 @@ namespace HeavenStudio
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"]); }
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); }
function: delegate { var e = eventCaller.currentEntity; SoundEffects.Ready(e.beat, (e.length / 2f)); }
),
new GameAction("and", "And", 0.5f,
function: delegate { SoundEffects.And(); }
@ -816,7 +842,7 @@ namespace HeavenStudio
new Minigame("vfx", "Visual Effects", "", false, true, new List<GameAction>()
{
new GameAction("flash", "Flash", 1f, true,
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."),
@ -853,7 +879,7 @@ namespace HeavenStudio
{
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(-0, 250, 10), "In / Out", "Set the position on the Z 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." )
}
@ -883,8 +909,8 @@ namespace HeavenStudio
),
new GameAction("scale view", "Scale Viewport", 1f, true, new List<Param>()
{
new Param("valA", new EntityTypes.Float(0, 50, 1), "Width", "Set the width of the viewport."),
new Param("valB", new EntityTypes.Float(0, 50, 1), "Height", "Set the height of the viewport."),
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." )
}
@ -894,10 +920,10 @@ namespace HeavenStudio
resizable = true,
parameters = new()
{
new("x1", new EntityTypes.Float(0f, 50f, 1f), "Start Width", "Set the width at the start of the event."),
new("x2", new EntityTypes.Float(0f, 50f, 1f), "End Width", "Set the width at the end of the event."),
new("y1", new EntityTypes.Float(0f, 50f, 1f), "Start Height", "Set the height at the start of the event."),
new("y2", new EntityTypes.Float(0f, 50f, 1f), "End Height", "Set the height at the end of the event."),
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" })
@ -1111,7 +1137,7 @@ namespace HeavenStudio
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 Param("axis", StaticCamera.ViewAxis.All, "Axis", "Set if only a specific axis should be modified."),
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" })
@ -1127,7 +1153,7 @@ namespace HeavenStudio
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 Param("axis", StaticCamera.ViewAxis.All, "Axis", "Set if only a specific axis should be modified."),
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" })
@ -1135,6 +1161,105 @@ namespace HeavenStudio
}
}
}),
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) {
var animators = gm.minigameObj.transform.GetComponentsInChildren<Animator>();
// not in an update loop so it's fine :3
((EntityTypes.DropdownObj)e["animator"]).SetValues(animators.Select(anim => {
var obj = anim.gameObject;
List<string> path = new() { obj.name };
for (int i = 0; i < 10; i++)
{
if (obj.transform.parent == null || obj.transform.parent.name == game.name) break;
obj = obj.transform.parent.gameObject;
path.Add(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"),
new((x, e) => !(bool)x, "pitch"),
}),
new Param("semitones", new EntityTypes.Integer(-24, 24, 0), "Semitones", "The semitones 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["useSemitones"] ? SoundByte.GetPitchFromSemiTones(e["semitones"], true) : e["pitch"];
GameManager.PlaySFXArbitrary(e.beat, e.length, e["game"].CurrentValue, e["sfxName"].CurrentValue, pitch, e["volume"], e["loop"], e["offset"]);
}
),
}),
};
foreach (var game in defaultGames)
@ -1143,6 +1268,10 @@ namespace HeavenStudio
}
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());
}
}
}
}