From 2a700b61c9cdcd15f30362a7e4beda7be14c0eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8D=9A=7ENex?= <87421482+NexIsDumb@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:17:13 +0200 Subject: [PATCH 1/9] FunkinSprite's copyFrom working for FlxSprite too --- source/funkin/backend/FunkinSprite.hx | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 2e4df83d..2f999e0d 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -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; } From 8c252bba81396f6568b509b5e6f4b51c3382b63d Mon Sep 17 00:00:00 2001 From: Furo Date: Sun, 20 Apr 2025 18:27:41 -0400 Subject: [PATCH 2/9] Better Health Icons (#484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Better Health Icons - Add Animated Icons Support - Switch to Funkin Sprite - Customizable Icon Bumps With HScript * Moved icons * Improve code and features for Health Icons V2 * Small fixes * fix icons in editors * Remove that not needed code * Use better onFinish callback * Animate Atlas icons * Cleanup * Small change * sing anim nodes on icons and updateIconPos can be overitten on hscript * remove sing nodes since its useless * fix potential null function pointer * remove that lines * fixing compiling errors * hawk tuah * ORGANIZE THEM >:D * mlem imports are organized based on the func in vsc smh * bruh --------- Co-authored-by: Ne_Eo Co-authored-by: ⍚~Nex <87421482+NexIsDumb@users.noreply.github.com> --- assets/data/scripts/pixel.hx | 3 - .../icons/{bf-old.png => bf-old/icon.png} | Bin assets/images/icons/bf-pixel/data.xml | 2 + .../icons/{bf-pixel.png => bf-pixel/icon.png} | Bin assets/images/icons/{bf.png => bf/icon.png} | Bin assets/images/icons/{dad.png => dad/icon.png} | Bin .../images/icons/{face.png => face/icon.png} | Bin assets/images/icons/gf-pixel/data.xml | 2 + .../icons/{gf-pixel.png => gf-pixel/icon.png} | Bin assets/images/icons/{gf.png => gf/icon.png} | Bin assets/images/icons/{mom.png => mom/icon.png} | Bin .../icons/{monster.png => monster/icon.png} | Bin .../icon.png} | Bin .../images/icons/{pico.png => pico/icon.png} | Bin assets/images/icons/senpai/data.xml | 2 + .../icons/{senpai.png => senpai/icon.png} | Bin assets/images/icons/spirit/data.xml | 2 + .../icons/{spirit.png => spirit/icon.png} | Bin .../icons/{spooky.png => spooky/icon.png} | Bin .../icons/{tankman.png => tankman/icon.png} | Bin source/funkin/backend/assets/Paths.hx | 23 + .../backend/scripting/events/NoteHitEvent.hx | 5 + .../editors/character/CharacterInfoScreen.hx | 19 +- .../editors/charter/ChartCreationScreen.hx | 9 +- .../editors/charter/CharterMetaDataScreen.hx | 19 +- .../editors/charter/CharterStrumline.hx | 6 +- .../editors/charter/CharterStrumlineScreen.hx | 9 +- .../editors/charter/SongCreationScreen.hx | 19 +- source/funkin/game/HealthIcon.hx | 415 ++++++++++++++++-- source/funkin/game/PlayState.hx | 41 +- 30 files changed, 452 insertions(+), 124 deletions(-) rename assets/images/icons/{bf-old.png => bf-old/icon.png} (100%) create mode 100644 assets/images/icons/bf-pixel/data.xml rename assets/images/icons/{bf-pixel.png => bf-pixel/icon.png} (100%) rename assets/images/icons/{bf.png => bf/icon.png} (100%) rename assets/images/icons/{dad.png => dad/icon.png} (100%) rename assets/images/icons/{face.png => face/icon.png} (100%) create mode 100644 assets/images/icons/gf-pixel/data.xml rename assets/images/icons/{gf-pixel.png => gf-pixel/icon.png} (100%) rename assets/images/icons/{gf.png => gf/icon.png} (100%) rename assets/images/icons/{mom.png => mom/icon.png} (100%) rename assets/images/icons/{monster.png => monster/icon.png} (100%) rename assets/images/icons/{parents-christmas.png => parents-christmas/icon.png} (100%) rename assets/images/icons/{pico.png => pico/icon.png} (100%) create mode 100644 assets/images/icons/senpai/data.xml rename assets/images/icons/{senpai.png => senpai/icon.png} (100%) create mode 100644 assets/images/icons/spirit/data.xml rename assets/images/icons/{spirit.png => spirit/icon.png} (100%) rename assets/images/icons/{spooky.png => spooky/icon.png} (100%) rename assets/images/icons/{tankman.png => tankman/icon.png} (100%) diff --git a/assets/data/scripts/pixel.hx b/assets/data/scripts/pixel.hx index 8f1887c8..1109f2dc 100644 --- a/assets/data/scripts/pixel.hx +++ b/assets/data/scripts/pixel.hx @@ -97,9 +97,6 @@ function postCreate() { defaultCamZoom /= daPixelZoom; } - iconP1.antialiasing = false; - iconP2.antialiasing = false; - if (enablePixelGameOver) { gameOverSong = "pixel/gameOver"; lossSFX = "pixel/gameOverSFX"; diff --git a/assets/images/icons/bf-old.png b/assets/images/icons/bf-old/icon.png similarity index 100% rename from assets/images/icons/bf-old.png rename to assets/images/icons/bf-old/icon.png diff --git a/assets/images/icons/bf-pixel/data.xml b/assets/images/icons/bf-pixel/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/bf-pixel/data.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/assets/images/icons/bf-pixel.png b/assets/images/icons/bf-pixel/icon.png similarity index 100% rename from assets/images/icons/bf-pixel.png rename to assets/images/icons/bf-pixel/icon.png diff --git a/assets/images/icons/bf.png b/assets/images/icons/bf/icon.png similarity index 100% rename from assets/images/icons/bf.png rename to assets/images/icons/bf/icon.png diff --git a/assets/images/icons/dad.png b/assets/images/icons/dad/icon.png similarity index 100% rename from assets/images/icons/dad.png rename to assets/images/icons/dad/icon.png diff --git a/assets/images/icons/face.png b/assets/images/icons/face/icon.png similarity index 100% rename from assets/images/icons/face.png rename to assets/images/icons/face/icon.png diff --git a/assets/images/icons/gf-pixel/data.xml b/assets/images/icons/gf-pixel/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/gf-pixel/data.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/assets/images/icons/gf-pixel.png b/assets/images/icons/gf-pixel/icon.png similarity index 100% rename from assets/images/icons/gf-pixel.png rename to assets/images/icons/gf-pixel/icon.png diff --git a/assets/images/icons/gf.png b/assets/images/icons/gf/icon.png similarity index 100% rename from assets/images/icons/gf.png rename to assets/images/icons/gf/icon.png diff --git a/assets/images/icons/mom.png b/assets/images/icons/mom/icon.png similarity index 100% rename from assets/images/icons/mom.png rename to assets/images/icons/mom/icon.png diff --git a/assets/images/icons/monster.png b/assets/images/icons/monster/icon.png similarity index 100% rename from assets/images/icons/monster.png rename to assets/images/icons/monster/icon.png diff --git a/assets/images/icons/parents-christmas.png b/assets/images/icons/parents-christmas/icon.png similarity index 100% rename from assets/images/icons/parents-christmas.png rename to assets/images/icons/parents-christmas/icon.png diff --git a/assets/images/icons/pico.png b/assets/images/icons/pico/icon.png similarity index 100% rename from assets/images/icons/pico.png rename to assets/images/icons/pico/icon.png diff --git a/assets/images/icons/senpai/data.xml b/assets/images/icons/senpai/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/senpai/data.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/assets/images/icons/senpai.png b/assets/images/icons/senpai/icon.png similarity index 100% rename from assets/images/icons/senpai.png rename to assets/images/icons/senpai/icon.png diff --git a/assets/images/icons/spirit/data.xml b/assets/images/icons/spirit/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/spirit/data.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/assets/images/icons/spirit.png b/assets/images/icons/spirit/icon.png similarity index 100% rename from assets/images/icons/spirit.png rename to assets/images/icons/spirit/icon.png diff --git a/assets/images/icons/spooky.png b/assets/images/icons/spooky/icon.png similarity index 100% rename from assets/images/icons/spooky.png rename to assets/images/icons/spooky/icon.png diff --git a/assets/images/icons/tankman.png b/assets/images/icons/tankman/icon.png similarity index 100% rename from assets/images/icons/tankman.png rename to assets/images/icons/tankman/icon.png diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 9486aed6..a29d3909 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -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. diff --git a/source/funkin/backend/scripting/events/NoteHitEvent.hx b/source/funkin/backend/scripting/events/NoteHitEvent.hx index a4595fd1..750e5f52 100644 --- a/source/funkin/backend/scripting/events/NoteHitEvent.hx +++ b/source/funkin/backend/scripting/events/NoteHitEvent.hx @@ -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 = true; + /** + * The attached healthIcon used distinction for icons amongst others + */ + public var healthIcon:HealthIcon; /** * Prevents the default sing animation from being played. diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index 1d7f313b..7e457183 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -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,20 +148,10 @@ 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); + iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); iconSprite.updateHitbox(); iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); } diff --git a/source/funkin/editors/charter/ChartCreationScreen.hx b/source/funkin/editors/charter/ChartCreationScreen.hx index c89835f9..21e925b5 100644 --- a/source/funkin/editors/charter/ChartCreationScreen.hx +++ b/source/funkin/editors/charter/ChartCreationScreen.hx @@ -320,7 +320,7 @@ class CompactCharacterButton extends UIButton { autoAlpha = false; charIcon = new HealthIcon(funkin.game.Character.getIconFromCharName(char)); - charIcon.scale.set(0.2, 0.2); + charIcon.scale.set(charIcon.defaultScale * 0.2, charIcon.defaultScale * 0.2); charIcon.updateHitbox(); charIcon.setPosition(10, bHeight/2 - charIcon.height / 2); charIcon.scrollFactor.set(1,1); @@ -332,11 +332,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.scale.set(charIcon.defaultScale * 0.2, charIcon.defaultScale * 0.2); charIcon.updateHitbox(); + charIcon.setPosition(10, bHeight/2 - charIcon.height / 2); } deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), "", function () { diff --git a/source/funkin/editors/charter/CharterMetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx index 4179dfc7..c7fdc49b 100644 --- a/source/funkin/editors/charter/CharterMetaDataScreen.hx +++ b/source/funkin/editors/charter/CharterMetaDataScreen.hx @@ -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,20 +131,10 @@ 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); + iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); iconSprite.updateHitbox(); iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); } diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index 3850c2fa..23da1e9a 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -49,7 +49,8 @@ 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); + healthIcon.scale.x = healthIcon.scale.y = healthIcon.defaultScale * newScale; healthIcon.updateHitbox(); healthIcon.x = FlxMath.lerp(0, icons.length * 20, (icons.length-1 != 0 ? i / (icons.length-1) : 0)); healthIcon.y = draggable ? 29 : 7; @@ -106,7 +107,8 @@ 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); + healthIcon.scale.x = healthIcon.scale.y = healthIcon.defaultScale * newScale; healthIcon.updateHitbox(); healthIcon.x = FlxMath.lerp(0, icons.length * 20, (icons.length-1 != 0 ? i / (icons.length-1) : 0)); healthIcon.y = draggable ? 14 : 7; diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index d9298430..915a1862 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -194,7 +194,7 @@ class CharacterButton extends UIButton { super(x, y, "", null, 250, 54); charIcon = new HealthIcon(Character.getIconFromCharName(char)); - charIcon.scale.set(0.3, 0.3); + charIcon.scale.set(charIcon.defaultScale * 0.3, charIcon.defaultScale * 0.3); charIcon.updateHitbox(); charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2); charIcon.scrollFactor.set(1,1); @@ -206,11 +206,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.scale.set(charIcon.defaultScale * 0.3, charIcon.defaultScale * 0.3); charIcon.updateHitbox(); + charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2); } deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), "", function () { diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index fdd9cf1e..c134da5a 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -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,20 +232,10 @@ class SongCreationScreen extends UISubstateWindow { } function updateIcon(icon:String) { - if (iconSprite == null) menuDataGroup.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); + iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); iconSprite.updateHitbox(); iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); } diff --git a/source/funkin/game/HealthIcon.hx b/source/funkin/game/HealthIcon.hx index 70f5024b..7900ca3b 100644 --- a/source/funkin/game/HealthIcon.hx +++ b/source/funkin/game/HealthIcon.hx @@ -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 = null; + public var healthSteps:Map> = null; + + /** + * Current animation state + */ + public var curAnimState:OneOfTwo = -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>) { // helper for hscript that can't do maps + @:dox(hide) @:noCompletion @:deprecated("use healthSteps instead") + public function setHealthSteps(steps:Array>>):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,316 @@ 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) - { + /** + * 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(); + } + + /** + * 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(iconPath)); + else { + var iconAsset:FlxGraphic = FlxG.bitmap.add(Paths.image(iconPath)); + var assetW:Float = iconAsset.width; + var assetH:Float = iconAsset.height; + + iconAmt = Math.floor(assetW / assetH); + iconSize = Math.floor(assetW / iconAmt); + + 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 = []; + + 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 is not allowed when not animated', WARNING); + continue; + } + if (!node.exists("anim")) { + Logs.trace('Icon ${char} data is missing anim', WARNING); + continue; + } + if (!node.exists("to")) { + Logs.trace('Icon ${char} data is missing to', WARNING); + continue; + } + if (!node.exists("from")) { + Logs.trace('Icon ${char} data 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 is not allowed when not animated', WARNING); + continue; + } + if (!node.exists("name")) { + Logs.trace('Icon ${char} data is missing name', WARNING); + continue; + } + if (!node.exists("anim")) { + Logs.trace('Icon ${char} data is missing anim', WARNING); + continue; + } + + var animName = node.get("name"); + + 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), 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 is missing percent', WARNING); + continue; + } + if (!node.exists("name")) { + Logs.trace('Icon ${char} data 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; + } + + var normalizedNames = ["neutral", "losing", "winning"]; + private function normalizeAnim(anim:OneOfTwo):OneOfTwo { + 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 = -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 (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; } - - if (i >= 0) animation.curAnim.curFrame = i; } } + + 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; + public var isValid:Bool; + + public function new(animState:OneOfTwo, 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; +} \ No newline at end of file diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 65d5b0ac..58ea592d 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -808,7 +808,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); @@ -1210,18 +1211,19 @@ 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() { @@ -1267,14 +1269,13 @@ 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 [iconP1, iconP2]) + if (icon.updateBump != null) + icon.updateBump(); - iconP1.updateHitbox(); - iconP2.updateHitbox(); - } - updateIconPositions(); + if (updateIconPositions != null) + updateIconPositions(); if (startingSong) { @@ -1684,9 +1685,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); @@ -1846,14 +1847,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 [iconP1, iconP2]) + if (icon.bump != null) + icon.bump(); scripts.call("beatHit", [curBeat]); } From 895fb5c981b0655d773759b85d39f3f13b312fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8D=9A=7ENex?= <87421482+NexIsDumb@users.noreply.github.com> Date: Mon, 21 Apr 2025 03:41:08 +0200 Subject: [PATCH 3/9] fixed healthicons in editors --- .../editors/character/CharacterInfoScreen.hx | 3 ++- .../editors/charter/CharterMetaDataScreen.hx | 3 ++- .../funkin/editors/charter/CharterStrumline.hx | 17 +---------------- .../editors/charter/SongCreationScreen.hx | 5 +++-- source/funkin/options/type/NewOption.hx | 1 - 5 files changed, 8 insertions(+), 21 deletions(-) diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index 7e457183..71252ba9 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -153,7 +153,8 @@ class CharacterInfoScreen extends UISubstateWindow { iconSprite.setIcon(icon); iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); 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() { diff --git a/source/funkin/editors/charter/CharterMetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx index c7fdc49b..80be1e12 100644 --- a/source/funkin/editors/charter/CharterMetaDataScreen.hx +++ b/source/funkin/editors/charter/CharterMetaDataScreen.hx @@ -136,7 +136,8 @@ class CharterMetaDataScreen extends UISubstateWindow { iconSprite.setIcon(icon); iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); 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() { diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index 23da1e9a..f094f5c1 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -43,21 +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)); - var newScale = 0.6 - (icons.length / 20); - healthIcon.scale.x = healthIcon.scale.y = healthIcon.defaultScale * newScale; - 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(); @@ -71,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; } diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index c134da5a..31123dc2 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -232,12 +232,13 @@ class SongCreationScreen extends UISubstateWindow { } function updateIcon(icon:String) { - if (iconSprite == null) add(iconSprite = new HealthIcon()); + if (iconSprite == null) menuDataGroup.add(iconSprite = new HealthIcon()); iconSprite.setIcon(icon); iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); 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() { diff --git a/source/funkin/options/type/NewOption.hx b/source/funkin/options/type/NewOption.hx index bb8af3be..78201013 100644 --- a/source/funkin/options/type/NewOption.hx +++ b/source/funkin/options/type/NewOption.hx @@ -1,7 +1,6 @@ package funkin.options.type; import flixel.util.FlxColor; -import funkin.game.HealthIcon; class NewOption extends TextOption { public var iconSpr:FlxSprite; From 2f2bad2dc9208af5e08e896cb708913e444248cb Mon Sep 17 00:00:00 2001 From: Furo Date: Mon, 21 Apr 2025 20:24:36 +0200 Subject: [PATCH 4/9] Icon Fixes --- .../editors/character/CharacterInfoScreen.hx | 3 ++- .../editors/charter/ChartCreationScreen.hx | 5 ++-- .../editors/charter/CharterMetaDataScreen.hx | 3 ++- .../editors/charter/CharterStrumline.hx | 3 ++- .../editors/charter/CharterStrumlineScreen.hx | 5 ++-- .../editors/charter/SongCreationScreen.hx | 3 ++- source/funkin/game/HealthIcon.hx | 26 ++++++++++++++++--- source/funkin/menus/FreeplayState.hx | 2 ++ source/funkin/options/type/IconOption.hx | 4 +++ 9 files changed, 42 insertions(+), 12 deletions(-) diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index 71252ba9..bd1c803d 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -151,7 +151,8 @@ class CharacterInfoScreen extends UISubstateWindow { if (iconSprite == null) add(iconSprite = new HealthIcon()); iconSprite.setIcon(icon); - iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); + var size = Std.int(150 * 0.5); + iconSprite.setUnstretchedGraphicSize(size, size, true); iconSprite.updateHitbox(); iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2)); iconSprite.scrollFactor.set(1, 1); diff --git a/source/funkin/editors/charter/ChartCreationScreen.hx b/source/funkin/editors/charter/ChartCreationScreen.hx index 21e925b5..1fe85e6b 100644 --- a/source/funkin/editors/charter/ChartCreationScreen.hx +++ b/source/funkin/editors/charter/ChartCreationScreen.hx @@ -320,7 +320,8 @@ class CompactCharacterButton extends UIButton { autoAlpha = false; charIcon = new HealthIcon(funkin.game.Character.getIconFromCharName(char)); - charIcon.scale.set(charIcon.defaultScale * 0.2, charIcon.defaultScale * 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); @@ -333,7 +334,7 @@ class CompactCharacterButton extends UIButton { textBox.onChange = function(char:String) { char = funkin.game.Character.getIconFromCharName(char); charIcon.setIcon(char); - charIcon.scale.set(charIcon.defaultScale * 0.2, charIcon.defaultScale * 0.2); + charIcon.setUnstretchedGraphicSize(size, size, true); charIcon.updateHitbox(); charIcon.setPosition(10, bHeight/2 - charIcon.height / 2); } diff --git a/source/funkin/editors/charter/CharterMetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx index 80be1e12..e58898c0 100644 --- a/source/funkin/editors/charter/CharterMetaDataScreen.hx +++ b/source/funkin/editors/charter/CharterMetaDataScreen.hx @@ -134,7 +134,8 @@ class CharterMetaDataScreen extends UISubstateWindow { if (iconSprite == null) add(iconSprite = new HealthIcon()); iconSprite.setIcon(icon); - iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); + var size = Std.int(150 * 0.5); + iconSprite.setUnstretchedGraphicSize(size, size, true); iconSprite.updateHitbox(); iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2)); iconSprite.scrollFactor.set(1, 1); diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index f094f5c1..cc0bb4be 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -93,7 +93,8 @@ class CharterStrumline extends UISprite { for (i=>icon in icons) { var healthIcon = new HealthIcon(Character.getIconFromCharName(icon)); var newScale = 0.6 - (icons.length / 20); - healthIcon.scale.x = healthIcon.scale.y = healthIcon.defaultScale * newScale; + 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; diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index 915a1862..fe018979 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -194,7 +194,8 @@ class CharacterButton extends UIButton { super(x, y, "", null, 250, 54); charIcon = new HealthIcon(Character.getIconFromCharName(char)); - charIcon.scale.set(charIcon.defaultScale * 0.3, charIcon.defaultScale * 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); @@ -207,7 +208,7 @@ class CharacterButton extends UIButton { textBox.onChange = function(char:String) { char = Character.getIconFromCharName(char); charIcon.setIcon(char); - charIcon.scale.set(charIcon.defaultScale * 0.3, charIcon.defaultScale * 0.3); + charIcon.setUnstretchedGraphicSize(size, size, true); charIcon.updateHitbox(); charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2); } diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index 31123dc2..007c8f6c 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -235,7 +235,8 @@ class SongCreationScreen extends UISubstateWindow { if (iconSprite == null) menuDataGroup.add(iconSprite = new HealthIcon()); iconSprite.setIcon(icon); - iconSprite.scale.set(iconSprite.defaultScale * 0.5, iconSprite.defaultScale * 0.5); + var size = Std.int(150 * 0.5); + iconSprite.setUnstretchedGraphicSize(size, size, true); iconSprite.updateHitbox(); iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2)); iconSprite.scrollFactor.set(1, 1); diff --git a/source/funkin/game/HealthIcon.hx b/source/funkin/game/HealthIcon.hx index 7900ca3b..8c636376 100644 --- a/source/funkin/game/HealthIcon.hx +++ b/source/funkin/game/HealthIcon.hx @@ -162,14 +162,18 @@ class HealthIcon extends FunkinSprite animateAtlas = null; // reset if (this.animated) - loadSprite(Paths.image(iconPath)); + 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.floor(assetW / assetH); + 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))); @@ -238,8 +242,20 @@ class HealthIcon extends FunkinSprite var animName = node.get("name"); - if (node.exists("offsetX") || node.exists("offsetY")) - addOffset(animName, Std.parseFloat(node.get("offsetX")).getDefault(0), Std.parseFloat(node.get("offsetY")).getDefault(0)); + 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; @@ -299,6 +315,8 @@ class HealthIcon extends FunkinSprite } 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"]; diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index ec9cbeb9..0542ab17 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -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); diff --git a/source/funkin/options/type/IconOption.hx b/source/funkin/options/type/IconOption.hx index 94923185..385dd563 100644 --- a/source/funkin/options/type/IconOption.hx +++ b/source/funkin/options/type/IconOption.hx @@ -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); } } \ No newline at end of file From cd8327604b9201d186ad648df1e85f5e943f0693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8D=9A=7ENex?= <87421482+NexIsDumb@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:40:35 +0200 Subject: [PATCH 5/9] shaders now support nullable values and FlxGraphic --- source/funkin/backend/shaders/FunkinShader.hx | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx index 152ca6c3..bd9d5dd1 100644 --- a/source/funkin/backend/shaders/FunkinShader.hx +++ b/source/funkin/backend/shaders/FunkinShader.hx @@ -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 FlxGraphic) bitmap = val.bitmap; + else if (val is BitmapData) bitmap = val; + else { throw new ShaderTypeException(name, Type.getClass(val), BitmapData); return null; } - field.input = cast val; + field.input = bitmap; } } From 0f2e542ca0d88e433ccdab8ed3f7e6b6f9963bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8D=9A=7ENex?= <87421482+NexIsDumb@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:49:26 +0200 Subject: [PATCH 6/9] lemme actually invert these oop cuz setting an flxgraphic is rarer, at least we let it be a little more optimized in most of cases --- source/funkin/backend/shaders/FunkinShader.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx index bd9d5dd1..a2833e81 100644 --- a/source/funkin/backend/shaders/FunkinShader.hx +++ b/source/funkin/backend/shaders/FunkinShader.hx @@ -518,8 +518,8 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { // shader input!! var bitmap:BitmapData; if (!isNotNull) bitmap = null; - else if (val is FlxGraphic) bitmap = val.bitmap; 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; From 315c5e007c517f7f48e5d9fb3cdbc3fd40433839 Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Wed, 23 Apr 2025 12:51:12 -0700 Subject: [PATCH 7/9] added `onScriptSetup` so now instead of using `onScriptCreated` in HScript, you can now use this function whent the script is actually initalized!!!! LETS FUCKING GO!!! (#618) --- source/funkin/backend/scripting/HScript.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/funkin/backend/scripting/HScript.hx b/source/funkin/backend/scripting/HScript.hx index 0597a32f..ba0f4b77 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -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() { From 75e934fd40950fe8755f6f1b960f346b5de69001 Mon Sep 17 00:00:00 2001 From: Hero Date: Fri, 2 May 2025 00:45:26 +0300 Subject: [PATCH 8/9] Difficulty based scripts (#623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes possible things like `songs/dadbattle/scripts/erect/`. * exclusive difficulty scripts * fixing a small ordering bug + optimization * actually, allowing both scripts to exist * im a super fucking dumbass * SPACES!!! * suggestion from sen --------- Co-authored-by: ⍚~Nex <87421482+NexIsDumb@users.noreply.github.com> --- source/funkin/game/PlayState.hx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 58ea592d..15d9027d 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -609,10 +609,11 @@ class PlayState extends MusicBeatState // case "": // ADD YOUR HARDCODED SCRIPTS HERE! default: - var scriptsFolders:Array = ['songs/${SONG.meta.name.toLowerCase()}/scripts', 'data/charts/', 'songs/']; + var normal = 'songs/${SONG.meta.name.toLowerCase()}/scripts'; + var scriptsFolders:Array = [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); From b5b604e580ae61948fa645d86c3df25d656638e6 Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Fri, 2 May 2025 02:05:22 +0200 Subject: [PATCH 9/9] Bug fix: Fix issues with extra libraries --- source/funkin/backend/assets/AssetsLibraryList.hx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/funkin/backend/assets/AssetsLibraryList.hx b/source/funkin/backend/assets/AssetsLibraryList.hx index 49447997..2afa2e3a 100644 --- a/source/funkin/backend/assets/AssetsLibraryList.hx +++ b/source/funkin/backend/assets/AssetsLibraryList.hx @@ -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 }