1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-11-26 06:09:02 +00:00

Merge branch 'bugfix/various-develop-fixes' into rewrite/master

This commit is contained in:
Cameron Taylor 2024-08-28 16:32:50 -04:00
commit 1fbca9ab6a
20 changed files with 605 additions and 116 deletions

2
assets

@ -1 +1 @@
Subproject commit e19aaca19254ec2717a4f942005f32b42b6ecb18 Subproject commit 7210562035c7ab6d2122606ec607b3e897a5ef20

View file

@ -38,6 +38,17 @@ class PlayerData
@:optional @:optional
public var freeplayDJ:Null<PlayerFreeplayDJData> = null; public var freeplayDJ:Null<PlayerFreeplayDJData> = null;
/**
* Data for displaying this character in the Character Select menu.
* If null, exclude from Character Select.
*/
@:optional
public var charSelect:Null<PlayerCharSelectData> = null;
/**
* Data for displaying this character in the results screen.
*/
@:optional
public var results:Null<PlayerResultsData> = null; public var results:Null<PlayerResultsData> = null;
/** /**
@ -97,6 +108,9 @@ class PlayerFreeplayDJData
@:optional @:optional
var cartoon:Null<PlayerFreeplayDJCartoonData>; var cartoon:Null<PlayerFreeplayDJCartoonData>;
@:optional
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
public function new() public function new()
{ {
animationMap = new Map(); animationMap = new Map();
@ -183,6 +197,58 @@ class PlayerFreeplayDJData
{ {
return cartoon?.channelChangeFrame ?? 60; return cartoon?.channelChangeFrame ?? 60;
} }
public function getFistPumpIntroStartFrame():Int
{
return fistPump?.introStartFrame ?? 0;
}
public function getFistPumpIntroEndFrame():Int
{
return fistPump?.introEndFrame ?? 0;
}
public function getFistPumpLoopStartFrame():Int
{
return fistPump?.loopStartFrame ?? 0;
}
public function getFistPumpLoopEndFrame():Int
{
return fistPump?.loopEndFrame ?? 0;
}
public function getFistPumpIntroBadStartFrame():Int
{
return fistPump?.introBadStartFrame ?? 0;
}
public function getFistPumpIntroBadEndFrame():Int
{
return fistPump?.introBadEndFrame ?? 0;
}
public function getFistPumpLoopBadStartFrame():Int
{
return fistPump?.loopBadStartFrame ?? 0;
}
public function getFistPumpLoopBadEndFrame():Int
{
return fistPump?.loopBadEndFrame ?? 0;
}
}
class PlayerCharSelectData
{
/**
* A zero-indexed number for the character's preferred position in the grid.
* 0 = top left, 4 = center, 8 = bottom right
* In the event of a conflict, the first character alphabetically gets it,
* and others get shifted over.
*/
@:optional
public var position:Null<Int>;
} }
typedef PlayerResultsData = typedef PlayerResultsData =
@ -242,3 +308,30 @@ typedef PlayerFreeplayDJCartoonData =
var loopFrame:Int; var loopFrame:Int;
var channelChangeFrame:Int; var channelChangeFrame:Int;
} }
typedef PlayerFreeplayDJFistPumpData =
{
@:default(0)
var introStartFrame:Int;
@:default(4)
var introEndFrame:Int;
@:default(4)
var loopStartFrame:Int;
@:default(-1)
var loopEndFrame:Int;
@:default(0)
var introBadStartFrame:Int;
@:default(4)
var introBadEndFrame:Int;
@:default(4)
var loopBadStartFrame:Int;
@:default(-1)
var loopBadEndFrame:Int;
};

View file

