193 lines
7.6 KiB
Haxe
193 lines
7.6 KiB
Haxe
package funkin.game.cutscenes.dialogue;
|
|
|
|
import flixel.addons.text.FlxTypeText;
|
|
import flixel.sound.FlxSound;
|
|
import funkin.backend.scripting.Script;
|
|
import funkin.backend.scripting.events.PlayAnimContext;
|
|
import funkin.backend.scripting.events.PlayAnimEvent;
|
|
import funkin.backend.scripting.events.dialogue.*;
|
|
import haxe.xml.Access;
|
|
|
|
class DialogueBox extends FunkinSprite {
|
|
public var dialogueBoxData:Access;
|
|
public var positions:Map<String, CharPosDef> = [];
|
|
public var dialogueEnded:Bool = false; // Using text._typing is also fair but it doesnt check for eventual opening anims! - Nex
|
|
|
|
public var nextSFX:String = Paths.sound('dialogue/next');
|
|
public var defaultTextTypeSFX:Array<FlxSound>;
|
|
public var text:FlxTypeText;
|
|
|
|
public var defPath:String = 'dialogue/boxes/';
|
|
public var dialogueBoxScript:Script;
|
|
public var cutscene:DialogueCutscene = DialogueCutscene.cutscene;
|
|
|
|
public function new(name:String) {
|
|
super();
|
|
var textTypeSFX:String = Paths.sound('dialogue/text'); // Default if xml doesnt have it - Nex
|
|
|
|
dialogueBoxScript = Script.create(Paths.script('data/' + defPath + name));
|
|
dialogueBoxScript.setParent(this);
|
|
dialogueBoxScript.load();
|
|
|
|
var event = EventManager.get(DialogueBoxStructureEvent).recycle(name, textTypeSFX, null);
|
|
dialogueBoxScript.call('create', [event]);
|
|
if(event.cancelled) return; // No idea the pyscho that would do this but hey, it can happen - Nex
|
|
|
|
try {
|
|
event.dialogueBoxData = new Access(Xml.parse(Assets.getText(Paths.xml(defPath + event.name))).firstElement());
|
|
dialogueBoxScript.call('structureLoaded', [event]);
|
|
if(event.cancelled) return;
|
|
name = event.name;
|
|
textTypeSFX = event.textTypeSFX;
|
|
dialogueBoxData = event.dialogueBoxData;
|
|
|
|
if(!dialogueBoxData.has.sprite) dialogueBoxData.x.set("sprite", name);
|
|
XMLUtil.loadSpriteFromXML(this, dialogueBoxData, defPath, NONE);
|
|
visible = false;
|
|
|
|
var preX:Float = x;
|
|
screenCenter(X); x += preX;
|
|
y += FlxG.height - height;
|
|
|
|
if(dialogueBoxData.has.textSound) textTypeSFX = Paths.sound(dialogueBoxData.att.textSound);
|
|
if(dialogueBoxData.has.nextSound) nextSFX = Paths.sound(dialogueBoxData.att.nextSound);
|
|
|
|
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"
|
|
};
|
|
}
|
|
|
|
var textNode = dialogueBoxData.node.text;
|
|
if (textNode == null)
|
|
throw "The dialog box XML requires one text element.";
|
|
text = new FlxTypeText(
|
|
textNode.has.x ? Std.parseFloat(textNode.att.x).getDefault(0) : 0,
|
|
FlxG.height - (textNode.has.y ? Std.parseFloat(textNode.att.y).getDefault(0) : 0),
|
|
textNode.has.width ? Std.parseInt(textNode.att.width).getDefault(FlxG.width) : FlxG.width, "");
|
|
text.color = textNode.getAtt("color").getColorFromDynamic().getDefault(0xFF000000);
|
|
text.size = Std.parseInt(textNode.att.size).getDefault(20);
|
|
text.font = Paths.font(textNode.getAtt("font").getDefault("vcr.ttf"));
|
|
text.antialiasing = textNode.getAtt("antialiasing").getDefault("false") == "true";
|
|
if(textNode.has.borderStyle) {
|
|
text.borderStyle = switch(textNode.att.borderStyle.trim().toLowerCase()) {
|
|
case "none": NONE;
|
|
case "shadow": SHADOW;
|
|
case "outline_fast": OUTLINE_FAST;
|
|
default: OUTLINE;
|
|
}
|
|
text.borderQuality = Std.parseFloat(textNode.getAtt("borderQuality")).getDefault(1);
|
|
text.shadowOffset.x = Std.parseFloat(textNode.getAtt("shadowOffsetX")).getDefault(1);
|
|
text.shadowOffset.y = Std.parseFloat(textNode.getAtt("shadowOffsetY")).getDefault(1);
|
|
text.borderSize = Std.parseFloat(textNode.getAtt("borderSize")).getDefault(1);
|
|
text.borderColor = textNode.getAtt("borderColor").getColorFromDynamic().getDefault(0xFFFFFFFF);
|
|
}
|
|
} catch(e) {
|
|
active = false;
|
|
Logs.trace('Couldn\'t load dialogue box "$name": ${e.toString()}', ERROR);
|
|
}
|
|
defaultTextTypeSFX = [FlxG.sound.load(textTypeSFX)];
|
|
FlxG.sound.cache(nextSFX);
|
|
dialogueBoxScript.call("postCreate");
|
|
}
|
|
|
|
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);
|
|
dialogueBoxScript.call("onPlayAnim", [event]);
|
|
if(event.cancelled) return;
|
|
|
|
super.playAnim(event.animName, event.force, event.context, event.reverse, event.startingFrame);
|
|
}
|
|
|
|
public override function beatHit(curBeat:Int) {
|
|
super.beatHit(curBeat);
|
|
dialogueBoxScript.call("beatHit", [curBeat]);
|
|
}
|
|
|
|
public override function update(elapsed:Float) {
|
|
super.update(elapsed);
|
|
dialogueBoxScript.call("update", [elapsed]);
|
|
}
|
|
|
|
public function popupChar(char:DialogueCharacter, force:Bool = false) {
|
|
var event = EventManager.get(DialogueBoxCharPopupEvent).recycle(char, force);
|
|
dialogueBoxScript.call("popupChar", [event]);
|
|
if (event.cancelled || event.char == null || !active) return;
|
|
var pos = positions[event.char.positionName];
|
|
if (pos == null) return;
|
|
|
|
event.char.show((FlxG.width / 2) + pos.x, FlxG.height - pos.y, null, event.force);
|
|
flipX = pos.flipBubble;
|
|
dialogueBoxScript.call("postPopupChar", [event]);
|
|
}
|
|
|
|
public function playBubbleAnim(bubble:String, suffix:String = '', text:String = '', ?format:Array<XMLUtil.TextFormat>, speed:Float = 0.05, ?customSFX:FlxSound, ?customTypeSFX:Array<FlxSound>, setTextAfter:Bool = false, allowDefault:Bool = true) {
|
|
var event = EventManager.get(DialogueBoxPlayBubbleEvent).recycle(bubble, suffix, text, format, speed, customSFX, customTypeSFX, setTextAfter, allowDefault);
|
|
dialogueBoxScript.call("playBubbleAnim", [event]);
|
|
if(event.cancelled) return;
|
|
|
|
if(event.customSFX != null) event.customSFX.play();
|
|
else if(event.allowDefault) FlxG.sound.play(nextSFX);
|
|
var idk:Void->Void = () -> {
|
|
if(text != null && text.trim().length > 0) startText(event.text, event.format, event.speed, event.customTypeSFX);
|
|
else dialogueEnded = true;
|
|
}
|
|
|
|
dialogueEnded = false;
|
|
resetText();
|
|
var anim:String = event.bubble + event.suffix;
|
|
if(hasAnimation(anim)) playAnim(anim, true);
|
|
if(!event.setTextAfter) idk();
|
|
else animation.finishCallback = (name:String) -> {
|
|
if(name != anim) return;
|
|
playAnim(event.bubble);
|
|
idk(); animation.finishCallback = null;
|
|
}
|
|
|
|
visible = true;
|
|
dialogueBoxScript.call("postPlayBubbleAnim", [event]);
|
|
}
|
|
|
|
public function resetText(text:String = '', ?formats:Array<XMLUtil.TextFormat>, ?event:DialogueBoxSetTextEvent) {
|
|
if(event == null) event = EventManager.get(DialogueBoxSetTextEvent).recycle(text, formats, null, null);
|
|
dialogueBoxScript.call("resetText", [event]);
|
|
if(event.cancelled) return;
|
|
|
|
this.text.clearFormats();
|
|
if(formats != null)
|
|
this.text.autoSetFormat(formats);
|
|
|
|
this.text.resetText(event.text);
|
|
dialogueBoxScript.call("postResetText");
|
|
}
|
|
|
|
public function startText(text:String = '', ?format:Array<XMLUtil.TextFormat>, speed:Float = 0.05, ?customTypeSFX:Array<FlxSound>) {
|
|
var event = EventManager.get(DialogueBoxSetTextEvent).recycle(text, format, speed, customTypeSFX);
|
|
dialogueBoxScript.call("startText", [event]);
|
|
if(event.cancelled) return;
|
|
|
|
resetText(event.text, event.format, event);
|
|
this.text.sounds = event.customTypeSFX != null ? event.customTypeSFX : defaultTextTypeSFX;
|
|
this.text.delay = event.speed;
|
|
this.text.start(event.speed, true);
|
|
this.text.completeCallback = () -> dialogueEnded = true;
|
|
dialogueBoxScript.call("postStartText");
|
|
}
|
|
|
|
public override function destroy() {
|
|
dialogueBoxScript.call("destroy");
|
|
dialogueBoxScript.destroy();
|
|
|
|
super.destroy();
|
|
positions = null;
|
|
}
|
|
}
|
|
|
|
typedef CharPosDef = {
|
|
var x:Float;
|
|
var y:Float;
|
|
var flipBubble:Bool;
|
|
} |