diff --git a/assets/data/dialogue/characters/senpai.hx b/assets/data/dialogue/characters/senpai.hx index cd17b115..1ea774d7 100644 --- a/assets/data/dialogue/characters/senpai.hx +++ b/assets/data/dialogue/characters/senpai.hx @@ -1,6 +1,6 @@ function postHide() { if(curTween != null) { - if(animation.curAnim?.name == 'angry-show') curTween.cancel(); + if(getAnimName() == 'angry-show') curTween.cancel(); else curTween.percent = 1; } -} \ No newline at end of file +} diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index e10bf5c5..ff67c29c 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -114,17 +114,28 @@ class Paths return scriptPath; } - static public function chart(song:String, ?difficulty:String = "normal"):String { + static public function chart(song:String, ?difficulty:String = "normal"):String + { difficulty = difficulty.toLowerCase(); song = song.toLowerCase(); return getPath('songs/$song/charts/$difficulty.json', null); } - static public function character(character:String):String { + inline static public function character(character:String):String + { return getPath('data/characters/$character.xml', null); } + /** + * Gets the name of a registered font. + * @param font The font's path (if it's already passed as a font name, the same name will be returned) + */ + inline static public function getFontName(font:String) + { + return OpenFlAssets.exists(font, FONT) ? OpenFlAssets.getFont(font).fontName : font; + } + inline static public function font(key:String) { return getPath('fonts/$key'); diff --git a/source/funkin/backend/scripting/events/CharacterNodeEvent.hx b/source/funkin/backend/scripting/events/CharacterNodeEvent.hx new file mode 100644 index 00000000..9577a64f --- /dev/null +++ b/source/funkin/backend/scripting/events/CharacterNodeEvent.hx @@ -0,0 +1,21 @@ +package funkin.backend.scripting.events; + +import funkin.game.Character; +import haxe.xml.Access; + +final class CharacterNodeEvent extends CancellableEvent { + /** + * The character instance + */ + public var character:Character; + + /** + * The node which is currently being parsed + */ + public var node:Access; + + /** + * The name of the node, quicker access than e.node.name + */ + public var name:String; +} \ No newline at end of file diff --git a/source/funkin/backend/scripting/events/CharacterXMLEvent.hx b/source/funkin/backend/scripting/events/CharacterXMLEvent.hx new file mode 100644 index 00000000..e4f107fd --- /dev/null +++ b/source/funkin/backend/scripting/events/CharacterXMLEvent.hx @@ -0,0 +1,16 @@ +package funkin.backend.scripting.events; + +import funkin.game.Character; +import haxe.xml.Access; + +final class CharacterXMLEvent extends CancellableEvent { + /** + * The character instance + */ + public var character:Character; + + /** + * The xml + */ + public var xml:Access; +} \ No newline at end of file diff --git a/source/funkin/backend/system/modules/AudioSwitchFix.hx b/source/funkin/backend/system/modules/AudioSwitchFix.hx index 232c1eb8..86ebcb0a 100644 --- a/source/funkin/backend/system/modules/AudioSwitchFix.hx +++ b/source/funkin/backend/system/modules/AudioSwitchFix.hx @@ -62,6 +62,7 @@ class AudioSwitchFix { } } +@:dox(hide) typedef PlayingSound = { var sound:FlxSound; var time:Float; diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index fe02f614..640fedcc 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -596,7 +596,7 @@ class CoolUtil var fmt = new FlxTextFormat(); fmt.format.color = Reflect.hasField(fmtt, "color") ? FlxColor.fromString(fmtt.color) : text.color; - fmt.format.font = Reflect.hasField(fmtt, "font") ? Paths.font(fmtt.font) : text.font; + fmt.format.font = Reflect.hasField(fmtt, "font") ? Paths.getFontName(Paths.font(fmtt.font)) : text.font; fmt.format.size = Reflect.hasField(fmtt, "size") ? Std.parseInt(fmtt.size) : text.size; fmt.format.italic = Reflect.hasField(fmtt, "italic") ? fmtt.italic == "true" : text.italic; fmt.format.bold = Reflect.hasField(fmtt, "bold") ? fmtt.bold == "true" : text.bold; diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx index 30bbc380..b75e18cd 100644 --- a/source/funkin/backend/utils/XMLUtil.hx +++ b/source/funkin/backend/utils/XMLUtil.hx @@ -6,6 +6,13 @@ import funkin.backend.FunkinSprite.XMLAnimType; import funkin.backend.FunkinSprite; import funkin.game.Character; import funkin.backend.system.ErrorCode; +import funkin.backend.FunkinSprite.XMLAnimType; +import flixel.util.FlxColor; +import haxe.xml.Access; +import funkin.backend.scripting.Script; +import funkin.backend.scripting.DummyScript; +import funkin.backend.scripting.ScriptPack; +import flixel.util.typeLimit.OneOfTwo; import funkin.backend.system.interfaces.IOffsetCompatible; import haxe.xml.Access; @@ -328,6 +335,51 @@ class XMLUtil { } } +class XMLImportedScriptInfo { + public var path:String; + public var shortLived:Bool = false; + public var loadBefore:Bool = true; + public var importStageSprites:Bool = false; // maybe will change later?? - Nex + public var parentScriptPack:ScriptPack = null; + + public function new(path:String, parentScriptPack:ScriptPack) { + this.parentScriptPack = parentScriptPack; + this.path = path; + } + + public function getScript():Script + return parentScriptPack == null ? null : parentScriptPack.getByPath(path); + + public static function prepareInfos(node:Access, parentScriptPack:ScriptPack, ?onScriptPreLoad:XMLImportedScriptInfo->Void):XMLImportedScriptInfo { + if (!node.has.script || parentScriptPack == null) return null; + + var folder = node.getAtt("folder").getDefault("data/scripts/"); + if (!folder.endsWith("/")) folder += "/"; + + var path = Paths.script(folder + node.getAtt("script")); + var daScript = Script.create(path); + if (daScript is DummyScript) { + Logs.trace('Script Extension at ${path} does not exist.', ERROR); + return null; + } + + var infos = new XMLImportedScriptInfo(daScript.path, parentScriptPack); + infos.shortLived = node.getAtt("isShortLived") == "true" || node.getAtt("shortLived") == "true"; + infos.importStageSprites = node.getAtt("importStageSprites") == "true"; + @:privateAccess infos.loadBefore = shouldLoadBefore(node); + + if (onScriptPreLoad != null) onScriptPreLoad(infos); + parentScriptPack.add(daScript); + daScript.set("scriptInfo", infos); + daScript.load(); + + return infos; + } + + @:dox(hide) public static inline function shouldLoadBefore(node:Access):Bool + return node.getAtt("loadBefore") != "false"; +} + typedef AnimData = { var name:String; var anim:String; diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index f35dbc8d..ee16850e 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -8,6 +8,9 @@ import flixel.util.FlxColor; import funkin.backend.FunkinSprite; import funkin.backend.scripting.DummyScript; import funkin.backend.scripting.Script; +import funkin.backend.scripting.ScriptPack; +import funkin.backend.scripting.events.CharacterNodeEvent; +import funkin.backend.scripting.events.CharacterXMLEvent; import funkin.backend.scripting.events.DanceEvent; import funkin.backend.scripting.events.DirectionAnimEvent; import funkin.backend.scripting.events.PlayAnimContext; @@ -44,8 +47,13 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public var cameraOffset:FlxPoint = FlxPoint.get(0, 0); public var globalOffset:FlxPoint = FlxPoint.get(0, 0); - public var script:Script; public var xml:Access; + public var scripts:ScriptPack; + public var xmlImportedScripts:Array = []; + public var script(default, set):Script; + + public function prepareInfos(node:Access) + return XMLImportedScriptInfo.prepareInfos(node, scripts, (infos) -> xmlImportedScripts.push(infos)); public var idleSuffix:String = ""; public var stunned(default, set):Bool = false; @@ -72,16 +80,15 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset script = Script.create(Paths.script(Path.withoutExtension(Paths.xml('characters/$curCharacter')), null, true)); else script = new DummyScript(curCharacter); - script.setParent(this); script.load(); buildCharacter(xml); - script.call("create"); + scripts.call("create"); if (script == null) script = new DummyScript(curCharacter); - script.call("postCreate"); + scripts.call("postCreate"); } @:noCompletion var __swappedLeftRightAnims:Bool = false; @@ -118,7 +125,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset override function update(elapsed:Float) { super.update(elapsed); - script.call("update", [elapsed]); + scripts.call("update", [elapsed]); if (stunned) { __stunnedTime += elapsed; if (__stunnedTime > 5 / 60) @@ -137,7 +144,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if(debugMode) return; var event = EventManager.get(DanceEvent).recycle(danced); - script.call("onDance", [event]); + scripts.call("onDance", [event]); if (event.cancelled) return; if (isDanceLeftDanceRight) @@ -167,14 +174,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset */ public var danceOnBeat:Bool = true; public override function beatHit(curBeat:Int) { - script.call("beatHit", [curBeat]); + scripts.call("beatHit", [curBeat]); if (danceOnBeat && (curBeat + beatOffset) % beatInterval == 0 && !__lockAnimThisFrame) tryDance(); } public override function stepHit(curStep:Int) - script.call("stepHit", [curStep]); + scripts.call("stepHit", [curStep]); @:noCompletion var __reverseDrawProcedure:Bool = false; public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect { @@ -237,7 +244,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public override function playAnim(AnimName:String, ?Force:Bool, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { var event = EventManager.get(PlayAnimEvent).recycle(AnimName, Force, Reversed, Frame, Context); - script.call("onPlayAnim", [event]); + scripts.call("onPlayAnim", [event]); if (event.cancelled) return; super.playAnim(event.animName, event.force, event.context, event.reverse, event.startingFrame); @@ -252,16 +259,16 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset var event = EventManager.get(PointEvent).recycle( midpoint.x + (isPlayer ? -100 : 150) + globalOffset.x + cameraOffset.x, midpoint.y - 100 + globalOffset.y + cameraOffset.y); - script.call("onGetCamPos", [event]); + scripts.call("onGetCamPos", [event]); midpoint.put(); return new FlxPoint(event.x, event.y); } public override function destroy() { - if(script != null) { - script.call('destroy'); - script.destroy(); + if(scripts != null) { + scripts.call('destroy'); + scripts.destroy(); } super.destroy(); @@ -304,9 +311,18 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset } public inline function buildCharacter(xml:Access) { - this.xml = xml; // Modders wassup :D + for(node in xml.elements) + switch(node.name) { + case "use-extension" | "extension" | "ext": + if (!XMLImportedScriptInfo.shouldLoadBefore(node)) continue; + prepareInfos(node); + } + + xml = scripts.event("onCharacterXMLParsed", EventManager.get(CharacterXMLEvent).recycle(this, xml)).xml; + sprite = curCharacter; spriteAnimType = BEAT; + this.xml = xml; // Modders wassup :D if (xml.x.exists("isPlayer")) playerOffsets = (xml.x.get("isPlayer") == "true"); if (xml.x.exists("x")) globalOffset.x = Std.parseFloat(xml.x.get("x")); @@ -330,9 +346,19 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (hasInterval) beatInterval = Std.parseInt(xml.x.get("interval")); loadSprite(Paths.image('characters/$sprite')); + for(node in xml.elements) { + switch(node.name) { + case "anim": + XMLUtil.addXMLAnimation(this, node); + case "use-extension" | "extension" | "ext": + if (XMLImportedScriptInfo.shouldLoadBefore(node)) continue; + prepareInfos(node); + default: + // nothing + } - for (anim in xml.nodes.anim) - XMLUtil.addXMLAnimation(this, anim); + scripts.event("onCharacterNodeParsed", EventManager.get(CharacterNodeEvent).recycle(this, node, node.name)); + } for (attribute in xml.x.attributes()) if (!characterProperties.contains(attribute)) @@ -340,6 +366,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset fixChar(__switchAnims, !hasInterval); dance(); + + for (info in xmlImportedScripts) if (info.shortLived) { + var script = info.getScript(); + if (script == null) continue; + + scripts.remove(script); + script.destroy(); + } } public static var characterProperties:Array = [ @@ -423,6 +457,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset return stunned = b; } + // ---- Backwards compat ---- // Interval at which the character will dance (higher number = slower dance) @:noCompletion public var danceInterval(get, set):Int; @:noCompletion private function set_danceInterval(v:Int) @@ -430,6 +465,22 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset @:noCompletion private function get_danceInterval() return beatInterval; + private function set_script(script:Script):Script { + if (scripts == null) (scripts = new ScriptPack("Character")).setParent(this); + + var lastIndex = scripts.scripts.indexOf(this.script); + if(lastIndex >= 0) { + if(script == null) // last != null && new == null + scripts.scripts.splice(lastIndex, 1); + else // last != null && new != null + scripts.scripts[lastIndex] = script; + } else if(script != null) // last == null + scripts.insert(0, script); + + return this.script = script; + } + // ---- end of Backwards compat ---- + public static var FALLBACK_CHARACTER:String = "bf"; public static var FALLBACK_DEAD_CHARACTER:String = "bf-dead"; diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index cd5d2d2f..393f499d 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -873,12 +873,12 @@ class PlayState extends MusicBeatState Conductor.songPosition = 0; Conductor.songPosition -= Conductor.crochet * introLength - Conductor.songOffset; - var swagCounter:Int = 0; - - startTimer = new FlxTimer().start(Conductor.crochet / 1000, function(tmr:FlxTimer) - { - countdown(swagCounter++); - }, introLength); + if(introLength > 0) { + var swagCounter:Int = 0; + startTimer = new FlxTimer().start(Conductor.crochet / 1000, (tmr:FlxTimer) -> { + countdown(swagCounter++); + }, introLength); + } scripts.call("onPostStartCountdown"); } @@ -1055,7 +1055,7 @@ class PlayState extends MusicBeatState vocals.pause(); } - if (!startTimer.finished) + if (startTimer != null && !startTimer.finished) startTimer.active = false; } @@ -1075,7 +1075,7 @@ class PlayState extends MusicBeatState resyncVocals(); } - if (!startTimer.finished) + if (startTimer != null && !startTimer.finished) startTimer.active = true; paused = false; diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index 7e26936c..2d939244 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -2,6 +2,9 @@ package funkin.game; import flixel.FlxState; import flixel.math.FlxPoint; +import haxe.xml.Access; +import funkin.backend.utils.XMLUtil.XMLImportedScriptInfo; +import funkin.backend.system.interfaces.IBeatReceiver; import funkin.backend.scripting.DummyScript; import funkin.backend.scripting.Script; import funkin.backend.scripting.events.StageNodeEvent; @@ -30,6 +33,9 @@ class Stage extends FlxBasic implements IBeatReceiver { public function setStagesSprites(script:Script) for (k=>e in stageSprites) script.set(k, e); + public function prepareInfos(node:Access) + return XMLImportedScriptInfo.prepareInfos(node, PlayState.instance.scripts, (infos) -> xmlImportedScripts.push(infos)); + public function new(stage:String, ?state:FlxState) { super(); @@ -136,7 +142,7 @@ class Stage extends FlxBasic implements IBeatReceiver { PlayState.instance.add(PlayState.instance.comboGroup); PlayState.instance.comboGroup; case "use-extension" | "extension" | "ext": - if (__shouldLoadBefore(node) || XMLImportedScriptInfo.prepareInfos(node, this) == null) continue; + if (XMLImportedScriptInfo.shouldLoadBefore(node) || prepareInfos(node) == null) continue; null; default: null; } @@ -201,13 +207,10 @@ class Stage extends FlxBasic implements IBeatReceiver { @:dox(hide) private function __pushNcheckNode(array:Array, node:Access) { array.push(node); - if ((node.name == "use-extension" || node.name == "extension" || node.name == "ext") && __shouldLoadBefore(node)) - XMLImportedScriptInfo.prepareInfos(node, this); + if ((node.name == "use-extension" || node.name == "extension" || node.name == "ext") && XMLImportedScriptInfo.shouldLoadBefore(node)) + prepareInfos(node); } - @:dox(hide) private static inline function __shouldLoadBefore(node:Access):Bool - return node.getAtt("loadBefore") != "false"; - public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo):StageCharPos { var charPos = new StageCharPos(); charPos.visible = charPos.active = false; @@ -317,45 +320,4 @@ typedef StageCharPosInfo = { var y:Float; var flip:Bool; var scroll:Float; -} - -class XMLImportedScriptInfo { - public var path:String; - public var shortLived:Bool = false; - public var loadBefore:Bool = true; - public var importStageSprites:Bool = false; - - public function new(path:String) - this.path = path; - - public function getScript():Script - return PlayState.instance == null ? null : PlayState.instance.scripts.getByPath(path); - - public static function prepareInfos(node:Access, ?stage:Stage):XMLImportedScriptInfo { - if (!node.has.script || PlayState.instance == null) return null; - - var folder = node.getAtt("folder").getDefault("data/scripts/"); - if (!folder.endsWith("/")) folder += "/"; - - var path = Paths.script(folder + node.getAtt("script")); - var daScript = Script.create(path); - if (daScript is DummyScript) { - var msg = 'Script Extension at ${path} does not exist'; - if (stage != null) msg += ' through stage "${stage.stageName}"'; - Logs.trace(msg + ".", ERROR); - return null; - } - - var infos = new XMLImportedScriptInfo(daScript.path); - infos.shortLived = node.getAtt("isShortLived") == "true"; - infos.importStageSprites = node.getAtt("importStageSprites") == "true"; - @:privateAccess infos.loadBefore = Stage.__shouldLoadBefore(node); - - if (stage != null) stage.xmlImportedScripts.push(infos); - PlayState.instance.scripts.add(daScript); - daScript.set("scriptInfo", infos); - daScript.load(); - - return infos; - } } \ No newline at end of file diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx index cca45b51..83d9c855 100644 --- a/source/funkin/game/StrumLine.hx +++ b/source/funkin/game/StrumLine.hx @@ -318,7 +318,7 @@ class StrumLine extends FlxTypedGroup { babyArrow.scrollSpeed = data.scrollSpeed; var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix); - event.__doAnimation = !MusicBeatState.skipTransIn; + event.__doAnimation = !MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true); event = PlayState.instance.scripts.event("onStrumCreation", event); if (!event.cancelled) { diff --git a/source/funkin/menus/credits/CreditsCodename.hx b/source/funkin/menus/credits/CreditsCodename.hx index 3e09c990..8ed343b6 100644 --- a/source/funkin/menus/credits/CreditsCodename.hx +++ b/source/funkin/menus/credits/CreditsCodename.hx @@ -11,9 +11,11 @@ using StringTools; class CreditsCodename extends funkin.options.OptionsScreen { public var error:Bool = false; public var author:String = "FNF-CNE-Devs"; + public var totalContributions:Int = 0; + public var mainDevCol:FlxColor = 0xFF9C35D5; public var minContrCol:FlxColor = 0xFFB4A7DA; - public var totalContributions:Int = 0; + public var contribFormats:Array = []; public override function new() { @@ -58,12 +60,17 @@ class CreditsCodename extends funkin.options.OptionsScreen { if(parent == null || parent.treeParent == null) return; var text:String = parent.treeParent.pathDesc.text; parent.treeParent.pathDesc.text = ""; - parent.treeParent.pathDesc.applyMarkup(text, [ + parent.treeParent.pathDesc.applyMarkup(text, contribFormats = [ new FlxTextFormatMarkerPair(new FlxTextFormat(mainDevCol), '*'), new FlxTextFormatMarkerPair(new FlxTextFormat(FlxColor.interpolate(minContrCol, mainDevCol, Options.contributors[curSelected].contributions / totalContributions)), '~') ]); } + override function close() { + super.close(); + for (frmt in contribFormats) parent.treeParent.pathDesc.removeFormat(frmt.format); + } + public function checkUpdate():Bool { var curTime:Float = Date.now().getTime(); if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits - Nex