diff --git a/.bin/.gitignore b/.bin/.gitignore index dbe7ada..937d6c9 100644 --- a/.bin/.gitignore +++ b/.bin/.gitignore @@ -1,3 +1,4 @@ * !/.gitignore !/fonts +!/songs diff --git a/.bin/songs/list.json b/.bin/songs/list.json new file mode 100644 index 0000000..22be6a8 --- /dev/null +++ b/.bin/songs/list.json @@ -0,0 +1,15 @@ +[ + { + "name": "test-60bpm", + "bpm": 60, + "music": "test-60bpm.ogg", + "script": "test-60bpm.sj", + + "preview": { + "play-offset": 1000, + "play-loop": 2000, + "bg-inner-color": [0.8, 0.8, 0.8, 1], + "bg-outer-color": [0.8, 0.8, 0.8, 1] + } + } +] diff --git a/src/sj/Game.d b/src/sj/Game.d index ffe1f22..708f68b 100644 --- a/src/sj/Game.d +++ b/src/sj/Game.d @@ -1,11 +1,17 @@ /// License: MIT module sj.Game; +import std.algorithm, + std.file, + std.json, + std.path; + import sj.AbstractGame, sj.FontSet, sj.LobbyWorld, sj.ProgramSet, sj.SelectScene, + sj.Song, sj.TitleScene; /// @@ -13,13 +19,19 @@ class Game : AbstractGame { public: /// this() { + const path = thisExePath.dirName; + + const songs_dir = buildPath(path, "songs"); + const songs_list = buildPath(songs_dir, "list.json").readText; + songs_ = Song.CreateFromJson(songs_list.parseJSON, songs_dir); + programs_ = new ProgramSet; fonts_ = new FontSet; lobby_ = new LobbyWorld(programs_); title_ = new TitleScene(lobby_, programs_); - select_ = new SelectScene(lobby_, programs_); + select_ = new SelectScene(lobby_, programs_, songs_); title_.SetupSceneDependency(select_); select_.SetupSceneDependency(title_); @@ -35,9 +47,13 @@ class Game : AbstractGame { programs_.destroy(); fonts_.destroy(); + + songs_.each!destroy(); } private: + Song[] songs_; + ProgramSet programs_; FontSet fonts_; diff --git a/src/sj/SelectScene.d b/src/sj/SelectScene.d index 08890ad..0471ebd 100644 --- a/src/sj/SelectScene.d +++ b/src/sj/SelectScene.d @@ -12,6 +12,7 @@ import sj.KeyInput, sj.LobbyWorld, sj.ProgramSet, sj.SceneInterface, + sj.Song, sj.TitleScene, sj.util.Animation, sj.util.Easing, @@ -22,8 +23,9 @@ class SelectScene : SceneInterface { public: /// - this(LobbyWorld lobby, ProgramSet program) { + this(LobbyWorld lobby, ProgramSet program, Song[] songs) { lobby_ = lobby; + songs_ = songs.dup; sound_ = sfSound_create(); soundres_.Load(); @@ -80,12 +82,12 @@ class SelectScene : SceneInterface { LobbyWorld lobby_; - sfSound* sound_; + Song[] songs_; + sfSound* sound_; SoundResources soundres_; - FirstSetupState first_state_; - + FirstSetupState first_state_; AbstractSceneState status_; } @@ -145,7 +147,7 @@ private class FirstSetupState : AbstractSceneState { owner.lobby_.background.outer_color = bg_outer_ease_.Calculate(ratio); if (anime_.isFinished) { - stage_appear_state_.Initialize(); + stage_appear_state_.Initialize(0); return CreateResult(stage_appear_state_); } return CreateResult(this); @@ -170,8 +172,11 @@ private class StageAppearState : AbstractSceneState { enum AnimeFrames = 30; enum CubeRotationSpeed = vec3(0, PI/500, 0); - void Initialize() { // TODO: pass a stage data + void Initialize(size_t song_index) { + song_index_ = song_index; + anime_ = Animation(AnimeFrames); + cube_interval_ease_ = Easing!float(owner.lobby_.cube_interval, 0.005); sfSound_setBuffer(owner.sound_, owner.soundres_.spotlight); @@ -187,6 +192,8 @@ private class StageAppearState : AbstractSceneState { } private: + size_t song_index_; + Animation anime_; Easing!float cube_interval_ease_; diff --git a/src/sj/Song.d b/src/sj/Song.d new file mode 100644 index 0000000..bdbd8f7 --- /dev/null +++ b/src/sj/Song.d @@ -0,0 +1,96 @@ +/// License: MIT +module sj.Song; + +import std.array, + std.conv, + std.exception, + std.json, + std.path, + std.string, + std.typecons; + +import derelict.sfml2.audio, + derelict.sfml2.system; + +import gl4d; + +static import sjplayer; + +/// +class Song { + public: + /// + static struct PreviewConfig { + public: + /// + size_t play_offset; + + /// + Nullable!vec4 bg_inner_color; + /// + Nullable!vec4 bg_outer_color; + } + + /// + static Song[] CreateFromJson(in JSONValue json, string basepath) { + auto result = appender!(Song[]); + result.reserve(json.array.length); + + foreach (item; json.array) { + result ~= new Song(item, basepath); + } + return result[]; + } + + /// + this(in JSONValue json, string basepath) { + const music_path = buildPath(basepath, json["music"].str); + + name_ = json["name"].str; + bpm_ = GetNumericAsFloatFromJson(json["bpm"]); + music_ = sfMusic_createFromFile(music_path.toStringz).enforce; + script_path_ = buildPath(basepath, json["script"].str); + + // TODO: update preview config + } + ~this() { + sfMusic_destroy(music_); + } + + /// + void PlayForGame() { + sfMusic_setPlayingOffset(music_, sfMilliseconds(0)); + sfMusic_play(music_); + } + /// + void PlayForPreview() { + sfMusic_setPlayingOffset( + music_, sfMilliseconds(preview_.play_offset.to!int)); + sfMusic_play(music_); + } + + /// + sjplayer.Context CreatePlayerContext() const { + assert(false); // TODO: + } + + /// + @property ref const(PreviewConfig) preview() const { + return preview_; + } + + private: + static float GetNumericAsFloatFromJson(in JSONValue json) { + return json.type == JSONType.float_? json.floating: json.integer; + } + + string name_; + + float bpm_; + + sfMusic* music_; + + string script_path_; + + PreviewConfig preview_; +}