Title Screen (#454)

* Barebones title screen prefab added

* logo and stuff

* cool

* Added sfx to title screen

* The logo now bops to the beat

* epic reveal

* Fixed something

* put some of the stuff into the main menu

* other logo bop

* Implemented logobop2 and starbop

* added scrolling bg, tweaked positioning and wip splash text for play button

* more menu

* ooops

* Expand implemented

* cool

* Made stars spawn in in the opening

* make UI elements look nicer on different aspect ratios

* add sound while hovering over logo

* add settings menu to title screen

make the title screen properly play after the opening

* swap out title screen hover sound

remove the old config path warning

* every button works, some play mode fixes

* fix issues with beataction/multisound and pausing

* fix dropdown menus not working in certain screens

* fix particles rotating when camera controls are used

* touch style pause menu items only trigger if cursor is over an item

* various changes

make playback (unpausing) more reliable
only apply changes to advanced audio settings on launch
fix title screen visuals
add opening music
continue past opening by pressing a key
update credits

* almost forgot this

* lol

* initial flow mems

* user-taggable fonts in textboxes

* alt materials for kurokane

* assets prep

* plan out judgement screen layout

change sound encodings

* start sequencing judgement

* judgement screen sequence

* full game loop

* fix major issue with pooled sound objects

rebalance ranking audio
fix issues with some effects in play mode

* new graphics

* particles

* make certain uses of the beat never go below 0

fix loop of superb music

* make markers non clamped

lockstep frees rendertextures when unloading

* lockstep creates its own rendertextures

swapped button order on title screen
added null checks to animation helpers
disabled controller auto-search for now

* enable particles on OK rank

* play mode info panel

* let play mode handle its own fade out

* fix that alignment bug in controller settings

* more safety here

* Update PauseMenu.cs

* settable (one-liner) rating screen text

* address minigame loading crashes

* don't do this twice

* wav converter for mp3

* Update Minigames.cs

* don't double-embed the converted audio

* studio dance bugfixing spree

* import redone sprites for studio dance

* update jukebox

prep epilogue screen

* epilogue screen

* studio dance inkling shuffle test

* new studio dance choreo system

* markers upgrade

* fix deleting volume changes and markers

prep category markers

* Update Editor.unity

* new rating / epilogue settings look

* update to use new tooltip system

mark certain editor components as blocking

* finish category system

* dedicated tempo / volume marker dialogs

* swing prep

* open properties dialog if mapper hasn't opened it prior for this chart

fix memory copy bug when making new chart

* fix ctrl + s

* return to title screen button

* make graphy work everywhere

studio dance selector
membillion mems

* make sure riq cache is clear when loading chart

* lol

* fix the stupid

* bring back tempo and volume change scrolling

* new look for panels

* adjust alignment

* round tooltip

* alignment of chart property prefab

* change scale factor of mem

* adjust open captions material

no dotted BG in results commentary (only epilogue)
bugfix for tempo / volume scroll

* format line 2 of judgement a bit better

update font

* oops

* adjust look of judgement score bar

* new rating bar

* judgement size adjustment

* fix timing window scaling with song pitch

* proper clamping in dialogs

better sync conductor to dsptime (experiment)

* disable timeline pitch change when song is paused

enable perfect challenge if no marker is set to do so

* new app icon

* timing window values are actually double now

* improve deferred timekeep even more

* re-generate font atlases

new app icon in credits

* default epilogue images

* more timing window adjustment

* fix timing display when pitched

* use proper terminology here

* new logo on titlescreen

* remove wip from play

update credits

* adjust spacing of play mode panel

* redo button spacing

* can pass title screen with any controller

fix issues with controller auto-search

* button scale fixes

* controller title screen nav

* remove song genre from properties editor

* disable circle cursor when not using touch style

* proper selection graphic

remove refs
re-add heart to the opening

* controller support in opening

---------

Co-authored-by: ev <85412919+evdial@users.noreply.github.com>
Co-authored-by: minenice55 <star.elementa@gmail.com>
Co-authored-by: ThatZeoMan <67521686+ThatZeoMan@users.noreply.github.com>
This commit is contained in:
Rapandrasmus
2023-12-26 06:22:51 +01:00
committed by GitHub
parent 8d8c275e66
commit 98835c3936
1314 changed files with 194146 additions and 11622 deletions

View File

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using DG.Tweening;
using HeavenStudio.Util;
@ -32,77 +33,83 @@ namespace HeavenStudio
public static void InitPreprocessor()
{
RiqBeatmap.OnUpdateBeatmap += PreProcessBeatmap;
RiqFileHandler.AudioConverter = JukeboxAudioConverter;
}
public static Dictionary<string, object> propertiesModel = new()
{
// mapper set properties? (future: use this to flash the button)
{"propertiesmodified", false},
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)
{"iconurl", ""}, // custom icon location (future)
{"challengetype", 0}, // perfect challenge type
{"playstyle", RecommendedControlStyle.Any}, // recommended control style
////// 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
{"playstyle", RecommendedControlStyle.Any}, // recommended control style
// chart song info
{"idolgenre", "Song Genre"}, // song genre
{"idolsong", "Song Name"}, // song name
{"idolcredit", "Artist"}, // song artist
// 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)
////// 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)
// 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)
// 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)
{"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)
{"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)
{"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)
{"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)
{"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)
{"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)
};
{"epilogue_hi", "Superb picture"}, // epilogue "Superb" message
{"epilogue_ok", "OK picture"}, // epilogue "OK" message
{"epilogue_ng", "Try Again picture"}, // epilogue "Try Again" message
static Dictionary<string, object> tempoChangeModel = new()
{"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},
{"timeSignature", new Vector2(4, 4)},
};
static Dictionary<string, object> volumeChangeModel = new()
readonly static Dictionary<string, object> volumeChangeModel = new()
{
{"volume", 1f},
{"fade", Util.EasingFunction.Ease.Instant},
};
static Dictionary<string, object> sectionMarkModel = new()
readonly static Dictionary<string, object> sectionMarkModel = new()
{
{"sectionName", ""},
{"isCheckpoint", false},
{"startPerfect", false},
{"breakSection", false},
{"extendsPrevious", false},
{"sectionWeight", 1f},
{"weight", 1f},
{"category", 0},
};
static void PreProcessSpecialEntity(RiqEntity e, Dictionary<string, object> model)
@ -142,6 +149,38 @@ namespace HeavenStudio
}
}
public static string JukeboxAudioConverter(string filePath, AudioType audioType, string specificType)
{
if (Directory.Exists(Path.Combine(Application.temporaryCachePath, "/savewav")))
{
Directory.Delete(Path.Combine(Application.temporaryCachePath, "/savewav"), true);
}
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("/savewav/" + fileName, clip, true);
filePath = Path.Combine(Application.temporaryCachePath, "/savewav/" + fileName + ".wav");
clip = null;
}
}
return filePath;
}
/// <summary>
/// processes an riq beatmap after it is loaded
/// </summary>
@ -151,96 +190,101 @@ namespace HeavenStudio
Minigames.Minigame game;
Minigames.GameAction action;
System.Type type, pType;
foreach (var e in data.entities)
if (EventCaller.instance != null)
{
var gameName = e.datamodel.Split(0);
var actionName = e.datamodel.Split(1);
game = EventCaller.instance.GetMinigame(gameName);
if (game == null)
foreach (var e in data.entities)
{
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)
var gameName = e.datamodel.Split(0);
var actionName = e.datamodel.Split(1);
game = EventCaller.instance.GetMinigame(gameName);
if (game == null)
{
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]"));
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 = 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)
action = EventCaller.instance.GetGameAction(game, actionName);
if (action == null)
{
type = param.parameter.GetType();
//add property if it doesn't exist
if (!e.dynamicData.ContainsKey(param.propertyName))
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.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;
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]"));
}
pType = e[param.propertyName].GetType();
if (pType != type)
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)
{
try
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[param.propertyName] = (int)e[param.propertyName];
e.dynamicData.Add(param.propertyName, ((EntityTypes.Integer)param.parameter).val);
else if (type == typeof(EntityTypes.Float))
e.dynamicData[param.propertyName] = (float)e[param.propertyName];
e.dynamicData.Add(param.propertyName, ((EntityTypes.Float)param.parameter).val);
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);
e.dynamicData.Add(param.propertyName, (int)param.parameter);
else
e.dynamicData[param.propertyName] = Convert.ChangeType(e[param.propertyName], type);
e.dynamicData.Add(param.propertyName, Convert.ChangeType(param.parameter, type));
continue;
}
catch
pType = e[param.propertyName].GetType();
if (pType != type)
{
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);
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 == 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.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);
}
}
}
}
@ -269,10 +313,25 @@ namespace HeavenStudio
//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]);
}
// 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;
@ -364,6 +423,13 @@ namespace HeavenStudio
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
@ -379,6 +445,13 @@ namespace HeavenStudio
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)
@ -401,6 +474,14 @@ namespace HeavenStudio
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;
@ -414,6 +495,14 @@ namespace HeavenStudio
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;
@ -655,7 +744,7 @@ namespace HeavenStudio
if (Timeline.instance != null)
Timeline.instance?.Stop(Timeline.instance.PlaybackBeat);
else
GameManager.instance.Stop(0);
GameManager.instance.Stop(eventCaller.currentEntity.beat);
}
),
new GameAction("skill star", "Skill Star", 1f, true)