package net.alcuria.umbracraft.editor.widget; import net.alcuria.umbracraft.Game; import net.alcuria.umbracraft.definitions.npc.ScriptPageDefinition; import net.alcuria.umbracraft.editor.Drawables; import net.alcuria.umbracraft.editor.modules.Module; import net.alcuria.umbracraft.engine.scripts.BattleScriptCommand; import net.alcuria.umbracraft.engine.scripts.BlockCommand; import net.alcuria.umbracraft.engine.scripts.CameraTargetScriptCommand; import net.alcuria.umbracraft.engine.scripts.ChangeDirectionScriptCommand; import net.alcuria.umbracraft.engine.scripts.ChangePoseScriptCommand; import net.alcuria.umbracraft.engine.scripts.ConditionalCommand; import net.alcuria.umbracraft.engine.scripts.ControlVariableCommand; import net.alcuria.umbracraft.engine.scripts.EmptyCommand; import net.alcuria.umbracraft.engine.scripts.EntityVisibilityScriptCommand; import net.alcuria.umbracraft.engine.scripts.FlagScriptCommand; import net.alcuria.umbracraft.engine.scripts.IndicatorOperationCommand; import net.alcuria.umbracraft.engine.scripts.ItemChangeScriptCommand; import net.alcuria.umbracraft.engine.scripts.LogScriptCommand; import net.alcuria.umbracraft.engine.scripts.MessageScriptCommand; import net.alcuria.umbracraft.engine.scripts.MoveScriptCommand; import net.alcuria.umbracraft.engine.scripts.PauseScriptCommand; import net.alcuria.umbracraft.engine.scripts.PlaySoundScriptCommand; import net.alcuria.umbracraft.engine.scripts.RecoverScriptCommand; import net.alcuria.umbracraft.engine.scripts.RemoveEntityCommand; import net.alcuria.umbracraft.engine.scripts.ScriptCommand; import net.alcuria.umbracraft.engine.scripts.SetAnimationCollectionScriptCommand; import net.alcuria.umbracraft.engine.scripts.ShowAnimationScriptCommand; import net.alcuria.umbracraft.engine.scripts.TeleportScriptCommand; import net.alcuria.umbracraft.engine.scripts.TintScreenCommand; import net.alcuria.umbracraft.listeners.Listener; import net.alcuria.umbracraft.listeners.TypeListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Array; import com.kotcrab.vis.ui.widget.VisLabel; import com.kotcrab.vis.ui.widget.VisTextField; import com.kotcrab.vis.ui.widget.color.ColorPicker; import com.kotcrab.vis.ui.widget.color.ColorPickerListener; public class ScriptCommandWidget extends Module<ScriptCommand> { public static enum Commands { BATTLE("Battle", BattleScriptCommand.class), CAMERA_TARGET("Camera Target", CameraTargetScriptCommand.class), COLLECTION("Animation Collection", SetAnimationCollectionScriptCommand.class), // CONDITIONAL("Conditional", ConditionalCommand.class), DIRECTION("Change Direction", ChangeDirectionScriptCommand.class), FLAG("Flag", FlagScriptCommand.class), // INDICATOR("Indicator Operations", IndicatorOperationCommand.class), ITEM("Change Items/Inventory", ItemChangeScriptCommand.class), LOG("Log", LogScriptCommand.class), MESSAGE("Message", MessageScriptCommand.class), // MOVE("Move", MoveScriptCommand.class), PAUSE("Pause", PauseScriptCommand.class), POSE("Change Pose", ChangePoseScriptCommand.class), // RECOVER("Heal/Recover Party", RecoverScriptCommand.class), REMOVE("Remove Entity", RemoveEntityCommand.class), SHOW_ANIM("Show Animation", ShowAnimationScriptCommand.class), // SOUND("Play Sound", PlaySoundScriptCommand.class), TELEPORT("Teleport", TeleportScriptCommand.class), TINT("Tint Screen", TintScreenCommand.class), // VARIABLE("Control Variable", ControlVariableCommand.class), VISIBILITY("Entity Visibility", EntityVisibilityScriptCommand.class); public static Commands from(Class<? extends ScriptCommand> clazz) { for (Commands c : Commands.values()) { if (c.clazz.equals(clazz)) { return c; } } return null; } public static Commands from(final String name) { for (Commands c : Commands.values()) { if (c.name.equals(name)) { return c; } } return null; } public static Array<String> getAll() { return new Array<String>() { { for (Commands c : Commands.values()) { add(c.name); } } }; } /** Given a {@link ScriptCommand}, gets the {@link String} representation * @param command a {@link ScriptCommand} * @return the {@link String} name */ public static String getNameFrom(ScriptCommand command) { for (Commands c : Commands.values()) { if (c.clazz == command.getClass()) { return c.name; } } return null; } private Class<?> clazz; private String name; private Commands(String name, Class<?> clazz) { this.name = name; this.clazz = clazz; } public Class<?> getCommandClass() { return clazz; } public ScriptCommand getCommandInstance() { try { return (ScriptCommand) clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return null; } } private static boolean consumedDown = false; private static ScriptCommand copiedCommand; private final ColorPicker colorPicker = new ColorPicker(); private final ScriptCommand command; private final ScriptPageWidget commandsWidget; private final Table content, popup, buttonTable = new Table(), popupFields = new Table(), colorPickerTable = new Table(); private ScriptCommand createdCommand; private ScriptCommandWidget nextWidget; private final ScriptPageDefinition page; private SuggestionWidget suggestionsWidget; public ScriptCommandWidget(ScriptPageWidget commandsWidget, Table content, Table popup, ScriptPageDefinition page, ScriptCommand command) { this.command = command != null ? command : new MessageScriptCommand("Empty"); this.popup = popup; this.page = page; this.content = content; this.commandsWidget = commandsWidget; colorPicker.setListener(new ColorPickerListener() { @Override public void canceled() { } @Override public void finished(Color newColor) { if (createdCommand instanceof TintScreenCommand) { ((TintScreenCommand) createdCommand).color = newColor.toString(); } } }); } public void addActor() { final VisLabel label = new VisLabel("<> " + (command != null ? command.getName() : "")); content.add(new Table() { { add(new Table() { { add(label); add().expandX().fillX(); setTouchable(Touchable.enabled); addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { ScriptPageWidget.selected.clear(); Game.log(command != null ? command.getName() : command + ""); ScriptPageWidget.selected.add(command); if (getTapCount() > 1) { createPopup("Insert Command", null); } } }); } }).expandX().fill().left().row(); } @Override public void act(float delta) { super.act(delta); // without this, weird stuff happens with multiple down keys triggering. okay. if (consumedDown && !Gdx.input.isKeyPressed(Keys.DOWN)) { consumedDown = false; } // update bg based on this widget's selected state setBackground(ScriptPageWidget.selected.contains(command) ? Drawables.get("yellow") : Drawables.get("black")); label.setColor(ScriptPageWidget.selected.contains(command) ? Color.BLACK : Color.WHITE); // handle keys if (command != null && ScriptPageWidget.selected.contains(command) && !popup.isVisible()) { if (Gdx.input.isKeyJustPressed(Keys.SPACE)) { createPopup("Edit Command", Commands.getNameFrom(command)); populate(popupFields, command.getClass(), command, populateConfig()); specialPopulate(Commands.from(command.getClass())); buttonTable.clear(); buttonTable.add(WidgetUtils.button("Update", commandUpdated())); } else if (!(command instanceof EmptyCommand) && (Gdx.input.isKeyJustPressed(Keys.DEL) || Gdx.input.isKeyJustPressed(Keys.BACKSPACE))) { deleteSelectedCommand(); } else if (Gdx.input.isKeyJustPressed(Keys.UP)) { final ScriptCommand parent = page.getPrevious(page.command, command); if (parent != null) { ScriptPageWidget.selected.clear(); ScriptPageWidget.selected.add(parent); } } else if (Gdx.input.isKeyJustPressed(Keys.DOWN) && !consumedDown) { consumedDown = true; if (command instanceof BlockCommand) { ScriptPageWidget.selected.clear(); ScriptPageWidget.selected.add(((BlockCommand) command).block); } else if (command.getNext() != null) { ScriptPageWidget.selected.clear(); ScriptPageWidget.selected.add(command.getNext()); } } else if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && Gdx.input.isKeyJustPressed(Keys.X) && !(command instanceof EmptyCommand)) { copiedCommand = command.copy(); deleteSelectedCommand(); } else if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && Gdx.input.isKeyJustPressed(Keys.C) && !(command instanceof EmptyCommand)) { copiedCommand = command.copy(); } else if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && Gdx.input.isKeyJustPressed(Keys.V) && copiedCommand != null) { createdCommand = copiedCommand; Listener commandCreated = commandCreated(); commandCreated.invoke(); copiedCommand = createdCommand.copy(); } } } }).expandX().fill().row(); WidgetUtils.divider(content, "blue"); // CONDITIONAL command if (command instanceof BlockCommand) { content.add(new Table() { { final BlockCommand blockCommand = (BlockCommand) command; ScriptCommandWidget conditionalWidget = new ScriptCommandWidget(commandsWidget, this, popup, page, blockCommand.block); conditionalWidget.addActor(); } }).expandX().fill().left().padLeft(20).row(); } // ELSE case if (command instanceof ConditionalCommand && ((ConditionalCommand) command).includeElse) { content.add(new Table() { { add(new VisLabel("Else:")).expandX().left().row(); WidgetUtils.divider(this, "blue"); setBackground(Drawables.get("black")); } }).expandX().fill().left().row(); content.add(new Table() { { final ConditionalCommand conditional = (ConditionalCommand) command; ScriptCommandWidget conditionalWidget = new ScriptCommandWidget(commandsWidget, this, popup, page, conditional.elseBlock); conditionalWidget.addActor(); } }).expandX().fill().left().padLeft(20).row(); } // NEXT command if (command.getNext() != null) { nextWidget = new ScriptCommandWidget(commandsWidget, content, popup, page, command.getNext()); nextWidget.addActor(); } } private Listener closePopup() { return new Listener() { @Override public void invoke() { popup.setVisible(false); } }; }; private Listener commandCreated() { return new Listener() { @Override public void invoke() { // insertion Game.log("Setting our new commands next to: " + (command != null ? command.getName() : "null")); createdCommand.setNext(command); ScriptCommand previous = null; previous = page.getPrevious(page.command, command); Game.log("Parent: " + (previous != null ? previous.getName() : "null")); Game.log("INSERTING: createdCommand: " + (createdCommand != null ? createdCommand.getName() : "null") + " command:" + (command != null ? command.getName() : "null") + " parent: " + (previous != null ? previous : "null")); if (previous != null) { if (previous instanceof BlockCommand && ((BlockCommand) previous).block == command) { ((BlockCommand) previous).block = createdCommand; Game.log("set block / conditional body"); } else if (previous instanceof ConditionalCommand && ((ConditionalCommand) previous).includeElse && ((ConditionalCommand) previous).elseBlock == command) { ((ConditionalCommand) previous).elseBlock = createdCommand; Game.log("set conditional else"); } else { previous.setNext(createdCommand); Game.log("set standard"); } } else { // no parent, this new command now becomes the HEAD page.command = createdCommand; } popup.setVisible(false); commandsWidget.setPage(); } }; } private Listener commandUpdated() { return new Listener() { @Override public void invoke() { // update popup.setVisible(false); commandsWidget.setPage(); } }; } private void createPopup(final String title, final String defCommand) { popup.setVisible(true); popup.clear(); popupFields.clear(); popup.setBackground(Drawables.get("black")); WidgetUtils.popupTitle(popup, title, closePopup()); suggestionsWidget = new SuggestionWidget(Commands.getAll(), 130, true); popup.add(new Table() { { add(new VisLabel("Enter Command:")); add(suggestionsWidget.getActor()); if (defCommand != null) { suggestionsWidget.getTextField().setText(defCommand); } if (title.contains("Edit")) { // lol dw bout it suggestionsWidget.getTextField().setDisabled(true); } } }).row(); suggestionsWidget.addSelectListener(createPopupFields(suggestionsWidget.getTextField())); suggestionsWidget.setGenericPopulate(createPopupFields(suggestionsWidget.getTextField())); popup.add(popupFields).expand().fill().row(); popup.add(buttonTable); } private Listener createPopupFields(final VisTextField textField) { return new Listener() { private String last = ""; @Override public void invoke() { // see if we should really repopulate if (!last.equals(textField.getText())) { Game.log("Command changed. Attempting rebuild."); // see if we have a real command Commands localCommand = Commands.from(textField.getText()); if (localCommand != null) { popupFields.clear(); if (createdCommand == null) { Game.log("Setting new command"); createdCommand = localCommand.getCommandInstance(); } populate(popupFields, localCommand.getCommandClass(), createdCommand, populateConfig()); specialPopulate(localCommand); buttonTable.clear(); buttonTable.add(WidgetUtils.button("Create", commandCreated())); } else { popupFields.clear(); createdCommand = null; } } last = textField.getText(); } }; } private void deleteSelectedCommand() { Game.log("Deleting..."); final ScriptCommand next = command.getNext(); final ScriptCommand parent = page.getPrevious(page.command, command); if (parent != null) { parent.setNext(next); } else { // no parent, this new command now becomes the HEAD page.command = next; } commandsWidget.setPage(); } @Override public String getTitle() { return "Title"; } /** Updates the filters if we change the "type" dropdown * @param textField * @return */ private TypeListener<String> listener(final VisTextField textField) { return new TypeListener<String>() { @Override public void invoke(String type) { if ("type".equals(type)) { Commands localCommand = Commands.from(textField.getText()); popupFields.clear(); populate(popupFields, localCommand.getCommandClass(), createdCommand, populateConfig()); specialPopulate(localCommand); } } }; } @Override public void populate(Table content) { } private PopulateConfig populateConfig() { final PopulateConfig config = new PopulateConfig(); config.cols = 1; config.textFieldWidth = 300; config.labelWidth = 100; // depending on whether or not we edit or create one or the other createdCommand/command is null ugghh idk why right now but this fixes stuff if (createdCommand == null) { createdCommand = command; } config.suggestions = createdCommand.getSuggestions(); config.filter = createdCommand.getFilter(); if (suggestionsWidget != null) { config.listener = listener(suggestionsWidget.getTextField()); } return config; } private void specialPopulate(Commands localCommand) { // now we can do special populates here that the default populate doesn't handle // example: color picker for TintScreenCommand if (localCommand == Commands.TINT) { String color = ((TintScreenCommand) createdCommand).color; final Color finalColor = color != null ? Color.valueOf(color) : Color.WHITE; popupFields.row(); popupFields.add(new Image(Drawables.get("white")) { { addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { colorPickerTable.clear(); colorPickerTable.add(colorPicker); colorPicker.setColor(finalColor); colorPicker.fadeIn(); } }); } @Override public void act(float delta) { super.act(delta); String color = ((TintScreenCommand) createdCommand).color; if (color != null && color.length() == 8) { setColor(Color.valueOf(color)); } else { setColor(Color.WHITE); } } }).size(30); popupFields.row(); popupFields.add(colorPickerTable); } else if (localCommand == Commands.MOVE || localCommand == Commands.TELEPORT) { popupFields.row(); popupFields.add(new MapPreviewWidget(null).getActor()); //FIXME: may want to see if we can intelligently crawl event commands to get the map } } }