Merge remote-tracking branch 'upstream/main' into new-editors-main
# Conflicts: # commandline/commands/Compiler.hx # source/funkin/backend/FunkinSprite.hx # source/funkin/backend/MusicBeatState.hx # source/funkin/backend/MusicBeatSubstate.hx # source/funkin/backend/chart/Chart.hx # source/funkin/backend/chart/EventsData.hx # source/funkin/backend/scripting/GlobalScript.hx # source/funkin/backend/system/Controls.hx # source/funkin/backend/system/MainState.hx # source/funkin/backend/utils/AudioAnalyzer.hx # source/funkin/backend/utils/CoolUtil.hx # source/funkin/editors/character/CharacterInfoScreen.hx # source/funkin/editors/charter/Charter.hx # source/funkin/editors/charter/CharterSelection.hx # source/funkin/editors/charter/CharterStrumline.hx # source/funkin/editors/charter/CharterStrumlineButton.hx # source/funkin/editors/charter/SongCreationScreen.hx # source/funkin/editors/ui/UIDropDown.hx # source/funkin/editors/ui/UIFileExplorer.hx # source/funkin/game/Note.hx # source/funkin/game/PlayState.hx # source/funkin/game/SplashGroup.hx # source/funkin/game/Stage.hx # source/funkin/game/Strum.hx # source/funkin/menus/FreeplayState.hx # source/funkin/options/Options.hx # source/funkin/options/keybinds/KeybindsOptions.hx
This commit is contained in:
@@ -101,30 +101,8 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||
if (__cancelDefault)
|
||||
return;
|
||||
|
||||
if (controls.ACCEPT)
|
||||
{
|
||||
endBullshit();
|
||||
}
|
||||
|
||||
if (controls.BACK)
|
||||
{
|
||||
if (PlayState.chartingMode && Charter.undos.unsaved)
|
||||
game.saveWarn(false);
|
||||
else {
|
||||
PlayState.resetSongInfos();
|
||||
if (Charter.instance != null) Charter.instance.__clearStatics();
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music = null;
|
||||
|
||||
if (PlayState.isStoryMode)
|
||||
FlxG.switchState(new StoryMenuState());
|
||||
else
|
||||
FlxG.switchState(new FreeplayState());
|
||||
}
|
||||
|
||||
}
|
||||
if (controls.ACCEPT) endBullshit();
|
||||
if (controls.BACK) exit();
|
||||
|
||||
if (!isEnding && ((!lossSFX.playing) || (character.getAnimName() == "firstDeath" && character.isAnimFinished())) && (FlxG.sound.music == null || !FlxG.sound.music.playing))
|
||||
{
|
||||
@@ -193,6 +171,25 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||
});
|
||||
}
|
||||
|
||||
function exit()
|
||||
{
|
||||
var event = new CancellableEvent();
|
||||
gameoverScript.call('onReturnToMenu', [event]);
|
||||
|
||||
if (event.cancelled)
|
||||
return;
|
||||
|
||||
if (PlayState.chartingMode && Charter.undos.unsaved) game.saveWarn(false);
|
||||
else {
|
||||
if (Charter.instance != null) Charter.instance.__clearStatics();
|
||||
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
FlxG.sound.music = null;
|
||||
|
||||
FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState());
|
||||
}
|
||||
}
|
||||
|
||||
override function destroy()
|
||||
{
|
||||
gameoverScript.call("destroy");
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
package funkin.game;
|
||||
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.backend.scripting.events.healthicon.HealthIconChangeEvent;
|
||||
|
||||
class HealthIcon extends FlxSprite
|
||||
class HealthIcon extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* Used for FreeplayState! If you use it elsewhere, prob gonna annoying
|
||||
* Attaches the icon to a sprite, following it's position
|
||||
*/
|
||||
public var sprTracker:FlxSprite;
|
||||
/**
|
||||
* Where to place the icon in relation to the sprite
|
||||
*
|
||||
* LEFT: Left of the sprite
|
||||
*
|
||||
* CENTER: Center of the sprite
|
||||
*
|
||||
* RIGHT: Right of the sprite
|
||||
*/
|
||||
public var sprTrackerAlignment:TrackerAlignment = RIGHT;
|
||||
/**
|
||||
* Offset of the icon in relation to the sprite
|
||||
*
|
||||
* By default it is set to (10, -30) and is intended to be used with Alphabet
|
||||
*/
|
||||
public var sprTrackerOffset:FlxPoint = new FlxPoint(10, -30);
|
||||
|
||||
/**
|
||||
* The currently showing icon
|
||||
@@ -20,31 +40,60 @@ class HealthIcon extends FlxSprite
|
||||
|
||||
/**
|
||||
* Health steps in this format:
|
||||
* Min Percentage => Frame Index
|
||||
*
|
||||
* Min Percentage => Frame Index / Animation Name
|
||||
*/
|
||||
public var healthSteps:Map<Int, Int> = null;
|
||||
public var healthSteps:Map<Int, OneOfTwo<String, Int>> = null;
|
||||
|
||||
/**
|
||||
* current animation state
|
||||
* Current animation state
|
||||
*/
|
||||
public var curAnimState:Int = -1;
|
||||
public var curAnimState:OneOfTwo<String, Int> = -1;
|
||||
|
||||
/**
|
||||
* The Default Scale For The Icon
|
||||
*
|
||||
* This is what scale the icon should return to when its bump animation is finished
|
||||
*/
|
||||
public var defaultScale:Float = 1;
|
||||
|
||||
/**
|
||||
* Whenever or not the icon is animated or not
|
||||
*/
|
||||
public var animated:Bool = false;
|
||||
|
||||
/**
|
||||
* XML Animated Icon data
|
||||
*
|
||||
* `null` if the icon is not animated or its invalid
|
||||
*/
|
||||
public var xmlData:Xml;
|
||||
|
||||
/**
|
||||
* Extra offsets to add when updating the hitbox
|
||||
*/
|
||||
public var extraOffsets:FlxPoint = FlxPoint.get();
|
||||
|
||||
/**
|
||||
* Helper for HScript who can't make maps
|
||||
* @param steps Something like this: `[[0, 1], [20, 0]]`
|
||||
*
|
||||
* THIS IS DEPRECATED AND WILL BE REMOVED IN THE FUTURE
|
||||
*
|
||||
* Please set the healthSteps directly in the script instead, hscript does support maps
|
||||
*
|
||||
* @param steps Something like this: `[[0, 1], [20, 0]]` or `[[0, "losing"], [20, "neutral"]]` for animated icons
|
||||
*/
|
||||
public function setHealthSteps(steps:Array<Array<Int>>) { // helper for hscript that can't do maps
|
||||
@:dox(hide) @:noCompletion @:deprecated("use healthSteps instead")
|
||||
public function setHealthSteps(steps:Array<Array<OneOfTwo<String, Int>>>):Void { // helper for hscript that can't do maps
|
||||
if (steps == null) return;
|
||||
healthSteps = [];
|
||||
for(s in steps)
|
||||
for (s in steps)
|
||||
if (s.length > 1)
|
||||
healthSteps[s[0]] = s[1];
|
||||
var am = 0;
|
||||
for(k=>e in healthSteps) am++;
|
||||
|
||||
if (am <= 0) healthSteps = [
|
||||
0 => 1, // losing icon
|
||||
20 => 0, // normal icon
|
||||
if (Lambda.count(healthSteps) <= 0) healthSteps = [
|
||||
0 => (this.animated ? "losing" : 1), // losing icon
|
||||
20 => (this.animated ? "neutral" : 0), // normal icon
|
||||
];
|
||||
}
|
||||
|
||||
@@ -58,50 +107,357 @@ class HealthIcon extends FlxSprite
|
||||
scrollFactor.set();
|
||||
}
|
||||
|
||||
public function setIcon(char:String, width:Int = 150, height:Int = 150) {
|
||||
if(curCharacter != char || this.width != width || this.height != height) {
|
||||
curCharacter = char;
|
||||
var path = Paths.image('icons/$char');
|
||||
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
|
||||
|
||||
loadGraphic(path, true, width, height);
|
||||
|
||||
animation.add(char, [for(i in 0...frames.frames.length) i], 0, false, isPlayer);
|
||||
antialiasing = true;
|
||||
animation.play(char);
|
||||
|
||||
healthSteps = [
|
||||
0 => 1, // losing icon
|
||||
20 => 0, // normal icon
|
||||
];
|
||||
|
||||
if (frames.frames.length >= 3)
|
||||
healthSteps[80] = 2; // winning icon
|
||||
}
|
||||
/**
|
||||
* Called every beat, and causes the icon to become bigger
|
||||
**/
|
||||
public dynamic function bump():Void {
|
||||
var iconScale = Flags.BOP_ICON_SCALE;
|
||||
scale.set(defaultScale * iconScale, defaultScale * iconScale);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
/**
|
||||
* Called every frame and causes the icon to become smaller
|
||||
**/
|
||||
public dynamic function updateBump():Void {
|
||||
var iconLerp = Flags.ICON_LERP;
|
||||
scale.set(CoolUtil.fpsLerp(scale.x, defaultScale, iconLerp), CoolUtil.fpsLerp(scale.y, defaultScale, iconLerp));
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon to the specified character
|
||||
* @param char Character to set the icon to
|
||||
* @param allowAnimated Whenever the icon can be animated
|
||||
**/
|
||||
public function setIcon(char:String, allowAnimated:Bool = true):Void {
|
||||
if (curCharacter == char) return;
|
||||
|
||||
var oldIconPath = 'icons/$char';
|
||||
var newIconPath = 'icons/$char/icon';
|
||||
|
||||
if (!Assets.exists(Paths.image(oldIconPath)) && !Assets.exists(Paths.image(newIconPath))) {
|
||||
char = 'face';
|
||||
oldIconPath = 'icons/$char';
|
||||
newIconPath = 'icons/$char/icon';
|
||||
}
|
||||
curCharacter = char;
|
||||
|
||||
var iconPath = Assets.exists(Paths.image(oldIconPath)) ? oldIconPath : newIconPath;
|
||||
var iconXmlPath = Paths.getPath('images/icons/$char/data.xml');
|
||||
var iconFoundData = Assets.exists(iconXmlPath);
|
||||
|
||||
try {
|
||||
xmlData = iconFoundData ? Xml.parse(Assets.getText(iconXmlPath)) : null;
|
||||
} catch(e) {
|
||||
Logs.trace('Error while parsing icon data for $char: ${e.message}', ERROR);
|
||||
xmlData = null;
|
||||
}
|
||||
var xmlValid = xmlData != null && (xmlData = xmlData.firstElement()) != null;
|
||||
|
||||
this.animated = allowAnimated && iconFoundData && xmlValid && Paths.framesExists(Paths.image(newIconPath), true, true, true);
|
||||
|
||||
var iconAmt:Int = 0;
|
||||
var iconSize:Int = 0;
|
||||
var iconIsPlayer = xmlValid ? xmlData.get("facing").getDefault("right").toLowerCase() == "left" : false;
|
||||
|
||||
animateAtlas = null; // reset
|
||||
if (this.animated)
|
||||
loadSprite(Paths.image(newIconPath));
|
||||
else {
|
||||
var iconAsset:FlxGraphic = FlxG.bitmap.add(Paths.image(iconPath));
|
||||
var assetW:Float = iconAsset.width;
|
||||
var assetH:Float = iconAsset.height;
|
||||
|
||||
iconAmt = Math.round(assetW / assetH); // Just in case the icon is in a weird aspect ratio
|
||||
iconSize = Math.floor(assetW / iconAmt);
|
||||
if (iconSize * iconAmt > assetW) {
|
||||
iconSize = Math.floor(assetW / iconAmt);
|
||||
iconAmt = Math.floor(assetW / iconSize);
|
||||
}
|
||||
|
||||
loadGraphic(iconAsset, true, Std.int(Math.min(iconSize, assetW)), Std.int(Math.min(iconSize, assetH)));
|
||||
|
||||
setGraphicSize(150);
|
||||
updateHitbox();
|
||||
|
||||
animation.add(char, [for(i in 0...iconAmt) i], 0, false, isPlayer != iconIsPlayer);
|
||||
animation.play(char);
|
||||
}
|
||||
|
||||
if(!animation.onFinishEnd.has(animFinishCallback))
|
||||
animation.onFinishEnd.add(animFinishCallback);
|
||||
if(animateAtlas != null && !animateAtlas.anim.onFinishEnd.has(animFinishCallback))
|
||||
animateAtlas.anim.onFinishEnd.add(animFinishCallback);
|
||||
|
||||
var parsedSteps:Map<Int, String> = [];
|
||||
|
||||
antialiasing = true;
|
||||
if (xmlValid) {
|
||||
if (xmlData.exists("antialiasing"))
|
||||
antialiasing = xmlData.get("antialiasing").toLowerCase() == "true";
|
||||
if (xmlData.exists("offsetX"))
|
||||
extraOffsets.x = Std.parseFloat(xmlData.get("offsetX")).getDefault(0);
|
||||
if (xmlData.exists("offsetY"))
|
||||
extraOffsets.y = Std.parseFloat(xmlData.get("offsetY")).getDefault(0);
|
||||
|
||||
for (node in xmlData.elements())
|
||||
switch(node.nodeName) {
|
||||
case "transition":
|
||||
if (this.animated == false) {
|
||||
Logs.trace('Icon ${char} data <transition> is not allowed when not animated', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("anim")) {
|
||||
Logs.trace('Icon ${char} data <transition> is missing anim', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("to")) {
|
||||
Logs.trace('Icon ${char} data <transition> is missing to', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("from")) {
|
||||
Logs.trace('Icon ${char} data <transition> is missing from', WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
var animName = 'from-${node.get("from")}-to-${node.get("to")}';
|
||||
|
||||
var offsetX:Float = 0;
|
||||
var offsetY:Float = 0;
|
||||
if (node.exists("offsetX"))
|
||||
offsetX = Std.parseFloat(node.get("offsetX")).getDefault(0);
|
||||
else if (node.exists("offsetx"))
|
||||
offsetX = Std.parseFloat(node.get("offsetx")).getDefault(0);
|
||||
|
||||
if (node.exists("offsetY"))
|
||||
offsetY = Std.parseFloat(node.get("offsetY")).getDefault(0);
|
||||
else if (node.exists("offsety"))
|
||||
offsetY = Std.parseFloat(node.get("offsety")).getDefault(0);
|
||||
|
||||
addOffset(animName, offsetX, offsetY);
|
||||
|
||||
addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), false); // don't allow looping for transitions
|
||||
if (animateAtlas == null && animation.exists(animName))
|
||||
animation.getByName(animName).flipX = isPlayer != iconIsPlayer;
|
||||
case "anim":
|
||||
if (this.animated == false) {
|
||||
Logs.trace('Icon ${char} data <anim> is not allowed when not animated', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("name")) {
|
||||
Logs.trace('Icon ${char} data <anim> is missing name', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("anim")) {
|
||||
Logs.trace('Icon ${char} data <anim> is missing anim', WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
var animName = node.get("name");
|
||||
|
||||
var offsetX:Float = 0;
|
||||
var offsetY:Float = 0;
|
||||
if (node.exists("offsetX"))
|
||||
offsetX = Std.parseFloat(node.get("offsetX")).getDefault(0);
|
||||
else if (node.exists("offsetx"))
|
||||
offsetX = Std.parseFloat(node.get("offsetx")).getDefault(0);
|
||||
|
||||
if (node.exists("offsetY"))
|
||||
offsetY = Std.parseFloat(node.get("offsetY")).getDefault(0);
|
||||
else if (node.exists("offsety"))
|
||||
offsetY = Std.parseFloat(node.get("offsety")).getDefault(0);
|
||||
|
||||
addOffset(animName, offsetX, offsetY);
|
||||
|
||||
var looped:Bool = false;
|
||||
if (node.exists("looped"))
|
||||
looped = node.get("looped").toLowerCase() == "true";
|
||||
else if (node.exists("loop"))
|
||||
looped = node.get("loop").toLowerCase() == "true";
|
||||
|
||||
addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), looped);
|
||||
if (animateAtlas == null && animation.exists(animName))
|
||||
animation.getByName(animName).flipX = isPlayer != iconIsPlayer;
|
||||
case "step":
|
||||
if (!node.exists("percent")) {
|
||||
Logs.trace('Icon ${char} data <step> is missing percent', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("name")) {
|
||||
Logs.trace('Icon ${char} data <step> is missing name', WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedSteps.set(Std.parseInt(node.get("percent")).getDefault(0), node.get("name"));
|
||||
}
|
||||
}
|
||||
|
||||
if (Lambda.count(parsedSteps) > 0) {
|
||||
healthSteps = parsedSteps;
|
||||
} else {
|
||||
var hasLosing = this.animated ? hasAnim("losing") : iconAmt >= 2;
|
||||
var hasWinning = this.animated ? hasAnim("winning") : iconAmt >= 3;
|
||||
|
||||
if (hasLosing) {
|
||||
healthSteps = [
|
||||
0 => (this.animated ? "losing" : 1), // losing icon
|
||||
20 => (this.animated ? "neutral" : 0), // normal icon
|
||||
];
|
||||
} else {
|
||||
healthSteps = [
|
||||
0 => (this.animated ? "neutral" : 0), // normal icon
|
||||
];
|
||||
}
|
||||
if (hasWinning)
|
||||
healthSteps.set(80, this.animated ? "winning" : 2); // winning icon
|
||||
}
|
||||
var data = getIconAnim(health);
|
||||
if (data.isValid) {
|
||||
if (this.animated)
|
||||
playAnim(data.animState);
|
||||
else
|
||||
animation.curAnim.curFrame = data.animState;
|
||||
curAnimState = data.animState;
|
||||
}
|
||||
|
||||
if (animateAtlas != null) {
|
||||
@:bypassAccessor
|
||||
frameWidth = 150;
|
||||
@:bypassAccessor
|
||||
frameHeight = 150;
|
||||
extraOffsets.x -= frameWidth / 2;
|
||||
extraOffsets.y -= frameHeight / 2;
|
||||
updateHitbox();
|
||||
} else {
|
||||
setGraphicSize(150);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
defaultScale = (xmlValid && xmlData.exists("scale")) ? Std.parseFloat(xmlData.get("scale")).getDefault(scale.x) : scale.x;
|
||||
scale.set(defaultScale, defaultScale);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
var normalizedNames = ["neutral", "losing", "winning"];
|
||||
private function normalizeAnim(anim:OneOfTwo<String, Int>):OneOfTwo<String, Int> {
|
||||
if(this.animated) {
|
||||
if (anim is Int) {
|
||||
var _:Int = cast anim;
|
||||
if(_ >= 0 && _ < normalizedNames.length)
|
||||
anim = normalizedNames[anim];
|
||||
}
|
||||
} else {
|
||||
if (anim is String) {
|
||||
var _ = normalizedNames.indexOf(cast anim);
|
||||
if(_ >= 0)
|
||||
anim = _;
|
||||
}
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the animation data for a specific health percentage
|
||||
* @param health Health percentage
|
||||
* @return Animation data (-1 if invalid)
|
||||
*/
|
||||
public dynamic function getIconAnim(health:Float):IconAnimData {
|
||||
var i:OneOfTwo<String, Int> = -1;
|
||||
var oldKey:Int = -1;
|
||||
var isValid = false;
|
||||
for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) {
|
||||
oldKey = k;
|
||||
i = icon;
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
i = normalizeAnim(i);
|
||||
|
||||
return new IconAnimData(i, isValid);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void {
|
||||
super.update(elapsed);
|
||||
|
||||
if (sprTracker != null)
|
||||
setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30);
|
||||
if (sprTracker != null) {
|
||||
setPosition(sprTracker.x + (switch sprTrackerAlignment {
|
||||
case LEFT: -width;
|
||||
case CENTER: (sprTracker.width - width) / 2;
|
||||
case RIGHT: sprTracker.width;
|
||||
}) + sprTrackerOffset.x, sprTracker.y + sprTrackerOffset.y);
|
||||
}
|
||||
|
||||
if (animation.curAnim != null) {
|
||||
var i:Int = -1;
|
||||
var oldKey:Int = -1;
|
||||
for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) {
|
||||
oldKey = k;
|
||||
i = icon;
|
||||
}
|
||||
if (i >= 0 && curAnimState != i) {
|
||||
var event = EventManager.get(funkin.backend.scripting.events.healthicon.HealthIconChangeEvent).recycle(i, this);
|
||||
if (animation.curAnim != null || (this.animated && animateAtlas != null)) {
|
||||
var data = getIconAnim(health);
|
||||
var localAnimState = data.animState;
|
||||
|
||||
if (data.isValid && curAnimState != localAnimState) {
|
||||
var event = EventManager.get(HealthIconChangeEvent).recycle(localAnimState, this);
|
||||
funkin.backend.scripting.GlobalScript.event("onHealthIconAnimChange", event);
|
||||
if (!event.cancelled)
|
||||
animation.curAnim.curFrame = event.amount;
|
||||
if (!event.cancelled) {
|
||||
if (this.animated) {
|
||||
var transAnim = 'from-$curAnimState-to-${event.anim}';
|
||||
playAnim(hasAnim(transAnim) ? transAnim : event.anim);
|
||||
} else {
|
||||
if(animation.curAnim != null)
|
||||
animation.curAnim.curFrame = event.anim;
|
||||
}
|
||||
}
|
||||
|
||||
curAnimState = event.amount;
|
||||
curAnimState = localAnimState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function animFinishCallback(anim:String):Void {
|
||||
if (this.animated)
|
||||
if (anim.startsWith("from-"))
|
||||
playAnim(anim.substr(anim.lastIndexOf('-') + 1));
|
||||
}
|
||||
|
||||
override function updateHitbox():Void {
|
||||
super.updateHitbox();
|
||||
offset += extraOffsets;
|
||||
}
|
||||
}
|
||||
|
||||
@:dox(hide)
|
||||
class IconAnimData {
|
||||
public var animState:OneOfTwo<String, Int>;
|
||||
public var isValid:Bool;
|
||||
|
||||
public function new(animState:OneOfTwo<String, Int>, isValid:Bool) {
|
||||
this.animState = animState;
|
||||
this.isValid = isValid;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return '$animState (Valid: $isValid)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for `funkin.game.HealthIcon.sprTrackerAlignment`.
|
||||
* This determines the position of the icon in relation to the sprite tracker.
|
||||
**/
|
||||
enum abstract TrackerAlignment(Int) {
|
||||
/**
|
||||
* Left of the sprite tracker
|
||||
*
|
||||
* Mathematically: `tracker.x - icon.width`
|
||||
*/
|
||||
var LEFT = 0;
|
||||
/**
|
||||
* Center of the sprite tracker
|
||||
*
|
||||
* Mathematically: `tracker.x + (tracker.width - icon.width) / 2`
|
||||
*/
|
||||
var CENTER = 1;
|
||||
/**
|
||||
* Right of the sprite tracker
|
||||
*
|
||||
* Mathematically: `tracker.x + tracker.width`
|
||||
*/
|
||||
var RIGHT = 2;
|
||||
}
|
||||
+23
-22
@@ -75,6 +75,11 @@ class Note extends FlxSprite
|
||||
public var scrollSpeed:Null<Float> = null;
|
||||
public var noteAngle:Null<Float> = null;
|
||||
|
||||
public var copyStrumAngle:Bool = true;
|
||||
public var updateNotesPosX:Bool = true;
|
||||
public var updateNotesPosY:Bool = true;
|
||||
public var updateFlipY:Bool = true;
|
||||
|
||||
public var noteType(get, never):String;
|
||||
|
||||
@:dox(hide) public var __strumCameras:Array<FlxCamera> = null;
|
||||
@@ -146,7 +151,9 @@ class Note extends FlxSprite
|
||||
case 0:
|
||||
animation.addByPrefix('scroll', 'purple0');
|
||||
animation.addByPrefix('hold', 'purple hold piece');
|
||||
animation.addByPrefix('holdend', 'pruple end hold');
|
||||
animation.addByPrefix("holdend", "pruple end hold");
|
||||
if (animation.exists("holdend") != true) // null or false
|
||||
animation.addByPrefix('holdend', 'purple hold end');
|
||||
case 1:
|
||||
animation.addByPrefix('scroll', 'blue0');
|
||||
animation.addByPrefix('hold', 'blue hold piece');
|
||||
@@ -191,7 +198,7 @@ class Note extends FlxSprite
|
||||
}
|
||||
|
||||
public var lastScrollSpeed:Null<Float> = null;
|
||||
public var gapFix:Single = 0;
|
||||
public var gapFix:SingleOrFloat = 0;
|
||||
public var useAntialiasingFix(get, set):Bool;
|
||||
inline function set_useAntialiasingFix(v:Bool) {
|
||||
if(v != useAntialiasingFix) {
|
||||
@@ -211,7 +218,7 @@ class Note extends FlxSprite
|
||||
|
||||
override function drawComplex(camera:FlxCamera) {
|
||||
var downscrollCam = (camera is HudCamera ? ({var _:HudCamera=cast camera;_;}).downscroll : false);
|
||||
flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
|
||||
if (updateFlipY) flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
|
||||
if (downscrollCam) {
|
||||
frameOffset.y += __notePosFrameOffset.y * 2;
|
||||
super.drawComplex(camera);
|
||||
@@ -267,30 +274,22 @@ class Note extends FlxSprite
|
||||
public function updateSustain(strum:Strum) {
|
||||
var scrollSpeed = strum.getScrollSpeed(this);
|
||||
|
||||
var len = 0.45 * CoolUtil.quantize(scrollSpeed, 100);
|
||||
|
||||
if (nextSustain != null && lastScrollSpeed != scrollSpeed) {
|
||||
// is long sustain
|
||||
if (lastScrollSpeed != scrollSpeed) {
|
||||
lastScrollSpeed = scrollSpeed;
|
||||
|
||||
scale.y = (sustainLength * len) / frameHeight;
|
||||
updateHitbox();
|
||||
scale.y += gapFix / frameHeight;
|
||||
if (nextSustain != null) {
|
||||
scale.y = (sustainLength * 0.45 * scrollSpeed) / frameHeight;
|
||||
updateHitbox();
|
||||
scale.y += gapFix / frameHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasGoodHit) return;
|
||||
var t = CoolUtil.bound((Conductor.songPosition - strumTime) / (height) * len, 0, 1);
|
||||
var swagRect = this.clipRect == null ? new FlxRect() : this.clipRect;
|
||||
swagRect.x = 0;
|
||||
swagRect.y = t * frameHeight;
|
||||
swagRect.width = frameWidth;
|
||||
swagRect.height = frameHeight;
|
||||
|
||||
setClipRect(swagRect);
|
||||
updateSustainClip();
|
||||
}
|
||||
|
||||
public inline function setClipRect(rect:FlxRect) {
|
||||
this.clipRect = rect;
|
||||
public function updateSustainClip() if (wasGoodHit) {
|
||||
var t = FlxMath.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1);
|
||||
var rect = clipRect == null ? FlxRect.get() : clipRect;
|
||||
clipRect = rect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@@ -306,5 +305,7 @@ class Note extends FlxSprite
|
||||
|
||||
public override function destroy() {
|
||||
super.destroy();
|
||||
|
||||
clipRect = FlxDestroyUtil.put(clipRect);
|
||||
}
|
||||
}
|
||||
|
||||
+278
-134
@@ -7,6 +7,7 @@ import flixel.math.FlxPoint;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.misc.VarTween;
|
||||
import flixel.ui.FlxBar;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSort;
|
||||
@@ -18,9 +19,9 @@ import funkin.backend.chart.EventsData;
|
||||
import funkin.backend.scripting.DummyScript;
|
||||
import funkin.backend.scripting.Script;
|
||||
import funkin.backend.scripting.ScriptPack;
|
||||
import funkin.backend.scripting.events.*;
|
||||
import funkin.backend.scripting.events.gameplay.*;
|
||||
import funkin.backend.scripting.events.note.*;
|
||||
import funkin.backend.scripting.events.*;
|
||||
import funkin.backend.system.Conductor;
|
||||
import funkin.backend.system.RotatingSpriteGroup;
|
||||
import funkin.editors.SaveWarning;
|
||||
@@ -29,7 +30,7 @@ import funkin.editors.charter.CharterSelection;
|
||||
import funkin.game.SplashHandler;
|
||||
import funkin.game.cutscenes.*;
|
||||
import funkin.menus.*;
|
||||
import funkin.menus.StoryMenuState.WeekData;
|
||||
import funkin.backend.week.WeekData;
|
||||
import funkin.savedata.FunkinSave;
|
||||
import haxe.io.Path;
|
||||
|
||||
@@ -69,15 +70,15 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public static var fromMods:Bool = false;
|
||||
/**
|
||||
* Whenever Charting Mode has been enabled for this song.
|
||||
* Whenever Charting Mode is enabled for this song.
|
||||
*/
|
||||
public static var chartingMode:Bool = false;
|
||||
/**
|
||||
* Whenever the song has been started with opponent mode on.
|
||||
* Whenever the song is started with opponent mode on.
|
||||
*/
|
||||
public static var opponentMode:Bool = Flags.DEFAULT_OPPONENT_MODE;
|
||||
/**
|
||||
* Whenever the song has been started with co-op mode on.
|
||||
* Whenever the song is started with co-op mode on.
|
||||
*/
|
||||
public static var coopMode:Bool = Flags.DEFAULT_COOP_MODE;
|
||||
|
||||
@@ -138,8 +139,8 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public var downscroll(get, set):Bool;
|
||||
|
||||
@:dox(hide) private function set_downscroll(v:Bool) {return camHUD.downscroll = v;}
|
||||
@:dox(hide) private function get_downscroll():Bool {return camHUD.downscroll;}
|
||||
private inline function set_downscroll(v:Bool) return camHUD.downscroll = v;
|
||||
private inline function get_downscroll():Bool return camHUD.downscroll;
|
||||
|
||||
/**
|
||||
* Instrumental sound (Inst.ogg).
|
||||
@@ -225,23 +226,6 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public var doIconBop:Bool = true;
|
||||
|
||||
/**
|
||||
* Whenever cam zooming is enabled, enables on a note hit if not cancelled.
|
||||
*/
|
||||
public var camZooming:Bool = false;
|
||||
/**
|
||||
* Interval of cam zooming (beats).
|
||||
* For example: if set to 4, the camera will zoom every 4 beats.
|
||||
*/
|
||||
public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL;
|
||||
/**
|
||||
* How strong the cam zooms should be (defaults to 1)
|
||||
*/
|
||||
public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH;
|
||||
/**
|
||||
* Maximum amount of zoom for the camera.
|
||||
*/
|
||||
public var maxCamZoom:Float = Flags.MAX_CAMERA_ZOOM;
|
||||
/**
|
||||
* Current song name (lowercase)
|
||||
*/
|
||||
@@ -263,12 +247,12 @@ class PlayState extends MusicBeatState
|
||||
/**
|
||||
* Current health. Goes from 0 to maxHealth (defaults to 2)
|
||||
*/
|
||||
public var health:Float = 1;
|
||||
public var health(default, set):Float = 1;
|
||||
|
||||
/**
|
||||
* Maximum health the player can have. Defaults to 2.
|
||||
*/
|
||||
@:isVar public var maxHealth(get, set):Float = Flags.DEFAULT_MAX_HEALTH;
|
||||
public var maxHealth(default, set):Float = Flags.DEFAULT_MAX_HEALTH;
|
||||
/**
|
||||
* Current combo.
|
||||
*/
|
||||
@@ -304,6 +288,11 @@ class PlayState extends MusicBeatState
|
||||
* Opponent's icon
|
||||
*/
|
||||
public var iconP2:HealthIcon;
|
||||
/**
|
||||
* Every active icon that will be updated during gameplay (defaults to `iconP1` and `iconP1` between `create` and `postCreate` in scripts)
|
||||
*/
|
||||
public var iconArray:Array<HealthIcon> = [];
|
||||
|
||||
/**
|
||||
* Camera for the HUD (notes, misses).
|
||||
*/
|
||||
@@ -383,6 +372,30 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public var camHUDZoomLerp:Float = Flags.DEFAULT_HUD_ZOOM_LERP;
|
||||
|
||||
/**
|
||||
* Whenever cam zooming is enabled, enables on a note hit if not cancelled.
|
||||
*/
|
||||
public var camZooming:Bool = false;
|
||||
/**
|
||||
* Interval of cam zooming (beats).
|
||||
* For example: if set to 4, the camera will zoom every 4 beats.
|
||||
*/
|
||||
public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL;
|
||||
/**
|
||||
* How strong the cam zooms should be (defaults to 1)
|
||||
*/
|
||||
public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH;
|
||||
/**
|
||||
* Default multiplier for `maxCamZoom`.
|
||||
*/
|
||||
public var maxCamZoomMult:Float = Flags.MAX_CAMERA_ZOOM_MULT;
|
||||
/**
|
||||
* Maximum amount of zoom for the camera (based on `maxCamZoomMult` and the camera's zoom IF not set).
|
||||
*/
|
||||
public var maxCamZoom(get, default):Float = Math.NaN;
|
||||
|
||||
private inline function get_maxCamZoom() return Math.isNaN(maxCamZoom) ? maxCamZoomMult * defaultCamZoom : maxCamZoom;
|
||||
|
||||
/**
|
||||
* Zoom for the pixel assets.
|
||||
*/
|
||||
@@ -532,10 +545,10 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public function updateRating() {
|
||||
var rating = null;
|
||||
var acc = get_accuracy();
|
||||
var acc = accuracy; // caching since it has a getter with an operation - Nex
|
||||
|
||||
for(e in comboRatings)
|
||||
if (e.percent <= acc && (rating == null || rating.percent < e.percent))
|
||||
if (comboRatings != null && comboRatings.length > 0) for (e in comboRatings)
|
||||
if ((e.percent <= acc && e.maxMisses >= misses) && (rating == null || (rating.percent < e.percent && e.maxMisses >= misses)))
|
||||
rating = e;
|
||||
|
||||
var event = gameAndCharsEvent("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating));
|
||||
@@ -543,13 +556,13 @@ class PlayState extends MusicBeatState
|
||||
curRating = event.rating;
|
||||
}
|
||||
|
||||
private inline function get_maxHealth()
|
||||
return this.maxHealth;
|
||||
private function set_maxHealth(v:Float) {
|
||||
if (healthBar != null && healthBar.max == this.maxHealth) {
|
||||
healthBar.setRange(healthBar.min, v);
|
||||
}
|
||||
return this.maxHealth = v;
|
||||
private inline function set_health(v:Float)
|
||||
return health = FlxMath.bound(v, 0, maxHealth);
|
||||
private inline function set_maxHealth(v:Float) {
|
||||
if (healthBar != null && healthBar.max == maxHealth) healthBar.setRange(healthBar.min, v);
|
||||
maxHealth = v;
|
||||
health = health; // running the setter - Nex
|
||||
return maxHealth;
|
||||
}
|
||||
|
||||
private inline function get_curStage()
|
||||
@@ -581,6 +594,7 @@ class PlayState extends MusicBeatState
|
||||
@:dox(hide) override public function create()
|
||||
{
|
||||
Note.__customNoteTypeExists = [];
|
||||
|
||||
// SCRIPTING & DATA INITIALIZATION
|
||||
#if REGION
|
||||
instance = this;
|
||||
@@ -609,8 +623,8 @@ class PlayState extends MusicBeatState
|
||||
detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay";
|
||||
|
||||
// Checks if cutscene files exists
|
||||
var cutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene');
|
||||
var endCutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene-end');
|
||||
var cutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene');
|
||||
var endCutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene-end');
|
||||
if (Assets.exists(cutscenePath)) cutscene = cutscenePath;
|
||||
if (Assets.exists(endCutscenePath)) endCutscene = endCutscenePath;
|
||||
|
||||
@@ -637,10 +651,11 @@ class PlayState extends MusicBeatState
|
||||
// case "":
|
||||
// ADD YOUR HARDCODED SCRIPTS HERE!
|
||||
default:
|
||||
var scriptsFolders:Array<String> = ['songs/${SONG.meta.name.toLowerCase()}/scripts', 'data/charts/', 'songs/'];
|
||||
var normal = 'songs/${SONG.meta.name}/scripts';
|
||||
var scriptsFolders:Array<String> = [normal, normal + '/$difficulty/', 'data/charts/', 'songs/'];
|
||||
|
||||
for(folder in scriptsFolders) {
|
||||
for(file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) {
|
||||
for (folder in scriptsFolders) {
|
||||
for (file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) {
|
||||
if (folder == 'data/charts/')
|
||||
Logs.warn('data/charts/ is deprecated and will be removed in the future. Please move script $file to songs/', DARKYELLOW, "PlayState");
|
||||
|
||||
@@ -701,7 +716,7 @@ class PlayState extends MusicBeatState
|
||||
case 2: "girlfriend";
|
||||
}) : strumLine.position;
|
||||
if (strumLine.characters != null) for(k=>charName in strumLine.characters) {
|
||||
var char = new Character(0, 0, charName, stage.isCharFlipped(charPosName, strumLine.type == 1));
|
||||
var char = new Character(0, 0, charName, stage.isCharFlipped(stage.characterPoses[charName] != null ? charName : charPosName, strumLine.type == 1));
|
||||
stage.applyCharStuff(char, charPosName, k);
|
||||
chars.push(char);
|
||||
}
|
||||
@@ -782,9 +797,9 @@ class PlayState extends MusicBeatState
|
||||
if (Flags.DEFAULT_HEALTH != null) health = Flags.DEFAULT_HEALTH;
|
||||
else health = maxHealth / 2;
|
||||
|
||||
iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true);
|
||||
iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false);
|
||||
for(icon in [iconP1, iconP2]) {
|
||||
iconArray.push(iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true));
|
||||
iconArray.push(iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false));
|
||||
for (icon in iconArray) {
|
||||
icon.y = healthBar.y - (icon.height / 2);
|
||||
add(icon);
|
||||
}
|
||||
@@ -801,7 +816,8 @@ class PlayState extends MusicBeatState
|
||||
scoreTxt.alignment = RIGHT;
|
||||
missesTxt.alignment = CENTER;
|
||||
accuracyTxt.alignment = LEFT;
|
||||
updateRatingStuff();
|
||||
if (updateRatingStuff != null)
|
||||
updateRatingStuff();
|
||||
|
||||
for(e in [healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt])
|
||||
e.cameras = [camHUD];
|
||||
@@ -838,7 +854,8 @@ class PlayState extends MusicBeatState
|
||||
|
||||
// Make icons appear in the correct spot during cutscenes
|
||||
healthBar.update(0);
|
||||
updateIconPositions();
|
||||
if (updateIconPositions != null)
|
||||
updateIconPositions();
|
||||
|
||||
__updateNote_event = EventManager.get(NoteUpdateEvent);
|
||||
|
||||
@@ -847,6 +864,7 @@ class PlayState extends MusicBeatState
|
||||
|
||||
/**
|
||||
* Function used to update Discord Presence.
|
||||
*
|
||||
* This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts.
|
||||
*/
|
||||
public dynamic function updateDiscordPresence()
|
||||
@@ -867,7 +885,7 @@ class PlayState extends MusicBeatState
|
||||
return;
|
||||
}
|
||||
|
||||
var songName = SONG.meta.name.toLowerCase();
|
||||
var songName = SONG.meta.name;
|
||||
|
||||
if (cutsceneScriptPath == null)
|
||||
cutsceneScriptPath = Paths.script('songs/$songName/${prefix}cutscene');
|
||||
@@ -1027,10 +1045,11 @@ class PlayState extends MusicBeatState
|
||||
Note.__customNoteTypeExists = [];
|
||||
}
|
||||
|
||||
public static function resetSongInfos() {
|
||||
deathCounter = 0;
|
||||
seenCutscene = false;
|
||||
}
|
||||
@:dox(hide) @:deprecated("scrollSpeedTween is deprecated, use eventsTween['scrollSpeed'] instead")
|
||||
public var scrollSpeedTween(get, set):FlxTween;
|
||||
private inline function get_scrollSpeedTween() return eventsTween.get("scrollSpeed");
|
||||
private inline function set_scrollSpeedTween(val:FlxTween) {eventsTween.set("scrollSpeed", val); return val;}
|
||||
// End of backwards compat
|
||||
|
||||
@:dox(hide) private function generateSong(?songData:ChartData):Void
|
||||
{
|
||||
@@ -1223,7 +1242,7 @@ class PlayState extends MusicBeatState
|
||||
if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, []));
|
||||
} else {
|
||||
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
|
||||
Sys.exit(0);
|
||||
openfl.system.System.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1236,7 +1255,7 @@ class PlayState extends MusicBeatState
|
||||
if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, []));
|
||||
} else {
|
||||
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
|
||||
Sys.exit(0);
|
||||
openfl.system.System.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1250,21 +1269,20 @@ class PlayState extends MusicBeatState
|
||||
]));
|
||||
}
|
||||
|
||||
function updateIconPositions() {
|
||||
dynamic function updateIconPositions() {
|
||||
var iconOffset = Flags.ICON_OFFSET;
|
||||
var healthBarPercent = healthBar.percent;
|
||||
|
||||
var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0);
|
||||
var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBarPercent, 0, 100, 1, 0);
|
||||
|
||||
iconP1.x = center - iconOffset;
|
||||
iconP2.x = center - (iconP2.width - iconOffset);
|
||||
|
||||
health = CoolUtil.bound(health, 0, maxHealth);
|
||||
|
||||
iconP1.health = healthBar.percent / 100;
|
||||
iconP2.health = 1 - (healthBar.percent / 100);
|
||||
iconP1.health = healthBarPercent / 100;
|
||||
iconP2.health = 1 - (healthBarPercent / 100);
|
||||
}
|
||||
|
||||
function updateRatingStuff() {
|
||||
dynamic function updateRatingStuff() {
|
||||
scoreTxt.text = 'Score:$songScore';
|
||||
missesTxt.text = '${comboBreaks ? "Combo Breaks" : "Misses"}:$misses';
|
||||
|
||||
@@ -1294,45 +1312,37 @@ class PlayState extends MusicBeatState
|
||||
return;
|
||||
}
|
||||
|
||||
updateRatingStuff();
|
||||
if (updateRatingStuff != null)
|
||||
updateRatingStuff();
|
||||
|
||||
if (controls.PAUSE && startedCountdown && canPause)
|
||||
pauseGame();
|
||||
if (canAccessDebugMenus && chartingMode && controls.DEV_ACCESS)
|
||||
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
|
||||
|
||||
if (canAccessDebugMenus) {
|
||||
if (chartingMode && FlxG.keys.justPressed.SEVEN) {
|
||||
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
|
||||
}
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
Logs.warn('Reloading scripts...', "PlayState");
|
||||
scripts.reload();
|
||||
Logs.warn('Song scripts successfully reloaded.', GREEN, "PlayState");
|
||||
}
|
||||
#end
|
||||
}
|
||||
if (doIconBop)
|
||||
for (icon in iconArray)
|
||||
if (icon.updateBump != null)
|
||||
icon.updateBump();
|
||||
|
||||
if (doIconBop) {
|
||||
var iconLerp = Flags.ICON_LERP;
|
||||
iconP1.scale.set(lerp(iconP1.scale.x, 1, iconLerp), lerp(iconP1.scale.y, 1, iconLerp));
|
||||
iconP2.scale.set(lerp(iconP2.scale.x, 1, iconLerp), lerp(iconP2.scale.y, 1, iconLerp));
|
||||
if (updateIconPositions != null)
|
||||
updateIconPositions();
|
||||
|
||||
iconP1.updateHitbox();
|
||||
iconP2.updateHitbox();
|
||||
}
|
||||
updateIconPositions();
|
||||
|
||||
if (startingSong)
|
||||
{
|
||||
if (startedCountdown)
|
||||
{
|
||||
if (startingSong) {
|
||||
if (startedCountdown) {
|
||||
Conductor.songPosition += Conductor.songOffset + elapsed * 1000;
|
||||
if (Conductor.songPosition >= 0)
|
||||
startSong();
|
||||
}
|
||||
} else if (FlxG.sound.music != null) {
|
||||
var instTime = FlxG.sound.music.time;
|
||||
var isOffsync = vocals.time != instTime || [for(strumLine in strumLines.members) strumLine.vocals.time != instTime].contains(true);
|
||||
var isOffsync:Bool = vocals.time != instTime;
|
||||
if(!isOffsync) {
|
||||
for(strumLine in strumLines.members) {
|
||||
if(strumLine.vocals.time != instTime) {
|
||||
isOffsync = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
__vocalOffsetViolation = Math.max(0, __vocalOffsetViolation + (isOffsync ? elapsed : -elapsed / 2));
|
||||
if (__vocalOffsetViolation > Flags.VOCAL_OFFSET_VIOLATION_THRESHOLD) {
|
||||
resyncVocals();
|
||||
@@ -1343,31 +1353,13 @@ class PlayState extends MusicBeatState
|
||||
while(events.length > 0 && events.last().time <= Conductor.songPosition)
|
||||
executeEvent(events.pop());
|
||||
|
||||
if (generatedMusic && strumLines.members[curCameraTarget] != null)
|
||||
{
|
||||
var pos = FlxPoint.get();
|
||||
var r = 0;
|
||||
for(c in strumLines.members[curCameraTarget].characters) {
|
||||
if (c == null || !c.visible) continue;
|
||||
var cpos = c.getCameraPosition();
|
||||
pos.x += cpos.x;
|
||||
pos.y += cpos.y;
|
||||
r++;
|
||||
//cpos.put();
|
||||
}
|
||||
if (r > 0) {
|
||||
pos.x /= r;
|
||||
pos.y /= r;
|
||||
if (controls.PAUSE && startedCountdown && canPause)
|
||||
pauseGame();
|
||||
|
||||
var event = gameAndCharsEvent("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r));
|
||||
if (!event.cancelled)
|
||||
camFollow.setPosition(pos.x, pos.y);
|
||||
}
|
||||
pos.put();
|
||||
}
|
||||
if (generatedMusic)
|
||||
moveCamera();
|
||||
|
||||
if (camZooming)
|
||||
{
|
||||
if (camZooming) {
|
||||
FlxG.camera.zoom = lerp(FlxG.camera.zoom, defaultCamZoom, camGameZoomLerp);
|
||||
camHUD.zoom = lerp(camHUD.zoom, defaultHudZoom, camHUDZoomLerp);
|
||||
}
|
||||
@@ -1401,7 +1393,52 @@ class PlayState extends MusicBeatState
|
||||
scripts.event("postDraw", e);
|
||||
}
|
||||
|
||||
public var scrollSpeedTween:FlxTween = null;
|
||||
public function moveCamera() if (strumLines.members[curCameraTarget] != null) {
|
||||
var data:CamPosData = getStrumlineCamPos(curCameraTarget);
|
||||
if (data.amount > 0) {
|
||||
var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(data.pos, strumLines.members[curCameraTarget], data.amount));
|
||||
if (!event.cancelled)
|
||||
camFollow.setPosition(event.position.x, event.position.y);
|
||||
}
|
||||
data.put();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the camera position of the specified strumline.
|
||||
* @param strumLine The strumline to get the camera position of.
|
||||
* @param pos The position to put the camera position in. If `null`, a new FlxPoint will be created.
|
||||
* @param ignoreInvisible Whenever invisible characters should be ignored.
|
||||
**/
|
||||
public inline function getStrumlineCamPos(strumLine:Int, ?pos:FlxPoint = null, ?ignoreInvisible:Bool = true):CamPosData {
|
||||
return getCharactersCamPos(strumLines.members[strumLine].characters, pos, ignoreInvisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the camera position of the specified characters.
|
||||
* @param chars The characters to get the camera position of.
|
||||
* @param pos The position to put the camera position in. If `null`, a new FlxPoint will be created.
|
||||
* @param ignoreInvisible Whenever invisible characters should be ignored.
|
||||
**/
|
||||
public dynamic function getCharactersCamPos(chars:Array<Character>, ?pos:FlxPoint = null, ?ignoreInvisible:Bool = true):CamPosData {
|
||||
if (pos == null) pos = FlxPoint.get();
|
||||
var amount = 0;
|
||||
for(c in chars) {
|
||||
if (c == null || (ignoreInvisible && !c.visible)) continue;
|
||||
var cpos = c.getCameraPosition();
|
||||
pos.x += cpos.x;
|
||||
pos.y += cpos.y;
|
||||
amount++;
|
||||
//cpos.put(); // not actually in the pool, so no need
|
||||
}
|
||||
if (amount > 0) {
|
||||
pos.x /= amount;
|
||||
pos.y /= amount;
|
||||
}
|
||||
return new CamPosData(pos, amount);
|
||||
}
|
||||
|
||||
|
||||
public var eventsTween:Map<String, FlxTween> = [];
|
||||
|
||||
public function executeEvent(event:ChartEvent) @:privateAccess {
|
||||
if (event == null || event.params == null) return;
|
||||
@@ -1413,12 +1450,92 @@ class PlayState extends MusicBeatState
|
||||
|
||||
switch(event.name) {
|
||||
case "HScript Call":
|
||||
scripts.call(event.params[0], event.params[1].split(','));
|
||||
var scriptPacks:Array<ScriptPack> = [scripts, stateScripts];
|
||||
for (strLine in strumLines.members) for (char in strLine.characters) scriptPacks.push(char.scripts);
|
||||
var args:Array<String> = event.params[1].split(',');
|
||||
|
||||
for (pack in scriptPacks) {
|
||||
pack.call(event.params[0], args);
|
||||
//public functions
|
||||
if (pack.publicVariables.exists(event.params[0])) {
|
||||
var func = pack.publicVariables.get(event.params[0]);
|
||||
if (func != null && Reflect.isFunction(func))
|
||||
Reflect.callMethod(null, func, args);
|
||||
}
|
||||
}
|
||||
|
||||
//static functions
|
||||
if (Script.staticVariables.exists(event.params[0])) {
|
||||
var func = Script.staticVariables.get(event.params[0]);
|
||||
if (func != null && Reflect.isFunction(func))
|
||||
Reflect.callMethod(null, func, args);
|
||||
}
|
||||
case "Camera Movement":
|
||||
var tween = eventsTween.get("cameraMovement");
|
||||
if (tween != null) {
|
||||
if (tween.onComplete != null) tween.onComplete(tween);
|
||||
tween.cancel();
|
||||
}
|
||||
|
||||
curCameraTarget = event.params[0];
|
||||
moveCamera();
|
||||
|
||||
if (strumLines.members[curCameraTarget] != null) {
|
||||
if (event.params[1] == false) FlxG.camera.snapToTarget();
|
||||
else if (event.params[3] != null && event.params[3] != "CLASSIC") { // making more nullchecks in this event because of the default save value being false - Nex
|
||||
var oldFollow = FlxG.camera.followEnabled;
|
||||
FlxG.camera.followEnabled = false;
|
||||
eventsTween.set("cameraMovement", FlxTween.tween(FlxG.camera.scroll, {x: camFollow.x - FlxG.camera.width * 0.5, y: camFollow.y - FlxG.camera.height * 0.5},
|
||||
(Conductor.stepCrochet / 1000) * (event.params[2] == null ? 4 : event.params[2]), {
|
||||
ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4]),
|
||||
onComplete: (_) -> FlxG.camera.followEnabled = oldFollow
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
case "Camera Position":
|
||||
var tween = eventsTween.get("cameraMovement");
|
||||
if (tween != null) {
|
||||
if (tween.onComplete != null) tween.onComplete(tween);
|
||||
tween.cancel();
|
||||
}
|
||||
|
||||
curCameraTarget = -1;
|
||||
var isOffset = event.params[6] == true;
|
||||
camFollow.setPosition(isOffset ? (camFollow.x + event.params[0]) : event.params[0], isOffset ? (camFollow.y + event.params[1]) : event.params[1]);
|
||||
|
||||
if (event.params[2] == false) FlxG.camera.snapToTarget();
|
||||
else if (event.params[4] != null && event.params[4] != "CLASSIC") {
|
||||
var oldFollow = FlxG.camera.followEnabled;
|
||||
FlxG.camera.followEnabled = false;
|
||||
eventsTween.set("cameraMovement", FlxTween.tween(FlxG.camera.scroll, {x: camFollow.x - FlxG.camera.width * 0.5, y: camFollow.y - FlxG.camera.height * 0.5},
|
||||
(Conductor.stepCrochet / 1000) * (event.params[3] == null ? 4 : event.params[3]), {
|
||||
ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]),
|
||||
onComplete: (_) -> FlxG.camera.followEnabled = oldFollow
|
||||
})
|
||||
);
|
||||
}
|
||||
case "Add Camera Zoom":
|
||||
var camera:FlxCamera = event.params[1] == "camHUD" ? camHUD : camGame;
|
||||
camera.zoom += event.params[0];
|
||||
case "Camera Zoom":
|
||||
var cam = event.params[2] == "camHUD" ? camHUD : camGame;
|
||||
var name = (event.params[2] == "camHUD" ? "camHUD" : "camGame") + ".zoom"; // avoiding having different values from these 2 - Nex
|
||||
var tween = eventsTween.get(name);
|
||||
if (tween != null) tween.cancel();
|
||||
|
||||
var finalZoom:Float = event.params[1] * (event.params[6] == 'direct' ? FlxCamera.defaultZoom : stage.defaultZoom);
|
||||
if (event.params[7] == true) finalZoom *= cam.zoom;
|
||||
|
||||
if (event.params[0] == false) {
|
||||
cam.zoom = finalZoom;
|
||||
if (cam == camHUD) defaultHudZoom = finalZoom;
|
||||
else defaultCamZoom = finalZoom;
|
||||
} else
|
||||
eventsTween.set(name, FlxTween.tween(cam, {zoom: finalZoom}, (Conductor.stepCrochet / 1000) * event.params[3], {ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]), onUpdate: function(_) {
|
||||
if (cam == camHUD) defaultHudZoom = cam.zoom;
|
||||
else defaultCamZoom = cam.zoom;
|
||||
}}));
|
||||
case "Camera Modulo Change":
|
||||
camZoomingInterval = event.params[0];
|
||||
camZoomingStrength = event.params[1];
|
||||
@@ -1431,12 +1548,16 @@ class PlayState extends MusicBeatState
|
||||
camera.flash(event.params[1], (Conductor.stepCrochet / 1000) * event.params[2], null, true);
|
||||
case "BPM Change": // automatically handled by conductor
|
||||
case "Scroll Speed Change":
|
||||
if (scrollSpeedTween != null) scrollSpeedTween.cancel();
|
||||
var tween = eventsTween.get("scrollSpeedTween");
|
||||
if (tween != null) tween.cancel();
|
||||
|
||||
var finalScroll:Float = event.params[1];
|
||||
if (event.params[5] == true) finalScroll *= scrollSpeed;
|
||||
|
||||
if (event.params[0] == false)
|
||||
scrollSpeed = event.params[1];
|
||||
scrollSpeed = finalScroll;
|
||||
else
|
||||
scrollSpeedTween = FlxTween.tween(this, {scrollSpeed: event.params[1]}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])});
|
||||
eventsTween.set("scrollSpeedTween", FlxTween.tween(this, {scrollSpeed: finalScroll}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])}));
|
||||
case "Alt Animation Toggle":
|
||||
var strLine = strumLines.members[event.params[2]];
|
||||
if (strLine != null) {
|
||||
@@ -1451,7 +1572,7 @@ class PlayState extends MusicBeatState
|
||||
case "Play Animation":
|
||||
if (strumLines.members[event.params[0]] != null && strumLines.members[event.params[0]].characters != null)
|
||||
for (char in strumLines.members[event.params[0]].characters)
|
||||
if (char != null) char.playAnim(event.params[1], event.params[2], null);
|
||||
if (char != null) char.playAnim(event.params[1], event.params[2], event.params[3] == "NONE" ? null : event.params[3]);
|
||||
case "Unknown": // nothing
|
||||
}
|
||||
}
|
||||
@@ -1506,6 +1627,7 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public function endSong():Void
|
||||
{
|
||||
endingSong = true;
|
||||
gameAndCharsCall("onSongEnd");
|
||||
canPause = false;
|
||||
inst.volume = 0;
|
||||
@@ -1546,8 +1668,6 @@ class PlayState extends MusicBeatState
|
||||
* Immediately switches to the next song, or goes back to the Story/Freeplay menu.
|
||||
*/
|
||||
public function nextSong() {
|
||||
resetSongInfos();
|
||||
|
||||
if (isStoryMode)
|
||||
{
|
||||
campaignScore += songScore;
|
||||
@@ -1581,7 +1701,7 @@ class PlayState extends MusicBeatState
|
||||
|
||||
FlxG.sound.music.stop();
|
||||
|
||||
__loadSong(storyPlaylist[0].toLowerCase(), difficulty);
|
||||
__loadSong(storyPlaylist[0], difficulty);
|
||||
|
||||
FlxG.switchState(new PlayState());
|
||||
}
|
||||
@@ -1711,9 +1831,9 @@ class PlayState extends MusicBeatState
|
||||
|
||||
var event:NoteHitEvent;
|
||||
if (strumLine != null && !strumLine.cpu)
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick");
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick", 0.5, true, 0.7, true, true, iconP1);
|
||||
else
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false);
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false, 0.5, true, 0.7, true, true, iconP2);
|
||||
event.deleteNote = !note.isSustainNote; // work around, to allow sustain notes to be deleted
|
||||
event = scripts.event(strumLine != null && !strumLine.cpu ? "onPlayerHit" : "onDadHit", event);
|
||||
strumLine.onHit.dispatch(event);
|
||||
@@ -1876,14 +1996,9 @@ class PlayState extends MusicBeatState
|
||||
}
|
||||
|
||||
if (doIconBop)
|
||||
{
|
||||
var iconScale = Flags.BOP_ICON_SCALE;
|
||||
iconP1.scale.set(iconScale, iconScale);
|
||||
iconP2.scale.set(iconScale, iconScale);
|
||||
|
||||
iconP1.updateHitbox();
|
||||
iconP2.updateHitbox();
|
||||
}
|
||||
for (icon in iconArray)
|
||||
if (icon.bump != null)
|
||||
icon.bump();
|
||||
|
||||
scripts.call("beatHit", [curBeat]);
|
||||
}
|
||||
@@ -1974,7 +2089,7 @@ class PlayState extends MusicBeatState
|
||||
public static function loadWeek(weekData:WeekData, ?difficulty:String) {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
storyWeek = weekData;
|
||||
storyPlaylist = [for(e in weekData.songs) e.name];
|
||||
storyPlaylist = [for (e in weekData.songs) e.name];
|
||||
isStoryMode = true;
|
||||
campaignScore = 0;
|
||||
campaignMisses = 0;
|
||||
@@ -2008,6 +2123,8 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public static function __loadSong(_name:String, _difficulty:String) {
|
||||
difficulty = _difficulty;
|
||||
seenCutscene = false;
|
||||
deathCounter = 0;
|
||||
|
||||
SONG = Chart.parse(_name, _difficulty);
|
||||
fromMods = SONG.fromMods;
|
||||
@@ -2018,8 +2135,10 @@ final class ComboRating {
|
||||
public var percent:Float;
|
||||
public var rating:String;
|
||||
public var color:FlxColor;
|
||||
public var maxMisses:Float; // Float since it could be Math.POSITIVE_INFINITY - Nex
|
||||
|
||||
public function new(percent:Float, rating:String, color:FlxColor) {
|
||||
public function new(?percent:Float, ?rating:String, ?color:FlxColor, ?misses:Float) {
|
||||
maxMisses = misses == null || Math.isNaN(misses) ? Math.POSITIVE_INFINITY : misses;
|
||||
this.percent = percent;
|
||||
this.rating = rating;
|
||||
this.color = color;
|
||||
@@ -2034,3 +2153,28 @@ typedef PlayStateTransitionData = {
|
||||
var camFollowY:Float;
|
||||
var camZoom:Float;
|
||||
}
|
||||
|
||||
class CamPosData {
|
||||
/**
|
||||
* The camera position.
|
||||
**/
|
||||
public var pos:FlxPoint;
|
||||
/**
|
||||
* The amount of characters that was involved in the calculation.
|
||||
**/
|
||||
public var amount:Int;
|
||||
|
||||
public function new(pos:FlxPoint, amount:Int) {
|
||||
this.pos = pos;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the position back into the pool, making it reusable.
|
||||
**/
|
||||
public function put() {
|
||||
if(pos == null) return;
|
||||
pos.put();
|
||||
pos = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package funkin.game;
|
||||
|
||||
class Splash extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* The current splash strum
|
||||
* WARNING: It can be null
|
||||
*/
|
||||
public var strum:Null<Strum>;
|
||||
|
||||
/**
|
||||
* Shortcut to `strum.ID`
|
||||
* WARNING: It can be null
|
||||
*/
|
||||
public var strumID:Null<Int>;
|
||||
|
||||
public static function copyFrom(source:Splash):Splash
|
||||
{
|
||||
var spr = new Splash();
|
||||
@:privateAccess {
|
||||
spr.setPosition(source.x, source.y);
|
||||
spr.frames = source.frames;
|
||||
if (source.animateAtlas != null && source.atlasPath != null)
|
||||
spr.loadSprite(source.atlasPath);
|
||||
spr.animation.copyFrom(source.animation);
|
||||
spr.visible = source.visible;
|
||||
spr.alpha = source.alpha;
|
||||
spr.antialiasing = source.antialiasing;
|
||||
spr.scale.set(source.scale.x, source.scale.y);
|
||||
spr.scrollFactor.set(source.scrollFactor.x, source.scrollFactor.y);
|
||||
spr.skew.set(source.skew.x, source.skew.y);
|
||||
spr.transformMatrix = source.transformMatrix;
|
||||
spr.matrixExposed = source.matrixExposed;
|
||||
spr.zoomFactor = source.zoomFactor;
|
||||
spr.animOffsets = source.animOffsets.copy();
|
||||
}
|
||||
return spr;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package funkin.game;
|
||||
|
||||
import haxe.xml.Access;
|
||||
|
||||
class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
class SplashGroup extends FlxTypedGroup<Splash> {
|
||||
/**
|
||||
* Whenever the splash group has successfully loaded or not.
|
||||
*/
|
||||
@@ -51,7 +51,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
var _antialiasing:Bool = true;
|
||||
|
||||
function createSplash(imagePath:String) {
|
||||
var splash = new FunkinSprite();
|
||||
var splash = new Splash();
|
||||
splash.antialiasing = true;
|
||||
splash.active = splash.visible = false;
|
||||
splash.loadSprite(Paths.image(imagePath));
|
||||
@@ -64,7 +64,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
return splash;
|
||||
}
|
||||
|
||||
function setupAnims(xml:Access, splash:FunkinSprite) {
|
||||
function setupAnims(xml:Access, splash:Splash) {
|
||||
for(strum in xml.nodes.strum) {
|
||||
var id:Null<Int> = Std.parseInt(strum.att.id);
|
||||
if (id != null) {
|
||||
@@ -93,12 +93,14 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
};
|
||||
}
|
||||
|
||||
function pregenerateSplashes(splash:FunkinSprite) {
|
||||
function pregenerateSplashes(splash:Splash) {
|
||||
// make 7 additional splashes
|
||||
for(i in 0...7) {
|
||||
var spr = FunkinSprite.copyFrom(splash);
|
||||
var spr = Splash.copyFrom(splash);
|
||||
spr.animation.finishCallback = function(name:String) {
|
||||
spr.active = spr.visible = false;
|
||||
spr.strum = null;
|
||||
spr.strumID = null;
|
||||
};
|
||||
add(spr);
|
||||
}
|
||||
@@ -112,11 +114,14 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
return animNames[FlxG.random.int(0, animNames.length - 1)];
|
||||
}
|
||||
|
||||
var __splash:FunkinSprite;
|
||||
var __splash:Splash;
|
||||
public function showOnStrum(strum:Strum) {
|
||||
if (!valid) return null;
|
||||
__splash = recycle();
|
||||
|
||||
__splash.strum = strum;
|
||||
__splash.strumID = strum.ID;
|
||||
|
||||
@:privateAccess
|
||||
__splash.scale.x = __splash.scale.y = _scale * strum.strumLine.strumScale;
|
||||
__splash.alpha = _alpha;
|
||||
|
||||
@@ -2,7 +2,7 @@ package funkin.game;
|
||||
|
||||
import funkin.backend.scripting.events.splash.*;
|
||||
|
||||
class SplashHandler extends FlxTypedGroup<FunkinSprite> {
|
||||
class SplashHandler extends FlxTypedGroup<Splash> {
|
||||
/**
|
||||
* Map containing all of the splashes group.
|
||||
*/
|
||||
|
||||
@@ -130,18 +130,19 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
case "box" | "solid":
|
||||
if (!node.has.name || !node.has.width || !node.has.height) continue;
|
||||
|
||||
var spr = new FunkinSprite(
|
||||
(node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0,
|
||||
(node.has.y) ? Std.parseFloat(node.att.y).getDefault(0) : 0
|
||||
);
|
||||
spr.name = node.getAtt("name");
|
||||
var isSolid = node.name == "solid";
|
||||
|
||||
(node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
|
||||
var spr = new FunkinSprite();
|
||||
(isSolid ? spr.makeSolid : spr.makeGraphic)(
|
||||
Std.parseInt(node.att.width),
|
||||
Std.parseInt(node.att.height),
|
||||
(node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1
|
||||
);
|
||||
|
||||
if (isSolid) node.x.remove("updateHitbox");
|
||||
for (a in ["width", "height", "color"]) node.x.remove(a);
|
||||
XMLUtil.loadSpriteFromXML(spr, node, "", NONE, false);
|
||||
|
||||
stageSprites.set(spr.name, spr);
|
||||
addSprite(spr);
|
||||
case "boyfriend" | "bf" | "player":
|
||||
@@ -353,6 +354,11 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
|
||||
public function measureHit(curMeasure:Int) {}
|
||||
|
||||
public override function destroy() {
|
||||
startCam.put();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of stages that are available to be used.
|
||||
* @param mods Whenever only the mods folder should be checked
|
||||
@@ -469,4 +475,4 @@ typedef OldCharInfo = {
|
||||
var alpha:Float;
|
||||
var zoomFactor:Float;
|
||||
var angle:Float;
|
||||
}
|
||||
}
|
||||
|
||||
+58
-32
@@ -42,6 +42,18 @@ class Strum extends FlxSprite {
|
||||
|
||||
public var lastDrawCameras(default, null):Array<FlxCamera> = [];
|
||||
|
||||
// Copy fields
|
||||
public var copyStrumCamera:Bool = true;
|
||||
public var copyStrumScrollX:Bool = true;
|
||||
public var copyStrumScrollY:Bool = true;
|
||||
public var copyStrumAngle:Bool = true;
|
||||
public var updateNotesPosX:Bool = true;
|
||||
public var updateNotesPosY:Bool = true;
|
||||
public var extraCopyFields(default, set):Array<String> = [];
|
||||
|
||||
private inline function set_extraCopyFields(val:Array<String>)
|
||||
return extraCopyFields = val == null ? [] : val;
|
||||
|
||||
/**
|
||||
* Whenever the strum is pressed.
|
||||
**/
|
||||
@@ -130,47 +142,61 @@ class Strum extends FlxSprite {
|
||||
public function updateNotePosition(daNote:Note) {
|
||||
if (!daNote.exists) return;
|
||||
|
||||
daNote.__strumCameras = lastDrawCameras;
|
||||
daNote.__strum = this;
|
||||
daNote.scrollFactor.set(scrollFactor.x, scrollFactor.y);
|
||||
daNote.__noteAngle = getNotesAngle(daNote);
|
||||
daNote.angle = daNote.isSustainNote ? daNote.__noteAngle : angle;
|
||||
if (copyStrumCamera) daNote.__strumCameras = lastDrawCameras;
|
||||
if (copyStrumScrollX) daNote.scrollFactor.x = scrollFactor.x;
|
||||
if (copyStrumScrollY) daNote.scrollFactor.y = scrollFactor.y;
|
||||
if (copyStrumAngle && daNote.copyStrumAngle) {
|
||||
daNote.__noteAngle = getNotesAngle(daNote);
|
||||
daNote.angle = daNote.isSustainNote ? daNote.__noteAngle : angle;
|
||||
}
|
||||
|
||||
updateNotePos(daNote);
|
||||
for (field in extraCopyFields)
|
||||
CoolUtil.cloneProperty(daNote, field, this); // TODO: make this cached to reduce the reflection calls - Neo
|
||||
}
|
||||
|
||||
private inline function updateNotePos(daNote:Note) {
|
||||
if (daNote.strumRelativePos) {
|
||||
daNote.setPosition((this.width - daNote.width) / 2, (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
|
||||
if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2 * (height / Note.swagWidth);
|
||||
} else {
|
||||
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
|
||||
var realOffset = FlxPoint.get(0, 0);
|
||||
var shouldX = updateNotesPosX && daNote.updateNotesPosX;
|
||||
var shouldY = updateNotesPosY && daNote.updateNotesPosY;
|
||||
|
||||
if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2 * (height / Note.swagWidth);
|
||||
|
||||
if (Std.int(daNote.__noteAngle % 360) != 0) {
|
||||
var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180);
|
||||
var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180);
|
||||
|
||||
var aOffset:FlxPoint = FlxPoint.get(
|
||||
(daNote.origin.x / daNote.scale.x) - daNote.offset.x,
|
||||
(daNote.origin.y / daNote.scale.y) - daNote.offset.y
|
||||
);
|
||||
realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y));
|
||||
realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y));
|
||||
|
||||
aOffset.put();
|
||||
if (shouldX || shouldY) {
|
||||
if (daNote.strumRelativePos) {
|
||||
if (shouldX) daNote.x = (this.width - daNote.width) / 2;
|
||||
if (shouldY) {
|
||||
daNote.y = (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100));
|
||||
if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2;
|
||||
}
|
||||
} else {
|
||||
realOffset.x = offset.x;
|
||||
realOffset.y = offset.y;
|
||||
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
|
||||
var realOffset = FlxPoint.get(0, 0);
|
||||
|
||||
if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2;
|
||||
|
||||
if (Std.int(daNote.__noteAngle % 360) != 0) {
|
||||
var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180);
|
||||
var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180);
|
||||
|
||||
var aOffset:FlxPoint = FlxPoint.get(
|
||||
(daNote.origin.x / daNote.scale.x) - daNote.offset.x,
|
||||
(daNote.origin.y / daNote.scale.y) - daNote.offset.y
|
||||
);
|
||||
realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y));
|
||||
realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y));
|
||||
|
||||
aOffset.put();
|
||||
} else {
|
||||
realOffset.x = offset.x;
|
||||
realOffset.y = offset.y;
|
||||
}
|
||||
realOffset.y *= -1;
|
||||
|
||||
if (shouldX) daNote.x = x + realOffset.x;
|
||||
if (shouldY) daNote.y = y + realOffset.y;
|
||||
|
||||
offset.put();
|
||||
realOffset.put();
|
||||
}
|
||||
realOffset.y *= -1;
|
||||
|
||||
daNote.setPosition(x + realOffset.x, y + realOffset.y);
|
||||
|
||||
offset.put();
|
||||
realOffset.put();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
|
||||
if (cpu && __updateNote_event.__autoCPUHit && !daNote.avoid && !daNote.wasGoodHit && daNote.strumTime < __updateNote_songPos) PlayState.instance.goodNoteHit(this, daNote);
|
||||
|
||||
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.sustainLength) < __updateNote_songPos) {
|
||||
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + daNote.sustainLength < __updateNote_songPos) {
|
||||
deleteNote(daNote);
|
||||
return;
|
||||
}
|
||||
@@ -247,8 +247,9 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
var __notePerStrum:Array<Note> = [];
|
||||
|
||||
function __inputProcessPressed(note:Note) {
|
||||
if (__pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) {
|
||||
if (__pressed[note.strumID] && note.isSustainNote && note.strumTime < __updateNote_songPos && !note.wasGoodHit) {
|
||||
PlayState.instance.goodNoteHit(this, note);
|
||||
note.updateSustainClip();
|
||||
}
|
||||
}
|
||||
function __inputProcessJustPressed(note:Note) {
|
||||
@@ -347,8 +348,10 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
* Creates a strum and returns the created strum (needs to be added manually).
|
||||
* @param i Index of the strum
|
||||
* @param animPrefix (Optional) Animation prefix (`left` = `arrowLEFT`, `left press`, `left confirm`).
|
||||
* @param spritePath (Optional) The sprite's graphic path if you don't want the default one.
|
||||
* @param playIntroAnimation (Optional) Whenever the intro animation should be played, by default might be `true` under certain conditions.
|
||||
*/
|
||||
public function createStrum(i:Int, ?animPrefix:String) {
|
||||
public function createStrum(i:Int, ?animPrefix:String, ?spritePath:String, ?playIntroAnimation:Bool) {
|
||||
if (animPrefix == null)
|
||||
animPrefix = strumAnimPrefix[i % strumAnimPrefix.length];
|
||||
var babyArrow:Strum = new Strum(startingPos.x + (Note.swagWidth * strumScale * (data.strumSpacing != null ? data.strumSpacing : 1) * i), startingPos.y + (Note.swagWidth*0.5) - (Note.swagWidth * strumScale * 0.5));
|
||||
@@ -359,7 +362,8 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
babyArrow.scrollSpeed = data.scrollSpeed;
|
||||
|
||||
var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix);
|
||||
event.__doAnimation = !MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true);
|
||||
event.__doAnimation = playIntroAnimation == null ? (!MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true)) : playIntroAnimation;
|
||||
if (spritePath != null) event.sprite = spritePath;
|
||||
if (PlayState.instance != null) event = PlayState.instance.gameAndCharsEvent("onStrumCreation", event);
|
||||
|
||||
if (!event.cancelled) {
|
||||
|
||||
@@ -8,11 +8,11 @@ import funkin.menus.PauseSubState;
|
||||
*/
|
||||
class Cutscene extends MusicBeatSubstate {
|
||||
var __callback:Void->Void;
|
||||
var __pausable:Bool;
|
||||
var game:PlayState = PlayState.instance;
|
||||
|
||||
public var paused:Bool = false;
|
||||
public var pauseItems:Array<String>;
|
||||
public var pausable:Bool;
|
||||
public var skippable(default, set):Bool;
|
||||
|
||||
public function set_skippable(val:Bool):Bool {
|
||||
@@ -26,9 +26,9 @@ class Cutscene extends MusicBeatSubstate {
|
||||
canOpenCustomTransition = true;
|
||||
|
||||
__callback = callback;
|
||||
__pausable = allowPause;
|
||||
|
||||
pauseItems = Flags.DEFAULT_CUTSCENE_PAUSE_ITEMS;
|
||||
pausable = allowPause;
|
||||
skippable = canSkip;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class Cutscene extends MusicBeatSubstate {
|
||||
}
|
||||
|
||||
public function pauseCheck():Bool
|
||||
return controls.PAUSE && __pausable;
|
||||
return controls.PAUSE && pausable;
|
||||
|
||||
var _before:Array<Bool> = [false, true];
|
||||
public function pauseCutscene() {
|
||||
|
||||
@@ -92,7 +92,7 @@ class DialogueCutscene extends ScriptedCutscene {
|
||||
callback: node.getAtt('callback'),
|
||||
changeDefAnim: node.getAtt('changeDefAnim'),
|
||||
speed: Std.parseFloat(node.getAtt("speed")).getDefault(0.05),
|
||||
musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.speed).getDefault(0.8)) : null,
|
||||
musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.musicVolume).getDefault(0.8)) : null,
|
||||
changeMusic: node.has.changeMusic ? FlxG.sound.load(Paths.music(node.att.changeMusic), volume, true) : null,
|
||||
playSound: node.has.playSound ? FlxG.sound.load(Paths.sound(node.att.playSound)) : null,
|
||||
nextSound: node.has.nextSound ? FlxG.sound.load(Paths.sound(node.att.nextSound)) : null,
|
||||
@@ -200,7 +200,7 @@ class DialogueCutscene extends ScriptedCutscene {
|
||||
if (curMusic != null && !curMusic.persist) curMusic.destroy();
|
||||
|
||||
super.destroy();
|
||||
cutscene = null;
|
||||
if (cutscene == this) cutscene = null;
|
||||
FlxG.cameras.remove(dialogueCamera);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ class VideoCutscene extends Cutscene {
|
||||
text.destroy();
|
||||
}});
|
||||
|
||||
__pausable = __loaded = true;
|
||||
pausable = __loaded = true;
|
||||
video.play();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user