@ -3,6 +3,7 @@ package funkin.data.freeplay.player;
import funkin.data.freeplay.player.PlayerData; import funkin.data.freeplay.player.PlayerData;
import funkin.ui.freeplay.charselect.PlayableCharacter; import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter; import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
import funkin.save.Save;
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
{ {
@ -53,6 +54,41 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.'); log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.');
} }
public function countUnlockedCharacters():Int
{
var count = 0;
for (charId in listEntryIds())
{
var player = fetchEntry(charId);
if (player == null) continue;
if (player.isUnlocked()) count++;
}
return count;
}
public function hasNewCharacter():Bool
{
var characters = Save.instance.charactersSeen.clone();
for (charId in listEntryIds())
{
var player = fetchEntry(charId);
if (player == null) continue;
if (!player.isUnlocked()) continue;
if (characters.contains(charId)) continue;
// This character is unlocked but we haven't seen them in Freeplay yet.
return true;
}
// Fallthrough case.
return false;
}
/** /**
* Get the playable character associated with a given stage character. * Get the playable character associated with a given stage character.
* @param characterId The stage character ID. * @param characterId The stage character ID.

View file

@ -1979,7 +1979,7 @@ class PlayState extends MusicBeatSubState
if (vocals == null) return; if (vocals == null) return;
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.) // Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
if (!FlxG.sound.music.playing) return; if (!(FlxG.sound.music?.playing ?? false)) return;
var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset; var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset;
FlxG.sound.music.pause(); FlxG.sound.music.pause();
vocals.pause(); vocals.pause();
@ -2221,10 +2221,14 @@ class PlayState extends MusicBeatSubState
// Calling event.cancelEvent() skips all the other logic! Neat! // Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) continue; if (event.eventCanceled) continue;
// Skip handling the miss in botplay!
if (!isBotPlayMode)
{
// Judge the miss. // Judge the miss.
// NOTE: This is what handles the scoring. // NOTE: This is what handles the scoring.
trace('Missed note! ${note.noteData}'); trace('Missed note! ${note.noteData}');
onNoteMiss(note, event.playSound, event.healthChange); onNoteMiss(note, event.playSound, event.healthChange);
}
note.handledMiss = true; note.handledMiss = true;
} }
@ -2321,9 +2325,16 @@ class PlayState extends MusicBeatSubState
playerStrumline.pressKey(input.noteDirection); playerStrumline.pressKey(input.noteDirection);
// Don't credit or penalize inputs in Bot Play.
if (isBotPlayMode) continue;
var notesInDirection:Array<NoteSprite> = notesByDirection[input.noteDirection]; var notesInDirection:Array<NoteSprite> = notesByDirection[input.noteDirection];
if (!Constants.GHOST_TAPPING && notesInDirection.length == 0) #if FEATURE_GHOST_TAPPING
if ((!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
#else
if (notesInDirection.length == 0)
#end
{ {
// Pressed a wrong key with no notes nearby. // Pressed a wrong key with no notes nearby.
// Perform a ghost miss (anti-spam). // Perform a ghost miss (anti-spam).
@ -2333,16 +2344,6 @@ class PlayState extends MusicBeatSubState
playerStrumline.playPress(input.noteDirection); playerStrumline.playPress(input.noteDirection);
trace('PENALTY Score: ${songScore}'); trace('PENALTY Score: ${songScore}');
} }
else if (Constants.GHOST_TAPPING && (!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
{
// Pressed a wrong key with notes visible on-screen.
// Perform a ghost miss (anti-spam).
ghostNoteMiss(input.noteDirection, notesInRange.length > 0);
// Play the strumline animation.
playerStrumline.playPress(input.noteDirection);
trace('PENALTY Score: ${songScore}');
}
else if (notesInDirection.length == 0) else if (notesInDirection.length == 0)
{ {
// Press a key with no penalty. // Press a key with no penalty.

View file

@ -305,6 +305,8 @@ class CharacterDataParser
icon = "darnell"; icon = "darnell";
case "senpai-angry": case "senpai-angry":
icon = "senpai"; icon = "senpai";
case "spooky-dark":
icon = "spooky";
case "tankman-atlas": case "tankman-atlas":
icon = "tankman"; icon = "tankman";
} }

View file

@ -114,7 +114,7 @@ class ZoomCameraSongEvent extends SongEvent
name: 'zoom', name: 'zoom',
title: 'Zoom Level', title: 'Zoom Level',
defaultValue: 1.0, defaultValue: 1.0,
step: 0.1, step: 0.05,
type: SongEventFieldType.FLOAT, type: SongEventFieldType.FLOAT,
units: 'x' units: 'x'
}, },

View file

@ -94,6 +94,10 @@ class Strumline extends FlxSpriteGroup
final noteStyle:NoteStyle; final noteStyle:NoteStyle;
#if FEATURE_GHOST_TAPPING
var ghostTapTimer:Float = 0.0;
#end
/** /**
* The note data for the song. Should NOT be altered after the song starts, * The note data for the song. Should NOT be altered after the song starts,
* so we can easily rewind. * so we can easily rewind.
@ -179,21 +183,36 @@ class Strumline extends FlxSpriteGroup
super.update(elapsed); super.update(elapsed);
updateNotes(); updateNotes();
#if FEATURE_GHOST_TAPPING
updateGhostTapTimer(elapsed);
#end
} }
#if FEATURE_GHOST_TAPPING
/** /**
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty. * Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
*/ */
public function mayGhostTap():Bool public function mayGhostTap():Bool
{ {
// TODO: Refine this. Only querying "can be hit" is too tight but "is being rendered" is too loose. // Any notes in range of the strumline.
// Also, if you just hit a note, there should be a (short) period where this is off so you can't spam. if (getNotesMayHit().length > 0)
{
// If there are any notes on screen, we can't ghost tap. return false;
return notes.members.filter(function(note:NoteSprite) {
return note != null && note.alive && !note.hasBeenHit;
}).length == 0;
} }
// Any hold notes in range of the strumline.
if (getHoldNotesHitOrMissed().length > 0)
{
return false;
}
// Note has been hit recently.
if (ghostTapTimer > 0.0) return false;
// **yippee**
return true;
}
#end
/** /**
* Return notes that are within `Constants.HIT_WINDOW` ms of the strumline. * Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
@ -492,6 +511,32 @@ class Strumline extends FlxSpriteGroup
} }
} }
/**
* Return notes that are within, or way after, `Constants.HIT_WINDOW` ms of the strumline.
* @return An array of `NoteSprite` objects.
*/
public function getNotesOnScreen():Array<NoteSprite>
{
return notes.members.filter(function(note:NoteSprite) {
return note != null && note.alive && !note.hasBeenHit;
});
}
#if FEATURE_GHOST_TAPPING
function updateGhostTapTimer(elapsed:Float):Void
{
// If it's still our turn, don't update the ghost tap timer.
if (getNotesOnScreen().length > 0) return;
ghostTapTimer -= elapsed;
if (ghostTapTimer <= 0)
{
ghostTapTimer = 0;
}
}
#end
/** /**
* Called when the PlayState skips a large amount of time forward or backward. * Called when the PlayState skips a large amount of time forward or backward.
*/ */
@ -563,6 +608,10 @@ class Strumline extends FlxSpriteGroup
playStatic(dir); playStatic(dir);
} }
resetScrollSpeed(); resetScrollSpeed();
#if FEATURE_GHOST_TAPPING
ghostTapTimer = 0;
#end
} }
public function applyNoteData(data:Array<SongNoteData>):Void public function applyNoteData(data:Array<SongNoteData>):Void
@ -602,6 +651,10 @@ class Strumline extends FlxSpriteGroup
note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition; note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition;
} }
#if FEATURE_GHOST_TAPPING
ghostTapTimer = Constants.GHOST_TAP_DELAY;
#end
} }
public function killNote(note:NoteSprite):Void public function killNote(note:NoteSprite):Void

View file

@ -315,7 +315,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
public function isAnimationFinished():Bool public function isAnimationFinished():Bool
{ {
return this.animation.finished; return this.animation?.finished ?? false;
} }
public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void

View file

@ -436,8 +436,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
// Start with the per-stage character position. // Start with the per-stage character position.
// Subtracting the origin ensures characters are positioned relative to their feet. // Subtracting the origin ensures characters are positioned relative to their feet.
// Subtracting the global offset allows positioning on a per-character basis. // Subtracting the global offset allows positioning on a per-character basis.
character.x = stageCharData.position[0] - character.characterOrigin.x + character.globalOffsets[0]; // We previously applied the global offset here but that is now done elsewhere.
character.y = stageCharData.position[1] - character.characterOrigin.y + character.globalOffsets[1]; character.x = stageCharData.position[0] - character.characterOrigin.x;
character.y = stageCharData.position[1] - character.characterOrigin.y;
@:privateAccess(funkin.play.stage.Bopper) @:privateAccess(funkin.play.stage.Bopper)
{ {

View file

@ -121,6 +121,12 @@ class Save
modOptions: [], modOptions: [],
}, },
unlocks:
{
// Default to having seen the default character.
charactersSeen: ["bf"],
},
optionsChartEditor: optionsChartEditor:
{ {
// Reasonable defaults. // Reasonable defaults.
@ -393,6 +399,22 @@ class Save
return data.optionsChartEditor.playbackSpeed; return data.optionsChartEditor.playbackSpeed;
} }
public var charactersSeen(get, never):Array<String>;
function get_charactersSeen():Array<String>
{
return data.unlocks.charactersSeen;
}
/**
* When we've seen a character unlock, add it to the list of characters seen.
* @param character
*/
public function addCharacterSeen(character:String):Void
{
data.unlocks.charactersSeen.push(character);
}
/** /**
* Return the score the user achieved for a given level on a given difficulty. * Return the score the user achieved for a given level on a given difficulty.
* *
@ -471,11 +493,19 @@ class Save
for (difficulty in difficultyList) for (difficulty in difficultyList)
{ {
var score:Null<SaveScoreData> = getLevelScore(levelId, difficulty); var score:Null<SaveScoreData> = getLevelScore(levelId, difficulty);
// TODO: Do we need to check accuracy/score here?
if (score != null) if (score != null)
{ {
if (score.score > 0)
{
// Level has score data, which means we cleared it!
return true; return true;
} }
else
{
// Level has score data, but the score is 0.
return false;
}
}
} }
return false; return false;
} }
@ -630,11 +660,19 @@ class Save
for (difficulty in difficultyList) for (difficulty in difficultyList)
{ {
var score:Null<SaveScoreData> = getSongScore(songId, difficulty); var score:Null<SaveScoreData> = getSongScore(songId, difficulty);
// TODO: Do we need to check accuracy/score here?
if (score != null) if (score != null)
{ {
if (score.score > 0)
{
// Level has score data, which means we cleared it!
return true; return true;
} }
else
{
// Level has score data, but the score is 0.
return false;
}
}
} }
return false; return false;
} }
@ -956,6 +994,8 @@ typedef RawSaveData =
*/ */
var options:SaveDataOptions; var options:SaveDataOptions;
var unlocks:SaveDataUnlocks;
/** /**
* The user's favorited songs in the Freeplay menu, * The user's favorited songs in the Freeplay menu,
* as a list of song IDs. * as a list of song IDs.
@ -980,6 +1020,15 @@ typedef SaveApiNewgroundsData =
var sessionId:Null<String>; var sessionId:Null<String>;
} }
typedef SaveDataUnlocks =
{
/**
* Every time we see the unlock animation for a character,
* add it to this list so that we don't show it again.
*/
var charactersSeen:Array<String>;
}
/** /**
* An anoymous structure containing options about the user's high scores. * An anoymous structure containing options about the user's high scores.
*/ */

View file

