Merge branch 'main' into pr/397

This commit is contained in:
Ne_Eo
2025-05-05 15:38:45 +02:00
290 changed files with 875 additions and 286 deletions
+17 -7
View File
@@ -72,24 +72,34 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
moves = false;
}
public static function copyFrom(source:FunkinSprite)
/**
* Gets the graphics and copies other properties from another sprite (Works both for `FlxSprite` and `FunkinSprite`!).
*/
public static function copyFrom(source:FlxSprite):FunkinSprite
{
var spr = new FunkinSprite();
var casted:FunkinSprite = null;
if (source is FunkinSprite)
casted = cast source;
@:privateAccess {
spr.setPosition(source.x, source.y);
spr.frames = source.frames;
if (source.animateAtlas != null && source.atlasPath != null)
spr.loadSprite(source.atlasPath);
if (casted != null && casted.animateAtlas != null && casted.atlasPath != null)
spr.loadSprite(casted.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.animOffsets = source.animOffsets.copy();
if (casted != null) {
spr.skew.set(casted.skew.x, casted.skew.y);
spr.transformMatrix = casted.transformMatrix;
spr.matrixExposed = casted.matrixExposed;
spr.animOffsets = casted.animOffsets.copy();
}
}
return spr;
}
@@ -63,7 +63,8 @@ class AssetsLibraryList extends AssetLibrary {
if (l is IModsAssetLibrary) {
var lib = cast(l, IModsAssetLibrary);
for(e in lib.getFiles(folder))
content.push(e);
if(!content.contains(e))
content.push(e);
}
#end
}
@@ -87,7 +88,8 @@ class AssetsLibraryList extends AssetLibrary {
if (l is IModsAssetLibrary) {
var lib = cast(l, IModsAssetLibrary);
for(e in lib.getFolders(folder))
content.push(e);
if(!content.contains(e))
content.push(e);
}
#end
}
+23
View File
@@ -200,6 +200,29 @@ class Paths
return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true));
}
/**
* Checks if the images needed for using getFrames() exist.
* @param key Path to the image
* @param checkAtlas Whenever to check for the Animation.json file (used in FlxAnimate)
* @param assetsPath Whenever to use the raw path or to pass it through Paths.image()
* @param library (Additional) library to load the frames from.
* @return True if the images exist, false otherwise.
**/
public static function framesExists(key:String, checkAtlas:Bool = false, checkMulti:Bool = true, assetsPath:Bool = false, ?library:String) {
var path = assetsPath ? key : Paths.image(key, library, true);
var noExt = Path.withoutExtension(path);
if(checkAtlas && Assets.exists('$noExt/Animation.json'))
return true;
if(checkMulti && Assets.exists('$noExt/1.png'))
return true;
if(Assets.exists('$noExt.xml'))
return true;
if(Assets.exists('$noExt.txt'))
return true;
if(Assets.exists('$noExt.json'))
return true;
return false;
}
/**
* Loads frames from a specific image path. Supports Sparrow Atlases, Packer Atlases, and multiple spritesheets.
@@ -119,6 +119,10 @@ class HScript extends Script {
interp.execute(expr);
call("new", []);
}
#if GLOBAL_SCRIPT
funkin.backend.scripting.GlobalScript.call("onScriptSetup", [this, "hscript"]);
#end
}
public override function reload() {
@@ -2,6 +2,7 @@ package funkin.backend.scripting.events;
import funkin.game.Note;
import funkin.game.Character;
import funkin.game.HealthIcon;
final class NoteHitEvent extends CancellableEvent {
@:dox(hide) public var animCancelled:Bool = false;
@@ -113,6 +114,10 @@ final class NoteHitEvent extends CancellableEvent {
* Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one).
*/
public var forceAnim:Null<Bool> = true;
/**
* The attached healthIcon used distinction for icons amongst others
*/
public var healthIcon:HealthIcon;
/**
* Prevents the default sing animation from being played.
@@ -2,12 +2,12 @@ package funkin.backend.scripting.events;
final class RatingUpdateEvent extends CancellableEvent {
/**
New combo
New combo (may be null if no ratings were found)
**/
public var rating:ComboRating;
public var rating:Null<ComboRating>;
/**
Old combo (may be null)
**/
public var oldRating:ComboRating;
public var oldRating:Null<ComboRating>;
}
+20 -21
View File
@@ -1,26 +1,20 @@
package funkin.backend.shaders;
import flixel.graphics.FlxGraphic;
import flixel.system.FlxAssets.FlxShader;
import haxe.Exception;
import hscript.IHScriptCustomBehaviour;
import flixel.graphics.tile.FlxGraphicsShader;
import openfl.display3D.Program3D;
import flixel.system.FlxAssets.FlxShader;
import lime.utils.Float32Array;
import openfl.display.BitmapData;
import openfl.display.ShaderInput;
import openfl.display.ShaderParameter;
import openfl.display.ShaderParameterType;
import openfl.display3D._internal.GLProgram;
import openfl.display3D._internal.GLShader;
import openfl.utils._internal.Log;
import openfl.display.BitmapData;
import openfl.display.ShaderParameter;
import openfl.display.ShaderParameterType;
import openfl.display.ShaderInput;
import lime.utils.Float32Array;
using StringTools;
import openfl.display.ShaderParameter;
import openfl.display.BitmapData;
import openfl.display.ShaderInput;
@:access(openfl.display3D.Context3D)
@:access(openfl.display3D.Program3D)
@:access(openfl.display.ShaderInput)
@@ -195,7 +189,7 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
var useBackCompat:Bool = true;
for (regex in ShaderTemplates.vertBackCompatVarList) if (!regex.match(value)) useBackCompat = false;
value = value.replace("#pragma header", useBackCompat ? ShaderTemplates.vertHeaderBackCompat : ShaderTemplates.vertHeader).replace("#pragma body", useBackCompat ? ShaderTemplates.vertBodyBackCompat : ShaderTemplates.vertBody);
if (value != __glVertexSource)
{
@@ -495,25 +489,26 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
} else {
var field = Reflect.field(data, name);
var cl = Type.getClassName(Type.getClass(field));
var isNotNull = val != null;
// cant do "field is ShaderInput" for some reason
if (cl.startsWith("openfl.display.ShaderParameter")) {
if (field.__length <= 1) {
// that means we wait for a single number, instead of an array
if (field.__isInt && !(val is Int)) {
if (field.__isInt && isNotNull && !(val is Int)) {
throw new ShaderTypeException(name, Type.getClass(val), 'Int');
return null;
} else
if (field.__isBool && !(val is Bool)) {
if (field.__isBool && isNotNull && !(val is Bool)) {
throw new ShaderTypeException(name, Type.getClass(val), 'Bool');
return null;
} else
if (field.__isFloat && !(val is Float)) {
if (field.__isFloat && isNotNull && !(val is Float)) {
throw new ShaderTypeException(name, Type.getClass(val), 'Float');
return null;
}
return field.value = [val];
return field.value = isNotNull ? [val] : null;
} else {
if (!(val is Array)) {
if (isNotNull && !(val is Array)) {
throw new ShaderTypeException(name, Type.getClass(val), Array);
return null;
}
@@ -521,11 +516,15 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
}
} else if (cl.startsWith("openfl.display.ShaderInput")) {
// shader input!!
if (!(val is BitmapData)) {
var bitmap:BitmapData;
if (!isNotNull) bitmap = null;
else if (val is BitmapData) bitmap = val;
else if (val is FlxGraphic) bitmap = val.bitmap;
else {
throw new ShaderTypeException(name, Type.getClass(val), BitmapData);
return null;
}
field.input = cast val;
field.input = bitmap;
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ class Socket implements IFlxDestroyable {
public function read():String {
try {
return this.socket.input.readUntil(('\n').charCodeAt(0)).replace("\\n", "\n");
return this.socket.input.readUntil('\n'.code).replace("\\n", "\n");
} catch(e) {
}
+120 -2
View File
@@ -609,11 +609,11 @@ class CoolUtil
* @param spr Sprite to load the graphic for
* @param path Path to the graphic
*/
public static function loadAnimatedGraphic(spr:FlxSprite, path:String) {
public static function loadAnimatedGraphic(spr:FlxSprite, path:String, fps:Float = 24.0) {
spr.frames = Paths.getFrames(path, true);
if (spr.frames != null && spr.frames.frames != null) {
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true);
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], fps, true);
spr.animation.play("idle");
}
@@ -904,6 +904,124 @@ class CoolUtil
return result.join(seperator);
}
public inline static function parsePropertyString(fieldPath:String):Array<OneOfTwo<String, Int>> {
return FlxTween.parseFieldString(fieldPath);
}
public static function stringifyFieldsPath(fields:Array<OneOfTwo<String, Int>>):String {
var str = new StringBuf();
var first = true;
for (field in fields) {
if (Type.typeof(field) == TInt) {
str.add('[${field}]');
} else {
if (!first)
str.add('.');
str.add(field);
}
first = false;
}
return str.toString();
}
public static function parseProperty(target:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>):Dynamic {
var fields:Array<OneOfTwo<String, Int>> = {
if((fields is String)) CoolUtil.parsePropertyString(fields);
else fields;
}
var field = CoolUtil.last(fields);
for (i in 0...fields.length - 1) {
var component = fields[i];
if (Type.typeof(component) == TInt) {
if ((target is Array)) {
var index:Int = cast component;
var arr:Array<Dynamic> = cast target;
target = arr[index];
}
} else { // TClass(String)
target = Reflect.getProperty(target, component);
}
if (!Reflect.isObject(target) && !(target is Array))
throw 'The object does not have the property "$component" in "${stringifyFieldsPath(fields)}"';
}
return new PropertyInfo(target, field);
}
public static function cloneProperty(toTarget:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>, fromTarget:Dynamic):Dynamic {
var fields:Array<OneOfTwo<String, Int>> = {
if((fields is String)) CoolUtil.parsePropertyString(fields);
else fields;
}
var toProperty = CoolUtil.parseProperty(toTarget, fields);
var fromProperty = CoolUtil.parseProperty(fromTarget, fields);
return toProperty.setValue(fromProperty.getValue());
}
}
class PropertyInfo {
public var object:Dynamic;
public var field:OneOfTwo<String, Int>;
public var typeOfField:Type.ValueType;
#if hscript_improved
public var isCustom:Bool = false;
public var custom:hscript.IHScriptCustomBehaviour;
#end
public function new(object:Dynamic, field:OneOfTwo<String, Int>) {
this.object = object;
this.field = field;
#if hscript_improved
if (object is hscript.IHScriptCustomBehaviour)
{
isCustom = true;
custom = cast object;
}
#end
typeOfField = Type.typeof(field);
}
public function getValue():Dynamic
{
if (typeOfField == TInt)
{
var index:Int = cast field;
var arr:Array<Dynamic> = cast object;
return arr[index];
}
else
{
#if hscript_improved
if (isCustom)
return custom.hget(field);
else
#end
return Reflect.getProperty(object, field);
}
}
public function setValue(value:Dynamic):Void
{
if (typeOfField == TInt)
{
var index:Int = cast field;
var arr:Array<Dynamic> = cast object;
arr[index] = value;
}
else
{
#if hscript_improved
if (isCustom)
custom.hset(field, value);
else
#end
Reflect.setProperty(object, field, value);
}
}
}
/**
+4 -4
View File
@@ -135,12 +135,12 @@ class ZipUtil {
if (prog == null) prog = new ZipProgress();
try {
var curPath:Array<String> = ['$path'];
var curPath:Array<String> = [path];
var destPath:Array<String> = [];
if (prefix != "") {
prefix = prefix.replace("\\", "/");
while(prefix.charAt(0) == "/") prefix = prefix.substr(1);
while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1);
while(prefix.charCodeAt(0) == "/".code) prefix = prefix.substr(1);
while(prefix.charCodeAt(prefix.length-1) == "/".code) prefix = prefix.substr(0, prefix.length-1);
destPath.push(prefix);
}
@@ -160,7 +160,7 @@ class ZipUtil {
} else {
// is file, put it in the list
var zipPath = '$zipPath/$e';
while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1);
while(zipPath.charCodeAt(0) == "/".code) zipPath = zipPath.substr(1);
files.push(new StrNameLabel('$path/$e', zipPath));
}
}
@@ -4,13 +4,14 @@ import flixel.math.FlxPoint;
import haxe.xml.Access;
import funkin.game.Character;
import funkin.editors.extra.PropertyButton;
import funkin.game.HealthIcon;
class CharacterInfoScreen extends UISubstateWindow {
public var character:Character;
public var spriteTextBox:UITextBox;
public var iconTextBox:UITextBox;
public var iconSprite:FlxSprite;
public var iconSprite:HealthIcon;
public var gameOverCharTextBox:UITextBox;
public var antialiasingCheckbox:UICheckbox;
public var flipXCheckbox:UICheckbox;
@@ -147,22 +148,14 @@ class CharacterInfoScreen extends UISubstateWindow {
}
function updateIcon(icon:String) {
if (iconSprite == null) add(iconSprite = new FlxSprite());
if (iconSprite == null) add(iconSprite = new HealthIcon());
if (iconSprite.animation.exists(icon)) return;
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/face');
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
iconSprite.antialiasing = true;
iconSprite.animation.play(icon);
iconSprite.scale.set(0.5, 0.5);
iconSprite.setIcon(icon);
var size = Std.int(150 * 0.5);
iconSprite.setUnstretchedGraphicSize(size, size, true);
iconSprite.updateHitbox();
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
iconSprite.scrollFactor.set(1, 1);
}
function saveCharacterInfo() {
@@ -320,7 +320,8 @@ class CompactCharacterButton extends UIButton {
autoAlpha = false;
charIcon = new HealthIcon(funkin.game.Character.getIconFromCharName(char));
charIcon.scale.set(0.2, 0.2);
var size = Std.int(150 * 0.2);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(10, bHeight/2 - charIcon.height / 2);
charIcon.scrollFactor.set(1,1);
@@ -332,11 +333,10 @@ class CompactCharacterButton extends UIButton {
textBox.antialiasing = true;
textBox.onChange = function(char:String) {
char = funkin.game.Character.getIconFromCharName(char);
var image = Paths.image("icons/" + char);
if(!Assets.exists(image))
image = Paths.image("icons/face");
charIcon.loadGraphic(image, true, 150, 150);
charIcon.setIcon(char);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(10, bHeight/2 - charIcon.height / 2);
}
deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), "", function () {
@@ -3,6 +3,7 @@ package funkin.editors.charter;
import flixel.math.FlxPoint;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.editors.extra.PropertyButton;
import funkin.game.HealthIcon;
using StringTools;
@@ -20,7 +21,7 @@ class CharterMetaDataScreen extends UISubstateWindow {
public var displayNameTextBox:UITextBox;
public var iconTextBox:UITextBox;
public var iconSprite:FlxSprite;
public var iconSprite:HealthIcon;
public var opponentModeCheckbox:UICheckbox;
public var coopAllowedCheckbox:UICheckbox;
public var colorWheel:UIColorwheel;
@@ -130,22 +131,14 @@ class CharterMetaDataScreen extends UISubstateWindow {
}
function updateIcon(icon:String) {
if (iconSprite == null) add(iconSprite = new FlxSprite());
if (iconSprite == null) add(iconSprite = new HealthIcon());
if (iconSprite.animation.exists(icon)) return;
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/face');
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
iconSprite.antialiasing = true;
iconSprite.animation.play(icon);
iconSprite.scale.set(0.5, 0.5);
iconSprite.setIcon(icon);
var size = Std.int(150 * 0.5);
iconSprite.setUnstretchedGraphicSize(size, size, true);
iconSprite.updateHitbox();
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
iconSprite.scrollFactor.set(1, 1);
}
public function saveMeta() {
@@ -43,20 +43,7 @@ class CharterStrumline extends UISprite {
if(strumLine.visible == null) strumLine.visible = true;
var icons = strumLine.characters != null ? strumLine.characters : [];
healthIcons = new FlxSpriteGroup(x, y);
for (i=>icon in icons) {
var healthIcon = new HealthIcon(Character.getIconFromCharName(icon));
healthIcon.scale.x = healthIcon.scale.y = 0.6 - (icons.length / 20);
healthIcon.updateHitbox();
healthIcon.x = FlxMath.lerp(0, icons.length * 20, (icons.length-1 != 0 ? i / (icons.length-1) : 0));
healthIcon.y = draggable ? 29 : 7;
healthIcon.alpha = strumLine.visible ? 1 : 0.4;
healthIcons.add(healthIcon);
}
members.push(healthIcons);
draggingSprite = new UISprite();
@@ -70,8 +57,7 @@ class CharterStrumline extends UISprite {
button = new CharterStrumlineOptions(this);
members.push(button);
vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound();
vocals.group = FlxG.sound.defaultMusicGroup;
updateInfo();
selectedWaveform = -1;
}
@@ -106,7 +92,9 @@ class CharterStrumline extends UISprite {
for (i=>icon in icons) {
var healthIcon = new HealthIcon(Character.getIconFromCharName(icon));
healthIcon.scale.x = healthIcon.scale.y = 0.6 - (icons.length / 20);
var newScale = 0.6 - (icons.length / 20);
var size = Std.int(150 * newScale);
healthIcon.setUnstretchedGraphicSize(size, size, true);
healthIcon.updateHitbox();
healthIcon.x = FlxMath.lerp(0, icons.length * 20, (icons.length-1 != 0 ? i / (icons.length-1) : 0));
healthIcon.y = draggable ? 14 : 7;
@@ -194,7 +194,8 @@ class CharacterButton extends UIButton {
super(x, y, "", null, 250, 54);
charIcon = new HealthIcon(Character.getIconFromCharName(char));
charIcon.scale.set(0.3, 0.3);
var size = Std.int(150 * 0.3);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2);
charIcon.scrollFactor.set(1,1);
@@ -206,11 +207,10 @@ class CharacterButton extends UIButton {
textBox.antialiasing = true;
textBox.onChange = function(char:String) {
char = Character.getIconFromCharName(char);
var image = Paths.image("icons/" + char);
if(!Assets.exists(image))
image = Paths.image("icons/face");
charIcon.loadGraphic(image, true, 150, 150);
charIcon.setIcon(char);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2);
}
deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), "", function () {
@@ -5,6 +5,7 @@ import flixel.text.FlxText.FlxTextFormatMarkerPair;
import flixel.group.FlxGroup;
import funkin.backend.chart.ChartData.ChartMetaData;
import haxe.io.Bytes;
import funkin.game.HealthIcon;
typedef SongCreationData = {
var meta:ChartMetaData;
@@ -25,7 +26,7 @@ class SongCreationScreen extends UISubstateWindow {
public var displayNameTextBox:UITextBox;
public var iconTextBox:UITextBox;
public var iconSprite:FlxSprite;
public var iconSprite:HealthIcon;
public var opponentModeCheckbox:UICheckbox;
public var coopAllowedCheckbox:UICheckbox;
public var colorWheel:UIColorwheel;
@@ -231,22 +232,14 @@ class SongCreationScreen extends UISubstateWindow {
}
function updateIcon(icon:String) {
if (iconSprite == null) menuDataGroup.add(iconSprite = new FlxSprite());
if (iconSprite == null) menuDataGroup.add(iconSprite = new HealthIcon());
if (iconSprite.animation.exists(icon)) return;
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/face');
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
iconSprite.antialiasing = true;
iconSprite.animation.play(icon);
iconSprite.scale.set(0.5, 0.5);
iconSprite.setIcon(icon);
var size = Std.int(150 * 0.5);
iconSprite.setUnstretchedGraphicSize(size, size, true);
iconSprite.updateHitbox();
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
iconSprite.scrollFactor.set(1, 1);
}
function saveSongInfo() {
+22 -24
View File
@@ -100,30 +100,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))
{
@@ -192,6 +170,26 @@ 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 {
PlayState.resetSongInfos();
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");
+388 -45
View File
@@ -1,12 +1,31 @@
package funkin.game;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.util.typeLimit.OneOfTwo;
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,30 +39,64 @@ 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
*/
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
];
}
public function new(char:String = 'bf', isPlayer:Bool = false)
public function new(?char:String, isPlayer:Bool = false)
{
super();
health = 0.5;
@@ -53,44 +106,334 @@ 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/face');
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 = 1.2;
scale.set(defaultScale * iconScale, defaultScale * iconScale);
updateHitbox();
}
override function update(elapsed:Float)
{
super.update(elapsed);
/**
* Called every frame and causes the icon to become smaller
**/
public dynamic function updateBump():Void {
var iconLerp = 0.33;
scale.set(CoolUtil.fpsLerp(scale.x, defaultScale, iconLerp), CoolUtil.fpsLerp(scale.y, defaultScale, iconLerp));
updateHitbox();
}
if (sprTracker != null)
setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30);
/**
* 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;
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;
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);
}
if (i >= 0) animation.curAnim.curFrame = i;
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")}';
if (node.exists("offsetX") || node.exists("offsetY"))
addOffset(animName, Std.parseFloat(node.get("offsetX")).getDefault(0), Std.parseFloat(node.get("offsetY")).getDefault(0));
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);
addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), node.get("looped").getDefault("true").toLowerCase() == "true");
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 + (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 || (this.animated && animateAtlas != null)) {
var data = getIconAnim(health);
var localAnimState = data.animState;
if (data.isValid && curAnimState != localAnimState) {
if (this.animated) {
var transAnim = 'from-$curAnimState-to-$localAnimState';
playAnim(hasAnim(transAnim) ? transAnim : localAnimState);
} else {
if(animation.curAnim != null)
animation.curAnim.curFrame = localAnimState;
}
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;
}
+6 -1
View File
@@ -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, null):String;
@:dox(hide) public var __strumCameras:Array<FlxCamera> = null;
@@ -215,7 +220,7 @@ class Note extends FlxSprite
override function drawComplex(camera:FlxCamera) {
var downscrollCam = (camera is HudCamera ? cast(camera, HudCamera).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);
+144 -65
View File
@@ -303,6 +303,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).
*/
@@ -531,10 +536,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 = scripts.event("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating));
@@ -618,10 +623,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.toLowerCase()}/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.trace('data/charts/ is deprecrated and will be removed in the future. Please move script $file to songs/', WARNING, DARKYELLOW);
@@ -682,7 +688,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);
}
@@ -758,9 +764,9 @@ class PlayState extends MusicBeatState
health = maxHealth / 2;
iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : "face", true);
iconP2 = new HealthIcon(dad != null ? dad.getIcon() : "face", false);
for(icon in [iconP1, iconP2]) {
iconArray.push(iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : "face", true));
iconArray.push(iconP2 = new HealthIcon(dad != null ? dad.getIcon() : "face", false));
for (icon in iconArray) {
icon.y = healthBar.y - (icon.height / 2);
add(icon);
}
@@ -777,7 +783,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];
@@ -817,7 +824,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);
@@ -1217,21 +1225,22 @@ class PlayState extends MusicBeatState
]));
}
function updateIconPositions() {
dynamic function updateIconPositions() {
var iconOffset:Int = 26;
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 = FlxMath.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';
@@ -1261,10 +1270,8 @@ class PlayState extends MusicBeatState
return;
}
updateRatingStuff();
if (controls.PAUSE && startedCountdown && canPause)
pauseGame();
if (updateRatingStuff != null)
updateRatingStuff();
if (canAccessDebugMenus) {
if (chartingMode && FlxG.keys.justPressed.SEVEN) {
@@ -1277,26 +1284,31 @@ class PlayState extends MusicBeatState
}
}
if (doIconBop) {
iconP1.scale.set(lerp(iconP1.scale.x, 1, 0.33), lerp(iconP1.scale.y, 1, 0.33));
iconP2.scale.set(lerp(iconP2.scale.x, 1, 0.33), lerp(iconP2.scale.y, 1, 0.33));
if (doIconBop)
for (icon in iconArray)
if (icon.updateBump != null)
icon.updateBump();
iconP1.updateHitbox();
iconP2.updateHitbox();
}
updateIconPositions();
if (updateIconPositions != null)
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 > 25) {
resyncVocals();
@@ -1307,31 +1319,20 @@ 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 = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r));
if (generatedMusic && 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(pos.x, pos.y);
camFollow.setPosition(event.position.x, event.position.y);
}
pos.put();
data.put();
}
if (camZooming)
{
if (camZooming) {
FlxG.camera.zoom = lerp(FlxG.camera.zoom, defaultCamZoom, camGameZoomLerp);
camHUD.zoom = lerp(camHUD.zoom, defaultHudZoom, camHUDZoomLerp);
}
@@ -1365,6 +1366,41 @@ class PlayState extends MusicBeatState
scripts.event("postDraw", e);
}
/**
* 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 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 scrollSpeedTween:FlxTween = null;
public function executeEvent(event:ChartEvent) @:privateAccess {
@@ -1375,7 +1411,27 @@ 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":
curCameraTarget = event.params[0];
case "Add Camera Zoom":
@@ -1674,9 +1730,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);
@@ -1836,14 +1892,10 @@ class PlayState extends MusicBeatState
camHUD.zoom += 0.03 * camZoomingStrength;
}
if (doIconBop)
{
iconP1.scale.set(1.2, 1.2);
iconP2.scale.set(1.2, 1.2);
iconP1.updateHitbox();
iconP2.updateHitbox();
}
if (doIconBop)
for (icon in iconArray)
if (icon.bump != null)
icon.bump();
scripts.call("beatHit", [curBeat]);
}
@@ -1962,8 +2014,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;
@@ -1978,3 +2032,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;
}
}
+58 -32
View File
@@ -19,6 +19,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;
public var getPressed:StrumLine->Bool = null;
public var getJustPressed:StrumLine->Bool = null;
public var getJustReleased:StrumLine->Bool = null;
@@ -84,47 +96,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;
} 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;
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();
}
}
@@ -86,7 +86,7 @@ class DialogueCutscene extends Cutscene {
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,
+2
View File
@@ -141,6 +141,8 @@ class FreeplayState extends MusicBeatState
var icon:HealthIcon = new HealthIcon(songs[i].icon);
icon.sprTracker = songText;
icon.setUnstretchedGraphicSize(150, 150, true);
icon.updateHitbox();
// using a FlxGroup is too much fuss!
iconArray.push(icon);
+4
View File
@@ -10,6 +10,10 @@ class IconOption extends TextOption {
iconSpr = new HealthIcon(icon, false);
iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2);
iconSpr.setUnstretchedGraphicSize(150, 150, true);
iconSpr.updateHitbox();
iconSpr.sprTracker = __text;
iconSpr.sprTrackerAlignment = LEFT;
add(iconSpr);
}
}
-1
View File
@@ -1,7 +1,6 @@
package funkin.options.type;
import flixel.util.FlxColor;
import funkin.game.HealthIcon;
class NewOption extends TextOption {
public var iconSpr:FlxSprite;