diff --git a/assets/data/dialogue/boxes/default.xml b/assets/data/dialogue/boxes/default.xml new file mode 100644 index 00000000..e6fd2f4d --- /dev/null +++ b/assets/data/dialogue/boxes/default.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/data/dialogue/characters/boyfriend.xml b/assets/data/dialogue/characters/boyfriend.xml new file mode 100644 index 00000000..c5774f46 --- /dev/null +++ b/assets/data/dialogue/characters/boyfriend.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/data/dialogue/characters/dad.xml b/assets/data/dialogue/characters/dad.xml new file mode 100644 index 00000000..c5774f46 --- /dev/null +++ b/assets/data/dialogue/characters/dad.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/data/dialogue/characters/niko.xml b/assets/data/dialogue/characters/niko.xml new file mode 100644 index 00000000..c5774f46 --- /dev/null +++ b/assets/data/dialogue/characters/niko.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/dialogue/speech_bubble_talking.png b/assets/images/dialogue/boxes/default.png similarity index 100% rename from assets/images/dialogue/speech_bubble_talking.png rename to assets/images/dialogue/boxes/default.png diff --git a/assets/images/dialogue/speech_bubble_talking.xml b/assets/images/dialogue/boxes/default.xml similarity index 100% rename from assets/images/dialogue/speech_bubble_talking.xml rename to assets/images/dialogue/boxes/default.xml diff --git a/assets/images/dialogue/characters/boyfriend.png b/assets/images/dialogue/characters/boyfriend.png new file mode 100644 index 00000000..61877299 Binary files /dev/null and b/assets/images/dialogue/characters/boyfriend.png differ diff --git a/assets/images/dialogue/characters/boyfriend.xml b/assets/images/dialogue/characters/boyfriend.xml new file mode 100644 index 00000000..1192c2ba --- /dev/null +++ b/assets/images/dialogue/characters/boyfriend.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/dialogue/characters/dad.png b/assets/images/dialogue/characters/dad.png new file mode 100644 index 00000000..1580867d Binary files /dev/null and b/assets/images/dialogue/characters/dad.png differ diff --git a/assets/images/dialogue/characters/dad.xml b/assets/images/dialogue/characters/dad.xml new file mode 100644 index 00000000..7a6171ed --- /dev/null +++ b/assets/images/dialogue/characters/dad.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/dialogue/characters/niko.png b/assets/images/dialogue/characters/niko.png new file mode 100644 index 00000000..3131de36 Binary files /dev/null and b/assets/images/dialogue/characters/niko.png differ diff --git a/assets/images/dialogue/characters/niko.xml b/assets/images/dialogue/characters/niko.xml new file mode 100644 index 00000000..e7892898 --- /dev/null +++ b/assets/images/dialogue/characters/niko.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/songs/test/dialogue.xml b/assets/songs/test/dialogue.xml new file mode 100644 index 00000000..b543e9b3 --- /dev/null +++ b/assets/songs/test/dialogue.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + yo its me your enemy! how are you + + + shut up u stupid bitch + ur useless af!!! loser! + + + agreed + + + shut up! u should respect me! im your dad! + + + haha no ur not my dad loser im a dream stan i dont have one + + + HOW COULD YOU + + + ok shut up and lets rap battle now u blue midget thing or whatever idot! + + + TF U MEAN U 13' SKYSCRAPER MF BRO CANT EVEN FIT IN HIS CAR TF + + + loser! + + + no u + + + HOW COULD YOU??? THIS IS WAR NOW! + + + yo its me niko bellic from the video game "Grand Theft Auto IV" + + + NO FUCKING WAY + + + NIKO BELLIC? yo ur cousin fat + + + fuck you dimitri rascalov + + + HOW DOES HE KNOW MY REAL IDENTITY + + + ok now stfu and lets rap its getting annoying + + \ No newline at end of file diff --git a/assets/songs/test/scripts/modchart.hx b/assets/songs/test/scripts/modchart.hx index 14cf6431..c517ed5a 100644 --- a/assets/songs/test/scripts/modchart.hx +++ b/assets/songs/test/scripts/modchart.hx @@ -3,6 +3,7 @@ function create() { pixelNotesForBF = false; enablePixelUI = true; enableCameraHacks = false; + playCutscenes = true; } function postCreate() { diff --git a/assets/sounds/pixel/clickText.mp3 b/assets/sounds/dialogue/next-pixel.mp3 similarity index 100% rename from assets/sounds/pixel/clickText.mp3 rename to assets/sounds/dialogue/next-pixel.mp3 diff --git a/assets/sounds/pixel/clickText.ogg b/assets/sounds/dialogue/next-pixel.ogg similarity index 100% rename from assets/sounds/pixel/clickText.ogg rename to assets/sounds/dialogue/next-pixel.ogg diff --git a/assets/sounds/dialogue/next.mp3 b/assets/sounds/dialogue/next.mp3 new file mode 100644 index 00000000..9c512593 Binary files /dev/null and b/assets/sounds/dialogue/next.mp3 differ diff --git a/assets/sounds/dialogue/next.ogg b/assets/sounds/dialogue/next.ogg new file mode 100644 index 00000000..db5c7d2f Binary files /dev/null and b/assets/sounds/dialogue/next.ogg differ diff --git a/assets/sounds/dialogue/text-pixel.mp3 b/assets/sounds/dialogue/text-pixel.mp3 new file mode 100644 index 00000000..981d310f Binary files /dev/null and b/assets/sounds/dialogue/text-pixel.mp3 differ diff --git a/assets/sounds/dialogue/text-pixel.ogg b/assets/sounds/dialogue/text-pixel.ogg new file mode 100644 index 00000000..9746e904 Binary files /dev/null and b/assets/sounds/dialogue/text-pixel.ogg differ diff --git a/buildnumber.txt b/buildnumber.txt index 2c9e4a82..6adc804a 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -2101 \ No newline at end of file +2112 \ No newline at end of file diff --git a/source/funkin/cutscenes/DialogueCutscene.hx b/source/funkin/cutscenes/DialogueCutscene.hx index 4829f196..64d0fdb1 100644 --- a/source/funkin/cutscenes/DialogueCutscene.hx +++ b/source/funkin/cutscenes/DialogueCutscene.hx @@ -1,10 +1,101 @@ package funkin.cutscenes; +import flixel.system.FlxSound; +import flixel.FlxG; +import flixel.FlxCamera; +import openfl.utils.Assets; +import funkin.cutscenes.dialogue.*; +import haxe.xml.Access; + class DialogueCutscene extends Cutscene { public var dialoguePath:String; + public var dialogueData:Access; + + public var charMap:Map = []; + + public var dialogueLines:Array = []; + public var curLine:DialogueLine = null; + public var dialogueBox:DialogueBox; + + public var nextSFX:FlxSound; public function new(dialoguePath:String, callback:Void->Void) { super(callback); this.dialoguePath = dialoguePath; + camera = new FlxCamera(); + camera.bgColor = 0; + FlxG.cameras.add(camera, false); } + + public override function create() { + super.create(); + + try { + dialogueData = new Access(Xml.parse(Assets.getText(dialoguePath)).firstElement()); + + // Add characters + for(char in dialogueData.nodes.char) { + if (!char.has.name) continue; + if (charMap.exists(char.att.name)) + Logs.trace('2 dialogue characters share the same name (${char.att.name}, ${char.att.name}). The old character has been replaced.'); + add(charMap[char.att.name] = new DialogueCharacter(char.att.name, char.getAtt('position').getDefault('default'))); + } + + // Add lines + for(node in dialogueData.nodes.line) { + dialogueLines.push({ + text: XMLUtil.fixXMLText(node.innerHTML), + char: node.getAtt('char').getDefault('boyfriend'), + bubble: node.getAtt('bubble').getDefault('normal'), + callback: node.getAtt('callback'), + speed: node.has.speed ? Std.parseFloat(node.att.speed).getDefault(0.02) : 0.02 + }); + } + + // Add dialogue box + dialogueBox = new DialogueBox(dialogueData.getAtt("box").getDefault("default")); + add(dialogueBox); + + next(); + } catch(e) { + Logs.trace('Error while loading dialogue at ${dialoguePath}', ERROR); + close(); + } + } + + public override function update(elapsed:Float) { + super.update(elapsed); + if (controls.ACCEPT) + next(); + } + + public function next() { + if ((curLine = dialogueLines.shift()) == null) { + close(); + return; + } + + for(c in charMap) { + if (c.visible) { + // TODO: remove character smoothly + } + } + + if (charMap[curLine.char] != null) + dialogueBox.popupChar(charMap[curLine.char]); + dialogueBox.playBubbleAnim(curLine.bubble); + } + + public override function destroy() { + super.destroy(); + FlxG.cameras.remove(camera); + } +} + +typedef DialogueLine = { + var text:String; + var char:String; + var bubble:String; + var callback:String; + var speed:Float; } \ No newline at end of file diff --git a/source/funkin/cutscenes/dialogue/DialogueBox.hx b/source/funkin/cutscenes/dialogue/DialogueBox.hx new file mode 100644 index 00000000..5f608ed2 --- /dev/null +++ b/source/funkin/cutscenes/dialogue/DialogueBox.hx @@ -0,0 +1,76 @@ +package funkin.cutscenes.dialogue; + +import openfl.utils.Assets; +import flixel.FlxG; +import flixel.math.FlxPoint; +import haxe.xml.Access; + +class DialogueBox extends FunkinSprite { + public var dialogueBoxData:Access; + public var positions:Map = []; + + public function new(name:String) { + super(); + try { + dialogueBoxData = new Access(Xml.parse(Assets.getText(Paths.xml('dialogue/boxes/$name'))).firstElement()); + + loadSprite(Paths.image('dialogue/boxes/${dialogueBoxData.getAtt('sprite').getDefault(name)}')); + + for(anim in dialogueBoxData.nodes.anim) { + if (!anim.has.name) continue; + XMLUtil.addXMLAnimation(this, anim); + } + + for(pos in dialogueBoxData.nodes.charpos) { + if (!pos.has.name) continue; + positions[pos.att.name] = { + x: pos.has.x ? Std.parseFloat(pos.att.x).getDefault(0) : 0, + y: pos.has.y ? Std.parseFloat(pos.att.y).getDefault(0) : 0, + flipBubble: pos.getAtt('flipBubble') == "true" + }; + } + + antialiasing = dialogueBoxData.getAtt("antialiasing").getDefault("true") == "true"; + screenCenter(X); + y = FlxG.height - height; + if (dialogueBoxData.has.x) x += Std.parseFloat(dialogueBoxData.att.x).getDefault(0); + if (dialogueBoxData.has.y) y += Std.parseFloat(dialogueBoxData.att.y).getDefault(0); + visible = false; + } catch(e) { + active = false; + Logs.trace('Couldn\'t load dialogue box "$name": ${e.toString()}', ERROR); + } + } + + public function popupChar(char:DialogueCharacter) { + if (!active) return; + var pos = positions[char.positionName]; + if (pos == null) return; + + if (!char.visible) { + char.alpha = 0; + char.visible = true; + char.setPosition(pos.x, pos.y + 100); + char.show(pos.y); + } + } + + public function playBubbleAnim(bubble:String) { + if (hasAnimation('$bubble-open')) + playAnim('$bubble-open', true); + else + playAnim(bubble); + visible = true; + } + + public override function destroy() { + super.destroy(); + positions = null; + } +} + +typedef CharPosDef = { + var x:Float; + var y:Float; + var flipBubble:Bool; +} \ No newline at end of file diff --git a/source/funkin/cutscenes/dialogue/DialogueCharacter.hx b/source/funkin/cutscenes/dialogue/DialogueCharacter.hx new file mode 100644 index 00000000..e9c32c23 --- /dev/null +++ b/source/funkin/cutscenes/dialogue/DialogueCharacter.hx @@ -0,0 +1,38 @@ +package funkin.cutscenes.dialogue; + +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import openfl.utils.Assets; +import haxe.xml.Access; + +class DialogueCharacter extends FunkinSprite { + public var charData:Access; + public var curTween:FlxTween; + public var positionName:String; + + public function new(name:String, position:String) { + super(); + this.positionName = position; + try { + charData = new Access(Xml.parse(Assets.getText(Paths.xml('dialogue/characters/$name'))).firstElement()); + loadSprite(Paths.image('dialogue/characters/${charData.getAtt('sprite').getDefault(name)}')); + antialiasing = charData.getAtt('antialiasing').getDefault('true') == 'true'; + offset.set( + charData.has.x ? Std.parseFloat(charData.att.x).getDefault(0) : 0, + charData.has.y ? Std.parseFloat(charData.att.y).getDefault(0) : 0); + scale.scale(charData.has.scale ? Std.parseFloat(charData.att.scale).getDefault(1) : 1); + for(anim in charData.nodes.anim) + XMLUtil.addXMLAnimation(this, anim, true); + } catch(e) { + Logs.trace('Failed to load dialogue character $name: ${e.toString()}', ERROR); + } + visible = false; + } + + public function show(y:Float) { + if (curTween != null) + curTween.cancel(); + + curTween = FlxTween.tween(this, {alpha: 1, y: y}, 0.5, {ease: FlxEase.cubeOut}); + } +} \ No newline at end of file diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 7682c3f5..6c9106f4 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -696,18 +696,24 @@ class PlayState extends MusicBeatState if (playCutscenes) { var videoCutscene = Paths.video('${PlayState.SONG.song.toLowerCase()}-cutscene'); var videoCutsceneAlt = Paths.file('songs/${PlayState.SONG.song.toLowerCase()}/cutscene.mp4'); + var dialogue = Paths.file('songs/${PlayState.SONG.song.toLowerCase()}/dialogue.xml'); persistentUpdate = false; if (cutscene != null) { openSubState(new ScriptedCutscene(cutscene, function() { startCountdown(); })); + } else if (Assets.exists(dialogue)) { + FlxTransitionableState.skipNextTransIn = true; + openSubState(new DialogueCutscene(dialogue, function() { + startCountdown(); + })); } else if (Assets.exists(videoCutsceneAlt)) { FlxTransitionableState.skipNextTransIn = true; openSubState(new VideoCutscene(videoCutsceneAlt, function() { startCountdown(); })); persistentDraw = false; - }else if (Assets.exists(videoCutscene)) { + } else if (Assets.exists(videoCutscene)) { FlxTransitionableState.skipNextTransIn = true; openSubState(new VideoCutscene(videoCutscene, function() { startCountdown(); diff --git a/source/funkin/utils/XMLUtil.hx b/source/funkin/utils/XMLUtil.hx index 76092647..9937a458 100644 --- a/source/funkin/utils/XMLUtil.hx +++ b/source/funkin/utils/XMLUtil.hx @@ -170,6 +170,11 @@ class XMLUtil { } return MISSING_PROPERTY; } + + public static inline function fixXMLText(text:String) { + var v:String; + return [for(l in text.split("\n")) if ((v = l.trim()) != "") v].join("\n"); + } } typedef AnimData = {