@ -22,14 +22,26 @@ class PixelatedIcon extends FlxSprite
switch (char) switch (char)
{ {
case 'monster-christmas': case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
charPath += 'monsterpixel'; charPath += "bfpixel";
case 'mom-car': case "monster-christmas":
charPath += 'mommypixel'; charPath += "monsterpixel";
case 'darnell-blazin': case "mom" | "mom-car":
charPath += 'darnellpixel'; charPath += "mommypixel";
case 'senpai-angry': case "pico-blazin" | "pico-playable" | "pico-speaker":
charPath += 'senpaipixel'; charPath += "picopixel";
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
charPath += "gfpixel";
case "dad":
charPath += "dadpixel";
case "darnell-blazin":
charPath += "darnellpixel";
case "senpai-angry":
charPath += "senpaipixel";
case "spooky-dark":
charPath += "spookypixel";
case "tankman-atlas":
charPath += "tankmanpixel";
default: default:
charPath += '${char}pixel'; charPath += '${char}pixel';
} }

View file

@ -1,27 +1,31 @@
package funkin.ui.charSelect; package funkin.ui.charSelect;
import funkin.ui.freeplay.FreeplayState; import flixel.FlxObject;
import flixel.text.FlxText;
import funkin.ui.PixelatedIcon;
import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.math.FlxPoint;
import flixel.tweens.FlxTween;
import openfl.display.BlendMode;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup; import flixel.group.FlxSpriteGroup;
import funkin.play.stage.Stage; import flixel.math.FlxPoint;
import flixel.sound.FlxSound;
import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxTimer;
import funkin.audio.FunkinSound;
import funkin.data.freeplay.player.PlayerData;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinCamera;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.play.stage.Stage;
import flixel.FlxObject; import funkin.ui.freeplay.charselect.PlayableCharacter;
import openfl.display.BlendMode; import funkin.ui.freeplay.FreeplayState;
import flixel.group.FlxGroup; import funkin.ui.PixelatedIcon;
import funkin.util.MathUtil; import funkin.util.MathUtil;
import flixel.util.FlxTimer; import funkin.vis.dsp.SpectralAnalyzer;
import flixel.tweens.FlxEase; import openfl.display.BlendMode;
import flixel.sound.FlxSound;
import funkin.audio.FunkinSound;
class CharSelectSubState extends MusicBeatSubState class CharSelectSubState extends MusicBeatSubState
{ {
@ -67,8 +71,29 @@ class CharSelectSubState extends MusicBeatSubState
{ {
super(); super();
availableChars.set(4, "bf"); loadAvailableCharacters();
availableChars.set(3, "pico"); }
function loadAvailableCharacters():Void
{
var playerIds:Array<String> = PlayerRegistry.instance.listEntryIds();
for (playerId in playerIds)
{
var player:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerId);
if (player == null) continue;
var playerData = player.getCharSelectData();
if (playerData == null) continue;
var targetPosition:Int = playerData.position ?? 0;
while (availableChars.exists(targetPosition))
{
targetPosition += 1;
}
trace('Placing player ${playerId} at position ${targetPosition}');
availableChars.set(targetPosition, playerId);
}
} }
override public function create():Void override public function create():Void
@ -269,7 +294,6 @@ class CharSelectSubState extends MusicBeatSubState
} }
var grpIcons:FlxSpriteGroup; var grpIcons:FlxSpriteGroup;
var grpXSpread(default, set):Float = 107; var grpXSpread(default, set):Float = 107;
var grpYSpread(default, set):Float = 127; var grpYSpread(default, set):Float = 127;

View file

@ -190,8 +190,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
var numberStepper:NumberStepper = new NumberStepper(); var numberStepper:NumberStepper = new NumberStepper();
numberStepper.id = field.name; numberStepper.id = field.name;
numberStepper.step = field.step ?? 1.0; numberStepper.step = field.step ?? 1.0;
numberStepper.min = field.min ?? 0.0; if (field.min != null) numberStepper.min = field.min;
numberStepper.max = field.max ?? 10.0; if (field.min != null) numberStepper.max = field.max;
if (field.defaultValue != null) numberStepper.value = field.defaultValue; if (field.defaultValue != null) numberStepper.value = field.defaultValue;
input = numberStepper; input = numberStepper;
case FLOAT: case FLOAT:

View file

