mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-09 16:24:42 +00:00
Various bug fixes for strumlines
This commit is contained in:
parent
1935373963
commit
60c6e5ee29
|
@ -2,16 +2,11 @@ package funkin;
|
|||
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.text.FlxText;
|
||||
import cpp.abi.Abi;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import flixel.FlxGame;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.ui.FlxUIState;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
class MusicBeatState extends FlxUIState
|
||||
{
|
||||
|
|
|
@ -5,10 +5,13 @@ import polymod.hscript.HScriptable;
|
|||
/**
|
||||
* 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.
|
||||
context: [FlxG, FlxSprite, Math, Paths, Std]
|
||||
})
|
||||
// @:autoBuild(funkin.util.macro.HookableMacro.build())
|
||||
@:autoBuild(funkin.util.macro.HookableMacro.build())
|
||||
interface IHook extends HScriptable {}
|
||||
|
|
|
@ -17,15 +17,24 @@ interface IScriptedClass
|
|||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve player input.
|
||||
* Defines a set of callbacks available to scripted classes which can follow the game between states.
|
||||
*/
|
||||
interface IInputScriptedClass extends IScriptedClass
|
||||
interface IStateChangingScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onKeyDown(event:KeyboardInputScriptEvent):Void;
|
||||
public function onKeyUp(event:KeyboardInputScriptEvent):Void;
|
||||
// TODO: OnMouseDown, OnMouseUp, OnMouseMove
|
||||
public function onStateChangeBegin(event:StateChangeScriptEvent):Void;
|
||||
public function onStateChangeEnd(event:StateChangeScriptEvent):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.
|
||||
*/
|
||||
|
|
|
@ -165,6 +165,18 @@ class ScriptEvent
|
|||
*/
|
||||
public static inline final SONG_LOADED:ScriptEventType = "SONG_LOADED";
|
||||
|
||||
/**
|
||||
* Called when the game is entering the current FlxState.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
public static inline final STATE_ENTER:ScriptEventType = "STATE_ENTER";
|
||||
|
||||
/**
|
||||
* 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_BEGIN should prevent the countdown from starting,
|
||||
|
@ -385,3 +397,19 @@ class SongLoadScriptEvent extends ScriptEvent
|
|||
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
|
||||
{
|
||||
public function new(type:ScriptEventType):Void
|
||||
{
|
||||
super(type, false);
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'StateChangeScriptEvent(type=' + type + ')';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.modding.events;
|
||||
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IInputScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
|
||||
/**
|
||||
|
@ -36,16 +35,14 @@ class ScriptEventDispatcher
|
|||
return;
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IInputScriptedClass))
|
||||
if (Std.isOfType(target, IStateChangingScriptedClass))
|
||||
{
|
||||
var t = cast(target, IInputScriptedClass);
|
||||
var t = cast(target, IStateChangingScriptedClass);
|
||||
var t = cast(target, IPlayStateScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case ScriptEvent.KEY_DOWN:
|
||||
t.onKeyDown(cast event);
|
||||
return;
|
||||
case ScriptEvent.KEY_UP:
|
||||
t.onKeyUp(cast event);
|
||||
case ScriptEvent.NOTE_HIT:
|
||||
t.onNoteHit(cast event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
|||
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IInputScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
|
||||
|
||||
/**
|
||||
* 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 IInputScriptedClass implements IPlayStateScriptedClass
|
||||
class Module implements IPlayStateScriptedClass implements IStateChangingScriptedClass
|
||||
{
|
||||
/**
|
||||
* Whether the module is currently active.
|
||||
|
@ -68,16 +68,20 @@ class Module implements IInputScriptedClass implements IPlayStateScriptedClass
|
|||
|
||||
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 onKeyDown(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
public function onKeyUp(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
public function onPause(event:ScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
@ -107,4 +111,8 @@ class Module implements IInputScriptedClass implements IPlayStateScriptedClass
|
|||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onSongLoaded(eent:SongLoadScriptEvent) {}
|
||||
|
||||
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
|
||||
|
||||
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
||||
}
|
||||
|
|
|
@ -96,10 +96,22 @@ class ModuleHandler
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ import funkin.util.Constants;
|
|||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.input.actions.FlxAction.FlxActionAnalog;
|
||||
import cpp.abi.Abi;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.play.Strumline.StrumlineStyle;
|
||||
import funkin.play.Strumline.StrumlineArrow;
|
||||
import flixel.addons.effects.FlxTrail;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxCamera;
|
||||
|
@ -24,12 +24,13 @@ import funkin.modding.events.ScriptEvent;
|
|||
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.IHook;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.Note;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.Strumline.StrumlineStyle;
|
||||
import funkin.Section.SwagSection;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.ui.PopUpStuff;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
|
@ -43,7 +44,7 @@ using StringTools;
|
|||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
||||
class PlayState extends MusicBeatState
|
||||
class PlayState extends MusicBeatState implements IHook
|
||||
{
|
||||
/**
|
||||
* STATIC VARIABLES
|
||||
|
@ -139,11 +140,6 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
private var inactiveNotes:Array<Note>;
|
||||
|
||||
/**
|
||||
* An object which the strumline (and its notes) are positioned relative to.
|
||||
*/
|
||||
private var strumlineAnchor:FlxObject;
|
||||
|
||||
/**
|
||||
* If true, the player is allowed to pause the game.
|
||||
* Disabled during the ending of a song.
|
||||
|
@ -231,7 +227,6 @@ class PlayState extends MusicBeatState
|
|||
private var vocals:VoicesGroup;
|
||||
private var vocalsFinished:Bool = false;
|
||||
|
||||
private var playerStrums:FlxTypedGroup<FlxSprite>;
|
||||
private var camZooming:Bool = false;
|
||||
private var gfSpeed:Int = 1;
|
||||
private var combo:Int = 0;
|
||||
|
@ -331,8 +326,6 @@ class PlayState extends MusicBeatState
|
|||
|
||||
add(grpNoteSplashes);
|
||||
|
||||
playerStrums = new FlxTypedGroup<FlxSprite>();
|
||||
|
||||
generateSong();
|
||||
|
||||
cameraFollowPoint = new FlxObject(0, 0, 1, 1);
|
||||
|
@ -407,6 +400,10 @@ class PlayState extends MusicBeatState
|
|||
case 'guns':
|
||||
VanillaCutscenes.playGunsCutscene();
|
||||
default:
|
||||
// VanillaCutscenes will call startCountdown later.
|
||||
// TODO: Alternatively: make a song script that allows startCountdown to be called,
|
||||
// then cancels the countdown, hides the strumline, plays the cutscene,
|
||||
// then calls Countdown.performCountdown()
|
||||
startCountdown();
|
||||
}
|
||||
}
|
||||
|
@ -415,8 +412,9 @@ class PlayState extends MusicBeatState
|
|||
startCountdown();
|
||||
}
|
||||
|
||||
// this.leftWatermarkText.text = '${currentSong.song.toUpperCase()} - ${SongLoad.curDiff.toUpperCase()}';
|
||||
#if debug
|
||||
this.rightWatermarkText.text = Constants.VERSION;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -936,6 +934,7 @@ class PlayState extends MusicBeatState
|
|||
super.update(elapsed);
|
||||
|
||||
updateHealthBar();
|
||||
updateScoreText();
|
||||
|
||||
if (needsReset)
|
||||
{
|
||||
|
@ -1173,7 +1172,7 @@ class PlayState extends MusicBeatState
|
|||
daNote.active = true;
|
||||
}
|
||||
|
||||
var strumLineMid = playerStrumline.offset.y + Note.swagWidth / 2;
|
||||
var strumLineMid = playerStrumline.y + Note.swagWidth / 2;
|
||||
|
||||
if (daNote.followsTime)
|
||||
daNote.y = (Conductor.songPosition - daNote.data.strumTime) * (0.45 * FlxMath.roundDecimal(SongLoad.getSpeed(),
|
||||
|
@ -1181,7 +1180,7 @@ class PlayState extends MusicBeatState
|
|||
|
||||
if (PreferencesMenu.getPref('downscroll'))
|
||||
{
|
||||
daNote.y += playerStrumline.offset.y;
|
||||
daNote.y += playerStrumline.y;
|
||||
if (daNote.isSustainNote)
|
||||
{
|
||||
if (daNote.animation.curAnim.name.endsWith("end") && daNote.prevNote != null)
|
||||
|
@ -1199,7 +1198,7 @@ class PlayState extends MusicBeatState
|
|||
else
|
||||
{
|
||||
if (daNote.followsTime)
|
||||
daNote.y = playerStrumline.offset.y - daNote.y;
|
||||
daNote.y = playerStrumline.y - daNote.y;
|
||||
if (daNote.isSustainNote
|
||||
&& (!daNote.mustPress || (daNote.wasGoodHit || (daNote.prevNote.wasGoodHit && !daNote.canBeHit)))
|
||||
&& daNote.y + daNote.offset.y * daNote.scale.y <= strumLineMid)
|
||||
|
@ -1284,7 +1283,7 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
|
||||
if (!isInCutscene)
|
||||
keyShit();
|
||||
keyShit(true);
|
||||
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
@ -1293,7 +1292,7 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
// clipRect is applied to graphic itself so use frame Heights
|
||||
var swagRect:FlxRect = new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight);
|
||||
var strumLineMid = playerStrumline.offset.y + Note.swagWidth / 2;
|
||||
var strumLineMid = playerStrumline.y + Note.swagWidth / 2;
|
||||
|
||||
if (PreferencesMenu.getPref('downscroll'))
|
||||
{
|
||||
|
@ -1549,7 +1548,14 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
private function keyShit():Void
|
||||
public var test:(PlayState) -> Void = function(instance:PlayState)
|
||||
{
|
||||
trace('test');
|
||||
trace(instance.currentStageId);
|
||||
};
|
||||
|
||||
@:hookable
|
||||
public function keyShit(test:Bool):Void
|
||||
{
|
||||
// control arrays, order L D R U
|
||||
var holdArray:Array<Bool> = [controls.NOTE_LEFT, controls.NOTE_DOWN, controls.NOTE_UP, controls.NOTE_RIGHT];
|
||||
|
@ -1566,27 +1572,27 @@ class PlayState extends MusicBeatState
|
|||
controls.NOTE_RIGHT_R
|
||||
];
|
||||
// HOLDS, check for sustain notes
|
||||
if (holdArray.contains(true) && generatedMusic)
|
||||
if (holdArray.contains(true) && PlayState.instance.generatedMusic)
|
||||
{
|
||||
activeNotes.forEachAlive(function(daNote:Note)
|
||||
PlayState.instance.activeNotes.forEachAlive(function(daNote:Note)
|
||||
{
|
||||
if (daNote.isSustainNote && daNote.canBeHit && daNote.mustPress && holdArray[daNote.data.noteData])
|
||||
goodNoteHit(daNote);
|
||||
PlayState.instance.goodNoteHit(daNote);
|
||||
});
|
||||
}
|
||||
|
||||
// PRESSES, check for note hits
|
||||
if (pressArray.contains(true) && generatedMusic)
|
||||
if (pressArray.contains(true) && PlayState.instance.generatedMusic)
|
||||
{
|
||||
Haptic.vibrate(100, 100);
|
||||
|
||||
currentStage.getBoyfriend().holdTimer = 0;
|
||||
PlayState.instance.currentStage.getBoyfriend().holdTimer = 0;
|
||||
|
||||
var possibleNotes:Array<Note> = []; // notes that can be hit
|
||||
var directionList:Array<Int> = []; // directions that can be hit
|
||||
var dumbNotes:Array<Note> = []; // notes to kill later
|
||||
|
||||
activeNotes.forEachAlive(function(daNote:Note)
|
||||
PlayState.instance.activeNotes.forEachAlive(function(daNote:Note)
|
||||
{
|
||||
if (daNote.canBeHit && daNote.mustPress && !daNote.tooLate && !daNote.wasGoodHit)
|
||||
{
|
||||
|
@ -1621,63 +1627,60 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
FlxG.log.add("killing dumb ass note at " + note.data.strumTime);
|
||||
note.kill();
|
||||
activeNotes.remove(note, true);
|
||||
PlayState.instance.activeNotes.remove(note, true);
|
||||
note.destroy();
|
||||
}
|
||||
|
||||
possibleNotes.sort((a, b) -> Std.int(a.data.strumTime - b.data.strumTime));
|
||||
|
||||
if (perfectMode)
|
||||
goodNoteHit(possibleNotes[0]);
|
||||
if (PlayState.instance.perfectMode)
|
||||
PlayState.instance.goodNoteHit(possibleNotes[0]);
|
||||
else if (possibleNotes.length > 0)
|
||||
{
|
||||
for (shit in 0...pressArray.length)
|
||||
{ // if a direction is hit that shouldn't be
|
||||
if (pressArray[shit] && !directionList.contains(shit))
|
||||
noteMiss(shit);
|
||||
PlayState.instance.noteMiss(shit);
|
||||
}
|
||||
for (coolNote in possibleNotes)
|
||||
{
|
||||
if (pressArray[coolNote.data.noteData])
|
||||
goodNoteHit(coolNote);
|
||||
PlayState.instance.goodNoteHit(coolNote);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (shit in 0...pressArray.length)
|
||||
if (pressArray[shit])
|
||||
noteMiss(shit);
|
||||
PlayState.instance.noteMiss(shit);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStage == null)
|
||||
if (PlayState.instance.currentStage == null)
|
||||
return;
|
||||
if (currentStage.getBoyfriend().holdTimer > Conductor.stepCrochet * 4 * 0.001 && !holdArray.contains(true))
|
||||
if (PlayState.instance.currentStage.getBoyfriend().holdTimer > Conductor.stepCrochet * 4 * 0.001 && !holdArray.contains(true))
|
||||
{
|
||||
if (currentStage.getBoyfriend().animation != null
|
||||
&& currentStage.getBoyfriend().animation.curAnim.name.startsWith('sing')
|
||||
&& !currentStage.getBoyfriend().animation.curAnim.name.endsWith('miss'))
|
||||
if (PlayState.instance.currentStage.getBoyfriend().animation != null
|
||||
&& PlayState.instance.currentStage.getBoyfriend().animation.curAnim.name.startsWith('sing')
|
||||
&& !PlayState.instance.currentStage.getBoyfriend().animation.curAnim.name.endsWith('miss'))
|
||||
{
|
||||
currentStage.getBoyfriend().playAnim('idle');
|
||||
PlayState.instance.currentStage.getBoyfriend().playAnim('idle');
|
||||
}
|
||||
}
|
||||
|
||||
playerStrums.forEach(function(spr:FlxSprite)
|
||||
for (keyId => isPressed in pressArray)
|
||||
{
|
||||
if (pressArray[spr.ID] && spr.animation.curAnim.name != 'confirm')
|
||||
spr.animation.play('pressed');
|
||||
if (!holdArray[spr.ID])
|
||||
spr.animation.play('static');
|
||||
var arrow:StrumlineArrow = PlayState.instance.playerStrumline.getArrow(keyId);
|
||||
|
||||
if (spr.animation.curAnim.name == 'confirm' && !currentStageId.startsWith('school'))
|
||||
if (isPressed && arrow.animation.curAnim.name != 'confirm')
|
||||
{
|
||||
spr.centerOffsets();
|
||||
spr.offset.x -= 13;
|
||||
spr.offset.y -= 13;
|
||||
arrow.playAnimation('pressed');
|
||||
}
|
||||
else
|
||||
spr.centerOffsets();
|
||||
});
|
||||
if (!holdArray[keyId])
|
||||
{
|
||||
arrow.playAnimation('static');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function noteMiss(direction:NoteDir = 1):Void
|
||||
|
@ -1707,13 +1710,7 @@ class PlayState extends MusicBeatState
|
|||
|
||||
currentStage.getBoyfriend().playAnim('sing' + note.dirNameUpper, true);
|
||||
|
||||
playerStrums.forEach(function(spr:FlxSprite)
|
||||
{
|
||||
if (Math.abs(note.data.noteData) == spr.ID)
|
||||
{
|
||||
spr.animation.play('confirm', true);
|
||||
}
|
||||
});
|
||||
playerStrumline.getArrow(note.data.noteData).playAnimation('confirm', true);
|
||||
|
||||
note.wasGoodHit = true;
|
||||
vocals.volume = 1;
|
||||
|
@ -1848,19 +1845,31 @@ class PlayState extends MusicBeatState
|
|||
var strumlineYPos = Strumline.getYPos();
|
||||
|
||||
playerStrumline = new Strumline(0, strumlineStyle, 4);
|
||||
playerStrumline.offset = new FlxPoint(50 + FlxG.width / 2, strumlineYPos);
|
||||
playerStrumline.x = 50 + FlxG.width / 2;
|
||||
playerStrumline.y = strumlineYPos;
|
||||
// Set the z-index so they don't appear in front of notes.
|
||||
playerStrumline.zIndex = 100;
|
||||
add(playerStrumline);
|
||||
playerStrumline.cameras = [camHUD];
|
||||
|
||||
if (!isStoryMode)
|
||||
{
|
||||
playerStrumline.fadeInArrows();
|
||||
}
|
||||
|
||||
enemyStrumline = new Strumline(1, strumlineStyle, 4);
|
||||
enemyStrumline.offset = new FlxPoint(50, strumlineYPos);
|
||||
enemyStrumline.x = 50;
|
||||
enemyStrumline.y = strumlineYPos;
|
||||
// Set the z-index so they don't appear in front of notes.
|
||||
enemyStrumline.zIndex = 100;
|
||||
add(enemyStrumline);
|
||||
enemyStrumline.cameras = [camHUD];
|
||||
|
||||
if (!isStoryMode)
|
||||
{
|
||||
enemyStrumline.fadeInArrows();
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
@ -1997,6 +2006,7 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
remove(currentStage);
|
||||
currentStage.kill();
|
||||
dispatchEvent(new ScriptEvent(ScriptEvent.DESTROY, false));
|
||||
currentStage = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.ui.PreferencesMenu;
|
||||
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 flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
|
||||
/**
|
||||
* 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 FlxTypedGroup<FlxSprite>
|
||||
class Strumline extends FlxTypedSpriteGroup<StrumlineArrow>
|
||||
{
|
||||
public var offset(default, set):FlxPoint = new FlxPoint(0, 0);
|
||||
|
||||
function set_offset(value:FlxPoint):FlxPoint
|
||||
{
|
||||
this.offset = value;
|
||||
updatePositions();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the strumline.
|
||||
* Options are normal and pixel.
|
||||
|
@ -62,132 +55,30 @@ class Strumline extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
function createStrumlineArrow(index:Int):Void
|
||||
{
|
||||
var arrow:FlxSprite = new FlxSprite(0, 0);
|
||||
|
||||
arrow.ID = index;
|
||||
|
||||
// Color changing for arrows is a WIP.
|
||||
/*
|
||||
var colorSwapShader:ColorSwap = new ColorSwap();
|
||||
colorSwapShader.update(Note.arrowColors[i]);
|
||||
arrow.shader = colorSwapShader;
|
||||
*/
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case NORMAL:
|
||||
createNormalNote(arrow);
|
||||
case PIXEL:
|
||||
createPixelNote(arrow);
|
||||
}
|
||||
|
||||
arrow.updateHitbox();
|
||||
arrow.scrollFactor.set();
|
||||
|
||||
arrow.animation.play('static');
|
||||
|
||||
applyFadeIn(arrow);
|
||||
|
||||
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 I guess?
|
||||
* 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 applyFadeIn(arrow:FlxSprite):Void
|
||||
function fadeInArrow(arrow:FlxSprite):Void
|
||||
{
|
||||
if (!PlayState.isStoryMode)
|
||||
{
|
||||
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)});
|
||||
}
|
||||
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)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the default note style to an arrow.
|
||||
* @param arrow The arrow to apply the style to.
|
||||
* @param index The index of the arrow in the strumline.
|
||||
*/
|
||||
function createNormalNote(arrow:FlxSprite):Void
|
||||
public function fadeInArrows():Void
|
||||
{
|
||||
arrow.frames = Paths.getSparrowAtlas('NOTE_assets');
|
||||
|
||||
arrow.animation.addByPrefix('green', 'arrowUP');
|
||||
arrow.animation.addByPrefix('blue', 'arrowDOWN');
|
||||
arrow.animation.addByPrefix('purple', 'arrowLEFT');
|
||||
arrow.animation.addByPrefix('red', 'arrowRIGHT');
|
||||
|
||||
arrow.setGraphicSize(Std.int(arrow.width * 0.7));
|
||||
arrow.antialiasing = true;
|
||||
|
||||
arrow.x += Note.swagWidth * arrow.ID;
|
||||
|
||||
switch (Math.abs(arrow.ID))
|
||||
for (arrow in this.members)
|
||||
{
|
||||
case 0:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 1');
|
||||
arrow.animation.addByPrefix('pressed', 'left press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'left confirm', 24, false);
|
||||
case 1:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 2');
|
||||
arrow.animation.addByPrefix('pressed', 'down press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'down confirm', 24, false);
|
||||
case 2:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 4');
|
||||
arrow.animation.addByPrefix('pressed', 'up press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'up confirm', 24, false);
|
||||
case 3:
|
||||
arrow.animation.addByPrefix('static', 'arrow static instance 3');
|
||||
arrow.animation.addByPrefix('pressed', 'right press', 24, false);
|
||||
arrow.animation.addByPrefix('confirm', 'right confirm', 24, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the pixel note style to an arrow.
|
||||
* @param arrow The arrow to apply the style to.
|
||||
* @param index The index of the arrow in the strumline.
|
||||
*/
|
||||
function createPixelNote(arrow:FlxSprite):Void
|
||||
{
|
||||
arrow.loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17);
|
||||
|
||||
arrow.animation.add('purplel', [4]);
|
||||
arrow.animation.add('blue', [5]);
|
||||
arrow.animation.add('green', [6]);
|
||||
arrow.animation.add('red', [7]);
|
||||
|
||||
arrow.setGraphicSize(Std.int(arrow.width * Constants.PIXEL_ART_SCALE));
|
||||
arrow.updateHitbox();
|
||||
|
||||
// Forcibly disable anti-aliasing on pixel graphics to stop blur.
|
||||
arrow.antialiasing = false;
|
||||
|
||||
arrow.x += Note.swagWidth * arrow.ID;
|
||||
|
||||
// TODO: Seems weird that these are hardcoded like this... no XML?
|
||||
switch (Math.abs(arrow.ID))
|
||||
{
|
||||
case 0:
|
||||
arrow.animation.add('static', [0]);
|
||||
arrow.animation.add('pressed', [4, 8], 12, false);
|
||||
arrow.animation.add('confirm', [12, 16], 24, false);
|
||||
case 1:
|
||||
arrow.animation.add('static', [1]);
|
||||
arrow.animation.add('pressed', [5, 9], 12, false);
|
||||
arrow.animation.add('confirm', [13, 17], 24, false);
|
||||
case 2:
|
||||
arrow.animation.add('static', [2]);
|
||||
arrow.animation.add('pressed', [6, 10], 12, false);
|
||||
arrow.animation.add('confirm', [14, 18], 12, false);
|
||||
case 3:
|
||||
arrow.animation.add('static', [3]);
|
||||
arrow.animation.add('pressed', [7, 11], 12, false);
|
||||
arrow.animation.add('confirm', [15, 19], 24, false);
|
||||
fadeInArrow(arrow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,33 +99,150 @@ class Strumline extends FlxTypedGroup<FlxSprite>
|
|||
* @param index The index to retrieve.
|
||||
* @return The corresponding FlxSprite.
|
||||
*/
|
||||
public inline function getArrow(value:Int):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):FlxSprite
|
||||
public inline function getArrowByNoteType(value:NoteType):StrumlineArrow
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteDir(value:NoteDir):FlxSprite
|
||||
public inline function getArrowByNoteDir(value:NoteDir):StrumlineArrow
|
||||
{
|
||||
return getArrow(value.int);
|
||||
}
|
||||
|
||||
public inline function getArrowByNoteColor(value:NoteColor):FlxSprite
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -61,7 +61,8 @@ class VanillaCutscenes
|
|||
blackScreen = null;
|
||||
|
||||
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
||||
Countdown.performCountdown(false);
|
||||
@:privateAccess
|
||||
PlayState.instance.startCountdown();
|
||||
@:privateAccess
|
||||
PlayState.instance.cameraMovement();
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import funkin.util.SortUtil;
|
|||
*
|
||||
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
||||
*/
|
||||
class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScriptedClass implements IInputScriptedClass
|
||||
class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScriptedClass
|
||||
{
|
||||
public final stageId:String;
|
||||
public final stageName:String;
|
||||
|
@ -312,7 +312,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 function onDestroy(event:ScriptEvent):Void
|
||||
{
|
||||
|
@ -322,24 +323,32 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
|
||||
for (prop in this.namedProps)
|
||||
{
|
||||
remove(prop);
|
||||
prop.kill();
|
||||
prop.destroy();
|
||||
}
|
||||
namedProps.clear();
|
||||
|
||||
for (char in this.characters)
|
||||
{
|
||||
remove(char);
|
||||
char.kill();
|
||||
char.destroy();
|
||||
}
|
||||
characters.clear();
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
remove(bopper);
|
||||
bopper.kill();
|
||||
bopper.destroy();
|
||||
}
|
||||
boppers = [];
|
||||
|
||||
for (sprite in this.group)
|
||||
{
|
||||
remove(sprite);
|
||||
sprite.kill();
|
||||
sprite.destroy();
|
||||
}
|
||||
group.clear();
|
||||
|
@ -391,10 +400,6 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
|
||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onKeyDown(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
public function onKeyUp(event:KeyboardInputScriptEvent) {}
|
||||
|
||||
/**
|
||||
* A function that should get called every frame.
|
||||
*/
|
||||
|
|
|
@ -18,8 +18,17 @@ class Constants
|
|||
public static final VERSION_SUFFIX = ' PROTOTYPE';
|
||||
public static var VERSION(get, null):String;
|
||||
|
||||
#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
|
||||
}
|
||||
|
|
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
|
|
@ -1,3 +1,70 @@
|
|||
package funkin.util.macro;
|
||||
|
||||
class HookableMacro {}
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using Lambda;
|
||||
|
||||
class HookableMacro
|
||||
{
|
||||
/**
|
||||
* The @:hookable annotation replaces a given function with a variable that contains a function.
|
||||
* It's still callable, like normal, but now you can also replace the value! Neat!
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
public static macro function build():Array<Field>
|
||||
{
|
||||
Context.info('Running HookableMacro...', Context.currentPos());
|
||||
|
||||
var cls:haxe.macro.Type.ClassType = Context.getLocalClass().get();
|
||||
var fields:Array<Field> = Context.getBuildFields();
|
||||
// Find all fields with @:hookable metadata
|
||||
for (field in fields)
|
||||
{
|
||||
if (field.meta == null)
|
||||
continue;
|
||||
var scriptable_meta = field.meta.find(function(m) return m.name == ':hookable');
|
||||
if (scriptable_meta != null)
|
||||
{
|
||||
Context.info(' @:hookable annotation found on field ${field.name}', Context.currentPos());
|
||||
switch (field.kind)
|
||||
{
|
||||
case FFun(originalFunc):
|
||||
// This is the type of the function, like (Int, Int) -> Int
|
||||
var replFieldTypeRet:ComplexType = originalFunc.ret == null ? Context.toComplexType(Context.getType('Void')) : originalFunc.ret;
|
||||
var replFieldType:ComplexType = TFunction([for (arg in originalFunc.args) arg.type], replFieldTypeRet);
|
||||
// This is the expression of the function, i.e. the function body.
|
||||
|
||||
var replFieldExpr:ExprDef = EFunction(FAnonymous, {
|
||||
ret: originalFunc.ret,
|
||||
params: originalFunc.params,
|
||||
args: originalFunc.args,
|
||||
expr: originalFunc.expr
|
||||
});
|
||||
|
||||
var replField:Field = {
|
||||
name: field.name,
|
||||
doc: field.doc,
|
||||
access: field.access,
|
||||
pos: field.pos,
|
||||
meta: field.meta,
|
||||
kind: FVar(replFieldType, {
|
||||
expr: replFieldExpr,
|
||||
pos: field.pos
|
||||
}),
|
||||
};
|
||||
|
||||
// Replace the original field with the new field
|
||||
fields[fields.indexOf(field)] = replField;
|
||||
default:
|
||||
Context.error('@:hookable can only be used on functions', field.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue