Tobee_Night_Funkin_PSYCH/source/editors/DialogueEditorState.hx
2021-11-05 17:34:27 -04:00

546 lines
No EOL
18 KiB
Haxe

package editors;
#if desktop
import Discord.DiscordClient;
#end
import flixel.FlxG;
import flixel.FlxSprite;
import flixel.addons.display.FlxGridOverlay;
import flixel.addons.transition.FlxTransitionableState;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxMath;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import flixel.system.FlxSound;
import flixel.addons.ui.FlxInputText;
import flixel.addons.ui.FlxUI9SliceSprite;
import flixel.addons.ui.FlxUI;
import flixel.addons.ui.FlxUICheckBox;
import flixel.addons.ui.FlxUIInputText;
import flixel.addons.ui.FlxUINumericStepper;
import flixel.addons.ui.FlxUITabMenu;
import flixel.ui.FlxButton;
import openfl.net.FileReference;
import openfl.events.Event;
import openfl.events.IOErrorEvent;
import flash.net.FileFilter;
import haxe.Json;
import DialogueBoxPsych;
import lime.system.Clipboard;
#if sys
import sys.io.File;
#end
using StringTools;
class DialogueEditorState extends MusicBeatState
{
var character:DialogueCharacter;
var box:FlxSprite;
var daText:Alphabet;
var selectedText:FlxText;
var animText:FlxText;
var defaultLine:DialogueLine;
var dialogueFile:DialogueFile = null;
override function create() {
persistentUpdate = persistentDraw = true;
FlxG.camera.bgColor = FlxColor.fromHSL(0, 0, 0.5);
defaultLine = {
portrait: DialogueCharacter.DEFAULT_CHARACTER,
expression: 'talk',
text: DEFAULT_TEXT,
boxState: DEFAULT_BUBBLETYPE,
speed: 0.05
};
dialogueFile = {
dialogue: [
copyDefaultLine()
]
};
character = new DialogueCharacter();
character.scrollFactor.set();
add(character);
box = new FlxSprite(70, 370);
box.frames = Paths.getSparrowAtlas('speech_bubble');
box.scrollFactor.set();
box.antialiasing = ClientPrefs.globalAntialiasing;
box.animation.addByPrefix('normal', 'speech bubble normal', 24);
box.animation.addByPrefix('angry', 'AHH speech bubble', 24);
box.animation.addByPrefix('center', 'speech bubble middle', 24);
box.animation.addByPrefix('center-angry', 'AHH Speech Bubble middle', 24);
box.animation.play('normal', true);
box.setGraphicSize(Std.int(box.width * 0.9));
box.updateHitbox();
add(box);
addEditorBox();
FlxG.mouse.visible = true;
var addLineText:FlxText = new FlxText(10, 10, FlxG.width - 20, 'Press O to remove the current dialogue line, Press P to add another line after the current one.', 8);
addLineText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
addLineText.scrollFactor.set();
add(addLineText);
selectedText = new FlxText(10, 32, FlxG.width - 20, '', 8);
selectedText.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
selectedText.scrollFactor.set();
add(selectedText);
animText = new FlxText(10, 62, FlxG.width - 20, '', 8);
animText.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
animText.scrollFactor.set();
add(animText);
changeText();
super.create();
}
var UI_box:FlxUITabMenu;
function addEditorBox() {
var tabs = [
{name: 'Dialogue Line', label: 'Dialogue Line'},
];
UI_box = new FlxUITabMenu(null, tabs, true);
UI_box.resize(250, 190);
UI_box.x = FlxG.width - UI_box.width - 10;
UI_box.y = 10;
UI_box.scrollFactor.set();
UI_box.alpha = 0.8;
addDialogueLineUI();
add(UI_box);
}
var characterInputText:FlxUIInputText;
var lineInputText:FlxUIInputText;
var angryCheckbox:FlxUICheckBox;
var speedStepper:FlxUINumericStepper;
function addDialogueLineUI() {
var tab_group = new FlxUI(null, UI_box);
tab_group.name = "Dialogue Line";
characterInputText = new FlxUIInputText(10, 20, 80, DialogueCharacter.DEFAULT_CHARACTER, 8);
blockPressWhileTypingOn.push(characterInputText);
speedStepper = new FlxUINumericStepper(10, characterInputText.y + 40, 0.005, 0.05, 0, 0.5, 3);
angryCheckbox = new FlxUICheckBox(speedStepper.x + 120, speedStepper.y, null, null, "Angry Textbox", 200);
angryCheckbox.callback = function()
{
updateTextBox();
dialogueFile.dialogue[curSelected].boxState = (angryCheckbox.checked ? 'angry' : 'normal');
};
lineInputText = new FlxUIInputText(10, speedStepper.y + 45, 200, DEFAULT_TEXT, 8);
blockPressWhileTypingOn.push(lineInputText);
var loadButton:FlxButton = new FlxButton(20, lineInputText.y + 30, "Load Dialogue", function() {
loadDialogue();
});
var saveButton:FlxButton = new FlxButton(loadButton.x + 120, loadButton.y, "Save Dialogue", function() {
saveDialogue();
});
tab_group.add(new FlxText(10, speedStepper.y - 18, 0, 'Interval/Speed (ms):'));
tab_group.add(new FlxText(10, characterInputText.y - 18, 0, 'Character:'));
tab_group.add(new FlxText(10, lineInputText.y - 18, 0, 'Text:'));
tab_group.add(characterInputText);
tab_group.add(angryCheckbox);
tab_group.add(speedStepper);
tab_group.add(lineInputText);
tab_group.add(lineInputText);
tab_group.add(loadButton);
tab_group.add(saveButton);
UI_box.addGroup(tab_group);
}
function copyDefaultLine():DialogueLine {
var copyLine:DialogueLine = {
portrait: defaultLine.portrait,
expression: defaultLine.expression,
text: defaultLine.text,
boxState: defaultLine.boxState,
speed: defaultLine.speed
};
return copyLine;
}
function updateTextBox() {
box.flipX = false;
var isAngry:Bool = angryCheckbox.checked;
var anim:String = isAngry ? 'angry' : 'normal';
switch(character.jsonFile.dialogue_pos) {
case 'left':
box.flipX = true;
case 'center':
if(isAngry) {
anim = 'center-angry';
} else {
anim = 'center';
}
}
box.animation.play(anim, true);
DialogueBoxPsych.updateBoxOffsets(box);
}
function reloadCharacter() {
character.frames = Paths.getSparrowAtlas('dialogue/' + character.jsonFile.image);
character.jsonFile = character.jsonFile;
character.reloadAnimations();
character.setGraphicSize(Std.int(character.width * DialogueCharacter.DEFAULT_SCALE * character.jsonFile.scale));
character.updateHitbox();
character.x = DialogueBoxPsych.LEFT_CHAR_X;
character.y = DialogueBoxPsych.DEFAULT_CHAR_Y;
switch(character.jsonFile.dialogue_pos) {
case 'right':
character.x = FlxG.width - character.width + DialogueBoxPsych.RIGHT_CHAR_X;
case 'center':
character.x = FlxG.width / 2;
character.x -= character.width / 2;
}
character.x += character.jsonFile.position[0];
character.y += character.jsonFile.position[1];
character.playAnim(); //Plays random animation
characterAnimSpeed();
if(character.animation.curAnim != null) {
animText.text = 'Animation: ' + character.jsonFile.animations[curAnim].anim + ' (' + (curAnim + 1) +' / ' + character.jsonFile.animations.length + ') - Press W or S to scroll';
} else {
animText.text = 'ERROR! NO ANIMATIONS FOUND';
}
}
private static var DEFAULT_TEXT:String = "coolswag";
private static var DEFAULT_SPEED:Float = 0.05;
private static var DEFAULT_BUBBLETYPE:String = "normal";
function reloadText(speed:Float = 0.05) {
if(daText != null) {
daText.killTheTimer();
daText.kill();
remove(daText);
daText.destroy();
}
if(Math.isNaN(speed) || speed < 0.001) speed = 0.0;
var textToType:String = lineInputText.text;
if(textToType == null || textToType.length < 1) textToType = ' ';
daText = new Alphabet(DialogueBoxPsych.DEFAULT_TEXT_X, DialogueBoxPsych.DEFAULT_TEXT_Y, textToType, false, true, speed, 0.7);
add(daText);
if(speed > 0) {
if(character.jsonFile.animations.length > curAnim && character.jsonFile.animations[curAnim] != null) {
character.playAnim(character.jsonFile.animations[curAnim].anim);
}
characterAnimSpeed();
}
#if desktop
// Updating Discord Rich Presence
var rpcText:String = lineInputText.text;
if(rpcText == null || rpcText.length < 1) rpcText = '(Empty)';
if(rpcText.length < 3) rpcText += ' '; //Fixes a bug on RPC that triggers an error when the text is too short
DiscordClient.changePresence("Dialogue Editor", rpcText);
#end
}
override function getEvent(id:String, sender:Dynamic, data:Dynamic, ?params:Array<Dynamic>) {
if(id == FlxUIInputText.CHANGE_EVENT && (sender is FlxUIInputText)) {
if(sender == characterInputText) {
character.reloadCharacterJson(characterInputText.text);
reloadCharacter();
updateTextBox();
if(character.jsonFile.animations.length > 0) {
curAnim = 0;
if(character.jsonFile.animations.length > curAnim && character.jsonFile.animations[curAnim] != null) {
character.playAnim(character.jsonFile.animations[curAnim].anim, daText.finishedText);
animText.text = 'Animation: ' + character.jsonFile.animations[curAnim].anim + ' (' + (curAnim + 1) +' / ' + character.jsonFile.animations.length + ') - Press W or S to scroll';
} else {
animText.text = 'ERROR! NO ANIMATIONS FOUND';
}
characterAnimSpeed();
}
dialogueFile.dialogue[curSelected].portrait = characterInputText.text;
} else if(sender == lineInputText) {
reloadText(0);
dialogueFile.dialogue[curSelected].text = lineInputText.text;
}
} else if(id == FlxUINumericStepper.CHANGE_EVENT && (sender == speedStepper)) {
reloadText(speedStepper.value);
dialogueFile.dialogue[curSelected].speed = speedStepper.value;
if(Math.isNaN(dialogueFile.dialogue[curSelected].speed) || dialogueFile.dialogue[curSelected].speed == null || dialogueFile.dialogue[curSelected].speed < 0.001) {
dialogueFile.dialogue[curSelected].speed = 0.0;
}
}
}
var curSelected:Int = 0;
var curAnim:Int = 0;
var blockPressWhileTypingOn:Array<FlxUIInputText> = [];
var transitioning:Bool = false;
override function update(elapsed:Float) {
if(transitioning) {
super.update(elapsed);
return;
}
if(character.animation.curAnim != null) {
if(daText.finishedText) {
if(character.animationIsLoop() && character.animation.curAnim.finished) {
character.playAnim(character.animation.curAnim.name, true);
}
} else if(character.animation.curAnim.finished) {
character.animation.curAnim.restart();
}
}
var blockInput:Bool = false;
for (inputText in blockPressWhileTypingOn) {
if(inputText.hasFocus) {
FlxG.sound.muteKeys = [];
FlxG.sound.volumeDownKeys = [];
FlxG.sound.volumeUpKeys = [];
blockInput = true;
if(FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.V && Clipboard.text != null) { //Copy paste
inputText.text = ClipboardAdd(inputText.text);
inputText.caretIndex = inputText.text.length;
getEvent(FlxUIInputText.CHANGE_EVENT, inputText, null, []);
}
if(FlxG.keys.justPressed.ENTER) {
if(inputText == lineInputText) {
inputText.text += '\\n';
inputText.caretIndex += 2;
} else {
inputText.hasFocus = false;
}
}
break;
}
}
if(!blockInput) {
FlxG.sound.muteKeys = TitleState.muteKeys;
FlxG.sound.volumeDownKeys = TitleState.volumeDownKeys;
FlxG.sound.volumeUpKeys = TitleState.volumeUpKeys;
if(FlxG.keys.justPressed.SPACE) {
reloadText(speedStepper.value);
}
if(FlxG.keys.justPressed.ESCAPE) {
MusicBeatState.switchState(new editors.MasterEditorMenu());
FlxG.sound.playMusic(Paths.music('freakyMenu'), 1);
transitioning = true;
}
var negaMult:Array<Int> = [1, -1];
var controlAnim:Array<Bool> = [FlxG.keys.justPressed.W, FlxG.keys.justPressed.S];
var controlText:Array<Bool> = [FlxG.keys.justPressed.D, FlxG.keys.justPressed.A];
for (i in 0...controlAnim.length) {
if(controlAnim[i] && character.jsonFile.animations.length > 0) {
curAnim += negaMult[i];
if(curAnim < 0) curAnim = character.jsonFile.animations.length - 1;
else if(curAnim >= character.jsonFile.animations.length) curAnim = 0;
var animToPlay:String = character.jsonFile.animations[curAnim].anim;
if(character.dialogueAnimations.exists(animToPlay)) {
character.playAnim(animToPlay, daText.finishedText);
dialogueFile.dialogue[curSelected].expression = animToPlay;
}
animText.text = 'Animation: ' + animToPlay + ' (' + (curAnim + 1) +' / ' + character.jsonFile.animations.length + ') - Press W or S to scroll';
}
if(controlText[i]) {
changeText(negaMult[i]);
}
}
if(FlxG.keys.justPressed.O) {
dialogueFile.dialogue.remove(dialogueFile.dialogue[curSelected]);
if(dialogueFile.dialogue.length < 1) //You deleted everything, dumbo!
{
dialogueFile.dialogue = [
copyDefaultLine()
];
}
changeText();
} else if(FlxG.keys.justPressed.P) {
dialogueFile.dialogue.insert(curSelected + 1, copyDefaultLine());
changeText(1);
}
}
super.update(elapsed);
}
function changeText(add:Int = 0) {
curSelected += add;
if(curSelected < 0) curSelected = dialogueFile.dialogue.length - 1;
else if(curSelected >= dialogueFile.dialogue.length) curSelected = 0;
var curDialogue:DialogueLine = dialogueFile.dialogue[curSelected];
characterInputText.text = curDialogue.portrait;
lineInputText.text = curDialogue.text;
angryCheckbox.checked = (curDialogue.boxState == 'angry');
speedStepper.value = curDialogue.speed;
curAnim = 0;
character.reloadCharacterJson(characterInputText.text);
reloadCharacter();
updateTextBox();
reloadText(curDialogue.speed);
var leLength:Int = character.jsonFile.animations.length;
if(leLength > 0) {
for (i in 0...leLength) {
var leAnim:DialogueAnimArray = character.jsonFile.animations[i];
if(leAnim != null && leAnim.anim == curDialogue.expression) {
curAnim = i;
break;
}
}
character.playAnim(character.jsonFile.animations[curAnim].anim, daText.finishedText);
animText.text = 'Animation: ' + character.jsonFile.animations[curAnim].anim + ' (' + (curAnim + 1) +' / ' + leLength + ') - Press W or S to scroll';
} else {
animText.text = 'ERROR! NO ANIMATIONS FOUND';
}
characterAnimSpeed();
selectedText.text = 'Line: (' + (curSelected + 1) + ' / ' + dialogueFile.dialogue.length + ') - Press A or D to scroll';
}
function characterAnimSpeed() {
if(character.animation.curAnim != null) {
var speed:Float = speedStepper.value;
var rate:Float = 24 - (((speed - 0.05) / 5) * 480);
if(rate < 12) rate = 12;
else if(rate > 48) rate = 48;
character.animation.curAnim.frameRate = rate;
}
}
function ClipboardAdd(prefix:String = ''):String {
if(prefix.toLowerCase().endsWith('v')) //probably copy paste attempt
{
prefix = prefix.substring(0, prefix.length-1);
}
var text:String = prefix + Clipboard.text.replace('\n', '');
return text;
}
var _file:FileReference = null;
function loadDialogue() {
var jsonFilter:FileFilter = new FileFilter('JSON', 'json');
_file = new FileReference();
_file.addEventListener(Event.SELECT, onLoadComplete);
_file.addEventListener(Event.CANCEL, onLoadCancel);
_file.addEventListener(IOErrorEvent.IO_ERROR, onLoadError);
_file.browse([jsonFilter]);
}
function onLoadComplete(_):Void
{
_file.removeEventListener(Event.SELECT, onLoadComplete);
_file.removeEventListener(Event.CANCEL, onLoadCancel);
_file.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError);
#if sys
var fullPath:String = null;
var jsonLoaded = cast Json.parse(Json.stringify(_file)); //Exploit(???) for accessing a private variable
if(jsonLoaded.__path != null) fullPath = jsonLoaded.__path; //I'm either a genious or dangerously dumb
if(fullPath != null) {
var rawJson:String = File.getContent(fullPath);
if(rawJson != null) {
var loadedDialog:DialogueFile = cast Json.parse(rawJson);
if(loadedDialog.dialogue != null && loadedDialog.dialogue.length > 0) //Make sure it's really a dialogue file
{
var cutName:String = _file.name.substr(0, _file.name.length - 5);
trace("Successfully loaded file: " + cutName);
dialogueFile = loadedDialog;
changeText();
_file = null;
return;
}
}
}
_file = null;
#else
trace("File couldn't be loaded! You aren't on Desktop, are you?");
#end
}
/**
* Called when the save file dialog is cancelled.
*/
function onLoadCancel(_):Void
{
_file.removeEventListener(Event.SELECT, onLoadComplete);
_file.removeEventListener(Event.CANCEL, onLoadCancel);
_file.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError);
_file = null;
trace("Cancelled file loading.");
}
/**
* Called if there is an error while saving the gameplay recording.
*/
function onLoadError(_):Void
{
_file.removeEventListener(Event.SELECT, onLoadComplete);
_file.removeEventListener(Event.CANCEL, onLoadCancel);
_file.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError);
_file = null;
trace("Problem loading file");
}
function saveDialogue() {
var data:String = Json.stringify(dialogueFile, "\t");
if (data.length > 0)
{
_file = new FileReference();
_file.addEventListener(Event.COMPLETE, onSaveComplete);
_file.addEventListener(Event.CANCEL, onSaveCancel);
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
_file.save(data, "dialogue.json");
}
}
function onSaveComplete(_):Void
{
_file.removeEventListener(Event.COMPLETE, onSaveComplete);
_file.removeEventListener(Event.CANCEL, onSaveCancel);
_file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
_file = null;
FlxG.log.notice("Successfully saved file.");
}
/**
* Called when the save file dialog is cancelled.
*/
function onSaveCancel(_):Void
{
_file.removeEventListener(Event.COMPLETE, onSaveComplete);
_file.removeEventListener(Event.CANCEL, onSaveCancel);
_file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
_file = null;
}
/**
* Called if there is an error while saving the gameplay recording.
*/
function onSaveError(_):Void
{
_file.removeEventListener(Event.COMPLETE, onSaveComplete);
_file.removeEventListener(Event.CANCEL, onSaveCancel);
_file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
_file = null;
FlxG.log.error("Problem saving file");
}
}