@ -15,7 +15,7 @@ class FreeplayDJ extends FlxAtlasSprite
{ {
// Represents the sprite's current status. // Represents the sprite's current status.
// Without state machines I would have driven myself crazy years ago. // Without state machines I would have driven myself crazy years ago.
public var currentState:DJBoyfriendState = Intro; public var currentState:FreeplayDJState = Intro;
// A callback activated when the intro animation finishes. // A callback activated when the intro animation finishes.
public var onIntroDone:FlxSignal = new FlxSignal(); public var onIntroDone:FlxSignal = new FlxSignal();
@ -99,7 +99,7 @@ class FreeplayDJ extends FlxAtlasSprite
playFlashAnimation(animPrefix, true, false, true); playFlashAnimation(animPrefix, true, false, true);
} }
if (getCurrentAnimation() == animPrefix && this.isLoopFinished()) if (getCurrentAnimation() == animPrefix && this.isLoopComplete())
{ {
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg) if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
{ {
@ -111,18 +111,69 @@ class FreeplayDJ extends FlxAtlasSprite
} }
} }
timeIdling += elapsed; timeIdling += elapsed;
case NewUnlock:
var animPrefix = playableCharData.getAnimationPrefix('newUnlock');
if (!hasAnimation(animPrefix))
{
currentState = Idle;
}
if (getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
case Confirm: case Confirm:
var animPrefix = playableCharData.getAnimationPrefix('confirm'); var animPrefix = playableCharData.getAnimationPrefix('confirm');
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false); if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false);
timeIdling = 0; timeIdling = 0;
case FistPumpIntro: case FistPumpIntro:
var animPrefix = playableCharData.getAnimationPrefix('fistPump'); var animPrefixA = playableCharData.getAnimationPrefix('fistPump');
if (getCurrentAnimation() != animPrefix) playFlashAnimation('Boyfriend DJ fist pump', false); var animPrefixB = playableCharData.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefix && anim.curFrame >= 4)
if (getCurrentAnimation() == animPrefixA)
{ {
playAnimation("Boyfriend DJ fist pump", true, false, false, 0); var endFrame = playableCharData.getFistPumpIntroEndFrame();
if (endFrame > -1 && anim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData.getFistPumpIntroStartFrame());
} }
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData.getFistPumpIntroBadEndFrame();
if (endFrame > -1 && anim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData.getFistPumpIntroBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPumpIntro: " + getCurrentAnimation());
}
case FistPump: case FistPump:
var animPrefixA = playableCharData.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData.getFistPumpLoopEndFrame();
if (endFrame > -1 && anim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData.getFistPumpLoopStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData.getFistPumpLoopBadEndFrame();
if (endFrame > -1 && anim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData.getFistPumpLoopBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPump: " + getCurrentAnimation());
}
case IdleEasterEgg: case IdleEasterEgg:
var animPrefix = playableCharData.getAnimationPrefix('idleEasterEgg'); var animPrefix = playableCharData.getAnimationPrefix('idleEasterEgg');
@ -184,8 +235,15 @@ class FreeplayDJ extends FlxAtlasSprite
// var name = anim.curSymbol.name; // var name = anim.curSymbol.name;
if (name == playableCharData.getAnimationPrefix('intro')) if (name == playableCharData.getAnimationPrefix('intro'))
{
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
}
else
{ {
currentState = Idle; currentState = Idle;
}
onIntroDone.dispatch(); onIntroDone.dispatch();
} }
else if (name == playableCharData.getAnimationPrefix('idle')) else if (name == playableCharData.getAnimationPrefix('idle'))
@ -225,9 +283,17 @@ class FreeplayDJ extends FlxAtlasSprite
// runTvLogic(); // runTvLogic();
} }
trace('Replay idle: ${frame}'); trace('Replay idle: ${frame}');
playAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, frame); playFlashAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, frame);
// trace('Finished confirm'); // trace('Finished confirm');
} }
else if (name == playableCharData.getAnimationPrefix('newUnlock'))
{
// Animation should loop.
}
else if (name == playableCharData.getAnimationPrefix('charSelect'))
{
onCharSelectComplete();
}
else else
{ {
trace('Finished ${name}'); trace('Finished ${name}');
@ -240,6 +306,15 @@ class FreeplayDJ extends FlxAtlasSprite
seenIdleEasterEgg = false; seenIdleEasterEgg = false;
} }
/**
* Dynamic function, it's actually a variable you can reassign!
* `dj.onCharSelectComplete = function() {};`
*/
public dynamic function onCharSelectComplete():Void
{
trace('onCharSelectComplete()');
}
var offsetX:Float = 0.0; var offsetX:Float = 0.0;
var offsetY:Float = 0.0; var offsetY:Float = 0.0;
@ -271,7 +346,7 @@ class FreeplayDJ extends FlxAtlasSprite
function loadCartoon() function loadCartoon()
{ {
cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() { cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() {
playAnimation("Boyfriend DJ watchin tv OG", true, false, false, 60); playFlashAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, 60);
}); });
// Fade out music to 40% volume over 1 second. // Fade out music to 40% volume over 1 second.
@ -301,21 +376,48 @@ class FreeplayDJ extends FlxAtlasSprite
currentState = Confirm; currentState = Confirm;
} }
public function fistPump():Void public function toCharSelect():Void
{
if (hasAnimation('charSelect'))
{
currentState = CharSelect;
var animPrefix = playableCharData.getAnimationPrefix('charSelect');
playFlashAnimation(animPrefix, true, false, false, 0);
}
else
{
currentState = Confirm;
// Call this immediately; otherwise, we get locked out of Character Select.
onCharSelectComplete();
}
}
public function fistPumpIntro():Void
{ {
currentState = FistPumpIntro; currentState = FistPumpIntro;
var animPrefix = playableCharData.getAnimationPrefix('fistPump');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpIntroStartFrame());
} }
public function pumpFist():Void public function fistPump():Void
{ {
currentState = FistPump; currentState = FistPump;
playAnimation("Boyfriend DJ fist pump", true, false, false, 4); var animPrefix = playableCharData.getAnimationPrefix('fistPump');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpLoopStartFrame());
} }
public function pumpFistBad():Void public function fistPumpLossIntro():Void
{
currentState = FistPumpIntro;
var animPrefix = playableCharData.getAnimationPrefix('loss');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpIntroBadStartFrame());
}
public function fistPumpLoss():Void
{ {
currentState = FistPump; currentState = FistPump;
playAnimation("Boyfriend DJ loss reaction 1", true, false, false, 4); var animPrefix = playableCharData.getAnimationPrefix('loss');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpLoopBadStartFrame());
} }
override public function getCurrentAnimation():String override public function getCurrentAnimation():String
@ -366,13 +468,53 @@ class FreeplayDJ extends FlxAtlasSprite
} }
} }
enum DJBoyfriendState enum FreeplayDJState
{ {
/**
* Character enters the frame and transitions to Idle.
*/
Intro; Intro;
/**
* Character loops in idle.
*/
Idle; Idle;
Confirm;
FistPumpIntro; /**
FistPump; * Plays an easter egg animation after a period in Idle, then reverts to Idle.
*/
IdleEasterEgg; IdleEasterEgg;
/**
* Plays an elaborate easter egg animation. Does not revert until another animation is triggered.
*/
Cartoon; Cartoon;
/**
* Player has selected a song.
*/
Confirm;
/**
* Character preps to play the fist pump animation; plays after the Results screen.
* The actual frame label that gets played may vary based on the player's success.
*/
FistPumpIntro;
/**
* Character plays the fist pump animation.
* The actual frame label that gets played may vary based on the player's success.
*/
FistPump;
/**
* Plays an animation to indicate that the player has a new unlock in Character Select.
* Overrides all idle animations as well as the fist pump. Only Confirm and CharSelect will override this.
*/
NewUnlock;
/**
* Plays an animation to transition to the Character Select screen.
*/
CharSelect;
} }

