mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-12-11 16:51:21 -05:00
Merge branch 'rewrite/master' into feature/pico-results-perfect
This commit is contained in:
commit
b1e68c27fd
10 changed files with 285 additions and 62 deletions
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 8e0d71c8f32ebd872f7f7411b3f7108083554de9
|
||||
Subproject commit 7c913a9e209a4765c3bcb0c049a472086269846c
|
2
hmm.json
2
hmm.json
|
@ -145,7 +145,7 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e0b2339e02fff91168789dbd1a0dd019ea3dda39",
|
||||
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -225,7 +225,7 @@ class InitState extends FlxState
|
|||
// -DRESULTS
|
||||
FlxG.switchState(() -> new funkin.play.ResultState(
|
||||
{
|
||||
storyMode: false,
|
||||
storyMode: true,
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico-playable",
|
||||
|
|
|
@ -71,7 +71,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||
|
||||
public function hasNewCharacter():Bool
|
||||
{
|
||||
var characters = Save.instance.charactersSeen.clone();
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
|
@ -79,7 +79,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||
if (player == null) continue;
|
||||
|
||||
if (!player.isUnlocked()) continue;
|
||||
if (characters.contains(charId)) continue;
|
||||
if (charactersSeen.contains(charId)) continue;
|
||||
|
||||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
return true;
|
||||
|
@ -89,6 +89,26 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||
return false;
|
||||
}
|
||||
|
||||
public function listNewCharacters():Array<String>
|
||||
{
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
var result = [];
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (!player.isUnlocked()) continue;
|
||||
if (charactersSeen.contains(charId)) continue;
|
||||
|
||||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
result.push(charId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playable character associated with a given stage character.
|
||||
* @param characterId The stage character ID.
|
||||
|
|
|
@ -503,6 +503,12 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
public var camGame:FlxCamera;
|
||||
|
||||
/**
|
||||
* Simple helper debug variable, to be able to move the camera around for debug purposes
|
||||
* without worrying about the camera tweening back to the follow point.
|
||||
*/
|
||||
public var debugUnbindCameraZoom:Bool = false;
|
||||
|
||||
/**
|
||||
* The camera which contains, and controls visibility of, a video cutscene, dialogue, pause menu and sticker transition.
|
||||
*/
|
||||
|
@ -992,7 +998,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x
|
||||
var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier.
|
||||
FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera.
|
||||
if (!debugUnbindCameraZoom) FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera.
|
||||
|
||||
camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95);
|
||||
}
|
||||
|
@ -1458,6 +1464,13 @@ class PlayState extends MusicBeatSubState
|
|||
super.destroy();
|
||||
}
|
||||
|
||||
public override function initConsoleHelpers():Void
|
||||
{
|
||||
FlxG.console.registerFunction("debugUnbindCameraZoom", () -> {
|
||||
debugUnbindCameraZoom = !debugUnbindCameraZoom;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the game and HUD cameras.
|
||||
*/
|
||||
|
@ -2224,10 +2237,10 @@ class PlayState extends MusicBeatSubState
|
|||
// Skip handling the miss in botplay!
|
||||
if (!isBotPlayMode)
|
||||
{
|
||||
// Judge the miss.
|
||||
// NOTE: This is what handles the scoring.
|
||||
trace('Missed note! ${note.noteData}');
|
||||
onNoteMiss(note, event.playSound, event.healthChange);
|
||||
// Judge the miss.
|
||||
// NOTE: This is what handles the scoring.
|
||||
trace('Missed note! ${note.noteData}');
|
||||
onNoteMiss(note, event.playSound, event.healthChange);
|
||||
}
|
||||
|
||||
note.handledMiss = true;
|
||||
|
@ -2344,31 +2357,31 @@ class PlayState extends MusicBeatSubState
|
|||
playerStrumline.playPress(input.noteDirection);
|
||||
trace('PENALTY Score: ${songScore}');
|
||||
}
|
||||
else if (notesInDirection.length == 0)
|
||||
{
|
||||
// Press a key with no penalty.
|
||||
else if (notesInDirection.length == 0)
|
||||
{
|
||||
// Press a key with no penalty.
|
||||
|
||||
// Play the strumline animation.
|
||||
playerStrumline.playPress(input.noteDirection);
|
||||
trace('NO PENALTY Score: ${songScore}');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Choose the first note, deprioritizing low priority notes.
|
||||
var targetNote:Null<NoteSprite> = notesInDirection.find((note) -> !note.lowPriority);
|
||||
if (targetNote == null) targetNote = notesInDirection[0];
|
||||
if (targetNote == null) continue;
|
||||
// Play the strumline animation.
|
||||
playerStrumline.playPress(input.noteDirection);
|
||||
trace('NO PENALTY Score: ${songScore}');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Choose the first note, deprioritizing low priority notes.
|
||||
var targetNote:Null<NoteSprite> = notesInDirection.find((note) -> !note.lowPriority);
|
||||
if (targetNote == null) targetNote = notesInDirection[0];
|
||||
if (targetNote == null) continue;
|
||||
|
||||
// Judge and hit the note.
|
||||
trace('Hit note! ${targetNote.noteData}');
|
||||
goodNoteHit(targetNote, input);
|
||||
trace('Score: ${songScore}');
|
||||
// Judge and hit the note.
|
||||
trace('Hit note! ${targetNote.noteData}');
|
||||
goodNoteHit(targetNote, input);
|
||||
trace('Score: ${songScore}');
|
||||
|
||||
notesInDirection.remove(targetNote);
|
||||
notesInDirection.remove(targetNote);
|
||||
|
||||
// Play the strumline animation.
|
||||
playerStrumline.playConfirm(input.noteDirection);
|
||||
}
|
||||
// Play the strumline animation.
|
||||
playerStrumline.playConfirm(input.noteDirection);
|
||||
}
|
||||
}
|
||||
|
||||
while (inputReleaseQueue.length > 0)
|
||||
|
|
|
@ -731,42 +731,59 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Determining the target state(s) to go to.
|
||||
// Default to main menu because that's better than `null`.
|
||||
var targetState:flixel.FlxState = new funkin.ui.mainmenu.MainMenuState();
|
||||
var shouldTween = false;
|
||||
|
||||
if (params.storyMode)
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||
if (PlayerRegistry.instance.hasNewCharacter())
|
||||
{
|
||||
targetState = new StoryMenuState(null);
|
||||
|
||||
var newCharacters = PlayerRegistry.instance.listNewCharacters();
|
||||
|
||||
for (charId in newCharacters)
|
||||
{
|
||||
shouldTween = true;
|
||||
// This works recursively, ehe!
|
||||
targetState = new funkin.ui.charSelect.CharacterUnlockState(charId, targetState);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var rigged:Bool = true;
|
||||
if (rank > Scoring.calculateRank(params?.prevScoreData)) // if (rigged)
|
||||
if (rank > Scoring.calculateRank(params?.prevScoreData))
|
||||
{
|
||||
trace('THE RANK IS Higher.....');
|
||||
|
||||
FlxTween.tween(rankBg, {alpha: 1}, 0.5,
|
||||
shouldTween = true;
|
||||
targetState = FreeplayState.build(
|
||||
{
|
||||
ease: FlxEase.expoOut,
|
||||
onComplete: function(_) {
|
||||
FlxG.switchState(FreeplayState.build(
|
||||
{
|
||||
character: playerCharacterId ?? "bf",
|
||||
fromResults:
|
||||
{
|
||||
{
|
||||
character: playerCharacterId ?? "bf",
|
||||
fromResults:
|
||||
{
|
||||
oldRank: Scoring.calculateRank(params?.prevScoreData),
|
||||
newRank: rank,
|
||||
songId: params.songId,
|
||||
difficultyId: params.difficultyId,
|
||||
playRankAnim: true
|
||||
}
|
||||
}
|
||||
}));
|
||||
oldRank: Scoring.calculateRank(params?.prevScoreData),
|
||||
newRank: rank,
|
||||
songId: params.songId,
|
||||
difficultyId: params.difficultyId,
|
||||
playRankAnim: true
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('rank is lower...... and/or equal');
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
|
||||
|
||||
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
|
||||
{
|
||||
{
|
||||
fromResults:
|
||||
|
@ -778,9 +795,24 @@ class ResultState extends MusicBeatSubState
|
|||
difficultyId: params.difficultyId
|
||||
}
|
||||
}
|
||||
}, sticker)));
|
||||
}, sticker));
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldTween)
|
||||
{
|
||||
FlxTween.tween(rankBg, {alpha: 1}, 0.5,
|
||||
{
|
||||
ease: FlxEase.expoOut,
|
||||
onComplete: function(_) {
|
||||
FlxG.switchState(targetState);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(targetState);
|
||||
}
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
|
|
|
@ -49,7 +49,7 @@ class HealthIcon extends FunkinSprite
|
|||
* this value allows you to set a relative scale for the icon.
|
||||
* @default 1x scale = 150px width and height.
|
||||
*/
|
||||
public var size:FlxPoint = new FlxPoint(1, 1);
|
||||
public var size:FlxPoint;
|
||||
|
||||
/**
|
||||
* Apply the "bop" animation once every X steps.
|
||||
|
@ -120,11 +120,15 @@ class HealthIcon extends FunkinSprite
|
|||
{
|
||||
super(0, 0);
|
||||
this.playerId = playerId;
|
||||
this.size = new FlxCallbackPoint(onSetSize);
|
||||
this.scrollFactor.set();
|
||||
|
||||
size.set(1.0, 1.0);
|
||||
this.characterId = char;
|
||||
}
|
||||
|
||||
initTargetSize();
|
||||
function onSetSize(value:FlxPoint):Void
|
||||
{
|
||||
snapToTargetSize();
|
||||
}
|
||||
|
||||
function set_characterId(value:Null<String>):Null<String>
|
||||
|
@ -243,6 +247,22 @@ class HealthIcon extends FunkinSprite
|
|||
this.updateHitbox();
|
||||
}
|
||||
|
||||
/*
|
||||
* Immediately snap the health icon to its target size without lerping.
|
||||
*/
|
||||
public function snapToTargetSize():Void
|
||||
{
|
||||
if (this.width > this.height)
|
||||
{
|
||||
setGraphicSize(Std.int(HEALTH_ICON_SIZE * this.size.x), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
setGraphicSize(0, Std.int(HEALTH_ICON_SIZE * this.size.y));
|
||||
}
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position (and status) of the health icon.
|
||||
*/
|
||||
|
@ -301,12 +321,6 @@ class HealthIcon extends FunkinSprite
|
|||
}
|
||||
}
|
||||
|
||||
inline function initTargetSize():Void
|
||||
{
|
||||
setGraphicSize(HEALTH_ICON_SIZE);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
function updateHealthIcon(health:Float):Void
|
||||
{
|
||||
// We want to efficiently handle animation playback
|
||||
|
|
|
@ -56,6 +56,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
|
||||
Conductor.beatHit.add(this.beatHit);
|
||||
Conductor.stepHit.add(this.stepHit);
|
||||
|
||||
initConsoleHelpers();
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
|
@ -79,6 +81,8 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
||||
public function initConsoleHelpers():Void {}
|
||||
|
||||
function reloadAssets()
|
||||
{
|
||||
PolymodHandler.forceReloadAssets();
|
||||
|
|
128
source/funkin/ui/charSelect/CharacterUnlockState.hx
Normal file
128
source/funkin/ui/charSelect/CharacterUnlockState.hx
Normal file
|
@ -0,0 +1,128 @@
|
|||
package funkin.ui.charSelect;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.components.HealthIcon;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
|
||||
using flixel.util.FlxSpriteUtil;
|
||||
|
||||
/**
|
||||
* When you want the player to unlock a character, call `CharacterUnlockState.unlock(characterName)`.
|
||||
* It handles both the act of unlocking the character and displaying the dialog.
|
||||
*/
|
||||
class CharacterUnlockState extends MusicBeatState
|
||||
{
|
||||
public var targetCharacterId:String = "";
|
||||
public var targetCharacterData:Null<PlayableCharacter>;
|
||||
|
||||
var nextState:FlxState;
|
||||
|
||||
static final DIALOG_BG_COLOR:FlxColor = 0xFF000000; // Iconic
|
||||
static final DIALOG_COLOR:FlxColor = 0xFF4344F6; // Iconic
|
||||
static final DIALOG_FONT_COLOR:FlxColor = 0xFFFFFFFF; // Iconic
|
||||
|
||||
var busy:Bool = false;
|
||||
|
||||
public function new(targetPlayableCharacter:String, ?nextState:FlxState)
|
||||
{
|
||||
super();
|
||||
|
||||
this.targetCharacterId = targetPlayableCharacter;
|
||||
this.targetCharacterData = PlayerRegistry.instance.fetchEntry(targetCharacterId);
|
||||
this.nextState = nextState == null ? new MainMenuState() : nextState;
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
handleMusic();
|
||||
|
||||
bgColor = DIALOG_BG_COLOR;
|
||||
|
||||
var dialogContainer:FlxSpriteGroup = new FlxSpriteGroup();
|
||||
add(dialogContainer);
|
||||
|
||||
// Build the graphic for the text...
|
||||
var charName:String = targetCharacterData != null ? targetCharacterData.getName() : targetCharacterId.toTitleCase();
|
||||
// var dialogText:FlxText = new FlxText(0, 0, 0, 'You can now play as $charName.\n\nCheck it out in Freeplay!');
|
||||
var dialogText:FlxText = new FlxText(0, 0, 0, 'You can now play as $charName.');
|
||||
dialogText.setFormat("VCR OSD Mono", 32, DIALOG_FONT_COLOR, LEFT);
|
||||
|
||||
// THEN we can size the dialog to match...
|
||||
var dialogBG:FlxSprite = new FlxSprite(0, 0);
|
||||
dialogBG.makeGraphic(Std.int(dialogText.width + 32), Std.int(dialogText.height + 32), FlxColor.TRANSPARENT);
|
||||
dialogBG.drawRoundRect(0, 0, dialogBG.width, dialogBG.height, 16, 16, DIALOG_COLOR);
|
||||
dialogContainer.add(dialogBG);
|
||||
|
||||
dialogBG.screenCenter(XY);
|
||||
|
||||
// THEN we can position the text inside that.
|
||||
dialogText.x = dialogBG.x + 16;
|
||||
dialogText.y = dialogBG.y + 16;
|
||||
dialogContainer.add(dialogText);
|
||||
|
||||
// HealthIcon handles getting the right frames for us,
|
||||
// but it has a bunch of overhead in it that makes it gross to work with outside the health bar.
|
||||
var healthIconCharacterId = targetCharacterData.getOwnedCharacterIds()[0];
|
||||
var baseCharacter = CharacterDataParser.fetchCharacter(healthIconCharacterId);
|
||||
var healthIcon:HealthIcon = new HealthIcon(healthIconCharacterId);
|
||||
@:privateAccess
|
||||
healthIcon.configure(baseCharacter._data.healthIcon);
|
||||
healthIcon.autoUpdate = false;
|
||||
healthIcon.bopEvery = 0; // You can increase this number later once the animation is done.
|
||||
healthIcon.size.set(0.5, 0.5);
|
||||
healthIcon.x = dialogBG.x + 390;
|
||||
healthIcon.y = dialogBG.y + 6;
|
||||
healthIcon.flipX = true;
|
||||
healthIcon.snapToTargetSize();
|
||||
dialogContainer.add(healthIcon);
|
||||
|
||||
dialogContainer.scale.set(0, 0);
|
||||
FlxTween.num(0.0, 1.0, 0.75,
|
||||
{
|
||||
ease: FlxEase.elasticOut,
|
||||
}, function(curScale) {
|
||||
dialogContainer.scale.set(curScale, curScale);
|
||||
healthIcon.size.set(0.5 * curScale, 0.5 * curScale);
|
||||
});
|
||||
|
||||
// performUnlock();
|
||||
}
|
||||
|
||||
function handleMusic():Void
|
||||
{
|
||||
FlxG.sound.music?.stop();
|
||||
FlxG.sound.play(Paths.sound('confirmMenu'));
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (controls.ACCEPT || controls.BACK && !busy)
|
||||
{
|
||||
busy = true;
|
||||
startClose();
|
||||
}
|
||||
}
|
||||
|
||||
function startClose():Void
|
||||
{
|
||||
// Fade to black, then switch state.
|
||||
FlxG.camera.fade(FlxColor.BLACK, 0.75, false, () -> {
|
||||
FlxG.switchState(nextState);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -344,8 +344,8 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Open the debug menu, defaults to ` / ~
|
||||
// This includes stuff like the Chart Editor, so it should be present on all builds.
|
||||
if (controls.DEBUG_MENU)
|
||||
{
|
||||
persistentUpdate = false;
|
||||
|
@ -353,6 +353,18 @@ class MainMenuState extends MusicBeatState
|
|||
FlxG.state.openSubState(new DebugMenuSubState());
|
||||
}
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Ctrl+Alt+Shift+P = Character Unlock screen
|
||||
// Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock
|
||||
// Ctrl+Alt+Shift+L = Revoke requirements for Pico Unlock
|
||||
// Ctrl+Alt+Shift+R = Score/Rank conflict test
|
||||
// Ctrl+Alt+Shift+E = Dump save data
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P)
|
||||
{
|
||||
FlxG.switchState(() -> new funkin.ui.charSelect.CharacterUnlockState('pico'));
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
|
|
Loading…
Reference in a new issue