mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2025-04-22 03:34:10 -04:00
Merge pull request #12 from ninjamuffin99/feature/scripted-modules
Polymod - Scripted Modules
This commit is contained in:
commit
d84207c1b4
62 changed files with 3693 additions and 2052 deletions
.gitignore
.vscode
Project.xmlexample_mods
source
Main.hx
funkin
Character.hxConductor.hxDialogueBox.hxFreeplayState.hxGameOverSubstate.hxHealthIcon.hxInitState.hxLatencyState.hxLoadingState.hxMainMenuState.hxMusicBeatState.hxMusicBeatSubstate.hxNote.hxPauseSubState.hxStoryMenuState.hxTitleState.hx
animate
audiovis
charting
freeplayStuff
i18n
modding
play
ui
util
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -2,7 +2,4 @@ export/
|
|||
.vscode/
|
||||
APIStuff.hx
|
||||
.DS_STORE
|
||||
|
||||
example_mods/enaSkin/
|
||||
|
||||
example_mods/tricky/
|
||||
RECOVER_*.fla
|
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
|
@ -12,6 +12,21 @@
|
|||
"name": "Haxe Eval",
|
||||
"type": "haxe-eval",
|
||||
"request": "launch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "HTML5 Debug",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://127.0.0.1:3001",
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"preLaunchTask": "debug: html"
|
||||
},
|
||||
{
|
||||
"name": "Mac (Debug)",
|
||||
"type": "hxcpp",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/export/debug/macos/bin/Funkin.app/Contents/MacOS/Funkin",
|
||||
"preLaunchTask": "debug: mac"
|
||||
]
|
||||
}
|
||||
|
|
12
Project.xml
12
Project.xml
|
@ -96,7 +96,13 @@
|
|||
|
||||
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
||||
|
||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md" />
|
||||
<!--
|
||||
AUTOMATICALLY MOVING EXAMPLE MODS INTO THE BUILD CAUSES ISSUES
|
||||
Currently, this line will add the mod files to the library manifest,
|
||||
which causes issues if the mod is not enabled.
|
||||
If we can exclude the `mods` folder from the manifest, we can re-enable this line.
|
||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md" />
|
||||
-->
|
||||
<assets path='art/readme.txt' rename='do NOT readme.txt' />
|
||||
|
||||
<assets path="CHANGELOG.md" rename='changelog.txt' />
|
||||
|
@ -122,7 +128,7 @@
|
|||
<!--haxelib name="newgrounds" unless="switch"/> -->
|
||||
<haxelib name="faxe" if='switch' />
|
||||
<haxelib name="polymod" />
|
||||
<haxelib name="firetongue" />
|
||||
<haxelib name="thx.semver" />
|
||||
|
||||
<!-- <haxelib name="colyseus"/> -->
|
||||
<!-- <haxelib name="colyseus-websocket" /> -->
|
||||
|
@ -191,7 +197,7 @@
|
|||
<haxeflag name="--macro" value="include('funkin')" />
|
||||
|
||||
<!-- Necessary to provide stack traces for HScript. -->
|
||||
<haxedef name="hscriptPos" />
|
||||
<haxedef name="hscriptPos" />
|
||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||
<haxedef name="HXCPP_STACK_LINE" />
|
||||
|
||||
|
|
6
example_mods/.gitignore
vendored
6
example_mods/.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
./tricky
|
||||
./enaSkin
|
||||
# Exclude any experimental mods that my have been added.
|
||||
/*
|
||||
!introMod
|
||||
!testing123
|
6
example_mods/introMod/README.md
Normal file
6
example_mods/introMod/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# introMod
|
||||
|
||||
This intro mod demonstrates two simple things:
|
||||
|
||||
1. You can replace any game asset simply by placing a modded asset in the right spot.
|
||||
2. You can append to text files simply by placing a text file in the right spot, but under the `_append` directory.
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"title": "Intro Mod",
|
||||
"description": "An introductory mod.",
|
||||
"author": "MasterEric",
|
||||
"contributors": [{
|
||||
"name": "MasterEric"
|
||||
}],
|
||||
"api_version": "0.1.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"title": "Testing123",
|
||||
"description": "Newgrounds? More like OLDGROUNDS lol.",
|
||||
"author": "MasterEric",
|
||||
"contributors": [{
|
||||
"name": "MasterEric"
|
||||
}],
|
||||
"api_version": "0.1.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package;
|
||||
|
||||
import funkin.InitState;
|
||||
import funkin.MemoryCounter;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.InitState;
|
||||
import funkin.MemoryCounter;
|
||||
import openfl.Lib;
|
||||
import openfl.display.FPS;
|
||||
import openfl.display.Sprite;
|
||||
|
@ -37,17 +37,9 @@ class Main extends Sprite
|
|||
{
|
||||
super();
|
||||
|
||||
// TODO: Ideally this should change to utilize a user interface.
|
||||
// 1. Call PolymodHandler.getAllMods(). This gives you an array of ModMetadata items,
|
||||
// each of which contains information about the mod including an icon.
|
||||
// 2. Provide an interface to enable, disable, and reorder enabled mods.
|
||||
// A design similar to that of Minecraft resource packs would be intuitive.
|
||||
// 3. The interface should save (to the save file) and output an ordered array of mod IDs.
|
||||
// 4. Replace the call to PolymodHandler.loadAllMods() with a call to PolymodHandler.loadModsById(ids:Array<String>).
|
||||
// TODO: Replace this with loadEnabledMods().
|
||||
funkin.modding.PolymodHandler.loadAllMods();
|
||||
|
||||
funkin.i18n.FireTongueHandler.init();
|
||||
|
||||
if (stage != null)
|
||||
{
|
||||
init();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.Section.SwagSection;
|
||||
|
@ -128,7 +129,7 @@ class Character extends FlxSprite
|
|||
|
||||
playAnim('danceRight');
|
||||
|
||||
setGraphicSize(Std.int(width * PlayState.daPixelZoom));
|
||||
setGraphicSize(Std.int(width * Constants.PIXEL_ART_SCALE));
|
||||
updateHitbox();
|
||||
antialiasing = false;
|
||||
|
||||
|
|
|
@ -15,9 +15,31 @@ typedef BPMChangeEvent =
|
|||
|
||||
class Conductor
|
||||
{
|
||||
/**
|
||||
* Beats per minute of the song.
|
||||
*/
|
||||
public static var bpm:Float = 100;
|
||||
public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds
|
||||
public static var stepCrochet:Float = crochet / 4; // steps in milliseconds
|
||||
|
||||
/**
|
||||
* Duration of a beat in millisecond.
|
||||
*/
|
||||
public static var crochet(get, null):Float;
|
||||
|
||||
static function get_crochet():Float
|
||||
{
|
||||
return ((60 / bpm) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration of a step in milliseconds.
|
||||
*/
|
||||
public static var stepCrochet(get, null):Float;
|
||||
|
||||
static function get_stepCrochet():Float
|
||||
{
|
||||
return crochet / 4;
|
||||
}
|
||||
|
||||
public static var songPosition:Float;
|
||||
public static var lastSongPos:Float;
|
||||
public static var offset:Float = 0;
|
||||
|
@ -52,12 +74,4 @@ class Conductor
|
|||
}
|
||||
trace("new BPM map BUDDY " + bpmChangeMap);
|
||||
}
|
||||
|
||||
public static function changeBPM(newBpm:Float)
|
||||
{
|
||||
bpm = newBpm;
|
||||
|
||||
crochet = ((60 / bpm) * 1000);
|
||||
stepCrochet = crochet / 4;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.text.FlxTypeText;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
|
@ -38,7 +39,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
{
|
||||
super();
|
||||
|
||||
switch (PlayState.SONG.song.toLowerCase())
|
||||
switch (PlayState.currentSong.song.toLowerCase())
|
||||
{
|
||||
case 'senpai':
|
||||
FlxG.sound.playMusic(Paths.music('Lunchbox'), 0);
|
||||
|
@ -63,7 +64,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
portraitLeft = new FlxSprite(-20, 40);
|
||||
portraitLeft.frames = Paths.getSparrowAtlas('weeb/senpaiPortrait');
|
||||
portraitLeft.animation.addByPrefix('enter', 'Senpai Portrait Enter', 24, false);
|
||||
portraitLeft.setGraphicSize(Std.int(portraitLeft.width * PlayState.daPixelZoom * 0.9));
|
||||
portraitLeft.setGraphicSize(Std.int(portraitLeft.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
portraitLeft.updateHitbox();
|
||||
portraitLeft.scrollFactor.set();
|
||||
add(portraitLeft);
|
||||
|
@ -72,7 +73,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
portraitRight = new FlxSprite(0, 40);
|
||||
portraitRight.frames = Paths.getSparrowAtlas('weeb/bfPortrait');
|
||||
portraitRight.animation.addByPrefix('enter', 'Boyfriend portrait enter', 24, false);
|
||||
portraitRight.setGraphicSize(Std.int(portraitRight.width * PlayState.daPixelZoom * 0.9));
|
||||
portraitRight.setGraphicSize(Std.int(portraitRight.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
portraitRight.updateHitbox();
|
||||
portraitRight.scrollFactor.set();
|
||||
add(portraitRight);
|
||||
|
@ -81,7 +82,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
box = new FlxSprite(-20, 45);
|
||||
|
||||
var hasDialog = false;
|
||||
switch (PlayState.SONG.song.toLowerCase())
|
||||
switch (PlayState.currentSong.song.toLowerCase())
|
||||
{
|
||||
case 'senpai':
|
||||
hasDialog = true;
|
||||
|
@ -113,7 +114,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
return;
|
||||
|
||||
box.animation.play('normalOpen');
|
||||
box.setGraphicSize(Std.int(box.width * PlayState.daPixelZoom * 0.9));
|
||||
box.setGraphicSize(Std.int(box.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
box.updateHitbox();
|
||||
add(box);
|
||||
|
||||
|
@ -121,7 +122,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
portraitLeft.screenCenter(X);
|
||||
|
||||
handSelect = new FlxSprite(1042, 590).loadGraphic(Paths.image('weeb/pixelUI/hand_textbox'));
|
||||
handSelect.setGraphicSize(Std.int(handSelect.width * PlayState.daPixelZoom * 0.9));
|
||||
handSelect.setGraphicSize(Std.int(handSelect.width * Constants.PIXEL_ART_SCALE * 0.9));
|
||||
handSelect.updateHitbox();
|
||||
handSelect.visible = false;
|
||||
add(handSelect);
|
||||
|
@ -154,9 +155,9 @@ class DialogueBox extends FlxSpriteGroup
|
|||
override function update(elapsed:Float)
|
||||
{
|
||||
// HARD CODING CUZ IM STUPDI
|
||||
if (PlayState.SONG.song.toLowerCase() == 'roses')
|
||||
if (PlayState.currentSong.song.toLowerCase() == 'roses')
|
||||
portraitLeft.visible = false;
|
||||
if (PlayState.SONG.song.toLowerCase() == 'thorns')
|
||||
if (PlayState.currentSong.song.toLowerCase() == 'thorns')
|
||||
{
|
||||
portraitLeft.color = FlxColor.BLACK;
|
||||
swagDialogue.color = FlxColor.WHITE;
|
||||
|
@ -192,7 +193,7 @@ class DialogueBox extends FlxSpriteGroup
|
|||
{
|
||||
isEnding = true;
|
||||
|
||||
if (PlayState.SONG.song.toLowerCase() == 'senpai' || PlayState.SONG.song.toLowerCase() == 'thorns')
|
||||
if (PlayState.currentSong.song.toLowerCase() == 'senpai' || PlayState.currentSong.song.toLowerCase() == 'thorns')
|
||||
FlxG.sound.music.fadeOut(2.2, 0);
|
||||
|
||||
new FlxTimer().start(0.2, function(tmr:FlxTimer)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.Controls.Control;
|
||||
import flash.text.TextField;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxGame;
|
||||
|
@ -21,16 +20,17 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.Controls.Control;
|
||||
import funkin.freeplayStuff.BGScrollingText;
|
||||
import funkin.freeplayStuff.DJBoyfriend;
|
||||
import funkin.freeplayStuff.FreeplayScore;
|
||||
import funkin.freeplayStuff.SongMenuItem;
|
||||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
import funkin.shaderslmfao.StrokeShader;
|
||||
import funkin.play.PlayState;
|
||||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -238,9 +238,6 @@ class FreeplayState extends MusicBeatSubstate
|
|||
add(new DifficultySelector(20, grpDifficulties.y - 10, false, controls));
|
||||
add(new DifficultySelector(325, grpDifficulties.y - 10, true, controls));
|
||||
|
||||
var animShit:ComboCounter = new ComboCounter(100, 300, 1000000);
|
||||
// add(animShit);
|
||||
|
||||
new FlxTimer().start(1 / 24, function(handShit)
|
||||
{
|
||||
fnfFreeplay.visible = true;
|
||||
|
@ -388,7 +385,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
{
|
||||
if (FlxG.sound.music.volume < 0.7)
|
||||
{
|
||||
FlxG.sound.music.volume += 0.5 * FlxG.elapsed;
|
||||
FlxG.sound.music.volume += 0.5 * elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,7 +432,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
if (touchTimer >= 1.5)
|
||||
accepted = true;
|
||||
|
||||
touchTimer += FlxG.elapsed;
|
||||
touchTimer += elapsed;
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
|
||||
velTouch = Math.abs((touch.screenY - dyTouch)) / 50;
|
||||
|
@ -472,24 +469,6 @@ class FreeplayState extends MusicBeatSubstate
|
|||
else
|
||||
{
|
||||
touchTimer = 0;
|
||||
|
||||
/* if (velTouch >= 0)
|
||||
{
|
||||
trace(velTouch);
|
||||
velTouch -= FlxG.elapsed;
|
||||
|
||||
veloctiyLoopShit += velTouch;
|
||||
|
||||
trace("VEL LOOP: " + veloctiyLoopShit);
|
||||
|
||||
if (veloctiyLoopShit >= 30)
|
||||
{
|
||||
veloctiyLoopShit = 0;
|
||||
changeSelection(1);
|
||||
}
|
||||
|
||||
// trace(velTouch);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,6 +498,9 @@ class FreeplayState extends MusicBeatSubstate
|
|||
if (controls.BACK)
|
||||
{
|
||||
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
|
||||
|
@ -538,7 +520,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
curDifficulty = 1;
|
||||
}*/
|
||||
|
||||
PlayState.SONG = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
|
||||
PlayState.isStoryMode = false;
|
||||
PlayState.storyDifficulty = curDifficulty;
|
||||
// SongLoad.curDiff = Highscore.formatSong()
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import haxe.display.Display;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
|
||||
class GameOverSubstate extends MusicBeatSubstate
|
||||
{
|
||||
|
@ -20,12 +17,12 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
|
||||
var gameOverMusic:FlxSound;
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
public function new()
|
||||
{
|
||||
gameOverMusic = new FlxSound();
|
||||
FlxG.sound.list.add(gameOverMusic);
|
||||
|
||||
var daStage = PlayState.curStageId;
|
||||
var daStage = PlayState.instance.currentStageId;
|
||||
var daBf:String = '';
|
||||
switch (daStage)
|
||||
{
|
||||
|
@ -36,7 +33,7 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
daBf = 'bf';
|
||||
}
|
||||
|
||||
var daSong = PlayState.SONG.song.toLowerCase();
|
||||
var daSong = PlayState.currentSong.song.toLowerCase();
|
||||
|
||||
switch (daSong)
|
||||
{
|
||||
|
@ -48,16 +45,18 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
|
||||
Conductor.songPosition = 0;
|
||||
|
||||
bf = new Boyfriend(x, y, daBf);
|
||||
var bfXPos = PlayState.instance.currentStage.getBoyfriend().getScreenPosition().x;
|
||||
var bfYPos = PlayState.instance.currentStage.getBoyfriend().getScreenPosition().y;
|
||||
bf = new Boyfriend(bfXPos, bfYPos, daBf);
|
||||
add(bf);
|
||||
|
||||
camFollow = new FlxObject(bf.getGraphicMidpoint().x, bf.getGraphicMidpoint().y, 1, 1);
|
||||
add(camFollow);
|
||||
|
||||
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + stageSuffix));
|
||||
// Conductor.changeBPM(100);
|
||||
// Conductor.bpm = 100;
|
||||
|
||||
switch (PlayState.SONG.player1)
|
||||
switch (PlayState.currentSong.player1)
|
||||
{
|
||||
case 'pico':
|
||||
stageSuffix = 'Pico';
|
||||
|
|
|
@ -37,7 +37,7 @@ class HealthIcon extends FlxSprite
|
|||
if (isOldIcon)
|
||||
changeIcon('bf-old');
|
||||
else
|
||||
changeIcon(PlayState.SONG.player1);
|
||||
changeIcon(PlayState.currentSong.player1);
|
||||
}
|
||||
|
||||
var pixelArrayFunny:Array<String> = CoolUtil.coolTextFile(Paths.file('images/icons/pixelIcons.txt'));
|
||||
|
|
|
@ -8,9 +8,12 @@ import flixel.math.FlxPoint;
|
|||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.charting.ChartingState;
|
||||
import funkin.charting.ChartingState;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.PicoFight;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.animDebugShit.DebugBoundingState;
|
||||
import funkin.ui.stageBuildShit.StageBuilderState;
|
||||
|
@ -123,6 +126,8 @@ class InitState extends FlxTransitionableState
|
|||
|
||||
StageDataParser.loadStageCache();
|
||||
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
#if song
|
||||
var song = getSong();
|
||||
|
||||
|
@ -187,7 +192,7 @@ class InitState extends FlxTransitionableState
|
|||
{
|
||||
var dif = getDif();
|
||||
|
||||
PlayState.SONG = SongLoad.loadFromJson(song, song);
|
||||
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
||||
PlayState.isStoryMode = isStoryMode;
|
||||
PlayState.storyDifficulty = dif;
|
||||
SongLoad.curDiff = switch (dif)
|
||||
|
|
|
@ -31,7 +31,7 @@ class LatencyState extends FlxState
|
|||
strumLine = new FlxSprite(FlxG.width / 2, 100).makeGraphic(FlxG.width, 5);
|
||||
add(strumLine);
|
||||
|
||||
Conductor.changeBPM(120);
|
||||
Conductor.bpm = 120;
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ package funkin;
|
|||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.play.PlayState;
|
||||
import haxe.io.Path;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
|
@ -12,7 +12,6 @@ import lime.utils.AssetLibrary;
|
|||
import lime.utils.AssetManifest;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
class LoadingState extends MusicBeatState
|
||||
{
|
||||
|
@ -21,7 +20,6 @@ class LoadingState extends MusicBeatState
|
|||
var target:FlxState;
|
||||
var stopMusic = false;
|
||||
var callbacks:MultiCallback;
|
||||
|
||||
var danceLeft = false;
|
||||
|
||||
var loadBar:FlxSprite;
|
||||
|
@ -57,9 +55,9 @@ class LoadingState extends MusicBeatState
|
|||
callbacks = new MultiCallback(onLoad);
|
||||
var introComplete = callbacks.add("introComplete");
|
||||
checkLoadSong(getSongPath());
|
||||
if (PlayState.SONG.needsVoices)
|
||||
if (PlayState.currentSong.needsVoices)
|
||||
{
|
||||
var files = PlayState.SONG.voiceList;
|
||||
var files = PlayState.currentSong.voiceList;
|
||||
|
||||
if (files == null)
|
||||
files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally
|
||||
|
@ -117,17 +115,15 @@ class LoadingState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
override function beatHit()
|
||||
override function beatHit():Bool
|
||||
{
|
||||
super.beatHit();
|
||||
// super.beatHit() returns false if a module cancelled the event.
|
||||
if (!super.beatHit())
|
||||
return false;
|
||||
|
||||
// logo.animation.play('bump');
|
||||
danceLeft = !danceLeft;
|
||||
/*
|
||||
if (danceLeft)
|
||||
gfDance.animation.play('danceRight');
|
||||
else
|
||||
gfDance.animation.play('danceLeft'); */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var targetShit:Float = 0;
|
||||
|
@ -173,12 +169,12 @@ class LoadingState extends MusicBeatState
|
|||
|
||||
static function getSongPath()
|
||||
{
|
||||
return Paths.inst(PlayState.SONG.song);
|
||||
return Paths.inst(PlayState.currentSong.song);
|
||||
}
|
||||
|
||||
static function getVocalPath(?suffix:String)
|
||||
{
|
||||
return Paths.voices(PlayState.SONG.song, suffix);
|
||||
return Paths.voices(PlayState.currentSong.song, suffix);
|
||||
}
|
||||
|
||||
inline static public function loadAndSwitchState(target:FlxState, stopMusic = false)
|
||||
|
@ -198,7 +194,7 @@ class LoadingState extends MusicBeatState
|
|||
}
|
||||
#if NO_PRELOAD_ALL
|
||||
var loaded = isSoundLoaded(getSongPath())
|
||||
&& (!PlayState.SONG.needsVoices || isSoundLoaded(getVocalPath()))
|
||||
&& (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath()))
|
||||
&& isLibraryLoaded("shared");
|
||||
|
||||
if (!loaded)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.NGio;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
|
@ -15,14 +14,18 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.ui.FlxButton;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.app.Application;
|
||||
import openfl.filters.ShaderFilter;
|
||||
import funkin.NGio;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
import funkin.ui.AtlasMenuList;
|
||||
import funkin.ui.MenuList;
|
||||
import funkin.ui.OptionsState;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.Constants;
|
||||
import lime.app.Application;
|
||||
import openfl.filters.ShaderFilter;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -30,8 +33,8 @@ using StringTools;
|
|||
import Discord.DiscordClient;
|
||||
#end
|
||||
#if newgrounds
|
||||
import io.newgrounds.NG;
|
||||
import funkin.ui.NgPrompt;
|
||||
import io.newgrounds.NG;
|
||||
#end
|
||||
|
||||
class MainMenuState extends MusicBeatState
|
||||
|
@ -99,7 +102,7 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
});
|
||||
|
||||
menuItems.enabled = false; // disable for intro
|
||||
menuItems.enabled = true; // can move on intro
|
||||
menuItems.createItem('story mode', function() startExitState(new StoryMenuState()));
|
||||
menuItems.createItem('freeplay', function()
|
||||
{
|
||||
|
@ -138,17 +141,21 @@ class MainMenuState extends MusicBeatState
|
|||
FlxG.camera.follow(camFollow, null, 0.06);
|
||||
// FlxG.camera.setScrollBounds(bg.x, bg.x + bg.width, bg.y, bg.y + bg.height * 1.2);
|
||||
|
||||
var versionStr = 'v${Application.current.meta.get('version')}';
|
||||
versionStr += ' (secret week 8 build do not leak)';
|
||||
super.create();
|
||||
|
||||
var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12);
|
||||
versionShit.scrollFactor.set();
|
||||
versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
add(versionShit);
|
||||
// This has to come AFTER!
|
||||
this.leftWatermarkText.text = Constants.VERSION;
|
||||
this.rightWatermarkText.text = "blablabla test";
|
||||
|
||||
// var versionStr = 'v${Application.current.meta.get('version')}';
|
||||
// versionStr += ' (secret week 8 build do not leak)';
|
||||
//
|
||||
// var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12);
|
||||
// versionShit.scrollFactor.set();
|
||||
// versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
// add(versionShit);
|
||||
|
||||
// NG.core.calls.event.logEvent('swag').send();
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
||||
override function closeSubState()
|
||||
|
@ -162,7 +169,7 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
super.finishTransIn();
|
||||
|
||||
menuItems.enabled = true;
|
||||
// menuItems.enabled = true;
|
||||
|
||||
// #if newgrounds
|
||||
// if (NGio.savedSessionFailed)
|
||||
|
@ -272,6 +279,8 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
|
@ -295,7 +304,7 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
if (FlxG.sound.music.volume < 0.8)
|
||||
{
|
||||
FlxG.sound.music.volume += 0.5 * FlxG.elapsed;
|
||||
FlxG.sound.music.volume += 0.5 * elapsed;
|
||||
}
|
||||
|
||||
if (_exiting)
|
||||
|
@ -306,8 +315,6 @@ class MainMenuState extends MusicBeatState
|
|||
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||
FlxG.switchState(new TitleState());
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import flixel.FlxGame;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.ui.FlxUIState;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import funkin.modding.PolymodHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
/**
|
||||
* MusicBeatState actually represents the core utility FlxState of the game.
|
||||
* It includes functionality for event handling, as well as maintaining BPM-based update events.
|
||||
*/
|
||||
class MusicBeatState extends FlxUIState
|
||||
{
|
||||
private var curStep:Int = 0;
|
||||
|
@ -17,16 +26,37 @@ class MusicBeatState extends FlxUIState
|
|||
inline function get_controls():Controls
|
||||
return PlayerSettings.player1.controls;
|
||||
|
||||
public var leftWatermarkText:FlxText = null;
|
||||
public var rightWatermarkText:FlxText = null;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
initCallbacks();
|
||||
}
|
||||
|
||||
function initCallbacks()
|
||||
{
|
||||
subStateOpened.add(onOpenSubstateComplete);
|
||||
subStateClosed.add(onCloseSubstateComplete);
|
||||
}
|
||||
|
||||
override function create()
|
||||
{
|
||||
if (transIn != null)
|
||||
trace('reg ' + transIn.region);
|
||||
|
||||
super.create();
|
||||
|
||||
createWatermarkText();
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// This can now be used in EVERY STATE YAY!
|
||||
if (FlxG.keys.justPressed.F5)
|
||||
debug_refreshModules();
|
||||
|
||||
// everyStep();
|
||||
var oldStep:Int = curStep;
|
||||
|
||||
|
@ -36,7 +66,41 @@ class MusicBeatState extends FlxUIState
|
|||
if (oldStep != curStep && curStep >= 0)
|
||||
stepHit();
|
||||
|
||||
super.update(elapsed);
|
||||
FlxG.watch.addQuick("songPos", Conductor.songPosition);
|
||||
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
||||
function createWatermarkText()
|
||||
{
|
||||
// Both have an xPos of 0, but a width equal to the full screen.
|
||||
// The rightWatermarkText is right aligned, which puts the text in the correct spot.
|
||||
leftWatermarkText = new FlxText(0, FlxG.height - 18, FlxG.width, '', 12);
|
||||
rightWatermarkText = new FlxText(0, FlxG.height - 18, FlxG.width, '', 12);
|
||||
|
||||
// 100,000 should be good enough.
|
||||
leftWatermarkText.zIndex = 100000;
|
||||
rightWatermarkText.zIndex = 100000;
|
||||
leftWatermarkText.scrollFactor.set(0, 0);
|
||||
rightWatermarkText.scrollFactor.set(0, 0);
|
||||
leftWatermarkText.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
rightWatermarkText.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
|
||||
add(leftWatermarkText);
|
||||
add(rightWatermarkText);
|
||||
}
|
||||
|
||||
function dispatchEvent(event:ScriptEvent)
|
||||
{
|
||||
ModuleHandler.callEvent(event);
|
||||
}
|
||||
|
||||
function debug_refreshModules()
|
||||
{
|
||||
PolymodHandler.forceReloadAssets();
|
||||
|
||||
// Restart the current state, so old data is cleared.
|
||||
FlxG.resetState();
|
||||
}
|
||||
|
||||
private function updateBeat():Void
|
||||
|
@ -60,15 +124,97 @@ class MusicBeatState extends FlxUIState
|
|||
curStep = lastChange.stepTime + Math.floor((Conductor.songPosition - lastChange.songTime) / Conductor.stepCrochet);
|
||||
}
|
||||
|
||||
public function stepHit():Void
|
||||
public function stepHit():Bool
|
||||
{
|
||||
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
if (event.eventCanceled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (curStep % 4 == 0)
|
||||
beatHit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function beatHit():Void
|
||||
public function beatHit():Bool
|
||||
{
|
||||
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
if (event.eventCanceled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lastBeatHitTime = Conductor.songPosition;
|
||||
// do literally nothing dumbass
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the state, by redoing the render order of all sprites.
|
||||
* It does this based on the `zIndex` of each prop.
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
}
|
||||
|
||||
override function switchTo(nextState:FlxState):Bool
|
||||
{
|
||||
var event = new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_BEGIN, nextState, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
if (event.eventCanceled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.switchTo(nextState);
|
||||
}
|
||||
|
||||
public override function openSubState(targetSubstate:FlxSubState):Void
|
||||
{
|
||||
var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_BEGIN, targetSubstate, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
if (event.eventCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
super.openSubState(targetSubstate);
|
||||
}
|
||||
|
||||
function onOpenSubstateComplete(targetState:FlxSubState):Void
|
||||
{
|
||||
dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_OPEN_END, targetState, true));
|
||||
}
|
||||
|
||||
public override function closeSubState():Void
|
||||
{
|
||||
var event = new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_BEGIN, this.subState, true);
|
||||
|
||||
dispatchEvent(event);
|
||||
|
||||
if (event.eventCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
super.closeSubState();
|
||||
}
|
||||
|
||||
function onCloseSubstateComplete(targetState:FlxSubState):Void
|
||||
{
|
||||
dispatchEvent(new SubStateScriptEvent(ScriptEvent.SUBSTATE_CLOSE_END, targetState, true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import flixel.FlxSubState;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
|
||||
/**
|
||||
* MusicBeatSubstate reincorporates the functionality of MusicBeatState into an FlxSubState.
|
||||
*/
|
||||
class MusicBeatSubstate extends FlxSubState
|
||||
{
|
||||
public function new()
|
||||
|
@ -53,6 +58,11 @@ class MusicBeatSubstate extends FlxSubState
|
|||
beatHit();
|
||||
}
|
||||
|
||||
function dispatchEvent(event:ScriptEvent)
|
||||
{
|
||||
ModuleHandler.callEvent(event);
|
||||
}
|
||||
|
||||
public function beatHit():Void
|
||||
{
|
||||
// do literally nothing dumbass
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
|
@ -109,10 +110,8 @@ class Note extends FlxSprite
|
|||
|
||||
data.noteData = noteData;
|
||||
|
||||
var daStage:String = PlayState.curStageId;
|
||||
|
||||
// TODO: Make this logic more generic
|
||||
switch (daStage)
|
||||
switch (PlayState.instance.currentStageId)
|
||||
{
|
||||
case 'school' | 'schoolEvil':
|
||||
loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17);
|
||||
|
@ -137,7 +136,7 @@ class Note extends FlxSprite
|
|||
animation.add('bluehold', [1]);
|
||||
}
|
||||
|
||||
setGraphicSize(Std.int(width * PlayState.daPixelZoom));
|
||||
setGraphicSize(Std.int(width * Constants.PIXEL_ART_SCALE));
|
||||
updateHitbox();
|
||||
|
||||
default:
|
||||
|
@ -194,7 +193,7 @@ class Note extends FlxSprite
|
|||
|
||||
x -= width / 2;
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
x += 30;
|
||||
|
||||
if (prevNote.isSustainNote)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.Controls.Control;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -11,6 +10,7 @@ import flixel.text.FlxText;
|
|||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.Controls.Control;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
class PauseSubState extends MusicBeatSubstate
|
||||
|
@ -33,6 +33,10 @@ class PauseSubState extends MusicBeatSubstate
|
|||
|
||||
var practiceText:FlxText;
|
||||
|
||||
var exitingToMenu:Bool = false;
|
||||
var bg:FlxSprite;
|
||||
var metaDataGrp:FlxTypedGroup<FlxSprite>;
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super();
|
||||
|
@ -48,39 +52,42 @@ class PauseSubState extends MusicBeatSubstate
|
|||
|
||||
FlxG.sound.list.add(pauseMusic);
|
||||
|
||||
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
bg.alpha = 0;
|
||||
bg.scrollFactor.set();
|
||||
add(bg);
|
||||
|
||||
metaDataGrp = new FlxTypedGroup<FlxSprite>();
|
||||
add(metaDataGrp);
|
||||
|
||||
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32);
|
||||
levelInfo.text += PlayState.SONG.song;
|
||||
levelInfo.text += PlayState.currentSong.song;
|
||||
levelInfo.scrollFactor.set();
|
||||
levelInfo.setFormat(Paths.font("vcr.ttf"), 32);
|
||||
levelInfo.updateHitbox();
|
||||
add(levelInfo);
|
||||
metaDataGrp.add(levelInfo);
|
||||
|
||||
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, "", 32);
|
||||
levelDifficulty.text += CoolUtil.difficultyString();
|
||||
levelDifficulty.scrollFactor.set();
|
||||
levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
levelDifficulty.updateHitbox();
|
||||
add(levelDifficulty);
|
||||
metaDataGrp.add(levelDifficulty);
|
||||
|
||||
var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, "", 32);
|
||||
deathCounter.text = "Blue balled: " + PlayState.deathCounter;
|
||||
deathCounter.scrollFactor.set();
|
||||
deathCounter.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
deathCounter.updateHitbox();
|
||||
add(deathCounter);
|
||||
metaDataGrp.add(deathCounter);
|
||||
|
||||
practiceText = new FlxText(20, 15 + 64 + 32, 0, "PRACTICE MODE", 32);
|
||||
practiceText.scrollFactor.set();
|
||||
practiceText.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
practiceText.updateHitbox();
|
||||
practiceText.x = FlxG.width - (practiceText.width + 20);
|
||||
practiceText.visible = PlayState.practiceMode;
|
||||
add(practiceText);
|
||||
practiceText.visible = PlayState.isPracticeMode;
|
||||
metaDataGrp.add(practiceText);
|
||||
|
||||
levelDifficulty.alpha = 0;
|
||||
levelInfo.alpha = 0;
|
||||
|
@ -133,71 +140,103 @@ class PauseSubState extends MusicBeatSubstate
|
|||
var downP = controls.UI_DOWN_P;
|
||||
var accepted = controls.ACCEPT;
|
||||
|
||||
if (upP)
|
||||
#if debug
|
||||
// to pause the game and get screenshots easy, press H on pause menu!
|
||||
if (FlxG.keys.justPressed.H)
|
||||
{
|
||||
changeSelection(-1);
|
||||
bg.visible = !bg.visible;
|
||||
grpMenuShit.visible = !grpMenuShit.visible;
|
||||
metaDataGrp.visible = !metaDataGrp.visible;
|
||||
}
|
||||
if (downP)
|
||||
{
|
||||
changeSelection(1);
|
||||
}
|
||||
|
||||
var androidPause:Bool = false;
|
||||
|
||||
#if android
|
||||
androidPause = FlxG.android.justPressed.BACK;
|
||||
#end
|
||||
|
||||
if (androidPause)
|
||||
close();
|
||||
|
||||
if (accepted)
|
||||
if (!exitingToMenu)
|
||||
{
|
||||
var daSelected:String = menuItems[curSelected];
|
||||
|
||||
switch (daSelected)
|
||||
if (upP)
|
||||
{
|
||||
case "Resume":
|
||||
close();
|
||||
case "EASY" | 'NORMAL' | "HARD":
|
||||
PlayState.SONG = SongLoad.loadFromJson(PlayState.SONG.song.toLowerCase(), PlayState.SONG.song.toLowerCase());
|
||||
SongLoad.curDiff = daSelected.toLowerCase();
|
||||
|
||||
PlayState.storyDifficulty = curSelected;
|
||||
|
||||
PlayState.needsReset = true;
|
||||
|
||||
close();
|
||||
|
||||
case 'Toggle Practice Mode':
|
||||
PlayState.practiceMode = !PlayState.practiceMode;
|
||||
practiceText.visible = PlayState.practiceMode;
|
||||
|
||||
case 'Change Difficulty':
|
||||
menuItems = difficultyChoices;
|
||||
regenMenu();
|
||||
case 'BACK':
|
||||
menuItems = pauseOG;
|
||||
regenMenu();
|
||||
case "Restart Song":
|
||||
PlayState.needsReset = true;
|
||||
|
||||
close();
|
||||
// FlxG.resetState();
|
||||
case "Exit to menu":
|
||||
PlayState.seenCutscene = false;
|
||||
PlayState.deathCounter = 0;
|
||||
if (PlayState.isStoryMode)
|
||||
FlxG.switchState(new StoryMenuState());
|
||||
else
|
||||
FlxG.switchState(new FreeplayState());
|
||||
changeSelection(-1);
|
||||
}
|
||||
if (downP)
|
||||
{
|
||||
changeSelection(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.J)
|
||||
{
|
||||
// for reference later!
|
||||
// PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxKey.J, null);
|
||||
var androidPause:Bool = false;
|
||||
|
||||
#if android
|
||||
androidPause = FlxG.android.justPressed.BACK;
|
||||
#end
|
||||
|
||||
if (androidPause)
|
||||
close();
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
var daSelected:String = menuItems[curSelected];
|
||||
|
||||
switch (daSelected)
|
||||
{
|
||||
case "Resume":
|
||||
close();
|
||||
case "EASY" | 'NORMAL' | "HARD":
|
||||
PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase());
|
||||
SongLoad.curDiff = daSelected.toLowerCase();
|
||||
|
||||
PlayState.storyDifficulty = curSelected;
|
||||
|
||||
PlayState.needsReset = true;
|
||||
|
||||
close();
|
||||
|
||||
case 'Toggle Practice Mode':
|
||||
PlayState.isPracticeMode = !PlayState.isPracticeMode;
|
||||
practiceText.visible = PlayState.isPracticeMode;
|
||||
|
||||
case 'Change Difficulty':
|
||||
menuItems = difficultyChoices;
|
||||
regenMenu();
|
||||
case 'BACK':
|
||||
menuItems = pauseOG;
|
||||
regenMenu();
|
||||
case "Restart Song":
|
||||
PlayState.needsReset = true;
|
||||
|
||||
close();
|
||||
// FlxG.resetState();
|
||||
case "Exit to menu":
|
||||
exitingToMenu = true;
|
||||
PlayState.seenCutscene = false;
|
||||
PlayState.deathCounter = 0;
|
||||
|
||||
for (item in grpMenuShit.members)
|
||||
{
|
||||
item.targetY = -3;
|
||||
item.alpha = 0.6;
|
||||
}
|
||||
|
||||
FlxTween.tween(bg, {alpha: 1}, 0.4, {
|
||||
ease: FlxEase.quartInOut,
|
||||
onComplete: function(_)
|
||||
{
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
FlxG.cameras.list[1].alpha = 0; // bullshit for the UI camera???
|
||||
|
||||
if (PlayState.isStoryMode)
|
||||
FlxG.switchState(new StoryMenuState());
|
||||
else
|
||||
FlxG.switchState(new FreeplayState());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.J)
|
||||
{
|
||||
// for reference later!
|
||||
// PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxKey.J, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,12 +258,9 @@ class PauseSubState extends MusicBeatSubstate
|
|||
if (curSelected >= menuItems.length)
|
||||
curSelected = 0;
|
||||
|
||||
var bullShit:Int = 0;
|
||||
|
||||
for (item in grpMenuShit.members)
|
||||
for (index => item in grpMenuShit.members)
|
||||
{
|
||||
item.targetY = bullShit - curSelected;
|
||||
bullShit++;
|
||||
item.targetY = index - curSelected;
|
||||
|
||||
item.alpha = 0.6;
|
||||
// item.setGraphicSize(Std.int(item.width * 0.8));
|
||||
|
|
|
@ -311,7 +311,7 @@ class StoryMenuState extends MusicBeatState
|
|||
PlayState.isStoryMode = true;
|
||||
selectedWeek = true;
|
||||
|
||||
PlayState.SONG = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
|
||||
PlayState.storyWeek = curWeek;
|
||||
PlayState.campaignScore = 0;
|
||||
|
||||
|
|
|
@ -1,47 +1,32 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.input.android.FlxAndroidKey;
|
||||
import flixel.input.android.FlxAndroidKeys;
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.input.gamepad.id.SwitchJoyconLeftID;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.app.Application;
|
||||
import lime.graphics.Image;
|
||||
import lime.media.AudioContext;
|
||||
import lime.ui.Window;
|
||||
import openfl.Assets;
|
||||
import openfl.display.Sprite;
|
||||
import openfl.events.AsyncErrorEvent;
|
||||
import openfl.events.Event;
|
||||
import openfl.events.MouseEvent;
|
||||
import openfl.events.NetStatusEvent;
|
||||
import openfl.media.Video;
|
||||
import openfl.net.NetConnection;
|
||||
import openfl.net.NetStream;
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import funkin.shaderslmfao.BuildingShaders;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
import funkin.shaderslmfao.TitleOutline;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.util.Constants;
|
||||
import openfl.Assets;
|
||||
import openfl.display.Sprite;
|
||||
import openfl.events.AsyncErrorEvent;
|
||||
import openfl.events.MouseEvent;
|
||||
import openfl.events.NetStatusEvent;
|
||||
import openfl.media.Video;
|
||||
import openfl.net.NetStream;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if desktop
|
||||
import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
import sys.thread.Thread;
|
||||
#end
|
||||
|
||||
class TitleState extends MusicBeatState
|
||||
{
|
||||
public static var initialized:Bool = false;
|
||||
|
@ -73,33 +58,33 @@ class TitleState extends MusicBeatState
|
|||
super.create();
|
||||
|
||||
/*
|
||||
#elseif web
|
||||
#elseif web
|
||||
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
|
||||
video = new Video();
|
||||
FlxG.stage.addChild(video);
|
||||
video = new Video();
|
||||
FlxG.stage.addChild(video);
|
||||
|
||||
var netConnection = new NetConnection();
|
||||
netConnection.connect(null);
|
||||
var netConnection = new NetConnection();
|
||||
netConnection.connect(null);
|
||||
|
||||
netStream = new NetStream(netConnection);
|
||||
netStream.client = {onMetaData: client_onMetaData};
|
||||
netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, netStream_onAsyncError);
|
||||
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus);
|
||||
// netStream.addEventListener(NetStatusEvent.NET_STATUS) // netStream.play(Paths.file('music/kickstarterTrailer.mp4'));
|
||||
netStream = new NetStream(netConnection);
|
||||
netStream.client = {onMetaData: client_onMetaData};
|
||||
netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, netStream_onAsyncError);
|
||||
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus);
|
||||
// netStream.addEventListener(NetStatusEvent.NET_STATUS) // netStream.play(Paths.file('music/kickstarterTrailer.mp4'));
|
||||
|
||||
overlay = new Sprite();
|
||||
overlay.graphics.beginFill(0, 0.5);
|
||||
overlay.graphics.drawRect(0, 0, 1280, 720);
|
||||
overlay.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown);
|
||||
overlay = new Sprite();
|
||||
overlay.graphics.beginFill(0, 0.5);
|
||||
overlay.graphics.drawRect(0, 0, 1280, 720);
|
||||
overlay.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown);
|
||||
|
||||
overlay.buttonMode = true;
|
||||
// FlxG.stage.addChild(overlay);
|
||||
overlay.buttonMode = true;
|
||||
// FlxG.stage.addChild(overlay);
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// netConnection.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown);
|
||||
|
@ -158,9 +143,9 @@ class TitleState extends MusicBeatState
|
|||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu'), 0);
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
Conductor.bpm = Constants.FREAKY_MENU_BPM;
|
||||
}
|
||||
|
||||
Conductor.changeBPM(102);
|
||||
persistentUpdate = true;
|
||||
|
||||
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
|
@ -189,8 +174,6 @@ class TitleState extends MusicBeatState
|
|||
gfDance.antialiasing = true;
|
||||
add(gfDance);
|
||||
|
||||
trace('MACRO TEST: ${gfDance.zIndex}');
|
||||
|
||||
// alphaShader.shader.funnyShit.input = gfDance.pixels; // old shit
|
||||
|
||||
logoBl.shader = alphaShader.shader;
|
||||
|
@ -220,6 +203,7 @@ class TitleState extends MusicBeatState
|
|||
|
||||
blackScreen = bg.clone();
|
||||
credGroup.add(blackScreen);
|
||||
credGroup.add(textGroup);
|
||||
|
||||
// var atlasBullShit:FlxSprite = new FlxSprite();
|
||||
// atlasBullShit.frames = CoolUtil.fromAnimate(Paths.image('money'), Paths.file('images/money.json'));
|
||||
|
@ -290,13 +274,13 @@ class TitleState extends MusicBeatState
|
|||
#end
|
||||
|
||||
/* if (FlxG.onMobile)
|
||||
{
|
||||
if (gfDance != null)
|
||||
{
|
||||
if (gfDance != null)
|
||||
{
|
||||
gfDance.x = (FlxG.width / 2) + (FlxG.accelerometer.x * (FlxG.width / 2));
|
||||
// gfDance.y = (FlxG.height / 2) + (FlxG.accelerometer.y * (FlxG.height / 2));
|
||||
}
|
||||
gfDance.x = (FlxG.width / 2) + (FlxG.accelerometer.x * (FlxG.width / 2));
|
||||
// gfDance.y = (FlxG.height / 2) + (FlxG.accelerometer.y * (FlxG.height / 2));
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (FlxG.keys.justPressed.I)
|
||||
{
|
||||
|
@ -313,37 +297,37 @@ class TitleState extends MusicBeatState
|
|||
}
|
||||
|
||||
/*
|
||||
FlxG.watch.addQuick('cur display', FlxG.stage.window.display.id);
|
||||
if (FlxG.keys.justPressed.Y)
|
||||
FlxG.watch.addQuick('cur display', FlxG.stage.window.display.id);
|
||||
if (FlxG.keys.justPressed.Y)
|
||||
{
|
||||
// trace(FlxG.stage.window.display.name);
|
||||
|
||||
if (FlxG.gamepads.firstActive != null)
|
||||
{
|
||||
// trace(FlxG.stage.window.display.name);
|
||||
|
||||
if (FlxG.gamepads.firstActive != null)
|
||||
{
|
||||
trace(FlxG.gamepads.firstActive.model);
|
||||
FlxG.gamepads.firstActive.id
|
||||
}
|
||||
else
|
||||
trace('gamepad null');
|
||||
|
||||
// FlxG.stage.window.title = Std.string(FlxG.random.int(0, 20000));
|
||||
// FlxG.stage.window.setIcon(Image.fromFile('assets/images/icon16.png'));
|
||||
// FlxG.stage.window.readPixels;
|
||||
|
||||
if (FlxG.stage.window.width == Std.int(FlxG.stage.window.display.bounds.width))
|
||||
{
|
||||
FlxG.stage.window.width = 1280;
|
||||
FlxG.stage.window.height = 720;
|
||||
FlxG.stage.window.y = 30;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.stage.window.width = Std.int(FlxG.stage.window.display.bounds.width);
|
||||
FlxG.stage.window.height = Std.int(FlxG.stage.window.display.bounds.height);
|
||||
FlxG.stage.window.x = Std.int(FlxG.stage.window.display.bounds.x);
|
||||
FlxG.stage.window.y = Std.int(FlxG.stage.window.display.bounds.y);
|
||||
}
|
||||
trace(FlxG.gamepads.firstActive.model);
|
||||
FlxG.gamepads.firstActive.id
|
||||
}
|
||||
else
|
||||
trace('gamepad null');
|
||||
|
||||
// FlxG.stage.window.title = Std.string(FlxG.random.int(0, 20000));
|
||||
// FlxG.stage.window.setIcon(Image.fromFile('assets/images/icon16.png'));
|
||||
// FlxG.stage.window.readPixels;
|
||||
|
||||
if (FlxG.stage.window.width == Std.int(FlxG.stage.window.display.bounds.width))
|
||||
{
|
||||
FlxG.stage.window.width = 1280;
|
||||
FlxG.stage.window.height = 720;
|
||||
FlxG.stage.window.y = 30;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.stage.window.width = Std.int(FlxG.stage.window.display.bounds.width);
|
||||
FlxG.stage.window.height = Std.int(FlxG.stage.window.display.bounds.height);
|
||||
FlxG.stage.window.x = Std.int(FlxG.stage.window.display.bounds.x);
|
||||
FlxG.stage.window.y = Std.int(FlxG.stage.window.display.bounds.y);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#if debug
|
||||
|
@ -382,6 +366,13 @@ class TitleState extends MusicBeatState
|
|||
pressedEnter = true;
|
||||
#end
|
||||
}
|
||||
|
||||
// a faster intro thing lol!
|
||||
if (pressedEnter && transitioning && skippedIntro)
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
|
||||
if (pressedEnter && !transitioning && skippedIntro)
|
||||
{
|
||||
if (FlxG.sound.music != null)
|
||||
|
@ -434,16 +425,16 @@ class TitleState extends MusicBeatState
|
|||
if (pressedEnter && !skippedIntro && initialized)
|
||||
skipIntro();
|
||||
/*
|
||||
#if web
|
||||
if (!initialized && controls.ACCEPT)
|
||||
{
|
||||
// netStream.dispose();
|
||||
// FlxG.stage.removeChild(video);
|
||||
#if web
|
||||
if (!initialized && controls.ACCEPT)
|
||||
{
|
||||
// netStream.dispose();
|
||||
// FlxG.stage.removeChild(video);
|
||||
|
||||
startIntro();
|
||||
skipIntro();
|
||||
}
|
||||
#end
|
||||
startIntro();
|
||||
skipIntro();
|
||||
}
|
||||
#end
|
||||
*/
|
||||
|
||||
if (controls.UI_LEFT)
|
||||
|
@ -497,39 +488,47 @@ class TitleState extends MusicBeatState
|
|||
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
|
||||
add(spec);
|
||||
|
||||
Conductor.changeBPM(190);
|
||||
Conductor.bpm = 190;
|
||||
FlxG.camera.flash(FlxColor.WHITE, 1);
|
||||
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
|
||||
}
|
||||
|
||||
function createCoolText(textArray:Array<String>)
|
||||
{
|
||||
if (credGroup == null || textGroup == null)
|
||||
return;
|
||||
|
||||
for (i in 0...textArray.length)
|
||||
{
|
||||
var money:Alphabet = new Alphabet(0, 0, textArray[i], true, false);
|
||||
var money:AtlasText = new AtlasText(0, 0, textArray[i], AtlasFont.BOLD);
|
||||
money.screenCenter(X);
|
||||
money.y += (i * 60) + 200;
|
||||
credGroup.add(money);
|
||||
// credGroup.add(money);
|
||||
textGroup.add(money);
|
||||
}
|
||||
}
|
||||
|
||||
function addMoreText(text:String)
|
||||
{
|
||||
if (credGroup == null || textGroup == null)
|
||||
return;
|
||||
|
||||
lime.ui.Haptic.vibrate(100, 100);
|
||||
|
||||
var coolText:Alphabet = new Alphabet(0, 0, text, true, false);
|
||||
var coolText:AtlasText = new AtlasText(0, 0, text, AtlasFont.BOLD);
|
||||
coolText.screenCenter(X);
|
||||
coolText.y += (textGroup.length * 60) + 200;
|
||||
credGroup.add(coolText);
|
||||
textGroup.add(coolText);
|
||||
}
|
||||
|
||||
function deleteCoolText()
|
||||
{
|
||||
if (credGroup == null || textGroup == null)
|
||||
return;
|
||||
|
||||
while (textGroup.members.length > 0)
|
||||
{
|
||||
credGroup.remove(textGroup.members[0], true);
|
||||
// credGroup.remove(textGroup.members[0], true);
|
||||
textGroup.remove(textGroup.members[0], true);
|
||||
}
|
||||
}
|
||||
|
@ -537,9 +536,11 @@ class TitleState extends MusicBeatState
|
|||
var isRainbow:Bool = false;
|
||||
var skippedIntro:Bool = false;
|
||||
|
||||
override function beatHit()
|
||||
override function beatHit():Bool
|
||||
{
|
||||
super.beatHit();
|
||||
// super.beatHit() returns false if a module cancelled the event.
|
||||
if (!super.beatHit())
|
||||
return false;
|
||||
|
||||
if (!skippedIntro)
|
||||
{
|
||||
|
@ -599,6 +600,8 @@ class TitleState extends MusicBeatState
|
|||
else
|
||||
gfDance.animation.play('danceLeft');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function skipIntro():Void
|
||||
|
|
|
@ -4,6 +4,9 @@ import haxe.format.JsonParser;
|
|||
import openfl.Assets;
|
||||
import openfl.geom.Matrix3D;
|
||||
import openfl.geom.Matrix;
|
||||
#if sys
|
||||
import sys.io.File;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Generally designed / written in a way that can be easily taken out of FNF and used elsewhere
|
||||
|
|
|
@ -48,12 +48,12 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
{
|
||||
// updateViz();
|
||||
|
||||
updateFFT();
|
||||
updateFFT(elapsed);
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
function updateFFT()
|
||||
function updateFFT(elapsed:Float)
|
||||
{
|
||||
if (vis.snd != null)
|
||||
{
|
||||
|
@ -112,7 +112,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
|
||||
avgVel *= 10000000;
|
||||
|
||||
volumes[i] += avgVel - (FlxG.elapsed * (volumes[i] * 50));
|
||||
volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
|
||||
|
||||
var animFrame:Int = Std.int(volumes[i]);
|
||||
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
package funkin.charting;
|
||||
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.Section.SwagSection;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.audiovis.ABotVis;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -23,14 +16,21 @@ import flixel.system.FlxSound;
|
|||
import flixel.text.FlxText;
|
||||
import flixel.ui.FlxButton;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.Section.SwagSection;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.audiovis.ABotVis;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.rendering.MeshRender;
|
||||
import haxe.Json;
|
||||
import lime.media.AudioBuffer;
|
||||
import lime.utils.Assets;
|
||||
import openfl.events.Event;
|
||||
import openfl.events.IOErrorEvent;
|
||||
import openfl.net.FileReference;
|
||||
import funkin.rendering.MeshRender;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
@ -122,9 +122,9 @@ class ChartingState extends MusicBeatState
|
|||
curRenderedNotes = new FlxTypedGroup<Note>();
|
||||
curRenderedSustains = new FlxTypedGroup<FlxSprite>();
|
||||
|
||||
if (PlayState.SONG != null)
|
||||
if (PlayState.currentSong != null)
|
||||
{
|
||||
_song = SongLoad.songData = PlayState.SONG;
|
||||
_song = SongLoad.songData = PlayState.currentSong;
|
||||
trace("LOADED A PLAYSTATE SONGFILE");
|
||||
}
|
||||
else
|
||||
|
@ -144,7 +144,7 @@ class ChartingState extends MusicBeatState
|
|||
updateGrid();
|
||||
|
||||
loadSong(_song.song);
|
||||
Conductor.changeBPM(_song.bpm);
|
||||
Conductor.bpm = _song.bpm;
|
||||
Conductor.mapBPMChanges(_song);
|
||||
|
||||
bpmTxt = new FlxText(1000, 50, 0, "", 16);
|
||||
|
@ -545,7 +545,7 @@ class ChartingState extends MusicBeatState
|
|||
{
|
||||
tempBpm = nums.value;
|
||||
Conductor.mapBPMChanges(_song);
|
||||
Conductor.changeBPM(nums.value);
|
||||
Conductor.bpm = nums.value;
|
||||
}
|
||||
else if (wname == 'note_susLength')
|
||||
{
|
||||
|
@ -814,7 +814,7 @@ class ChartingState extends MusicBeatState
|
|||
|
||||
lastSection = curSection;
|
||||
|
||||
PlayState.SONG = _song;
|
||||
PlayState.currentSong = _song;
|
||||
|
||||
// JUST FOR DEBUG DARNELL STUFF, GENERALIZE THIS FOR BETTER LOADING ELSEWHERE TOO!
|
||||
PlayState.storyWeek = 8;
|
||||
|
@ -903,7 +903,7 @@ class ChartingState extends MusicBeatState
|
|||
{
|
||||
if (FlxG.keys.pressed.W || FlxG.keys.pressed.S)
|
||||
{
|
||||
var daTime:Float = 700 * FlxG.elapsed;
|
||||
var daTime:Float = 700 * elapsed;
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
daTime *= 0.2;
|
||||
|
@ -975,9 +975,9 @@ class ChartingState extends MusicBeatState
|
|||
_song.bpm = tempBpm;
|
||||
|
||||
/* if (FlxG.keys.justPressed.UP)
|
||||
Conductor.changeBPM(Conductor.bpm + 1);
|
||||
Conductor.bpm = Conductor.bpm + 1;
|
||||
if (FlxG.keys.justPressed.DOWN)
|
||||
Conductor.changeBPM(Conductor.bpm - 1); */
|
||||
Conductor.bpm = Conductor.bpm - 1; */
|
||||
|
||||
var shiftThing:Int = 1;
|
||||
if (FlxG.keys.pressed.SHIFT)
|
||||
|
@ -1213,7 +1213,7 @@ class ChartingState extends MusicBeatState
|
|||
|
||||
if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0)
|
||||
{
|
||||
Conductor.changeBPM(SongLoad.getSong()[curSection].bpm);
|
||||
Conductor.bpm = SongLoad.getSong()[curSection].bpm;
|
||||
FlxG.log.add('CHANGED BPM!');
|
||||
}
|
||||
else
|
||||
|
@ -1223,7 +1223,7 @@ class ChartingState extends MusicBeatState
|
|||
for (i in 0...curSection)
|
||||
if (SongLoad.getSong()[i].changeBPM)
|
||||
daBPM = SongLoad.getSong()[i].bpm;
|
||||
Conductor.changeBPM(daBPM);
|
||||
Conductor.bpm = daBPM;
|
||||
}
|
||||
|
||||
/* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE
|
||||
|
@ -1462,13 +1462,13 @@ class ChartingState extends MusicBeatState
|
|||
|
||||
function loadJson(song:String):Void
|
||||
{
|
||||
PlayState.SONG = SongLoad.loadFromJson(song.toLowerCase(), song.toLowerCase());
|
||||
PlayState.currentSong = SongLoad.loadFromJson(song.toLowerCase(), song.toLowerCase());
|
||||
LoadingState.loadAndSwitchState(new ChartingState());
|
||||
}
|
||||
|
||||
function loadAutosave():Void
|
||||
{
|
||||
PlayState.SONG = FlxG.save.data.autosave;
|
||||
PlayState.currentSong = FlxG.save.data.autosave;
|
||||
FlxG.resetState();
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class BGScrollingText extends FlxSpriteGroup
|
|||
{
|
||||
for (txt in grpTexts.group)
|
||||
{
|
||||
txt.x -= 1 * (speed * (FlxG.elapsed / (1 / 60)));
|
||||
txt.x -= 1 * (speed * (elapsed / (1 / 60)));
|
||||
|
||||
if (speed > 0)
|
||||
{
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
package funkin.i18n;
|
||||
|
||||
import firetongue.FireTongue;
|
||||
|
||||
class FireTongueHandler
|
||||
{
|
||||
static final DEFAULT_LOCALE = 'en-US';
|
||||
// static final DEFAULT_LOCALE = 'pt-BR';
|
||||
static final LOCALE_DIR = 'assets/locales/';
|
||||
|
||||
static var tongue:FireTongue;
|
||||
|
||||
/**
|
||||
* Initialize the FireTongue instance.
|
||||
* This will automatically start with the default locale for you.
|
||||
*/
|
||||
public static function init():Void
|
||||
{
|
||||
tongue = new FireTongue(OPENFL, // Haxe framework being used.
|
||||
// This should really have been a parameterized object...
|
||||
null, // Function to check if a file exists. Specify null to use the one from the framework.
|
||||
null, // Function to retrieve the text of a file. Specify null to use the one from the framework.
|
||||
null, // Function to get a list of files in a directory. Specify null to use the one from the framework.
|
||||
firetongue.FireTongue.Case.Upper);
|
||||
|
||||
// TODO: Make this use the language from the user's preferences.
|
||||
setLanguage(DEFAULT_LOCALE);
|
||||
|
||||
trace('[FIRETONGUE] Initialized. Available locales: ${tongue.locales.join(', ')}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the language used by FireTongue.
|
||||
* @param locale The name of the locale to use, such as `en-US`.
|
||||
*/
|
||||
public static function setLanguage(locale:String):Void
|
||||
{
|
||||
tongue.initialize({
|
||||
locale: locale, // The locale to load.
|
||||
|
||||
finishedCallback: onFinishLoad, // Function run when the locale is loaded.
|
||||
directory: LOCALE_DIR, // Folder (relative to assets/) to load data from.
|
||||
replaceMissing: false, // If true, missing flags fallback to the default locale.
|
||||
checkMissing: true, // If true, check for and store the list of missing flags for this locale.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when FireTongue finishes loading a language.
|
||||
*/
|
||||
static function onFinishLoad()
|
||||
{
|
||||
if (tongue == null)
|
||||
return;
|
||||
|
||||
trace('[FIRETONGUE] Finished loading locale: ${tongue.locale}');
|
||||
if (tongue.missingFlags != null)
|
||||
{
|
||||
if (tongue.missingFlags.get(tongue.locale) != null && tongue.missingFlags.get(tongue.locale).length != 0)
|
||||
{
|
||||
trace('[FIRETONGUE] Missing flags: ${tongue.missingFlags.get(tongue.locale).join(', ')}');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[FIRETONGUE] No missing flags for this locale. (Note: Another locale has missing flags.)');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[FIRETONGUE] No missing flags.');
|
||||
}
|
||||
|
||||
trace('[FIRETONGUE] HELLO_WORLD = ${t("HELLO_WORLD")}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a localized string based on the given key.
|
||||
*
|
||||
* Example:
|
||||
* import i18n.FiretongueHandler.t;
|
||||
* trace(t('HELLO')); // Prints "Hello!"
|
||||
*
|
||||
* @param key The key to use to retrieve the localized string.
|
||||
* @param context The data file to load the key from.
|
||||
* @return The localized string.
|
||||
*/
|
||||
public static function t(key:String, context:String = 'data'):String
|
||||
{
|
||||
// The localization strings can be stored all in one file,
|
||||
// or split into several contexts.
|
||||
return tongue.get(key, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a localized string while replacing specific values.
|
||||
* In this way, you can use the same invocation call to properly localize
|
||||
* a variety of different languages with distinct grammar.
|
||||
*
|
||||
* Example:
|
||||
* import i18n.FiretongueHandler.f;
|
||||
* trace(f('COLLECT_X_APPLES', 'data', ['<X>'], ['10']); // Prints "Collect 10 apples!"
|
||||
*
|
||||
* @param key The key to use to retrieve the localized string.
|
||||
* @param context The data file to load the key from.
|
||||
* @param flags The flags to replace in the string.
|
||||
* @param values The values to replace those flags with.
|
||||
* @return The localized string.
|
||||
*/
|
||||
public static function f(key:String, context:String = 'data', flags:Array<String> = null, values:Array<String> = null):String
|
||||
{
|
||||
var str = t(key, context);
|
||||
return firetongue.Replace.flags(str, flags, values);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# i18n
|
||||
|
||||
This package contains functions used for internationalization (i18n).
|
|
@ -3,8 +3,11 @@ package funkin.modding;
|
|||
import polymod.hscript.HScriptable;
|
||||
|
||||
/**
|
||||
* Add this interface to a class to make it a scriptable object.
|
||||
* Functions annotated with @:hscript will call the relevant script.
|
||||
* Functions annotated with @:hookable can be reassigned.
|
||||
* NOTE: If you receive the following error when making a function use @:hookable:
|
||||
* `Cannot access this or other member field in variable initialization`
|
||||
* This is because you need to perform calls and assignments using a static variable referencing the target object.
|
||||
*/
|
||||
@:hscript({
|
||||
// ALL of these values are added to ALL scripts in the child classes.
|
||||
|
|
76
source/funkin/modding/IScriptedClass.hx
Normal file
76
source/funkin/modding/IScriptedClass.hx
Normal file
|
@ -0,0 +1,76 @@
|
|||
package funkin.modding;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to all scripted classes.
|
||||
*
|
||||
* Includes events handling basic life cycle relevant to all scripted classes.
|
||||
*/
|
||||
interface IScriptedClass
|
||||
{
|
||||
public function onScriptEvent(event:ScriptEvent):Void;
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void;
|
||||
public function onDestroy(event:ScriptEvent):Void;
|
||||
public function onUpdate(event:UpdateScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes which can follow the game between states.
|
||||
*/
|
||||
interface IStateChangingScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onStateChangeBegin(event:StateChangeScriptEvent):Void;
|
||||
public function onStateChangeEnd(event:StateChangeScriptEvent):Void;
|
||||
|
||||
public function onSubstateOpenBegin(event:SubStateScriptEvent):Void;
|
||||
public function onSubstateOpenEnd(event:SubStateScriptEvent):Void;
|
||||
public function onSubstateCloseBegin(event:SubStateScriptEvent):Void;
|
||||
public function onSubstateCloseEnd(event:SubStateScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes which represent notes.
|
||||
*/
|
||||
interface INoteScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Developer note:
|
||||
*
|
||||
* I previously considered adding events for onKeyDown, onKeyUp, mouse events, etc.
|
||||
* However, I realized that you can simply call something like the following within a module:
|
||||
* `FlxG.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);`
|
||||
* This is more efficient than adding an entire event handler for every key press.
|
||||
*
|
||||
* -Eric
|
||||
*/
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||
*/
|
||||
interface IPlayStateScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onPause(event:PauseScriptEvent):Void;
|
||||
public function onResume(event:ScriptEvent):Void;
|
||||
|
||||
public function onSongLoaded(eent:SongLoadScriptEvent):Void;
|
||||
public function onSongStart(event:ScriptEvent):Void;
|
||||
public function onSongEnd(event:ScriptEvent):Void;
|
||||
public function onGameOver(event:ScriptEvent):Void;
|
||||
public function onSongRetry(event:ScriptEvent):Void;
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void;
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent):Void;
|
||||
public function onCountdownStep(event:CountdownScriptEvent):Void;
|
||||
public function onCountdownEnd(event:CountdownScriptEvent):Void;
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
package funkin.modding;
|
||||
|
||||
import polymod.Polymod.ModMetadata;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.stage.StageData;
|
||||
import polymod.Polymod;
|
||||
import polymod.backends.OpenFLBackend;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.LinesParseFormat;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
|
||||
class PolymodHandler
|
||||
|
@ -31,6 +30,15 @@ class PolymodHandler
|
|||
loadModsById(getAllModIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the game with configured mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadEnabledMods()
|
||||
{
|
||||
trace("Initializing Polymod (using configured mods)...");
|
||||
loadModsById(getEnabledModIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the game without any mods enabled with Polymod.
|
||||
*/
|
||||
|
@ -146,8 +154,8 @@ class PolymodHandler
|
|||
{
|
||||
return {
|
||||
assetLibraryPaths: [
|
||||
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8",
|
||||
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -165,4 +173,61 @@ class PolymodHandler
|
|||
var modIds = [for (i in getAllMods()) i.id];
|
||||
return modIds;
|
||||
}
|
||||
|
||||
public static function setEnabledMods(newModList:Array<String>):Void
|
||||
{
|
||||
FlxG.save.data.enabledMods = newModList;
|
||||
// Make sure to COMMIT the changes.
|
||||
FlxG.save.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled mods.
|
||||
* @return Array<String>
|
||||
*/
|
||||
public static function getEnabledModIds():Array<String>
|
||||
{
|
||||
if (FlxG.save.data.enabledMods == null)
|
||||
{
|
||||
// NOTE: If the value is null, the enabled mod list is unconfigured.
|
||||
// Currently, we default to disabling newly installed mods.
|
||||
// If we want to auto-enable new mods, but otherwise leave the configured list in place,
|
||||
// we will need some custom logic.
|
||||
FlxG.save.data.enabledMods = [];
|
||||
}
|
||||
return FlxG.save.data.enabledMods;
|
||||
}
|
||||
|
||||
public static function getEnabledMods():Array<ModMetadata>
|
||||
{
|
||||
var modIds = getEnabledModIds();
|
||||
var modMetadata = getAllMods();
|
||||
var enabledMods = [];
|
||||
for (item in modMetadata)
|
||||
{
|
||||
if (modIds.indexOf(item.id) != -1)
|
||||
{
|
||||
enabledMods.push(item);
|
||||
}
|
||||
}
|
||||
return enabledMods;
|
||||
}
|
||||
|
||||
public static function forceReloadAssets()
|
||||
{
|
||||
// Forcibly clear scripts so that scripts can be edited.
|
||||
ModuleHandler.clearModuleCache();
|
||||
polymod.hscript.PolymodScriptClass.clearScriptClasses();
|
||||
|
||||
// Forcibly reload Polymod so it finds any new files.
|
||||
loadEnabledMods();
|
||||
|
||||
// Reload scripted classes so stages and modules will update.
|
||||
polymod.hscript.PolymodScriptClass.registerAllScriptClasses();
|
||||
|
||||
// Reload the stages in cache.
|
||||
// TODO: Currently this causes lag since you're reading a lot of files, how to fix?
|
||||
StageDataParser.loadStageCache();
|
||||
ModuleHandler.loadModuleCache();
|
||||
}
|
||||
}
|
||||
|
|
554
source/funkin/modding/events/ScriptEvent.hx
Normal file
554
source/funkin/modding/events/ScriptEvent.hx
Normal file
|
@ -0,0 +1,554 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import funkin.Note.NoteDir;
|
||||
import funkin.play.Countdown.CountdownStep;
|
||||
import openfl.events.EventType;
|
||||
import openfl.events.KeyboardEvent;
|
||||
|
||||
typedef ScriptEventType = EventType<ScriptEvent>;
|
||||
|
||||
/**
|
||||
* This is a base class for all events that are issued to scripted classes.
|
||||
* It can be used to identify the type of event called, store data, and cancel event propagation.
|
||||
*/
|
||||
class ScriptEvent
|
||||
{
|
||||
/**
|
||||
* Called when the relevant object is created.
|
||||
* Keep in mind that the constructor may be called before the object is needed,
|
||||
* for the purposes of caching data or otherwise.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final CREATE:ScriptEventType = "CREATE";
|
||||
|
||||
/**
|
||||
* Called when the relevant object is destroyed.
|
||||
* This should perform relevant cleanup to ensure good performance.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final DESTROY:ScriptEventType = "DESTROY";
|
||||
|
||||
/**
|
||||
* Called during the update function.
|
||||
* This is called every frame, so be careful!
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final UPDATE:ScriptEventType = "UPDATE";
|
||||
|
||||
/**
|
||||
* Called when the player moves to pause the game.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from pausing.
|
||||
*/
|
||||
public static inline final PAUSE:ScriptEventType = "PAUSE";
|
||||
|
||||
/**
|
||||
* Called when the player moves to unpause the game while paused.
|
||||
*
|
||||
* This event IS cancelable! Canceling the event will prevent the game from resuming.
|
||||
*/
|
||||
public static inline final RESUME:ScriptEventType = "RESUME";
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 4 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_BEAT_HIT:ScriptEventType = "BEAT_HIT";
|
||||
|
||||
/**
|
||||
* Called once per step in the song. This happens 16 times per measure.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_STEP_HIT:ScriptEventType = "STEP_HIT";
|
||||
|
||||
/**
|
||||
* Called when a character hits a note.
|
||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being hit,
|
||||
* and will likely result in a miss later.
|
||||
*/
|
||||
public static inline final NOTE_HIT:ScriptEventType = "NOTE_HIT";
|
||||
|
||||
/**
|
||||
* Called when a character misses a note.
|
||||
* Important information such as note data, player/opponent, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding a combo break and lost health.
|
||||
*/
|
||||
public static inline final NOTE_MISS:ScriptEventType = "NOTE_MISS";
|
||||
|
||||
/**
|
||||
* Called when a character presses a note when there was none there, causing them to lose health.
|
||||
* Important information such as direction pressed, etc. are all provided.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||
* avoiding lost health/score and preventing the miss animation.
|
||||
*/
|
||||
public static inline final NOTE_GHOST_MISS:ScriptEventType = "NOTE_GHOST_MISS";
|
||||
|
||||
/**
|
||||
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_START:ScriptEventType = "SONG_START";
|
||||
|
||||
/**
|
||||
* Called when the song ends. This happens as the instrumental and vocals end.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_END:ScriptEventType = "SONG_END";
|
||||
|
||||
/**
|
||||
* Called when the countdown begins. This occurs before the song starts.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
|
||||
* - The song will not start until you call Countdown.performCountdown() later.
|
||||
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
|
||||
*/
|
||||
public static inline final COUNTDOWN_START:ScriptEventType = "COUNTDOWN_START";
|
||||
|
||||
/**
|
||||
* Called when a step of the countdown happens.
|
||||
* Includes information about what step of the countdown was hit.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will pause the countdown.
|
||||
* - The countdown will not resume until you call PlayState.resumeCountdown().
|
||||
*/
|
||||
public static inline final COUNTDOWN_STEP:ScriptEventType = "COUNTDOWN_STEP";
|
||||
|
||||
/**
|
||||
* Called when the countdown is done but just before the song starts.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final COUNTDOWN_END:ScriptEventType = "COUNTDOWN_END";
|
||||
|
||||
/**
|
||||
* Called before the game over screen triggers and the death animation plays.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER";
|
||||
|
||||
/**
|
||||
* Called when the player presses a key to restart the game.
|
||||
* This can happen from the pause menu or the game over screen.
|
||||
*
|
||||
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
||||
*/
|
||||
public static inline final SONG_RETRY:ScriptEventType = "SONG_RETRY";
|
||||
|
||||
/**
|
||||
* Called when the player pushes down any key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final KEY_DOWN:ScriptEventType = "KEY_DOWN";
|
||||
|
||||
/**
|
||||
* Called when the player releases a key on the keyboard.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final KEY_UP:ScriptEventType = "KEY_UP";
|
||||
|
||||
/**
|
||||
* Called when the game has finished loading the notes from JSON.
|
||||
* This allows modders to mutate the notes before they are used in the song.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SONG_LOADED:ScriptEventType = "SONG_LOADED";
|
||||
|
||||
/**
|
||||
* Called when the game is about to switch the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final STATE_CHANGE_BEGIN:ScriptEventType = "STATE_CHANGE_BEGIN";
|
||||
|
||||
/**
|
||||
* Called when the game has finished switching the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final STATE_CHANGE_END:ScriptEventType = "STATE_CHANGE_END";
|
||||
|
||||
/**
|
||||
* Called when the game is about to open a new FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = "SUBSTATE_OPEN_BEGIN";
|
||||
|
||||
/**
|
||||
* Called when the game has finished opening a new FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_OPEN_END:ScriptEventType = "SUBSTATE_OPEN_END";
|
||||
|
||||
/**
|
||||
* Called when the game is about to close the current FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = "SUBSTATE_CLOSE_BEGIN";
|
||||
|
||||
/**
|
||||
* Called when the game has finished closing the current FlxSubState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final SUBSTATE_CLOSE_END:ScriptEventType = "SUBSTATE_CLOSE_END";
|
||||
|
||||
/**
|
||||
* Called when the game is exiting the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
/**
|
||||
* If true, the behavior associated with this event can be prevented.
|
||||
* For example, cancelling COUNTDOWN_START should prevent the countdown from starting,
|
||||
* until another script restarts it, or cancelling NOTE_HIT should cause the note to be missed.
|
||||
*/
|
||||
public var cancelable(default, null):Bool;
|
||||
|
||||
/**
|
||||
* The type associated with the event.
|
||||
*/
|
||||
public var type(default, null):ScriptEventType;
|
||||
|
||||
/**
|
||||
* Whether the event should continue to be triggered on additional targets.
|
||||
*/
|
||||
public var shouldPropagate(default, null):Bool;
|
||||
|
||||
/**
|
||||
* Whether the event has been canceled by one of the scripts that received it.
|
||||
*/
|
||||
public var eventCanceled(default, null):Bool;
|
||||
|
||||
public function new(type:ScriptEventType, cancelable:Bool = false):Void
|
||||
{
|
||||
this.type = type;
|
||||
this.cancelable = cancelable;
|
||||
this.eventCanceled = false;
|
||||
this.shouldPropagate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function on a cancelable event to cancel the associated behavior.
|
||||
* For example, cancelling COUNTDOWN_START will prevent the countdown from starting.
|
||||
*/
|
||||
public function cancelEvent():Void
|
||||
{
|
||||
if (cancelable)
|
||||
{
|
||||
eventCanceled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel():Void
|
||||
{
|
||||
// This typo happens enough that I just added this.
|
||||
cancelEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to stop any other Scripteds from receiving the event.
|
||||
*/
|
||||
public function stopPropagation():Void
|
||||
{
|
||||
shouldPropagate = false;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'ScriptEvent(type=$type, cancelable=$cancelable)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SPECIFIC EVENTS
|
||||
*/
|
||||
/**
|
||||
* An event that is fired associated with a specific note.
|
||||
*/
|
||||
class NoteScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var note(default, null):Note;
|
||||
|
||||
public function new(type:ScriptEventType, note:Note, cancelable:Bool = false):Void
|
||||
{
|
||||
super(type, cancelable);
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when you press a key with no note present.
|
||||
*/
|
||||
class GhostMissNoteScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The direction that was mistakenly pressed.
|
||||
*/
|
||||
public var dir(default, null):NoteDir;
|
||||
|
||||
/**
|
||||
* Whether there was a note within judgement range when this ghost note was pressed.
|
||||
*/
|
||||
public var hasPossibleNotes(default, null):Bool;
|
||||
|
||||
/**
|
||||
* How much health should be lost when this ghost note is pressed.
|
||||
* Remember that max health is 2.00.
|
||||
*/
|
||||
public var healthChange(default, default):Float;
|
||||
|
||||
/**
|
||||
* How much score should be lost when this ghost note is pressed.
|
||||
*/
|
||||
public var scoreChange(default, default):Int;
|
||||
|
||||
/**
|
||||
* Whether to play the record scratch sound.
|
||||
*/
|
||||
public var playSound(default, default):Bool;
|
||||
|
||||
/**
|
||||
* Whether to play the miss animation on the player.
|
||||
*/
|
||||
public var playAnim(default, default):Bool;
|
||||
|
||||
public function new(dir:NoteDir, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
|
||||
{
|
||||
super(ScriptEvent.NOTE_GHOST_MISS, true);
|
||||
this.dir = dir;
|
||||
this.hasPossibleNotes = hasPossibleNotes;
|
||||
this.healthChange = healthChange;
|
||||
this.scoreChange = scoreChange;
|
||||
this.playSound = true;
|
||||
this.playAnim = true;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'GhostMissNoteScriptEvent(dir=' + dir + ', hasPossibleNotes=' + hasPossibleNotes + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired during the update loop.
|
||||
*/
|
||||
class UpdateScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var elapsed(default, null):Float;
|
||||
|
||||
public function new(elapsed:Float):Void
|
||||
{
|
||||
super(ScriptEvent.UPDATE, false);
|
||||
this.elapsed = elapsed;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'UpdateScriptEvent(elapsed=$elapsed)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired regularly during the song.
|
||||
* May be on beat or on step.
|
||||
*/
|
||||
class SongTimeScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The current beat of the song.
|
||||
*/
|
||||
public var beat(default, null):Int;
|
||||
|
||||
/**
|
||||
* The current step of the song.
|
||||
*/
|
||||
public var step(default, null):Int;
|
||||
|
||||
public function new(type:ScriptEventType, beat:Int, step:Int):Void
|
||||
{
|
||||
super(type, true);
|
||||
this.beat = beat;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'SongTimeScriptEvent(type=' + type + ', beat=' + beat + ', step=' + step + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired regularly during the song.
|
||||
* May be on beat or on step.
|
||||
*/
|
||||
class CountdownScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The current step of the countdown.
|
||||
*/
|
||||
public var step(default, null):CountdownStep;
|
||||
|
||||
public function new(type:ScriptEventType, step:CountdownStep, cancelable = true):Void
|
||||
{
|
||||
super(type, cancelable);
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'CountdownScriptEvent(type=' + type + ', step=' + step + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when the player presses a key.
|
||||
*/
|
||||
class KeyboardInputScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The associated keyboard event.
|
||||
*/
|
||||
public var event(default, null):KeyboardEvent;
|
||||
|
||||
public function new(type:ScriptEventType, event:KeyboardEvent):Void
|
||||
{
|
||||
super(type, false);
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'KeyboardInputScriptEvent(type=' + type + ', event=' + event + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired once the song's chart has been parsed.
|
||||
*/
|
||||
class SongLoadScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var notes(default, set):Array<Note>;
|
||||
|
||||
public var id(default, null):String;
|
||||
|
||||
public var difficulty(default, null):String;
|
||||
|
||||
function set_notes(notes:Array<Note>):Array<Note>
|
||||
{
|
||||
this.notes = notes;
|
||||
return this.notes;
|
||||
}
|
||||
|
||||
public function new(id:String, difficulty:String, notes:Array<Note>):Void
|
||||
{
|
||||
super(ScriptEvent.SONG_LOADED, false);
|
||||
this.id = id;
|
||||
this.difficulty = difficulty;
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
var noteStr = notes == null ? 'null' : 'Array(' + notes.length + ')';
|
||||
return 'SongLoadScriptEvent(notes=$noteStr, id=$id, difficulty=$difficulty)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when moving out of or into an FlxState.
|
||||
*/
|
||||
class StateChangeScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The state the game is moving into.
|
||||
*/
|
||||
public var targetState(default, null):FlxState;
|
||||
|
||||
public function new(type:ScriptEventType, targetState:FlxState, cancelable:Bool = false):Void
|
||||
{
|
||||
super(type, cancelable);
|
||||
this.targetState = targetState;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'StateChangeScriptEvent(type=' + type + ', targetState=' + targetState + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when moving out of or into an FlxSubState.
|
||||
*/
|
||||
class SubStateScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The state the game is moving into.
|
||||
*/
|
||||
public var targetState(default, null):FlxSubState;
|
||||
|
||||
public function new(type:ScriptEventType, targetState:FlxSubState, cancelable:Bool = false):Void
|
||||
{
|
||||
super(type, cancelable);
|
||||
this.targetState = targetState;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'SubStateScriptEvent(type=' + type + ', targetState=' + targetState + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event which is called when the player attempts to pause the game.
|
||||
*/
|
||||
class PauseScriptEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* Whether to use the Gitaroo Man pause.
|
||||
*/
|
||||
public var gitaroo(default, default):Bool;
|
||||
|
||||
public function new(gitaroo:Bool):Void
|
||||
{
|
||||
super(ScriptEvent.PAUSE, true);
|
||||
this.gitaroo = gitaroo;
|
||||
}
|
||||
}
|
152
source/funkin/modding/events/ScriptEventDispatcher.hx
Normal file
152
source/funkin/modding/events/ScriptEventDispatcher.hx
Normal file
|
@ -0,0 +1,152 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.IScriptedClass;
|
||||
|
||||
/**
|
||||
* Utility functions to assist with handling scripted classes.
|
||||
*/
|
||||
class ScriptEventDispatcher
|
||||
{
|
||||
public static function callEvent(target:IScriptedClass, event:ScriptEvent):Void
|
||||
{
|
||||
if (target == null || event == null)
|
||||
return;
|
||||
|
||||
target.onScriptEvent(event);
|
||||
|
||||
// If one target says to stop propagation, stop.
|
||||
if (!event.shouldPropagate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// IScriptedClass
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.CREATE:
|
||||
target.onCreate(event);
|
||||
return;
|
||||
case ScriptEvent.DESTROY:
|
||||
target.onDestroy(event);
|
||||
return;
|
||||
case ScriptEvent.UPDATE:
|
||||
target.onUpdate(cast event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||
{
|
||||
var t = cast(target, IPlayStateScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.NOTE_HIT:
|
||||
t.onNoteHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.NOTE_MISS:
|
||||
t.onNoteMiss(cast event);
|
||||
return;
|
||||
case ScriptEvent.NOTE_GHOST_MISS:
|
||||
t.onNoteGhostMiss(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_START:
|
||||
t.onSongStart(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_END:
|
||||
t.onSongEnd(event);
|
||||
return;
|
||||
case ScriptEvent.SONG_RETRY:
|
||||
t.onSongRetry(event);
|
||||
return;
|
||||
case ScriptEvent.GAME_OVER:
|
||||
t.onGameOver(event);
|
||||
return;
|
||||
case ScriptEvent.PAUSE:
|
||||
t.onPause(cast event);
|
||||
return;
|
||||
case ScriptEvent.RESUME:
|
||||
t.onResume(event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_START:
|
||||
t.onCountdownStart(cast event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_STEP:
|
||||
t.onCountdownStep(cast event);
|
||||
return;
|
||||
case ScriptEvent.COUNTDOWN_END:
|
||||
t.onCountdownEnd(cast event);
|
||||
return;
|
||||
case ScriptEvent.SONG_LOADED:
|
||||
t.onSongLoaded(cast event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IStateChangingScriptedClass))
|
||||
{
|
||||
var t = cast(target, IStateChangingScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.STATE_CHANGE_BEGIN:
|
||||
t.onStateChangeBegin(cast event);
|
||||
return;
|
||||
case ScriptEvent.STATE_CHANGE_END:
|
||||
t.onStateChangeEnd(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_OPEN_BEGIN:
|
||||
t.onSubstateOpenBegin(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_OPEN_END:
|
||||
t.onSubstateOpenEnd(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_CLOSE_BEGIN:
|
||||
t.onSubstateCloseBegin(cast event);
|
||||
return;
|
||||
case ScriptEvent.SUBSTATE_CLOSE_END:
|
||||
t.onSubstateCloseEnd(cast event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prevent "NO HELPER error."
|
||||
return;
|
||||
}
|
||||
|
||||
throw "No function called for event type: " + event.type;
|
||||
}
|
||||
|
||||
public static function callEventOnAllTargets(targets:Iterator<IScriptedClass>, event:ScriptEvent):Void
|
||||
{
|
||||
if (targets == null || event == null)
|
||||
return;
|
||||
|
||||
if (Std.isOfType(targets, Array))
|
||||
{
|
||||
var t = cast(targets, Array<Dynamic>);
|
||||
if (t.length == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
for (target in targets)
|
||||
{
|
||||
var t:IScriptedClass = cast target;
|
||||
if (t == null)
|
||||
continue;
|
||||
|
||||
callEvent(t, event);
|
||||
|
||||
// If one target says to stop propagation, stop.
|
||||
if (!event.shouldPropagate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
source/funkin/modding/module/Module.hx
Normal file
118
source/funkin/modding/module/Module.hx
Normal file
|
@ -0,0 +1,118 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
/**
|
||||
* A module is a scripted class which receives all events without requiring a specific context.
|
||||
* You may have the module active at all times, or only when another script enables it.
|
||||
*/
|
||||
class Module implements IPlayStateScriptedClass implements IStateChangingScriptedClass
|
||||
{
|
||||
/**
|
||||
* Whether the module is currently active.
|
||||
*/
|
||||
public var active(default, set):Bool = true;
|
||||
|
||||
function set_active(value:Bool):Bool
|
||||
{
|
||||
this.active = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public var moduleId(default, null):String = 'UNKNOWN';
|
||||
|
||||
/**
|
||||
* Determines the order in which modules receive events.
|
||||
* You can modify this to change the order in which a given module receives events.
|
||||
*
|
||||
* Priority 1 is processed before Priority 1000, etc.
|
||||
*/
|
||||
public var priority(default, set):Int;
|
||||
|
||||
function set_priority(value:Int):Int
|
||||
{
|
||||
this.priority = value;
|
||||
@:privateAccess
|
||||
ModuleHandler.reorderModuleCache();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the module is initialized.
|
||||
* It may not be safe to reference other modules here since they may not be loaded yet.
|
||||
*
|
||||
* NOTE: To make the module start inactive, call `this.active = false` in the constructor.
|
||||
*/
|
||||
public function new(moduleId:String, priority:Int = 1000):Void
|
||||
{
|
||||
this.moduleId = moduleId;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public function toString()
|
||||
{
|
||||
return 'Module(' + this.moduleId + ')';
|
||||
}
|
||||
|
||||
// TODO: Half of these aren't actually being called!!!!!!!
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
/**
|
||||
* Called when the module is first created.
|
||||
* This happens before the title screen appears!
|
||||
*/
|
||||
public function onCreate(event:ScriptEvent) {}
|
||||
|
||||
/**
|
||||
* Called when a module is destroyed.
|
||||
* This currently only happens when reloading modules with F5.
|
||||
*/
|
||||
public function onDestroy(event:ScriptEvent) {}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||
|
||||
public function onBeatHit(event:SongTimeScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
||||
|
||||
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
|
||||
|
||||
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
||||
|
||||
public function onSubstateOpenBegin(event:SubStateScriptEvent) {}
|
||||
|
||||
public function onSubstateOpenEnd(event:SubStateScriptEvent) {}
|
||||
|
||||
public function onSubstateCloseBegin(event:SubStateScriptEvent) {}
|
||||
|
||||
public function onSubstateCloseEnd(event:SubStateScriptEvent) {}
|
||||
|
||||
public function onSongRetry(event:ScriptEvent) {}
|
||||
}
|
132
source/funkin/modding/module/ModuleHandler.hx
Normal file
132
source/funkin/modding/module/ModuleHandler.hx
Normal file
|
@ -0,0 +1,132 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
|
||||
using funkin.util.IteratorTools;
|
||||
|
||||
/**
|
||||
* Utility functions for loading and manipulating active modules.
|
||||
*/
|
||||
class ModuleHandler
|
||||
{
|
||||
static final moduleCache:Map<String, Module> = new Map<String, Module>();
|
||||
static var modulePriorityOrder:Array<String> = [];
|
||||
|
||||
/**
|
||||
* Parses and preloads the game's stage data and scripts when the game starts.
|
||||
*
|
||||
* If you want to force stages to be reloaded, you can just call this function again.
|
||||
*/
|
||||
public static function loadModuleCache():Void
|
||||
{
|
||||
// Clear any stages that are cached if there were any.
|
||||
clearModuleCache();
|
||||
trace("[MODULEHANDLER] Loading module cache...");
|
||||
|
||||
var scriptedModuleClassNames:Array<String> = ScriptedModule.listScriptClasses();
|
||||
trace(' Instantiating ${scriptedModuleClassNames.length} modules...');
|
||||
for (moduleCls in scriptedModuleClassNames)
|
||||
{
|
||||
var module:Module = ScriptedModule.init(moduleCls, moduleCls);
|
||||
if (module != null)
|
||||
{
|
||||
trace(' Loaded module: ${moduleCls}');
|
||||
|
||||
// Then store it.
|
||||
addToModuleCache(module);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Failed to instantiate module: ${moduleCls}');
|
||||
}
|
||||
}
|
||||
reorderModuleCache();
|
||||
|
||||
trace("[MODULEHANDLER] Module cache loaded.");
|
||||
}
|
||||
|
||||
static function addToModuleCache(module:Module):Void
|
||||
{
|
||||
moduleCache.set(module.moduleId, module);
|
||||
}
|
||||
|
||||
static function reorderModuleCache():Void
|
||||
{
|
||||
modulePriorityOrder = moduleCache.keys().array();
|
||||
|
||||
modulePriorityOrder.sort(function(a:String, b:String):Int
|
||||
{
|
||||
var aModule:Module = moduleCache.get(a);
|
||||
var bModule:Module = moduleCache.get(b);
|
||||
|
||||
if (aModule.priority != bModule.priority)
|
||||
{
|
||||
return aModule.priority - bModule.priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sort alphabetically. Yes that's how this works.
|
||||
return a > b ? 1 : -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function getModule(moduleId:String):Module
|
||||
{
|
||||
return moduleCache.get(moduleId);
|
||||
}
|
||||
|
||||
public static function activateModule(moduleId:String):Void
|
||||
{
|
||||
var module:Module = getModule(moduleId);
|
||||
if (module != null)
|
||||
{
|
||||
module.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static function deactivateModule(moduleId:String):Void
|
||||
{
|
||||
var module:Module = getModule(moduleId);
|
||||
if (module != null)
|
||||
{
|
||||
module.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the module cache, forcing all modules to call shutdown events.
|
||||
*/
|
||||
public static function clearModuleCache():Void
|
||||
{
|
||||
if (moduleCache != null)
|
||||
{
|
||||
var event = new ScriptEvent(ScriptEvent.DESTROY, false);
|
||||
|
||||
// Note: Ignore stopPropagation()
|
||||
for (key => value in moduleCache)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(value, event);
|
||||
moduleCache.remove(key);
|
||||
}
|
||||
|
||||
moduleCache.clear();
|
||||
modulePriorityOrder = [];
|
||||
}
|
||||
}
|
||||
|
||||
public static function callEvent(event:ScriptEvent):Void
|
||||
{
|
||||
for (moduleId in modulePriorityOrder)
|
||||
{
|
||||
var module:Module = moduleCache.get(moduleId);
|
||||
// The module needs to be active to receive events.
|
||||
if (module != null && module.active)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(module, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
source/funkin/modding/module/ScriptedModule.hx
Normal file
9
source/funkin/modding/module/ScriptedModule.hx
Normal file
|
@ -0,0 +1,9 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedModule extends Module implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
63
source/funkin/play/AnimationData.hx
Normal file
63
source/funkin/play/AnimationData.hx
Normal file
|
@ -0,0 +1,63 @@
|
|||
package funkin.play;
|
||||
|
||||
typedef AnimationData =
|
||||
{
|
||||
/**
|
||||
* The name for the animation.
|
||||
* This should match the animation name queried by the game;
|
||||
* for example, characters need animations with names `idle`, `singDOWN`, `singUPmiss`, etc.
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The prefix for the frames of the animation as defined by the XML file.
|
||||
* This will may or may not differ from the `name` of the animation,
|
||||
* depending on how your animator organized their FLA or whatever.
|
||||
*/
|
||||
var prefix:String;
|
||||
|
||||
/**
|
||||
* Optionally specify an asset path to use for this specific animation.
|
||||
* ONLY for use by MultiSparrow characters.
|
||||
* @default The assetPath of the parent sprite
|
||||
*/
|
||||
var assetPath:Null<String>;
|
||||
|
||||
/**
|
||||
* Offset the character's position by this amount when playing this animation.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
var offsets:Null<Array<Float>>;
|
||||
|
||||
/**
|
||||
* Whether the animation should loop when it finishes.
|
||||
* @default false
|
||||
*/
|
||||
var looped:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Whether the animation's sprites should be flipped horizontally.
|
||||
* @default false
|
||||
*/
|
||||
var flipX:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Whether the animation's sprites should be flipped vertically.
|
||||
* @default false
|
||||
*/
|
||||
var flipY:Null<Bool>;
|
||||
|
||||
/**
|
||||
* The frame rate of the animation.
|
||||
* @default 24
|
||||
*/
|
||||
var frameRate:Null<Int>;
|
||||
|
||||
/**
|
||||
* If you want this animation to use only certain frames of an animation with a given prefix,
|
||||
* select them here.
|
||||
* @example [0, 1, 2, 3] (use only the first four frames)
|
||||
* @default [] (all frames)
|
||||
*/
|
||||
var frameIndices:Null<Array<Int>>;
|
||||
}
|
311
source/funkin/play/Countdown.hx
Normal file
311
source/funkin/play/Countdown.hx
Normal file
|
@ -0,0 +1,311 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Countdown
|
||||
{
|
||||
/**
|
||||
* The current step of the countdown.
|
||||
*/
|
||||
public static var countdownStep(default, null):CountdownStep = BEFORE;
|
||||
|
||||
/**
|
||||
* The currently running countdown. This will be null if there is no countdown running.
|
||||
*/
|
||||
static var countdownTimer:FlxTimer = null;
|
||||
|
||||
/**
|
||||
* Performs the countdown.
|
||||
* Pauses the song, plays the countdown graphics/sound, and then starts the song.
|
||||
* This will automatically stop and restart the countdown if it is already running.
|
||||
*/
|
||||
public static function performCountdown(isPixelStyle:Bool):Void
|
||||
{
|
||||
// Stop any existing countdown.
|
||||
stopCountdown();
|
||||
|
||||
PlayState.isInCountdown = true;
|
||||
Conductor.songPosition = Conductor.crochet * -5;
|
||||
countdownStep = BEFORE;
|
||||
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
if (cancelled)
|
||||
return;
|
||||
|
||||
// The timer function gets called based on the beat of the song.
|
||||
countdownTimer = new FlxTimer();
|
||||
|
||||
countdownTimer.start(Conductor.crochet / 1000, function(tmr:FlxTimer)
|
||||
{
|
||||
countdownStep = decrement(countdownStep);
|
||||
|
||||
// Play the dance animations manually.
|
||||
@:privateAccess
|
||||
PlayState.instance.danceOnBeat();
|
||||
|
||||
// Countdown graphic.
|
||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
||||
|
||||
// Countdown sound.
|
||||
playCountdownSound(countdownStep, isPixelStyle);
|
||||
|
||||
// Event handling bullshit.
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
|
||||
if (cancelled)
|
||||
pauseCountdown();
|
||||
|
||||
if (countdownStep == AFTER)
|
||||
{
|
||||
stopCountdown();
|
||||
}
|
||||
}, 6); // Before, 3, 2, 1, GO!, After
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TRUE if the event was cancelled.
|
||||
*/
|
||||
static function propagateCountdownEvent(index:CountdownStep):Bool
|
||||
{
|
||||
var event:ScriptEvent;
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case BEFORE:
|
||||
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_START, index);
|
||||
case THREE | TWO | ONE | GO: // I didn't know you could use `|` in a switch/case block!
|
||||
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_STEP, index);
|
||||
case AFTER:
|
||||
event = new CountdownScriptEvent(ScriptEvent.COUNTDOWN_END, index, false);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stage
|
||||
ScriptEventDispatcher.callEvent(PlayState.instance.currentStage, event);
|
||||
|
||||
// Modules
|
||||
ModuleHandler.callEvent(event);
|
||||
|
||||
return event.eventCanceled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the countdown at the current step. You can start it up again later by calling resumeCountdown().
|
||||
*
|
||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
||||
*/
|
||||
public static function pauseCountdown()
|
||||
{
|
||||
if (countdownTimer != null && !countdownTimer.finished)
|
||||
{
|
||||
countdownTimer.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the countdown at the current step. Only makes sense if you called pauseCountdown() first.
|
||||
*
|
||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
||||
*/
|
||||
public static function resumeCountdown()
|
||||
{
|
||||
if (countdownTimer != null && !countdownTimer.finished)
|
||||
{
|
||||
countdownTimer.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the countdown at the current step. You will have to restart it again later.
|
||||
*
|
||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event.
|
||||
*/
|
||||
public static function stopCountdown()
|
||||
{
|
||||
if (countdownTimer != null)
|
||||
{
|
||||
countdownTimer.cancel();
|
||||
countdownTimer.destroy();
|
||||
countdownTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the current countdown, then starts the song for you.
|
||||
*/
|
||||
public static function skipCountdown()
|
||||
{
|
||||
stopCountdown();
|
||||
// This will trigger PlayState.startSong()
|
||||
Conductor.songPosition = 0;
|
||||
// PlayState.isInCountdown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the countdown. Only works if it's already running.
|
||||
*/
|
||||
public static function resetCountdown()
|
||||
{
|
||||
if (countdownTimer != null)
|
||||
{
|
||||
countdownTimer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the graphic to use for this step of the countdown.
|
||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
||||
*
|
||||
* This is public so modules can do lol funny shit.
|
||||
*/
|
||||
public static function showCountdownGraphic(index:CountdownStep, isPixelStyle:Bool):Void
|
||||
{
|
||||
var spritePath:String = null;
|
||||
|
||||
if (isPixelStyle)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case TWO:
|
||||
spritePath = 'weeb/pixelUI/ready-pixel';
|
||||
case ONE:
|
||||
spritePath = 'weeb/pixelUI/set-pixel';
|
||||
case GO:
|
||||
spritePath = 'weeb/pixelUI/date-pixel';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case TWO:
|
||||
spritePath = 'ready';
|
||||
case ONE:
|
||||
spritePath = 'set';
|
||||
case GO:
|
||||
spritePath = 'go';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
|
||||
if (spritePath == null)
|
||||
return;
|
||||
|
||||
var countdownSprite:FlxSprite = new FlxSprite(0, 0).loadGraphic(Paths.image(spritePath));
|
||||
countdownSprite.scrollFactor.set(0, 0);
|
||||
|
||||
if (isPixelStyle)
|
||||
countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
||||
|
||||
countdownSprite.updateHitbox();
|
||||
countdownSprite.screenCenter();
|
||||
|
||||
// Fade sprite in, then out, then destroy it.
|
||||
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.crochet / 1000, {
|
||||
ease: FlxEase.cubeInOut,
|
||||
onComplete: function(twn:FlxTween)
|
||||
{
|
||||
countdownSprite.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
PlayState.instance.add(countdownSprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sound file to use for this step of the countdown.
|
||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
||||
*
|
||||
* This is public so modules can do lol funny shit.
|
||||
*/
|
||||
public static function playCountdownSound(index:CountdownStep, isPixelStyle:Bool):Void
|
||||
{
|
||||
var soundPath:String = null;
|
||||
|
||||
if (isPixelStyle)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case THREE:
|
||||
soundPath = 'intro3-pixel';
|
||||
case TWO:
|
||||
soundPath = 'intro2-pixel';
|
||||
case ONE:
|
||||
soundPath = 'intro1-pixel';
|
||||
case GO:
|
||||
soundPath = 'introGo-pixel';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case THREE:
|
||||
soundPath = 'intro3';
|
||||
case TWO:
|
||||
soundPath = 'intro2';
|
||||
case ONE:
|
||||
soundPath = 'intro1';
|
||||
case GO:
|
||||
soundPath = 'introGo';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
|
||||
if (soundPath == null)
|
||||
return;
|
||||
|
||||
FlxG.sound.play(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME);
|
||||
}
|
||||
|
||||
public static function decrement(step:CountdownStep):CountdownStep
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case BEFORE:
|
||||
return THREE;
|
||||
case THREE:
|
||||
return TWO;
|
||||
case TWO:
|
||||
return ONE;
|
||||
case ONE:
|
||||
return GO;
|
||||
case GO:
|
||||
return AFTER;
|
||||
|
||||
default:
|
||||
return AFTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The countdown step.
|
||||
* This can't be an enum abstract because scripts may need it.
|
||||
*/
|
||||
enum CountdownStep
|
||||
{
|
||||
BEFORE;
|
||||
THREE;
|
||||
TWO;
|
||||
ONE;
|
||||
GO;
|
||||
AFTER;
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.effects.FlxTrail;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
|
||||
class PicoFight extends MusicBeatState
|
||||
{
|
||||
|
@ -37,7 +36,7 @@ class PicoFight extends MusicBeatState
|
|||
FlxG.sound.playMusic(Paths.inst("blazin"));
|
||||
|
||||
SongLoad.loadFromJson('blazin', "blazin");
|
||||
Conductor.changeBPM(SongLoad.songData.bpm);
|
||||
Conductor.bpm = SongLoad.songData.bpm;
|
||||
|
||||
for (dumbassSection in SongLoad.songData.noteMap['hard'])
|
||||
{
|
||||
|
@ -184,13 +183,15 @@ class PicoFight extends MusicBeatState
|
|||
super.update(elapsed);
|
||||
}
|
||||
|
||||
override function stepHit()
|
||||
override function stepHit():Bool
|
||||
{
|
||||
super.stepHit();
|
||||
return super.stepHit();
|
||||
}
|
||||
|
||||
override function beatHit()
|
||||
override function beatHit():Bool
|
||||
{
|
||||
if (!super.beatHit())
|
||||
return false;
|
||||
funnyWave.thickness = 10;
|
||||
funnyWave.waveAmplitude = 300;
|
||||
funnyWave.realtimeVisLenght = 0.1;
|
||||
|
@ -198,7 +199,6 @@ class PicoFight extends MusicBeatState
|
|||
picoHealth += 1;
|
||||
|
||||
makeNotes();
|
||||
// trace(picoHealth);
|
||||
super.beatHit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
6
source/funkin/play/Scoring.hx
Normal file
6
source/funkin/play/Scoring.hx
Normal file
|
@ -0,0 +1,6 @@
|
|||
package funkin.play;
|
||||
|
||||
/**
|
||||
* A static class which holds any functions related to scoring.
|
||||
*/
|
||||
class Scoring {}
|
253
source/funkin/play/Strumline.hx
Normal file
253
source/funkin/play/Strumline.hx
Normal file
|
@ -0,0 +1,253 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import funkin.Note.NoteColor;
|
||||
import funkin.Note.NoteDir;
|
||||
import funkin.Note.NoteType;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.util.Constants;
|
||||
|
||||
/**
|
||||
* A group controlling the individual notes of the strumline for a given player.
|
||||
*
|
||||
* FUN FACT: Setting the X and Y of a FlxSpriteGroup will move all the sprites in the group.
|
||||
*/
|
||||
class Strumline extends FlxTypedSpriteGroup<StrumlineArrow>
|
||||
{
|
||||
/**
|
||||
* The style of the strumline.
|
||||
* Options are normal and pixel.
|
||||
*/
|
||||
var style:StrumlineStyle;
|
||||
|
||||
/**
|
||||
* The player this strumline belongs to.
|
||||
* 0 is Player 1, etc.
|
||||
*/
|
||||
var playerId:Int;
|
||||
|
||||
/**
|
||||
* The number of notes in the strumline.
|
||||
*/
|
||||
var size:Int;
|
||||
|
||||
public function new(playerId:Int = 0, style:StrumlineStyle = NORMAL, size:Int = 4)
|
||||
{
|
||||
super(0);
|
||||
this.playerId = playerId;
|
||||
this.style = style;
|
||||
this.size = size;
|
||||
|
||||
generateStrumline();
|
||||
}
|
||||
|
||||
function generateStrumline():Void
|
||||
{
|
||||
for (index in 0...size)
|
||||
{
|
||||
createStrumlineArrow(index);
|
||||
}
|
||||
}
|
||||
|
||||
function createStrumlineArrow(index:Int):Void
|
||||
{
|
||||
var arrow:StrumlineArrow = new StrumlineArrow(index, style);
|
||||
add(arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a small animation which moves the arrow down and fades it in.
|
||||
* Only plays at the start of Free Play songs.
|
||||
*
|
||||
* Note that modifying the offset of the whole strumline won't have the
|
||||
* @param arrow The arrow to animate.
|
||||
* @param index The index of the arrow in the strumline.
|
||||
*/
|
||||
function fadeInArrow(arrow:FlxSprite):Void
|
||||
{
|
||||
arrow.y -= 10;
|
||||
arrow.alpha = 0;
|
||||
FlxTween.tween(arrow, {y: arrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * arrow.ID)});
|
||||
}
|
||||
|
||||
public function fadeInArrows():Void
|
||||
{
|
||||
for (arrow in this.members)
|
||||
{
|
||||
fadeInArrow(arrow);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePositions()
|
||||
{
|
||||
for (arrow in members)
|
||||
{
|
||||
arrow.x = Note.swagWidth * arrow.ID;
|
||||
arrow.x += offset.x;
|
||||
|
||||
arrow.y = 0;
|
||||
arrow.y += offset.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the arrow at the given position in the strumline.
|
||||
* @param index The index to retrieve.
|
||||
* @return The corresponding FlxSprite.
|
||||
*/
|
||||
public inline function getArrow(value:Int):StrumlineArrow
|
||||
{
|
||||
// members maintains the order that the arrows were added.
|
||||
return this.members[value];
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteType(value:NoteType):StrumlineArrow
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteDir(value:NoteDir):StrumlineArrow
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteColor(value:NoteColor):StrumlineArrow
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default Y offset of the strumline.
|
||||
* @return Int
|
||||
*/
|
||||
public static inline function getYPos():Int
|
||||
{
|
||||
return PreferencesMenu.getPref('downscroll') ? (FlxG.height - 150) : 50;
|
||||
}
|
||||
}
|
||||
|
||||
class StrumlineArrow extends FlxSprite
|
||||
{
|
||||
var style:StrumlineStyle;
|
||||
|
||||
public function new(id:Int, style:StrumlineStyle)
|
||||
{
|
||||
super(0, 0);
|
||||
|
||||
this.ID = id;
|
||||
this.style = style;
|
||||
|
||||
// TODO: Unhardcode this. Maybe use a note style system>
|
||||
switch (style)
|
||||
{
|
||||
case PIXEL:
|
||||
buildPixelGraphic();
|
||||
case NORMAL:
|
||||
buildNormalGraphic();
|
||||
}
|
||||
|
||||
this.updateHitbox();
|
||||
scrollFactor.set(0, 0);
|
||||
animation.play('static');
|
||||
}
|
||||
|
||||
public function playAnimation(anim:String, force:Bool = false)
|
||||
{
|
||||
animation.play(anim, force);
|
||||
centerOffsets();
|
||||
centerOrigin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the default note style to an arrow.
|
||||
*/
|
||||
function buildNormalGraphic():Void
|
||||
{
|
||||
this.frames = Paths.getSparrowAtlas('NOTE_assets');
|
||||
|
||||
this.animation.addByPrefix('green', 'arrowUP');
|
||||
this.animation.addByPrefix('blue', 'arrowDOWN');
|
||||
this.animation.addByPrefix('purple', 'arrowLEFT');
|
||||
this.animation.addByPrefix('red', 'arrowRIGHT');
|
||||
|
||||
this.setGraphicSize(Std.int(this.width * 0.7));
|
||||
this.antialiasing = true;
|
||||
|
||||
this.x += Note.swagWidth * this.ID;
|
||||
|
||||
switch (Math.abs(this.ID))
|
||||
{
|
||||
case 0:
|
||||
this.animation.addByPrefix('static', 'arrow static instance 1');
|
||||
this.animation.addByPrefix('pressed', 'left press', 24, false);
|
||||
this.animation.addByPrefix('confirm', 'left confirm', 24, false);
|
||||
case 1:
|
||||
this.animation.addByPrefix('static', 'arrow static instance 2');
|
||||
this.animation.addByPrefix('pressed', 'down press', 24, false);
|
||||
this.animation.addByPrefix('confirm', 'down confirm', 24, false);
|
||||
case 2:
|
||||
this.animation.addByPrefix('static', 'arrow static instance 4');
|
||||
this.animation.addByPrefix('pressed', 'up press', 24, false);
|
||||
this.animation.addByPrefix('confirm', 'up confirm', 24, false);
|
||||
case 3:
|
||||
this.animation.addByPrefix('static', 'arrow static instance 3');
|
||||
this.animation.addByPrefix('pressed', 'right press', 24, false);
|
||||
this.animation.addByPrefix('confirm', 'right confirm', 24, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the pixel note style to an arrow.
|
||||
*/
|
||||
function buildPixelGraphic():Void
|
||||
{
|
||||
this.loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17);
|
||||
|
||||
this.animation.add('purplel', [4]);
|
||||
this.animation.add('blue', [5]);
|
||||
this.animation.add('green', [6]);
|
||||
this.animation.add('red', [7]);
|
||||
|
||||
this.setGraphicSize(Std.int(this.width * Constants.PIXEL_ART_SCALE));
|
||||
this.updateHitbox();
|
||||
|
||||
// Forcibly disable anti-aliasing on pixel graphics to stop blur.
|
||||
this.antialiasing = false;
|
||||
|
||||
this.x += Note.swagWidth * this.ID;
|
||||
|
||||
// TODO: Seems weird that these are hardcoded like this... no XML?
|
||||
switch (Math.abs(this.ID))
|
||||
{
|
||||
case 0:
|
||||
this.animation.add('static', [0]);
|
||||
this.animation.add('pressed', [4, 8], 12, false);
|
||||
this.animation.add('confirm', [12, 16], 24, false);
|
||||
case 1:
|
||||
this.animation.add('static', [1]);
|
||||
this.animation.add('pressed', [5, 9], 12, false);
|
||||
this.animation.add('confirm', [13, 17], 24, false);
|
||||
case 2:
|
||||
this.animation.add('static', [2]);
|
||||
this.animation.add('pressed', [6, 10], 12, false);
|
||||
this.animation.add('confirm', [14, 18], 12, false);
|
||||
case 3:
|
||||
this.animation.add('static', [3]);
|
||||
this.animation.add('pressed', [7, 11], 12, false);
|
||||
this.animation.add('confirm', [15, 19], 24, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Unhardcode this and make it part of the note style system.
|
||||
*/
|
||||
enum StrumlineStyle
|
||||
{
|
||||
NORMAL;
|
||||
PIXEL;
|
||||
}
|
103
source/funkin/play/VanillaCutscenes.hx
Normal file
103
source/funkin/play/VanillaCutscenes.hx
Normal file
|
@ -0,0 +1,103 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
/**
|
||||
* Static methods for playing cutscenes in the PlayState.
|
||||
* TODO: Softcode this shit!!!!!1!
|
||||
*/
|
||||
class VanillaCutscenes
|
||||
{
|
||||
public static function playUghCutscene():Void
|
||||
{
|
||||
playVideoCutscene('music/ughCutscene.mp4');
|
||||
}
|
||||
|
||||
public static function playGunsCutscene():Void
|
||||
{
|
||||
playVideoCutscene('music/gunsCutscene.mp4');
|
||||
}
|
||||
|
||||
public static function playStressCutscene():Void
|
||||
{
|
||||
playVideoCutscene('music/stressCutscene.mp4');
|
||||
}
|
||||
|
||||
static var blackScreen:FlxSprite;
|
||||
|
||||
/**
|
||||
* Plays a cutscene from a video file, then starts the countdown once the video is done.
|
||||
* TODO: Cutscene is currently skipped on native platforms.
|
||||
*/
|
||||
static function playVideoCutscene(path:String):Void
|
||||
{
|
||||
PlayState.isInCutscene = true;
|
||||
PlayState.instance.camHUD.visible = false;
|
||||
|
||||
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
blackScreen.scrollFactor.set(0, 0);
|
||||
PlayState.instance.add(blackScreen);
|
||||
|
||||
#if html5
|
||||
var vid:FlxVideo = new FlxVideo(path);
|
||||
vid.finishCallback = finishVideoCutscene;
|
||||
#else
|
||||
finishVideoCutscene();
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the cleanup to start the countdown after the video is done.
|
||||
* Gets called immediately if the video can't be played.
|
||||
*/
|
||||
static function finishVideoCutscene():Void
|
||||
{
|
||||
PlayState.instance.remove(blackScreen);
|
||||
blackScreen = null;
|
||||
|
||||
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
||||
@:privateAccess
|
||||
PlayState.instance.startCountdown();
|
||||
@:privateAccess
|
||||
PlayState.instance.cameraMovement();
|
||||
}
|
||||
|
||||
public static function playHorrorStartCutscene()
|
||||
{
|
||||
PlayState.isInCutscene = true;
|
||||
PlayState.instance.camHUD.visible = false;
|
||||
|
||||
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
blackScreen.scrollFactor.set(0, 0);
|
||||
PlayState.instance.add(blackScreen);
|
||||
|
||||
new FlxTimer().start(0.1, function(tmr:FlxTimer)
|
||||
{
|
||||
PlayState.instance.remove(blackScreen);
|
||||
FlxG.sound.play(Paths.sound('Lights_Turn_On'));
|
||||
PlayState.instance.cameraFollowPoint.y = -2050;
|
||||
PlayState.instance.cameraFollowPoint.x += 200;
|
||||
FlxG.camera.focusOn(PlayState.instance.cameraFollowPoint.getPosition());
|
||||
FlxG.camera.zoom = 1.5;
|
||||
|
||||
new FlxTimer().start(0.8, function(tmr:FlxTimer)
|
||||
{
|
||||
PlayState.instance.camHUD.visible = true;
|
||||
PlayState.instance.remove(blackScreen);
|
||||
blackScreen = null;
|
||||
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, 2.5, {
|
||||
ease: FlxEase.quadInOut,
|
||||
onComplete: function(twn:FlxTween)
|
||||
{
|
||||
Countdown.performCountdown(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
/**
|
||||
* A Bopper is a stage prop which plays a dance animation.
|
||||
* Y'know, a thingie that bops. A bopper.
|
||||
*/
|
||||
class Bopper extends FlxSprite
|
||||
class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||
{
|
||||
/**
|
||||
* The bopper plays the dance animation once every `danceEvery` beats.
|
||||
* Set to 0 to disable idle animation.
|
||||
*/
|
||||
public var danceEvery:Int = 1;
|
||||
|
||||
|
@ -23,16 +26,14 @@ class Bopper extends FlxSprite
|
|||
public var shouldAlternate:Null<Bool> = null;
|
||||
|
||||
/**
|
||||
* Set this value to define an additional horizontal offset to this sprite's position.
|
||||
* Offset the character's sprite by this much when playing each animation.
|
||||
*/
|
||||
public var xOffset:Float = 0;
|
||||
|
||||
override function set_x(value:Float):Float
|
||||
{
|
||||
this.x = value + this.xOffset;
|
||||
return value;
|
||||
}
|
||||
public var animationOffsets:Map<String, Array<Float>> = new Map<String, Array<Float>>();
|
||||
|
||||
/**
|
||||
* Add a suffix to the `idle` animation (or `danceLeft` and `danceRight` animations)
|
||||
* that this bopper will play.
|
||||
*/
|
||||
public var idleSuffix(default, set):String = "";
|
||||
|
||||
function set_idleSuffix(value:String):String
|
||||
|
@ -43,14 +44,26 @@ class Bopper extends FlxSprite
|
|||
}
|
||||
|
||||
/**
|
||||
* Set this value to define an additional vertical offset to this sprite's position.
|
||||
* The offset of the character relative to the position specified by the stage.
|
||||
*/
|
||||
public var yOffset:Float = 0;
|
||||
public var globalOffsets(default, null):Array<Float> = [0, 0];
|
||||
|
||||
override function set_y(value:Float):Float
|
||||
private var animOffsets(default, set):Array<Float> = [0, 0];
|
||||
|
||||
function set_animOffsets(value:Array<Float>)
|
||||
{
|
||||
this.y = value + this.yOffset;
|
||||
return value;
|
||||
if (animOffsets == null)
|
||||
animOffsets = [0, 0];
|
||||
if (animOffsets == value)
|
||||
return value;
|
||||
|
||||
var xDiff = animOffsets[0] - value[0];
|
||||
var yDiff = animOffsets[1] - value[1];
|
||||
|
||||
this.x += xDiff;
|
||||
this.y += yDiff;
|
||||
|
||||
return animOffsets = value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +80,7 @@ class Bopper extends FlxSprite
|
|||
|
||||
function update_shouldAlternate():Void
|
||||
{
|
||||
if (this.animation.getByName('danceLeft') != null)
|
||||
if (hasAnimation('danceLeft'))
|
||||
{
|
||||
this.shouldAlternate = true;
|
||||
}
|
||||
|
@ -76,18 +89,18 @@ class Bopper extends FlxSprite
|
|||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(curBeat:Int):Void
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
if (curBeat % danceEvery == 0)
|
||||
if (danceEvery > 0 && event.beat % danceEvery == 0)
|
||||
{
|
||||
dance();
|
||||
dance(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every `danceEvery` beats of the song.
|
||||
*/
|
||||
public function dance():Void
|
||||
public function dance(force:Bool = false):Void
|
||||
{
|
||||
if (this.animation == null)
|
||||
{
|
||||
|
@ -103,17 +116,146 @@ class Bopper extends FlxSprite
|
|||
{
|
||||
if (hasDanced)
|
||||
{
|
||||
this.animation.play('danceRight$idleSuffix');
|
||||
playAnimation('danceRight$idleSuffix', true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animation.play('danceLeft$idleSuffix');
|
||||
playAnimation('danceLeft$idleSuffix', true);
|
||||
}
|
||||
hasDanced = !hasDanced;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animation.play('idle$idleSuffix');
|
||||
playAnimation('idle$idleSuffix', true);
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAnimation(id:String):Bool
|
||||
{
|
||||
if (this.animation == null)
|
||||
return false;
|
||||
|
||||
return this.animation.getByName(id) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that a given animation exists before playing it.
|
||||
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
|
||||
* @param name
|
||||
*/
|
||||
function correctAnimationName(name:String)
|
||||
{
|
||||
// If the animation exists, we're good.
|
||||
if (hasAnimation(name))
|
||||
return name;
|
||||
|
||||
trace('[BOPPER] Animation "$name" does not exist!');
|
||||
|
||||
// Attempt to strip a `-alt` suffix, if it exists.
|
||||
if (name.lastIndexOf('-') != -1)
|
||||
{
|
||||
var correctName = name.substring(0, name.lastIndexOf('-'));
|
||||
trace('[BOPPER] Attempting to fallback to "$correctName"');
|
||||
return correctAnimationName(correctName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name != 'idle')
|
||||
{
|
||||
trace('[BOPPER] Attempting to fallback to "idle"');
|
||||
return correctAnimationName('idle');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[BOPPER] Failing animation playback.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name The name of the animation to play.
|
||||
* @param restart Whether to restart the animation if it is already playing.
|
||||
*/
|
||||
public function playAnimation(name:String, restart:Bool = false):Void
|
||||
{
|
||||
var correctName = correctAnimationName(name);
|
||||
if (correctName == null)
|
||||
return;
|
||||
|
||||
this.animation.play(correctName, restart, false, 0);
|
||||
|
||||
applyAnimationOffsets(correctName);
|
||||
}
|
||||
|
||||
function applyAnimationOffsets(name:String)
|
||||
{
|
||||
var offsets = animationOffsets.get(name);
|
||||
if (offsets != null)
|
||||
{
|
||||
this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animOffsets = globalOffsets;
|
||||
}
|
||||
}
|
||||
|
||||
public function isAnimationFinished():Bool
|
||||
{
|
||||
return this.animation.finished;
|
||||
}
|
||||
|
||||
public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void
|
||||
{
|
||||
animationOffsets.set(name, [xOffset, yOffset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the animation that is currently playing.
|
||||
* If no animation is playing (usually this means the character is BROKEN!),
|
||||
* returns an empty string to prevent NPEs.
|
||||
*/
|
||||
public function getCurrentAnimation():String
|
||||
{
|
||||
if (this.animation == null || this.animation.curAnim == null)
|
||||
return "";
|
||||
return this.animation.curAnim.name;
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onCreate(event:ScriptEvent) {}
|
||||
|
||||
public function onDestroy(event:ScriptEvent) {}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onSongLoaded(eent:SongLoadScriptEvent) {}
|
||||
|
||||
public function onSongRetry(event:ScriptEvent) {}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,22 @@ package funkin.play.stage;
|
|||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.modding.IHook;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.Character.CharacterType;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
|
||||
/**
|
||||
* A Stage is a group of objects rendered in the PlayState.
|
||||
*
|
||||
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
||||
*/
|
||||
class Stage extends FlxSpriteGroup implements IHook
|
||||
class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScriptedClass
|
||||
{
|
||||
public final stageId:String;
|
||||
public final stageName:String;
|
||||
|
@ -50,16 +53,24 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the player is moving into the PlayState where the song will be played.
|
||||
*/
|
||||
public function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
buildStage();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default stage construction routine. Called when the stage is going to be played in.
|
||||
* Instantiates each prop and adds it to the stage, while setting its parameters.
|
||||
*/
|
||||
public function buildStage()
|
||||
function buildStage()
|
||||
{
|
||||
trace('Building stage for display: ${this.stageId}');
|
||||
|
||||
this.camZoom = _data.cameraZoom;
|
||||
// this.scrollFactor = new FlxPoint(1, 1);
|
||||
|
||||
for (dataProp in _data.props)
|
||||
{
|
||||
|
@ -130,19 +141,19 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
for (propAnim in dataProp.animations)
|
||||
{
|
||||
propSprite.animation.add(propAnim.name, propAnim.frameIndices);
|
||||
|
||||
if (Std.isOfType(propSprite, Bopper))
|
||||
{
|
||||
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
|
||||
}
|
||||
}
|
||||
default: // "sparrow"
|
||||
for (propAnim in dataProp.animations)
|
||||
FlxAnimationUtil.addAtlasAnimations(propSprite, dataProp.animations);
|
||||
if (Std.isOfType(propSprite, Bopper))
|
||||
{
|
||||
if (propAnim.frameIndices.length == 0)
|
||||
for (propAnim in dataProp.animations)
|
||||
{
|
||||
propSprite.animation.addByPrefix(propAnim.name, propAnim.prefix, propAnim.frameRate, propAnim.loop, propAnim.flipX,
|
||||
propAnim.flipY);
|
||||
}
|
||||
else
|
||||
{
|
||||
propSprite.animation.addByIndices(propAnim.name, propAnim.prefix, propAnim.frameIndices, "", propAnim.frameRate, propAnim.loop,
|
||||
propAnim.flipX, propAnim.flipY);
|
||||
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,8 +173,6 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
}
|
||||
trace(' Prop placed.');
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,39 +209,6 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
trace('Stage sorted by z-index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the stage and it's props (needs to be overridden with your own logic!)
|
||||
*/
|
||||
public function resetStage()
|
||||
{
|
||||
// Override me in your script to reset stage shit however you please!
|
||||
// also note: maybe add some default behaviour to reset stage stuff?
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that should get called every frame.
|
||||
*/
|
||||
public function onUpdate(elapsed:Float):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called when the player hits a note.
|
||||
*/
|
||||
public function onNoteHit(note:Note):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called when the player hits a note.
|
||||
*/
|
||||
public function onNoteMiss(note:Note):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the position and other properties of the soon-to-be child of this sprite group.
|
||||
* Private helper to avoid duplicate code in `add()` and `insert()`.
|
||||
|
@ -253,30 +229,6 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
clipRectTransform(sprite, clipRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called once per step in the song.
|
||||
* @param curStep The current step number.
|
||||
*/
|
||||
public function onStepHit(curStep:Int):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called once per beat in the song (once every four steps).
|
||||
* @param curStep The current beat number.
|
||||
*/
|
||||
public function onBeatHit(curBeat:Int):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
// Make sure to call super.onBeatHit(curBeat) if you want to keep the boppers dancing.
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
bopper.onBeatHit(curBeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the PlayState to add a character to the stage.
|
||||
*/
|
||||
|
@ -358,45 +310,108 @@ class Stage extends FlxSpriteGroup implements IHook
|
|||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup for when you are leaving the level.
|
||||
* onDestroy gets called when the player is leaving the PlayState,
|
||||
* and is used to clean up any objects that need to be destroyed.
|
||||
*/
|
||||
public override function kill()
|
||||
public function onDestroy(event:ScriptEvent):Void
|
||||
{
|
||||
super.kill();
|
||||
// Make sure to call kill() when returning a stage to cache,
|
||||
// and destroy() only when performing a hard cache refresh.
|
||||
kill();
|
||||
|
||||
for (prop in this.namedProps)
|
||||
{
|
||||
prop.destroy();
|
||||
if (prop != null)
|
||||
{
|
||||
remove(prop);
|
||||
prop.kill();
|
||||
prop.destroy();
|
||||
}
|
||||
}
|
||||
namedProps.clear();
|
||||
|
||||
for (char in this.characters)
|
||||
{
|
||||
char.destroy();
|
||||
if (char != null)
|
||||
{
|
||||
remove(char);
|
||||
char.kill();
|
||||
char.destroy();
|
||||
}
|
||||
}
|
||||
characters.clear();
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
bopper.destroy();
|
||||
if (bopper != null)
|
||||
{
|
||||
remove(bopper);
|
||||
bopper.kill();
|
||||
bopper.destroy();
|
||||
}
|
||||
}
|
||||
boppers = [];
|
||||
|
||||
for (sprite in this.group)
|
||||
{
|
||||
sprite.destroy();
|
||||
if (sprite != null)
|
||||
{
|
||||
sprite.kill();
|
||||
sprite.destroy();
|
||||
remove(sprite);
|
||||
}
|
||||
}
|
||||
group.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup for when you are destroying the stage
|
||||
* and removing all its data from cache.
|
||||
*
|
||||
* Call this ONLY when you are performing a hard cache clear.
|
||||
* A function that gets called once per step in the song.
|
||||
* @param curStep The current step number.
|
||||
*/
|
||||
public override function destroy()
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
||||
|
||||
/**
|
||||
* A function that gets called once per beat in the song (once every four steps).
|
||||
* @param curStep The current beat number.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
super.destroy();
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
// Make sure to call super.onBeatHit(curBeat) if you want to keep the boppers dancing.
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||
|
||||
public function onSongLoaded(eent:SongLoadScriptEvent) {}
|
||||
|
||||
public function onSongRetry(event:ScriptEvent) {}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import openfl.Assets;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.util.VersionUtil;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import haxe.Json;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import openfl.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -17,7 +18,12 @@ class StageDataParser
|
|||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:String = "1.0";
|
||||
public static final STAGE_DATA_VERSION:String = "1.0.0";
|
||||
|
||||
/**
|
||||
* The current version rule check for the stage data format.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION_RULE:String = "1.0.x";
|
||||
|
||||
static final stageCache:Map<String, Stage> = new Map<String, Stage>();
|
||||
|
||||
|
@ -163,20 +169,21 @@ class StageDataParser
|
|||
}
|
||||
}
|
||||
|
||||
static final DEFAULT_NAME:String = "Untitled Stage";
|
||||
static final DEFAULT_CAMERAZOOM:Float = 1.0;
|
||||
static final DEFAULT_ZINDEX:Int = 0;
|
||||
static final DEFAULT_DANCEEVERY:Int = 0;
|
||||
static final DEFAULT_SCALE:Float = 1.0;
|
||||
static final DEFAULT_ISPIXEL:Bool = false;
|
||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
static final DEFAULT_FRAMEINDICES:Array<Int> = [];
|
||||
static final DEFAULT_ANIMTYPE:String = "sparrow";
|
||||
static final DEFAULT_CAMERAZOOM:Float = 1.0;
|
||||
static final DEFAULT_DANCEEVERY:Int = 0;
|
||||
static final DEFAULT_ISPIXEL:Bool = false;
|
||||
static final DEFAULT_NAME:String = "Untitled Stage";
|
||||
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||
static final DEFAULT_SCALE:Float = 1.0;
|
||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
static final DEFAULT_ZINDEX:Int = 0;
|
||||
|
||||
static final DEFAULT_CHARACTER_DATA:StageDataCharacter = {
|
||||
zIndex: DEFAULT_ZINDEX,
|
||||
position: DEFAULT_POSITION,
|
||||
cameraOffsets: DEFAULT_OFFSETS,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,12 +201,18 @@ class StageDataParser
|
|||
return null;
|
||||
}
|
||||
|
||||
if (input.version != STAGE_DATA_VERSION)
|
||||
if (input.version == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!VersionUtil.validateVersion(input.version, STAGE_DATA_VERSION_RULE))
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": bad version (got ${input.version}, expected ${STAGE_DATA_VERSION_RULE})');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.name == null)
|
||||
{
|
||||
trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
|
||||
|
@ -211,10 +224,9 @@ class StageDataParser
|
|||
input.cameraZoom = DEFAULT_CAMERAZOOM;
|
||||
}
|
||||
|
||||
if (input.props == null || input.props.length == 0)
|
||||
if (input.props == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props');
|
||||
return null;
|
||||
input.props = [];
|
||||
}
|
||||
|
||||
for (inputProp in input.props)
|
||||
|
@ -296,14 +308,14 @@ class StageDataParser
|
|||
inputAnimation.frameRate = 24;
|
||||
}
|
||||
|
||||
if (inputAnimation.frameIndices == null)
|
||||
if (inputAnimation.offsets == null)
|
||||
{
|
||||
inputAnimation.frameIndices = DEFAULT_FRAMEINDICES;
|
||||
inputAnimation.offsets = DEFAULT_OFFSETS;
|
||||
}
|
||||
|
||||
if (inputAnimation.loop == null)
|
||||
if (inputAnimation.looped == null)
|
||||
{
|
||||
inputAnimation.loop = true;
|
||||
inputAnimation.looped = true;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipX == null)
|
||||
|
@ -347,6 +359,10 @@ class StageDataParser
|
|||
{
|
||||
inputCharacter.position = [0, 0];
|
||||
}
|
||||
if (inputCharacter.cameraOffsets == null || inputCharacter.cameraOffsets.length != 2)
|
||||
{
|
||||
inputCharacter.cameraOffsets = [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
// All good!
|
||||
|
@ -356,8 +372,12 @@ class StageDataParser
|
|||
|
||||
typedef StageData =
|
||||
{
|
||||
// Uses semantic versioning.
|
||||
/**
|
||||
* The sematic version number of the stage data JSON format.
|
||||
* Supports fancy comparisons like NPM does it's neat.
|
||||
*/
|
||||
var version:String;
|
||||
|
||||
var name:String;
|
||||
var cameraZoom:Null<Float>;
|
||||
var props:Array<StageDataProp>;
|
||||
|
@ -432,7 +452,7 @@ typedef StageDataProp =
|
|||
* An optional array of animations which the prop can play.
|
||||
* @default Prop has no animations.
|
||||
*/
|
||||
var animations:Array<StageDataPropAnimation>;
|
||||
var animations:Array<AnimationData>;
|
||||
|
||||
/**
|
||||
* If animations are used, this is the name of the animation to play first.
|
||||
|
@ -448,52 +468,6 @@ typedef StageDataProp =
|
|||
var animType:String;
|
||||
};
|
||||
|
||||
typedef StageDataPropAnimation =
|
||||
{
|
||||
/**
|
||||
* The name of the animation.
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The common beginning of image names in atlas for this animation's frames.
|
||||
* For example, if the frames are named "test0001.png", "test0002.png", etc., use "test".
|
||||
*/
|
||||
var prefix:String;
|
||||
|
||||
/**
|
||||
* If you want this animation to use only certain frames of an animation with a given prefix,
|
||||
* select them here.
|
||||
* @example [0, 1, 2, 3] (use only the first four frames)
|
||||
* @default [] (all frames)
|
||||
*/
|
||||
var frameIndices:Array<Int>;
|
||||
|
||||
/**
|
||||
* The speed of the animation in frames per second.
|
||||
* @default 24
|
||||
*/
|
||||
var frameRate:Null<Int>;
|
||||
|
||||
/**
|
||||
* Whether the animation should loop.
|
||||
* @default false
|
||||
*/
|
||||
var loop:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Whether to flip the sprite horizontally while animating.
|
||||
* @default false
|
||||
*/
|
||||
var flipX:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Whether to flip the sprite vertically while animating.
|
||||
* @default false
|
||||
*/
|
||||
var flipY:Null<Bool>;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
{
|
||||
/**
|
||||
|
@ -505,5 +479,12 @@ typedef StageDataCharacter =
|
|||
|
||||
/**
|
||||
* The position to render the character at.
|
||||
*/ position:Array<Float>
|
||||
*/
|
||||
position:Array<Float>,
|
||||
|
||||
/**
|
||||
* The camera offsets to apply when focusing on the character on this stage.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
cameraOffsets:Array<Float>,
|
||||
};
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
package funkin.ui;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.util.FlxStringUtil;
|
||||
|
||||
@:forward
|
||||
abstract BoldText(AtlasText) from AtlasText to AtlasText
|
||||
{
|
||||
inline public function new(x = 0.0, y = 0.0, text:String)
|
||||
{
|
||||
this = new AtlasText(x, y, text, Bold);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alphabet.hx has a ton of bugs and does a bunch of stuff I don't need, fuck that class
|
||||
* AtlasText is an improved version of Alphabet and FlxBitmapText.
|
||||
* It supports animations on the letters, and is less buggy than Alphabet.
|
||||
*/
|
||||
class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
||||
{
|
||||
|
@ -41,7 +33,7 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
|||
inline function get_maxHeight()
|
||||
return font.maxHeight;
|
||||
|
||||
public function new(x = 0.0, y = 0.0, text:String, fontName:AtlasFont = Default)
|
||||
public function new(x = 0.0, y = 0.0, text:String, fontName:AtlasFont = AtlasFont.DEFAULT)
|
||||
{
|
||||
if (!fonts.exists(fontName))
|
||||
fonts[fontName] = new AtlasFontData(fontName);
|
||||
|
@ -246,7 +238,14 @@ private class AtlasFontData
|
|||
|
||||
public function new(name:AtlasFont)
|
||||
{
|
||||
atlas = Paths.getSparrowAtlas("fonts/" + name.getName().toLowerCase());
|
||||
var fontName:String = name;
|
||||
atlas = Paths.getSparrowAtlas('fonts/${fontName.toLowerCase()}');
|
||||
if (atlas == null)
|
||||
{
|
||||
FlxG.log.warn('Could not find font atlas for font "${fontName}".');
|
||||
return;
|
||||
}
|
||||
|
||||
atlas.parent.destroyOnNoUse = false;
|
||||
atlas.parent.persist = true;
|
||||
|
||||
|
@ -276,8 +275,8 @@ enum Case
|
|||
Lower;
|
||||
}
|
||||
|
||||
enum AtlasFont
|
||||
enum abstract AtlasFont(String) from String to String
|
||||
{
|
||||
Default;
|
||||
Bold;
|
||||
var DEFAULT = "default";
|
||||
var BOLD = "bold";
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.ui;
|
||||
|
||||
import funkin.Controls;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -8,6 +7,7 @@ import flixel.group.FlxGroup;
|
|||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import funkin.Controls;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.MenuList;
|
||||
import funkin.ui.TextMenuList;
|
||||
|
@ -66,11 +66,11 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
|
|||
|
||||
var item;
|
||||
|
||||
item = deviceList.createItem("Keyboard", Bold, selectDevice.bind(Keys));
|
||||
item = deviceList.createItem("Keyboard", AtlasFont.BOLD, selectDevice.bind(Keys));
|
||||
item.x = FlxG.width / 2 - item.width - 30;
|
||||
item.y = (devicesBg.height - item.height) / 2;
|
||||
|
||||
item = deviceList.createItem("Gamepad", Bold, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id)));
|
||||
item = deviceList.createItem("Gamepad", AtlasFont.BOLD, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id)));
|
||||
item.x = FlxG.width / 2 + 30;
|
||||
item.y = (devicesBg.height - item.height) / 2;
|
||||
}
|
||||
|
@ -87,20 +87,20 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
|
|||
if (currentHeader != "UI_" && name.indexOf("UI_") == 0)
|
||||
{
|
||||
currentHeader = "UI_";
|
||||
headers.add(new BoldText(0, y, "UI")).screenCenter(X);
|
||||
headers.add(new AtlasText(0, y, "UI", AtlasFont.BOLD)).screenCenter(X);
|
||||
y += spacer;
|
||||
}
|
||||
else if (currentHeader != "NOTE_" && name.indexOf("NOTE_") == 0)
|
||||
{
|
||||
currentHeader = "NOTE_";
|
||||
headers.add(new BoldText(0, y, "NOTES")).screenCenter(X);
|
||||
headers.add(new AtlasText(0, y, "NOTES", AtlasFont.BOLD)).screenCenter(X);
|
||||
y += spacer;
|
||||
}
|
||||
|
||||
if (currentHeader != null && name.indexOf(currentHeader) == 0)
|
||||
name = name.substr(currentHeader.length);
|
||||
|
||||
var label = labels.add(new BoldText(150, y, name));
|
||||
var label = labels.add(new AtlasText(150, y, name, AtlasFont.BOLD));
|
||||
label.alpha = 0.6;
|
||||
for (i in 0...COLUMNS)
|
||||
createItem(label.x + 400 + i * 300, y, control, i);
|
||||
|
@ -317,7 +317,7 @@ class InputItem extends TextMenuItem
|
|||
this.index = index;
|
||||
this.input = getInput();
|
||||
|
||||
super(x, y, getLabel(input), Default, callback);
|
||||
super(x, y, getLabel(input), DEFAULT, callback);
|
||||
}
|
||||
|
||||
public function updateDevice(device:Device)
|
||||
|
|
|
@ -5,10 +5,9 @@ import flixel.FlxSubState;
|
|||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.util.FlxSignal;
|
||||
import funkin.i18n.FireTongueHandler.t;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
// typedef OptionsState = OptionsMenu_old;
|
||||
// class OptionsState_new extends MusicBeatState
|
||||
class OptionsState extends MusicBeatState
|
||||
{
|
||||
var pages = new Map<PageName, Page>();
|
||||
|
@ -31,17 +30,12 @@ class OptionsState extends MusicBeatState
|
|||
var options = addPage(Options, new OptionsMenu(false));
|
||||
var preferences = addPage(Preferences, new PreferencesMenu());
|
||||
var controls = addPage(Controls, new ControlsMenu());
|
||||
// var colors = addPage(Colors, new ColorsMenu());
|
||||
|
||||
var mods = addPage(Mods, new ModMenu());
|
||||
|
||||
if (options.hasMultipleOptions())
|
||||
{
|
||||
options.onExit.add(exitToMainMenu);
|
||||
controls.onExit.add(switchPage.bind(Options));
|
||||
// colors.onExit.add(switchPage.bind(Options));
|
||||
preferences.onExit.add(switchPage.bind(Options));
|
||||
mods.onExit.add(switchPage.bind(Options));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -67,12 +61,18 @@ class OptionsState extends MusicBeatState
|
|||
function setPage(name:PageName)
|
||||
{
|
||||
if (pages.exists(currentName))
|
||||
{
|
||||
currentPage.exists = false;
|
||||
currentPage.visible = false;
|
||||
}
|
||||
|
||||
currentName = name;
|
||||
|
||||
if (pages.exists(currentName))
|
||||
{
|
||||
currentPage.exists = true;
|
||||
currentPage.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
override function finishTransIn()
|
||||
|
@ -91,7 +91,7 @@ class OptionsState extends MusicBeatState
|
|||
function exitToMainMenu()
|
||||
{
|
||||
currentPage.enabled = false;
|
||||
// Todo animate?
|
||||
// TODO: Animate this transition?
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
}
|
||||
|
@ -172,30 +172,29 @@ class OptionsMenu extends Page
|
|||
super();
|
||||
|
||||
add(items = new TextMenuList());
|
||||
createItem(t("PREFERENCES"), function() switchPage(Preferences));
|
||||
createItem(t("CONTROLS"), function() switchPage(Controls));
|
||||
// createItem(t("COLORS"), function() switchPage(Colors));
|
||||
createItem(t("MODS"), function() switchPage(Mods));
|
||||
createItem("PREFERENCES", function() switchPage(Preferences));
|
||||
createItem("CONTROLS", function() switchPage(Controls));
|
||||
// createItem("COLORS", function() switchPage(Colors));
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
if (showDonate)
|
||||
{
|
||||
var hasPopupBlocker = #if web true #else false #end;
|
||||
createItem(t("DONATE"), selectDonate, hasPopupBlocker);
|
||||
createItem("DONATE", selectDonate, hasPopupBlocker);
|
||||
}
|
||||
#end
|
||||
#if newgrounds
|
||||
if (NGio.isLoggedIn)
|
||||
createItem(t("LOGOUT"), selectLogout);
|
||||
createItem("LOGOUT", selectLogout);
|
||||
else
|
||||
createItem(t("LOGIN"), selectLogin);
|
||||
createItem("LOGIN", selectLogin);
|
||||
#end
|
||||
createItem(t("EXIT"), exit);
|
||||
createItem("EXIT", exit);
|
||||
}
|
||||
|
||||
function createItem(name:String, callback:Void->Void, fireInstantly = false)
|
||||
{
|
||||
var item = items.createItem(0, 100 + items.length * 100, name, Bold, callback);
|
||||
var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.screenCenter(X);
|
||||
return item;
|
||||
|
@ -219,11 +218,7 @@ class OptionsMenu extends Page
|
|||
#if CAN_OPEN_LINKS
|
||||
function selectDonate()
|
||||
{
|
||||
#if linux
|
||||
Sys.command('/usr/bin/xdg-open', ["https://ninja-muffin24.itch.io/funkin", "&"]);
|
||||
#else
|
||||
FlxG.openURL('https://ninja-muffin24.itch.io/funkin');
|
||||
#end
|
||||
WindowUtil.openURL(Constants.URL_ITCH);
|
||||
}
|
||||
#end
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.ui;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
@ -22,7 +23,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var rating:FlxSprite = new FlxSprite();
|
||||
var ratingPath:String = daRating;
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
||||
|
||||
rating.loadGraphic(Paths.image(ratingPath));
|
||||
|
@ -40,9 +41,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
add(rating);
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
rating.setGraphicSize(Std.int(rating.width * PlayState.daPixelZoom * 0.7));
|
||||
rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -69,7 +70,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var pixelShitPart1:String = "";
|
||||
var pixelShitPart2:String = '';
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
pixelShitPart1 = 'weeb/pixelUI/';
|
||||
pixelShitPart2 = '-pixel';
|
||||
|
@ -90,9 +91,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
add(comboSpr);
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * PlayState.daPixelZoom * 0.7));
|
||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 0.7));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -129,9 +130,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var numScore:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
|
||||
numScore.y = comboSpr.y;
|
||||
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
numScore.setGraphicSize(Std.int(numScore.width * PlayState.daPixelZoom));
|
||||
numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -4,8 +4,8 @@ import flixel.FlxCamera;
|
|||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.ui.AtlasText.AtlasFont;
|
||||
import funkin.ui.TextMenuList.TextMenuItem;
|
||||
import funkin.ui.OptionsState.Page;
|
||||
import funkin.ui.TextMenuList.TextMenuItem;
|
||||
|
||||
class PreferencesMenu extends Page
|
||||
{
|
||||
|
@ -84,7 +84,7 @@ class PreferencesMenu extends Page
|
|||
|
||||
private function createPrefItem(prefName:String, prefString:String, prefValue:Dynamic):Void
|
||||
{
|
||||
items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.Bold, function()
|
||||
items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function()
|
||||
{
|
||||
preferenceCheck(prefString, prefValue);
|
||||
|
||||
|
@ -157,16 +157,17 @@ class PreferencesMenu extends Page
|
|||
});
|
||||
}
|
||||
|
||||
private static function preferenceCheck(prefString:String, prefValue:Dynamic):Void
|
||||
private static function preferenceCheck(prefString:String, defaultValue:Dynamic):Void
|
||||
{
|
||||
if (preferences.get(prefString) == null)
|
||||
{
|
||||
preferences.set(prefString, prefValue);
|
||||
trace('set preference!');
|
||||
// Set the value to default.
|
||||
preferences.set(prefString, defaultValue);
|
||||
trace('Set preference to default: ${prefString} = ${defaultValue}');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('found preference: ' + preferences.get(prefString));
|
||||
trace('Found preference: ${prefString} = ${preferences.get(prefString)}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package funkin.ui;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.AtlasText.AtlasFont;
|
||||
import funkin.ui.MenuList;
|
||||
|
||||
/**
|
||||
* Opens a yes/no dialog box as a substate over the current state.
|
||||
*/
|
||||
class Prompt extends flixel.FlxSubState
|
||||
{
|
||||
inline static var MARGIN = 100;
|
||||
|
@ -26,7 +26,7 @@ class Prompt extends flixel.FlxSubState
|
|||
|
||||
buttons = new TextMenuList(Horizontal);
|
||||
|
||||
field = new BoldText(text);
|
||||
field = new AtlasText(text, AtlasFont.BOLD);
|
||||
field.scrollFactor.set(0, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
|
|||
super(navControls, wrapMode);
|
||||
}
|
||||
|
||||
public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = Bold, callback, fireInstantly = false)
|
||||
public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback, fireInstantly = false)
|
||||
{
|
||||
var item = new TextMenuItem(x, y, name, font, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
|
@ -20,7 +20,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
|
|||
|
||||
class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
||||
{
|
||||
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = Bold, callback)
|
||||
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback)
|
||||
{
|
||||
super(x, y, new AtlasText(0, 0, name, font), name, callback);
|
||||
setEmptyBackground();
|
||||
|
|
39
source/funkin/util/Constants.hx
Normal file
39
source/funkin/util/Constants.hx
Normal file
|
@ -0,0 +1,39 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import lime.app.Application;
|
||||
|
||||
class Constants
|
||||
{
|
||||
/**
|
||||
* The scale factor to use when increasing the size of pixel art graphics.
|
||||
*/
|
||||
public static final PIXEL_ART_SCALE = 6;
|
||||
|
||||
public static final HEALTH_BAR_RED:FlxColor = 0xFFFF0000;
|
||||
public static final HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
|
||||
|
||||
public static final COUNTDOWN_VOLUME = 0.6;
|
||||
|
||||
public static final VERSION_SUFFIX = ' PROTOTYPE';
|
||||
public static var VERSION(get, null):String;
|
||||
|
||||
public static final FREAKY_MENU_BPM = 102;
|
||||
|
||||
#if debug
|
||||
public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash();
|
||||
|
||||
static function get_VERSION():String
|
||||
{
|
||||
return 'v${Application.current.meta.get('version')} (${GIT_HASH})' + VERSION_SUFFIX;
|
||||
}
|
||||
#else
|
||||
static function get_VERSION():String
|
||||
{
|
||||
return 'v${Application.current.meta.get('version')}' + VERSION_SUFFIX;
|
||||
}
|
||||
#end
|
||||
|
||||
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/";
|
||||
public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin";
|
||||
}
|
16
source/funkin/util/IteratorTools.hx
Normal file
16
source/funkin/util/IteratorTools.hx
Normal file
|
@ -0,0 +1,16 @@
|
|||
package funkin.util;
|
||||
|
||||
/**
|
||||
* A static extension which provides utility functions for Iterators.
|
||||
*
|
||||
* For example, add `using IteratorTools` then call `iterator.array()`.
|
||||
*
|
||||
* @see https://haxe.org/manual/lf-static-extension.html
|
||||
*/
|
||||
class IteratorTools
|
||||
{
|
||||
public static function array<T>(iterator:Iterator<T>):Array<T>
|
||||
{
|
||||
return [for (i in iterator) i];
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package funkin.util;
|
||||
|
||||
#if !macro
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.FlxObject;
|
||||
#end
|
||||
|
||||
class SortUtil
|
||||
|
@ -12,8 +11,18 @@ class SortUtil
|
|||
* You can use this function in FlxTypedGroup.sort() to sort FlxObjects by their z-index values.
|
||||
* The value defaults to 0, but by assigning it you can easily rearrange objects as desired.
|
||||
*/
|
||||
public static inline function byZIndex(Order:Int, Obj1:FlxObject, Obj2:FlxObject):Int
|
||||
public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int
|
||||
{
|
||||
return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two Notes, returns 1 or -1 based on whether `a` or `b` has an earlier strumtime.
|
||||
*
|
||||
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
|
||||
*/
|
||||
public static inline function byStrumtime(order:Int, a:Note, b:Note)
|
||||
{
|
||||
return FlxSort.byValues(order, a.data.strumTime, b.data.strumTime);
|
||||
}
|
||||
}
|
||||
|
|
31
source/funkin/util/VersionUtil.hx
Normal file
31
source/funkin/util/VersionUtil.hx
Normal file
|
@ -0,0 +1,31 @@
|
|||
package funkin.util;
|
||||
|
||||
import thx.semver.Version;
|
||||
import thx.semver.VersionRule;
|
||||
|
||||
/**
|
||||
* Remember, increment the patch version (1.0.x) if you make a bugfix,
|
||||
* increment the minor version (1.x.0) if you make a new feature (but previous content is still compatible),
|
||||
* and increment the major version (x.0.0) if you make a breaking change (e.g. new API or reorganized file format).
|
||||
*/
|
||||
class VersionUtil
|
||||
{
|
||||
/**
|
||||
* Checks that a given verison number satisisfies a given version rule.
|
||||
* Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports.
|
||||
*/
|
||||
public static function validateVersion(version:String, versionRule:String):Bool
|
||||
{
|
||||
try
|
||||
{
|
||||
var v:Version = version; // Perform a cast.
|
||||
var vr:VersionRule = versionRule; // Perform a cast.
|
||||
return v.satisfies(vr);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace('[VERSIONUTIL] Invalid semantic version: ${version}');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
18
source/funkin/util/WindowUtil.hx
Normal file
18
source/funkin/util/WindowUtil.hx
Normal file
|
@ -0,0 +1,18 @@
|
|||
package funkin.util;
|
||||
|
||||
class WindowUtil
|
||||
{
|
||||
public static function openURL(targetUrl:String)
|
||||
{
|
||||
#if CAN_OPEN_LINKS
|
||||
#if linux
|
||||
// Sys.command('/usr/bin/xdg-open', [, "&"]);
|
||||
Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]);
|
||||
#else
|
||||
FlxG.openURL(targetUrl);
|
||||
#end
|
||||
#else
|
||||
trace('Cannot open')
|
||||
#end
|
||||
}
|
||||
}
|
42
source/funkin/util/assets/FlxAnimationUtil.hx
Normal file
42
source/funkin/util/assets/FlxAnimationUtil.hx
Normal file
|
@ -0,0 +1,42 @@
|
|||
package funkin.util.assets;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.play.AnimationData;
|
||||
|
||||
class FlxAnimationUtil
|
||||
{
|
||||
/**
|
||||
* Properly adds an animation to a sprite based on JSON data.
|
||||
*/
|
||||
public static function addAtlasAnimation(target:FlxSprite, anim:AnimationData)
|
||||
{
|
||||
var frameRate = anim.frameRate == null ? 24 : anim.frameRate;
|
||||
var looped = anim.looped == null ? false : anim.looped;
|
||||
var flipX = anim.flipX == null ? false : anim.flipX;
|
||||
var flipY = anim.flipY == null ? false : anim.flipY;
|
||||
|
||||
if (anim.frameIndices != null && anim.frameIndices.length > 0)
|
||||
{
|
||||
// trace('addByIndices(${anim.name}, ${anim.prefix}, ${anim.frameIndices}, ${frameRate}, ${looped}, ${flipX}, ${flipY})');
|
||||
target.animation.addByIndices(anim.name, anim.prefix, anim.frameIndices, "", frameRate, looped, flipX, flipY);
|
||||
// trace('RESULT:${target.animation.getAnimationList()}');
|
||||
}
|
||||
else
|
||||
{
|
||||
// trace('addByPrefix(${anim.name}, ${anim.prefix}, ${frameRate}, ${looped}, ${flipX}, ${flipY})');
|
||||
target.animation.addByPrefix(anim.name, anim.prefix, frameRate, looped, flipX, flipY);
|
||||
// trace('RESULT:${target.animation.getAnimationList()}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly adds multiple animations to a sprite based on JSON data.
|
||||
*/
|
||||
public static function addAtlasAnimations(target:FlxSprite, animations:Array<AnimationData>)
|
||||
{
|
||||
for (anim in animations)
|
||||
{
|
||||
addAtlasAnimation(target, anim);
|
||||
}
|
||||
}
|
||||
}
|
35
source/funkin/util/macro/GitCommit.hx
Normal file
35
source/funkin/util/macro/GitCommit.hx
Normal file
|
@ -0,0 +1,35 @@
|
|||
package funkin.util.macro;
|
||||
|
||||
#if debug
|
||||
class GitCommit
|
||||
{
|
||||
public static macro function getGitCommitHash():haxe.macro.Expr.ExprOf<String>
|
||||
{
|
||||
#if !display
|
||||
// Get the current line number.
|
||||
var pos = haxe.macro.Context.currentPos();
|
||||
|
||||
var process = new sys.io.Process('git', ['rev-parse', 'HEAD']);
|
||||
if (process.exitCode() != 0)
|
||||
{
|
||||
var message = process.stderr.readAll().toString();
|
||||
haxe.macro.Context.info('[WARN] Could not determine current git commit; is this a proper Git repository?', pos);
|
||||
}
|
||||
|
||||
// read the output of the process
|
||||
var commitHash:String = process.stdout.readLine();
|
||||
var commitHashSplice:String = commitHash.substr(0, 7);
|
||||
|
||||
trace('Git Commit ID ${commitHashSplice}');
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{commitHashSplice};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case returning an
|
||||
// empty string is good enough; We don't want to call git on every hint.
|
||||
var commitHash:String = "";
|
||||
return macro $v{commitHashSplice};
|
||||
#end
|
||||
}
|
||||
}
|
||||
#end
|
Loading…
Add table
Add a link
Reference in a new issue