View file

@ -177,9 +177,22 @@ class FreeplayState extends MusicBeatSubState
var stickerSubState:Null<StickerSubState> = null; var stickerSubState:Null<StickerSubState> = null;
public static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY; /**
* The difficulty we were on when this menu was last accessed.
*/
public static var rememberedDifficulty:String = Constants.DEFAULT_DIFFICULTY;
/**
* The song we were on when this menu was last accessed.
* NOTE: `null` if the last song was `Random`.
*/
public static var rememberedSongId:Null<String> = 'tutorial'; public static var rememberedSongId:Null<String> = 'tutorial';
/**
* The character we were on when this menu was last accessed.
*/
public static var rememberedCharacterId:String = Constants.DEFAULT_CHARACTER;
var funnyCam:FunkinCamera; var funnyCam:FunkinCamera;
var rankCamera:FunkinCamera; var rankCamera:FunkinCamera;
var rankBg:FunkinSprite; var rankBg:FunkinSprite;
@ -209,14 +222,16 @@ class FreeplayState extends MusicBeatSubState
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState) public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
{ {
currentCharacterId = params?.character ?? Constants.DEFAULT_CHARACTER; currentCharacterId = params?.character ?? rememberedCharacterId;
var fetchPlayableCharacter = function():PlayableCharacter { var fetchPlayableCharacter = function():PlayableCharacter {
var result = PlayerRegistry.instance.fetchEntry(params?.character ?? Constants.DEFAULT_CHARACTER); var result = PlayerRegistry.instance.fetchEntry(params?.character ?? rememberedCharacterId);
if (result == null) throw 'No valid playable character with id ${params?.character}'; if (result == null) throw 'No valid playable character with id ${params?.character}';
return result; return result;
}; };
currentCharacter = fetchPlayableCharacter(); currentCharacter = fetchPlayableCharacter();
rememberedCharacterId = currentCharacter?.id ?? Constants.DEFAULT_CHARACTER;
fromResultsParams = params?.fromResults; fromResultsParams = params?.fromResults;
if (fromResultsParams?.playRankAnim == true) if (fromResultsParams?.playRankAnim == true)
@ -629,8 +644,8 @@ class FreeplayState extends MusicBeatSubState
speed: 0.3 speed: 0.3
}); });
var diffSelLeft:DifficultySelector = new DifficultySelector(20, grpDifficulties.y - 10, false, controls); var diffSelLeft:DifficultySelector = new DifficultySelector(this, 20, grpDifficulties.y - 10, false, controls);
var diffSelRight:DifficultySelector = new DifficultySelector(325, grpDifficulties.y - 10, true, controls); var diffSelRight:DifficultySelector = new DifficultySelector(this, 325, grpDifficulties.y - 10, true, controls);
diffSelLeft.visible = false; diffSelLeft.visible = false;
diffSelRight.visible = false; diffSelRight.visible = false;
add(diffSelLeft); add(diffSelLeft);
@ -743,10 +758,7 @@ class FreeplayState extends MusicBeatSubState
var tempSongs:Array<Null<FreeplaySongData>> = songs; var tempSongs:Array<Null<FreeplaySongData>> = songs;
// Remember just the difficulty because it's important for song sorting. // Remember just the difficulty because it's important for song sorting.
if (rememberedDifficulty != null)
{
currentDifficulty = rememberedDifficulty; currentDifficulty = rememberedDifficulty;
}
if (filterStuff != null) tempSongs = sortSongs(tempSongs, filterStuff); if (filterStuff != null) tempSongs = sortSongs(tempSongs, filterStuff);
@ -901,7 +913,15 @@ class FreeplayState extends MusicBeatSubState
changeSelection(); changeSelection();
changeDiff(); changeDiff();
if (dj != null) dj.fistPump(); if (fromResultsParams?.newRank == SHIT)
{
if (dj != null) dj.fistPumpLossIntro();
}
else
{
if (dj != null) dj.fistPumpIntro();
}
// rankCamera.fade(FlxColor.BLACK, 0.5, true); // rankCamera.fade(FlxColor.BLACK, 0.5, true);
rankCamera.fade(0xFF000000, 0.5, true, null, true); rankCamera.fade(0xFF000000, 0.5, true, null, true);
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0; if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
@ -1083,11 +1103,11 @@ class FreeplayState extends MusicBeatSubState
if (fromResultsParams?.newRank == SHIT) if (fromResultsParams?.newRank == SHIT)
{ {
if (dj != null) dj.pumpFistBad(); if (dj != null) dj.fistPumpLoss();
} }
else else
{ {
if (dj != null) dj.pumpFist(); if (dj != null) dj.fistPump();
} }
rankCamera.zoom = 0.8; rankCamera.zoom = 0.8;
@ -1190,7 +1210,7 @@ class FreeplayState extends MusicBeatSubState
/** /**
* If true, disable interaction with the interface. * If true, disable interaction with the interface.
*/ */
var busy:Bool = false; public var busy:Bool = false;
var originalPos:FlxPoint = new FlxPoint(); var originalPos:FlxPoint = new FlxPoint();
@ -1233,8 +1253,33 @@ class FreeplayState extends MusicBeatSubState
if (controls.FREEPLAY_CHAR_SELECT && !busy) if (controls.FREEPLAY_CHAR_SELECT && !busy)
{ {
// Check if we have ACCESS to character select!
trace('Is Pico unlocked? ${PlayerRegistry.instance.fetchEntry('pico')?.isUnlocked()}');
trace('Number of characters: ${PlayerRegistry.instance.countUnlockedCharacters()}');
if (PlayerRegistry.instance.countUnlockedCharacters() > 1)
{
if (dj != null)
{
busy = true;
// Transition to character select after animation
dj.onCharSelectComplete = function() {
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState()); FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
} }
dj.toCharSelect();
}
else
{
// Transition to character select immediately
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
}
}
else
{
trace('Not enough characters unlocked to open character select!');
FunkinSound.playOnce(Paths.sound('cancelMenu'));
}
}
if (controls.FREEPLAY_FAVORITE && !busy) if (controls.FREEPLAY_FAVORITE && !busy)
{ {
@ -1326,6 +1371,8 @@ class FreeplayState extends MusicBeatSubState
} }
handleInputs(elapsed); handleInputs(elapsed);
if (dj != null) FlxG.watch.addQuick('dj-anim', dj.getCurrentAnimation());
} }
function handleInputs(elapsed:Float):Void function handleInputs(elapsed:Float):Void
@ -1483,7 +1530,7 @@ class FreeplayState extends MusicBeatSubState
generateSongList(currentFilter, true); generateSongList(currentFilter, true);
} }
if (controls.BACK) if (controls.BACK && !busy)
{ {
busy = true; busy = true;
FlxTween.globalManager.clear(); FlxTween.globalManager.clear();
@ -1891,7 +1938,7 @@ class FreeplayState extends MusicBeatSubState
intendedCompletion = 0.0; intendedCompletion = 0.0;
diffIdsCurrent = diffIdsTotal; diffIdsCurrent = diffIdsTotal;
rememberedSongId = null; rememberedSongId = null;
rememberedDifficulty = null; rememberedDifficulty = Constants.DEFAULT_DIFFICULTY;
albumRoll.albumId = null; albumRoll.albumId = null;
} }
@ -2000,10 +2047,13 @@ class DifficultySelector extends FlxSprite
var controls:Controls; var controls:Controls;
var whiteShader:PureColor; var whiteShader:PureColor;
public function new(x:Float, y:Float, flipped:Bool, controls:Controls) var parent:FreeplayState;
public function new(parent:FreeplayState, x:Float, y:Float, flipped:Bool, controls:Controls)
{ {
super(x, y); super(x, y);
this.parent = parent;
this.controls = controls; this.controls = controls;
frames = Paths.getSparrowAtlas('freeplay/freeplaySelector'); frames = Paths.getSparrowAtlas('freeplay/freeplaySelector');
@ -2019,8 +2069,8 @@ class DifficultySelector extends FlxSprite
override function update(elapsed:Float):Void override function update(elapsed:Float):Void
{ {
if (flipX && controls.UI_RIGHT_P) moveShitDown(); if (flipX && controls.UI_RIGHT_P && !parent.busy) moveShitDown();
if (!flipX && controls.UI_LEFT_P) moveShitDown(); if (!flipX && controls.UI_LEFT_P && !parent.busy) moveShitDown();
super.update(elapsed); super.update(elapsed);
} }

View file

@ -88,6 +88,11 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
return _data.freeplayDJ.getFreeplayDJText(index); return _data.freeplayDJ.getFreeplayDJText(index);
} }
public function getCharSelectData():PlayerCharSelectData
{
return _data.charSelect;
}
/** /**
* @param rank Which rank to get info for * @param rank Which rank to get info for
* @return An array of animations. For example, BF Great has two animations, one for BF and one for GF * @return An array of animations. For example, BF Great has two animations, one for BF and one for GF

View file

@ -98,14 +98,7 @@ class MainMenuState extends MusicBeatState
add(menuItems); add(menuItems);
menuItems.onChange.add(onMenuItemChange); menuItems.onChange.add(onMenuItemChange);
menuItems.onAcceptPress.add(function(_) { menuItems.onAcceptPress.add(function(_) {
if (_.name == 'freeplay')
{
magenta.visible = true;
}
else
{
FlxFlicker.flicker(magenta, 1.1, 0.15, false, true); FlxFlicker.flicker(magenta, 1.1, 0.15, false, true);
}
}); });
menuItems.enabled = true; // can move on intro menuItems.enabled = true; // can move on intro
@ -117,10 +110,7 @@ class MainMenuState extends MusicBeatState
FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true; FlxTransitionableState.skipNextTransOut = true;
openSubState(new FreeplayState( openSubState(new FreeplayState());
{
character: FlxG.keys.pressed.SHIFT ? 'pico' : 'bf',
}));
}); });
#if CAN_OPEN_LINKS #if CAN_OPEN_LINKS
@ -355,6 +345,7 @@ class MainMenuState extends MusicBeatState
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W) if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
{ {
FunkinSound.playOnce(Paths.sound('confirmMenu'));
// Give the user a score of 1 point on Weekend 1 story mode. // Give the user a score of 1 point on Weekend 1 story mode.
// This makes the level count as cleared and displays the songs in Freeplay. // This makes the level count as cleared and displays the songs in Freeplay.
funkin.save.Save.instance.setLevelScore('weekend1', 'easy', funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
@ -375,6 +366,29 @@ class MainMenuState extends MusicBeatState
}); });
} }
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L)
{
FunkinSound.playOnce(Paths.sound('confirmMenu'));
// Give the user a score of 0 points on Weekend 1 story mode.
// This makes the level count as uncleared and no longer displays the songs in Freeplay.
funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
{
score: 1,
tallies:
{
sick: 0,
good: 0,
bad: 0,
shit: 0,
missed: 0,
combo: 0,
maxCombo: 0,
totalNotesHit: 0,
totalNotes: 0,
}
});
}
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R) if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R)
{ {
// Give the user a hypothetical overridden score, // Give the user a hypothetical overridden score,

View file

@ -16,7 +16,7 @@ class LevelProp extends Bopper
this.propData = value; this.propData = value;
this.visible = this.propData != null; this.visible = this.propData != null;
danceEvery = this.propData?.danceEvery ?? 0.0; danceEvery = this.propData?.danceEvery ?? 1.0;
applyData(); applyData();
} }
@ -32,7 +32,7 @@ class LevelProp extends Bopper
public function playConfirm():Void public function playConfirm():Void
{ {
playAnimation('confirm', true, true); if (hasAnimation('confirm')) playAnimation('confirm', true, true);
} }
function applyData():Void function applyData():Void

View file

@ -314,25 +314,28 @@ class LoadingState extends MusicBeatSubState
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num7')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num7'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num8')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num8'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num9')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num9'));
FunkinSprite.cacheTexture(Paths.image('notes', 'shared')); FunkinSprite.cacheTexture(Paths.image('notes', 'shared'));
FunkinSprite.cacheTexture(Paths.image('noteSplashes', 'shared')); FunkinSprite.cacheTexture(Paths.image('noteSplashes', 'shared'));
FunkinSprite.cacheTexture(Paths.image('noteStrumline', 'shared')); FunkinSprite.cacheTexture(Paths.image('noteStrumline', 'shared'));
FunkinSprite.cacheTexture(Paths.image('NOTE_hold_assets')); FunkinSprite.cacheTexture(Paths.image('NOTE_hold_assets'));
FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/ready', 'shared')); FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/ready', 'shared'));
FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/set', 'shared')); FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/set', 'shared'));
FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/go', 'shared')); FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/go', 'shared'));
FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/ready', 'shared')); FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/ready', 'shared'));
FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/set', 'shared')); FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/set', 'shared'));
FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/go', 'shared')); FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/go', 'shared'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/normal/sick'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/normal/good')); FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/sick'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/normal/bad')); FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/good'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/normal/shit')); FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/bad'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/shit'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/sick')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/sick'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/good')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/good'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/bad')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/bad'));
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/shit')); FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/shit'));
FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this
// List all image assets in the level's library. // List all image assets in the level's library.
// This is crude and I want to remove it when we have a proper asset caching system. // This is crude and I want to remove it when we have a proper asset caching system.

View file

@ -524,12 +524,16 @@ class Constants
* OTHER * OTHER
*/ */
// ============================== // ==============================
#if FEATURE_GHOST_TAPPING
// Hey there, Eric here.
// This feature is currently still in development. You can test it out by creating a special debug build!
// lime build windows -DFEATURE_GHOST_TAPPING
/** /**
* If true, the player will not receive the ghost miss penalty if there are no notes within the hit window. * Duration, in seconds, after the player's section ends before the player can spam without penalty.
* This is the thing people have been begging for forever lolol.
*/ */
public static final GHOST_TAPPING:Bool = false; public static final GHOST_TAP_DELAY:Float = 3 / 8;
#end
/** /**
* The maximum number of previous file paths for the Chart Editor to remember. * The maximum number of previous file paths for the Chart Editor to remember.