diff --git a/forge-adventure/src/main/java/forge/adventure/editor/ActionEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/ActionEdit.java new file mode 100644 index 00000000000..e3ea16fcda3 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/ActionEdit.java @@ -0,0 +1,156 @@ +package forge.adventure.editor; + +import forge.adventure.data.DialogData; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class ActionEdit extends FormPanel { + DialogData.ActionData currentData; + + JTextField issueQuest = new JTextField(); + JTextField mapFlagName = new JTextField(); + JTextField questFlagName = new JTextField(); + JTextField advanceMapFlag = new JTextField(); + JTextField advanceQuestFlag = new JTextField(); + JTextField battleWithActorID = new JTextField(); + JTextField deleteMapObject = new JTextField(); + JTextField setColorIdentity = new JTextField(); + JSpinner addReputation = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, 1)); + JSpinner addLife = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, 1)); + JTextField POIReference = new JTextField(); + JTextField removeItem = new JTextField(); + JSpinner mapFlagValue = new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1)); + JSpinner questFlagValue = new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1)); + + private boolean updating=false; + + public ActionEdit() + { + + //todo: add info pane to explain primary usage + + add("Issue Quest:",issueQuest); + add("Set Map Flag Name:",mapFlagName); + add("Map Flag value to set:",mapFlagValue); + add("Set Quest Flag name:",questFlagName); + add("Quest Flag value to set:",questFlagValue); + + add("Advance Map Flag name:",advanceMapFlag); + add("Advance Quest Flag name:",advanceQuestFlag); + add("Battle with actor ID:",battleWithActorID); + add("Delete map object:",deleteMapObject); + add("Set color identity:",setColorIdentity); + add("Add Reputation:",addReputation); + add("Add Life:",addLife); + add("POI Reference:",POIReference); + add("Remove Item:",removeItem); + + issueQuest.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + mapFlagName.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + mapFlagValue.getModel().addChangeListener(e -> ActionEdit.this.updateAction()); + questFlagName.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + questFlagValue.getModel().addChangeListener(e -> ActionEdit.this.updateAction()); + advanceMapFlag.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + advanceQuestFlag.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + battleWithActorID.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + deleteMapObject.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + setColorIdentity.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + addLife.getModel().addChangeListener(e -> ActionEdit.this.updateAction()); + addReputation.getModel().addChangeListener(e -> ActionEdit.this.updateAction()); + POIReference.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + removeItem.getDocument().addDocumentListener(new DocumentChangeListener(ActionEdit.this::updateAction)); + + } + + private void updateAction() { + if(updating) + return; + if (currentData == null) + currentData = new DialogData.ActionData(); + + DialogData.ActionData.QuestFlag mapFlag = new DialogData.ActionData.QuestFlag(); + mapFlag.key = mapFlagName.getText(); + mapFlag.val = (int)mapFlagValue.getModel().getValue(); + currentData.setMapFlag= mapFlag; + + DialogData.ActionData.QuestFlag questFlag = new DialogData.ActionData.QuestFlag(); + questFlag.key = questFlagName.getText(); + questFlag.val = (int)questFlagValue.getModel().getValue(); + currentData.setQuestFlag= questFlag; + + currentData.issueQuest = issueQuest.getText(); //This is currently a boolean effect, eventually needs arguments + currentData.advanceMapFlag= advanceMapFlag.getText(); + currentData.advanceQuestFlag= advanceQuestFlag.getText(); + currentData.battleWithActorID= Integer.parseInt(battleWithActorID.getText()); + currentData.deleteMapObject= Integer.parseInt(deleteMapObject.getText()); + currentData.setColorIdentity= setColorIdentity.getText(); + currentData.addLife= (int) addLife.getModel().getValue(); + currentData.addMapReputation= (int)addReputation.getModel().getValue(); + currentData.POIReference= POIReference.getText(); + currentData.removeItem= removeItem.getText(); + + //These need a dedicated effect editor +// JTextField setEffect = new JTextField(); +// JTextField giveBlessing = new JTextField(); +// currentData.giveBlessing= giveBlessing.getText(); +// currentData.setEffect= setEffect.getText(); + + //These are valid pre-existing fields, but should be handled through the dedicated rewards editor +// currentData.addGold =; +// currentData.addItem=; +// currentData.grantRewards =; + + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + + } + public void addChangeListener(ChangeListener l) { + listenerList.add(ChangeListener.class, l); + } + public void setCurrentAction(DialogData.ActionData data) + { + currentData=data; + refresh(); + } + + private void refresh() { + setEnabled(currentData!=null); + if(currentData==null) + { + return; + } + updating=true; + + mapFlagName.setText(currentData.setMapFlag==null?"":currentData.setMapFlag.key); + mapFlagValue.getModel().setValue(currentData.setMapFlag==null?0:currentData.setMapFlag.val); + + questFlagName.setText(currentData.setQuestFlag==null?"":currentData.setQuestFlag.key); + questFlagValue.getModel().setValue(currentData.setQuestFlag==null?0:currentData.setQuestFlag.val); + + issueQuest.setText(currentData.issueQuest); + advanceMapFlag.setText(currentData.advanceMapFlag); + advanceQuestFlag.setText(currentData.advanceQuestFlag); + + battleWithActorID.setText("" + currentData.battleWithActorID); + deleteMapObject.setText("" + currentData.deleteMapObject); + setColorIdentity.setText(currentData.setColorIdentity); + addLife.getModel().setValue(currentData.addLife); + addReputation.getModel().setValue(currentData.addMapReputation); + + POIReference.setText(currentData.POIReference); + removeItem.setText(currentData.removeItem); + + updating=false; + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/ActionEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/ActionEditor.java new file mode 100644 index 00000000000..a680d5362d1 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/ActionEditor.java @@ -0,0 +1,141 @@ +package forge.adventure.editor; + +import forge.adventure.data.DialogData; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionListener; + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class ActionEditor extends JComponent{ + DefaultListModel model = new DefaultListModel<>(); + JList list = new JList<>(model); + JToolBar toolBar = new JToolBar("toolbar"); + ActionEdit edit=new ActionEdit(); + boolean updating; + + + public class RewardDataRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent( + JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if(!(value instanceof DialogData.ActionData)) + return label; + DialogData.ActionData action=(DialogData.ActionData) value; + StringBuilder builder=new StringBuilder(); +// if(action.type==null||action.type.isEmpty()) + builder.append("Action"); +// else +// builder.append(action.type); + label.setText(builder.toString()); + return label; + } + } + public void addButton(String name, ActionListener action) + { + JButton newButton=new JButton(name); + newButton.addActionListener(action); + toolBar.add(newButton); + + } + + public ActionEditor() + { + + list.setCellRenderer(new RewardDataRenderer()); + list.addListSelectionListener(e -> ActionEditor.this.updateEdit()); + addButton("add", e -> ActionEditor.this.addAction()); + addButton("remove", e -> ActionEditor.this.remove()); + addButton("copy", e -> ActionEditor.this.copy()); + BorderLayout layout=new BorderLayout(); + setLayout(layout); + add(list, BorderLayout.LINE_START); + add(toolBar, BorderLayout.PAGE_START); + add(edit,BorderLayout.CENTER); + + + edit.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + emitChanged(); + } + }); + } + protected void emitChanged() { + if (updating) + return; + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } + private void copy() { + + int selected=list.getSelectedIndex(); + if(selected<0) + return; + DialogData.ActionData data=new DialogData.ActionData(model.get(selected)); + model.add(model.size(),data); + } + + private void updateEdit() { + + int selected=list.getSelectedIndex(); + if(selected<0) + return; + edit.setCurrentAction(model.get(selected)); + } + + void addAction() + { + DialogData.ActionData data=new DialogData.ActionData(); + model.add(model.size(),data); + } + void remove() + { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + model.remove(selected); + } + public void setAction(DialogData.ActionData[] actions) { + + model.clear(); + if(actions==null) + return; + for (int i=0;i 0){ + continue; //handled in separate editor and joined in on save, will get duplicated if it appears here + } + model.add(i,actions[i]); + } + } + + public DialogData.ActionData[] getAction() { + + DialogData.ActionData[] action= new DialogData.ActionData[model.getSize()]; + for(int i=0;i 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/DialogEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/DialogEditor.java new file mode 100644 index 00000000000..c8d1c4857b5 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/DialogEditor.java @@ -0,0 +1,242 @@ +package forge.adventure.editor; + +import com.google.common.collect.ObjectArrays; +import forge.adventure.data.DialogData; +import forge.adventure.data.RewardData; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; + + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class DialogEditor extends JComponent{ + + private boolean updating = false; + private java.util.List allNodes = new ArrayList<>(); + + public JTextArea text =new JTextArea("(Initial dialog text)", 3, 80); + public RewardsEditor rewardsEditor = new RewardsEditor(); + public ActionEditor actionEditor = new ActionEditor(); + public DialogOptionEditor optionEditor = new DialogOptionEditor(); + public DialogTree navTree = new DialogTree(); + + public DialogEdit edit = new DialogEdit(); + + private DialogData root = new DialogData(); + private DialogData current = new DialogData(); + + public DialogEditor(){ + buildUI(); + + navTree.addSelectionListener(q -> loadNewNodeSelection()); + edit.addChangeListener(q-> acceptEdits()); + edit.addNode.addActionListener(q -> addNode()); + edit.removeNode.addActionListener(q -> removeNode()); + } + + public void loadData(DialogData rootDialogData){ + updating = true; + if (rootDialogData == null) + { + rootDialogData = new DialogData(); + } + root = rootDialogData; + navTree.loadDialog(rootDialogData); + text.setText(rootDialogData.text); + + updating = false; + } + + public DialogData getDialogData(){ + + return root; + } + + JTabbedPane tabs = new JTabbedPane(); + JToolBar conditionsToolbar = new JToolBar("conditionsToolbar"); + JToolBar actionsToolbar = new JToolBar("actionsToolbar"); + JToolBar optionsToolbar = new JToolBar("optionsToolbar"); + JToolBar tokensToolbar = new JToolBar("tokensToolbar"); + + JPanel conditionsPanel = new JPanel(); + JPanel actionsPanel = new JPanel(); + JPanel optionsPanel = new JPanel(); + JPanel rewardsPanel = new JPanel(); + JPanel tokensPanel = new JPanel(); + + class QuestTextDocumentListener implements DocumentListener { + public void changedUpdate(DocumentEvent e) { + root.text = text.getText(); + emitChanged(); + } + public void removeUpdate(DocumentEvent e) { + root.text = text.getText(); + emitChanged(); + } + public void insertUpdate(DocumentEvent e) { + root.text = text.getText(); + emitChanged(); + } + } + + public void buildUI(){ + buildTabs(); + BorderLayout layout=new BorderLayout(); + setLayout(layout); + JPanel textArea = new JPanel(); + textArea.setLayout(new FlowLayout()); + textArea.add(new JLabel("Dialog Start")); + textArea.add(text); + text.getDocument().addDocumentListener(new QuestTextDocumentListener()); + + JSplitPane splitPane = new JSplitPane(); + splitPane.setLeftComponent(navTree); + splitPane.setRightComponent(tabs); + splitPane.setResizeWeight(0.2); + splitPane.setDividerLocation(.2); + + add(textArea, BorderLayout.NORTH); + add(splitPane, BorderLayout.CENTER); + } + + public void loadNewNodeSelection(){ + updating = true; + current = navTree.getSelectedData(); + edit.currentData = navTree.getSelectedData(); + edit.refresh(root.equals(current)); + rewardsEditor.clear(); + actionEditor.clear(); + if (navTree.getSelectedData() != null) { + for (DialogData.ActionData action : navTree.getSelectedData().action) { + if (action.grantRewards != null && action.grantRewards.length > 0) + rewardsEditor.setRewards(action.grantRewards); + } + actionEditor.setAction(navTree.getSelectedData().action); + } + updating = false; + } + + public void acceptEdits(){ + if (current == null) + return; + current.name = edit.name.getText(); + current.text = edit.text.getText(); + current.locname = edit.locname.getText(); + current.loctext = edit.loctext.getText(); + root.text = text.getText(); + + DialogData.ActionData[] action = actionEditor.getAction(); + + ArrayList actionsList = new ArrayList<>(Arrays.stream(action).collect(Collectors.toList())); + + RewardData[] rewards= rewardsEditor.getRewards(); + if (rewards.length > 0) + { + DialogData.ActionData rewardAction = new DialogData.ActionData(); + rewardAction.grantRewards = rewards; + actionsList.add(rewardAction); + } + + current.action = actionsList.toArray(current.action); + + navTree.replaceCurrent(); + emitChanged(); + } + + public void addNode(){ + DialogData newNode = new DialogData(); + newNode.name = "NewResponse"; + DialogData parent = navTree.getSelectedData()!=null?navTree.getSelectedData():root; + parent.options = ObjectArrays.concat(parent.options, newNode); + navTree.loadDialog(root); + navTree.setSelectedData(newNode); + emitChanged(); + } + public void removeNode(){ + if (navTree.getSelectedData() == null) + return; + navTree.removeSelectedData(); + emitChanged(); + } + + public void buildTabs(){ + buildToolBars(); + + actionsPanel.add(actionsToolbar); + actionsPanel.add(actionEditor); + optionsPanel.add(edit); + rewardsPanel.add(rewardsEditor); + rewardsEditor.addChangeListener(e -> DialogEditor.this.acceptEdits()); + actionEditor.addChangeListener(e -> DialogEditor.this.acceptEdits()); + tokensPanel.add(tokensToolbar); + tokensPanel.add(new JLabel("Insert token editor here")); + + tabs.addTab("Options", optionsPanel); + tabs.addTab("Conditions", conditionsPanel); + tabs.addTab("Actions", actionsPanel); + tabs.addTab("Rewards", rewardsPanel); + tabs.addTab("Tokens",tokensPanel); + } + + public void buildToolBars(){ + conditionsToolbar.setFloatable(false); + actionsToolbar.setFloatable(false); + optionsToolbar.setFloatable(false); + + JButton addOption = new JButton("Add Option"); + addOption.addActionListener(e -> DialogEditor.this.addOption()); + optionsToolbar.add(addOption); + + JButton copyOption = new JButton("Copy Selected"); + copyOption.addActionListener(e -> DialogEditor.this.copyOption()); + optionsToolbar.add(copyOption); + + JButton removeOption = new JButton("Remove Selected"); + removeOption.addActionListener(e -> DialogEditor.this.removeOption()); + optionsToolbar.add(removeOption); + + } + public void addOption(){ + optionEditor.addOption(); + emitChanged(); + } + + public void copyOption(){ + emitChanged(); + } + + public void removeOption(){ + int selected=optionEditor.list.getSelectedIndex(); + if(selected<0) + return; + optionEditor.model.remove(selected); + emitChanged(); + } + + protected void emitChanged() { + if (updating) + return; + + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } + + public void addChangeListener(ChangeListener listener) { + listenerList.add(ChangeListener.class, listener); + } + +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/DialogOptionEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/DialogOptionEdit.java new file mode 100644 index 00000000000..0d9e439b3b0 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/DialogOptionEdit.java @@ -0,0 +1,85 @@ +package forge.adventure.editor; + +import forge.adventure.data.DialogData; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class DialogOptionEdit extends FormPanel { + DialogData currentData; + + JLabel nameLabel = new JLabel("Name (Player dialog / action)"); + JLabel textLabel = new JLabel("Text (Game response to Name - Leave blank to end dialog)"); + JTextArea text =new JTextArea(3,80); + JTextArea name =new JTextArea(3,80); + JButton add = new JButton(); + JButton load = new JButton(); + + private boolean updating=false; + + public DialogOptionEdit() + { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + JPanel upper = new JPanel(); + upper.setLayout(new BorderLayout()); + + upper.add(nameLabel, BorderLayout.NORTH); + upper.add(name, BorderLayout.CENTER); + add(upper); + + JPanel middle = new JPanel(); + middle.setLayout(new BorderLayout()); + middle.add(textLabel, BorderLayout.NORTH); + middle.add(text, BorderLayout.CENTER); + + add(middle); + + name.getDocument().addDocumentListener(new DocumentChangeListener(() -> DialogOptionEdit.this.updateDialog())); + text.getDocument().addDocumentListener(new DocumentChangeListener(() -> DialogOptionEdit.this.updateDialog())); + + + } + + private void updateDialog() { + if(currentData==null||updating) + return; + + currentData.text = text.getText().trim(); + currentData.name = name.getText().trim(); + + //currentData.condition = conditionEditor.getConditions(); + + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + + } + public void addChangeListener(ChangeListener l) { + listenerList.add(ChangeListener.class, l); + } + public void setCurrentOption(DialogData data) + { + currentData=data; + refresh(); + } + + private void refresh() { + setEnabled(currentData!=null); + updating=true; + + text.setText(currentData.text!=null?currentData.text:""); + name.setText(currentData.name!=null?currentData.name:""); + + updating=false; + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/DialogOptionEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/DialogOptionEditor.java new file mode 100644 index 00000000000..73793b4644e --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/DialogOptionEditor.java @@ -0,0 +1,139 @@ +package forge.adventure.editor; + +import forge.adventure.data.DialogData; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionListener; + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class DialogOptionEditor extends JComponent{ + DefaultListModel model = new DefaultListModel<>(); + JList list = new JList<>(model); + JToolBar toolBar = new JToolBar("toolbar"); + DialogOptionEdit edit=new DialogOptionEdit(); + + + + public class DialogDataRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent( + JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if(!(value instanceof DialogData)) + return label; + DialogData dialog=(DialogData) value; + StringBuilder builder=new StringBuilder(); + if(dialog.name==null||dialog.name.isEmpty()) + builder.append("[[Blank Option]]"); + else + builder.append(dialog.name, 0, Math.min(dialog.name.length(), 25)); + label.setText(builder.toString()); + return label; + } + } + public void addButton(String name, ActionListener action) + { + JButton newButton=new JButton(name); + newButton.addActionListener(action); + toolBar.add(newButton); + + } + + public DialogOptionEditor() + { + + list.setCellRenderer(new DialogDataRenderer()); + list.addListSelectionListener(e -> DialogOptionEditor.this.updateEdit()); + addButton("add", e -> DialogOptionEditor.this.addOption()); + addButton("remove", e -> DialogOptionEditor.this.remove()); + addButton("copy", e -> DialogOptionEditor.this.copy()); + BorderLayout layout=new BorderLayout(); + setLayout(layout); + add(list, BorderLayout.LINE_START); + add(toolBar, BorderLayout.PAGE_START); + toolBar.setFloatable(false); + add(edit,BorderLayout.CENTER); + edit.setVisible(false); + + edit.addChangeListener(e -> emitChanged()); + } + protected void emitChanged() { + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } + + private void copy() { + + int selected=list.getSelectedIndex(); + if(selected<0) + return; + DialogData data=new DialogData(model.get(selected)); + model.add(model.size(),data); + } + + private void updateEdit() { + + int selected=list.getSelectedIndex(); + if(selected<0) + edit.setCurrentOption(new DialogData()); + else + edit.setCurrentOption(model.get(selected)); + } + + void addOption() + { + DialogData data=new DialogData(); + model.add(model.size(),data); + edit.setVisible(true); + list.setSelectedIndex(model.size() - 1); + } + void remove() + { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + model.remove(selected); + edit.setVisible(model.size() > 0); + } + public void setOptions(DialogData[] options) { + model.clear(); + if(options==null || options.length == 0) + options = new DialogData[0]; + for (int i=0;i 0) + { + edit.setVisible(true); + list.setSelectedIndex(0); + updateEdit(); + } + else{ + edit.setVisible(false); + } + } + + public DialogData[] getOptions() { + DialogData[] options= new DialogData[model.getSize()]; + for(int i=0;i selectionListeners = new ArrayList<>(); + + public void addSelectionListener(){ + //subscribe to valueChanged, change to that object in edit pane + dialogTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + emitChanged(e); + } + }); + + } + + public void addSelectionListener(final TreeSelectionListener listener) { + selectionListeners.remove(listener); //ensure listener not added multiple times + selectionListeners.add(listener); + } + + protected void emitChanged(TreeSelectionEvent evt) { + if (selectionListeners != null && selectionListeners.size() > 0) { + for (TreeSelectionListener listener : selectionListeners) { + listener.valueChanged(evt); + } + } + } + + public void replaceCurrent(){ + if (dialogTree.getSelectionPath() == null || dialogTree.getSelectionPath().getLastPathComponent() == null) + return; + dialogTree.updateUI(); + } + + public DialogData getSelectedData() { + DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) dialogTree.getLastSelectedPathComponent(); + if (selectedNode != null) { + return (DialogData) selectedNode.getUserObject(); + } + return null; + } + + public void removeSelectedData() { + //Todo: Enhance this to not collapse any nodes (after setSelectedData other paths are still collapsed) + DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) dialogTree.getLastSelectedPathComponent(); + + DialogData parentData = (DialogData) ((DefaultMutableTreeNode)selectedNode.getParent()).getUserObject(); + ((DefaultTreeModel) dialogTree.getModel()).removeNodeFromParent(selectedNode); + ((DefaultTreeModel) dialogTree.getModel()).reload(); + + setSelectedData(parentData); + } + public void setSelectedData(DialogData data) { + // Find the node with the given data object and select it in the tree + DefaultMutableTreeNode node = findNode((DefaultMutableTreeNode)dialogTree.getModel().getRoot(), data); + if (node != null) { + dialogTree.setSelectionPath(new TreePath(node.getPath())); + } + } + + private DefaultMutableTreeNode findNode(DefaultMutableTreeNode parent, DialogData data) { + // Search for the node with the given data object in the subtree rooted at the parent node + if (parent.getUserObject() == data) { + return parent; + } + for (int i = 0; i < parent.getChildCount(); i++) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) parent.getChildAt(i); + DefaultMutableTreeNode result = findNode(child, data); + if (result != null) { + return result; + } + } + return null; + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java b/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java index 8c3eaf96b71..a7f819e3de2 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java @@ -35,14 +35,18 @@ public class EditorMainWindow extends JFrame { newButton.addActionListener(e -> EventQueue.invokeLater(() ->new ParticleEditor())); toolBar.add(newButton); setLayout(layout); + toolBar.setFloatable(false); add(toolBar, BorderLayout.NORTH); add(tabs, BorderLayout.CENTER); tabs.addTab("World",worldEditor); tabs.addTab("POI",new PointOfInterestEditor()); tabs.addTab("Items",new ItemsEditor()); tabs.addTab("Enemies",new EnemyEditor()); + tabs.addTab("Quests",new QuestEditor()); setVisible(true); setSize(800,600); + GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow( this ); + } } diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java index ab9817fb228..cc2b1d676c4 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java @@ -3,6 +3,12 @@ package forge.adventure.editor; import forge.adventure.data.EnemyData; import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Enumeration; /** * Editor class to edit configuration, maybe moved or removed @@ -10,6 +16,7 @@ import javax.swing.*; public class EnemyEdit extends FormPanel { EnemyData currentData; JTextField nameField=new JTextField(); + JTextField nameOverride=new JTextField(); JTextField colorField=new JTextField(); JTextField ai=new JTextField(); JCheckBox flying=new JCheckBox(); @@ -24,51 +31,235 @@ public class EnemyEdit extends FormPanel { JTextField equipment=new JTextField(); RewardsEditor rewards=new RewardsEditor(); SwingAtlasPreview preview=new SwingAtlasPreview(); + JTextField manualEntry = new JTextField(20); private boolean updating=false; + DefaultListModel existingModel = new DefaultListModel<>(); + DefaultListModel enemyModel = new DefaultListModel<>(); + JList existingTags; + JList enemyTags; + public EnemyEdit() { + setLayout(new BorderLayout()); + FormPanel top=new FormPanel() { }; + add(top, BorderLayout.NORTH); - FormPanel center=new FormPanel() { }; + JTabbedPane tabs = new JTabbedPane(); + add(tabs, BorderLayout.CENTER); + FormPanel basicInfo=new FormPanel() { }; + tabs.addTab("Basic Info", basicInfo); - center.add("Name:",nameField); - center.add("Life:",lifeFiled); - center.add("Spawn rate:",spawnRate); - center.add("Difficulty:",difficulty); - center.add("Speed:",speed); - center.add("Deck:",deck); - center.add("Sprite:",atlas); - center.add("Equipment:",equipment); - center.add("Colors:",colorField); + top.add("Name:",nameField); + top.add("Display Name:", nameOverride); - center.add("ai:",ai); - center.add("flying:",flying); - center.add("boss:",boss); + basicInfo.add("Life:",lifeFiled); + basicInfo.add("Spawn rate:",spawnRate); + basicInfo.add("Difficulty:",difficulty); + basicInfo.add("Speed:",speed); + basicInfo.add("Deck:",deck); + basicInfo.add("Equipment:",equipment); + basicInfo.add("Colors:",colorField); + + basicInfo.add("ai:",ai); + basicInfo.add("flying:",flying); + basicInfo.add("boss:",boss); + + JPanel visual = new JPanel(); + + visual.add("Sprite:",atlas); + visual.add(preview); + + JPanel tags = new JPanel(); + existingTags = new JList<>(); - add(preview); - add(center); - add(rewards); + existingTags.getInputMap(JList.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( + KeyStroke.getKeyStroke("ENTER"), "addSelected"); + existingTags.getActionMap().put("addSelected", new AbstractAction() { - equipment.getDocument().addDocumentListener(new DocumentChangeListener(() -> EnemyEdit.this.updateEnemy())); - atlas.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(() -> EnemyEdit.this.updateEnemy())); - colorField.getDocument().addDocumentListener(new DocumentChangeListener(() -> EnemyEdit.this.updateEnemy())); - ai.getDocument().addDocumentListener(new DocumentChangeListener(() -> EnemyEdit.this.updateEnemy())); + @Override + public void actionPerformed(ActionEvent e) { + addSelected(); + + int index = existingTags.getSelectedIndex(); + + String selectedItem = existingTags.getSelectedValue(); + + if (selectedItem != null) { + enemyModel.addElement(selectedItem); + existingTags.grabFocus(); + existingTags.setSelectedIndex(index(); + enemyModel = new DefaultListModel<>(); + enemyTags.setModel(enemyModel); + + enemyTags.getModel().addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + doUpdate(); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + doUpdate(); + } + + @Override + public void contentsChanged(ListDataEvent e) { + doUpdate(); + } + }); + + JButton select = new JButton("Select"); + select.addActionListener(q -> addSelected()); + JButton add = new JButton("Manual Add"); + add.addActionListener(q -> manualAdd(enemyModel)); + JButton remove = new JButton("Remove Item"); + remove.addActionListener(q -> removeSelected()); + + tags.setLayout(new BorderLayout()); + + JPanel left = new JPanel(); + left.setLayout(new BorderLayout()); + left.add(new JLabel("Tags already in use"), BorderLayout.NORTH); + JScrollPane listScroller = new JScrollPane(existingTags); + listScroller.setMinimumSize(new Dimension(400, 800)); + left.add(listScroller, BorderLayout.CENTER); + tags.add(left, BorderLayout.WEST); + + FormPanel tagEdit = new FormPanel(); + tagEdit.setLayout(new BorderLayout()); + + FormPanel mappedTags = new FormPanel(); + mappedTags.setLayout(new BorderLayout()); + mappedTags.add(new JLabel("Tags Mapped to this object"), BorderLayout.NORTH); + JScrollPane listScroller2 = new JScrollPane(enemyTags); + listScroller2.setMinimumSize(new Dimension(400, 800)); + mappedTags.add(listScroller2, BorderLayout.CENTER); + tagEdit.add(mappedTags,BorderLayout.EAST); + + JPanel controlPanel = new JPanel(); + + controlPanel.add(select); + controlPanel.add(add); + controlPanel.add(manualEntry); + + manualEntry.getInputMap(JList.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( + KeyStroke.getKeyStroke("ENTER"), "addTyped"); + manualEntry.getActionMap().put("addTyped", new AbstractAction() { + + @Override + public void actionPerformed(ActionEvent e) { + if (!manualEntry.getText().trim().isEmpty()) { + manualAdd(enemyModel); + manualEntry.grabFocus(); + } + } + }); + + + controlPanel.add(remove); + tagEdit.add(controlPanel, BorderLayout.CENTER); + tags.add(tagEdit,BorderLayout.CENTER); + + JTextArea right1 = new JTextArea("This is really just to pad some space\n" + + "but also to explain the use of tags.\n" + + "Rather than adding 100's of object names\n" + + "to every quest definition, instead we will\n"+ + "categorize enemies and points of interest with\n"+ + "tags and reference those categories in quests"); + right1.setEnabled(false); + tags.add(right1, BorderLayout.EAST); + tabs.addTab("Sprite", visual); + tabs.addTab("Rewards", rewards); + tabs.addTab("Quest Tags", tags); + + + equipment.getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); + atlas.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); + colorField.getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); + ai.getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); flying.addChangeListener(e -> EnemyEdit.this.updateEnemy()); boss.addChangeListener(e -> EnemyEdit.this.updateEnemy()); ignoreDungeonEffect.addChangeListener(e -> EnemyEdit.this.updateEnemy()); - nameField.getDocument().addDocumentListener(new DocumentChangeListener(() -> EnemyEdit.this.updateEnemy())); - deck.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(() -> EnemyEdit.this.updateEnemy())); + nameField.getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); + nameOverride.getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); + deck.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(EnemyEdit.this::updateEnemy)); lifeFiled.addChangeListener(e -> EnemyEdit.this.updateEnemy()); speed.addChangeListener(e -> EnemyEdit.this.updateEnemy()); difficulty.addChangeListener(e -> EnemyEdit.this.updateEnemy()); spawnRate.addChangeListener(e -> EnemyEdit.this.updateEnemy()); rewards.addChangeListener(e -> EnemyEdit.this.updateEnemy()); lifeFiled.addChangeListener(e -> EnemyEdit.this.updateEnemy()); + enemyModel.addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + EnemyEdit.this.updateEnemy(); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + EnemyEdit.this.updateEnemy(); + } + + @Override + public void contentsChanged(ListDataEvent e) { + EnemyEdit.this.updateEnemy(); + } + }); refresh(); } - private void updateEnemy() { + + private void doUpdate(){ + EnemyEdit.this.updateEnemy(); + } + + private void addSelected(){ + if (existingTags.getSelectedIndex()>-1) + enemyModel.addElement(existingTags.getModel().getElementAt(existingTags.getSelectedIndex())); + doUpdate(); + } + + private void removeSelected(){ + if (enemyTags.getSelectedIndex()>-1) + enemyModel.remove(enemyTags.getSelectedIndex()); + doUpdate(); + } + + private void filterExisting(DefaultListModel filter){ + DefaultListModel toReturn = new DefaultListModel<>(); + for (Enumeration e = QuestController.getInstance().getEnemyTags(true).elements(); e.hasMoreElements();){ + String toTest = e.nextElement(); + if (toTest != null & !filter.contains(toTest)){ + toReturn.addElement(toTest); + } + } + existingTags.setModel(toReturn); + } + + private void manualAdd(DefaultListModel model){ + if (!manualEntry.getText().trim().isEmpty()) + model.addElement(manualEntry.getText().trim()); + manualEntry.setText(""); + doUpdate(); + } + + public void updateEnemy() { if(currentData==null||updating) return; currentData.name=nameField.getText(); @@ -88,6 +279,15 @@ public class EnemyEdit extends FormPanel { currentData.deck= deck.getEdit().getText().split(","); currentData.rewards= rewards.getRewards(); preview.setSpritePath(currentData.sprite); + + ArrayList tags = new ArrayList<>(); + for (Enumeration e = enemyModel.elements(); e.hasMoreElements();){ + tags.add(e.nextElement()); + } + + currentData.questTags = tags.toArray(currentData.questTags); + QuestController.getInstance().refresh(); + filterExisting(enemyModel); } public void setCurrentEnemy(EnemyData data) @@ -120,6 +320,12 @@ public class EnemyEdit extends FormPanel { difficulty.setValue(new Float(currentData.difficulty).doubleValue()); rewards.setRewards(currentData.rewards); preview.setSpritePath(currentData.sprite); + enemyModel.clear(); + for(String val : currentData.questTags) { + if (val != null) + enemyModel.addElement(val); + } + filterExisting(enemyModel); updating=false; } } \ No newline at end of file diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java index b278b755716..58e1c178e45 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java @@ -9,10 +9,7 @@ import forge.adventure.util.Config; import forge.adventure.util.Paths; import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; import java.awt.*; -import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** @@ -61,45 +58,19 @@ public class EnemyEditor extends JComponent { { list.setCellRenderer(new EnemyDataRenderer()); - list.addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - EnemyEditor.this.updateEdit(); - } - }); - addButton("add", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - EnemyEditor.this.addEnemy(); - } - }); - addButton("remove", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - EnemyEditor.this.remove(); - } - }); - addButton("copy", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - EnemyEditor.this.copy(); - } - }); - addButton("load", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - EnemyEditor.this.load(); - } - }); - addButton("save", new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - EnemyEditor.this.save(); - } + list.addListSelectionListener(e -> EnemyEditor.this.updateEdit()); + addButton("add", e -> EnemyEditor.this.addEnemy()); + addButton("remove", e -> EnemyEditor.this.remove()); + addButton("copy", e -> EnemyEditor.this.copy()); + addButton("load", e -> { + EnemyEditor.this.load(); + QuestController.getInstance().load(); }); + addButton("save", e -> EnemyEditor.this.save()); BorderLayout layout=new BorderLayout(); setLayout(layout); add(new JScrollPane(list), BorderLayout.LINE_START); + toolBar.setFloatable(false); add(toolBar, BorderLayout.PAGE_START); add(edit,BorderLayout.CENTER); load(); @@ -118,6 +89,7 @@ public class EnemyEditor extends JComponent { if(selected<0) return; edit.setCurrentEnemy(model.get(selected)); + edit.updateEnemy(); } void save() @@ -138,8 +110,7 @@ public class EnemyEditor extends JComponent { FileHandle handle = Config.instance().getFile(Paths.ENEMIES); if (handle.exists()) { - Array readEnemies=json.fromJson(Array.class, EnemyData.class, handle); - allEnemies = readEnemies; + allEnemies = json.fromJson(Array.class, EnemyData.class, handle); } for (int i=0;i existingModel = new DefaultListModel<>(); + DefaultListModel POIModel = new DefaultListModel<>(); + JList existingTags; + JList POITags; private boolean updating=false; public PointOfInterestEdit() { + JTabbedPane tabs = new JTabbedPane(); + add(tabs, BorderLayout.CENTER); setLayout(new BoxLayout(this,BoxLayout.Y_AXIS)); FormPanel parameters=new FormPanel(); + //parameters.setLayout(new BoxLayout(parameters, BoxLayout.Y_AXIS)); parameters.setBorder(BorderFactory.createTitledBorder("Parameter")); + JPanel tags = new JPanel(); + + tabs.addTab("Basic Info", parameters); + tabs.addTab("Quest Tags", tags); + parameters.add("Name:",name); parameters.add("Type:",type); parameters.add("Count:",count); @@ -37,15 +56,137 @@ public class PointOfInterestEdit extends JComponent { parameters.add("Radius factor:",radiusFactor); parameters.add(preview); - add(parameters); - - name.getDocument().addDocumentListener(new DocumentChangeListener(() -> PointOfInterestEdit.this.updateItem())); - type.getDocument().addDocumentListener(new DocumentChangeListener(() -> PointOfInterestEdit.this.updateItem())); + name.getDocument().addDocumentListener(new DocumentChangeListener(PointOfInterestEdit.this::updateItem)); + type.getDocument().addDocumentListener(new DocumentChangeListener(PointOfInterestEdit.this::updateItem)); count.addChangeListener(e -> PointOfInterestEdit.this.updateItem()); - spriteAtlas.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(() -> PointOfInterestEdit.this.updateItem())); - sprite.getDocument().addDocumentListener(new DocumentChangeListener(() -> PointOfInterestEdit.this.updateItem())); - map.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(() -> PointOfInterestEdit.this.updateItem())); + spriteAtlas.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(PointOfInterestEdit.this::updateItem)); + sprite.getDocument().addDocumentListener(new DocumentChangeListener(PointOfInterestEdit.this::updateItem)); + map.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(PointOfInterestEdit.this::updateItem)); radiusFactor.addChangeListener(e -> PointOfInterestEdit.this.updateItem()); + + + + existingTags = new JList<>(); + + + existingTags.getInputMap(JList.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( + KeyStroke.getKeyStroke("ENTER"), "addSelected"); + existingTags.getActionMap().put("addSelected", new AbstractAction() { + + @Override + public void actionPerformed(ActionEvent e) { + int index = existingTags.getSelectedIndex(); + + String selectedItem = existingTags.getSelectedValue(); + + if (selectedItem != null) { + POIModel.addElement(selectedItem); + existingTags.grabFocus(); + existingTags.setSelectedIndex(index(); + POIModel = new DefaultListModel<>(); + POITags.setModel(POIModel); + + POITags.getModel().addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + updateItem(); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + updateItem(); + } + + @Override + public void contentsChanged(ListDataEvent e) { + updateItem(); + } + }); + + JButton select = new JButton("Select"); + select.addActionListener(q -> addSelected()); + JButton add = new JButton("Manual Add"); + add.addActionListener(q -> manualAdd(POIModel)); + JButton remove = new JButton("Remove Item"); + remove.addActionListener(q -> removeSelected()); + + tags.setLayout(new BorderLayout()); + + JPanel left = new JPanel(); + left.setLayout(new BorderLayout()); + left.add(new JLabel("Tags already in use"), BorderLayout.NORTH); + JScrollPane listScroller = new JScrollPane(existingTags); + listScroller.setMinimumSize(new Dimension(400, 800)); + left.add(listScroller, BorderLayout.CENTER); + tags.add(left, BorderLayout.WEST); + + FormPanel tagEdit = new FormPanel(); + tagEdit.setLayout(new BorderLayout()); + + FormPanel mappedTags = new FormPanel(); + mappedTags.setLayout(new BorderLayout()); + mappedTags.add(new JLabel("Tags Mapped to this object"), BorderLayout.NORTH); + JScrollPane listScroller2 = new JScrollPane(POITags); + listScroller2.setMinimumSize(new Dimension(400, 800)); + mappedTags.add(listScroller2, BorderLayout.CENTER); + tagEdit.add(mappedTags,BorderLayout.EAST); + + JPanel controlPanel = new JPanel(); + + controlPanel.add(select); + controlPanel.add(add); + controlPanel.add(manualEntry); + + manualEntry.getInputMap(JList.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( + KeyStroke.getKeyStroke("ENTER"), "addTyped"); + manualEntry.getActionMap().put("addTyped", new AbstractAction() { + + @Override + public void actionPerformed(ActionEvent e) { + + if (!manualEntry.getText().trim().isEmpty()) { + manualAdd(POIModel); + manualEntry.grabFocus(); + } + } + }); + + + controlPanel.add(remove); + tagEdit.add(controlPanel, BorderLayout.CENTER); + tags.add(tagEdit,BorderLayout.CENTER); + + JTextArea right1 = new JTextArea("This is really just to pad some space\n" + + "but also to explain the use of tags.\n" + + "Rather than adding 100's of object names\n" + + "to every quest definition, instead we will\n"+ + "categorize enemies and points of interest with\n"+ + "tags and reference those categories in quests"); + right1.setEnabled(false); + tags.add(right1, BorderLayout.EAST); + + + + + + + + + refresh(); } @@ -54,11 +195,20 @@ public class PointOfInterestEdit extends JComponent { return; currentData.name=name.getText(); currentData.type= type.getText(); - currentData.count= ((Integer) count.getValue()).intValue(); + currentData.count= (Integer) count.getValue(); currentData.spriteAtlas=spriteAtlas.getEdit().getText(); currentData.sprite=sprite.getText(); currentData.map=map.getEdit().getText(); - currentData.radiusFactor=((Float) radiusFactor.getValue()).floatValue(); + currentData.radiusFactor= (Float) radiusFactor.getValue(); + + ArrayList tags = new ArrayList<>(); + for (Enumeration e = POIModel.elements(); e.hasMoreElements();){ + tags.add(e.nextElement()); + } + + currentData.questTags = tags.toArray(currentData.questTags); + QuestController.getInstance().refresh(); + filterExisting(POIModel); } public void setCurrent(PointOfInterestData data) @@ -67,6 +217,36 @@ public class PointOfInterestEdit extends JComponent { refresh(); } + private void addSelected(){ + if (existingTags.getSelectedIndex()>-1) + POIModel.addElement(existingTags.getModel().getElementAt(existingTags.getSelectedIndex())); + updateItem(); + } + + private void removeSelected(){ + if (POITags.getSelectedIndex()>-1) + POIModel.remove(POITags.getSelectedIndex()); + updateItem(); + } + + private void filterExisting(DefaultListModel filter){ + DefaultListModel toReturn = new DefaultListModel<>(); + for (Enumeration e = QuestController.getInstance().getPOITags(true).elements(); e.hasMoreElements();){ + String toTest = e.nextElement(); + if (toTest != null & !filter.contains(toTest)){ + toReturn.addElement(toTest); + } + } + existingTags.setModel(toReturn); + } + + private void manualAdd(DefaultListModel model){ + if (!manualEntry.getText().trim().isEmpty()) + model.addElement(manualEntry.getText().trim()); + manualEntry.setText(""); + updateItem(); + } + private void refresh() { setEnabled(currentData!=null); if(currentData==null) @@ -83,6 +263,12 @@ public class PointOfInterestEdit extends JComponent { radiusFactor.setValue(currentData.radiusFactor); preview.setSpritePath(currentData.spriteAtlas,currentData.sprite); + POIModel.clear(); + for(String val : currentData.questTags) { + if (val != null) + POIModel.addElement(val); + } + filterExisting(POIModel); updating=false; } } diff --git a/forge-adventure/src/main/java/forge/adventure/editor/PointOfInterestEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/PointOfInterestEditor.java index d3fd1578e20..0959ee91732 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/PointOfInterestEditor.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/PointOfInterestEditor.java @@ -23,7 +23,7 @@ public class PointOfInterestEditor extends JComponent { - public class PointOfInterestRenderer extends DefaultListCellRenderer { + public static class PointOfInterestRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent( JList list, Object value, int index, @@ -71,6 +71,7 @@ public class PointOfInterestEditor extends JComponent { BorderLayout layout=new BorderLayout(); setLayout(layout); add(new JScrollPane(list), BorderLayout.LINE_START); + toolBar.setFloatable(false); add(toolBar, BorderLayout.PAGE_START); add(edit,BorderLayout.CENTER); load(); @@ -109,12 +110,13 @@ public class PointOfInterestEditor extends JComponent { FileHandle handle = Config.instance().getFile(Paths.POINTS_OF_INTEREST); if (handle.exists()) { - Array readEnemies=json.fromJson(Array.class, PointOfInterestData.class, handle); - allEnemies = readEnemies; + allEnemies =json.fromJson(Array.class, PointOfInterestData.class, handle); } for (int i=0;i POITags = new DefaultListModel<>(); + public final DefaultListModel enemyTags = new DefaultListModel<>(); + public final DefaultListModel questEnemyTags = new DefaultListModel<>(); + public final DefaultListModel questTags = new DefaultListModel<>(); + public final DefaultListModel questPOITags = new DefaultListModel<>(); + private final DefaultListModel allPOI = new DefaultListModel<>(); + private final DefaultListModel allEnemies = new DefaultListModel<>(); + + private final DefaultListModel allQuests = new DefaultListModel<>(); + + private static QuestController instance; + + public static QuestController getInstance() { + if (instance == null) + instance = new QuestController(); + return instance; + } + + public DefaultListModel getAllQuests() { + return allQuests; + } + + private QuestController(){ + load(); + } + + public DefaultListModel getEnemyTags(boolean includeQuest){ + DefaultListModel toReturn = new DefaultListModel<>(); + for (int i = 0; i < enemyTags.size(); i++){ + toReturn.removeElement(enemyTags.get(i)); + toReturn.addElement(enemyTags.get(i)); + } + + if (includeQuest){ + for (int i = 0; i < questEnemyTags.size(); i++) + { + toReturn.removeElement(questEnemyTags.get(i)); + toReturn.addElement(questEnemyTags.get(i)); + } + } + + List sortedObjects = Arrays.stream(toReturn.toArray()).sorted().collect(Collectors.toList()); + + toReturn.clear(); + + for (Object sortedObject : sortedObjects) { + toReturn.addElement((String) sortedObject); + } + + return toReturn; + } + + public DefaultListModel getPOITags(boolean includeQuest){ + DefaultListModel toReturn = new DefaultListModel<>(); + for (int i = 0; i < POITags.size(); i++){ + toReturn.removeElement(POITags.get(i)); + toReturn.addElement(POITags.get(i)); + } + + if (includeQuest){ + for (int i = 0; i < questEnemyTags.size(); i++) + { + toReturn.removeElement(questEnemyTags.get(i)); + toReturn.addElement(questEnemyTags.get(i)); + } + } + + List sortedObjects = Arrays.stream(toReturn.toArray()).sorted().collect(Collectors.toList()); + + toReturn.clear(); + + for (Object sortedObject : sortedObjects) { + toReturn.addElement((String) sortedObject); + } + + return toReturn; + } + + public void refresh(){ + enemyTags.clear(); + POITags.clear(); + questPOITags.clear(); + questEnemyTags.clear(); + questTags.clear(); + + for (int i=0;i enemyJSON=new Array<>(); + Json json = new Json(); + FileHandle handle = Config.instance().getFile(Paths.ENEMIES); + if (handle.exists()) + { + enemyJSON = json.fromJson(Array.class, EnemyData.class, handle); + } + for (int i=0;i POIJSON=new Array<>(); + json = new Json(); + handle = Config.instance().getFile(Paths.POINTS_OF_INTEREST); + if (handle.exists()) + { + POIJSON = json.fromJson(Array.class, PointOfInterestData.class, handle); + } + for (int i=0;i questJSON=new Array<>(); + json = new Json(); + handle = Config.instance().getFile(Paths.QUESTS); + if (handle.exists()) + { + questJSON = json.fromJson(Array.class, AdventureQuestData.class, handle); + } + for (int i=0;i QuestEdit.this.updateQuest()); + rewardDescription.getDocument().addDocumentListener(new DocumentChangeListener(QuestEdit.this::updateQuest)); + stages.addChangeListener(e -> QuestEdit.this.updateQuest()); + offerEditor.addChangeListener(e -> QuestEdit.this.updateQuest()); + prologueEditor.addChangeListener(e -> QuestEdit.this.updateQuest()); + epilogueEditor.addChangeListener(e -> QuestEdit.this.updateQuest()); + failureEditor.addChangeListener(e -> QuestEdit.this.updateQuest()); + declineEditor.addChangeListener(e -> QuestEdit.this.updateQuest()); + stages.addChangeListener(e -> QuestEdit.this.updateQuest()); + + refresh(); + } + + protected void updateQuest() { + if(currentData==null||updating) + return; + + currentData.name = name.getText(); + currentData.storyQuest = storyQuest.isSelected(); + currentData.synopsis = synopsis.getText(); + currentData.description = description.getText(); + currentData.rewardDescription = rewardDescription.getText(); + currentData.stages = stages.getStages(); + currentData.offerDialog = offerEditor.getDialogData(); + currentData.prologue = prologueEditor.getDialogData(); + currentData.epilogue = epilogueEditor.getDialogData(); + currentData.failureDialog = failureEditor.getDialogData(); + currentData.declinedDialog = declineEditor.getDialogData(); + } + + public void setCurrentQuest(AdventureQuestData data) + { + currentData=data; + refresh(); + } + + private void refresh() { + setEnabled(currentData!=null); + if(currentData==null) + { + return; + } + setVisible(true); + updating=true; + id.setText(currentData.getID() + ""); + name.setText(currentData.name); + description.setText(currentData.description); + synopsis.setText(currentData.synopsis); + storyQuest.getModel().setSelected(currentData.storyQuest); + rewardDescription.setText(currentData.rewardDescription); + stages.setStages(currentData); + + offerEditor.loadData(currentData.offerDialog); + prologueEditor.loadData(currentData.prologue); + epilogueEditor.loadData(currentData.epilogue); + failureEditor.loadData(currentData.failureDialog); + declineEditor.loadData(currentData.declinedDialog); + + updating=false; + } + + public JPanel getOfferTab(){ + JPanel offerTab = new JPanel(); + offerTab.setLayout(new BoxLayout(offerTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(offerEditor); + offerTab.add(center); + return offerTab; + } + + public JPanel getPrologueTab(){ + JPanel prologueTab = new JPanel(); + prologueTab.setLayout(new BoxLayout(prologueTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(prologueEditor); + prologueTab.add(center); + return prologueTab; + } + + public JPanel getEpilogueTab(){ + JPanel epilogueTab = new JPanel(); + epilogueTab.setLayout(new BoxLayout(epilogueTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(epilogueEditor); + epilogueTab.add(center); + return epilogueTab; + } + + public JPanel getFailureTab(){ + JPanel failureTab = new JPanel(); + failureTab.setLayout(new BoxLayout(failureTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(failureEditor); + failureTab.add(center); + return failureTab; + } + + public JPanel getDeclineTab(){ + JPanel declineTab = new JPanel(); + declineTab.setLayout(new BoxLayout(declineTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(declineEditor); + declineTab.add(center); + return declineTab; + } + + public JPanel getStagesTab(){ + JPanel stagesTab = new JPanel(); + stagesTab.setLayout(new BoxLayout(stagesTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(stages); + stagesTab.add(center); + return stagesTab; + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/QuestEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/QuestEditor.java new file mode 100644 index 00000000000..e9e082bb3e8 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/QuestEditor.java @@ -0,0 +1,104 @@ +package forge.adventure.editor; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.JsonWriter; +import forge.adventure.data.AdventureQuestData; +import forge.adventure.util.Config; +import forge.adventure.util.Paths; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class QuestEditor extends JComponent { + JList list = new JList<>(QuestController.getInstance().getAllQuests()); + JToolBar toolBar = new JToolBar("toolbar"); + QuestEdit edit=new QuestEdit(); + + public class QuestDataRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent( + JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if(!(value instanceof AdventureQuestData)) + return label; + AdventureQuestData quest=(AdventureQuestData) value; + // Get the renderer component from parent class + + label.setText(quest.name); + return label; + } + } + public void addButton(String name,ActionListener action) + { + JButton newButton=new JButton(name); + newButton.addActionListener(action); + toolBar.add(newButton); + + } + public QuestEditor() + { + + list.setCellRenderer(new QuestDataRenderer()); + list.addListSelectionListener(e -> QuestEditor.this.updateEdit()); + addButton("Add Quest", e -> QuestEditor.this.addStage()); + addButton("Remove", e -> QuestEditor.this.remove()); + addButton("Copy", e -> QuestEditor.this.copy()); + addButton("Load", e -> QuestController.getInstance().load()); + addButton("Save", e -> QuestEditor.this.save()); + BorderLayout layout=new BorderLayout(); + setLayout(layout); + add(new JScrollPane(list), BorderLayout.LINE_START); + toolBar.setFloatable(false); + add(toolBar, BorderLayout.PAGE_START); + add(edit,BorderLayout.CENTER); + edit.setVisible(false); + } + private void copy() { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + AdventureQuestData data=new AdventureQuestData(QuestController.getInstance().getAllQuests().get(selected)); + data.isTemplate = true; + QuestController.getInstance().getAllQuests().add(QuestController.getInstance().getAllQuests().size(),data); + } + private void updateEdit() { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + edit.setCurrentQuest(QuestController.getInstance().getAllQuests().get(selected)); + } + + void save() + { + Array allQuests=new Array<>(); + for(int i=0;i !q.equals(currentData)).toArray():new String[]{}); //May not be the right way to do this, will come back to it. + + JTabbedPane tabs =new JTabbedPane(); + DialogEditor prologueEditor = new DialogEditor(); + DialogEditor epilogueEditor = new DialogEditor(); + + public QuestStageEdit() + { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + add(getInfoTab()); + add(tabs); + tabs.add("Objective", getObjectiveTab()); + tabs.add("Prologue",getPrologueTab()); + tabs.add("Epilogue",getEpilogueTab()); + tabs.add("Prerequisites",getPrereqTab()); + + //temp + nyi.setForeground(Color.red); + // + + addListeners(); + } + + public JPanel getInfoTab(){ + JPanel infoTab = new JPanel(); + FormPanel center=new FormPanel(); + center.add("name:",name); + center.add("description:",description); + name.setSize(400, name.getHeight()); + description.setSize(400, description.getHeight()); + infoTab.add(center); + name.getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + description.getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + prerequisites.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + return infoTab; + } + + public JPanel getPrologueTab(){ + JPanel prologueTab = new JPanel(); + prologueTab.setLayout(new BoxLayout(prologueTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(prologueEditor); + prologueTab.add(center); + return prologueTab; + } + + public JPanel getEpilogueTab(){ + JPanel epilogueTab = new JPanel(); + epilogueTab.setLayout(new BoxLayout(epilogueTab, BoxLayout.Y_AXIS)); + FormPanel center = new FormPanel(); + center.add(epilogueEditor); + epilogueTab.add(center); + return epilogueTab; + } + + private JComboBox objectiveType; + + private final JLabel nyi = new JLabel("Not yet implemented"); + private final JTextField deliveryItem=new JTextField(25); + private final JTextField mapFlag = new JTextField(25); + + private Box mapFlagGroup; + private final JSpinner flagSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 1000, 1)); + private Box flagValueGroup; + private final JCheckBox anyPOI = new JCheckBox("Any Point of Interest matching"); + private final JCheckBox here = new JCheckBox("Use current map instead of selecting tags"); + private final JCheckBox mixedEnemies = new JCheckBox("Mixture of enemy types matching"); + private final JLabel count1Description = new JLabel("(Count 1 description)"); + private final JLabel count2Description = new JLabel("(Count 2 description)"); + private final JLabel count3Description = new JLabel("(Count 3 description)"); + private final JSpinner count1Spinner = new JSpinner(new SpinnerNumberModel(1, 0, 100, 1)); + private final JSpinner count2Spinner = new JSpinner(new SpinnerNumberModel(1, 0, 100, 1)); + private final JSpinner count3Spinner = new JSpinner(new SpinnerNumberModel(1, 0, 100, 1)); + + private final JLabel arenaLabel = new JLabel("Enter the arena and prove your worth. (Note: Please be sure the PoIs selected have an arena)"); + private final JLabel clearLabel = new JLabel("Clear all enemies from the target area."); + private final JLabel defeatLabel = new JLabel("Defeat a number of enemies of the indicated type."); + private final JLabel deliveryLabel = new JLabel("Travel to the given destination to deliver an item (not tracked in inventory)."); + private final JLabel escortLabel = new JLabel("Protect your target as they travel to their destination."); + private final JLabel fetchLabel = new JLabel("Obtain the requested items (not tracked in inventory)."); + private final JLabel findLabel = new JLabel("Locate the and enter a PoI."); + private final JLabel gatherLabel = new JLabel("Have the requested item in your inventory (tracked in inventory)"); + private final JLabel giveLabel = new JLabel("Have the requested items removed from your inventory."); + private final JLabel huntLabel = new JLabel("Track down and defeat your target (on the overworld map)."); + private final JLabel leaveLabel = new JLabel("Exit the current PoI and return to the overworld map."); + private final JLabel noneLabel = new JLabel("No visible objective. Use in coordination with hidden parallel objectives to track when to progress"); + private final JLabel patrolLabel = new JLabel("Get close to generated coordinates before starting your next objective"); + private final JLabel rescueLabel = new JLabel("Reach and rescue the target"); + private final JLabel siegeLabel = new JLabel("Travel to the target location and defeat enemies attacking it"); + private final JLabel mapFlagLabel = new JLabel("Have a map flag set to a minimum value"); + private final JLabel questFlagLabel = new JLabel("Have a global quest flag set to a minimum value"); + private final JLabel travelLabel = new JLabel("Travel to the given destination."); + private final JLabel useLabel = new JLabel("Use the indicated item from your inventory."); + private JTabbedPane poiPane = new JTabbedPane(); + private final QuestTagSelector poiSelector = new QuestTagSelector("Destination Tags", false,true); + private final QuestTagSelector enemySelector = new QuestTagSelector("Enemy Tags", true,false); + private final JTextField poiTokenInput = new JTextField(25); + + private final JLabel poiTokenLabel = new JLabel(); + private final JLabel poiTokenDescription = new JLabel( + "At the bottom of many objectives involving a PoI, you will see a text field in the format of '$poi_#'." + + "Enter that tag here to ignore the PoI tag selector of this stage and instead use the same PoI that was selected " + + "for that stage as the target PoI for this one as well."); + + private void hideAllControls(){ + arenaLabel.setVisible(false); + clearLabel.setVisible(false); + deliveryLabel.setVisible(false); + defeatLabel.setVisible(false); + escortLabel.setVisible(false); + fetchLabel.setVisible(false); + findLabel.setVisible(false); + gatherLabel.setVisible(false); + giveLabel.setVisible(false); + huntLabel.setVisible(false); + leaveLabel.setVisible(false); + noneLabel.setVisible(false); + patrolLabel.setVisible(false); + rescueLabel.setVisible(false); + siegeLabel.setVisible(false); + mapFlagLabel.setVisible(false); + questFlagLabel.setVisible(false); + travelLabel.setVisible(false); + useLabel.setVisible(false); + deliveryItem.setVisible(false); + mapFlagGroup.setVisible(false); + flagValueGroup.setVisible(false); + anyPOI.setVisible(false); + here.setVisible(false); + mixedEnemies.setVisible(false); + enemySelector.setVisible(false); + count1Description.setVisible(false); + count2Description.setVisible(false); + count3Description.setVisible(false); + count1Spinner.setVisible(false); + count2Spinner.setVisible(false); + count3Spinner.setVisible(false); + poiPane.setVisible(false); + poiTokenLabel.setVisible(false); + nyi.setVisible(false); + } + private void switchPanels(){ + hideAllControls(); + if (objectiveType.getSelectedItem() == null){ + return; + } + switch(objectiveType.getSelectedItem().toString()){ + case "Arena": + arenaLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + poiTokenLabel.setVisible(true); + break; + case "Clear": + clearLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + count1Description.setText("Target candidate percentile"); + count1Description.setVisible(true); + count1Spinner.setVisible(true); + count2Description.setText("Percent variance"); + count2Description.setVisible(true); + count2Spinner.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + poiTokenLabel.setVisible(true); + break; + case "Defeat": + defeatLabel.setVisible(true); + mixedEnemies.setVisible(true); + count1Description.setText("Number to defeat"); + count1Description.setVisible(true); + count1Spinner.setVisible(true); + count2Description.setText("Maximum losses"); + count2Description.setVisible(true); + count2Spinner.setVisible(true); + enemySelector.setVisible(true); + break; + case "Delivery": + deliveryLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + deliveryItem.setVisible(true); + poiTokenLabel.setVisible(true); + break; + case "Escort": + escortLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + here.setVisible(true); + spriteNames.setVisible(true); + poiTokenLabel.setVisible(true); + break; + case "Fetch": + fetchLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + deliveryItem.setVisible(true); + enemySelector.setVisible(true); + poiTokenLabel.setVisible(true); + break; + case "Find": + findLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + anyPOI.setVisible(true); + poiTokenLabel.setVisible(true); + break; + case "Gather": + gatherLabel.setVisible(true); + gatherLabel.setVisible(true); + nyi.setVisible(true); + itemNames.setVisible(true); + break; + case "Give": + giveLabel.setVisible(true); + nyi.setVisible(true); + itemNames.setVisible(true); + poiPane.setVisible(true); + poiTokenLabel.setVisible(true); + here.setVisible(true); + break; + case "Hunt": + huntLabel.setVisible(true); + enemySelector.setVisible(true); + count1Description.setText("Lifespan (seconds to complete hunt before despawn)"); + count1Description.setVisible(true); + count1Spinner.setVisible(true); + count1Spinner.setVisible(true); + break; + case "Leave": + leaveLabel.setVisible(true); + break; + case "None": + noneLabel.setVisible(true); + nyi.setVisible(true); + break; + case "Patrol": + patrolLabel.setVisible(true); + nyi.setVisible(true); + break; + case "Rescue": + rescueLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + anyPOI.setVisible(true); + poiTokenLabel.setVisible(true); + here.setVisible(true); + spriteNames.setVisible(true); + enemySelector.setVisible(true); + break; + case "Siege": + siegeLabel.setVisible(true); + nyi.setVisible(true); + poiPane.setVisible(true); + poiTokenLabel.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + enemySelector.setVisible(true); + mixedEnemies.setVisible(true); + break; + case "MapFlag": + mapFlagLabel.setVisible(true); + poiPane.setVisible(true); + here.setVisible(true); + anyPOI.setVisible(true); + mapFlagGroup.setVisible(true); + flagValueGroup.setVisible(true); + break; + case "QuestFlag": + nyi.setVisible(true); + questFlagLabel.setVisible(true); + mapFlagGroup.setVisible(true); + flagValueGroup.setVisible(true); + break; + case "Travel": + travelLabel.setVisible(true); + poiPane.setVisible(true); + poiTokenLabel.setVisible(true); + poiTokenInput.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + count1Description.setText("Target % of possible distances"); + count1Description.setVisible(true); + count1Spinner.setVisible(true); + count2Description.setText("Plus or minus %"); + count2Description.setVisible(true); + count2Spinner.setVisible(true); + break; + case "Use": + useLabel.setVisible(true); + nyi.setVisible(true); + itemNames.setVisible(true); + poiPane.setVisible(true); + anyPOI.setVisible(true); + here.setVisible(true); + poiTokenLabel.setVisible(true); + here.setVisible(true); + break; + } + } + + private void changeObjective(){ + if (objectiveType.getSelectedItem() != null) + currentData.objective = AdventureQuestController.ObjectiveTypes.valueOf(objectiveType.getSelectedItem().toString()); + switchPanels(); + } + + private JPanel getObjectiveTab(){ + objectiveType = new JComboBox<>(AdventureQuestController.ObjectiveTypes.values()); + objectiveType.addActionListener( e -> changeObjective()); + + JPanel objectiveTab = new JPanel(); + JScrollPane scrollPane = new JScrollPane(); + objectiveTab.add(scrollPane); + FormPanel center=new FormPanel(); + center.add(objectiveType); + scrollPane.add(center); + + mapFlagGroup = new Box(BoxLayout.Y_AXIS); + mapFlagGroup.add(new JLabel("Map flag to check")); + mapFlagGroup.add(mapFlag); + + flagValueGroup = new Box(BoxLayout.Y_AXIS); + flagValueGroup.add(new JLabel("Flag value to check")); + flagValueGroup.add(flagSpinner); + + JPanel poiSelectorPane = new JPanel(); + poiSelectorPane.setLayout(new BorderLayout()); + poiSelectorPane.add(poiSelector, BorderLayout.CENTER); + JPanel poiTokenPanel = new JPanel(); + + JPanel tokenPanel = new JPanel(); + tokenPanel.add(new JLabel("Token to use:")); + tokenPanel.add(poiTokenInput); + tokenPanel.setBorder(new EmptyBorder(10, 10, 30, 10)); + + poiTokenPanel.add(poiTokenDescription); + poiTokenPanel.add(tokenPanel); + poiTokenPanel.add(here); + + GroupLayout poiTokenLayout = new GroupLayout(poiTokenPanel); + + + poiTokenLayout.setHorizontalGroup( + poiTokenLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(poiTokenDescription) + .addComponent(tokenPanel,GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(here)); + + poiTokenLayout.setVerticalGroup( + poiTokenLayout.createSequentialGroup() + .addComponent(poiTokenDescription) + .addComponent(tokenPanel,GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(here)); + + poiTokenPanel.setLayout(poiTokenLayout); + + poiPane.add("Specific PoI", poiTokenPanel); + poiPane.add("Tag Selector", poiSelectorPane); + poiPane.setPreferredSize(new Dimension(0,200)); + + + center.add(arenaLabel); + center.add(clearLabel); + center.add(defeatLabel); + center.add(deliveryLabel); + center.add(escortLabel); + center.add(fetchLabel); + center.add(findLabel); + center.add(gatherLabel); + center.add(giveLabel); + center.add(huntLabel); + center.add(leaveLabel); + center.add(noneLabel); + center.add(patrolLabel); + center.add(rescueLabel); + center.add(siegeLabel); + center.add(mapFlagLabel); + center.add(questFlagLabel); + center.add(travelLabel); + center.add(useLabel); + center.add(nyi); + center.add(deliveryItem); + center.add(mapFlagGroup); + center.add(flagValueGroup); + center.add(anyPOI); + center.add(mixedEnemies); + center.add(enemySelector); + center.add(count1Description); + center.add(count1Spinner); + center.add(count2Description); + center.add(count2Spinner); + center.add(count3Description); + center.add(count3Spinner); + center.add(poiPane); + center.add(poiTokenLabel); + + switchPanels(); + + poiSelector.selectedItems.addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + rebuildPOIList(); + rebuildEnemyList(); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + rebuildPOIList(); + rebuildEnemyList(); + } + + @Override + public void contentsChanged(ListDataEvent e) { + rebuildPOIList(); + rebuildEnemyList(); + } + }); + + return center; + } + + private void rebuildPOIList(){ + List currentList = new ArrayList<>(); + + for(int i = 0; i< poiSelector.selectedItems.getSize(); i++){ + currentList.add(poiSelector.selectedItems.getElementAt(i)); + } + currentData.POITags = currentList; + } + + private void rebuildEnemyList(){ + List currentList = new ArrayList<>(); + + for(int i = 0; i< enemySelector.selectedItems.getSize(); i++){ + currentList.add(enemySelector.selectedItems.getElementAt(i)); + } + currentData.enemyTags = currentList; + } + + private JPanel getPrereqTab(){ + JPanel prereqTab = new JPanel(); + prereqTab.add(new JLabel("Insert Prereq data here")); + + return prereqTab; + } + + private void refresh() { + + if(currentData==null) + { + return; + } + setEnabled(false); + updating=true; + objectiveType.setSelectedItem(currentData.objective); + if (objectiveType.getSelectedItem() != null) + currentData.objective = AdventureQuestController.ObjectiveTypes.valueOf(objectiveType.getSelectedItem().toString()); //Ensuring this gets initialized on new + + name.setText(currentData.name); + description.setText(currentData.description); + deliveryItem.setText(currentData.deliveryItem); + + if (currentData.enemyTags != null){ + DefaultListModel selectedEnemies = new DefaultListModel<>(); + for (int i = 0; i < currentData.enemyTags.size(); i++) { + selectedEnemies.add(i, currentData.enemyTags.get(i)); + } + enemySelector.load(selectedEnemies); + } + if (currentData.POITags != null){ + DefaultListModel selectedPOI = new DefaultListModel<>(); + for (int i = 0; i < currentData.POITags.size(); i++) { + selectedPOI.add(i, currentData.POITags.get(i)); + } + poiSelector.load(selectedPOI); + } + + itemNames.setText(currentData.itemNames); + equipNames.setText(currentData.equipNames); + prologueEditor.loadData(currentData.prologue); + epilogueEditor.loadData(currentData.epilogue); + + + if (currentData.POITags != null){ + DefaultListModel selectedPOI = new DefaultListModel<>(); + for (int i = 0; i < currentData.POITags.size(); i++) { + selectedPOI.add(i, currentData.POITags.get(i)); + } + poiSelector.load(selectedPOI); + } + here.getModel().setSelected(currentData.here); + poiTokenInput.setText(currentData.POIToken); + poiTokenLabel.setText( "To reference this point of interest: $(poi_" + currentData.id + ")"); + + ArrayList temp = new ArrayList<>(); + for (AdventureQuestStage stage : currentQuestData.stages){ + if (stage.equals(currentData)) + continue; + temp.add(stage.name); + } + prerequisites.setOptions(temp); + + count1Spinner.getModel().setValue(currentData.count1); + count2Spinner.getModel().setValue(currentData.count2); + count3Spinner.getModel().setValue(currentData.count3); + + updating=false; + setEnabled(true); + } + public void updateStage() + { + if(currentData==null||updating) + return; + + currentData.name=name.getText(); + currentData.description= description.getText(); + currentData.prologue = prologueEditor.getDialogData(); + currentData.epilogue = epilogueEditor.getDialogData(); + currentData.deliveryItem = deliveryItem.getText(); + currentData.itemNames = itemNames.getList()==null?new ArrayList<>():Arrays.asList(itemNames.getList()); + currentData.equipNames = equipNames.getList()==null?new ArrayList<>():Arrays.asList(equipNames.getList()); + currentData.anyPOI = anyPOI.getModel().isSelected(); + currentData.mapFlag = mapFlag.getText(); + currentData.mapFlagValue = Integer.parseInt(flagSpinner.getModel().getValue().toString()); + currentData.count1 = Integer.parseInt(count1Spinner.getModel().getValue().toString()); + currentData.count2 = Integer.parseInt(count2Spinner.getModel().getValue().toString()); + currentData.count3 = Integer.parseInt(count3Spinner.getModel().getValue().toString()); + currentData.mixedEnemies = mixedEnemies.getModel().isSelected(); + currentData.here = here.getModel().isSelected(); + currentData.POIToken = poiTokenInput.getText(); + + rebuildPOIList(); + rebuildEnemyList(); + emitChanged(); + } + public void setCurrentStage(AdventureQuestStage stageData, AdventureQuestData data) { + if (stageData == null) + stageData = new AdventureQuestStage(); + if (data == null) + data = new AdventureQuestData(); + currentData =stageData; + currentQuestData=data; + setVisible(true); + refresh(); + } + + public void addChangeListener(ChangeListener listener) { + listenerList.add(ChangeListener.class, listener); + } + protected void emitChanged() { + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } + + private void addListeners(){ + deliveryItem.getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + mapFlag.getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + flagSpinner.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + count1Spinner.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + count2Spinner.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + count3Spinner.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + mixedEnemies.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + here.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + anyPOI.getModel().addChangeListener(q -> QuestStageEdit.this.updateStage()); + deliveryItem.getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + mapFlag.getDocument().addDocumentListener(new DocumentChangeListener(QuestStageEdit.this::updateStage)); + prologueEditor.addChangeListener(q -> QuestStageEdit.this.updateStage()); + epilogueEditor.addChangeListener(q -> QuestStageEdit.this.updateStage()); + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/QuestStageEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/QuestStageEditor.java new file mode 100644 index 00000000000..a3c90b96346 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/QuestStageEditor.java @@ -0,0 +1,139 @@ +package forge.adventure.editor; + +import forge.adventure.data.AdventureQuestData; +import forge.adventure.data.AdventureQuestStage; +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionListener; + +/** + * Editor class to edit configuration, maybe moved or removed + */ +public class QuestStageEditor extends JComponent{ + DefaultListModel model = new DefaultListModel<>(); + JList list = new JList<>(model); + JScrollPane scroll; + JToolBar toolBar = new JToolBar("toolbar"); + QuestStageEdit edit=new QuestStageEdit(); + + AdventureQuestData currentData; + + public class QuestStageRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent( + JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if(!(value instanceof AdventureQuestStage)) + return label; + AdventureQuestStage stageData=(AdventureQuestStage) value; + label.setText(stageData.name); + //label.setIcon(new ImageIcon(Config.instance().getFilePath(stageData.sourcePath))); //Type icon eventually? + return label; + } + } + public void addButton(String name, ActionListener action) + { + JButton newButton=new JButton(name); + newButton.addActionListener(action); + toolBar.add(newButton); + + } + + public QuestStageEditor() + { + list.setCellRenderer(new QuestStageRenderer()); + list.addListSelectionListener(e -> QuestStageEditor.this.updateEdit()); + addButton("Add Quest Stage", e -> QuestStageEditor.this.addStage()); + addButton("Remove Selected", e -> QuestStageEditor.this.remove()); + toolBar.setFloatable(false); + setLayout(new BorderLayout()); + scroll = new JScrollPane(list); + add(scroll, BorderLayout.WEST); + JPanel editPanel = new JPanel(); + editPanel.setLayout(new BorderLayout()); + add(toolBar, BorderLayout.NORTH); + editPanel.add(edit,BorderLayout.CENTER); + add(editPanel); + edit.addChangeListener(e -> emitChanged()); + } + + protected void emitChanged() { + ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + + } + + private void updateEdit() { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + edit.setCurrentStage(model.get(selected),currentData); + } + + void addStage() + { + AdventureQuestStage data=new AdventureQuestStage(); + data.name = "New Stage"; + model.add(model.size(),data); + edit.setVisible(true); + scroll.setVisible(true); + int id = 0; + for (int i = 0; i < model.size(); i++) + { + if (model.get(i).id >= id) + id = model.get(i).id +1; + } + data.id = id; + if (model.size() == 1){ + list.setSelectedIndex(0); + } + emitChanged(); + } + void remove() + { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + model.remove(selected); + edit.setVisible(false); + scroll.setVisible(list.getModel().getSize() > 0); + emitChanged(); + } + public void setStages(AdventureQuestData data) { + + currentData=data; + model.clear(); + if(data==null||data.stages==null || data.stages.length == 0) + { + edit.setVisible(false); + return; + } + for (int i=0;i 0) { + list.setSelectedIndex(0); + } + } + + public AdventureQuestStage[] getStages() { + + AdventureQuestStage[] stages= new AdventureQuestStage[model.getSize()]; + for(int i=0;i allItems = new DefaultListModel<>(); + DefaultListModel selectedItems = new DefaultListModel<>(); + JList unselectedList; + JList selectedList; + + boolean useEnemyTags = false; + boolean usePOITags = false; + + public QuestTagSelector(String title, boolean useEnemyTags, boolean usePOITags) + { + if (useEnemyTags){ + this.useEnemyTags = true; + } else if (usePOITags) { + this.usePOITags = true; + } + else{ + return; + } + + unselectedList = new JList<>(allItems); + selectedList = new JList<>(selectedItems); +// unselectedList.setCellRenderer(new PointOfInterestEditor.PointOfInterestRenderer()); // Replace with use count of tag? +// selectedList.setCellRenderer(new PointOfInterestEditor.PointOfInterestRenderer()); + + JButton addButton=new JButton("add"); + JButton removeButton=new JButton("remove"); + addButton.addActionListener( e -> QuestTagSelector.this.addTag()); + removeButton.addActionListener( e -> QuestTagSelector.this.removeTag()); + + BorderLayout layout=new BorderLayout(); + setLayout(layout); + if (title.length() > 0) + add(new JLabel(title),BorderLayout.PAGE_START); + add(new JScrollPane(unselectedList), BorderLayout.LINE_START); + add(new JScrollPane(selectedList), BorderLayout.LINE_END); + + JPanel buttonPanel = new JPanel(); + GridLayout buttonLayout = new GridLayout(2,0); + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(addButton); + buttonPanel.add(removeButton); + add(buttonPanel); + } + + public void addTag(){ + if (unselectedList.isSelectionEmpty()){ + return; + } + for (String toAdd : unselectedList.getSelectedValuesList()) + { + if (selectedItems.contains(toAdd)) continue; + selectedItems.addElement(toAdd); + } + refresh(); + } + + public void removeTag(){ + if (selectedList.isSelectionEmpty()){ + return; + } + for (String toRemove : selectedList.getSelectedValuesList()) + { + selectedItems.removeElement(toRemove); + } + refresh(); + } + + public void load(DefaultListModel selectedNames) + { + allItems.clear(); + selectedItems.clear(); + + if (useEnemyTags){ + allItems = QuestController.getInstance().getEnemyTags(true); + } + else if (usePOITags) { + allItems = QuestController.getInstance().getPOITags(true); + } + unselectedList.setModel(allItems); + for (int i=0;i(allItems); + //selectedList = new JList<>(selectedItems); + updating=false; + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java index 786128f6bad..a4eee56a5b4 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java @@ -16,7 +16,7 @@ public class RewardsEditor extends JComponent{ JList list = new JList<>(model); JToolBar toolBar = new JToolBar("toolbar"); RewardEdit edit=new RewardEdit(); - + boolean updating; public class RewardDataRenderer extends DefaultListCellRenderer { @@ -67,14 +67,11 @@ public class RewardsEditor extends JComponent{ add(edit,BorderLayout.CENTER); - edit.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - emitChanged(); - } - }); + edit.addChangeListener(e -> emitChanged()); } protected void emitChanged() { + if (updating) + return; ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class); if (listeners != null && listeners.length > 0) { ChangeEvent evt = new ChangeEvent(this); @@ -131,6 +128,12 @@ public class RewardsEditor extends JComponent{ } return rewards; } + + public void clear(){ + updating = true; + model.clear(); + updating = false; + } public void addChangeListener(ChangeListener listener) { listenerList.add(ChangeListener.class, listener); } diff --git a/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java b/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java index 98a96406288..a31163de211 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java @@ -20,7 +20,7 @@ public class SwingAtlasPreview extends Box { Timer timer; public SwingAtlasPreview() { - this(32,200); + this(64,200); } public SwingAtlasPreview(int imageSize,int timeDelay) { super(BoxLayout.Y_AXIS); @@ -47,7 +47,7 @@ public class SwingAtlasPreview extends Box { setSpritePath(sprite,null); } public void setSpritePath(String sprite,String name) { - if(this.sprite==null||sprite==null||(this.sprite.equals(sprite)&&(spriteName!=null&&name!=null&&spriteName.equals(name)))) + if(this.sprite==null||sprite==null||(this.sprite.equals(sprite)&&(spriteName != null && spriteName.equals(name)))) return; removeAll(); counter=0; @@ -85,10 +85,10 @@ public class SwingAtlasPreview extends Box { { timer.restart(); } - doLayout(); - revalidate(); - update(getGraphics()); - repaint(); +// doLayout(); //These lines cause images to bleed on to other tabs and don't appear to be needed +// revalidate(); +// update(getGraphics()); +// repaint(); } } diff --git a/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java index 8cc56aac09c..4a78f3d7602 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java @@ -1,13 +1,7 @@ package forge.adventure.editor; -import forge.adventure.util.Config; - import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.IOException; import java.util.List; /** @@ -20,16 +14,11 @@ public class TextListEdit extends Box { public TextListEdit(String[] possibleElements) { super(BoxLayout.X_AXIS); - findButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - TextListEdit.this.find(); - } - }); + findButton.addActionListener(e -> TextListEdit.this.find()); add(edit); edit.setPreferredSize(new Dimension(400,edit.getPreferredSize().height)); - //add(findButton); + add(findButton); elements= new JComboBox(possibleElements); add(elements); @@ -45,20 +34,32 @@ public class TextListEdit extends Box { } private void find() { - JFileChooser fc = new JFileChooser(); - fc.setCurrentDirectory(new File(Config.instance().getFilePath(""))); - fc.setMultiSelectionEnabled(false); - if (fc.showOpenDialog(this) == - JFileChooser.APPROVE_OPTION) { - File selected = fc.getSelectedFile(); + edit.setText((edit.getText().trim().length()>0?edit.getText() + ";": "")+elements.getSelectedItem().toString()); + elements.remove(elements.getSelectedIndex()); + elements.setSelectedIndex(0); +// JFileChooser fc = new JFileChooser(); +// fc.setCurrentDirectory(new File(Config.instance().getFilePath(""))); +// fc.setMultiSelectionEnabled(false); +// if (fc.showOpenDialog(this) == +// JFileChooser.APPROVE_OPTION) { +// File selected = fc.getSelectedFile(); +// +// try { +// if (selected != null&&selected.getCanonicalPath().startsWith(new File(Config.instance().getFilePath("")).getCanonicalPath())) { +// edit.setText(selected.getCanonicalPath().substring(new File(Config.instance().getFilePath("")).getCanonicalPath().length()+1)); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } + } - try { - if (selected != null&&selected.getCanonicalPath().startsWith(new File(Config.instance().getFilePath("")).getCanonicalPath())) { - edit.setText(selected.getCanonicalPath().substring(new File(Config.instance().getFilePath("")).getCanonicalPath().length()+1)); - } - } catch (IOException e) { - e.printStackTrace(); - } + public void setOptions(List itemNames) { + if(itemNames==null) + elements.removeAllItems(); + else { + for(String item: itemNames) + elements.addItem(item); } } @@ -105,7 +106,7 @@ public class TextListEdit extends Box { String intName=stringList[i]; try { - retList[i] = Integer.valueOf(intName); + retList[i] = Integer.parseInt(intName); } catch (NumberFormatException e) { diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 4090e7cb3d8..8bcf22c421f 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -1023,8 +1023,11 @@ public class Forge implements ApplicationListener { return ((Forge)Gdx.app.getApplicationListener()).assets; } public static boolean switchScene(Scene newScene) { - if (newScene instanceof RewardScene || newScene instanceof SpellSmithScene || newScene instanceof DeckSelectScene || newScene instanceof PlayerStatisticScene) { - if (!(currentScene instanceof ForgeScene)) //prevent overwriting the last preview if last scene is instance of ForgeScene + return switchScene(newScene, false); + } + public static boolean switchScene(Scene newScene, boolean skipPreview) { + if (newScene instanceof RewardScene || newScene instanceof SpellSmithScene || newScene instanceof DeckSelectScene || newScene instanceof PlayerStatisticScene || newScene instanceof QuestLogScene) { + if (!(currentScene instanceof ForgeScene || skipPreview)) //prevent overwriting the last preview if last scene is instance of ForgeScene WorldSave.getCurrentSave().header.createPreview(); } if (currentScene != null) { diff --git a/forge-gui-mobile/src/forge/adventure/character/DialogActor.java b/forge-gui-mobile/src/forge/adventure/character/DialogActor.java index 59476142ebd..e8d9d12599b 100644 --- a/forge-gui-mobile/src/forge/adventure/character/DialogActor.java +++ b/forge-gui-mobile/src/forge/adventure/character/DialogActor.java @@ -2,7 +2,9 @@ package forge.adventure.character; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import forge.adventure.data.AdventureQuestData; import forge.adventure.stage.MapStage; +import forge.adventure.util.Current; import forge.adventure.util.MapDialog; /** @@ -11,7 +13,8 @@ import forge.adventure.util.MapDialog; public class DialogActor extends CharacterSprite { private final MapStage stage; private final TextureRegion textureRegion; - private final MapDialog dialog; + private MapDialog dialog; + public AdventureQuestData questData; public DialogActor(MapStage stage, int id, String S, TextureRegion textureRegion) { super(id,""); @@ -26,6 +29,19 @@ public class DialogActor extends CharacterSprite { this.textureRegion = null; } + public DialogActor(AdventureQuestData data, MapStage stage, int id){ + super(id,""); + this.stage = stage; + dialog = new MapDialog(data.offerDialog, stage, id); + this.textureRegion = null; + this.questData = data; + dialog.addQuestAcceptedListener(e -> acceptQuest()); + } + + public void acceptQuest(){ + Current.player().addQuest(questData); + } + @Override public void onPlayerCollide() { stage.resetPosition(); diff --git a/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java b/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java index 2a64accb84a..18c87eb65f3 100644 --- a/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java +++ b/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java @@ -14,9 +14,11 @@ import forge.adventure.data.EffectData; import forge.adventure.data.EnemyData; import forge.adventure.data.RewardData; import forge.adventure.player.AdventurePlayer; +import forge.adventure.pointofintrest.PointOfInterestChanges; import forge.adventure.util.Current; import forge.adventure.util.MapDialog; import forge.adventure.util.Reward; +import forge.adventure.world.WorldSave; import forge.card.CardRarity; import forge.deck.Deck; import forge.item.PaperCard; @@ -54,6 +56,8 @@ public class EnemySprite extends CharacterSprite { public float threatRange = 0.0f; public float fleeRange = 0.0f; public boolean ignoreDungeonEffect = false; + private PointOfInterestChanges changes; + public float spawnDelay; public EnemySprite(EnemyData enemyData) { this(0,enemyData); @@ -64,6 +68,12 @@ public class EnemySprite extends CharacterSprite { data = enemyData; } + public EnemySprite(int id, EnemyData enemyData, PointOfInterestChanges changes) { + super(id,enemyData.sprite); + data = enemyData; + this.changes = changes; + } + public void parseWaypoints(String waypoints){ String[] wp = waypoints.replaceAll("\\s", "").split(","); for (String s : wp) { @@ -106,6 +116,7 @@ public class EnemySprite extends CharacterSprite { } public Vector2 getTargetVector(PlayerSprite player, float delta) { + //todo - this can be integrated into overworld movement as well, giving flee behaviors or moving to generated waypoints Vector2 target = pos(); Vector2 routeToPlayer = new Vector2(player.pos()).sub(target); @@ -227,14 +238,25 @@ public class EnemySprite extends CharacterSprite { Deck enemyDeck = Current.latestDeck(); /*// By popular demand, remove basic lands from the reward pool. CardPool deckNoBasicLands = enemyDeck.getMain().getFilteredPool(Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND), PaperCard.FN_GET_RULES));*/ + if (changes!=null){ + long shopSeed = changes.getShopSeed(objectId); + WorldSave.getCurrentSave().getWorld().getRandom().setSeed(shopSeed); + } + for (RewardData rdata : data.rewards) { - ret.addAll(rdata.generate(false, enemyDeck == null ? null : enemyDeck.getMain().toFlatList() )); + ret.addAll(rdata.generate(false, enemyDeck == null ? null : enemyDeck.getMain().toFlatList(),true )); } } if(rewards != null) { //Collect additional rewards. for(RewardData rdata:rewards) { //Do not filter in case we want to FORCE basic lands. If it ever becomes a problem just repeat the same as above. - ret.addAll(rdata.generate(false,(Current.latestDeck() != null ? Current.latestDeck().getMain().toFlatList() : null))); + if (changes != null) + { + long shopSeed = changes.getShopSeed(objectId); + WorldSave.getCurrentSave().getWorld().getRandom().setSeed(shopSeed); + } + + ret.addAll(rdata.generate(false,(Current.latestDeck() != null ? Current.latestDeck().getMain().toFlatList() : null), true)); } } } @@ -307,8 +329,21 @@ public class EnemySprite extends CharacterSprite { return data.speed; } + public float getLifetime() { + //default and minimum value for time to remain on overworld map + Float lifetime = 20f; + return Math.max(data.lifetime, lifetime); + } + public class MovementBehavior { + + //temporary placeholders for overworld behavior integration + public boolean wander = false; + public boolean flee = false; + public boolean stop = false; + //end temporary + float duration = 0.0f; float x = 0.0f; float y = 0.0f; diff --git a/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java b/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java index 47cd0fe395c..1d9f02578cb 100644 --- a/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java +++ b/forge-gui-mobile/src/forge/adventure/character/RewardSprite.java @@ -46,7 +46,7 @@ public class RewardSprite extends CharacterSprite { Array ret = new Array(); if(rewards == null) return ret; for(RewardData rdata:rewards) { - ret.addAll(rdata.generate(false)); + ret.addAll(rdata.generate(false, true)); } return ret; } diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java new file mode 100644 index 00000000000..6f02e142e46 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestData.java @@ -0,0 +1,416 @@ +package forge.adventure.data; + + +import com.badlogic.gdx.utils.Array; +import forge.adventure.character.EnemySprite; +import forge.adventure.pointofintrest.PointOfInterest; +import forge.adventure.scene.GameScene; +import forge.adventure.util.AdventureQuestController; +import forge.adventure.util.Current; +import forge.adventure.world.WorldSave; +import forge.util.Aggregates; + +import java.io.Serializable; +import java.util.*; + + +public class AdventureQuestData implements Serializable { + + private int id; + + public int getID(){ + if (isTemplate && id < 1) + id = Current.world().getNextQuestId(); + return id; + } + public boolean isTemplate = false; + public String name = ""; + public String description = ""; + public String synopsis =""; //Intended for Dev Mode only at most + public transient boolean completed = false; + public transient boolean failed = false; + private transient boolean prologueDisplayed = false; + private transient boolean epilogueDisplayed = false; + + public DialogData offerDialog; + public DialogData prologue; + public DialogData epilogue; + public DialogData failureDialog; + + public DialogData declinedDialog; + + public RewardData reward; + public String rewardDescription = ""; + + public AdventureQuestStage[] stages = new AdventureQuestStage[0]; + public String[] questTags = new String[0]; + public String[] questEnemyTags = new String[0]; + public String[] questPOITags = new String[0]; + private transient EnemySprite targetEnemySprite = null; + private PointOfInterest targetPoI = null; + Dictionary poiTokens = new Hashtable<>(); + Dictionary enemyTokens = new Hashtable<>(); + Dictionary otherTokens = new Hashtable<>(); + public boolean storyQuest = false; + public transient boolean isTracked = false; + public String sourceID = ""; + + public String getName() { + return name; + } + public String getDescription() { + return description; + } + public RewardData getReward() { + return reward; + } + + public AdventureQuestData(AdventureQuestData data){ + id = data.id; + isTemplate = false; //Anything being copied is by definition not a template + name = data.name; + description = data.description; + synopsis = data.synopsis; + offerDialog = new DialogData(data.offerDialog); + prologue = new DialogData(data.prologue); + epilogue = new DialogData(data.epilogue); + failureDialog = new DialogData(data.failureDialog); + declinedDialog = new DialogData(data.declinedDialog); + reward = new RewardData(data.reward); + rewardDescription = data.rewardDescription; + completed = data.completed; + stages = new AdventureQuestStage[data.stages.length]; + for (int i = 0; i < stages.length; i++){ + stages[i] = new AdventureQuestStage(data.stages[i]); + } + questTags = data.questTags.clone(); + questPOITags = data.questPOITags.clone(); + questEnemyTags = data.questEnemyTags.clone(); + targetPoI = data.targetPoI; + targetEnemySprite = data.targetEnemySprite; + storyQuest = data.storyQuest; + sourceID = data.sourceID; + poiTokens = data.poiTokens; + enemyTokens = data.enemyTokens; + otherTokens = data.otherTokens; + } + + public AdventureQuestData() + { + declinedDialog = new DialogData(); + declinedDialog.text = "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do."; + DialogData dismiss = new DialogData(); + dismiss.name = "(Catching the not so subtle hint, you leave.)"; + declinedDialog.options = new DialogData[1]; + declinedDialog.options[0] = dismiss; + } + + public List getActiveStages(){ + List toReturn = new ArrayList<>(); + + //Temporarily allow only one active stage until parallel stages and prerequisites are implemented + for (AdventureQuestStage stage : stages) { + if (stage.getStatus() == AdventureQuestController.QuestStatus.Complete) + continue; + if (stage.getStatus() == AdventureQuestController.QuestStatus.Inactive ) { + //todo: check prerequisites instead of break statement below + stage.setStatus(AdventureQuestController.QuestStatus.Active); + } + toReturn.add(stage); + break; + } + return toReturn; + } + + public List getStagesForQuestLog(){ + List toReturn = new ArrayList<>(); + + //Temporarily allow only one active stage until parallel stages and prerequisites are implemented + for (AdventureQuestStage stage : stages) { + if (stage.getStatus() == AdventureQuestController.QuestStatus.Complete + || stage.getStatus() == AdventureQuestController.QuestStatus.Failed) + { + toReturn.add(stage); + continue; + } + toReturn.add(stage); + break; + } + return toReturn; + } + + + public PointOfInterest getTargetPOI() { + + for (AdventureQuestStage stage : getActiveStages()) { + targetPoI = stage.getTargetPOI(); + if (targetPoI != null) + break; + } + + return targetPoI; + } + + public EnemySprite getTargetEnemySprite(){ + if (targetEnemySprite == null){ + for (AdventureQuestStage stage : getActiveStages()) { + targetEnemySprite = stage.getTargetSprite(); + if (targetEnemySprite != null){ + break; + } + } + } + return targetEnemySprite; + } + + public void initialize(){ + for (AdventureQuestStage stage : stages){ + initializeStage(stage); + } + + replaceTokens(); + } + + public void initializeStage(AdventureQuestStage stage){ + if (stage == null || stage.objective == null) return; + + switch (stage.objective){ + case Clear: + stage.setTargetPOI(poiTokens); + break; + case Defeat: + if (!stage.mixedEnemies) + stage.setTargetEnemyData(generateTargetEnemyData(stage)); + break; + case Delivery: + stage.setTargetPOI(poiTokens); + //Set delivery item as a miscellaneous token + break; + case Escort: + //add configuration of what is being escorted. + stage.setTargetPOI(poiTokens); + if (!stage.mixedEnemies) + stage.setTargetEnemyData(generateTargetEnemyData(stage)); + break; + case Hunt: + stage.setTargetSprite(generateTargetEnemySprite(stage)); + break; + case Leave: + stage.setTargetPOI(poiTokens); + break; + case MapFlag: + stage.setTargetPOI(poiTokens); + break; + case Patrol: + //Need ability to set a series of target coordinates that can be reached, point nav arrow to them + // This might get oddly complex. + break; + case Rescue: + stage.setTargetPOI(poiTokens); + break; + case Travel: + stage.setTargetPOI(poiTokens); + } + + if (stage.getTargetPOI() != null + && ("cave".equalsIgnoreCase( stage.getTargetPOI().getData().type) + || "dungeon".equalsIgnoreCase( stage.getTargetPOI().getData().type))){ + //todo: decide how to handle this in "anyPOI" scenarios + WorldSave.getCurrentSave().getPointOfInterestChanges(stage.getTargetPOI().getID() + stage.getTargetPOI().getData().map).clearDeletedObjects(); + } + + PointOfInterest temp = stage.getTargetPOI(); + if (temp != null) + poiTokens.put("$(poi_" + stage.id+")", temp); + + EnemyData target = stage.getTargetEnemyData(); + if (target != null) + enemyTokens.put("$(enemy_" + stage.id +")", target); + + otherTokens.put("$(playername)", Current.player().getName()); + otherTokens.put("$(currentbiome)", GameScene.instance().getAdventurePlayerLocation(true,false)); + + } + + public void replaceTokens(){ + replaceTokens(offerDialog); + replaceTokens(prologue); + replaceTokens(epilogue); + replaceTokens(failureDialog); + replaceTokens(declinedDialog); + + name = replaceTokens(name); + description = replaceTokens(description); + rewardDescription = replaceTokens(rewardDescription); + + for (AdventureQuestStage stage: stages) + { + replaceTokens(stage); + } + } + + private void replaceTokens(AdventureQuestStage stage){ + replaceTokens(stage.prologue); + replaceTokens(stage.epilogue); + replaceTokens(stage.failureDialog); + stage.name = replaceTokens(stage.name); + stage.description = replaceTokens(stage.description); + } + + private String replaceTokens(String data){ + for (Enumeration e = poiTokens.keys(); e.hasMoreElements();){ + String key = e.nextElement(); + data = data.replace(key, poiTokens.get(key).getData().name); + } + for (Enumeration enemy = enemyTokens.keys(); enemy.hasMoreElements();){ + String enemyKey = enemy.nextElement(); + data = data.replace(enemyKey, enemyTokens.get(enemyKey).getName()); + } + for (Enumeration other = otherTokens.keys(); other.hasMoreElements();){ + String key = other.nextElement(); + data = data.replace(key, otherTokens.get(key)); + } + return data; + } + + private void replaceTokens(DialogData data){ + for (DialogData option : data.options){ + replaceTokens(option); + } + for (Enumeration e = poiTokens.keys(); e.hasMoreElements();){ + String key = e.nextElement(); + data.text = data.text.replace(key, poiTokens.get(key).getData().name); + data.name = data.name.replace(key, poiTokens.get(key).getData().name); + } + for (Enumeration e = enemyTokens.keys(); e.hasMoreElements();){ + String key = e.nextElement(); + data.text = data.text.replace(key, enemyTokens.get(key).getName()); + data.name = data.name.replace(key, enemyTokens.get(key).getName()); + } + + for (Enumeration other = otherTokens.keys(); other.hasMoreElements();){ + String key = other.nextElement(); + data.text = data.text.replace(key, otherTokens.get(key)); + data.name = data.name.replace(key, otherTokens.get(key)); + } + + for (DialogData.ActionData ad: data.action) { + if ( ad != null && ad.POIReference != null) + { + for (Enumeration e = poiTokens.keys(); e.hasMoreElements(); ) { + String key = e.nextElement(); + ad.POIReference = ad.POIReference.replace(key, poiTokens.get(key).getID()); + } + } + } + } + + private EnemySprite generateTargetEnemySprite(AdventureQuestStage stage){ + if (stage.objective == AdventureQuestController.ObjectiveTypes.Hunt){ + EnemyData toUse = generateTargetEnemyData(stage); + toUse.lifetime = stage.count1; + return new EnemySprite(toUse); + } + return null; + } + + private EnemyData generateTargetEnemyData(AdventureQuestStage stage) + { + ArrayList matchesTags = new ArrayList<>(); + for(EnemyData data: new Array.ArrayIterator<>(WorldData.getAllEnemies())) { + boolean valid = true; + for (String tag : stage.enemyTags) { + if (!Arrays.asList(data.questTags).contains(tag)) { + valid = false; + break; + } + } + if (valid) + matchesTags.add(data); + } + if (matchesTags.isEmpty()){ + return new EnemyData(Aggregates.random(WorldData.getAllEnemies())); + } + else{ + return new EnemyData(Aggregates.random(matchesTags)); + } + } + + public void updateEnteredPOI(PointOfInterest arrivedAt){ + boolean done = true; + for (AdventureQuestStage stage: stages) { + + done = stage.updateEnterPOI(arrivedAt) == AdventureQuestController.QuestStatus.Complete && done; + if (!done) + break; + } + completed = done; + } + + public void updateMapFlag(String questFlag, int flagValue){ + boolean done = true; + for (AdventureQuestStage stage: stages) { + done = stage.updateMapFlag(questFlag, flagValue) == AdventureQuestController.QuestStatus.Complete && done; + if (!done) + break; + } + completed = done; + } + + public void updateLeave(){ + boolean done = true; + for (AdventureQuestStage stage: stages) { + done = stage.updateLeave() == AdventureQuestController.QuestStatus.Complete && done; + if (!done) + break; + } + completed = done; + } + + public void updateWin(EnemySprite defeated, boolean cleared){ + boolean done = true; + for (AdventureQuestStage stage: stages) { + done = stage.updateWin(defeated, cleared) == AdventureQuestController.QuestStatus.Complete && done; + if (!done) + break; + } + completed = done; + } + + public void updateLose(EnemySprite defeatedBy){ + for (AdventureQuestStage stage: stages) { + if(failed) + break; + failed = stage.updateLose(defeatedBy) == AdventureQuestController.QuestStatus.Failed; + } + } + + public void updateDespawn(EnemySprite despawned){ + for (AdventureQuestStage stage: stages) { + failed = stage.updateDespawn(despawned)== AdventureQuestController.QuestStatus.Failed || failed; + } + } + + public DialogData getPrologue() { + if (!prologueDisplayed) { + prologueDisplayed = true; + return prologue; + } + return null; + } + + public DialogData getEpilogue() { + if (!epilogueDisplayed) { + epilogueDisplayed = true; + return epilogue; + } + return null; + } + + public void fail(){ + failed = true; + isTracked = false; + //todo: handle any necessary cleanup or reputation loss + } +} + diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java new file mode 100644 index 00000000000..275c78180a7 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureQuestStage.java @@ -0,0 +1,307 @@ +package forge.adventure.data; + +import forge.adventure.character.EnemySprite; +import forge.adventure.pointofintrest.PointOfInterest; +import forge.adventure.util.AdventureQuestController; +import forge.adventure.util.Current; +import forge.util.Aggregates; + +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; + +public class AdventureQuestStage implements Serializable { + public int id; + private AdventureQuestController.QuestStatus status = AdventureQuestController.QuestStatus.Inactive; + public String name = ""; + public String description = ""; + public boolean anyPOI = false; //false: Pick one PoI. True: Any PoI matching tags is usable + public String mapFlag; //Map (or quest) flag to check + public int mapFlagValue; //Minimum value for the flag + public int count1; //use defined by objective type, this can be enemies to defeat, minimum PoI distance, etc + public int count2; //use defined by objective type, this can be enemies to defeat, minimum PoI distance, etc + public int count3; //use defined by objective type, this can be enemies to defeat, minimum PoI distance, etc + private int progress1; //Progress toward count1 + private int progress2; //Progress toward count2 + private int progress3; //Progress toward count3 + public boolean mixedEnemies; //false: Pick one enemy type. True: Combine all potential types + public boolean here; //Default PoI selection to current location + private PointOfInterest targetPOI; //Destination. Expand to array to cover "anyPOI?" + private transient EnemySprite targetSprite; //EnemySprite targeted by this quest stage. + private EnemyData targetEnemyData; //Valid enemy type for this quest stage when mixedEnemies is false. + public List POITags = new ArrayList<>(); //Tags defining potential targets + public AdventureQuestController.ObjectiveTypes objective; + public List prerequisiteNames = new ArrayList<>(); + public List enemyTags = new ArrayList<>(); //Tags defining potential targets + public List itemNames = new ArrayList<>(); //Tags defining items to use + public List equipNames = new ArrayList<>(); //Tags defining equipment to use + public boolean prologueDisplayed = false; + public boolean epilogueDisplayed = false; + public DialogData prologue; + public DialogData epilogue; + public DialogData failureDialog; + public boolean prequisitesComplete = false; + public String deliveryItem = ""; //Imaginary item to get/fetch/deliver. Could be a general purpose field. + public String POIToken; //If defined, ignore tags input and use the target POI from a different stage's objective instead. + private transient boolean inTargetLocation = false; + + public void checkPrerequisites() { + //Todo - implement + } + + public AdventureQuestController.QuestStatus getStatus(){ + return status; + } + + public void setStatus(AdventureQuestController.QuestStatus newStatus){ + if (!status.equals(newStatus) && newStatus.equals(AdventureQuestController.QuestStatus.Active)){ + AdventureQuestController.instance().addQuestSprites(this); + } + status = newStatus; + } + + public PointOfInterest getTargetPOI() { + return targetPOI; + } + + public void setTargetPOI(PointOfInterest target) { + if (!anyPOI) + targetPOI = target; + } + + public void setTargetPOI(Dictionary poiTokens){ + + if (POIToken != null && POIToken.length() > 0){ + PointOfInterest tokenTarget = poiTokens.get(POIToken); + if (tokenTarget != null){ + setTargetPOI(tokenTarget); + return; + } + else{ + System.out.println("Quest Stage '" + this.name+ "' failed to generate POI from token reference: '" + POIToken +"'"); + } + } + if (here){ + setTargetPOI(AdventureQuestController.instance().mostRecentPOI); + return; + } + if (!anyPOI) { + List candidates = Current.world().getAllPointOfInterest(); + for (String tag : POITags) + { + candidates.removeIf(q -> Arrays.stream(q.getData().questTags).noneMatch(tag::equals)); + } + if (candidates.size() < 1) + { + return; + } + count1 = (count1* candidates.size()/ 100); + count2 = (count2* candidates.size()) /100; + int targetIndex = Math.max(0,(int) (count1 - count2 + (new Random().nextFloat() * count2 * 2))); + + if (targetIndex < candidates.size() && targetIndex > 0 && count1 > 0) { + candidates.sort(new AdventureQuestController.DistanceSort()); + setTargetPOI(candidates.get(targetIndex)); + } + else{ + if (count1 !=0 || count2 != 0) { + System.out.println("Quest Stage '" + this.name + "' has invalid count1 ('" + count1 + "') and/or count2 ('" + count2 + "') value"); + } + setTargetPOI(Aggregates.random(candidates)); + } + } + //"else" any POI matching all the POITags is valid, evaluate as needed + } + + public EnemySprite getTargetSprite() { + return targetSprite; + } + public void setTargetEnemyData(EnemyData target) { + targetEnemyData = target; + } + public EnemyData getTargetEnemyData() { + if (targetEnemyData == null & targetSprite!=null) + return targetSprite.getData(); + return targetEnemyData; + } + + public void setTargetSprite(EnemySprite target) { + targetSprite = target; + } + + public AdventureQuestController.QuestStatus updateEnterPOI(PointOfInterest entered) { + if (getStatus() == AdventureQuestController.QuestStatus.Complete){ + return status; + } + else if (getStatus() == AdventureQuestController.QuestStatus.Failed){ + return status; + } + else{ + checkIfInTargetLocation(entered); + if (inTargetLocation && + (this.objective == AdventureQuestController.ObjectiveTypes.Delivery || + this.objective == AdventureQuestController.ObjectiveTypes.Travel)) { + status = AdventureQuestController.QuestStatus.Complete; + } + } + return status; + } + + public AdventureQuestController.QuestStatus updateMapFlag(String mapFlag, int mapFlagValue){ + if (getStatus() == AdventureQuestController.QuestStatus.Complete){ + return status; + } + else if (getStatus() == AdventureQuestController.QuestStatus.Failed){ + return status; + } + else { + if (this.objective == AdventureQuestController.ObjectiveTypes.MapFlag) { + if (mapFlag.equals(this.mapFlag) && mapFlagValue >= this.mapFlagValue) + status = AdventureQuestController.QuestStatus.Complete; + } + return status; + } + } + public AdventureQuestController.QuestStatus updateLeave(){ + if (status == AdventureQuestController.QuestStatus.Complete){ + return status; + } + if (status == AdventureQuestController.QuestStatus.Failed){ + return status; + } + inTargetLocation = false; //todo: handle case when called between multi-map PoIs (if necessary) + if (this.objective == AdventureQuestController.ObjectiveTypes.Leave){ + status = AdventureQuestController.QuestStatus.Complete; + } + return status; + } + + public AdventureQuestController.QuestStatus updateWin(EnemySprite defeated, boolean mapCleared){ + //todo - Does this need to also be called for alternate mob removal types? + if (status == AdventureQuestController.QuestStatus.Complete){ + return status; + } + if (status == AdventureQuestController.QuestStatus.Failed){ + return status; + } + if (this.objective == AdventureQuestController.ObjectiveTypes.Clear) + { + if (mapCleared && inTargetLocation) + { + status = AdventureQuestController.QuestStatus.Complete; + } + } + else if (this.objective == AdventureQuestController.ObjectiveTypes.Defeat) { + { + List defeatedTags = Arrays.stream(defeated.getData().questTags).collect(Collectors.toList()); + for (String targetTag : enemyTags) { + if (!defeatedTags.contains(targetTag)) { + //Does not count toward objective + return status; + } + } + //All tags matched, kill confirmed + if (++progress1 >= count1){ + status = AdventureQuestController.QuestStatus.Complete; + } + } + } + else if (this.objective == AdventureQuestController.ObjectiveTypes.Hunt) + { + if (defeated.equals(targetSprite)){ + status = AdventureQuestController.QuestStatus.Complete; + } + } + + return status; + } + + public void checkIfInTargetLocation(PointOfInterest entered){ + if (targetPOI == null){ + List enteredTags = Arrays.stream(entered.getData().questTags).collect(Collectors.toList()); + for (String tag : POITags){ + if (!enteredTags.contains(tag)) { + inTargetLocation = false; + return; + } + } + } + else if (!targetPOI.equals(entered)){ + inTargetLocation = false; + return; + } + inTargetLocation = true; + } + + public AdventureQuestController.QuestStatus updateLose(EnemySprite defeatedBy){ + + if (status != AdventureQuestController.QuestStatus.Failed && this.objective == AdventureQuestController.ObjectiveTypes.Defeat) { + { + List defeatedByTags = Arrays.stream(defeatedBy.getData().questTags).collect(Collectors.toList()); + for (String targetTag : enemyTags) { + if (!defeatedByTags.contains(targetTag)) { + //Does not count + return status; + } + } + //All tags matched + if (status == AdventureQuestController.QuestStatus.Active && ++progress2 >= count2){ + status = AdventureQuestController.QuestStatus.Failed; + } + + } + } + else if (status == AdventureQuestController.QuestStatus.Active && this.objective == AdventureQuestController.ObjectiveTypes.Hunt) + { + if (defeatedBy.equals(targetSprite)){ + status = AdventureQuestController.QuestStatus.Failed; + } + } + return status; + } + + public AdventureQuestController.QuestStatus updateDespawn(EnemySprite despawned){ + if (status == AdventureQuestController.QuestStatus.Active && this.objective == AdventureQuestController.ObjectiveTypes.Hunt) + { + if (despawned.equals(targetSprite)){ + status = AdventureQuestController.QuestStatus.Failed; + } + } + return status; + } + + public AdventureQuestStage(){} + public AdventureQuestStage(AdventureQuestStage other){ + this.status = other.status; + this.prologueDisplayed = other.prologueDisplayed; + this.prologue = new DialogData(other.prologue); + this.epilogueDisplayed = other.epilogueDisplayed; + this.epilogue = new DialogData(other.epilogue); + this.failureDialog = new DialogData(other.failureDialog); + this.name = other.name; + this.description = other.description; + this.progress1 = other.progress1; + this.progress2 = other.progress2; + this.progress3 = other.progress3; + this.count1 = other.count1; + this.count2 = other.count2; + this.count3 = other.count3; + this.enemyTags = other.enemyTags; + this.anyPOI = other.anyPOI; + this.here = other.here; + this.targetPOI = other.targetPOI; + this.objective = other.objective; + this.mapFlagValue = other.mapFlagValue; + this.mapFlag = other.mapFlag; + this.equipNames = other.equipNames; + this.mixedEnemies = other.mixedEnemies; + this.itemNames = other.itemNames; + this.prequisitesComplete = other.prequisitesComplete; + this.prerequisiteNames = other.prerequisiteNames; + this.POIToken = other.POIToken; + this.id = other.id; + this.POITags = other.POITags; + this.targetEnemyData = other.targetEnemyData; + this.deliveryItem = other.deliveryItem; + } +} diff --git a/forge-gui-mobile/src/forge/adventure/data/DialogData.java b/forge-gui-mobile/src/forge/adventure/data/DialogData.java index ce4810fc68f..33d9e1781f2 100644 --- a/forge-gui-mobile/src/forge/adventure/data/DialogData.java +++ b/forge-gui-mobile/src/forge/adventure/data/DialogData.java @@ -1,23 +1,50 @@ package forge.adventure.data; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + /** * Dialog Data JSON loader class. * Carries all text, branches and effects of dialogs. */ -public class DialogData { - public ActionData[] action; //List of effects to cause when the dialog shows. - public ConditionData[] condition; //List of conditions for the action to show. - public String name; //Text to display when action is listed as a button. - public String locname; //References a localized string for the button labels. - public String text; //The text body. - public String loctext; //References a localized string for the text body. - public DialogData[] options; //List of sub-dialogs. Show up as options in the current one. +public class DialogData implements Serializable { + public ActionData[] action = new ActionData[0]; //List of effects to cause when the dialog shows. + public ConditionData[] condition = new ConditionData[0]; //List of conditions for the action to show. + public String name = ""; //Text to display when action is listed as a button. + public String locname = ""; //References a localized string for the button labels. + public String text = ""; //The text body. + public String loctext= ""; //References a localized string for the text body. + public DialogData[] options = new DialogData[0]; //List of sub-dialogs. Show up as options in the current one. + public DialogData(){} + public DialogData(DialogData other){ + if (other == null) + return; + + this.action = other.action.clone(); + this.condition = other.condition.clone(); + this.name = other.name; + this.locname = other.locname.isEmpty()?"":("Copy of " + other.locname); + this.text = other.text; + this.loctext = other.loctext; + List clonedOptions = new ArrayList<>(); + for (DialogData option: other.options){ + clonedOptions.add(new DialogData(option)); + } + this.options = clonedOptions.toArray(new DialogData[0]); + this.voiceFile = other.voiceFile; + } + + @Override + public String toString(){ + return this.name; + } public String voiceFile; - static public class ActionData { - static public class QuestFlag { + static public class ActionData implements Serializable { + static public class QuestFlag implements Serializable{ public String key; public int val; } @@ -25,6 +52,7 @@ public class DialogData { public String addItem; //Add item name to inventory. public int addLife = 0; //Gives the player X health. Negative to take. public int addGold = 0; //Gives the player X gold. Negative to take. + public int deleteMapObject = 0; //Remove ID from the map. -1 for self. public int battleWithActorID = 0; //Start a battle with enemy ID. -1 for self if possible. public EffectData giveBlessing; //Give a blessing to the player. @@ -35,6 +63,41 @@ public class DialogData { public QuestFlag setQuestFlag; //Set quest flag. public QuestFlag setMapFlag; //Set map flag. + public RewardData[] grantRewards = new RewardData[0]; //launch a RewardScene with the provided data. + public String issueQuest; //Add quest with this ID to the player's questlog. + + public int addMapReputation = 0; //Gives the player X reputation points in this POI. Negative to take. + public String POIReference; //used with addMapReputation when a quest step affects reputation in another location + + public ActionData(){} + + public ActionData(ActionData other){ + removeItem = other.removeItem; + addItem = other.removeItem; + addLife = other.addLife; + addGold = other.addGold; + deleteMapObject = other.deleteMapObject; + battleWithActorID = other.battleWithActorID; + giveBlessing = other.giveBlessing; + setColorIdentity = other.setColorIdentity; + advanceQuestFlag = other.advanceQuestFlag; + advanceMapFlag = other.advanceMapFlag; + setEffect = other.setEffect; + setQuestFlag = new QuestFlag(); + if (other.setQuestFlag != null) { + setQuestFlag.key = other.setQuestFlag.key; + setQuestFlag.val = other.setQuestFlag.val; + } + setMapFlag = new QuestFlag(); + if (other.setMapFlag != null) { + setMapFlag.key = other.setMapFlag.key; + setMapFlag.val = other.setMapFlag.val; + } + grantRewards = other.grantRewards.clone(); + issueQuest = other.issueQuest; + addMapReputation = other.addMapReputation; + POIReference = other.POIReference; + } } static public class ConditionData { @@ -47,6 +110,7 @@ public class DialogData { public int actorID = 0; //Check for an actor ID. public String hasBlessing = null; //Check for specific blessing, if named. public int hasGold = 0; //Check for player gold. True if gold is equal or higher than X. + public int hasMapReputation = Integer.MIN_VALUE; //Check for player reputation in this POI. True if reputation is equal or higher than X. public int hasLife = 0; //Check for player life. True if life is equal or higher than X. public String colorIdentity = null; //Check for player's current color identity. public String checkQuestFlag = null; //Check if a quest flag is not 0. False if equals 0 (not started, not set). diff --git a/forge-gui-mobile/src/forge/adventure/data/EnemyData.java b/forge-gui-mobile/src/forge/adventure/data/EnemyData.java index 4237be00b35..3c733083230 100644 --- a/forge-gui-mobile/src/forge/adventure/data/EnemyData.java +++ b/forge-gui-mobile/src/forge/adventure/data/EnemyData.java @@ -4,12 +4,14 @@ import forge.adventure.util.*; import forge.deck.Deck; import forge.util.Aggregates; +import java.io.Serializable; + /** * Data class that will be used to read Json configuration files * BiomeData * contains the information of enemies */ -public class EnemyData { +public class EnemyData implements Serializable { public String name; public String nameOverride; public String sprite; @@ -31,6 +33,9 @@ public class EnemyData { public EnemyData nextEnemy; public int teamNumber = -1; + public String[] questTags = new String[0]; + public float lifetime; + public EnemyData() { } @@ -53,6 +58,8 @@ public class EnemyData { teamNumber = enemyData.teamNumber; nextEnemy = enemyData.nextEnemy == null ? null : new EnemyData(enemyData.nextEnemy); nameOverride = enemyData.nameOverride == null ? "" : enemyData.nameOverride; + questTags = enemyData.questTags.clone(); + lifetime = enemyData.lifetime; if (enemyData.scale == 0.0f) { scale = 1.0f; } @@ -71,4 +78,11 @@ public class EnemyData { } return CardUtil.getDeck(deck[Current.player().getEnemyDeckNumber(this.name, deck.length)], true, isFantasyMode, colors, life > 13, life > 16 && useGeneticAI); } + + public String getName(){ + //todo: make this the default accessor for anything seen in UI + if (!nameOverride.isEmpty()) + return nameOverride; + return name; + } } diff --git a/forge-gui-mobile/src/forge/adventure/data/PointOfInterestData.java b/forge-gui-mobile/src/forge/adventure/data/PointOfInterestData.java index fe888ff566f..3bd468d294f 100644 --- a/forge-gui-mobile/src/forge/adventure/data/PointOfInterestData.java +++ b/forge-gui-mobile/src/forge/adventure/data/PointOfInterestData.java @@ -6,12 +6,14 @@ import com.badlogic.gdx.utils.Json; import forge.adventure.util.Config; import forge.adventure.util.Paths; +import java.io.Serializable; + /** * Data class that will be used to read Json configuration files * BiomeData * contains the information for the point of interests like towns and dungeons */ -public class PointOfInterestData { +public class PointOfInterestData implements Serializable { public String name; public String type; public int count; @@ -21,6 +23,8 @@ public class PointOfInterestData { public float radiusFactor; public float offsetX=0f; public float offsetY=0f; + public boolean active = true; + public String[] questTags = new String[0]; @@ -31,9 +35,7 @@ public class PointOfInterestData { Json json = new Json(); FileHandle handle = Config.instance().getFile(Paths.POINTS_OF_INTEREST); if (handle.exists()) { - Array readJson = json.fromJson(Array.class, PointOfInterestData.class, handle); - pointOfInterestList = readJson; - + pointOfInterestList = json.fromJson(Array.class, PointOfInterestData.class, handle); } } @@ -60,5 +62,7 @@ public class PointOfInterestData { radiusFactor=other.radiusFactor; offsetX=other.offsetX; offsetY=other.offsetY; + active=other.active; + questTags = other.questTags.clone(); } } diff --git a/forge-gui-mobile/src/forge/adventure/data/RewardData.java b/forge-gui-mobile/src/forge/adventure/data/RewardData.java index 23c4cc6d0d0..a334be1dfc7 100644 --- a/forge-gui-mobile/src/forge/adventure/data/RewardData.java +++ b/forge-gui-mobile/src/forge/adventure/data/RewardData.java @@ -12,10 +12,8 @@ import forge.adventure.world.WorldSave; import forge.item.PaperCard; import forge.model.FModel; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; +import java.io.Serializable; +import java.util.*; /** * Data class that will be used to read Json configuration files @@ -24,7 +22,7 @@ import java.util.List; * that can be a random card, gold or items. * Also used for deck generation and shops */ -public class RewardData { +public class RewardData implements Serializable { public String type; public float probability; public int count; @@ -51,6 +49,9 @@ public class RewardData { public RewardData() { } public RewardData(RewardData rewardData) { + if (rewardData == null) + return; + type =rewardData.type; probability =rewardData.probability; count =rewardData.count; @@ -115,17 +116,24 @@ public class RewardData { return allCards; } - public Array generate(boolean isForEnemy) { - return generate(isForEnemy, null); + public Array generate(boolean isForEnemy, boolean useSeedlessRandom) { + return generate(isForEnemy, null, useSeedlessRandom); } - public Array generate(boolean isForEnemy, Iterable cards) { + + public Array generate(boolean isForEnemy, Iterable cards, boolean useSeedlessRandom) { + + Random rewardRandom = useSeedlessRandom?new Random():WorldSave.getCurrentSave().getWorld().getRandom(); + //Keep using same generation method for shop rewards, but fully randomize loot drops by not using the instance pre-seeded by the map + if(allCards==null) initializeAllCards(); Array ret=new Array<>(); - if(probability == 0 || WorldSave.getCurrentSave().getWorld().getRandom().nextFloat() <= probability) { + + + if(probability == 0 || rewardRandom.nextFloat() <= probability) { if(type==null || type.isEmpty()) type="randomCard"; int maxCount=Math.round(addMaxCount*Current.player().getDifficulty().rewardMaxFactor); - int addedCount = (maxCount > 0 ? WorldSave.getCurrentSave().getWorld().getRandom().nextInt(maxCount) : 0); + int addedCount = (maxCount > 0 ? rewardRandom.nextInt(maxCount) : 0); switch(type) { case "Union": @@ -137,7 +145,7 @@ public class RewardData { if (finalPool.size() > 0){ for (int i = 0; i < count; i++) { - ret.add(new Reward(finalPool.get(WorldSave.getCurrentSave().getWorld().getRandom().nextInt(finalPool.size())))); + ret.add(new Reward(finalPool.get(rewardRandom.nextInt(finalPool.size())))); } } break; @@ -148,7 +156,7 @@ public class RewardData { ret.add(new Reward(StaticData.instance().getCommonCards().getCard(cardName))); } } else { - for(PaperCard card:CardUtil.generateCards(isForEnemy ? allEnemyCards:allCards,this, count+addedCount)) { + for(PaperCard card:CardUtil.generateCards(isForEnemy ? allEnemyCards:allCards,this, count+addedCount, rewardRandom)) { ret.add(new Reward(card)); } } @@ -168,7 +176,7 @@ public class RewardData { break; case "deckCard": if(cards == null) return ret; - for(PaperCard card: CardUtil.generateCards(cards,this, count + addedCount + Current.player().bonusDeckCards() )) { + for(PaperCard card: CardUtil.generateCards(cards,this, count + addedCount + Current.player().bonusDeckCards() ,rewardRandom)) { ret.add(new Reward(card)); } break; @@ -193,7 +201,7 @@ public class RewardData { static public Iterable generateAll(Iterable dataList, boolean isForEnemy) { Array ret=new Array(); for (RewardData data:dataList) - ret.addAll(data.generate(isForEnemy)); + ret.addAll(data.generate(isForEnemy, false)); return ret; } static public List rewardsToCards(Iterable dataList) { diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index 0ad4640b8c1..f4a454551ee 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -5,10 +5,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; import com.google.common.collect.Lists; -import forge.adventure.data.DifficultyData; -import forge.adventure.data.EffectData; -import forge.adventure.data.HeroListData; -import forge.adventure.data.ItemData; +import forge.adventure.data.*; import forge.adventure.util.*; import forge.adventure.world.WorldSave; import forge.card.ColorSet; @@ -23,10 +20,7 @@ import forge.sound.SoundSystem; import forge.util.ItemPool; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * Class that represents the player (not the player sprite) @@ -59,6 +53,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { private final Array inventoryItems=new Array<>(); private final HashMap equippedItems=new HashMap<>(); + private List quests= new ArrayList<>(); // Fantasy/Chaos mode settings. private boolean fantasyMode = false; @@ -99,6 +94,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { inventoryItems.clear(); equippedItems.clear(); questFlags.clear(); + quests.clear(); cards.clear(); statistic.clear(); newCards.clear(); @@ -292,6 +288,12 @@ public class AdventurePlayer implements Serializable, SaveFileContent { questFlags.put(keys[i], values[i]); } } + if(data.containsKey("quests")){ + quests.clear(); + Object[] q = (Object[]) data.readObject("quests"); + for (Object itsReallyAQuest: q) + quests.add((AdventureQuestData) itsReallyAQuest); + } for(int i=0;i getQuests() { + return quests; + } + public int getEnemyDeckNumber(String enemyName, int maxDecks){ int deckNumber = 0; if (statistic.getWinLossRecord().get(enemyName)!=null) @@ -721,4 +755,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { return deckNumber; } + public void removeQuest(AdventureQuestData quest) { + quests.remove(quest); + } } diff --git a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterest.java b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterest.java index 07baac698a9..227b6c93360 100644 --- a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterest.java +++ b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterest.java @@ -9,13 +9,13 @@ import forge.adventure.util.Config; import forge.adventure.util.SaveFileContent; import forge.adventure.util.SaveFileData; +import java.io.Serializable; import java.util.Random; /** * Point of interest stored in the world */ -public class PointOfInterest implements SaveFileContent { - +public class PointOfInterest implements Serializable, SaveFileContent { @Override public void load(SaveFileData saveFileData) { @@ -24,7 +24,13 @@ public class PointOfInterest implements SaveFileContent { data=PointOfInterestData.getPointOfInterest(saveFileData.readString("name")); rectangle.set(saveFileData.readRectangle("rectangle")); spriteIndex=saveFileData.readInt("spriteIndex"); - + if (saveFileData.containsKey("active")){ + active = saveFileData.readBool("active"); + } + else + { + active = data.active; + } oldMapId=""; Array textureAtlas = Config.instance().getAtlas(this.data.spriteAtlas).createSprites(this.data.sprite); @@ -39,15 +45,17 @@ public class PointOfInterest implements SaveFileContent { data.store("position",position); data.store("rectangle",rectangle); data.store("spriteIndex",spriteIndex); + data.store("active",active); return data; } PointOfInterestData data; final Vector2 position=new Vector2(); - Sprite sprite; + transient Sprite sprite; int spriteIndex; final Rectangle rectangle=new Rectangle(); String oldMapId=""; + boolean active = true; public PointOfInterest() { } public PointOfInterest(PointOfInterestData d, Vector2 pos, Random rand) { @@ -58,6 +66,7 @@ public class PointOfInterest implements SaveFileContent { spriteIndex = rand.nextInt(Integer.SIZE - 1) % textureAtlas.size; sprite = textureAtlas.get(spriteIndex); data = d; + active = d.active; position.set(pos); rectangle.set(position.x, position.y, sprite.getWidth(), sprite.getHeight()); @@ -66,6 +75,7 @@ public class PointOfInterest implements SaveFileContent { spriteIndex = parent.spriteIndex; sprite = parent.sprite; data = d; + active = d.active; position.set(parent.position); oldMapId=parent.getID(); rectangle.set(position.x, position.y, sprite.getWidth(), sprite.getHeight()); @@ -98,4 +108,14 @@ public class PointOfInterest implements SaveFileContent { return getSeedOffset()+data.name+"/"+oldMapId; } + public boolean getActive() {return active;} + + public void setActive(boolean active) {this.active = active;} + + public Vector2 getNavigationVector(Vector2 origin){ + Vector2 navVector = new Vector2(rectangle.x + rectangle.getWidth() / 2, rectangle.y + rectangle.getHeight() / 2); + if (origin != null) navVector.sub(origin); + + return navVector; + } } diff --git a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java index 715930e707b..4af345af804 100644 --- a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java +++ b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java @@ -17,6 +17,7 @@ public class PointOfInterestChanges implements SaveFileContent { private final java.util.Map mapFlags = new HashMap<>(); private final java.util.Map shopSeeds = new HashMap<>(); private final java.util.Map shopModifiers = new HashMap<>(); + private final java.util.Map reputation = new HashMap<>(); public static class Map extends HashMap implements SaveFileContent { @Override @@ -103,6 +104,7 @@ public class PointOfInterestChanges implements SaveFileContent { } public void generateNewShopSeed(int objectID){ + shopSeeds.put(objectID, Current.world().getRandom().nextLong()); cardsBought.put(objectID, new HashSet<>()); //Allows cards to appear in slots of previous purchases } @@ -134,11 +136,33 @@ public class PointOfInterestChanges implements SaveFileContent { return shopModifiers.get(0); } - public void setTownModifier(float mod){ - shopModifiers.put(0, mod); + public void addMapReputation(int delta) + { + addObjectReputation(0, delta); + } + + public void addObjectReputation(int id, int delta) + { + reputation.put(id, (reputation.containsKey(id)?reputation.get(id):0) + delta); + } + + public int getMapReputation(){ + return getObjectReputation(0); + } + + public int getObjectReputation(int id){ + if (!reputation.containsKey(id)) + { + reputation.put(id, 0); + } + return reputation.get(id); } public boolean hasDeletedObjects() { return deletedObjects != null && !deletedObjects.isEmpty(); } + public void clearDeletedObjects() { + // reset map when assigning as a quest target that needs enemies + deletedObjects.clear(); + } } diff --git a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestMap.java b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestMap.java index a8b5157a568..d24c3a0ae2c 100644 --- a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestMap.java +++ b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestMap.java @@ -25,7 +25,7 @@ public class PointOfInterestMap implements SaveFileContent { mapObjects = new List[numberOfChunksX][numberOfChunksY]; for (int x = 0; x < numberOfChunksX; x++) { for (int y = 0; y < numberOfChunksY; y++) { - mapObjects[x][y] = new ArrayList(); + mapObjects[x][y] = new ArrayList<>(); } } } @@ -53,9 +53,23 @@ public class PointOfInterestMap implements SaveFileContent { } return null; } + + public List getAllPointOfInterest() { + List allPOI = new ArrayList<>(); + + for(List[] poiList1:mapObjects) + { + for(List poiList:poiList1) + { + allPOI.addAll(poiList); + } + } + return allPOI; + } + public List pointsOfInterest(int chunkX, int chunkY) { if (chunkX >= numberOfChunksX || chunkY >= numberOfChunksY || chunkX < 0 || chunkY < 0) - return new ArrayList(); + return new ArrayList<>(); return mapObjects[chunkX][chunkY]; } @@ -70,7 +84,7 @@ public class PointOfInterestMap implements SaveFileContent { mapObjects = new List[numberOfChunksX][numberOfChunksY]; for (int x = 0; x < numberOfChunksX; x++) { for (int y = 0; y < numberOfChunksY; y++) { - mapObjects[x][y] = new ArrayList(); + mapObjects[x][y] = new ArrayList<>(); int arraySize=data.readInt("mapObjects["+x +"]["+y+"]"); for(int i=0;i data = new Array<>(); for (int i = 0; i < roundsWon; i++) { for (int j = 0; j < arenaData.rewards[i].length; j++) { - data.addAll(arenaData.rewards[i][j].generate(false, null)); + data.addAll(arenaData.rewards[i][j].generate(false, null, true)); } } RewardScene.instance().loadRewards(data, RewardScene.Type.Loot, null); diff --git a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java index a386d174feb..74ee9dcb995 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java @@ -12,10 +12,7 @@ import com.github.tommyettinger.textra.TextraLabel; import forge.Forge; import forge.adventure.data.DifficultyData; import forge.adventure.data.HeroListData; -import forge.adventure.util.AdventureModes; -import forge.adventure.util.Config; -import forge.adventure.util.Selector; -import forge.adventure.util.UIActor; +import forge.adventure.util.*; import forge.adventure.world.WorldSave; import forge.card.CardEdition; import forge.card.ColorSet; diff --git a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java index 8f7dff55a45..1f50a4820f8 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java @@ -1,6 +1,5 @@ package forge.adventure.scene; -import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.ui.Image; @@ -50,6 +49,7 @@ public class PlayerStatisticScene extends UIScene { Table root; boolean toggle = false; AchievementCollection planeswalkers, achievements; + Scene lastGameScene; private PlayerStatisticScene() { super(Forge.isLandscapeMode() ? "ui/statistic.json" : "ui/statistic_portrait.json"); @@ -59,10 +59,10 @@ public class PlayerStatisticScene extends UIScene { scrollContainer.row(); achievementContainer = new Table(Controls.getSkin()); blessingScroll = Controls.newTextraLabel(""); - blessingScroll.setColor(Color.BLACK); blessingScroll.setAlignment(Align.topLeft); blessingScroll.setWrap(true); ui.onButtonPress("return", PlayerStatisticScene.this::back); + ui.onButtonPress("quests", PlayerStatisticScene.this::quests); avatar = ui.findActor("avatar"); avatarBorder = ui.findActor("avatarBorder"); playerName = ui.findActor("playerName"); @@ -104,13 +104,14 @@ public class PlayerStatisticScene extends UIScene { private static PlayerStatisticScene object; - public static PlayerStatisticScene instance() { + public static PlayerStatisticScene instance(Scene lastGameScene) { if (object == null) object = new PlayerStatisticScene(); + if (lastGameScene != null) + object.lastGameScene=lastGameScene; return object; } - @Override public void dispose() { if (achievements != null) { @@ -243,4 +244,16 @@ public class PlayerStatisticScene extends UIScene { achievementContainer.row(); } } + + public boolean quests() { + Forge.switchScene(QuestLogScene.instance(lastGameScene),true); + return true; + } + + @Override + public boolean back(){ + Forge.switchScene(lastGameScene==null?GameScene.instance():lastGameScene); + return true; + } + } diff --git a/forge-gui-mobile/src/forge/adventure/scene/QuestLogScene.java b/forge-gui-mobile/src/forge/adventure/scene/QuestLogScene.java new file mode 100644 index 00000000000..7f64aee444f --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/scene/QuestLogScene.java @@ -0,0 +1,240 @@ +package forge.adventure.scene; + +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.*; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Align; +import com.github.tommyettinger.textra.TextraButton; +import com.github.tommyettinger.textra.TypingLabel; +import forge.Forge; +import forge.adventure.data.AdventureQuestData; +import forge.adventure.data.AdventureQuestStage; +import forge.adventure.stage.MapStage; +import forge.adventure.util.AdventureQuestController; +import forge.adventure.util.Controls; +import forge.adventure.util.Current; + +public class QuestLogScene extends UIScene { + private Table scrollContainer, detailScrollContainer; + Window scrollWindow; + ScrollPane scroller,detailScroller; + Table root, detailRoot; + TextraButton trackButton, backToListButton, abandonQuestButton; + Scene lastGameScene; + + private QuestLogScene() { + super(Forge.isLandscapeMode() ? "ui/quests.json" : "ui/quests_portrait.json"); + + + scrollWindow = ui.findActor("scrollWindow"); + root = ui.findActor("questList"); + detailRoot = ui.findActor("questDetails"); + abandonQuestButton = ui.findActor("abandonQuest"); + trackButton = ui.findActor("trackQuest"); + backToListButton = ui.findActor("backToList");//Controls.newTextButton("Quest List"); + ui.onButtonPress("return", QuestLogScene.this::back); + ui.onButtonPress("status", QuestLogScene.this::status); + ui.onButtonPress("backToList", QuestLogScene.this::backToList); + + + //Todo - refactor below, replace buttons in landscape + + scrollContainer = new Table(Controls.getSkin()); + scrollContainer.row(); + + detailScrollContainer = new Table(Controls.getSkin()); + detailScrollContainer.row(); + + detailScroller = new ScrollPane(detailScrollContainer); + detailRoot.row(); + detailRoot.add(detailScroller).expandX().fillX(); + detailRoot.row(); + scrollWindow.setTouchable(Touchable.disabled); + detailRoot.setVisible(false); + scroller = new ScrollPane(scrollContainer); + root.add(scroller).colspan(3); + root.align(Align.right); + root.row(); + Label column0Label = new Label("Quest Name", Controls.getSkin()); + column0Label.setColor(Color.BLACK); + root.add(column0Label).align(Align.bottomLeft); + root.row(); + ScrollPane scroller = new ScrollPane(scrollContainer); + root.add(scroller).colspan(3).fill().expand(); + + + + } + + private static QuestLogScene object; + + public static QuestLogScene instance(Scene lastGameScene) { + //if (object == null) + object = new QuestLogScene(); + if (lastGameScene != null) + object.lastGameScene=lastGameScene; + return object; + } + + @Override + public void dispose() { + + } + + @Override + public void enter() { + super.enter(); + buildList(); + + + } + + public void buildList(){ + backToList(); + + scrollContainer.clear(); + + for (AdventureQuestData quest : Current.player().getQuests()) { + TypingLabel nameLabel = Controls.newTypingLabel(quest.getName()); + nameLabel.skipToTheEnd(); + nameLabel.setWrap(true); + nameLabel.setColor(Color.BLACK); + scrollContainer.add(nameLabel).align(Align.left).expandX(); + Button details = Controls.newTextButton("Details"); + details.addListener( new ClickListener(){ + public void clicked(InputEvent event, float x, float y){ + loadDetailsPane(quest); + } + }); + scrollContainer.add(details).align(Align.center).padRight(10); + scrollContainer.row().padTop(5); + addToSelectable(details); + } + performTouch(scrollPaneOfActor(scrollContainer)); //can use mouse wheel if available to scroll + } + + private void backToList(){ + abandonQuestButton.setVisible(false); + trackButton.setVisible(false); + backToListButton.setVisible(false); + + root.setVisible(true); + detailRoot.setVisible(false); + } + + private void loadDetailsPane(AdventureQuestData quest){ + if (quest == null){ + return; + } + root.setVisible(false); + detailRoot.setVisible(true); + detailScrollContainer.row(); + trackButton.setText(quest.isTracked?"Untrack Quest":"Track Quest"); + trackButton.addListener( new ClickListener(){ + public void clicked(InputEvent event, float x, float y){ + toggleTracked(quest); + } + }); + + abandonQuestButton.setColor(Color.RED); + abandonQuestButton.addListener( new ClickListener(){ + public void clicked(InputEvent event, float x, float y){ + + Dialog confirm = createGenericDialog("", "Abandon Quest?","Yes","No", () -> abandonQuest(quest), null); + showDialog(confirm); + } + }); + + TypingLabel dNameLabel = Controls.newTypingLabel(quest.getName()); + dNameLabel.skipToTheEnd(); + dNameLabel.setWrap(true); + dNameLabel.setColor(Color.BLACK); + + detailScrollContainer.add(dNameLabel).align(Align.left).expandX().padLeft(10); + + abandonQuestButton.setVisible(!quest.storyQuest); + trackButton.setVisible(true); + backToListButton.setVisible(true); + + TypingLabel dDescriptionLabel = Controls.newTypingLabel(quest.getDescription()); + dDescriptionLabel.skipToTheEnd(); + dDescriptionLabel.setWrap(true); + dDescriptionLabel.setColor(Color.DARK_GRAY); + + detailScrollContainer.row(); + detailScrollContainer.add(dDescriptionLabel).align(Align.left).padLeft(25); + + for (AdventureQuestStage stage : quest.getStagesForQuestLog()){ + // Todo: Eventually needs to be multiple loops or sort stages by status + // because parallel objectives will make this messy + switch (stage.getStatus()){ + case Complete: + TypingLabel completeLabel = Controls.newTypingLabel("* " + stage.name); + completeLabel.skipToTheEnd(); + completeLabel.setColor(Color.GREEN); + completeLabel.setWrap(true); + detailScrollContainer.row(); + detailScrollContainer.add(completeLabel).align(Align.left).padLeft(25); + break; + case Failed: + TypingLabel failedLabel = Controls.newTypingLabel("* " + stage.name); + failedLabel.skipToTheEnd(); + failedLabel.setColor(Color.RED); + failedLabel.setText(stage.name); + failedLabel.setWrap(true); + detailScrollContainer.row(); + detailScrollContainer.add(failedLabel).align(Align.left).padLeft(25); + break; + case Active: + TypingLabel activeLabel = Controls.newTypingLabel("* " + stage.name); + activeLabel.skipToTheEnd(); + activeLabel.setColor(Color.BLACK); + activeLabel.setWrap(true); + detailScrollContainer.row(); + detailScrollContainer.add(activeLabel).align(Align.left).padLeft(25); + + TypingLabel activeDescriptionLabel = Controls.newTypingLabel(stage.description); + activeDescriptionLabel.skipToTheEnd(); + activeDescriptionLabel.setColor(Color.DARK_GRAY); + activeDescriptionLabel.setWrap(true); + + detailScrollContainer.row(); + detailScrollContainer.add(activeDescriptionLabel).padLeft(35).width(scrollWindow.getWidth() - 50).colspan(4); + detailScrollContainer.row(); + break; + } + } + } + + private void toggleTracked(AdventureQuestData quest){ + quest.isTracked = !quest.isTracked; + if (quest.isTracked){ + for (AdventureQuestData q: Current.player().getQuests()){ + if (q.equals(quest)) + continue; + q.isTracked = false; + } + } + trackButton.setText(quest.isTracked?"Untrack Quest":"Track Quest"); + } + + private void status() { + Forge.switchScene(PlayerStatisticScene.instance(lastGameScene),true); + } + + @Override + public boolean back(){ + //Needed so long as quest log and stats are separate scenes that link to each other + Forge.switchScene(lastGameScene==null?GameScene.instance():lastGameScene); + return true; + } + + private void abandonQuest(AdventureQuestData quest) { + AdventureQuestController.instance().abandon(quest); + AdventureQuestController.instance().showQuestDialogs(MapStage.getInstance()); + buildList(); + } + +} diff --git a/forge-gui-mobile/src/forge/adventure/scene/RewardScene.java b/forge-gui-mobile/src/forge/adventure/scene/RewardScene.java index acc6e20e867..b6b19bcb1c1 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/RewardScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/RewardScene.java @@ -44,7 +44,8 @@ public class RewardScene extends UIScene { private boolean showTooltips = false; public enum Type { Shop, - Loot + Loot, + QuestReward } Type type; @@ -146,15 +147,16 @@ public class RewardScene extends UIScene { showLootOrDone(); return true; } - if (type != null) { - switch (type) { - case Shop: - doneButton.setText(Forge.getLocalizer().getMessage("lblLeave")); - break; - case Loot: - doneButton.setText(Forge.getLocalizer().getMessage("lblDone")); - break; - } + if (type != null) { + switch (type) { + case Shop: + doneButton.setText(Forge.getLocalizer().getMessage("lblLeave")); + break; + case QuestReward: + case Loot: + doneButton.setText(Forge.getLocalizer().getMessage("lblDone")); + break; + } } shown = false; clearGenerated(); @@ -169,6 +171,8 @@ public class RewardScene extends UIScene { RewardActor reward = (RewardActor) actor; if (type == Type.Loot) AdventurePlayer.current().addReward(reward.getReward()); + if (type == Type.QuestReward) + AdventurePlayer.current().addReward(reward.getReward()); // Want to customize this soon to have selectable rewards which will be handled different here reward.clearHoldToolTip(); try { stage.getActors().removeValue(reward, true); @@ -181,7 +185,7 @@ public class RewardScene extends UIScene { stage.act(delta); ImageCache.allowSingleLoad(); if (doneClicked) { - if (type == Type.Loot) { + if (type == Type.Loot || type == Type.QuestReward) { flipCountDown -= Gdx.graphics.getDeltaTime(); exitCountDown += Gdx.graphics.getDeltaTime(); } @@ -212,7 +216,7 @@ public class RewardScene extends UIScene { } if (exit) done(true); - else if (type == Type.Loot && !shown) { + else if ((type == Type.Loot || type == Type.QuestReward) && !shown) { shown = true; float delay = 0.09f; generated.shuffle(); @@ -268,7 +272,7 @@ public class RewardScene extends UIScene { long shopSeed = changes.getShopSeed(shopActor.getObjectId()); WorldSave.getCurrentSave().getWorld().getRandom().setSeed(shopSeed); for (RewardData rdata : new Array.ArrayIterator<>(data.rewards)) { - ret.addAll(rdata.generate(false)); + ret.addAll(rdata.generate(false, false)); } shopActor.setRewardData(ret); loadRewards(ret, RewardScene.Type.Shop,shopActor); @@ -343,6 +347,7 @@ public class RewardScene extends UIScene { restockButton.setDisabled(true); } break; + case QuestReward: case Loot: shopNameLabel.setVisible(false); shopNameLabel.setText(""); @@ -424,7 +429,9 @@ public class RewardScene extends UIScene { if (lastRowCount != 0) lastRowXAdjust = ((numberOfColumns * cardWidth) - (lastRowCount * cardWidth)) / 2; } - RewardActor actor = new RewardActor(reward, type == Type.Loot, type); + + RewardActor actor = new RewardActor(reward, type == Type.Loot || type == Type.QuestReward,type); + actor.setBounds(lastRowXAdjust + xOff + cardWidth * (i % numberOfColumns) + spacing, yOff + cardHeight * currentRow + spacing, cardWidth - spacing * 2, cardHeight - spacing * 2); if (type == Type.Shop) { diff --git a/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java b/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java index ef311f20798..bbc611489da 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java @@ -8,10 +8,7 @@ import forge.Forge; import forge.adventure.pointofintrest.PointOfInterest; import forge.adventure.stage.MapStage; import forge.adventure.stage.PointOfInterestMapRenderer; -import forge.adventure.util.Config; -import forge.adventure.util.Current; -import forge.adventure.util.Paths; -import forge.adventure.util.TemplateTmxMapLoader; +import forge.adventure.util.*; import forge.adventure.world.WorldSave; import forge.sound.SoundEffectType; import forge.sound.SoundSystem; @@ -97,9 +94,14 @@ public class TileMapScene extends HudScene { if (Current.player().fullHeal()) autoheal = true; // to play sound/effect on act } + AdventureQuestController.instance().updateEnteredPOI(rootPoint); + AdventureQuestController.instance().showQuestDialogs(stage); + + } public void load(PointOfInterest point) { + AdventureQuestController.instance().mostRecentPOI = point; rootPoint = point; oldMap = point.getData().map; map = new TemplateTmxMapLoader().load(Config.instance().getFilePath(point.getData().map)); @@ -115,7 +117,7 @@ public class TileMapScene extends HudScene { return AUTO_HEAL_LOCATIONS.contains(rootPoint.getData().type); } - PointOfInterest rootPoint; + public PointOfInterest rootPoint; String oldMap; private void load(String targetMap) { diff --git a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java index 2801e5416a0..74fde1b0f3b 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java +++ b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java @@ -157,6 +157,18 @@ public class ConsoleCommandInterpreter { Current.player().giveGold(amount); return "Added " + amount + " gold"; }); + registerCommand(new String[]{"give", "quest"}, s -> { + if (s.length<1) return "Command needs 1 parameter: QuestID"; + int ID; + try{ + ID =Integer.parseInt(s[0]); + } + catch (Exception e){ + return "Can not convert " +s[0]+" to number"; + } + Current.player().addQuest(ID); + return "Quest generated"; + }); registerCommand(new String[]{"give", "shards"}, s -> { if (s.length < 1) return "Command needs 1 parameter: Amount."; int amount; @@ -308,19 +320,7 @@ public class ConsoleCommandInterpreter { Current.player().addShards(value); return "Player now has " + Current.player().getShards() + " shards"; }); -// registerCommand(new String[]{"getMana", "percent"}, s -> { -// if(s.length<1) return "Command needs 1 parameter: Amount"; -// float value = 0; -// try { value = Float.parseFloat(s[0]); } -// catch (Exception e) { return "Can not convert " + s[0] + " to integer"; } -// Current.player().addManaPercent(value); -// return "Player healed to " + Current.player().getLife() + "/" + Current.player().getMaxLife(); -// }); -// registerCommand(new String[]{"getMana", "full"}, s -> { -// Current.player().addManaPercent(1.0f); -// return "Player healed to " + Current.player().getLife() + "/" + Current.player().getMaxLife(); -// }); - registerCommand(new String[]{"debug", "map"}, s -> { + registerCommand(new String[]{"debug","map"}, s -> { GameHUD.getInstance().setDebug(true); return "Debug map ON"; }); diff --git a/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java b/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java index 53f6355c93c..912eeb9cb0a 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java +++ b/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java @@ -46,7 +46,7 @@ public class GameHUD extends Stage { private final TextraLabel lifePoints, money, shards, keys; private final Image miniMap, gamehud, mapborder, avatarborder, blank; private final InputEvent eventTouchDown, eventTouchUp; - private final TextraButton deckActor, openMapActor, menuActor, statsActor, inventoryActor, exitToWorldMapActor; + private final TextraButton deckActor, openMapActor, menuActor, logbookActor, inventoryActor, exitToWorldMapActor; public final UIActor ui; private final Touchpad touchpad; private final Console console; @@ -83,7 +83,7 @@ public class GameHUD extends Stage { ui.onButtonPress("openmap", () -> GameHUD.this.openMap()); menuActor = ui.findActor("menu"); referenceX = menuActor.getX(); - statsActor = ui.findActor("statistic"); + logbookActor = ui.findActor("logbook"); inventoryActor = ui.findActor("inventory"); gamehud = ui.findActor("gamehud"); exitToWorldMapActor = ui.findActor("exittoworldmap"); @@ -115,7 +115,7 @@ public class GameHUD extends Stage { avatar = ui.findActor("avatar"); ui.onButtonPress("menu", () -> menu()); ui.onButtonPress("inventory", () -> openInventory()); - ui.onButtonPress("statistic", () -> statistic()); + ui.onButtonPress("logbook", () -> logbook()); ui.onButtonPress("deck", () -> openDeck()); ui.onButtonPress("exittoworldmap", () -> exitToWorldMap()); lifePoints = ui.findActor("lifePoints"); @@ -157,8 +157,8 @@ public class GameHUD extends Stage { Forge.switchScene(MapViewScene.instance()); } - private void statistic() { - Forge.switchScene(PlayerStatisticScene.instance()); + private void logbook() { + Forge.switchScene(QuestLogScene.instance(Forge.getCurrentScene())); } public static GameHUD getInstance() { @@ -222,7 +222,7 @@ public class GameHUD extends Stage { && !(Controls.actorContainsVector(menuActor, touch)) //not inside menu button && !(Controls.actorContainsVector(deckActor, touch)) //not inside deck button && !(Controls.actorContainsVector(openMapActor, touch)) //not inside openmap button - && !(Controls.actorContainsVector(statsActor, touch)) //not inside stats button + && !(Controls.actorContainsVector(logbookActor, touch)) //not inside stats button && !(Controls.actorContainsVector(inventoryActor, touch)) //not inside inventory button && !(Controls.actorContainsVector(exitToWorldMapActor, touch)) //not inside exit button && (Controls.actorContainsVector(ui, touch)) //inside display bounds @@ -503,7 +503,7 @@ public class GameHUD extends Stage { setAlpha(avatar, visible); setAlpha(deckActor, visible); setAlpha(menuActor, visible); - setAlpha(statsActor, visible); + setAlpha(logbookActor, visible); setAlpha(inventoryActor, visible); setAlpha(exitToWorldMapActor, visible); opacity = visible ? 1f : 0.4f; @@ -579,7 +579,7 @@ public class GameHUD extends Stage { isHiding = true; deckActor.addAction(Actions.sequence(Actions.fadeOut(0.10f), Actions.hide(), Actions.moveTo(deckActor.getX() + deckActor.getWidth(), deckActor.getY()))); inventoryActor.addAction(Actions.sequence(Actions.fadeOut(0.15f), Actions.hide(), Actions.moveTo(inventoryActor.getX() + inventoryActor.getWidth(), inventoryActor.getY()))); - statsActor.addAction(Actions.sequence(Actions.fadeOut(0.20f), Actions.hide(), Actions.moveTo(statsActor.getX() + statsActor.getWidth(), statsActor.getY()))); + logbookActor.addAction(Actions.sequence(Actions.fadeOut(0.20f), Actions.hide(), Actions.moveTo(logbookActor.getX() + logbookActor.getWidth(), logbookActor.getY()))); menuActor.addAction(Actions.sequence(Actions.fadeOut(0.25f), Actions.hide(), Actions.moveTo(menuActor.getX() + menuActor.getWidth(), menuActor.getY()))); if (GameScene.instance().isNotInWorldMap()) exitToWorldMapActor.addAction(Actions.sequence(Actions.fadeOut(0.2f), Actions.hide(), Actions.moveTo(exitToWorldMapActor.getX() + exitToWorldMapActor.getWidth(), exitToWorldMapActor.getY()))); @@ -595,7 +595,7 @@ public class GameHUD extends Stage { return; isShowing = true; menuActor.addAction(Actions.sequence(Actions.delay(0.1f), Actions.parallel(Actions.show(), Actions.alpha(opacity, 0.1f), Actions.moveTo(referenceX, menuActor.getY(), 0.25f)))); - statsActor.addAction(Actions.sequence(Actions.delay(0.15f), Actions.parallel(Actions.show(), Actions.alpha(opacity, 0.1f), Actions.moveTo(referenceX, statsActor.getY(), 0.25f)))); + logbookActor.addAction(Actions.sequence(Actions.delay(0.15f), Actions.parallel(Actions.show(), Actions.alpha(opacity, 0.1f), Actions.moveTo(referenceX, logbookActor.getY(), 0.25f)))); inventoryActor.addAction(Actions.sequence(Actions.delay(0.2f), Actions.parallel(Actions.show(), Actions.alpha(opacity, 0.1f), Actions.moveTo(referenceX, inventoryActor.getY(), 0.25f)))); deckActor.addAction(Actions.sequence(Actions.delay(0.25f), Actions.parallel(Actions.show(), Actions.alpha(opacity, 0.1f), Actions.moveTo(referenceX, deckActor.getY(), 0.25f)))); if (GameScene.instance().isNotInWorldMap()) diff --git a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java index 8cfe904a0d6..558970e4b3a 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java @@ -126,8 +126,9 @@ public class MapStage extends GameStage { public PointOfInterestChanges getChanges() { return changes; } + private boolean matchJustEnded = false; - private MapStage() { + protected MapStage() { dialog = Controls.newDialog(""); eventTouchDown = new InputEvent(); eventTouchDown.setPointer(-1); @@ -550,7 +551,7 @@ public class MapStage extends GameStage { System.err.printf("Enemy \"%s\" not found.", enemy); break; } - EnemySprite mob = new EnemySprite(id, EN); + EnemySprite mob = new EnemySprite(id, EN, changes); Object dialogObject = prop.get("dialog"); //Check if the enemy has a dialogue attached to it. if (dialogObject != null && !dialogObject.toString().isEmpty()) { mob.dialog = new MapDialog(dialogObject.toString(), this, mob.getId()); @@ -644,28 +645,17 @@ public class MapStage extends GameStage { } break; case "quest": - DialogActor dialog; + if (prop.containsKey("questtype")) { TiledMapTileMapObject tiledObj = (TiledMapTileMapObject) obj; - String questOrigin = prop.containsKey("questtype") ? prop.get("questtype").toString() : ""; + AdventureQuestData questInfo = AdventureQuestController.instance().getQuestNPCResponse(TileMapScene.instance().rootPoint.getID(), changes,questOrigin); - String placeholderText = "[" + - " {" + - " \"name\":\"Quest Offer\"," + - " \"text\":\"Please, help us!\\n((QUEST DESCRIPTION))\"," + - " \"condition\":[]," + - " \"options\":[" + - " { \"name\":\"No, I'm not ready yet.\nMaybe next snapshot.\" }," + - " ]" + - " }" + - "]"; - - { - dialog = new DialogActor(this, id, placeholderText, tiledObj.getTextureRegion()); + if (questInfo != null) { + DialogActor questActor = new DialogActor(questInfo, this, id); + questActor.setVisible(false); + addMapActor(obj, questActor); } - dialog.setVisible(false); - addMapActor(obj, dialog); } break; @@ -757,7 +747,7 @@ public class MapStage extends GameStage { Array ret = new Array<>(); WorldSave.getCurrentSave().getWorld().getRandom().setSeed(changes.getShopSeed(id)); for (RewardData rdata : new Array.ArrayIterator<>(data.rewards)) { - ret.addAll(rdata.generate(false)); + ret.addAll(rdata.generate(false, false)); } ShopActor actor = new ShopActor(this, id, ret, data); addMapActor(obj, actor); @@ -787,6 +777,8 @@ public class MapStage extends GameStage { } public boolean exitDungeon() { + AdventureQuestController.instance().updateQuestsLeave(); + AdventureQuestController.instance().showQuestDialogs(this); isLoadingMatch = false; effect = null; //Reset dungeon effects. clearIsInMap(); @@ -809,6 +801,9 @@ public class MapStage extends GameStage { currentMob.setAnimation(CharacterSprite.AnimationTypes.Death); currentMob.resetCollisionHeight(); startPause(0.3f, MapStage.this::getReward); + AdventureQuestController.instance().updateQuestsWin(currentMob,enemies); + AdventureQuestController.instance().showQuestDialogs(MapStage.this); + player.setAnimation(CharacterSprite.AnimationTypes.Idle); } }, 1f); } else { @@ -821,6 +816,8 @@ public class MapStage extends GameStage { currentMob.resetCollisionHeight(); player.setPosition(oldPosition4); currentMob.freezeMovement(); + AdventureQuestController.instance().updateQuestsLose(currentMob); + AdventureQuestController.instance().showQuestDialogs(MapStage.this); boolean defeated = Current.player().defeated(); if (canFailDungeon && defeated) { //If hardcore mode is added, check and redirect to game over screen here @@ -919,41 +916,37 @@ public class MapStage extends GameStage { @Override protected void onActing(float delta) { + if (isPaused() || isDialogOnlyInput()) + return; Iterator it = enemies.iterator(); - while (it.hasNext()) { - EnemySprite mob = it.next(); - mob.updatePositon(); - mob.targetVector = mob.getTargetVector(player, delta); - Vector2 currentVector = new Vector2(mob.targetVector); - mob.clearActions(); - if (mob.targetVector.len() == 0.0f) { - mob.setAnimation(CharacterSprite.AnimationTypes.Idle); - continue; - } - if (!mob.getData().flying)//if direct path is not possible - { - //Todo: fix below for collision logic - float safeLen = lengthWithoutCollision(mob, mob.targetVector); - if (safeLen > 0.1f) { - currentVector.setLength(Math.min(safeLen, mob.targetVector.len())); - } else { - currentVector = Vector2.Zero; + if (!matchJustEnded) { + while (it.hasNext()) { + EnemySprite mob = it.next(); + mob.updatePositon(); + mob.targetVector = mob.getTargetVector(player, delta); + Vector2 currentVector = new Vector2(mob.targetVector); + mob.clearActions(); + if (mob.targetVector.len() == 0.0f) { + mob.setAnimation(CharacterSprite.AnimationTypes.Idle); + continue; } + + if (!mob.getData().flying)//if direct path is not possible + { + //Todo: fix below for collision logic + float safeLen = lengthWithoutCollision(mob, mob.targetVector); + if (safeLen > 0.1f) { + currentVector.setLength(Math.min(safeLen, mob.targetVector.len())); + } else { + currentVector = Vector2.Zero; + } + } + currentVector.setLength(Math.min(mob.speed() * delta, mob.targetVector.len())); + mob.moveBy(currentVector.x, currentVector.y); } - - - //mob.targetVector.setLength(Math.min(mob.speed() * delta, mob.targetVector.len())); -// if (mob.targetVector.len() < 0.3f) { -// mob.targetVector = Vector2.Zero; -// } - currentVector.setLength(Math.min(mob.speed() * delta, mob.targetVector.len())); - //if (destination.len() < 0.3f) destination = Vector2.Zero; - mob.moveBy(currentVector.x, currentVector.y); - } - float sprintingMod = currentModifications.containsKey(PlayerModification.Sprint) ? 2 : 1; player.setMoveModifier(2 * sprintingMod); oldPosition4.set(oldPosition3); @@ -965,6 +958,7 @@ public class MapStage extends GameStage { if (actor instanceof EnemySprite) { EnemySprite mob = (EnemySprite) actor; currentMob = mob; + currentMob.clearCollisionHeight(); resetPosition(); if (mob.dialog != null && mob.dialog.canShow()) { //This enemy has something to say. Display a dialog like if it was a DialogActor but only if dialogue is possible. mob.dialog.activate(); @@ -1032,7 +1026,9 @@ public class MapStage extends GameStage { } public void showDialog() { - + if (dialogStage == null){ + setDialogStage(GameHUD.getInstance()); + } dialogButtonMap.clear(); for (int i = 0; i < dialog.getButtonTable().getCells().size; i++) { dialogButtonMap.add((TextraButton) dialog.getButtonTable().getCells().get(i).getActor()); @@ -1048,6 +1044,7 @@ public class MapStage extends GameStage { dialog.hide(Actions.sequence(Actions.sizeTo(dialog.getOriginX(), dialog.getOriginY(), 0.3f), Actions.hide())); dialogOnlyInput = false; selectedKey = null; + dialog.clearListeners(); } public void setDialogStage(Stage dialogStage) { @@ -1061,6 +1058,9 @@ public class MapStage extends GameStage { public void setQuestFlag(String key, int value) { changes.getMapFlags().put(key, (byte) value); + + AdventureQuestController.instance().updateQuestsMapFlag(key,value); + AdventureQuestController.instance().showQuestDialogs(this); } public void advanceQuestFlag(String key) { @@ -1070,6 +1070,9 @@ public class MapStage extends GameStage { } else { C.put(key, (byte) 1); } + + AdventureQuestController.instance().updateQuestsMapFlag(key,changes.getMapFlags().get(key)); + AdventureQuestController.instance().showQuestDialogs(this); } public boolean checkQuestFlag(String key) { diff --git a/forge-gui-mobile/src/forge/adventure/stage/PointOfInterestMapSprite.java b/forge-gui-mobile/src/forge/adventure/stage/PointOfInterestMapSprite.java index 145833dfd49..bd41d25f8aa 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/PointOfInterestMapSprite.java +++ b/forge-gui-mobile/src/forge/adventure/stage/PointOfInterestMapSprite.java @@ -48,7 +48,8 @@ public class PointOfInterestMapSprite extends MapSprite { @Override public void draw(Batch batch, float parentAlpha) { - super.draw(batch, parentAlpha); + if (pointOfInterest.getActive()) + super.draw(batch, parentAlpha); //batch.draw(getDebugTexture(),getX(),getY()); } } diff --git a/forge-gui-mobile/src/forge/adventure/stage/WorldBackground.java b/forge-gui-mobile/src/forge/adventure/stage/WorldBackground.java index f63e426ff53..9bd0dfd4fda 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/WorldBackground.java +++ b/forge-gui-mobile/src/forge/adventure/stage/WorldBackground.java @@ -91,7 +91,7 @@ public class WorldBackground extends Actor { if (chunksSpritesBackground[x][y] == null) chunksSpritesBackground[x][y] = MapSprite.getMapSprites(x, y, MapSprite.BackgroundLayer); for (Actor sprite : chunksSpritesBackground[x][y]) { - stage.getBackgroundSprites().addActor(sprite); + stage.getBackgroundSprites().addActor(sprite); } } diff --git a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java index af1b09a9a61..cff62cbc529 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java @@ -11,6 +11,7 @@ import com.badlogic.gdx.utils.viewport.Viewport; import forge.Forge; import forge.adventure.character.CharacterSprite; import forge.adventure.character.EnemySprite; +import forge.adventure.data.AdventureQuestData; import forge.adventure.data.BiomeData; import forge.adventure.data.EnemyData; import forge.adventure.data.WorldData; @@ -18,10 +19,7 @@ import forge.adventure.scene.DuelScene; import forge.adventure.scene.RewardScene; import forge.adventure.scene.Scene; import forge.adventure.scene.TileMapScene; -import forge.adventure.util.Current; -import forge.adventure.util.Paths; -import forge.adventure.util.SaveFileContent; -import forge.adventure.util.SaveFileData; +import forge.adventure.util.*; import forge.adventure.world.World; import forge.adventure.world.WorldSave; import forge.gui.FThreads; @@ -52,11 +50,15 @@ public class WorldStage extends GameStage implements SaveFileContent { private final static Float dieTimer = 20f;//todo config private Float globalTimer = 0f; + NavArrowActor navArrow; public WorldStage() { super(); background = new WorldBackground(this); addActor(background); background.setZIndex(0); + navArrow = new NavArrowActor(); + addActor(navArrow); + navArrow.setZIndex(1); } public static WorldStage getInstance() { @@ -68,6 +70,9 @@ public class WorldStage extends GameStage implements SaveFileContent { @Override protected void onActing(float delta) { + if (isPaused() || MapStage.getInstance().isDialogOnlyInput()) + return; + drawNavigationArrow(); if (player.isMoving()) { handleMonsterSpawn(delta); handlePointsOfInterestCollision(); @@ -75,8 +80,9 @@ public class WorldStage extends GameStage implements SaveFileContent { Iterator> it = enemies.iterator(); while (it.hasNext()) { Pair pair = it.next(); - if (globalTimer >= pair.getKey() + dieTimer) { - + if (globalTimer >= pair.getKey() + pair.getValue().getLifetime()) { + AdventureQuestController.instance().updateDespawn(pair.getValue()); + AdventureQuestController.instance().showQuestDialogs(MapStage.getInstance()); foregroundSprites.removeActor(pair.getValue()); it.remove(); continue; @@ -166,8 +172,10 @@ public class WorldStage extends GameStage implements SaveFileContent { startPause(0.3f, () -> { RewardScene.instance().loadRewards(currentMob.getRewards(), RewardScene.Type.Loot, null); WorldStage.this.removeEnemy(currentMob); - currentMob = null; + AdventureQuestController.instance().updateQuestsWin(currentMob); + AdventureQuestController.instance().showQuestDialogs(MapStage.getInstance()); Forge.switchScene(RewardScene.instance()); + currentMob = null; }); } }, 1f); @@ -178,6 +186,8 @@ public class WorldStage extends GameStage implements SaveFileContent { startPause(0.5f, () -> { currentMob.resetCollisionHeight(); Current.player().defeated(); + AdventureQuestController.instance().updateQuestsLose(currentMob); + AdventureQuestController.instance().showQuestDialogs(MapStage.getInstance()); WorldStage.this.removeEnemy(currentMob); currentMob = null; }); @@ -188,6 +198,10 @@ public class WorldStage extends GameStage implements SaveFileContent { for (Actor actor : foregroundSprites.getChildren()) { if (actor.getClass() == PointOfInterestMapSprite.class) { PointOfInterestMapSprite point = (PointOfInterestMapSprite) actor; + if (!point.getPointOfInterest().getActive()) + { + continue; + } if (player.collideWith(point.getBoundingRect())) { if (point == collidingPoint) { continue; @@ -222,6 +236,16 @@ public class WorldStage extends GameStage implements SaveFileContent { } private void handleMonsterSpawn(float delta) { + for (Actor sprite : foregroundSprites.getChildren()) { + if (AdventureQuestController.instance().getQuestSprites().remove(sprite)) { + } + } + for (EnemySprite questSprite : AdventureQuestController.instance().getQuestSprites()) { + if (!foregroundSprites.getChildren().contains(questSprite, true)) { + spawnQuestSprite(questSprite,2.5f); + } + } + World world = WorldSave.getCurrentSave().getWorld(); int currentBiome = World.highestBiome(world.getBiome((int) player.getX() / world.getTileSize(), (int) player.getY() / world.getTileSize())); List biomeData = WorldSave.getCurrentSave().getWorld().getData().GetBiomes(); @@ -245,10 +269,9 @@ public class WorldStage extends GameStage implements SaveFileContent { spawn(enemyData); } - private boolean spawn(EnemyData enemyData) { - if (enemyData == null) + private boolean spawn(EnemySprite sprite){ + if (sprite == null) return false; - EnemySprite sprite = new EnemySprite(enemyData); float unit = Scene.getIntendedHeight() / 6f; Vector2 spawnPos = new Vector2(1, 1); for (int j = 0; j < 10; j++) { @@ -264,7 +287,37 @@ public class WorldStage extends GameStage implements SaveFileContent { foregroundSprites.addActor(sprite); return true; } - int g = 0; + } + } + return false; + } + + private boolean spawn(EnemyData enemyData) { + if (enemyData == null) + return false; + EnemySprite sprite = new EnemySprite(enemyData); + return spawn(sprite); + + } + + private boolean spawnQuestSprite(EnemySprite sprite, float distanceMultiplier){ + if (sprite == null) + return false; + float unit = Scene.getIntendedHeight() / 6f; + Vector2 spawnPos = new Vector2(1, 1); + for (int j = 0; j < 10; j++) { + spawnPos.setLength((unit + (unit * 3) * rand.nextFloat()) * distanceMultiplier); + spawnPos.setAngleDeg(360 * rand.nextFloat()); + for (int i = 0; i < 10; i++) { + boolean enemyXIsBigger = sprite.getX() > player.getX(); + boolean enemyYIsBigger = sprite.getY() > player.getY(); + sprite.setX(player.getX() + spawnPos.x + (i * sprite.getWidth() * (enemyXIsBigger ? 1 : -1)));//maybe find a better way to get spawn points + sprite.setY(player.getY() + spawnPos.y + (i * sprite.getHeight() * (enemyYIsBigger ? 1 : -1))); + if (sprite.getData().flying || !WorldSave.getCurrentSave().getWorld().collidingTile(sprite.boundingRect())) { + enemies.add(Pair.of(globalTimer, sprite)); + foregroundSprites.addActor(sprite); + return true; + } } } return false; @@ -382,4 +435,38 @@ public class WorldStage extends GameStage implements SaveFileContent { player.playEffect(Paths.TRIGGER_KILL); } } + + private void drawNavigationArrow(){ + Vector2 navDirection = null; + for (AdventureQuestData adq: Current.player().getQuests()) + { + if (adq.isTracked) { + if (adq.getTargetPOI() != null) { + navDirection = adq.getTargetPOI().getNavigationVector(player.pos()); + + } else if (adq.getTargetEnemySprite() != null) { + EnemySprite target = adq.getTargetEnemySprite(); + for (Pair active :enemies) + { + EnemySprite sprite = active.getValue(); + if (sprite.equals(target)){ + navDirection = new Vector2(adq.getTargetEnemySprite().pos()).sub(player.getX(), player.getY()); + break; + } + } + } + break; + } + } + if (navDirection != null) + { + navArrow.navTargetAngle = navDirection.angleDeg(); + navArrow.setVisible(true); + navArrow.setPosition(getPlayerSprite().getX() + (getPlayerSprite().getWidth()/2), getPlayerSprite().getY() + (getPlayerSprite().getHeight()/2)); + } + else + { + navArrow.setVisible(false); + } + } } diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureQuestController.java b/forge-gui-mobile/src/forge/adventure/util/AdventureQuestController.java new file mode 100644 index 00000000000..7d127dece2a --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureQuestController.java @@ -0,0 +1,347 @@ +package forge.adventure.util; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Json; +import forge.adventure.character.EnemySprite; +import forge.adventure.data.*; +import forge.adventure.pointofintrest.PointOfInterest; +import forge.adventure.pointofintrest.PointOfInterestChanges; +import forge.adventure.stage.GameStage; +import forge.adventure.stage.MapStage; +import forge.util.Aggregates; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.*; +import java.util.List; + +public class AdventureQuestController implements Serializable { + + public enum ObjectiveTypes{ + None, + Arena, + Clear, + Defeat, + Delivery, + Escort, + Fetch, + Find, + Gather, + Give, + Hunt, + MapFlag, + Leave, + Patrol, + QuestFlag, + Rescue, + Siege, + Travel, + Use + } + + public enum QuestStatus{ + None, + Inactive, + Active, + Complete, + Failed + } + private Map nextQuestDate = new HashMap<>(); + private int maximumSideQuests = 5; //todo: move to configuration file + private transient boolean inDialog = false; + private transient Array allQuests = new Array<>(); + private transient Array allSideQuests = new Array<>(); + private Queue dialogQueue = new LinkedList<>(); + private Map questAvailability = new HashMap<>(); + public PointOfInterest mostRecentPOI; + private List enemySpriteList= new ArrayList<>(); + public void showQuestDialogs(GameStage stage) { + List finishedQuests = new ArrayList<>(); + + if (stage instanceof MapStage){ + for (AdventureQuestData quest : Current.player().getQuests()) { + DialogData prologue = quest.getPrologue(); + if (prologue != null && (!prologue.text.isEmpty()) ){ + dialogQueue.add(prologue); + } + for (AdventureQuestStage questStage : quest.stages) + { + if (questStage.prologue != null && (!questStage.prologue.text.isEmpty()) && !questStage.prologueDisplayed){ + questStage.prologueDisplayed = true; + dialogQueue.add(questStage.prologue); + } + + if (questStage.getStatus() == QuestStatus.Failed && questStage.failureDialog != null && !questStage.failureDialog.text.isEmpty()){ + dialogQueue.add(questStage.failureDialog); + continue; + } + + if (questStage.getStatus() == QuestStatus.Complete && questStage.epilogue != null && (!questStage.epilogue.text.isEmpty()) && !questStage.epilogueDisplayed){ + questStage.epilogueDisplayed = true; + dialogQueue.add(questStage.epilogue); + } +// if (questStage.getStatus() != QuestStatus.Complete){ +// break; +// } + } + + if (quest.failed){ + finishedQuests.add(quest); + if (quest.failureDialog != null && !quest.failureDialog.text.isEmpty()){ + dialogQueue.add(quest.failureDialog); + } + } + + if (!quest.completed) + continue; + DialogData epilogue = quest.getEpilogue(); + if (epilogue != null && (!epilogue.text.isEmpty())){ + dialogQueue.add(epilogue); + + } + finishedQuests.add(quest); + } + if (!inDialog){ + inDialog = true; + displayNextDialog((MapStage) stage); + } + } + for (AdventureQuestData toRemove : finishedQuests) { + + if (!toRemove.failed && locationHasMoreQuests()){ + nextQuestDate.remove(toRemove.sourceID); + } + + Current.player().removeQuest(toRemove); + //Todo: Add quest to a separate "completed / failed" log? + } + } + + public boolean locationHasMoreQuests(){ + //intent: eventually stop providing quests for the day in a given town to encourage exploration + //todo: make values configurable + return new Random().nextFloat() <= 0.85f; + } + public void displayNextDialog(MapStage stage){ + if (dialogQueue.peek() == null) + { + inDialog = false; + return; + } + + MapDialog dialog = new MapDialog(dialogQueue.remove(), stage, -1); + stage.showDialog(); + dialog.activate(); + dialog.addDialogCompleteListener(e -> displayNextDialog(stage)); + + } + public static class DistanceSort implements Comparator + { + //ToDo: Make this more generic, compare PoI, mobs, random points, and player position + // In process, perhaps adjust nav indicator based on distance to target + //Sorts POI by distance from the player + public int compare(PointOfInterest a, PointOfInterest b) + { + float distToA = new Vector2(a.getPosition()).sub(Current.player().getWorldPosX(), Current.player().getWorldPosY()).len(); + float distToB = new Vector2(b.getPosition()).sub(Current.player().getWorldPosX(), Current.player().getWorldPosY()).len(); + if (distToA - distToB < 0.0f) + return -1; + else if (distToA - distToB > 0.0f) + return 1; + return 0; + } + } + private static AdventureQuestController object; + + public static AdventureQuestController instance() { + if (object == null) { + object = new AdventureQuestController(); + object.loadData(); + } + return object; + } + + private AdventureQuestController(){ + + } + + public AdventureQuestController(AdventureQuestController other){ + if (object == null) { + maximumSideQuests = other.maximumSideQuests; + mostRecentPOI = other.mostRecentPOI; + dialogQueue = other.dialogQueue; + questAvailability = other.questAvailability; + + object = this; + loadData(); + } + else{ + System.out.println("Could not initialize AdventureQuestController. An instance already exists and cannot be merged."); + } + } + + private void loadData(){ + Json json = new Json(); + FileHandle handle = Config.instance().getFile(Paths.QUESTS); + if (handle.exists()) + { + allQuests =json.fromJson(Array.class, AdventureQuestData.class, handle); + } + + for (AdventureQuestData q : allQuests){ + if (q.storyQuest) continue; + allSideQuests.add(q); + } + } + + public void updateEnteredPOI(PointOfInterest arrivedAt) + { + for(AdventureQuestData currentQuest : Current.player().getQuests()) { + currentQuest.updateEnteredPOI(arrivedAt); + } + } + + public void updateQuestsMapFlag(String updatedMapFlag, int updatedFlagValue) + { + for(AdventureQuestData currentQuest : Current.player().getQuests()) { + currentQuest.updateMapFlag(updatedMapFlag, updatedFlagValue); + } + } + + public void updateQuestsLeave(){ + for(AdventureQuestData currentQuest : Current.player().getQuests()) { + currentQuest.updateLeave(); + } + } + + public void updateQuestsWin(EnemySprite defeated, ArrayList enemies){ + boolean allEnemiesCleared = true; + if (enemies != null) { + //battle was won in a dungeon, check for "clear" objectives + for (EnemySprite enemy : enemies) { + if (enemy.getStage() != null && !enemy.equals(defeated)) { + //actor is an enemy that is present on the map. Check to see if there's a valid reason. + if (enemy.defeatDialog != null) { + //This enemy cannot be removed from the map by defeating it, ignore it for "cleared" purposes + continue; + } + allEnemiesCleared = false; + break; + } + } + } + for(AdventureQuestData currentQuest : Current.player().getQuests()) { + currentQuest.updateWin(defeated, allEnemiesCleared); + } + } + public void updateQuestsWin(EnemySprite defeated){ + updateQuestsWin(defeated, null); + } + + public void updateQuestsLose(EnemySprite defeatedBy){ + for(AdventureQuestData currentQuest : Current.player().getQuests()) { + currentQuest.updateLose(defeatedBy); + } + } + + public void updateDespawn(EnemySprite despawned){ + for(AdventureQuestData currentQuest: Current.player().getQuests()) { + currentQuest.updateDespawn(despawned); + } + } + + public AdventureQuestData generateQuest(int id){ + AdventureQuestData generated = null; + for (AdventureQuestData template: allQuests) { + if (template.isTemplate && template.getID() == id){ + generated = new AdventureQuestData(template); + generated.initialize(); + break; + } + } + return generated; + } + + public void addQuestSprites(AdventureQuestStage stage){ + if (stage.getTargetSprite() != null){ + enemySpriteList.add(stage.getTargetSprite()); + } + } + public List getQuestSprites(){ + return enemySpriteList; + } + + + String randomItemName() + { //todo: expand and include in fetch/delivery quests + String[] options = {"collection of frequently asked questions","case of card sleeves", "well loved playmat", "copy of Richard Garfield's autobiography", "collection of random foreign language cards", "lucky coin", "giant card binder", "unsorted box of commons", "bucket full of pieces of shattered artifacts","depleted mana shard"}; + + return Aggregates.random(options); + } + + public void abandon(AdventureQuestData quest){ + quest.fail(); + } + + public AdventureQuestData getQuestNPCResponse(String pointID, PointOfInterestChanges changes, String questOrigin) { + AdventureQuestData ret; + + for (AdventureQuestData q : Current.player().getQuests()) { + if (q.completed || q.storyQuest) + continue; + if (q.sourceID.equals(pointID)) { + //remind player about current active side quest + DialogData response = new DialogData(); + response.text = "\"You haven't finished the last thing we asked you to do!\" (" + q.name +") "; + DialogData dismiss = new DialogData(); + dismiss.name = "\"Oh, right, let me go take care of that.\""; + response.options = new DialogData[]{dismiss}; + ret = new AdventureQuestData(); + ret.offerDialog = response; + return ret; + } + } + if (nextQuestDate.containsKey(pointID) && nextQuestDate.get(pointID) >= LocalDate.now().toEpochDay()){ + //No more side quests available here today due to previous activity + DialogData response = new DialogData(); + response.text = "\"We don't have anything new for you to do right now. Come back tomorrow.\""; + DialogData dismiss = new DialogData(); + dismiss.name = "\"Okay.\" (Leave)"; + response.options = new DialogData[]{dismiss}; + ret = new AdventureQuestData(); + ret.offerDialog = response; + return ret; + } + + if (tooManyQuests(Current.player().getQuests())) { + //No more side quests available here today, too many active + DialogData response = new DialogData(); + response.text = "\"Adventurer, we need your assistance!\""; + DialogData dismiss = new DialogData(); + dismiss.name = "\"I can't, I have far too many things to do right now\" (Your quest log is too full already) (Leave)"; + response.options = new DialogData[]{dismiss}; + ret = new AdventureQuestData(); + ret.offerDialog = response; + return ret; + } + //todo - Make use of questOrigin in selecting appropriate quests + nextQuestDate.put(pointID, LocalDate.now().toEpochDay()); + ret = new AdventureQuestData(Aggregates.random(allSideQuests)); + ret.sourceID = pointID; + ret.initialize(); + return ret; + } + + private boolean tooManyQuests(List existing){ + int sideQuests = 0; + + for (AdventureQuestData quest : existing){ + if (quest.storyQuest || quest.completed || quest.failed) + continue; + sideQuests++; + } + return (sideQuests >= maximumSideQuests); + } +} diff --git a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java index 1aceb7abb97..2324f18ac70 100644 --- a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java +++ b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java @@ -8,7 +8,6 @@ import forge.StaticData; import forge.adventure.data.GeneratedDeckData; import forge.adventure.data.GeneratedDeckTemplateData; import forge.adventure.data.RewardData; -import forge.adventure.world.WorldSave; import forge.card.*; import forge.card.mana.ManaCostShard; import forge.deck.Deck; @@ -298,13 +297,14 @@ public class CardUtil { return result; } - public static List generateCards(Iterable cards,final RewardData data, final int count) + public static List generateCards(Iterable cards,final RewardData data, final int count, Random r) { + final List result = new ArrayList<>(); List pool = getPredicateResult(cards, data); if (pool.size() > 0) { for (int i = 0; i < count; i++) { - PaperCard candidate = pool.get(WorldSave.getCurrentSave().getWorld().getRandom().nextInt(pool.size())); + PaperCard candidate = pool.get(r.nextInt(pool.size())); if (candidate != null) { result.add(candidate); } diff --git a/forge-gui-mobile/src/forge/adventure/util/Config.java b/forge-gui-mobile/src/forge/adventure/util/Config.java index 852a2553a85..d32975a873f 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Config.java +++ b/forge-gui-mobile/src/forge/adventure/util/Config.java @@ -94,6 +94,8 @@ public class Config { if(settingsData.cardTooltipAdjLandscape == null || settingsData.cardTooltipAdjLandscape == 0f) settingsData.cardTooltipAdjLandscape=1f; + + //prefix = "forge-gui/res/adventure/Shandalar/"; prefix = getPlanePath(settingsData.plane); currentConfig = this; @@ -115,7 +117,7 @@ public class Config { private String resPath() { - return GuiBase.isAndroid() ? ForgeConstants.ASSETS_DIR : Files.exists(Paths.get("./res"))?"./":"../forge-gui/"; + return GuiBase.isAndroid() ? ForgeConstants.ASSETS_DIR : Files.exists(Paths.get("./res"))?"./":Files.exists(Paths.get("./forge-gui/"))?"./forge-gui/":"../forge-gui"; } public String getPlanePath(String plane) { diff --git a/forge-gui-mobile/src/forge/adventure/util/MapDialog.java b/forge-gui-mobile/src/forge/adventure/util/MapDialog.java index e9ba36c237e..a079188c080 100644 --- a/forge-gui-mobile/src/forge/adventure/util/MapDialog.java +++ b/forge-gui-mobile/src/forge/adventure/util/MapDialog.java @@ -14,15 +14,23 @@ import com.github.tommyettinger.textra.TypingLabel; import forge.Forge; import forge.adventure.character.EnemySprite; import forge.adventure.data.DialogData; +import forge.adventure.data.RewardData; import forge.adventure.player.AdventurePlayer; +import forge.adventure.pointofintrest.PointOfInterestChanges; import forge.adventure.stage.GameHUD; +import forge.adventure.scene.RewardScene; import forge.adventure.stage.MapStage; +import forge.adventure.world.WorldSave; import forge.card.ColorSet; import forge.localinstance.properties.ForgePreferences; import forge.model.FModel; import forge.util.Localizer; import org.apache.commons.lang3.tuple.Pair; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; + /** * MapDialog * Implements a dialogue/event tree for dialogs. @@ -35,7 +43,7 @@ public class MapDialog { private final static float WIDTH = 250f; static private final String defaultJSON = "[\n" + " {\n" + - " \"effect\":[],\n" + + //" \"effect\":[],\n" + " \"name\":\"Error\",\n" + " \"text\":\"This is a fallback dialog.\\nPlease check Forge logs for errors.\",\n" + " \"condition\":[],\n" + @@ -50,7 +58,6 @@ public class MapDialog { this.stage = stage; this.parentID = parentID; try { - if (S.isEmpty()) { System.err.print("Dialog error. Dialog property is empty.\n"); this.data = JSONStringLoader.parse(Array.class, DialogData.class, defaultJSON, defaultJSON); @@ -63,6 +70,25 @@ public class MapDialog { } } + public MapDialog(DialogData prebuiltDialog, MapStage stage, int parentID) { + this.stage = stage; + this.parentID = parentID; + try + { + if (prebuiltDialog == null) { + System.err.print("Dialog error. Dialog provided is null.\n"); + this.data = JSONStringLoader.parse(Array.class, DialogData.class, defaultJSON, defaultJSON); + return; + } + this.data = new Array<>(); + this.data.add(prebuiltDialog); + } + catch (Exception exception) + { + exception.printStackTrace(); + } + } + Pair audio = null; void unload() { @@ -174,8 +200,10 @@ public class MapDialog { super.clicked(event, x, y); } }); - if (i == 0) + if (i == 0) { stage.hideDialog(); + emitDialogFinished(); + } else stage.showDialog(); } else { @@ -183,6 +211,35 @@ public class MapDialog { } } + protected EventListenerList dialogCompleteList = new EventListenerList(); + protected EventListenerList questAcceptedList = new EventListenerList(); + public void addDialogCompleteListener(ChangeListener listener) { + dialogCompleteList.add(ChangeListener.class, listener); + } + public void addQuestAcceptedListener(ChangeListener listener){ + questAcceptedList.add(ChangeListener.class, listener); + } + + private void emitDialogFinished(){ + ChangeListener[] listeners = dialogCompleteList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } + + private void emitQuestAccepted(){ + ChangeListener[] listeners = questAcceptedList.getListeners(ChangeListener.class); + if (listeners != null && listeners.length > 0) { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(evt); + } + } + } + void fadeIn() { disposeAudio(true); GameHUD.getInstance().fadeIn(); @@ -203,10 +260,13 @@ public class MapDialog { void setEffects(DialogData.ActionData[] data) { if (data == null) return; for (DialogData.ActionData E : data) { - if (E.removeItem != null) { //Removes an item from the player's inventory. + if (E == null){ + continue; + } + if (E.removeItem != null&& (!E.removeItem.isEmpty())) { //Removes an item from the player's inventory. Current.player().removeItem(E.removeItem); } - if (E.addItem != null) { //Gives an item to the player. + if (E.addItem != null&& (!E.addItem.isEmpty())) { //Gives an item to the player. Current.player().addItem(E.addItem); } if (E.addLife != 0) { //Gives (positive or negative) life to the player. Cannot go over max health. @@ -216,6 +276,15 @@ public class MapDialog { if (E.addGold > 0) Current.player().giveGold(E.addGold); else Current.player().takeGold(-E.addGold); } + if (E.addMapReputation != 0) { + PointOfInterestChanges p; + if (E.POIReference.length()>0 && !E.POIReference.contains("$")) + p = WorldSave.getCurrentSave().getPointOfInterestChanges( E.POIReference); + else + p = stage.getChanges(); + if (p != null) + p.addMapReputation(E.addMapReputation); + } if (E.deleteMapObject != 0) { //Removes a dummy object from the map. if (E.deleteMapObject < 0) stage.deleteObject(parentID); else stage.deleteObject(E.deleteMapObject); @@ -246,6 +315,17 @@ public class MapDialog { EnemySprite EN = stage.getEnemyByID(parentID); EN.effect = E.setEffect; } + if (E.grantRewards != null && E.grantRewards.length > 0) { + Array ret = new Array(); + for(RewardData rdata:E.grantRewards) { + ret.addAll(rdata.generate(false, true)); + } + RewardScene.instance().loadRewards(ret, RewardScene.Type.QuestReward, null); + Forge.switchScene(RewardScene.instance()); + } + if (E.issueQuest != null && (!E.issueQuest.isEmpty())) { + emitQuestAccepted(); + } } } @@ -284,10 +364,15 @@ public class MapDialog { if (!condition.not) return false; } else if (condition.not) return false; } - if (condition.hasBlessing != null && !condition.hasBlessing.isEmpty()) { //Check for a named blessing. - if (!player.hasBlessing(condition.hasBlessing)) { + if (condition.hasMapReputation != Integer.MIN_VALUE){ //Check for at least X reputation. + if ( stage.getChanges().getMapReputation() < condition.hasMapReputation + 1) { if (!condition.not) return false; - } else if (condition.not) return false; + } else if(condition.not) return false; + } + if (condition.hasBlessing != null && !condition.hasBlessing.isEmpty()){ //Check for a named blessing. + if(!player.hasBlessing(condition.hasBlessing)){ + if(!condition.not) return false; + } else if(condition.not) return false; } if (condition.actorID != 0) { //Check for actor ID. if (!stage.lookForID(condition.actorID)) { diff --git a/forge-gui-mobile/src/forge/adventure/util/NavArrowActor.java b/forge-gui-mobile/src/forge/adventure/util/NavArrowActor.java new file mode 100644 index 00000000000..ca24c7df8c2 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/util/NavArrowActor.java @@ -0,0 +1,31 @@ +package forge.adventure.util; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.utils.Array; + +public class NavArrowActor extends Actor { + + Sprite texture; + public float navTargetAngle = 0.0f; + + public NavArrowActor() { + //TODO: Expand compass sprite to have color coded arrows, swap sprites based on distance to target + Array textureAtlas = Config.instance().getAtlas("maps/tileset/compass.atlas").createSprites("compass"); + if (textureAtlas.isEmpty()) { + System.out.print("NavArrow sprite not found"); + } + texture = textureAtlas.get(0); + setHeight(texture.getRegionHeight()); + setWidth(texture.getRegionWidth()); + } + + @Override + public void draw(Batch batch, float parentAlpha) { + if (texture == null) + return; + //TODO: Simplify params somehow for readability? All this does is spin the image around the player. + batch.draw(texture, getX()-texture.getWidth()/2, getY()-texture.getHeight()/2 ,(texture.getWidth()*texture.getScaleX()/2),(texture.getHeight()*texture.getScaleY()/2), texture.getWidth(), texture.getHeight(), texture.getScaleX(), texture.getScaleY(), navTargetAngle); + } +} diff --git a/forge-gui-mobile/src/forge/adventure/util/Paths.java b/forge-gui-mobile/src/forge/adventure/util/Paths.java index 406e3216a2b..7227c5bb761 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Paths.java +++ b/forge-gui-mobile/src/forge/adventure/util/Paths.java @@ -10,6 +10,7 @@ public class Paths { public static final String HEROES = "world/heroes.json"; public static final String POINTS_OF_INTEREST = "world/points_of_interest.json"; public static final String ITEMS = "world/items.json"; + public static final String QUESTS = "world/quests.json"; public static final String SKIN = "skin/ui_skin.json"; public static final String ITEMS_EQUIP = "skin/equip.png"; public static final String ITEMS_ATLAS = "sprites/items.atlas"; diff --git a/forge-gui-mobile/src/forge/adventure/world/World.java b/forge-gui-mobile/src/forge/adventure/world/World.java index 1a06aa44f10..bcff504aed3 100644 --- a/forge-gui-mobile/src/forge/adventure/world/World.java +++ b/forge-gui-mobile/src/forge/adventure/world/World.java @@ -47,6 +47,7 @@ public class World implements Disposable, SaveFileContent { private final Random random = new Random(); private boolean worldDataLoaded = false; private Texture globalTexture = null; + private int nextQuestId; public Random getRandom() { return random; @@ -114,6 +115,7 @@ public class World implements Disposable, SaveFileContent { mapPoiIds = new PointOfInterestMap(getChunkSize(), this.data.tileSize, this.data.width / getChunkSize(), this.data.height / getChunkSize()); mapPoiIds.load(saveFileData.readSubData("mapPoiIds")); seed = saveFileData.readLong("seed"); + nextQuestId = saveFileData.readInt("nextQuestId"); } @Override @@ -129,6 +131,7 @@ public class World implements Disposable, SaveFileContent { data.store("mapObjectIds", mapObjectIds.save()); data.store("mapPoiIds", mapPoiIds.save()); data.store("seed", seed); + data.store("nextQuestId", nextQuestId); return data; @@ -899,6 +902,10 @@ public class World implements Disposable, SaveFileContent { return mapPoiIds.findPointsOfInterest(name); } + public List getAllPointOfInterest(){ + return mapPoiIds.getAllPointOfInterest(); + } + public int getChunkSize() { return (Scene.getIntendedWidth() > Scene.getIntendedHeight() ? Scene.getIntendedWidth() : Scene.getIntendedHeight()) / data.tileSize; } @@ -919,4 +926,8 @@ public class World implements Disposable, SaveFileContent { } return globalTexture; } + + public int getNextQuestId() { + return nextQuestId++; + } } diff --git a/forge-gui/res/adventure/Shandalar/custom_card_pics/Flame Sword.png b/forge-gui/res/adventure/Shandalar/custom_card_pics/Flame Sword.png new file mode 100644 index 00000000000..2875056f979 Binary files /dev/null and b/forge-gui/res/adventure/Shandalar/custom_card_pics/Flame Sword.png differ diff --git a/forge-gui/res/adventure/Shandalar/custom_cards/flame_sword.txt b/forge-gui/res/adventure/Shandalar/custom_cards/flame_sword.txt new file mode 100644 index 00000000000..a38dea43478 --- /dev/null +++ b/forge-gui/res/adventure/Shandalar/custom_cards/flame_sword.txt @@ -0,0 +1,9 @@ +Name:Flame Sword +Types:Artifact +A:AB$ DealDamage | ActivationLimit$ 1 | Cost$ PayShards<3> | ActivationZone$ Command | ValidTgts$ Creature,Player,Planeswalker | NumDmg$ Z | SubAbility$ Eject | SpellDescription$ Deal 3 damage to any target. If the target is a tapped creature, deal 5 damage to it instead. +SVar:X:3 +SVar:Y:Targeted$Valid Creature.tapped/Times.2 +SVar:Z:SVar$X/Plus.Y +SVar:Eject:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile +Oracle:{M}{M}{M}: Flame Sword deals 3 damage to any target, or 5 damage to target tapped creature. + diff --git a/forge-gui/res/adventure/Shandalar/maps/map/slime_hive.tmx b/forge-gui/res/adventure/Shandalar/maps/map/slime_hive.tmx index 8ee16699abc..a95caba9dcb 100644 --- a/forge-gui/res/adventure/Shandalar/maps/map/slime_hive.tmx +++ b/forge-gui/res/adventure/Shandalar/maps/map/slime_hive.tmx @@ -7,17 +7,17 @@ - eJxjYBgFo4Ay4MY50C7ABF4D7KYPWMSQ3URqmD1jpcg5YPCBAbu7SHUHOqaGuyh1kyPUHY5UdBc6YOOFYGLcA7OfQwBBU9tdv3ggmFjALYDKh7kNOS4PsFPPfchgGlp6d6RB/AxVMJDlJ668NxjL9MHqJlq6C5RnYZhUN9HDXaS4iZaAVPeAAK3dFEaG+bR2EznmD8Z8NxgBAK1JHpE= + eJxjYBgFo4Ay4MY50C7ABF4D7KYPWMSQ3URqmD1jpcg5YPCBAbu7SHUHOqaGu6jtJmq4Cx2w8UIwKe7hEKCdu37xQDCxgFsAtzth+AA79dyHDKahpXdHGsTPUAUDWX7iynuDsUwfrG6ipbtAeRaGSXUTPdxFiptoCUh1DwjQ2k1hZJhPazeRY/5gzHeDEQAAOM4lLQ== - eJy1ljFuwzAMRbXYgZO0yNy9BykKdM8tcoVO7Sk6+iw5SqfMOUJBKAQ+2E9Sip0PfNimbOqZVOSMT6WMgc8bbnvP+/N6bmW5DNUeE15jftQjmdi9NhYx4TUT4znt/zOpIib2zky7A3fEhHWyPWtxJpyfxRiLcqC/xlK+xzbGpUyWy84p5yKN47nH5+lnqs56x2pi878N1dEzl6Gkipgk7uWPevF7m/dztz6TjouytS162dfj66aacWVidUFG+a2rkUfGtjdn+ZXrmONQJmvLdC3V2XPKosetWZtLmMQfU7XH5DGitKbeeAsHep7qPitcvXWy8th6eHDv72XC9YY9XMJkv0XKZHPq/Npj23ePifUu4hMOey352XtGTBpnc7I81va/A2MqTp2UB+e/Fh7vte49rG6aO2PSHtvY2kzz1MfEPC/gamHCddXC8wgu27seDutobbN1LMI9AMfk+yI58Xtzjz2We/wHYwbUuA== + eJy1lk1OAzEMhbNp0RRKgV4AuEolzsICLlHuwHLOwmm67hGQlVo8np7z07SWnqo4E+cb25N0uU5pWdDPjRY/s7u/nFpZDousiAnHGB/tmkzqWfaVmHCsTPG838U1KzGpd1Z2+6BVYsI8cc1aVDPcX/kUi3OgehhHmZhLMSCLWcRbY/qesmq1K+XE9bxN6QX0utXralZiMn/EpGpxeEzpY/O3Zv+kuUaYfJ7rpnrbcmQ8n5v/+WWumqm8IKN96y7ksbnVSbX4zvV1YutlYjHTMWXV1jmL/66oN0eYTG9TVsQUMaJ5TqP5Fg7UPOVz1rh688QWsfXw4Nnfy4T9hjUcYeK7yJk4pu/vNea6R0yqdiU+4+CxxVfvWWJyv9pTxWHxfwfFlII8OQ/uf0za3ys/e1TePHaNyWvMvkszzVMfk9I8wNXChH3VwnMNLq5dDwer1Nuqj83wDMA5u18sJt435yhiOUe/IfLYdQ== - eJydlm1ugzAMhpnWrYS20i6xK+wGKRUn2Q12JPp1iF1j3Z9J/eIM1X4Mi7zjxRhKa8lKlID92EmcRFFb5q7W1FVjnvoZ9UUWrlItzzNb7xFhWcdRtI0r33nZFsEv+kOZxM4m2BK7zHQLM/KzL/UY2kPo+8B7HMjEeQYTK2IH+8G1bYjKv6dSz0FX5bef40o9zS3jqgWTFTPneR18Tl5sFXuimgn7iLklJ2DKyDZ0T32JmfMv/IWKvYtJ/F2m7Xwjl7wWwgJBnhDTSsWVGWuoxeLxlActC9pH/7E+Nb+RfHAO87heS50TLfOwzpqpoFh5DQ6KBTxfSRTtSL+TNufWiE+zQDVT5przj7Om6rUQ+ZhU7U/g2BlMfkB+upg8zVn/8Xk7G9+8jmsuEWGD9gmfi6PB9D6t/KO9pl2CtRQei58lNeb1HpxTy76xf7GHpU1CPCLWmbnG3sUEPzhr6KNmw6b4Z+2qJRD0UaMQC9fmTdw+Ozq+Qq2NFYNmSxQPc2k+ML492N/pbzUPzgEkj+t6CMX90pUry0/fGI/z2rOeXF0D9q55jzN35pr7kGNhVtRQHrdiRn7028HKlYhX/tMrTJZ/K0/eyInYuRg+YT9XMWAedymPjYy3DhiSjnVkJtQs7H9mEgGTrvPwb9Ur5gIL1o3fOSxsHz7xb6bs4/4r1HifwPZo1t6P2B+pa74fMK/reW74Hlq7u7h+jXcP5wRvO9TuZdx8FxY9vm/h4X/47Ipw3Ho/3nuX3cqk3+jW+Rqqf6YK8do= + eJylVltuwjAQTFVa2RQEp+kNTFBO0o/Sa4VHbxUeOQPqR7NKRhlvNg/alUY2MfbOjtdrJ0nXNr5F6utvgfoZ9cW2voa216WNv5hwObok+Xa177xqy8Yv+lM5yTqnZi1Zlzk9whn6FBUuTXtu+qHhe5nIiXUGJwZiB/ez764hkLnXCrcGB9f+J9DY3tUtOFkxs87Hxufb2oast3ddTsgj5s2aZLQ2UFBfYmb9hX+pYu/jJP7ui67e0NLaC9YJMR1UXFnPvDFOgXTQtqU86jPRgzXMXbuXWhNtm2afNaeSYuU90Ps0ZNeX+Pe3EZ/mAmhOmY/Hn5cx9F7APldJsqvwtbLHwwR9OFfBRzQONGbN4/N2o//IfLTMS/QChozPxcV3c+ljUftHOwZwAcR2SrexPEqNcZ2DG2q1bieqA9LO17FOGn26j3GCH5w19FGzsab4Z/TVEhj6qFGIhWvzydlnR587655l09zmig/zghU+7r8/2f/T8zUfnE1Y7tp6COB+6dPK8jP0jb/z3jOudKcVPr7HmXfm4zzkWJgraih/t2KGPvrtYGklFpT/1OBkxa510b+DoYn4vhs+wSlXMWAcdyl/mxlvHXCY9+wjc0LNQv4zJzFw0nUe/q2awbzABftmvXP0+vCJuZlaH/df6e19sQxrz5bdfER+pD5+P2Bc1/Pc8N2nxVReP8a7hzXB2w61e+/id2E54PsRPjyHz64Yx63zccpd9l9DvnKtts7XVPwCwzvlKg== @@ -25,7 +25,7 @@ - eJzNllFSgzAQhqMzVVLF6aN30Veo0xN4F0/hAfTVar2Rj7SVYzRr+IdlS8hGcOo/swMtIfny77JgzO9U2va8sN3fp5KWqbRtxK6N2ZdcYwqmoXEh7Wc++uYKMYXW/MiM2Yj4zMYxSe0SeCgeXFwtjHlx415dvDmeWukTjX3P4kwpPJzp7syYexeV1ftEY6UHlcLbIZ6f/bnjt/X7JbYUn0L6mvsjfARnjEUyLxu/UoV80b2I22vPJX3UMNH5tskVmOpEb7jHfVyos1WkhiBioXrCflBLF3kbUqhpLuwHAd+Ii/K3znRMqBti4s8PKcSEHJ/nvodAtJ+n3M8jfSsiLJypavJG862atbh/sqfz57QQ47E+zUn9bWe7/21ZjWj6Mfzi/kvx/ooc03G+OA7MQb7JGpP7XdpjFvK5ZmuFxD0KifKFORF9tc9zKX2AzxvGE3rmSgV3SLLuea2jX3NP/0qolXXW7cXon9yLvvym6PEynQ35Rg3KdccyjdHQenTt+Wb4uoZ3qm/ZfWSeU/g3Rv/hGx/SvBc0740pdQCOCZmI + eJzFVltSwzAMNHxATCeQ+8Bvkk5PwCG4AafgAPDLq7fqgx4Da5KdKGpty9hMd0bjpPZIq5Wl1Ji/obPTc2vn7+eCllNnJ4vt5eQlY5TgFDqXwgnm4+SL+VUZ8y1sXeVxktgl8CFbOls0xry6c2/O3h2fg1InOvtZpXMM8eGc7i+MeXC2sXqd6KzUYKPQNsSHsHfrjx3yJW4pOsUAHcEzxkVy7ke9cvB0N3+XOmo44ZlqRZygUQqgcQyryB0CiAvdJ+SDu3RVT6ZBH8iD8vwYOcc4QZOlPe4fHyfMjst6WAHK57ke/PC7SdZG9OGcSJPtqM/KznOVfSf7tBXnEZ980nzb2flv29G431D/QK+e+ZHg8xU1pvWmOTb4IN0WzdxkvrLm0PnAYvnANfKB6gWfMF7DU7WUOkBnaB3quU7B2wcek1bef5jXXNP/Ap/HfBZjfnItTtU3BY/X6dxQb9xBGTeXUw5C8Wjv5Ta8r+Fb6n/MPuLnHPrloJQuJaD5Lmi+GyXxCxKjjW8= diff --git a/forge-gui/res/adventure/Shandalar/maps/tileset/compass.atlas b/forge-gui/res/adventure/Shandalar/maps/tileset/compass.atlas new file mode 100644 index 00000000000..5ef5d489e7b --- /dev/null +++ b/forge-gui/res/adventure/Shandalar/maps/tileset/compass.atlas @@ -0,0 +1,9 @@ + +compass.png +size: 50,50 +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +compass + xy: 1,1 + size: 48,48 diff --git a/forge-gui/res/adventure/Shandalar/maps/tileset/compass.png b/forge-gui/res/adventure/Shandalar/maps/tileset/compass.png new file mode 100644 index 00000000000..5411c9a5294 Binary files /dev/null and b/forge-gui/res/adventure/Shandalar/maps/tileset/compass.png differ diff --git a/forge-gui/res/adventure/Shandalar/sprites/items.atlas b/forge-gui/res/adventure/Shandalar/sprites/items.atlas index f27f7d7a9de..0ce4558d0a9 100644 --- a/forge-gui/res/adventure/Shandalar/sprites/items.atlas +++ b/forge-gui/res/adventure/Shandalar/sprites/items.atlas @@ -419,4 +419,7 @@ GreenLeaf2 size: 16, 16 SearchPost xy: 384, 63 + size: 16, 16 +LogBook + xy: 240, 912 size: 16, 16 \ No newline at end of file diff --git a/forge-gui/res/adventure/Shandalar/ui/hud.json b/forge-gui/res/adventure/Shandalar/ui/hud.json index b4d5f2a283e..a9747682d2d 100644 --- a/forge-gui/res/adventure/Shandalar/ui/hud.json +++ b/forge-gui/res/adventure/Shandalar/ui/hud.json @@ -118,9 +118,9 @@ { "type": "TextButton", - "name": "statistic", + "name": "logbook", "style":"menu", - "text": "[%120][+Status]", + "text": "[%120][+Logbook]", "binding": "Status", "width": 48, "height": 36, diff --git a/forge-gui/res/adventure/Shandalar/ui/hud_landscape.json b/forge-gui/res/adventure/Shandalar/ui/hud_landscape.json index 20ea9b51bb9..dc0eb34866b 100644 --- a/forge-gui/res/adventure/Shandalar/ui/hud_landscape.json +++ b/forge-gui/res/adventure/Shandalar/ui/hud_landscape.json @@ -104,13 +104,11 @@ "x": 416, "y": 146 }, - { "type": "TextButton", - "name": "statistic", + "name": "logbook", "style":"menu", - "text": "[%120][+Status]", - "binding": "Status", + "text": "[%120][+Logbook]", "width": 64, "height": 36, "x": 416, diff --git a/forge-gui/res/adventure/Shandalar/ui/hud_portrait.json b/forge-gui/res/adventure/Shandalar/ui/hud_portrait.json index 2d5bb2578aa..4d8f7d73e58 100644 --- a/forge-gui/res/adventure/Shandalar/ui/hud_portrait.json +++ b/forge-gui/res/adventure/Shandalar/ui/hud_portrait.json @@ -106,9 +106,9 @@ { "type": "TextButton", - "name": "statistic", + "name": "logbook", "style":"menu", - "text": "[%120][+Status]", + "text": "[%120][+Logbook]", "binding": "Status", "width": 64, "height": 32, diff --git a/forge-gui/res/adventure/Shandalar/ui/items_portrait.json b/forge-gui/res/adventure/Shandalar/ui/items_portrait.json index 8fca230a006..45d33a8a045 100644 --- a/forge-gui/res/adventure/Shandalar/ui/items_portrait.json +++ b/forge-gui/res/adventure/Shandalar/ui/items_portrait.json @@ -20,7 +20,7 @@ { "name": "cards", "x": 5, - "y": 5, + "y": 20, "width": 260, "height": 405 }, @@ -79,7 +79,7 @@ "text": "A Street Market", "width": 48, "height": 20, - "x": 200, + "x": 0, "y": 0 } ] diff --git a/forge-gui/res/adventure/Shandalar/ui/quests.json b/forge-gui/res/adventure/Shandalar/ui/quests.json new file mode 100644 index 00000000000..b55a347c468 --- /dev/null +++ b/forge-gui/res/adventure/Shandalar/ui/quests.json @@ -0,0 +1,87 @@ +{ + "width": 480, + "height": 270, + "yDown": true, + "elements": [ + { + "type": "Image", + "name": "lastScreen", + "width": 480, + "height": 270 + }, + { + "type": "Window", + "name": "scrollWindow", + "style": "paper", + "x": 15, + "y": 18, + "width": 400, + "height": 235, + }, + { + "type": "Table", + "name": "questList", + "x": 20, + "y": 22, + "width": 390, + "height": 220, + "fontColor":"black" + }, + { + "type": "Table", + "name": "questDetails", + "x": 20, + "y": 22, + "width": 390, + "height": 220, + "fontColor":"black" + }, + { + "type": "TextButton", + "name": "abandonQuest", + "text": "Abandon Quest", + "width": 130, + "height": 30, + "x": 20, + "y": 25 + }, + { + "type": "TextButton", + "name": "trackQuest", + "text": "Track Quest", + "width": 130, + "height": 30, + "x": 150, + "y": 25 + }, + { + "type": "TextButton", + "name": "backToList", + "text": "Quest List", + "width": 130, + "height": 30, + "x": 280, + "y": 25 + }, + { + "type": "TextButton", + "name": "return", + "text": "tr(lblBack)", + "binding": "Back", + "width": 60, + "height": 30, + "x": 420, + "y": 224 + }, + { + "type": "TextButton", + "name": "status", + "text": "Status", + "binding": "Status", + "width": 60, + "height": 30, + "x": 420, + "y": 184 + } + ] +} diff --git a/forge-gui/res/adventure/Shandalar/ui/quests_portrait.json b/forge-gui/res/adventure/Shandalar/ui/quests_portrait.json new file mode 100644 index 00000000000..6cfc29125d4 --- /dev/null +++ b/forge-gui/res/adventure/Shandalar/ui/quests_portrait.json @@ -0,0 +1,86 @@ +{ + "width": 270, + "height": 480, + "yDown": true, + "elements": [ + { + "type": "Image", + "name": "lastScreen", + "width": 270, + "height": 480 + }, + { + "type": "Window", + "name": "scrollWindow", + "style": "paper", + "x": 4, + "y": 4, + "width": 262, + "height": 419 + }, + { + "type": "Table", + "name": "questList", + "x": 8, + "y": 10, + "width": 258, + "height": 413 + }, + { + "type": "Table", + "name": "questDetails", + "x": 8, + "y": 10, + "width": 258, + "height": 413 + "fontColor":"black" + }, + { + "type": "TextButton", + "name": "abandonQuest", + "text": "Abandon Quest", + "width": 250, + "height": 30, + "x": 10, + "y": 14 + }, + { + "type": "TextButton", + "name": "trackQuest", + "text": "Track Quest", + "width": 250, + "height": 30, + "x": 10, + "y": 54 + }, + { + "type": "TextButton", + "name": "backToList", + "text": "Quest List", + "width": 250, + "height": 30, + "x": 10, + "y": 94 + }, + { + "type": "TextButton", + "name": "return", + "text": "tr(lblBack)", + "binding": "Back", + "width": 130, + "height": 30, + "x": 135, + "y": 440 + }, + { + "type": "TextButton", + "name": "status", + "text": "Status", + "binding": "Status", + "width": 130, + "height": 30, + "x": 5, + "y": 440 + } + ] +} \ No newline at end of file diff --git a/forge-gui/res/adventure/Shandalar/ui/statistic.json b/forge-gui/res/adventure/Shandalar/ui/statistic.json index 0e901284a71..1feba1b9185 100644 --- a/forge-gui/res/adventure/Shandalar/ui/statistic.json +++ b/forge-gui/res/adventure/Shandalar/ui/statistic.json @@ -122,9 +122,9 @@ "name": "return", "text": "tr(lblBack)", "binding": "Back", - "width": 100, + "width": 80, "height": 30, - "x": 345, + "x": 395, "y": 224 }, { @@ -133,7 +133,7 @@ "text": "[%120][+AWARD]", "width": 30, "height": 30, - "x": 315, + "x": 285, "y": 224 }, { @@ -169,9 +169,15 @@ "x": 394, "y": 60 }, - { - "type": "Table", - "font": "default" + { + "type": "TextButton", + "name": "quests", + "text": "Quests", + "binding": "Status", + "width": 80, + "height": 30, + "x": 315, + "y": 224 } ] } diff --git a/forge-gui/res/adventure/Shandalar/ui/statistic_portrait.json b/forge-gui/res/adventure/Shandalar/ui/statistic_portrait.json index 9f01117d94d..bfbecae1869 100644 --- a/forge-gui/res/adventure/Shandalar/ui/statistic_portrait.json +++ b/forge-gui/res/adventure/Shandalar/ui/statistic_portrait.json @@ -113,11 +113,21 @@ "name": "return", "text": "tr(lblBack)", "binding": "Back", - "width": 230, + "width": 115, + "height": 30, + "x": 155, + "y": 440 + }, + { + "type": "TextButton", + "name": "quests", + "text": "Quests", + "binding": "Status", + "width": 115, "height": 30, "x": 35, "y": 440 - }, + } { "type": "TextButton", "name": "toggleAward", diff --git a/forge-gui/res/adventure/Shandalar/world/enemies.json b/forge-gui/res/adventure/Shandalar/world/enemies.json index 08e2b0515e9..2cee7fb686b 100644 --- a/forge-gui/res/adventure/Shandalar/world/enemies.json +++ b/forge-gui/res/adventure/Shandalar/world/enemies.json @@ -54,7 +54,17 @@ "addMaxCount": 90 } ], - "colors": "UW" + "colors": "UW", + "questTags": [ + "Human", + "Soldier", + "Wandering", + "IdentityAzorius", + "IdentityWhite", + "IdentityBlue", + "BiomeWhite", + "Loner" + ] }, { "name": "Aether Channeler", @@ -112,7 +122,17 @@ "addMaxCount": 90 } ], - "colors": "WU" + "colors": "WU", + "questTags": [ + "Human", + "Wizard", + "BiomeWhite", + "BiomeBlue", + "IdentityWhite", + "IdentityBlue", + "IdentityAzorius", + "Mystic" + ] }, { "name": "Akroma", @@ -148,7 +168,16 @@ "equipment": [ "Mox Pearl" ], - "colors": "W" + "colors": "W", + "questTags": [ + "Angel", + "Holy", + "Humanoid", + "Boss", + "Flying", + "IdentityWhite", + "Akroma" + ] }, { "name": "Amonkhet Minotaur", @@ -222,7 +251,18 @@ "addMaxCount": 90 } ], - "colors": "BR" + "colors": "BR", + "questTags": [ + "Humanoid", + "Aggressive", + "Minotaur", + "Tribal", + "IdentityRed", + "BiomeRed", + "IdentityBlack", + "IdentityRakdos", + "BiomeRed" + ] }, { "name": "Ape", @@ -295,7 +335,17 @@ "addMaxCount": 90 } ], - "colors": "GR" + "colors": "GR", + "questTags": [ + "Humanoid", + "Territorial", + "Animal", + "Wild", + "IdentityRed", + "IdentityGreen", + "IdentityGruul", + "BiomeGreen" + ] }, { "name": "Archer", @@ -368,7 +418,15 @@ "addMaxCount": 90 } ], - "colors": "GW" + "colors": "GW", + "questTags": [ + "Human", + "Soldier", + "IdentityWhite", + "IdentityGreen", + "IdentitySelesnya", + "BiomeWhite" + ] }, { "name": "Armored Knight", @@ -425,6 +483,15 @@ "count": 50, "addMaxCount": 100 } + ], + "questTags": [ + "Human", + "Soldier", + "Leader", + "IdentityWhite", + "IdentityRed", + "IdentityBoros", + "BiomeWhite" ] }, { @@ -451,10 +518,30 @@ "addMaxCount": 87 } ], - "colors": "BR" + "colors": "BR", + "questTags": [ + "Humanoid", + "Devil", + "IdentityRed", + "IdentityBlack", + "IdentityRakdos", + "BiomeRed" + ] }, { "name": "Axgard Dwarf", + "questTags": [ + "Dwarf", + "Soldier", + "Territorial", + "Tribal", + "Warrior", + "Subterranean", + "IdentityRed", + "IdentityWhite", + "IdentityBoros", + "BiomeRed" + ], "sprite": "sprites/dwarf_8.atlas", "deck": [ "decks/axgard_dwarf.dck" @@ -528,6 +615,14 @@ }, { "name": "Badger", + "questTags": [ + "Animal", + "Territorial", + "Wild", + "Small", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/dungeon/badger.atlas", "deck": [ "decks/badger.dck" @@ -579,6 +674,16 @@ }, { "name": "Bandit", + "questTags": [ + "Dwarf", + "Wandering", + "Warrior", + "Bandit", + "IdentityBlack", + "IdentityRed", + "IdentityRakdos", + "BiomeColorless" + ], "sprite": "sprites/dwarf_7.atlas", "deck": [ "decks/bandit.dck" @@ -652,6 +757,14 @@ }, { "name": "Bear", + "questTags": [ + "Territorial", + "Animal", + "Wild", + "Domesticated", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/bear.atlas", "deck": [ "decks/bear.json" @@ -725,6 +838,16 @@ }, { "name": "Beastmaster", + "questTags": [ + "Human", + "Loner", + "Nature", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeGreen", + "BiomeWhite" + ], "sprite": "sprites/calibou_elite.atlas", "deck": [ "decks/beastmaster.dck" @@ -801,6 +924,16 @@ }, { "name": "Beholder", + "questTags": [ + "Aberration", + "Floating", + "Flying", + "IdentityBlack", + "IdentityBlue", + "IdentityRed", + "IdentityGrixis", + "BiomeBlack" + ], "sprite": "sprites/beholder.atlas", "deck": [ "decks/beholder.dck" @@ -876,6 +1009,18 @@ }, { "name": "Berserker", + "questTags": [ + "Dwarf", + "Aggressive", + "Loner", + "Barbarian", + "Warrior", + "IdentityRed", + "IdentityBlack", + "IdentityGreen", + "IdentityJund", + "BiomeRed" + ], "sprite": "sprites/dwarf_5.atlas", "deck": [ "decks/berserker.json" @@ -902,6 +1047,13 @@ }, { "name": "Big Zombie", + "questTags": [ + "Undead", + "Zombie", + "Humanoid", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/zombie_2.atlas", "deck": [ "decks/zombie_bad.json" @@ -939,6 +1091,18 @@ }, { "name": "Bird", + "questTags": [ + "Animal", + "Flying", + "Territorial", + "Bird", + "Predator", + "Nesting", + "Wild", + "Domesticated", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/griffin_2.atlas", "deck": [ "decks/bird_blue.json" @@ -1014,6 +1178,13 @@ }, { "name": "Black Wiz1", + "questTags": [ + "Wizard", + "Necromancer", + "IdentityBlack", + "BiomeColorless", + "BiomeBlack" + ], "sprite": "sprites/black_wizard.atlas", "deck": [ "decks/black_bad.json" @@ -1087,6 +1258,13 @@ }, { "name": "Black Wiz2", + "questTags": [ + "Wizard", + "Necromancer", + "IdentityBlack", + "BiomeColorless", + "BiomeBlack" + ], "sprite": "sprites/black_wiz2.atlas", "deck": [ "decks/fear.dck" @@ -1113,6 +1291,13 @@ }, { "name": "Black Wiz3", + "questTags": [ + "Wizard", + "Necromancer", + "IdentityBlack", + "BiomeColorless", + "BiomeBlack" + ], "sprite": "sprites/black_wiz3.atlas", "deck": [ "decks/black_wiz3.dck" @@ -1139,6 +1324,15 @@ }, { "name": "Blue Wiz1", + "questTags": [ + "Wizard", + "Illusionist", + "Flying", + "Human", + "IdentityBlue", + "BiomeColorless", + "BiomeBlue" + ], "sprite": "sprites/mage.atlas", "deck": [ "decks/blue_bad.json" @@ -1214,6 +1408,14 @@ }, { "name": "Blue Wiz2", + "questTags": [ + "Illusionist", + "Wizard", + "Human", + "IdentityBlue", + "BiomeColorless", + "BiomeBlue" + ], "sprite": "sprites/blue_wiz2.atlas", "deck": [ "decks/mill.dck" @@ -1240,6 +1442,14 @@ }, { "name": "Blue Wiz3", + "questTags": [ + "Human", + "Wizard", + "Illusionist", + "IdentityBlue", + "BiomeColorless", + "BiomeBlue" + ], "sprite": "sprites/mage_2.atlas", "deck": [ "decks/counter.dck" @@ -1268,6 +1478,15 @@ }, { "name": "Boar", + "questTags": [ + "Animal", + "Territorial", + "Prey", + "Wild", + "IdentityGreen", + "BiomeRed", + "BiomeColorless" + ], "sprite": "sprites/dungeon/boar.atlas", "scale": 0.55, "deck": [ @@ -1324,6 +1543,16 @@ }, { "name": "Boggart", + "questTags": [ + "Goblin", + "Humanoid", + "Coward", + "Tribal", + "IdentityRed", + "IdentityGreen", + "IdentityGruul", + "BiomeRed" + ], "sprite": "sprites/goblin_2.atlas", "deck": [ "decks/eyeblight.dck" @@ -1361,6 +1590,13 @@ }, { "name": "Bull", + "questTags": [ + "Farm", + "Animal", + "Domesticated", + "IdentityGreen", + "BiomeRed" + ], "sprite": "sprites/dungeon/bull.atlas", "scale": 0.65, "deck": [ @@ -1417,6 +1653,15 @@ }, { "name": "Cat", + "questTags": [ + "Cat", + "Humanoid", + "Soldier", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeWhite" + ], "sprite": "sprites/lion.atlas", "deck": [ "decks/cat.json" @@ -1490,6 +1735,16 @@ }, { "name": "Cathar", + "questTags": [ + "Soldier", + "Human", + "Religious", + "Holy", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeWhite" + ], "sprite": "sprites/cathar.atlas", "deck": [ "decks/cathar.dck" @@ -1563,6 +1818,18 @@ }, { "name": "Cave Spider", + "questTags": [ + "Territorial", + "Animal", + "Ambush", + "Predator", + "Wild", + "Subterranean", + "IdentityRed", + "IdentityGreen", + "IdentityBlack", + "IdentityJund" + ], "sprite": "sprites/spider_2.atlas", "deck": [ "decks/cave_spider.dck" @@ -1603,6 +1870,15 @@ }, { "name": "Centaur", + + "questTags": [ + "Humanoid", + "Tribal", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeGreen" + ], "sprite": "sprites/centaur.atlas", "deck": [ "decks/centaur.json" @@ -1629,6 +1905,15 @@ }, { "name": "Centaur Warrior", + "questTags": [ + "Humanoid", + "Soldier", + "Tribal", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeGreen" + ], "sprite": "sprites/centaur_2.atlas", "deck": [ "decks/centaur_warrior.dck" @@ -1655,6 +1940,16 @@ }, { "name": "Challenger 20", + "questTags": [ + "Challenger", + "IdentityUnknown", + "BiomeGreen", + "BiomeRed", + "BiomeColorless", + "BiomeWhite", + "BiomeBlue", + "BiomeBlack" + ], "nameOverride": "Challenger", "sprite": "sprites/doppelganger.atlas", "deck": [ @@ -1738,6 +2033,16 @@ }, { "name": "Challenger 21", + "questTags": [ + "Challenger", + "IdentityUnknown", + "BiomeGreen", + "BiomeRed", + "BiomeColorless", + "BiomeWhite", + "BiomeBlue", + "BiomeBlack" + ], "nameOverride": "Challenger", "sprite": "sprites/doppelganger.atlas", "deck": [ @@ -1821,6 +2126,16 @@ }, { "name": "Challenger 22", + "questTags": [ + "Challenger", + "IdentityUnknown", + "BiomeGreen", + "BiomeRed", + "BiomeColorless", + "BiomeWhite", + "BiomeBlue", + "BiomeBlack" + ], "nameOverride": "Challenger", "sprite": "sprites/doppelganger.atlas", "deck": [ @@ -1904,6 +2219,16 @@ }, { "name": "Chandra", + "questTags": [ + "Human", + "Wizard", + "Pyromancer", + "Planeswalker", + "Boss", + "Chandra", + "Fire", + "IdentityRed" + ], "sprite": "sprites/boss/chandra.atlas", "deck": [ "decks/miniboss/Fire of Kaladesh.dck" @@ -1991,6 +2316,14 @@ }, { "name": "Chandra's Acolyte", + "questTags": [ + "Pyromancer", + "Human", + "Wizard", + "Chandra", + "Fire", + "IdentityRed" + ], "sprite": "sprites/red_wiz2.atlas", "deck": [ "decks/chandra1.dck" @@ -2087,6 +2420,14 @@ }, { "name": "Chandra's Firestarter", + "questTags": [ + "Pyromancer", + "Wizard", + "Human", + "Chandra", + "Fire", + "IdentityRed" + ], "sprite": "sprites/red_wiz3.atlas", "deck": [ "decks/chandra2.dck" @@ -2183,6 +2524,13 @@ }, { "name": "Chandra's Hellhound", + "questTags": [ + "Aberration", + "Animal", + "Chandra", + "FIre", + "IdentityRed" + ], "sprite": "sprites/hellhound_2.atlas", "deck": [ "decks/hellhound2.dck" @@ -2278,6 +2626,14 @@ }, { "name": "Chandra's Immolator", + "questTags": [ + "Chandra", + "Human", + "Pyromancer", + "Wizard", + "FIre", + "IdentityRed" + ], "sprite": "sprites/red_wiz2.atlas", "deck": [ "decks/chandra3.dck" @@ -2374,6 +2730,14 @@ }, { "name": "Chandra's Lavamancer", + "questTags": [ + "Chandra", + "Pyromancer", + "Human", + "Wizard", + "FIre", + "IdentityRed" + ], "sprite": "sprites/red_wiz3.atlas", "deck": [ "decks/chandra4.dck" @@ -2470,6 +2834,14 @@ }, { "name": "Chandra's Pyromancer", + "questTags": [ + "Pyromancer", + "Wizard", + "Chandra", + "Human", + "FIre", + "IdentityRed" + ], "sprite": "sprites/red_wiz2.atlas", "deck": [ "decks/chandra5.dck" @@ -2566,6 +2938,14 @@ }, { "name": "Chandra's Scorcher", + "questTags": [ + "Human", + "Pyromancer", + "Chandra", + "Wizard", + "Fire", + "IdentityRed" + ], "sprite": "sprites/red_wiz2.atlas", "deck": [ "decks/chandra6.dck" @@ -2662,6 +3042,17 @@ }, { "name": "Chicken", + "questTags": [ + "Farm", + "Animal", + "Bird", + "Prey", + "Domesticated", + "Small", + "IdentityWhite", + "IdentityGreen", + "IdentitySelesnya" + ], "sprite": "sprites/dungeon/chicken.atlas", "scale": 0.75, "deck": [ @@ -2697,6 +3088,12 @@ }, { "name": "ClayGolem", + "questTags": [ + "Golem", + "Construct", + "IdentityWhite", + "BiomeColorless" + ], "sprite": "sprites/golem_2.atlas", "deck": [ "decks/golem_good.json" @@ -2732,6 +3129,14 @@ }, { "name": "Cleric", + "questTags": [ + "Human", + "Religious", + "Wizard", + "Holy", + "IdentityWhite", + "BiomeWhite" + ], "sprite": "sprites/cleric.atlas", "deck": [ "decks/cleric.json" @@ -2805,6 +3210,14 @@ }, { "name": "Construct", + "questTags": [ + "Construct", + "Mechanical", + "IdentityRed", + "IdentityBlue", + "IdentityIzzet", + "BiomeColorless" + ], "sprite": "sprites/golem_3.atlas", "deck": [ "decks/artificer.dck", @@ -2865,6 +3278,11 @@ }, { "name": "Copper Host Brutalizer", + "questTags": [ + "Phyrexian", + "Copper Host", + "Humanoid" + ], "sprite": "sprites/copperhostbrutalizer.atlas", "deck": [ "deckscopperhostbrutalizer.json" @@ -2937,6 +3355,11 @@ }, { "name": "Copper Host Infector", + "questTags": [ + "Phyrexian", + "Copper Host", + "Humanoid" + ], "sprite": "sprites/copperhostinfector.atlas", "deck": [ "decks/copperhostinfector.json" @@ -3009,6 +3432,18 @@ }, { "name": "Crab", + "questTags": [ + "Animal", + "Prey", + "Swimming", + "Wild", + "Water", + "Tiny", + "IdentityBlue", + "IdentityBlack", + "IdentityDimir", + "BiomeBlue" + ], "sprite": "sprites/dungeon/crab.atlas", "scale": 0.9, "deck": [ @@ -3064,6 +3499,15 @@ }, { "name": "Curselord", + "questTags": [ + "Human", + "Wizard", + "IdentityRed", + "IdentityBlue", + "IdentityBlack", + "IdentityGrixis", + "BiomeBlack" + ], "sprite": "sprites/black_wiz2.atlas", "deck": [ "decks/curselord.dck" @@ -3112,6 +3556,15 @@ }, { "name": "Cyclops", + "questTags": [ + "Humanoid", + "Giant", + "Barbarian", + "Loner", + "Mythical", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/cyclops.atlas", "deck": [ "decks/cyclops.dck" @@ -3138,6 +3591,15 @@ }, { "name": "Dark Knight", + "questTags": [ + "Undead", + "Soldier", + "Knight", + "Unholy", + "Mounted", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/death_knight.atlas", "deck": [ "decks/death_knight.json" @@ -3211,6 +3673,14 @@ }, { "name": "Dawnhart Witch", + "questTags": [ + "Human", + "Mystic", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeWhite" + ], "sprite": "sprites/dawnhart_witch.atlas", "deck": [ "decks/dawnhart_witch.dck" @@ -3284,6 +3754,15 @@ }, { "name": "Death Knight", + "questTags": [ + "Undead", + "Soldier", + "Knight", + "Unholy", + "Mounted", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/death_knight_2.atlas", "deck": [ "decks/death_knight.dck" @@ -3357,6 +3836,13 @@ }, { "name": "Demon", + "questTags": [ + "Demon", + "Humanoid", + "Unholy", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/demon_3.atlas", "deck": [ "decks/demon.json" @@ -3430,6 +3916,14 @@ }, { "name": "Devil", + "questTags": [ + "Devil", + "Humanoid", + "Small", + "Thief", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/imp.atlas", "deck": [ "decks/devil.json" @@ -3476,6 +3970,20 @@ }, { "name": "Dino", + "questTags": [ + "Animal", + "Territorial", + "Aggressive", + "Dinosaur", + "Nesting", + "Predator", + "Wild", + "IdentityGreen", + "IdentityRed", + "IdentityWhite", + "IdentityNaya", + "BiomeGreen" + ], "sprite": "sprites/ancient.atlas", "deck": [ "decks/dinosaurs.json" @@ -3515,6 +4023,19 @@ }, { "name": "Dinosaur", + "questTags": [ + "Dinosaur", + "Animal", + "Territorial", + "Aggressive", + "Nesting", + "Predator", + "Wild", + "IdentityRed", + "IdentityWhite", + "IdentityBoros", + "BiomeRed" + ], "sprite": "sprites/ancient_2.atlas", "deck": [ "decks/dinosaur_w_r.dck" @@ -3554,6 +4075,16 @@ }, { "name": "Disciple of Teferi", + "questTags": [ + "Human", + "Wizard", + "Illusionist", + "Teferi", + "IdentityBlue", + "IdentityRed", + "IdentityWhite", + "Identity Jeskai" + ], "sprite": "sprites/monk.atlas", "deck": [ "decks/disciple_of_teferi.dck" @@ -3611,6 +4142,15 @@ }, { "name": "Djinn", + "questTags": [ + "Elemental", + "Flying", + "Humanoid", + "IdentityRed", + "IdentityBlue", + "IdentityIzzet", + "BiomeBlue" + ], "sprite": "sprites/djinn.atlas", "deck": [ "decks/djinn.json" @@ -3686,6 +4226,19 @@ }, { "name": "Dog", + "questTags": [ + "Predator", + "Prey", + "Animal", + "Domesticated", + "Wild", + "Scavenger", + "IdentityWhite", + "IdentityGreen", + "IdentitySelesnya", + "BiomeColorless", + "BiomeWhite" + ], "sprite": "sprites/dungeon/dog.atlas", "deck": [ "decks/dog.json", @@ -3740,6 +4293,13 @@ }, { "name": "Doppelganger", + "questTags": [ + "Aberration", + "Challenger", + "IdentityUnknown", + "BiomeColorless", + "BiomeBlue" + ], "sprite": "sprites/doppelganger.atlas", "deck": [ "decks/mimic.dck" @@ -3781,6 +4341,15 @@ }, { "name": "Dragon", + "questTags": [ + "Dragon", + "Flying", + "Territorial", + "Loner", + "Large", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/dragon.atlas", "deck": [ "decks/dragon.dck" @@ -3856,6 +4425,17 @@ }, { "name": "Dwarf", + "questTags": [ + "Dwarf", + "Territorial", + "Humanoid", + "Warrior", + "Subterranean", + "IdentityRed", + "IdentityWhite", + "IdentityBoros", + "BiomeRed" + ], "sprite": "sprites/dwarf_2.atlas", "deck": [ "decks/dwarf.json" @@ -3929,6 +4509,15 @@ }, { "name": "Efreet", + "questTags": [ + "Elemental", + "Flying", + "Humanoid", + "IdentityRed", + "IdentityBlue", + "IdentityIzzet", + "BiomeRed" + ], "sprite": "sprites/efreet_2.atlas", "deck": [ "decks/efreet.dck" @@ -3957,6 +4546,16 @@ }, { "name": "Eldraine Faerie", + "questTags": [ + "Mythical", + "Flying", + "Faerie", + "Eldraine", + "IdentityGreen", + "IdentityBlue", + "IdentitySimic", + "BiomeGreen" + ], "sprite": "sprites/pixie.atlas", "deck": [ "decks/eldraine_faerie.dck" @@ -4032,6 +4631,16 @@ }, { "name": "Eldraine Knight", + "questTags": [ + "Soldier", + "Human", + "Knight", + "Eldraine", + "IdentityRed", + "IdentityWhite", + "IdentityBoros", + "BiomeWhite" + ], "sprite": "sprites/paladin_2.atlas", "deck": [ "decks/eldraine_knight.dck" @@ -4105,6 +4714,17 @@ }, { "name": "Eldrazi", + "questTags": [ + "Aberration", + "Eldrazi", + "Wandering", + "Huge", + "IdentityBlack", + "IdentityGreen", + "IdentityBlue", + "IdentitySultai", + "BiomeColorless" + ], "sprite": "sprites/mindelemental.atlas", "deck": [ "decks/eldrazi.json" @@ -4180,6 +4800,12 @@ }, { "name": "Elemental", + "questTags": [ + "Elemental", + "Humanoid", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/crystalelemental.atlas", "deck": [ "decks/elemental_blue.json" @@ -4253,6 +4879,13 @@ }, { "name": "Elf", + "questTags": [ + "Elf", + "Humanoid", + "Tribal", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/druid.atlas", "deck": [ "decks/elf_bad.json" @@ -4290,6 +4923,13 @@ }, { "name": "Elf warrior", + "questTags": [ + "Elf", + "Humanoid", + "Tribal", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/hunter.atlas", "deck": [ "decks/elf_mid.json" @@ -4327,6 +4967,13 @@ }, { "name": "Elk", + "questTags": [ + "Animal", + "Wild", + "Prey", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/deer_2.atlas", "deck": [ "decks/elk.dck" @@ -4400,6 +5047,12 @@ }, { "name": "Emrakul", + "questTags": [ + "Huge", + "Eldrazi", + "Boss", + "IdentityColorless" + ], "sprite": "sprites/boss/emrakul.atlas", "deck": [ "decks/boss/emrakul.dck" @@ -4430,6 +5083,12 @@ }, { "name": "Eye", + "questTags": [ + "Aberration", + "Floating", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/dungeon/eye.atlas", "deck": [ "decks/eye.dck" @@ -4483,6 +5142,15 @@ }, { "name": "Faerie", + "questTags": [ + "Faerie", + "Mythical", + "IdentityBlack", + "IdentityGreen", + "IdentityBlue", + "IdentitySultai", + "BiomeGreen" + ], "sprite": "sprites/pixie_2.atlas", "deck": [ "decks/faerie.json" @@ -4558,6 +5226,14 @@ }, { "name": "Fire Elemental", + "questTags": [ + "FIre", + "Elemental", + "Humanoid", + "Flying", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/fireelemental.atlas", "deck": [ "decks/fire_elemental.dck" @@ -4633,6 +5309,13 @@ }, { "name": "Fire Giant", + "questTags": [ + "Fire", + "Giant", + "Warrior", + "Territorial", + "IdentityRed" + ], "sprite": "sprites/dungeon/firegiant.atlas", "deck": [ "decks/firegiant.dck" @@ -4679,6 +5362,13 @@ }, { "name": "Flame Elemental", + "questTags": [ + "Fire", + "Elemental", + "Humanoid", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/magmaelemental.atlas", "deck": [ "decks/flame_elemental.dck" @@ -4752,6 +5442,16 @@ }, { "name": "Fox", + "questTags": [ + "Animal", + "Wild", + "Prey", + "Thief", + "IdentityWhite", + "BiomeGreen", + "BiomeColorless", + "BiomeWhite" + ], "sprite": "sprites/dungeon/fox.atlas", "deck": [ "decks/fox.json" @@ -4805,6 +5505,18 @@ }, { "name": "Frog", + "questTags": [ + "Animal", + "Swimming", + "Prey", + "IdentityBlue", + "IdentityGreen", + "IdentityBlack", + "IdentitySultai", + "BiomeGreen", + "BiomeBlue", + "BiomeBlack" + ], "sprite": "sprites/dungeon/frog.atlas", "deck": [ "decks/frog.json" @@ -4857,6 +5569,17 @@ }, { "name": "Frost Titan", + "questTags": [ + "Ice", + "Giant", + "Warrior", + "Territorial", + "IdentityWhite", + "IdentityBlue", + "IdentityAzorius", + "BiomeWhite", + "BiomeBlue" + ], "sprite": "sprites/titan.atlas", "deck": [ "decks/frost_titan.dck" @@ -4932,6 +5655,15 @@ }, { "name": "Fungus", + "questTags": [ + "Fungus", + "Inhuman", + "Nature", + "Wild", + "Subterranean", + "IdentityGreen", + "BiomeBlack" + ], "sprite": "sprites/dungeon/fungus.atlas", "deck": [ "decks/fungus.json" @@ -4985,6 +5717,21 @@ }, { "name": "Gargoyle", + "questTags": [ + "Stone", + "Flying", + "Humanoid", + "Construct", + "Gargoyle", + "Passive", + "Territorial", + "Stone", + "IdentityRed", + "IdentityWhite", + "IdentityBlue", + "IdentityJeskai", + "BiomeColorless" + ], "sprite": "sprites/gargoyle.atlas", "deck": [ "decks/gargoyle.json" @@ -5060,6 +5807,18 @@ }, { "name": "Gargoyle 2", + "questTags": [ + "Stone", + "Flying", + "Humanoid", + "Construct", + "Gargoyle", + "Passive", + "Territorial", + "Stone", + "IdentityWhite", + "BiomeColorless" + ], "sprite": "sprites/gargoyle_2.atlas", "deck": [ "decks/gargoyle.dck" @@ -5135,6 +5894,15 @@ }, { "name": "Ghalta", + "questTags": [ + "Dinosaur", + "Huge", + "Boss", + "Nature", + "Territorial", + "IdentityGreen", + "Ghalta" + ], "sprite": "sprites/boss/ghalta.atlas", "deck": [ "decks/boss/ghalta.dck" @@ -5169,6 +5937,14 @@ }, { "name": "Garruk", + "questTags": [ + "Planeswalker", + "Garruk", + "Boss", + "IdentityGreen", + "IdentityBlack", + "IdentityGolgari" + ], "sprite": "sprites/dungeon/garruk.atlas", "deck": [ "decks/miniboss/garruk.dck" @@ -5238,6 +6014,13 @@ }, { "name": "Geist", + "questTags": [ + "Undead", + "Ghost", + "Flying", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/ghost.atlas", "deck": [ "decks/ghost_blue.dck" @@ -5313,6 +6096,14 @@ }, { "name": "Ghoul", + "questTags": [ + "Undead", + "Humanoid", + "IdentityBlack", + "IdentityBlue", + "IdentityDimir", + "BiomeBlack" + ], "sprite": "sprites/ghoul.atlas", "deck": [ "decks/ghoul.dck" @@ -5339,6 +6130,14 @@ }, { "name": "Ghost", + "questTags": [ + "Undead", + "Spirit", + "Flying", + "Ghost", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/ghost_2.atlas", "deck": [ "decks/ghost.json" @@ -5414,6 +6213,16 @@ }, { "name": "Giant Spider", + "questTags": [ + "Spider", + "Territorial", + "Predator", + "Ambush", + "Wild", + "Subterranean", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/spider_2.atlas", "deck": [ "decks/spider_token.dck" @@ -5440,6 +6249,15 @@ }, { "name": "Gitaxian Scientist", + "questTags": [ + "Phyrexian", + "Gitaxian", + "Humanoid", + "Scientist", + "IdentityBlue", + "IdentityGreen", + "IdentitySimic" + ], "sprite": "sprites/gitaxianscientist.atlas", "deck": [ "decks/gitaxianscientist.json" @@ -5512,6 +6330,12 @@ }, { "name": "Gitaxian Underling", + "questTags": [ + "Gitaxian", + "Phyrexian", + "Humanoid", + "IdentityBlue" + ], "sprite": "sprites/gitaxianunderling.atlas", "deck": [ "decks/gitaxianunderling.json" @@ -5584,6 +6408,15 @@ }, { "name": "Griselbrand", + "questTags": [ + "Demon", + "Huge", + "Flying", + "Unholy", + "Boss", + "IdentityBlack", + "Griselbrand" + ], "sprite": "sprites/boss/griselbrand.atlas", "deck": [ "decks/boss/griselbrand.dck" @@ -5620,6 +6453,15 @@ }, { "name": "Grolnok", + "questTags": [ + "Boss", + "Animal", + "IdentityBlack", + "IdentityBlue", + "IdentityGreen", + "Giant", + "IdentitySultai" + ], "sprite": "sprites/dungeon/frogboss.atlas", "scale": 0.50, "deck": [ @@ -5703,6 +6545,16 @@ }, { "name": "Gorilla", + "questTags": [ + "Humanoid", + "Simian", + "Territorial", + "Animal", + "Wild", + "IdentityGreen", + "IdentityRed", + "IdentityGruul" + ], "sprite": "sprites/dungeon/gorilla.atlas", "scale": 0.55, "deck": [ @@ -5803,6 +6655,19 @@ }, { "name": "Goblin", + "questTags": [ + "Goblin", + "Coward", + "Humanoid", + "Territorial", + "Aggressive", + "Tribal", + "Small", + "Thief", + "Scavenger", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/goblin.atlas", "deck": [ "decks/goblin_bad.json" @@ -5844,6 +6709,18 @@ }, { "name": "Goblin Artificer", + "questTags": [ + "Goblin", + "Coward", + "Humanoid", + "Tribal", + "Territorial", + "Scientist", + "Small", + "Artificer", + "Scavenger", + "IdentityRed" + ], "sprite": "sprites/goblin.atlas", "deck": [ "decks/goblin_artificer.dck" @@ -5894,6 +6771,20 @@ }, { "name": "Goblin Chief", + "questTags": [ + "Goblin", + "Leader", + "Warrior", + "Humanoid", + "Territorial", + "Aggressive", + "Tribal", + "Small", + "IdentityRed", + "IdentityBlack", + "IdentityRakdos", + "BiomeRed" + ], "sprite": "sprites/wolf_rider_2.atlas", "deck": [ "decks/goblin_good.json" @@ -5931,6 +6822,12 @@ }, { "name": "Goblin pack", + "questTags": [ + "Goblin", + "Horde", + "Aggressive", + "IdentityRed" + ], "sprite": "sprites/goblin_group.atlas", "deck": [ "decks/goblin_bad.json" @@ -5988,6 +6885,17 @@ }, { "name": "Goblin Warrior", + "questTags": [ + "Goblin", + "Warrior", + "Humanoid", + "Territorial", + "Aggressive", + "Tribal", + "Small", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/wolf_rider.atlas", "deck": [ "decks/goblin_mid.json" @@ -6025,6 +6933,14 @@ }, { "name": "Golem", + "questTags": [ + "Golem", + "Construct", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya", + "BiomeColorless" + ], "sprite": "sprites/golem.atlas", "deck": [ "decks/golem.json" @@ -6066,6 +6982,13 @@ }, { "name": "Golem that is Generous", + "questTags": [ + "Golem", + "Construct", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya" + ], "sprite": "sprites/golem.atlas", "deck": [ "decks/golem.json" @@ -6119,6 +7042,16 @@ }, { "name": "Gorgon", + "questTags": [ + "Humanoid", + "Mythical", + "Loner", + "Stone", + "IdentityBlack", + "IdentityGreen", + "IdentityGolgari", + "BiomeGreen" + ], "sprite": "sprites/gorgone.atlas", "deck": [ "decks/gorgon.dck" @@ -6192,6 +7125,16 @@ }, { "name": "Gorgon 2", + "questTags": [ + "Humanoid", + "Mythical", + "Loner", + "Stone", + "IdentityBlack", + "IdentityGreen", + "IdentityGolgari", + "BiomeGreen" + ], "sprite": "sprites/gorgonen.atlas", "deck": [ "decks/gorgon_2.dck" @@ -6265,6 +7208,15 @@ }, { "name": "Green Beast", + "questTags": [ + "Beast", + "Animal", + "Wild", + "Territorial", + "Nesting", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/basilisk.atlas", "deck": [ "decks/beast_green.json" @@ -6338,6 +7290,14 @@ }, { "name": "Green Wiz1", + "questTags": [ + "Nature", + "Wizard", + "Human", + "IdentityGreen", + "BiomeGreen", + "BiomeColorless" + ], "sprite": "sprites/green_wiz1.atlas", "deck": [ "decks/green_bad.json" @@ -6411,6 +7371,14 @@ }, { "name": "Green Wiz2", + "questTags": [ + "Nature", + "Wizard", + "Human", + "IdentityGreen", + "BiomeGreen", + "BiomeColorless" + ], "sprite": "sprites/green_wiz2.atlas", "deck": [ "decks/trample.dck" @@ -6484,6 +7452,14 @@ }, { "name": "Green Wiz3", + "questTags": [ + "Nature", + "Wizard", + "Human", + "IdentityGreen", + "BiomeGreen", + "BiomeColorless" + ], "sprite": "sprites/green_wiz3.atlas", "deck": [ "decks/ramp.dck" @@ -6557,6 +7533,17 @@ }, { "name": "Griffin", + "questTags": [ + "Bird", + "Predator", + "Wild", + "Nesting", + "Animal", + "Mythical", + "Flying", + "IdentityWhite", + "BiomeWhite" + ], "sprite": "sprites/griffin.atlas", "deck": [ "decks/griffin.json" @@ -6632,6 +7619,14 @@ }, { "name": "Harpy", + "questTags": [ + "Flying", + "Humanoid", + "Nesting", + "Thief", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/harpy.atlas", "deck": [ "decks/harpy.dck" @@ -6707,6 +7702,16 @@ }, { "name": "Harpy 2", + "questTags": [ + "Flying", + "Humanoid", + "Nesting", + "Thief", + "IdentityBlack", + "IdentityBlue", + "IdentityDimir", + "BiomeBlack" + ], "sprite": "sprites/harpy_2.atlas", "deck": [ "decks/harpy_2.dck" @@ -6782,6 +7787,18 @@ }, { "name": "Hellhound", + "questTags": [ + "Aberration", + "Animal", + "Predator", + "Unholy", + "Beast", + "Subterranean", + "IdentityBlack", + "IdentityRed", + "IdentityRakdos", + "BiomeRed" + ], "sprite": "sprites/hellhound_2.atlas", "deck": [ "decks/hellhound.dck" @@ -6855,6 +7872,16 @@ }, { "name": "High Elf", + "questTags": [ + "Elf", + "Nature", + "Wizard", + "Territorial", + "Mystic", + "IdentityGreen", + "IdentityBlack", + "BiomeGreen" + ], "sprite": "sprites/druid_2.atlas", "deck": [ "decks/elf_good.json" @@ -6892,6 +7919,17 @@ }, { "name": "High Vampire", + "questTags": [ + "Undead", + "Vampire", + "Unholy", + "Necromancer", + "Flying", + "Leader", + "Nocturnal", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/vampire_2.atlas", "deck": [ "decks/vampire.json" @@ -6967,6 +8005,15 @@ }, { "name": "Horror", + "questTags": [ + "Aberration", + "Inhuman", + "IdentityBlue", + "IdentityBlack", + "IdentityDimir", + "BiomeBlue", + "BiomeBlack" + ], "sprite": "sprites/leech_2.atlas", "deck": [ "decks/horror.dck" @@ -7041,6 +8088,15 @@ }, { "name": "Horse", + "questTags": [ + "Animal", + "Domesticated", + "Farm", + "Prey", + "IdentityGreen", + "IdentityWhite", + "IdentitySelesnya" + ], "sprite": "sprites/dungeon/horse.atlas", "deck": [ "decks/horse.dck" @@ -7088,6 +8144,15 @@ }, { "name": "Horseman", + "questTags": [ + "Human", + "Soldier", + "Mounted", + "IdentityBlue", + "IdentityWhite", + "IdentityAzorius", + "BiomeWhite" + ], "sprite": "sprites/cavalier_2.atlas", "deck": [ "decks/horsemanship.dck" @@ -7125,6 +8190,15 @@ }, { "name": "Human", + "questTags": [ + "Human", + "Warrior", + "IdentityWhite", + "IdentityBlack", + "IdentityRed", + "IdentityMardu", + "BiomeWhite" + ], "sprite": "sprites/pikeman.atlas", "deck": [ "decks/human_bad.json" @@ -7162,6 +8236,14 @@ }, { "name": "Human elite", + "questTags": [ + "Human", + "Soldier", + "Wandering", + "Leader", + "IdentityWhite", + "BiomeWhite" + ], "sprite": "sprites/legionite.atlas", "deck": [ "decks/human_good.json" @@ -7199,6 +8281,15 @@ }, { "name": "Human guard", + "questTags": [ + "Human", + "Territorial", + "Warrior", + "IdentityWhite", + "IdentityBlack", + "IdentityOrzhov", + "BiomeWhite" + ], "sprite": "sprites/swordsman.atlas", "deck": [ "decks/human_mid.json" @@ -7236,6 +8327,14 @@ }, { "name": "Hydra", + "questTags": [ + "Wurm", + "Mythical", + "Giant", + "Aberration", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/hydra.atlas", "deck": [ "decks/hydra.json" @@ -7273,6 +8372,14 @@ }, { "name": "Illusionist", + "questTags": [ + "Human", + "Wizard", + "Illusionist", + "Scientist", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/blue_wiz2.atlas", "deck": [ "decks/illusion.dck" @@ -7314,6 +8421,22 @@ }, { "name": "Insect", + "questTags": [ + "Insect", + "Animal", + "Wild", + "Nesting", + "Swarming", + "Flying", + "Tiny", + "IdentityWhite", + "IdentityBlack", + "IdentityGreen", + "IdentityAbzan", + "BiomeColorless", + "BiomeWhite", + "BiomeBlack" + ], "sprite": "sprites/dungeon/dragonfly.atlas", "deck": [ "decks/insect.dck" @@ -7358,6 +8481,16 @@ }, { "name": "Immersturm Demon", + "questTags": [ + "Demon", + "Humanoid", + "Unholy", + "Fire", + "IdentityBlack", + "IdentityRed", + "IdentityRakdos", + "BiomeRed" + ], "sprite": "sprites/devil_2.atlas", "deck": [ "decks/immersturm_demon.dck" @@ -7431,6 +8564,15 @@ }, { "name": "Jackal Warrior", + "questTags": [ + "Humanoid", + "Warrior", + "Aggressive", + "IdentityRed", + "IdentityGreen", + "IdentityGruul", + "BiomeRed" + ], "sprite": "sprites/dungeon/jackalwarrior.atlas", "scale": 0.5, "deck": [ @@ -7479,6 +8621,15 @@ }, { "name": "Jace", + "questTags": [ + "Boss", + "Jace", + "Planeswalker", + "Illusionist", + "IdentityBlue", + "IdentityWhite", + "IdentityAzorius" + ], "sprite": "sprites/dungeon/jace.atlas", "deck": [ "decks/miniboss/jace.dck" @@ -7543,6 +8694,14 @@ }, { "name": "Jellyfish", + "questTags": [ + "Swimming", + "Animal", + "Territorial", + "Water", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/dungeon/jellyfish.atlas", "deck": [ "decks/jellyfish.dck" @@ -7595,6 +8754,22 @@ }, { "name": "Kavu", + "questTags": [ + "Kavu", + "Animal", + "Beast", + "Wild", + "Nature", + "Nesting", + "Territorial", + "Tribal", + "IdentityGreen", + "IdentityWhite", + "IdentityRed", + "IdentityNaya", + "BiomeGreen", + "BiomeRed" + ], "sprite": "sprites/kavu.atlas", "deck": [ "decks/kavu.dck" @@ -7668,6 +8843,17 @@ }, { "name": "Khan", + "questTags": [ + "Mounted", + "Human", + "Warrior", + "Aggressive", + "IdentityBlack", + "IdentityRed", + "IdentityWhite", + "IdentityMardu", + "BiomeRed" + ], "sprite": "sprites/cavalier.atlas", "deck": [ "decks/mardu.dck" @@ -7741,6 +8927,18 @@ }, { "name": "Kiora", + "questTags": [ + "Planeswalker", + "Kiora", + "Boss", + "Merfolk", + "Wizard", + "Swimming", + "Water", + "IdentityGreen", + "IdentityBlue", + "IdentitySimic" + ], "sprite": "sprites/dungeon/kiora.atlas", "scale": 0.35, "deck": [ @@ -7824,6 +9022,13 @@ }, { "name": "Knight", + "questTags": [ + "Human", + "Knight", + "Warrior", + "IdentityWhite", + "BiomeWhite" + ], "sprite": "sprites/paladin.atlas", "deck": [ "decks/knight.json" @@ -7861,6 +9066,18 @@ }, { "name": "Kobold", + "questTags": [ + "Kobold", + "Coward", + "Sneaky", + "Small", + "Humanoid", + "Thief", + "IdentityRed", + "IdentityBlack", + "IdentityRakdos", + "BiomeRed" + ], "sprite": "sprites/dungeon/kobold.atlas", "deck": [ "decks/kobold.dck" @@ -7914,6 +9131,16 @@ }, { "name": "Kor Warrior", + "questTags": [ + "Humanoid", + "Warrior", + "Kor", + "Tribal", + "IdentityRed", + "IdentityWhite", + "IdentityBoros", + "BiomeWhite" + ], "sprite": "sprites/mineguard.atlas", "deck": [ "decks/kor_warrior.dck" @@ -7960,6 +9187,14 @@ }, { "name": "Lich", + "questTags": [ + "Lich", + "Undead", + "Necromancer", + "Unholy", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/lich_2.atlas", "deck": [ "decks/lich.dck" @@ -8033,6 +9268,14 @@ }, { "name": "Lathliss", + "questTags": [ + "IdentityRed", + "Flying", + "Dragon", + "Huge", + "Boss", + "Lathliss" + ], "sprite": "sprites/boss/lathiss.atlas", "deck": [ "decks/boss/lathliss.dck" @@ -8069,6 +9312,12 @@ }, { "name": "Lightning Elemental", + "questTags": [ + "Elemental", + "Lightning", + "Inhuman", + "IdentityRed" + ], "sprite": "sprites/lightningelemental.atlas", "deck": [ "decks/lightning_elemental.dck" @@ -8165,6 +9414,13 @@ }, { "name": "Lorthos", + "questTags": [ + "Boss", + "Water", + "Octopus", + "IdentityBlue", + "Lorthos" + ], "sprite": "sprites/boss/lorthos.atlas", "deck": [ "decks/boss/lorthos.dck" @@ -8199,6 +9455,13 @@ }, { "name": "Magma Elemental", + "questTags": [ + "Humanoid", + "Fire", + "Elemental", + "Subterranean", + "IdentityRed" + ], "sprite": "sprites/magmaelemental.atlas", "deck": [ "decks/magma_elemental.dck" @@ -8294,6 +9557,16 @@ "colors": "R" },{ "name": "Merfolk", + "questTags": [ + "Merfolk", + "Humanoid", + "Swimming", + "Tribal", + "Territorial", + "Water", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/waterelemental.atlas", "deck": [ "decks/merfolk_bad.json" @@ -8331,6 +9604,17 @@ }, { "name": "Merfolk Avatar", + "questTags": [ + "Elemental", + "Merfolk", + "Tribal", + "Water", + "Humanoid", + "IdentityGreen", + "IdentityBlue", + "IdentitySimic", + "BiomeBlue" + ], "sprite": "sprites/iceelemental.atlas", "deck": [ "decks/merfolk_good.json" @@ -8369,7 +9653,17 @@ "colors": "GU" }, { - "name": "Merfolk Fighter", + "name": "Merfolk Fighter", "questTags": [ + "Merfolk", + "Humanoid", + "Swimming", + "Territorial", + "Tribal", + "IdentityGreen", + "IdentityBlue", + "IdentitySimic", + "BiomeBlue" + ], "sprite": "sprites/merfolk.atlas", "deck": [ "decks/merfolk_lords.dck" @@ -8417,6 +9711,19 @@ }, { "name": "Merfolk Lord", + "questTags": [ + "Merfolk", + "Humanoid", + "Tribal", + "Territorial", + "Water", + "Swimming", + "Leader", + "IdentityGreen", + "IdentityBlue", + "IdentitySimic", + "BiomeBlue" + ], "sprite": "sprites/merfolk_lord.atlas", "deck": [ "decks/merfolk_lord2.dck" @@ -8464,6 +9771,17 @@ }, { "name": "Merfolk Soldier", + "questTags": [ + "Merfolk", + "Humanoid", + "Tribal", + "Water", + "Swimming", + "Soldier", + "Territorial", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/mermaid.atlas", "deck": [ "decks/merfolk_v_goblins.dck" @@ -8501,6 +9819,17 @@ }, { "name": "Merfolk warrior", + "questTags": [ + "Merfolk", + "Humanoid", + "Water", + "Swimming", + "Territorial", + "Tribal", + "Warrior", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/airelemental.atlas", "deck": [ "decks/merfolk_mid.json" @@ -8540,6 +9869,16 @@ }, { "name": "Mimic", + "questTags": [ + "Mimic", + "Sneaky", + "Ambush", + "Predator", + "Inhuman", + "IdentityBlack", + "IdentityBlue", + "IdentityDimir" + ], "sprite": "sprites/mimic.atlas", "deck": [ "decks/mimic.dck" @@ -8613,6 +9952,19 @@ }, { "name": "Mindclaw Shaman", + "questTags": [ + "Viashino", + "Humanoid", + "Shaman", + "Tribal", + "Wizard", + "Leader", + "IdentityRed", + "IdentityBlack", + "IdentityRakdos", + "BiomeRed", + "BiomeBlack" + ], "sprite": "sprites/dreamwalker.atlas", "deck": [ "decks/mindclaw_shaman.dck" @@ -8687,6 +10039,15 @@ }, { "name": "Minotaur", + "questTags": [ + "Minotaur", + "Humanoid", + "Aggressive", + "Tribal", + "Territorial", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/minotaur.atlas", "deck": [ "decks/minotaur.json" @@ -8760,6 +10121,18 @@ }, { "name": "Minotaur Flayer", + "questTags": [ + "Territorial", + "Tribal", + "Minotaur", + "Warrior", + "Aggressive", + "Humanoid", + "IdentityBlack", + "IdentityRed", + "IdentityRakdos", + "BiomeRed" + ], "sprite": "sprites/warden_2.atlas", "deck": [ "decks/minotaur.dck" @@ -8833,6 +10206,13 @@ }, { "name": "Monk", + "questTags": [ + "Human", + "Mystic", + "Passive", + "IdentityWhite", + "BiomeWhite" + ], "sprite": "sprites/monk.atlas", "deck": [ "decks/angel.json", @@ -8907,7 +10287,17 @@ "colors": "W" }, { - "name": "Mummy", + "name": "Mummy","questTags": [ + "Undead", + "Humanoid", + "Loner", + "Territorial", + "IdentityWhite", + "IdentityBlack", + "IdentityOrzhov", + "BiomeColorless", + "BiomeBlack" + ], "sprite": "sprites/dungeon/mummy.atlas", "scale": 0.70, "deck": [ @@ -8956,6 +10346,15 @@ }, { "name": "Nahiri", + "questTags": [ + "Planeswalker", + "Kor", + "Stone", + "Boss", + "IdentityRed", + "IdentityWhite", + "IdentityBoros" + ], "sprite": "sprites/dungeon/nahiri.atlas", "deck": [ "decks/miniboss/nahiri.dck" @@ -9019,6 +10418,13 @@ }, { "name": "Necromancer", + "questTags": [ + "Necromancer", + "Unholy", + "Wizard", + "Human", + "IdentityBlack" + ], "sprite": "sprites/black_wiz2.atlas", "deck": [ "decks/reanimator.dck" @@ -9067,6 +10473,17 @@ }, { "name": "Octopus", + "questTags": [ + "Water", + "Swimming", + "Predator", + "Ambush", + "Beast", + "Wild", + "Octopus", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/dungeon/octopus.atlas", "deck": [ "decks/octopus.dck" @@ -9119,6 +10536,20 @@ }, { "name": "Ooze", + "questTags": [ + "Aberration", + "Ambush", + "Nature", + "Nesting", + "Predator", + "Ooze", + "Subterranean", + "IdentityBlue", + "IdentityGreen", + "IdentitySimic", + "BiomeGreen", + "BiomeColorless" + ], "sprite": "sprites/dungeon/ooze.atlas", "scale": 0.6, "deck": [ @@ -9172,6 +10603,18 @@ }, { "name": "Ooze Boss", + "questTags": [ + "Leader", + "Ooze", + "Aberration", + "Ambush", + "Nature", + "Nesting", + "Subterranean", + "IdentityGreen", + "IdentityBlack", + "IdentityGolgari" + ], "sprite": "sprites/dungeon/oozeboss.atlas", "scale": 0.5, "deck": [ @@ -9248,6 +10691,23 @@ }, { "name": "Owl", + "questTags": [ + "Bird", + "Animal", + "Domesticated", + "Flying", + "Nature", + "Wild", + "Nocturnal", + "Small", + "Predator", + "IdentityBlack", + "IdentityBlue", + "IdentityDimir", + "BiomeGreen", + "BiomeWhite", + "BiomeBlue" + ], "sprite": "sprites/dungeon/owl.atlas", "deck": [ "decks/owl.dck" @@ -9299,7 +10759,19 @@ ] }, { - "name": "Pirate", + "name": "Pirate", "questTags": [ + "Aggressive", + "Human", + "Warrior", + "Water", + "Bandit", + "Thief", + "IdentityBlue", + "IdentityRed", + "IdentityIzzet", + "BiomeColorless", + "BiomeBlue" + ], "sprite": "sprites/pirate.atlas", "deck": [ "decks/pirate.dck" @@ -9368,6 +10840,14 @@ }, { "name": "Plant", + "questTags": [ + "Nature", + "Territorial", + "Inhuman", + "Prey", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/dungeon/plant.atlas", "deck": [ "decks/plant.json", @@ -9421,6 +10901,13 @@ }, { "name": "Orthodoxy Angel", + "questTags": [ + "Phyrexian", + "Angel", + "IdentityWhite", + "IdentityGreen", + "IdentitySelesnya" + ], "sprite": "sprites/phyrexianangel.atlas", "deck": [ "decks/phyrexianangel.json" @@ -9493,6 +10980,13 @@ }, { "name": "Orthodoxy Duelist", + "questTags": [ + "Phyrexian", + "Warrior", + "IdentityWhite", + "IdentityBlack", + "IdentityOrzhov" + ], "sprite": "sprites/phyrexianduelist.atlas", "deck": [ "decks/phyrexianduelist.json" @@ -9565,6 +11059,15 @@ }, { "name": "Phoenix", + "questTags": [ + "Mythical", + "Fire", + "Bird", + "Flying", + "Nesting", + "Loner", + "IdentityRed" + ], "sprite": "sprites/phoenix.atlas", "deck": [ "decks/phoenix.dck" @@ -9661,6 +11164,19 @@ }, { "name": "Rakdos Devil", + "questTags": [ + "Devil", + "Fire", + "Unholy", + "Sneaky", + "Humanoid", + "Thief", + "Subterranean", + "IdentityRed", + "IdentityBlack", + "IdentityRakdos", + "BiomeBlack" + ], "sprite": "sprites/juggler.atlas", "deck": [ "decks/rakdos_devil.dck" @@ -9687,6 +11203,18 @@ }, { "name": "Rat Ninja", + "questTags": [ + "Humanoid", + "Ninja", + "Bandit", + "Rat", + "Sneaky", + "Thief", + "Subterranean", + "Scavenger", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/dungeon/humanoidrat.atlas", "deck": [ "decks/humanoidrat.dck" @@ -9739,6 +11267,19 @@ }, { "name": "Raven", + "questTags": [ + "Bird", + "Flying", + "Animal", + "Wild", + "Domesticated", + "Scavenger", + "IdentityBlack", + "IdentityBlue", + "IdentityDimir", + "BiomeColorless", + "BiomeWhite" + ], "sprite": "sprites/dungeon/raven.atlas", "deck": [ "decks/raven.dck" @@ -9791,6 +11332,15 @@ }, { "name": "Red Beast", + "questTags": [ + "Beast", + "Animal", + "Wild", + "Territorial", + "Nesting", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/basilisk_2.atlas", "deck": [ "decks/beast_red.json" @@ -9864,6 +11414,13 @@ }, { "name": "Reassembling Skeleton", + "questTags": [ + "Undead", + "Skeleton", + "Regenerating", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/skeleton.atlas", "deck": [ "decks/reassemblingskeleton.dck" @@ -9889,13 +11446,18 @@ }, { "name": "Red Wiz1", + "questTags": [ + "Wizard", + "Human", + "Fire", + "Pyromancer", + "IdentityRed", + "BiomeRed", + "BiomeColorless" + ], "sprite": "sprites/enchanter.atlas", "deck": [ - "decks/red_bad.json", - "decks/red_bad[1].json", - "decks/red_bad[2].json", - "decks/red_bad[3].json", - "decks/red_bad[4].json" + "decks/red_bad.json" ], "spawnRate": 1, "difficulty": 0.1, @@ -9966,6 +11528,15 @@ }, { "name": "Red Wiz2", + "questTags": [ + "Wizard", + "Human", + "Fire", + "Pyromancer", + "IdentityRed", + "BiomeRed", + "BiomeColorless" + ], "sprite": "sprites/red_wiz2.atlas", "deck": [ "decks/haste_burn.dck" @@ -9991,6 +11562,15 @@ }, { "name": "Red Wiz3", + "questTags": [ + "Wizard", + "Human", + "Fire", + "Pyromancer", + "IdentityRed", + "BiomeRed", + "BiomeColorless" + ], "sprite": "sprites/red_wiz3.atlas", "deck": [ "decks/lava_axe.dck" @@ -10017,6 +11597,17 @@ }, { "name": "Rogue", + "questTags": [ + "Bandit", + "Human", + "Sneaky", + "Wandering", + "Thief", + "IdentityBlack", + "IdentityBlue", + "IdentityDimir", + "BiomeBlue" + ], "sprite": "sprites/rogue.atlas", "deck": [ "decks/rogue.json" @@ -10040,6 +11631,16 @@ }, { "name": "Satyr", + "questTags": [ + "Humanoid", + "Mythical", + "Sneaky", + "Thief", + "IdentityGreen", + "IdentityRed", + "IdentityGruul", + "BiomeGreen" + ], "sprite": "sprites/satyr.atlas", "deck": [ "decks/satyr.dck" @@ -10113,6 +11714,13 @@ }, { "name": "Scarecrow", + "questTags": [ + "Construct", + "Nature", + "Territorial", + "IdentityBlue", + "BiomeColorless" + ], "sprite": "sprites/dungeon/scarecrow.atlas", "deck": [ "decks/scarecrow.dck" @@ -10165,6 +11773,13 @@ }, { "name": "Scarecrow Captain", + "questTags": [ + "Construct", + "Nature", + "Territorial", + "Leader", + "IdentityAll" + ], "sprite": "sprites/dungeon/scarecrowcaptain.atlas", "deck": [ "decks/scarecrowcaptain.dck" @@ -10232,6 +11847,20 @@ }, { "name": "Scorpion", + "questTags": [ + "Tiny", + "Insect", + "Nature", + "Territorial", + "Predator", + "Ambush", + "IdentityBlack", + "IdentityGreen", + "IdentityGolgari", + "BiomeRed", + "BiomeWhite", + "BiomeBlue" + ], "sprite": "sprites/dungeon/scorpion.atlas", "scale": 0.9, "deck": [ @@ -10280,6 +11909,13 @@ }, { "name": "Sea Monster", + "questTags": [ + "Water", + "Swimming", + "Inhuman", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/leech_2.atlas", "deck": [ "decks/sea_monster.dck" @@ -10353,6 +11989,16 @@ }, { "name": "Shaman", + "questTags": [ + "Shaman", + "Nature", + "Wizard", + "Mystic", + "IdentityGreen", + "IdentityRed", + "IdentityGruul", + "BiomeRed" + ], "sprite": "sprites/shaman_2.atlas", "deck": [ "decks/shaman.json" @@ -10426,6 +12072,13 @@ }, { "name": "Skeleton", + "questTags": [ + "Undead", + "Skeleton", + "Regenerating", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/skeleton.atlas", "deck": [ "decks/skeleton.dck" @@ -10451,6 +12104,14 @@ }, { "name": "Skeleton Soldier", + "questTags": [ + "Undead", + "Skeleton", + "Soldier", + "Regenerating", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/skeleton_2.atlas", "deck": [ "decks/skeleton_2.dck" @@ -10476,6 +12137,15 @@ }, { "name": "Slimefoot", + "questTags": [ + "Fungus", + "Nature", + "Boss", + "Inhuman", + "IdentityBlack", + "IdentityGreen", + "IdentityGolgari" + ], "sprite": "sprites/dungeon/slimefoot.atlas", "scale": 0.5, "deck": [ @@ -10553,6 +12223,18 @@ }, { "name": "Sliver", + "questTags": [ + "Tribal", + "Inhuman", + "Territorial", + "Subterranean", + "Nesting", + "Swarm", + "IdentityGreen", + "IdentityRed", + "IdentityWhite", + "BiomeColorless" + ], "sprite": "sprites/sliver.atlas", "deck": [ "decks/sliver.json" @@ -10575,6 +12257,15 @@ }, { "name": "Slobad", + "questTags": [ + "Goblin", + "Artificer", + "Wandering", + "Tribal", + "Boss", + "Loner", + "IdentityRed" + ], "sprite": "sprites/dungeon/slobad.atlas", "scale": 0.75, "deck": [ @@ -10651,6 +12342,15 @@ }, { "name": "Sliver Queen", + "questTags": [ + "Sliver", + "Boss", + "Leader", + "Inhuman", + "Territorial", + "Nesting", + "IdentityAll" + ], "sprite": "sprites/boss/sliver_queen.atlas", "deck": [ "decks/boss/sliver_queen.dck" @@ -10685,6 +12385,16 @@ }, { "name": "Snake", + "questTags": [ + "Animal", + "Wild", + "Nature", + "Territorial", + "Predator", + "IdentityGreen", + "IdentityBlue", + "IdentitySimic" + ], "sprite": "sprites/big_snake.atlas", "deck": [ "decks/snake.json" @@ -10758,6 +12468,17 @@ }, { "name": "Spider", + "questTags": [ + "Spider", + "Predator", + "Ambush", + "Territorial", + "Wild", + "IdentityBlack", + "IdentityGreen", + "IdentityGolgari", + "BiomeGreen" + ], "sprite": "sprites/spider.atlas", "deck": [ "decks/spider.json" @@ -10831,6 +12552,14 @@ }, { "name": "Stray Cat", + "questTags": [ + "Cat", + "Animal", + "Domesticated", + "Prey", + "Small", + "IdentityWhite" + ], "sprite": "sprites/dungeon/cat.atlas", "deck": [ "decks/straycat.dck" @@ -10882,6 +12611,19 @@ }, { "name": "Squirrel", + "questTags": [ + "Animal", + "Sneaky", + "Tiny", + "Prey", + "Nature", + "Territorial", + "Nesting", + "IdentityGreen", + "IdentityBlack", + "IdentityGolgari", + "BiomeGreen" + ], "sprite": "sprites/dungeon/squirrel.atlas", "deck": [ "decks/squirrel.dck" @@ -10913,6 +12655,17 @@ }, { "name": "Symbiote", + "questTags": [ + "Inhuman", + "Nature", + "Wild", + "Predator", + "Beast", + "IdentityBlack", + "IdentityBlue", + "IdentityGreen", + "IdentitySultai" + ], "sprite": "sprites/basilisk_3.atlas", "deck": [ "decks/symbiote.dck" @@ -10987,6 +12740,16 @@ }, { "name": "Tarkir Djinn", + "questTags": [ + "Flying", + "Humanoid", + "Elemental", + "Wind", + "IdentityBlue", + "IdentityRed", + "IdentityIzzet", + "BiomeBlue" + ], "sprite": "sprites/djinn_2.atlas", "deck": [ "decks/djinn_tarkir.dck" @@ -11015,6 +12778,17 @@ }, { "name": "Teferi", + "questTags": [ + "Boss", + "Illusionist", + "Human", + "Wizard", + "Territorial", + "Planeswalker", + "IdentityBlue", + "IdentityWhite", + "IdentityAzorius" + ], "sprite": "sprites/dungeon/teferi.atlas", "scale": 0.5, "deck": [ @@ -11080,6 +12854,13 @@ }, { "name": "Tibalt", + "questTags": [ + "Sneaky", + "Devil", + "Boss", + "Planeswalker", + "IdentityRed" + ], "sprite": "sprites/dungeon/tibalt.atlas", "scale": 0.50, "deck": [ @@ -11163,6 +12944,13 @@ }, { "name": "Torturer", + "questTags": [ + "Inhuman", + "Devil", + "Flying", + "Aggressive", + "IdentityBlack" + ], "sprite": "sprites/dungeon/torturer.atlas", "deck": [ "decks/torturer.dck" @@ -11212,6 +13000,18 @@ }, { "name": "Turtle", + "questTags": [ + "Passive", + "Territorial", + "Nature", + "Animal", + "Wild", + "Swimming", + "Water", + "Prey", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/dungeon/turtle.atlas", "deck": [ "decks/turtle.json" @@ -11265,6 +13065,15 @@ }, { "name": "Treefolk", + "questTags": [ + "Humanoid", + "Territorial", + "Nature", + "Passive", + "Giant", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/treant.atlas", "deck": [ "decks/treefolk.json" @@ -11311,6 +13120,15 @@ }, { "name": "Treefolk Guardian", + "questTags": [ + "Humanoid", + "Nature", + "Giant", + "Warrior", + "Territorial", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/treant_2.atlas", "deck": [ "decks/treefolk.dck" @@ -11384,6 +13202,15 @@ }, { "name": "Troll", + "questTags": [ + "Giant", + "Humanoid", + "Regenerating", + "Subterranean", + "IdentityGreen", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/troll.atlas", "deck": [ "decks/troll.json" @@ -11457,6 +13284,17 @@ }, { "name": "Vampire", + "questTags": [ + "Vampire", + "Undead", + "Unholy", + "Flying", + "Humanoid", + "Predator", + "Nocturnal", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/vampire.atlas", "deck": [ "decks/vampire.dck" @@ -11478,6 +13316,18 @@ }, { "name": "Vampire Lord", + "questTags": [ + "Vampire", + "Unholy", + "Undead", + "Flying", + "Predator", + "Leader", + "Nocturnal", + "IdentityBlack", + "IdentityRed", + "BiomeRed" + ], "sprite": "sprites/vampire_3.atlas", "deck": [ "decks/vampire_blood_token_fly.dck" @@ -11499,6 +13349,16 @@ }, { "name": "Viashino", + "questTags": [ + "Viashino", + "Tribal", + "Humanoid", + "IdentityBlack", + "IdentityGreen", + "IdentityRed", + "IdentityJund", + "BiomeRed" + ], "sprite": "sprites/battler.atlas", "deck": [ "decks/viashino.dck" @@ -11572,6 +13432,19 @@ }, { "name": "Viper", + "questTags": [ + "Animal", + "Snake", + "Wild", + "Territorial", + "Ambush", + "Predator", + "Small", + "IdentityBlack", + "IdentityGreen", + "IdentityGolgari", + "BiomeGreen" + ], "sprite": "sprites/big_snake_2.atlas", "deck": [ "decks/snake.dck" @@ -11644,6 +13517,15 @@ "colors": "BG" },{ "name": "Volcano Elemental", + "questTags": [ + "Fire", + "Elemental", + "Inhuman", + "Subterranean", + "IdentityRed", + "IdentityBlack", + "IdentityRakdos" + ], "sprite": "sprites/volcanoelemental.atlas", "deck": [ "decks/br_elemental.dck" @@ -11739,6 +13621,16 @@ }, { "name": "Walking Brain", + "questTags": [ + "Aberration", + "Sneaky", + "Construct", + "Leader", + "Small", + "IdentityBlue", + "BiomeBlue", + "BiomeBlack" + ], "sprite": "sprites/dungeon/walkingbrain.atlas", "deck": [ "decks/walkingbrain.dck" @@ -11790,6 +13682,18 @@ }, { "name": "Wasp", + "questTags": [ + "Tiny", + "Flying", + "Insect", + "Territorial", + "Aggressive", + "Swarm", + "IdentityGreen", + "IdentityRed", + "IdentityBlack", + "IdentityJund" + ], "sprite": "sprites/dungeon/wasp.atlas", "deck": [ "decks/insect.dck" @@ -11838,6 +13742,21 @@ }, { "name": "Werewolf", + "questTags": [ + "Nocturnal", + "Animal", + "Humanoid", + "Human", + "Beast", + "Wild", + "Loner", + "Aggressive", + "Territorial", + "IdentityGreen", + "IdentityRed", + "IdentityGruul", + "BiomeGreen" + ], "sprite": "sprites/hellhound.atlas", "deck": [ "decks/werewolf.dck" @@ -11863,6 +13782,14 @@ }, { "name": "White Dwarf", + "questTags": [ + "Dwarf", + "Warrior", + "Humanoid", + "Subterranean", + "IdentityWhite", + "BiomeWhite" + ], "sprite": "sprites/dwarf_6.atlas", "deck": [ "decks/white_dwarf.dck" @@ -11889,6 +13816,15 @@ }, { "name": "White Wiz1", + "questTags": [ + "Holy", + "Wizard", + "Human", + "Religious", + "IdentityWhite", + "BiomeColorless", + "BiomeWhite" + ], "sprite": "sprites/priest.atlas", "deck": [ "decks/white_bad.json" @@ -11962,6 +13898,16 @@ }, { "name": "White Wiz2", + "questTags": [ + "Holy", + "Wizard", + "Human", + "Religious", + "Passive", + "IdentityWhite", + "BiomeColorless", + "BiomeWhite" + ], "sprite": "sprites/white_wiz2.atlas", "deck": [ "decks/basri.dck" @@ -12035,6 +13981,14 @@ }, { "name": "White Wiz3", + "questTags": [ + "Holy", + "Wizard", + "Human", + "IdentityWhite", + "BiomeColorless", + "BiomeWhite" + ], "sprite": "sprites/white_wiz3.atlas", "deck": [ "decks/human_soldier_token.dck" @@ -12108,6 +14062,18 @@ }, { "name": "Wild Rat", + "questTags": [ + "Subterranean", + "Animal", + "Wild", + "Sneaky", + "Small", + "Scavenger", + "Swarm", + "IdentityBlack", + "BiomeColorless", + "BiomeBlack" + ], "sprite": "sprites/dungeon/wildrat.atlas", "deck": [ "decks/infectrat.dck", @@ -12163,6 +14129,17 @@ }, { "name": "Wild-Magic Sorcerer", + "questTags": [ + "Viashino", + "Leader", + "Wizard", + "Humanoid", + "IdentityRed", + "IdentityGreen", + "IdentityGruul", + "BiomeGreen", + "BiomeRed" + ], "sprite": "sprites/dreamwalker_2.atlas", "deck": [ "decks/wild-magic_sorcerer.dck" @@ -12237,6 +14214,13 @@ }, { "name": "Wurm", + "questTags": [ + "Wurm", + "Nature", + "Subterranean", + "IdentityGreen", + "BiomeGreen" + ], "sprite": "sprites/wurm.atlas", "deck": [ "decks/wurm.json" @@ -12275,6 +14259,13 @@ }, { "name": "Vulture", + "questTags": [ + "Scavenger", + "Bird", + "Wild", + "Flying", + "IdentityBlack" + ], "sprite": "sprites/dungeon/vulture.atlas", "deck": [ "decks/vulture.dck" @@ -12320,6 +14311,13 @@ }, { "name": "Water Elemental", + "questTags": [ + "Water", + "Elemental", + "Humanoid", + "IdentityBlue", + "BiomeBlue" + ], "sprite": "sprites/waterelemental.atlas", "deck": [ "decks/water_elemental.dck" @@ -12357,6 +14355,17 @@ }, { "name": "Wounded Sliver", + "questTags": [ + "Sliver", + "Boss", + "Inhuman", + "Territorial", + "Nesting", + "IdentityGreen", + "IdentityRed", + "IdentityWhite", + "IdentityNaya" + ], "sprite": "sprites/boss/wsliver.atlas", "deck": [ "decks/miniboss/sliver_shandalar.dck" @@ -12381,6 +14390,15 @@ }, { "name": "Xira", + "questTags": [ + "Insect", + "Boss", + "Flying", + "IdentityBlack", + "IdentityGreen", + "IdentityRed", + "IdentityJund" + ], "sprite": "sprites/dungeon/xira.atlas", "scale": 0.50, "deck": [ @@ -12464,6 +14482,16 @@ }, { "name": "Yeti", + "questTags": [ + "Loner", + "Giant", + "Humanoid", + "IdentityRed", + "IdentityGreen", + "IdentityBlue", + "IdentityTemur", + "BiomeRed" + ], "sprite": "sprites/yeti_2.atlas", "deck": [ "decks/yeti.dck" @@ -12537,6 +14565,13 @@ }, { "name": "Zombie", + "questTags": [ + "Undead", + "Zombie", + "Humanoid", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/zombie.atlas", "deck": [ "decks/zombie_bad.json" @@ -12574,6 +14609,14 @@ }, { "name": "Zombie Lord", + "questTags": [ + "Undead", + "Zombie", + "Humanoid", + "Leader", + "IdentityBlack", + "BiomeBlack" + ], "sprite": "sprites/lich.atlas", "deck": [ "decks/zombie_good.json" diff --git a/forge-gui/res/adventure/Shandalar/world/items.json b/forge-gui/res/adventure/Shandalar/world/items.json index fabb01a8265..93040574945 100644 --- a/forge-gui/res/adventure/Shandalar/world/items.json +++ b/forge-gui/res/adventure/Shandalar/world/items.json @@ -434,9 +434,9 @@ "equipmentSlot": "Left", "iconName": "FlameSword", "effect": { - "opponent": { - "lifeModifier": -5 - } + "startBattleWithCardInCommandZone": [ + "Flame Sword" + ] } }, { diff --git a/forge-gui/res/adventure/Shandalar/world/points_of_interest.json b/forge-gui/res/adventure/Shandalar/world/points_of_interest.json index c11a4199665..ae8c0e16cd1 100644 --- a/forge-gui/res/adventure/Shandalar/world/points_of_interest.json +++ b/forge-gui/res/adventure/Shandalar/world/points_of_interest.json @@ -6,7 +6,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Aerie", "map": "maps/map/aerie_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Nest", + "Dungeon", + "Sidequest" + ] }, { "name": "BarbarianCamp", @@ -15,7 +21,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "BarbarianCamp", "map": "maps/map/barbariancamp_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "BarbarianCamp", + "BiomeRed", + "Sidequest" + ] }, { "name": "BarbarianCamp1", @@ -24,7 +36,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "BarbarianCamp", "map": "maps/map/barbariancamp_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "BarbarianCamp", + "BiomeRed", + "Sidequest" + ] }, { "name": "BarbarianCamp2", @@ -33,7 +51,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "BarbarianCamp", "map": "maps/map/barbariancamp_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "BarbarianCamp", + "BiomeRed", + "Sidequest" + ] }, { "name": "Black Castle", @@ -44,7 +68,14 @@ "map": "maps/map/main_story/black_castle.tmx", "radiusFactor": 0.01, "offsetX": 0.1, - "offsetY": -0.1 + "offsetY": -0.1, + "questTags": [ + "Story", + "Boss", + "RespawnEnemies", + "DungeonEffect", + "Castle" + ] }, { "name": "Blue Castle", @@ -55,7 +86,14 @@ "map": "maps/map/main_story/blue_castle.tmx", "radiusFactor": 0.01, "offsetX": 0.1, - "offsetY": 0.1 + "offsetY": 0.1, + "questTags": [ + "Story", + "Boss", + "RespawnEnemies", + "DungeonEffect", + "Castle" + ] }, { "name": "Castle", @@ -64,7 +102,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Castle", "map": "maps/map/castle_plains_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Castle", + "BiomeWhite", + "Sidequest" + ] }, { "name": "Castle1", @@ -73,7 +118,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Castle", "map": "maps/map/castle_plains_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Castle", + "BiomeWhite", + "Sidequest" + ] }, { "name": "Castle2", @@ -82,7 +134,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Castle", "map": "maps/map/castle_plains_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Castle", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CatLairG", @@ -91,7 +150,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "CatLair", "map": "maps/map/catlair_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "CatLair", + "BiomeGreen", + "Hostile", + "Sidequest" + ] }, { "name": "CatLairG1", @@ -100,7 +165,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "CatLair", "map": "maps/map/catlair_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "CatLair", + "BiomeGreen", + "Hostile", + "Sidequest" + ] }, { "name": "CatLairG2", @@ -109,7 +180,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "CatLair", "map": "maps/map/catlair_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "CatLair", + "BiomeGreen", + "Hostile", + "Sidequest" + ] }, { "name": "CatLairW", @@ -118,7 +195,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "CatLair", "map": "maps/map/catlair_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "CatLair", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CatLairW1", @@ -127,7 +211,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "CatLair", "map": "maps/map/catlair_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "CatLair", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CatLairW2", @@ -136,7 +227,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "CatLair", "map": "maps/map/catlair_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "CatLair", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveB", @@ -145,7 +243,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB1", @@ -154,7 +258,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB2", @@ -163,7 +273,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB3", @@ -172,7 +288,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_7.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB4", @@ -181,7 +303,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_8.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB5", @@ -190,7 +318,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_11.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB6", @@ -199,7 +333,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_12.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveB8", @@ -208,7 +348,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_20.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveBA", @@ -217,7 +363,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_22.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "CaveC", @@ -226,7 +378,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC1", @@ -235,7 +393,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC2", @@ -244,7 +408,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC3", @@ -253,7 +423,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC4", @@ -262,7 +438,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_6.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC5", @@ -271,7 +453,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_7.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC6", @@ -280,7 +468,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_9.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC7", @@ -289,7 +483,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_10.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC8", @@ -298,7 +498,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_11.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveC9", @@ -307,7 +513,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_12.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveCA", @@ -316,7 +528,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_14.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveCB", @@ -325,7 +543,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_20.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveCD", @@ -334,7 +558,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_22.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeColorless", + "Sidequest" + ] }, { "name": "CaveG", @@ -343,7 +573,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG1", @@ -352,7 +588,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG2", @@ -361,7 +603,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG3", @@ -370,7 +618,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG4", @@ -379,7 +633,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_9.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG5", @@ -388,7 +648,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_11.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG6", @@ -397,7 +663,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_15.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveG9", @@ -406,7 +678,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_20.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveGB", @@ -415,7 +693,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_22.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "CaveU", @@ -424,7 +708,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeBlue", + "Sidequest" + ] }, { "name": "CaveU1", @@ -433,7 +723,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_11.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeBlue", + "Sidequest" + ] }, { "name": "CaveU2", @@ -442,7 +738,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_12.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeBlue", + "Sidequest" + ] }, { "name": "CaveU3", @@ -451,7 +753,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_13.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeBlue", + "Sidequest" + ] }, { "name": "CaveU4", @@ -460,7 +768,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_20.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeBlue", + "Sidequest" + ] }, { "name": "CaveR", @@ -469,7 +783,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR2", @@ -478,7 +798,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR3", @@ -487,7 +813,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR4", @@ -496,7 +828,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR5", @@ -505,7 +843,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR6", @@ -514,7 +858,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_6.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR7", @@ -523,7 +873,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_8.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR8", @@ -532,7 +888,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_9.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveR9", @@ -541,7 +903,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_10.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRA", @@ -550,7 +918,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_12.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRB", @@ -559,7 +933,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_14.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRC", @@ -568,7 +948,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_15.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRE", @@ -577,7 +963,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_17.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRG", @@ -586,7 +978,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_19.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRH", @@ -595,7 +993,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_20.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveRJ", @@ -604,7 +1008,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_22.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeRed", + "Sidequest" + ] }, { "name": "CaveW", @@ -613,7 +1023,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveW1", @@ -622,7 +1038,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveW2", @@ -631,7 +1053,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveW3", @@ -640,7 +1068,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_12.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveW4", @@ -649,7 +1083,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_15.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveW5", @@ -658,7 +1098,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_19.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "CaveW6", @@ -667,7 +1113,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/cave_20.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "BiomeWhite", + "Sidequest" + ] }, { "name": "Colorless Castle", @@ -676,7 +1128,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "colorless_castle", "map": "maps/map/main_story/colorless_castle.tmx", - "radiusFactor": 0.4 + "radiusFactor": 0.4, + "questTags": [ + "Story", + "Castle", + "BiomeColorless" + ] }, { "name": "CopperhostForest", @@ -694,7 +1151,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Mine", - "map": "maps/map/crawlspace.tmx" + "map": "maps/map/crawlspace.tmx", + "questTags": [ + "DungeonEffect", + "Dungeon", + "Hostile" + ] }, { "name": "DEBUGZONE", @@ -711,7 +1173,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "DjinnPalace", "map": "maps/map/djinnpalace_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "DjinnPalace", + "Hostile", + "Sidequest" + ] }, { "name": "DjinnPalace1", @@ -720,7 +1187,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "DjinnPalace", "map": "maps/map/djinnpalace_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "DjinnPalace", + "Hostile", + "Sidequest" + ] }, { "name": "Dream Halls", @@ -729,7 +1201,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "StonePyramid", - "map": "maps/map/dreamhalls.tmx" + "map": "maps/map/dreamhalls.tmx", + "questTags": [ + "DungeonEffect", + "Dungeon", + "Hostile" + ] }, { "name": "ElfTown", @@ -738,7 +1215,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "ForestTown", "map": "maps/map/elftown.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "OccupiedTown", + "Hostile", + "BiomeGreen", + "Sidequest" + ] }, { "name": "EvilGrove", @@ -747,7 +1230,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", "map": "maps/map/evilgrove_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "EvilGrove", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "EvilGrove1", @@ -756,7 +1245,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", "map": "maps/map/evilgrove_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "EvilGrove", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "EvilGrove2", @@ -765,7 +1260,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", "map": "maps/map/evilgrove_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "EvilGrove", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "EvilGrove3", @@ -774,7 +1275,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", "map": "maps/map/evilgrove_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "EvilGrove", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "EvilGrove4", @@ -783,7 +1290,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", "map": "maps/map/evilgrove_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "EvilGrove", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "Factory", @@ -792,7 +1305,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Factory", "map": "maps/map/factory_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Factory", + "Sidequest" + ] }, { "name": "Factory1", @@ -801,7 +1320,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Factory", "map": "maps/map/factory_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Factory", + "Sidequest" + ] }, { "name": "Factory2", @@ -810,7 +1335,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Factory", "map": "maps/map/factory_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Factory", + "Sidequest" + ] }, { "name": "Factory3", @@ -819,7 +1350,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Factory", "map": "maps/map/factory_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Factory", + "Sidequest" + ] }, { "name": "Final Castle", @@ -828,7 +1365,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "final_castle", "map": "maps/map/main_story/final_castle.tmx", - "offsetY": 0.025 + "offsetY": 0.025, + "questTags": [ + "Story", + "Boss", + "RespawnEnemies", + "Castle", + "BiomeColorless" + ] }, { "name": "Forest Capital", @@ -836,7 +1380,12 @@ "count": 1, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "ForestCapital", - "map": "maps/map/main_story/forest_capital.tmx" + "map": "maps/map/main_story/forest_capital.tmx", + "questTags": [ + "Capital", + "BiomeGreen", + "Sidequest" + ] }, { "name": "Forest Town Generic", @@ -845,7 +1394,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "ForestTown", "map": "maps/map/forest_town_generic.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "BiomeGreen", + "TownGeneric", + "Sidequest" + ] }, { "name": "Forest Town Identity", @@ -854,7 +1409,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "ForestTown", "map": "maps/map/forest_town_identity.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "BiomeGreen", + "TownIdentity", + "Sidequest" + ] }, { "name": "Forest Town Tribal", @@ -863,7 +1424,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "ForestTown", "map": "maps/map/forest_town_tribal.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "BiomeGreen", + "TownTribal", + "Sidequest" + ] }, { "name": "Forest Town", @@ -881,7 +1448,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort1", @@ -890,7 +1463,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort2", @@ -899,7 +1478,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort3", @@ -908,7 +1493,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort4", @@ -917,7 +1508,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort5", @@ -926,7 +1523,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_6.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort6", @@ -935,7 +1538,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_7.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort7", @@ -944,7 +1553,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_8.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort8", @@ -953,7 +1568,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_9.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Fort9", @@ -962,7 +1583,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Fort", "map": "maps/map/fort_10.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Fort", + "Sidequest" + ] }, { "name": "Garruk Forest", @@ -971,7 +1598,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", - "map": "maps/map/garruk.tmx" + "map": "maps/map/garruk.tmx", + "questTags": [ + "Boss", + "Planeswalker", + "Hostile" + ] }, { "name": "GitaxianLab", @@ -980,7 +1612,13 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/Phyrexian_Buildings.atlas", "sprite": "GitLabSmall", - "map": "maps/map/phyrexian_b1.tmx" + "map": "maps/map/phyrexian_b1.tmx", + "questTags": [ + "Hostile", + "Phyrexian", + "Dungeon", + "Sidequest" + ] }, { "name": "Green Castle", @@ -991,7 +1629,14 @@ "map": "maps/map/main_story/green_castle.tmx", "radiusFactor": 0.01, "offsetX": -0.1, - "offsetY": 0.1 + "offsetY": 0.1, + "questTags": [ + "Story", + "Boss", + "RespawnEnemies", + "DungeonEffect", + "Castle" + ] }, { "name": "Graveyard", @@ -1000,7 +1645,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Graveyard", "map": "maps/map/graveyard.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Graveyard", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "Graveyard1", @@ -1009,7 +1660,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Graveyard", "map": "maps/map/graveyard_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Graveyard", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "Graveyard2", @@ -1018,7 +1675,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Graveyard", "map": "maps/map/graveyard_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Graveyard", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "Graveyard3", @@ -1027,7 +1690,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Graveyard", "map": "maps/map/graveyard_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Graveyard", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "Graveyard4", @@ -1036,7 +1705,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Graveyard", "map": "maps/map/graveyard_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Graveyard", + "BiomeBlack", + "Hostile", + "Sidequest" + ] }, { "name": "Grolnoks Bog", @@ -1045,7 +1720,11 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", - "map": "maps/map/grolnok.tmx" + "map": "maps/map/grolnok.tmx", + "questTags": [ + "Boss", + "Hostile" + ] }, { "name": "Grove", @@ -1054,7 +1733,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove1", @@ -1063,7 +1748,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove2", @@ -1072,7 +1763,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove3", @@ -1081,7 +1778,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove4", @@ -1090,7 +1793,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove5", @@ -1099,7 +1808,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_6.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove6", @@ -1108,7 +1823,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_7.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove7", @@ -1117,7 +1838,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_8.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Grove8", @@ -1126,7 +1853,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Grove", "map": "maps/map/grove_9.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeGreen", + "Grove", + "Hostile", + "Sidequest" + ] }, { "name": "Island Capital", @@ -1134,7 +1867,12 @@ "count": 1, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "IslandCapital", - "map": "maps/map/main_story/island_capital.tmx" + "map": "maps/map/main_story/island_capital.tmx", + "questTags": [ + "Capital", + "BiomeBlue", + "Sidequest" + ] }, { @@ -1144,7 +1882,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "IslandTown", "map": "maps/map/island_town_generic.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownGeneric", + "BiomeBlue", + "Sidequest" + ] }, { "name": "Island Town Identity", @@ -1153,7 +1897,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "IslandTown", "map": "maps/map/island_town_identity.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownIdentity", + "BiomeBlue", + "Sidequest" + ] }, { "name": "Island Town Tribal", @@ -1162,7 +1912,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "IslandTown", "map": "maps/map/island_town_tribal.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownTribal", + "BiomeBlue", + "Sidequest" + ] }, { "name": "Island Town", @@ -1180,7 +1936,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", - "map": "maps/map/jacehold.tmx" + "map": "maps/map/jacehold.tmx", + "questTags": [ + "Boss", + "Planeswalker", + "Hostile" + ] }, { "name": "Kavu Lair", @@ -1189,7 +1950,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", - "map": "maps/map/kavulair.tmx" + "map": "maps/map/kavulair.tmx", + "questTags": [ + "Hostile", + "DungeonEffect", + "Cave" + ] }, { "name": "Kiora Island", @@ -1198,7 +1964,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", - "map": "maps/map/kiora_island.tmx" + "map": "maps/map/kiora_island.tmx", + "questTags": [ + "Boss", + "Planeswalker", + "Hostile" + ] }, { "name": "Kobold Mine", @@ -1207,7 +1978,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "LavaForge", - "map": "maps/map/kobold_mine.tmx" + "map": "maps/map/kobold_mine.tmx", + "questTags": [ + "Hostile", + "Dungeon", + "Sidequest" + ] }, { "name": "LavaForge1", @@ -1216,7 +1992,14 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "LavaForge", - "map": "maps/map/lavaforge_1.tmx" + "map": "maps/map/lavaforge_1.tmx", + "questTags": [ + "DungeonEffect", + "Dungeon", + "LavaForge", + "Hostile", + "Sidequest" + ] }, { "name": "LavaForge2", @@ -1225,7 +2008,14 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "LavaForge", - "map": "maps/map/lavaforge_2.tmx" + "map": "maps/map/lavaforge_2.tmx", + "questTags": [ + "DungeonEffect", + "Dungeon", + "LavaForge", + "Hostile", + "Sidequest" + ] }, { "name": "Lich's Mirror", @@ -1234,7 +2024,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "StonePyramid", - "map": "maps/map/lichsmirror.tmx" + "map": "maps/map/lichsmirror.tmx", + "questTags": [ + "Hostile", + "DungeonEffect", + "Dungeon" + ] }, { "name": "MageTowerC", @@ -1243,7 +2038,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerC2", @@ -1252,7 +2054,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerC3", @@ -1261,7 +2070,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerC4", @@ -1270,7 +2086,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerC5", @@ -1279,7 +2102,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerC6", @@ -1288,7 +2118,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_6.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerC8", @@ -1297,7 +2134,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_8.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerX", @@ -1306,7 +2150,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_13.tmx", - "radiusFactor": 0.2 + "radiusFactor": 0.2, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerCE", @@ -1315,7 +2166,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_14.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeColorless", + "MageTower", + "Dungeon", + "Hostile", + "Sidequest" + ] }, { "name": "MageTowerU", @@ -1324,7 +2182,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerU1", @@ -1333,7 +2198,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerU2", @@ -1342,7 +2214,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerU3", @@ -1351,7 +2230,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerU4", @@ -1360,7 +2246,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerU5", @@ -1369,7 +2262,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_6.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerU7", @@ -1378,7 +2278,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_8.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "MageTowerUD", @@ -1387,7 +2294,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MageTower", "map": "maps/map/magetower_14.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MageTower", + "Hostile", + "Dungeon", + "BiomeBlue", + "Sidequest" + ] }, { "name": "Maze", @@ -1396,7 +2310,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Maze", "map": "maps/map/maze_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Maze", + "BiomeRed", + "Sidequest" + ] }, { "name": "Maze1", @@ -1405,7 +2325,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Maze", "map": "maps/map/maze_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Maze", + "BiomeRed", + "Sidequest" + ] }, { "name": "Maze2", @@ -1414,7 +2340,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Maze", "map": "maps/map/maze_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Maze", + "BiomeRed", + "Sidequest" + ] }, { "name": "Maze3", @@ -1423,7 +2355,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Maze", "map": "maps/map/maze_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Maze", + "BiomeRed", + "Sidequest" + ] }, { "name": "MerfolkPool", @@ -1432,7 +2370,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", "map": "maps/map/merfolkpool_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MerfolkPool", + "Hostile", + "Sidequest" + ] }, { "name": "MerfolkPool1", @@ -1441,7 +2384,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", "map": "maps/map/merfolkpool_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MerfolkPool", + "Hostile", + "Sidequest" + ] }, { "name": "MerfolkPool2", @@ -1450,7 +2398,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", "map": "maps/map/merfolkpool_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MerfolkPool", + "Hostile", + "Sidequest" + ] }, { "name": "MerfolkPool3", @@ -1459,7 +2412,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", "map": "maps/map/merfolkpool_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MerfolkPool", + "Hostile", + "Sidequest" + ] }, { "name": "MerfolkPool4", @@ -1468,7 +2426,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", "map": "maps/map/merfolkpool_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "MerfolkPool", + "Hostile", + "Sidequest" + ] }, { "name": "Monastery", @@ -1477,7 +2440,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Monastery", "map": "maps/map/monastery_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Monastery", + "Sidequest" + ] }, { "name": "Monastery1", @@ -1486,7 +2455,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Monastery", "map": "maps/map/monastery_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Monastery", + "Sidequest" + ] }, { "name": "Monastery2", @@ -1495,7 +2470,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Monastery", "map": "maps/map/monastery_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Monastery", + "Sidequest" + ] }, { "name": "Monastery3", @@ -1504,7 +2485,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Monastery", "map": "maps/map/monastery_4.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Monastery", + "Sidequest" + ] }, { "name": "Monastery4", @@ -1513,7 +2500,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Monastery", "map": "maps/map/monastery_5.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Dungeon", + "Monastery", + "Sidequest" + ] }, { "name": "Mountain Town Generic", @@ -1522,7 +2515,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MountainTown", "map": "maps/map/mountain_town_generic.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownGeneric", + "BiomeRed", + "Sidequest" + ] }, { "name": "Mountain Town Identity", @@ -1531,7 +2530,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MountainTown", "map": "maps/map/mountain_town_identity.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownIdentity", + "BiomeRed", + "Sidequest" + ] }, { "name": "Mountain Town Tribal", @@ -1540,7 +2545,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MountainTown", "map": "maps/map/mountain_town_tribal.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownTribal", + "BiomeRed", + "Sidequest" + ] }, { "name": "Mountain Town", @@ -1557,7 +2568,12 @@ "count": 1, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MountainCapital", - "map": "maps/map/main_story/mountain_capital.tmx" + "map": "maps/map/main_story/mountain_capital.tmx", + "questTags": [ + "Capital", + "BiomeRed", + "Sidequest" + ] }, { "name": "Nahiri Encampment", @@ -1566,7 +2582,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Castle", - "map": "maps/map/nahiri.tmx" + "map": "maps/map/nahiri.tmx", + "questTags": [ + "Boss", + "Planeswalker", + "Hostile" + ] }, { "name": "NestW", @@ -1575,7 +2596,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Nest", "map": "maps/map/nest_white_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Nest", + "BiomeWhite", + "Sidequest" + ] }, { "name": "NestU", @@ -1584,7 +2611,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Nest", "map": "maps/map/nest_blue_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeBlue", + "Hostile", + "Nest", + "Sidequest" + ] }, { "name": "OrthodoxyBasilica", @@ -1593,7 +2626,13 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/Phyrexian_Buildings.atlas", "sprite": "BasilicaSmall", - "map": "maps/map/phyrexian_w1.tmx" + "map": "maps/map/phyrexian_w1.tmx", + "questTags": [ + "Hostile", + "Phyrexian", + "Dungeon", + "Sidequest" + ] }, { "name": "Plains Capital", @@ -1601,7 +2640,12 @@ "count": 1, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "PlainsCapital", - "map": "maps/map/main_story/plains_capital.tmx" + "map": "maps/map/main_story/plains_capital.tmx", + "questTags": [ + "Capital", + "BiomeWhite", + "Sidequest" + ] }, { "name": "Plains Town Generic", @@ -1610,7 +2654,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "PlainsTown", "map": "maps/map/plains_town_generic.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "BiomeWhite", + "TownGeneric", + "Sidequest" + ] }, { "name": "Plains Town Identity", @@ -1619,7 +2669,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "PlainsTown", "map": "maps/map/plains_town_identity.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownIdentity", + "BiomeWhite", + "Sidequest" + ] }, { "name": "Plains Town Tribal", @@ -1628,7 +2684,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "PlainsTown", "map": "maps/map/plains_town_tribal.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownTribal", + "BiomeWhite", + "Sidequest" + ] }, { "name": "Plains Town", @@ -1648,7 +2710,14 @@ "map": "maps/map/main_story/red_castle.tmx", "radiusFactor": 0.01, "offsetX": -0.1, - "offsetY": -0.1 + "offsetY": -0.1, + "questTags": [ + "Story", + "Boss", + "RespawnEnemies", + "DungeonEffect", + "Castle" + ] }, { "name": "SkullCaveR", @@ -1657,7 +2726,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SkullCave", "map": "maps/map/skullcave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "SkullCave", + "BiomeRed", + "Sidequest" + ] }, { "name": "SkullCaveR1", @@ -1666,7 +2742,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SkullCave", "map": "maps/map/skullcave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "SkullCave", + "BiomeRed", + "Sidequest" + ] }, { "name": "SkullCaveR2", @@ -1675,7 +2758,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SkullCave", "map": "maps/map/skullcave_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Cave", + "SkullCave", + "BiomeRed", + "Sidequest" + ] }, { "name": "SnowAbbey", @@ -1684,7 +2774,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SnowAbbey", "map": "maps/map/snowabbey_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeRed", + "Hostile", + "SnowAbbey", + "Snow", + "Sidequest" + ] }, { "name": "SnowAbbey1", @@ -1693,7 +2790,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SnowAbbey", "map": "maps/map/snowabbey_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeRed", + "Hostile", + "SnowAbbey", + "Snow", + "Sidequest" + ] }, { "name": "SnowAbbey2", @@ -1702,7 +2806,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SnowAbbey", "map": "maps/map/snowabbey_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeRed", + "Hostile", + "SnowAbbey", + "Snow", + "Sidequest" + ] }, { "name": "Spawn", @@ -1710,7 +2821,12 @@ "count": 1, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Spawn", - "map": "maps/map/main_story/spawn.tmx" + "map": "maps/map/main_story/spawn.tmx", + "questTags": [ + "Story", + "Spawn", + "BiomeColorless" + ] }, { "name": "Slimefoots Lair", @@ -1719,7 +2835,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "EvilGrove", - "map": "maps/map/slimefoot_boss.tmx" + "map": "maps/map/slimefoot_boss.tmx", + "questTags": [ + "Hostile", + "DungeonEffect", + "Boss" + ] }, { "name": "Slime Cave", @@ -1728,7 +2849,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", - "map": "maps/map/slime_hive.tmx" + "map": "maps/map/slime_hive.tmx", + "questTags": [ + "Cave", + "Hostile", + "Sidequest" + ] }, { "name": "Scarecrow Farm", @@ -1737,7 +2863,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "farm", - "map": "maps/map/scarecrow_farm.tmx" + "map": "maps/map/scarecrow_farm.tmx", + "questTags": [ + "Boss", + "Farm", + "Hostile" + ] }, { "name": "Slobads Factory", @@ -1746,7 +2877,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Factory", - "map": "maps/map/slobad_factory.tmx" + "map": "maps/map/slobad_factory.tmx", + "questTags": [ + "Boss", + "Factory", + "Hostile" + ] }, { "name": "Skep", @@ -1755,7 +2891,12 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", "map": "maps/map/main_story/skep.tmx", - "radiusFactor": 0.5 + "radiusFactor": 0.5, + "questTags": [ + "Boss", + "Cave", + "DungeonEffect" + ] }, { "name": "SkullCaveB", @@ -1764,7 +2905,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SkullCave", "map": "maps/map/skullcave_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "SkullCave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "SkullCaveB1", @@ -1773,7 +2921,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SkullCave", "map": "maps/map/skullcave_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "SkullCave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "SkullCaveB2", @@ -1782,7 +2937,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SkullCave", "map": "maps/map/skullcave_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Cave", + "SkullCave", + "Hostile", + "BiomeBlack", + "Sidequest" + ] }, { "name": "Swamp Capital", @@ -1790,7 +2952,12 @@ "count": 1, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SwampCapital", - "map": "maps/map/main_story/swamp_capital.tmx" + "map": "maps/map/main_story/swamp_capital.tmx", + "questTags": [ + "Capital", + "BiomeBlack", + "Sidequest" + ] }, { "name": "Swamp Town Generic", @@ -1799,7 +2966,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SwampTown", "map": "maps/map/swamp_town_generic.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownGeneric", + "BiomeBlack", + "Sidequest" + ] }, { "name": "Swamp Town Identity", @@ -1808,7 +2981,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SwampTown", "map": "maps/map/swamp_town_identity.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownIdentity", + "BiomeBlack", + "Sidequest" + ] }, { "name": "Swamp Town Tribal", @@ -1817,7 +2996,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SwampTown", "map": "maps/map/Swamp_town_tribal.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownTribal", + "BiomeBlack", + "Sidequest" + ] }, { "name": "Swamp Town", @@ -1843,7 +3028,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "MerfolkPool", - "map": "maps/map/teferi.tmx" + "map": "maps/map/teferi.tmx", + "questTags": [ + "Hostile", + "Boss", + "Planeswalker" + ] }, { "name": "Temple of Chandra", @@ -1852,7 +3042,15 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "hall_of_flame", - "map": "maps/map/main_story/TempleOfChandra.tmx" + "map": "maps/map/main_story/TempleOfChandra.tmx", + "questTags": [ + "Story", + "BiomeRed", + "Temple", + "DungeonEffect", + "Hostile", + "Boss" + ] }, { "name": "Test", @@ -1869,7 +3067,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "LavaForge", - "map": "maps/map/tibalt.tmx" + "map": "maps/map/tibalt.tmx", + "questTags": [ + "Hostile", + "Boss", + "Planeswalker" + ] }, { "name": "VampireCastle", @@ -1878,7 +3081,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "VampireCastle", "map": "maps/map/vampirecastle_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "VampireCastle", + "Hostile", + "BiomeBlack", + "Dungeon", + "Sidequest" + ] }, { "name": "VampireCastle1", @@ -1887,7 +3097,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "VampireCastle", "map": "maps/map/vampirecastle_2.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "VampireCastle", + "Hostile", + "BiomeBlack", + "Dungeon", + "Sidequest" + ] }, { "name": "VampireCastle2", @@ -1896,7 +3113,14 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "VampireCastle", "map": "maps/map/vampirecastle_3.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "VampireCastle", + "Hostile", + "BiomeBlack", + "Dungeon", + "Sidequest" + ] }, { @@ -1906,7 +3130,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "WasteTown", "map": "maps/map/waste_town_generic.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownGeneric", + "BiomeColorless", + "Sidequest" + ] }, { "name": "Waste Town Identity", @@ -1915,7 +3145,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "WasteTown", "map": "maps/map/waste_town_identity.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownIdentity", + "BiomeColorless", + "Sidequest" + ] }, { "name": "Waste Town Tribal", @@ -1924,7 +3160,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "WasteTown", "map": "maps/map/waste_town_tribal.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownTribal", + "BiomeColorless", + "Sidequest" + ] }, { "name": "White Castle", @@ -1934,7 +3176,14 @@ "sprite": "white_castle", "map": "maps/map/main_story/white_castle.tmx", "radiusFactor": 0.01, - "offsetY": 0.1 + "offsetY": 0.1, + "questTags": [ + "Story", + "Boss", + "RespawnEnemies", + "DungeonEffect", + "Castle" + ] }, { "name": "WurmPond", @@ -1943,7 +3192,11 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "WurmPond", "map": "maps/map/wurmpond_1.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "DungeonEffect", + "Hostile" + ] }, { "name": "Xiras Hive", @@ -1952,7 +3205,12 @@ "radiusFactor": 0.8, "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "Cave", - "map": "maps/map/xira.tmx" + "map": "maps/map/xira.tmx", + "questTags": [ + "Hostile", + "Boss", + "DungeonEffect" + ] }, { "name": "YuleTown", @@ -1961,7 +3219,11 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "YuleTown", "map": "maps/map/yule_town.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "BiomeRed", + "Snow" + ] }, { "name": "Zombie Town", @@ -1970,6 +3232,13 @@ "spriteAtlas": "maps/tileset/buildings.atlas", "sprite": "SwampTown", "map": "maps/map/zombietown.tmx", - "radiusFactor": 0.8 + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "OccupiedTown", + "BiomeBlack", + "Sidequest" + ] + } ] \ No newline at end of file diff --git a/forge-gui/res/adventure/Shandalar/world/quests.json b/forge-gui/res/adventure/Shandalar/world/quests.json new file mode 100644 index 00000000000..ea82b8b2451 --- /dev/null +++ b/forge-gui/res/adventure/Shandalar/world/quests.json @@ -0,0 +1,5669 @@ +[ +{ + "id": 1, + "isTemplate": true, + "name": "Go Forth and Slay", + "description": "Defeat two $(enemy_1)s and collect a reward", + "offerDialog": { + "text": "\"Hey, you! Yeah, you, the big bad wizard with a surprised look on your face.\" A haggard old man shouts at you from the spot in which he sits, you could have sworn that side of the street was empty just seconds before.", + "options": [ + { + "name": "\"Well, you got the big bad wizard part right, what do you need?\"", + "text": "\"There's a $(enemy_1) bounty available right now, if you talk to the right people.\" He grins. \"I'm not the right people, but I know where to find them.\"", + "options": [ + { + "name": "\"What's the catch?\"", + "text": "\"No catch, just a deal. I'm in no shape to fight right now, but I'm short on gold. Go take out one $(enemy_1) for yourself and one for me, I'll introduce you to the right people, and we'll split the profits.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Alright, a deal it is.\" (Accept Quest)" + }, + { + "name": "\"$(enemy_1)? Two of them? Not worth the time to find them.\" (Decline Quest)" + } + ] + }, + { + "name": "\"And what if I find the right people myself?\"", + "text": "He shrugs as though that wouldn't bother him. \"Then I'll have to find someone bigger, badder, and most importantly faster than you to work with.\"", + "options": [ + { + "name": "\"Good luck with that.\" (Decline Quest)" + }, + { + "name": "\"So if I were to run across a $(enemy_1) or two. What are you suggesting?\"", + "text": "\"No catch, just a deal. I'm in no shape to fight right now, but I'm short on gold. Go take out one $(enemy_1) for yourself and one for me, I'll introduce you to the right people, and we'll split the profits.\"", + "options": [ + { + "name": "\"Doesn't sound worth it. Sorry.\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Well get them ready for a meet and greet.\" (Accept Quest)" + } + ] + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": {}, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "Having spent as much time searching for $(enemy_1)s as you care to, you scratch this item out of your notes. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Half of an unspecified bounty", + "stages": [ + { + "id": 1, + "name": "Defeat $(enemy_1)", + "description": "Defeat $(enemy_1) twice as your part of the deal.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 2, + "objective": "Defeat", + "enemyTags": [ + "BiomeColorless" + ], + "prologue": { + "text": "The wasteland biome is usually a good place to look for $(enemy_1)s.", + "options": [ + { + "name": "(Continue your quest)" + } + ] + }, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to town to collect your part of the rewards.", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": { + "text": "Having finally handled the pair of $(enemy_1), it's time to go collect your reward. As a reminder, you can track this quest in your quest log for navigation assistance.", + "options": [ + { + "name": "(Continue Your Quest)" + } + ] + }, + "epilogue": { + "text": "You find your partner in this endeavor exactly where you left them. Not much of a partner in that case, but they hold up their side of the deal and you walk away with your half of the loot.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "gold", + "count": 500 + }, + { + "type": "shards", + "count": 10 + }, + { + "type": "card", + "probability": 0.2, + "count": 2, + "rarity": [ + "Common" + ], + "colorType": "Colorless" + }, + { + "type": "card", + "probability": 0.2, + "count": 2, + "rarity": [ + "Common" + ], + "colorType": "Colorless" + }, + { + "type": "card", + "probability": 0.2, + "count": 1, + "rarity": [ + "Common" + ], + "colorType": "Any" + }, + { + "type": "card", + "probability": 0.2, + "count": 1, + "rarity": [ + "Common" + ], + "colorType": "Any" + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "POIToken": "" + } + ] +}, +{ + "id": 2, + "isTemplate": true, + "name": "Wanderlust", + "description": "Make a delivery to a distant location", + "offerDialog": { + "text": "\"Excuse me, but you look like a well traveled individual.\" A frazzled looking mage gets your attention. \"I have a letter of some great importance that MUST be hand delivered. Would you be willing to handle this for me?\"", + "options": [ + { + "name": "\"Why does it have to be hand delivered?\"", + "text": "He hesitates. \"Well, to be blunt, the intended recipient is rather... odd. She doesn't trust magical delivery methods anymore. Can you imagine? One little summoning accident...\"", + "options": [ + { + "name": "\"Well, then perhaps I'm not the right person for the job. You know, as a wizard...\"", + "text": "\"No no, it will be fine. Really!\" He pauses for a moment \"Just... maybe just don't mention it. And maybe lie about it if she asks. That should keep you safe.\"", + "options": [ + { + "name": "\"...safe? You know, suddenly I think I have better things to do.\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"I like to live dangerously\" (Accept Quest)" + } + ] + } + ] + }, + { + "name": "\"Carrying someone's mail doesn't sound like traveling well. No thank you.\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Sure, I could use a change of scenery.\" (Accept Quest)" + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "After a lot of travel, a little teleporting, or both, you finally arrive at your destination. The letter you are carrying looks no worse for wear, at least.", + "options": [ + { + "name": "(Continue)", + "text": "You wander over to the building that was described to you, and a woman calls out to you from an upstairs window. \"You! You have something of mine! Give it! Give it! Give it!\"", + "options": [ + { + "name": "\"Certainly.\" You conjure a gust of wind to carry the letter up to her window.", + "text": "\"Ahhh! I knew it! I knew you smelled of magic!\" She slams the window shut, and refuses to answer the door.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 1, + "POIReference": "" + } + ], + "name": "\"I guess I should have asked for the reward first.\" (+1 local reputation. Complete Quest)" + }, + { + "name": "(Break down the door)", + "text": "Several people in the village have turned their attention to you after the woman's outburst. Perhaps this isn't a good idea after all. (-1 local reputation)", + "options": [ + { + "name": "You mutter to yourself. \"Not worth a scene I suppose.\" (Complete Quest)" + } + ] + } + ] + }, + { + "name": "\"If you mean this letter, then yes.\" You walk closer.", + "text": "She lowers down a bucket on a rope. Inside is a small coinpurse. \"Give it!!!\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "grantRewards": [ + { + "type": "gold", + "count": 150, + "addMaxCount": 300 + }, + { + "type": "card", + "count": 1, + "rarity": [ + "Common", + "Uncommon" + ], + "cardText": "courier" + } + ], + "issueQuest": "", + "addMapReputation": 2, + "POIReference": "" + } + ], + "name": "You take the coins and place the letter in the bucket. \"I have to say, I do I find your demeanor unnerving.\" ", + "text": "(+2 local reputation)", + "options": [ + { + "name": "(Complete Quest)" + } + ] + } + ] + } + ] + } + ] + }, + "failureDialog": { + "text": "The trip just doesn't seem worth it anymore, and you give up on trying to reach $(poi_1).", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "A change of scenery at the least", + "stages": [ + { + "id": 1, + "name": "Travel", + "description": "Make the long journey", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 80, + "count2": 15, + "POITags": [ + "Town" + ], + "objective": "Travel", + "prologue": { + "text": "Nothing like a really long journey to strech the legs, right? You could likely save yourself some time with the right spells, but... is that going to be safe?", + "options": [ + { + "name": "(Begin your quest)" + } + ] + }, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 3, + "isTemplate": true, + "name": "(Almost) Open for Business", + "description": "Assist a new merchant as they open their shop", + "offerDialog": { + "text": "A portly man in the corner of the tavern catches your eye. \"I hear you're looking for work.\"", + "options": [ + { + "name": "\"Sorry, you've got the wrong person.\" (Decline Quest)" + }, + { + "name": "So long as it pays. What do you need?", + "text": "I'm new to town, and looking to open a new spell shop. But I need supplies that I had to leave behind. Can you go get them for me?", + "options": [ + { + "name": "\"And the pay?\"", + "text": "\"How about a sample of the merchandise?\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "3", + "POIReference": "" + } + ], + "name": "\"You've got a deal.\" (Accept Quest)" + }, + { + "name": "\"I was looking for something a little more concrete. I'll pass.\" (Decline Quest)" + } + ] + }, + { + "name": "\"Sorry, but I'm a busy mage, which means I have no time for busy work. Good luck with your shop.\" (Decline Quest)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": {}, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -5, + "POIReference": "$(poi_4)" + } + ], + "text": "Not every venture is meant to succeed. In this case, two have failed: A business venture and an adventure. (-5 local reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "A sample of the merchandise", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town", + "mapFlag": "", + "mapFlagValue": 1, + "objective": "Leave", + "prologue": {}, + "epilogue": { + "text": "(As a reminder, you can track this quest from your quest log to get directions to your destination)", + "options": [ + { + "name": "(Begin your quest)" + } + ] + }, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Go to the destination town", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 50, + "count2": 20, + "POITags": [ + "BiomeColorless", + "Town" + ], + "objective": "Travel", + "prologue": {}, + "epilogue": { + "text": "Upon arriving at the pickup point, you find a rather modest looking spellbook among the supplies. Presumably, this is the merchandise your employer is planning to sell", + "options": [ + { + "name": "You pick up the goods and begin your journey back. (Leave)" + } + ] + }, + "POIToken": "" + }, + { + "id": 3, + "name": "Leave", + "description": "Leave town", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 50, + "count2": 20, + "prologue": {}, + "epilogue": { + "text": "Just as you leave town, the spellbook slides out of a rip you hadn't noticed in the sack of goods. It opens as it lands on the ground.\t ", + "options": [ + { + "name": "You decide to investigate the spellbook.", + "text": "As would suit a brand new shop, the contents are mostly common spells.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "count": 1, + "rarity": [ + "Uncommon" + ] + } + ] + } + ], + "name": "Quickly and discretely help yourself to a spell before continuing", + "text": "You receive a spell of dubious quality.", + "options": [ + { + "name": "I do get a sample of the merchandise, after all... (Leave)" + } + ] + }, + { + "name": "Move the items to another bag and carry on" + }, + { + "name": "Search for something more useful", + "text": "Toward the back of the collection, you find some things that are at least a little more uncommon. And you also notice a young boy watching you from beneath a tree near the road.", + "options": [ + { + "name": "Move the items to another bag and carry on (Continue quest)" + }, + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "count": 1, + "rarity": [ + "Uncommon" + ] + } + ] + }, + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + }, + { + "grantRewards": [ + { + "type": "card", + "count": 4, + "rarity": [ + "Uncommon" + ] + } + ] + } + ], + "name": "\"It's just a kid. Whatever.\" You take one of the cards and leave.", + "text": "(-2 local reputation)", + "options": [ + { + "name": "(Continue your quest)" + } + ] + } + ] + } + ] + }, + { + "name": "You move the items to another bag and carry on. (Continue Quest)" + } + ] + }, + "POIToken": "" + }, + { + "id": 4, + "name": "Travel", + "description": "Return to the vendor", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "POITags": [ + "BiomeColorless" + ], + "objective": "Travel", + "prologue": {}, + "epilogue": { + "text": "While you were gone, the new merchant has set up a tent filled with mismatched and bare shelves. It will be a little less bare now, but you doubt that their business will succeed.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "count": 4, + "rarity": [ + "Common" + ] + }, + { + "type": "shards", + "count": 5, + "addMaxCount": 5 + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "POIToken": "" + } + ] +}, +{ + "id": 4, + "isTemplate": true, + "name": "On the Hunt", + "description": "Find and slay the $(enemy_2) before it escapes.", + "offerDialog": { + "text": "A well dressed elf, probably a merchant, approaches you. \"Adventurer, are you available? A $(enemy_2) has been causing trouble in this area lately, and we need someone to take care of the matter.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "$(poi_1)" + } + ], + "name": "\"$(enemy_2)s are my specialty. Consider it done.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "$(poi_1)" + } + ], + "name": "\"I have better things to do right now.\" (Decline Quest)", + "text": "The elf says nothing, but looks disappointed. (-1 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "\"Is there a bounty posted?\"", + "text": "\"Officially, no.\" The elf thinks for a moment. \"But unofficially, I can offer you a small selection of spells and mana shards.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "$(poi_1)" + } + ], + "name": "\"I'll handle it.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "" + } + ], + "name": "\"For a $(enemy_2)? No thank you.\" (Decline Quest)", + "text": "The elf says nothing, but looks disappointed. (-2 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "Consciously or unconsciously, you brush your shoulders off as you walk back in to town. The locals appear delighted that you have taken care of their problem. (+3 local reputation)", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Common" + ], + "colorType": "MonoColor" + }, + { + "type": "card", + "probability": 0.5, + "count": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Common", + "Uncommon" + ], + "colorType": "MonoColor" + }, + { + "type": "card", + "count": 1, + "colors": [ + "Green" + ], + "rarity": [ + "Uncommon" + ], + "colorType": "MonoColor" + }, + { + "type": "shards", + "count": 4, + "addMaxCount": 4 + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "text": "You gave it your best effort, but today was not a successful hunt by any means. The $(enemy_2) will continue to be a problem for the area. (-2 town reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Shards, green commons & uncommons", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town to begin the hunt", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 15, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Hunt a $(enemy_2)", + "description": "Find and defeat the $(enemy_2) before it escapes. You only get one shot, so make it count.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 30, + "objective": "Hunt", + "enemyTags": [ + "BiomeGreen" + ], + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Head back to town to collect the $(enemy_2) bounty", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 5, + "isTemplate": true, + "name": "A Scheduled Burial", + "description": "Find and slay the $(enemy_2) before it escapes.", + "offerDialog": { + "text": "A cloaked and hooded humanoid approaches you and speaks in a quiet raspy voice. \"You'll do. I have need of a $(enemy_2). Dead or alive. And by alive, I mean dead. Quickly.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "$(poi_1)" + } + ], + "name": "\"With pleasure.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "$(poi_1)" + } + ], + "name": "\"That's rather ominous\" (Decline Quest)", + "text": "The silence that follows is much more ominous, but the figure eventually turns away and leaves. (-1 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "\"Urgency is expensive.\"", + "text": "\"So is not being the next scheduled burial.\" As you're still processesing that statement, the figure continues. \"Ten mana shards. And you can keep the bones.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "$(poi_1)" + } + ], + "name": "\"Point me to them.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "" + } + ], + "name": "\"For a $(enemy_2)? No thank you.\" (Decline Quest)", + "text": "The air grows cold for a moment as they turn and walk away. (-2 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "No sooner than you walk through the gates, a pair of ghouls scamper over and take the corpse from you. They disappear into a nearby building. Mere moments later, one returns with a wooden chest while the other carries away a matching one. (+3 local reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + }, + { + "grantRewards": [ + { + "type": "card", + "count": 1, + "subTypes": [ + "Skeleton" + ] + }, + { + "type": "shards", + "count": 10 + }, + { + "type": "card", + "probability": 0.5, + "count": 2, + "colors": [ + "Black" + ], + "rarity": [ + "common" + ], + "colorType": "MonoColor" + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Black" + ], + "rarity": [ + "uncommon" + ], + "colorType": "MonoColor" + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "text": "The $(enemy_2) escapes, and your opportunity is missed. Hopefully that doesn't result in your parts being harvested next. (-2 town reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Shards and bones", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town to locate your victim", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 15, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Hunt a $(enemy_2)", + "description": "Find and defeat the $(enemy_2) before it escapes. You only get one shot, so make it count.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 30, + "objective": "Hunt", + "enemyTags": [ + "BiomeBlack" + ], + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Head back to town to collect the $(enemy_2) bounty. And bones.", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 6, + "isTemplate": true, + "name": "High Plains Justice", + "description": "Catch the $(enemy_2) before it escapes.", + "offerDialog": { + "text": "As you walk out of the local inn, you spot a militiaman putting up wanted posters.", + "options": [ + { + "name": "(Continue)", + "text": "Carrying on through town, you spot someone else interacting with a poster, tearing it down. The resemblance is uncanny.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "You shrug your shoulders. It's not your problem. (Decline Quest)", + "text": "The criminal glances at you and hurredly scampers off. (-1 Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "You approach the suspect. ", + "text": "Hearing your footsteps, the $(enemy_2) takes off at a full run.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "Definitely not your problem. (Decline Quest)", + "text": "The $(enemy_2) slips through a crowded gate with his head down and you lose sight of them. (-1 Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "Chase after them. (Accept Quest)" + } + ] + }, + { + "name": "You clear your throat in an exagerated manner.", + "text": "The $(enemy_2) drops a small satchel as they begin to run away.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "probability": 0.5, + "count": 1, + "addMaxCount": 2, + "rarity": [ + "Common" + ], + "cardTypes": [ + "Artifact" + ] + }, + { + "type": "gold", + "count": 300, + "addMaxCount": 100 + } + ] + } + ], + "name": "Investigate the dropped bag", + "text": "They get away, but you find some trinkets and gold inside the bag, all very likely stolen. (-2 reputation)", + "options": [ + { + "name": "(Continue, Decline Quest)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "Chase after them. (Accept Quest)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + } + ], + "text": "You seek out the town guard barracks, ready to claim the reward for $(enemy_2). The militia captain nods as you explain what happened, then hands over a meager reward. (+3 local reputation)", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "gold", + "count": 100 + }, + { + "type": "card", + "count": 2, + "colors": [ + "White" + ], + "rarity": [ + "Common" + ] + }, + { + "type": "shards", + "count": 3 + } + ] + } + ], + "name": "You quickly scan what he handed over to you, and think about the stolen items you found on your target.", + "text": "I wonder if he knows that I would have found anything?", + "options": [ + { + "name": "Turn over the stolen loot. (Complete Quest)", + "text": " (+1 Reputation) You receive a second nod, more respectful than the previous acknowledgement. \"Thank you, citizen.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 1, + "POIReference": "" + } + ], + "name": "(Continue)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "$(poi_1)" + }, + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "grantRewards": [ + { + "type": "card", + "count": 2, + "addMaxCount": 1, + "colors": [ + "White" + ], + "rarity": [ + "Common", + "Uncommon" + ], + "colorType": "MonoColor" + }, + { + "type": "card", + "count": 1, + "colors": [ + "White" + ], + "rarity": [ + "Rare", + "Uncommon" + ], + "colorType": "MonoColor" + }, + { + "type": "card", + "count": 2, + "addMaxCount": 1, + "colors": [ + "White" + ], + "rarity": [ + "Common", + "Uncommon" + ], + "colorType": "MonoColor" + }, + { + "type": "card", + "count": 1, + "colors": [ + "White" + ], + "rarity": [ + "Rare", + "Uncommon" + ], + "colorType": "MonoColor" + } + ] + } + ], + "name": "Make no mention of what you found. (Complete Quest)", + "text": "(-1 Reputation) The captain keeps his eyes on you as you leave, but says nothing further.", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + "failureDialog": { + "text": "The $(enemy_2) has escaped, and will likely be trouble again in the future. (-2 town reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Shards, white commons & uncommons", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Begin the chase", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 15, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Hunt a $(enemy_2)", + "description": "Bring the $(enemy_2) to justice before they can escape.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 30, + "objective": "Hunt", + "enemyTags": [ + "BiomeWhite", + "Human" + ], + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Head back to town to collect the $(enemy_2) bounty", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 7, + "isTemplate": true, + "name": "Sacred Sands", + "description": "Find and slay the $(enemy_2) before it escapes.", + "offerDialog": { + "text": "Stepping out of the cool shade of the local tavern, you find yourself face to face with a Viashino adorned in tribal garb.", + "options": [ + { + "action": [ + null + ], + "name": "\"...Can I help you?\"", + "text": "\"Shaman Izka hunts defilers of sacred sands. You will help.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"That wasn't what I meant, but... sure. Sacred vengeance sounds pretty rare around here.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "" + } + ], + "name": "\"I don't like sand.\" (Decline Quest)", + "text": "The viashino's tail whips back and forth. \"Szil will remember this.\" (-2 reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "$(poi_1)" + } + ], + "name": "You walk by without slowing. (Decline Quest)", + "text": "The Viashino's tail twitches as you walk by, but they let you go. (-1 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "The Viashino holds still for a moment, regarding you with a long evaluating look. \"Shaman Cresh thanks you, and wishes your eggs to hatch well.\" (+3 local reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + }, + { + "grantRewards": [ + { + "type": "shards", + "count": 5 + }, + { + "type": "card", + "count": 3, + "colors": [ + "Red" + ], + "rarity": [ + "Common", + "Uncommon" + ] + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "text": "The $(enemy_2) will not be receiving vengeance today. (-2 town reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Shards, red commons & uncommons", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town to begin the hunt", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 15, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Hunt a $(enemy_2)", + "description": "Find and defeat the $(enemy_2) before it escapes. You only get one shot, so make it count.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 30, + "objective": "Hunt", + "enemyTags": [ + "BiomeRed" + ], + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Head back to town to collect the $(enemy_2) bounty", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 8, + "isTemplate": true, + "name": "Remote Instruction", + "description": "Find the $(enemy_2) before it escapes and put on a show", + "offerDialog": { + "text": "A robed wizard leads a more mundane dressed individual over to you. \"You there, you are a battle mage, yes?\"", + "options": [ + { + "name": "\"Among other things, yes.\"", + "text": "He turns to his companion. \"As you can see, the drab appearance was a dead giveaway.\"", + "options": [ + { + "name": "You clear your throat.", + "text": "The wizard turns back to you. \"My new apprentice believes that he wishes to be a battle mage himself. To dissuade him, I shall have him scry over you as you fight a $(enemy_2).\"", + "options": [ + { + "name": "You pause for a moment, considering your words carefully. \"I'm not so sure I'm comfortable with that.\" (Decline Quest)", + "text": "The wizard frowns and vanishes. His confused companion turns around and walks back the way they had come from. (-1 town reputation)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "You laugh. \"Then I shall put on a show for him, so long as he doesn't blink. Point me to your $(enemy_2).\" (Accept Quest)" + } + ] + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "$(poi_1)" + } + ], + "name": "\"And a busy one as well.\" (Decline Quest)", + "text": "The wizard frowns and vanishes. His confused companion turns around and walks back the way they had come from. (-1 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "\"Who's asking?\"", + "text": "\"I am Morelith The Great, however it is my foolish apprentice who is asking through me.\" The younger man winces and looks away, seemingly shamed.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 1, + "POIReference": "" + } + ], + "name": "\"Oh my! Of course I didn't recognize the fabled master of illusions! What can I do for you?\"", + "text": "(+1 reputation) \"My new apprentice believes that he wishes to be a battle mage himself. To dissuade him, I shall have him scry over you as you fight a $(enemy_2).\"", + "options": [ + { + "name": "You pause for a moment, considering your words carefully. \"I'm not so sure I'm comfortable with that.\" (Decline Quest)", + "text": "Morelith turns to his apprentice and speaks sternly. \"Not only are battle mages generally incompetent, they are rude as well.\"", + "options": [ + { + "name": "You consider a much more aggressive response than you gave before, but think better of it. (Leave)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "You laugh. \"Then I shall put on a show for him, so long as he doesn't blink. Point me to your $(enemy_2).\" (Accept Quest)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "" + } + ], + "name": "\"Morelith The Great? More or less average is more like it. I don't have time for this.\" (Decline Quest)", + "text": "The indignant wizard teleports himself and his companion away. The marketplace crowd takes notice and quiets slightly. Perhaps Morelith is an important figure here. (-2 town reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "You feel a sense of elation joining the eery feeling that some has been watching you. You also wonder if you heard an indignant huff, or you just imagined it. Regardless, your pockets bulge with conjured rewards. (+3 local reputation)", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "count": 1, + "colors": [ + "Blue" + ], + "rarity": [ + "Uncommon", + "Common" + ], + "subTypes": [ + "Illusion" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Blue" + ], + "rarity": [ + "Common" + ] + }, + { + "type": "card", + "probability": 0.8, + "count": 1, + "colors": [ + "Blue" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 1, + "colors": [ + "Blue" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "shards", + "count": 2, + "addMaxCount": 3 + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "text": "You now feel as though you are being both watched AND mocked. (-2 town reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Shards, blue commons & uncommons", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town to begin the hunt", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 15, + "objective": "Leave", + "prologue": {}, + "epilogue": { + "text": "No more than a step out of the town gates, you have a sudden and unshakable feeling that you are being watched.", + "options": [ + { + "name": "(Continue)" + } + ] + }, + "POIToken": "" + }, + { + "id": 2, + "name": "Hunt a $(enemy_2)", + "description": "Find and defeat the $(enemy_2) before it escapes. You only get one shot, so make it count.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 30, + "objective": "Hunt", + "enemyTags": [ + "BiomeBlue" + ], + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Head back to town to collect the $(enemy_2) bounty", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 9, + "isTemplate": true, + "name": "Waste 'em", + "description": "Find and slay the $(enemy_2) before it escapes.", + "offerDialog": { + "text": "A job board has been constructed outside the local inn, and you see that it is covered in various papers and posters.", + "options": [ + { + "name": "You walk away, having your own goals in mind already. (Decline Quest)" + }, + { + "name": "You take a moment to look over the board.", + "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", + "options": [ + { + "name": "You review a new sheet of paper with a basic but clear sketch on it.", + "text": "Not a word is written upon it, but the drawing is clearly a $(enemy_2).", + "options": [ + { + "name": "\"An artist like that should find their way to a larger city.\" You walk away, impressed but uninterested. (Decline Quest)" + }, + { + "name": "Curious as to why this would be on the board, your gaze lingers for a moment.", + "text": "As you look at the wordless paper, words find their way in to your mind by unknown other means. 'FIND.' '{COLOR=red}KILL!{ENDCOLOR}' 'REWARD.'", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "$(poi_1)" + } + ], + "name": "'YES.' (Accept Quest)" + }, + { + "name": "'REWARD?'", + "text": "'{COLOR=red}KILL!{ENDCOLOR}.' 'REWARD.'", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "$(poi_1)" + } + ], + "name": "'YES.' (Accept Quest)" + }, + { + "name": "Unnerved by the situation, you shake your head and walk away. (Decline Quest)" + } + ] + }, + { + "name": "You decide that the invasive thoughts, if you can call them that, are unwelcomed, and you take a step back.", + "text": "The thoughts urgently follow you for a moment. '{COLOR=red}KKKKiiiiill...{ENDCOLOR}' But as you take another step back, the words vanish from your mind." + "options": [ + {"name": "With no delay at all, you walk away. Far away. (Decline Quest)"} + ] + } + ] + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "" + } + ], + "name": "You look over an old and tattered sheet, with two faded but mostly legible colors of ink upon it.", + "text": "What follows is the saddest marriage proposal you've ever heard of, let alone read first hand. Firstly, it's addressed \"to whom it may concern.\"", + "options": [ + { + "name": "You continue to read.", + "text": "Secondly, another handwriting has scrawled over what might have actually been a romantic bit with the following. \"Don't bother. I killed him yesterday\"", + "options": [ + { + "name": "You shake your head and walk away. (Decline Quest)" + } + ] + }, + { + "name": "You spare yourself from the details and walk away. (Decline Quest)" + } + ] + }, + { + "name": "A very colorful advertisement catches your eye.", + "text": "{RAINBOW=2;2;1;0.8}CIRCUS OF SHANDALAR, COMING SOON TO EVERYWHERE!!!!", + "options": [ + { + "name": "You're not sure what you were looking for, but a circus wasn't it. (Decline Quest)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "Your unknown employer is still nowhere to be seen, and is not heard from again either. But you find a box waiting for you beneath the job board. The box is warded, as the scorch marks off to one side and smell of burnt hair confirm, but it opens at your approach. (+3 local reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + }, + { + "grantRewards": [ + { + "type": "shards", + "count": 5 + }, + { + "type": "card", + "count": 1, + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 1, + "rarity": [ + "Common" + ], + "colorType": "Colorless" + }, + { + "type": "card", + "count": 1, + "rarity": [ + "Common" + ] + }, + { + "type": "card", + "count": 1, + "rarity": [ + "Uncommon" + ], + "colorType": "Colorless" + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "text": "The $(enemy_2) eludes you. (-2 town reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Shards, commons & uncommons", + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town to begin the hunt", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 15, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Hunt a $(enemy_2)", + "description": "Find and defeat the $(enemy_2) before it escapes. You only get one chance, so give it your best shot.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 30, + "objective": "Hunt", + "enemyTags": [ + "BiomeColorless" + ], + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Head back to town to collect the $(enemy_2) bounty", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 10, + "isTemplate": true, + "name": "Room for New Growth", + "description": "Clear out all enemies in the $(poi_1) and report back", + "offerDialog": { + "text": "A druid approaches you. \"Will you help save our world?\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "What do you think I'm trying to do? (Decline Quest)", + "text": "Stunned, the Druid watches you leave. (-1 local reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "\"Certainly. What tiny woodland creatures need rescuing today?\"", + "text": "The druid shakes her head. \"Most of them, but they are not your direct concern.\"", + "options": [ + { + "name": "\"And what is, then?\"", + "text": "\"The inhabitants of the nearby $(poi_1). They must be removed for the sake of balance and to ensure space is available for new life to grow.\" She nods as though this were an indisputable fact.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Huh. Not the way I thought this was going to go, but... sure. Lead the way.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "\"Sorry, I've got bigger things to worry about right now.\" (Decline Quest)", + "text": "(-1 Local Reputation) The druid keeps a passive look on her face. \"Soon those things will be balanced as well.\"", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + { + "name": "\"I'm beginning to think that is my role in life. What can I do for you?\"", + "text": "\"The inhabitants of the nearby $(poi_1) must be removed for the sake of balance and to ensure space is available for new life to grow.\" She nods as though this were an indisputable fact.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Saving the world, one defeated enemy at a time.\" (Accept Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "\"I'm not entirely sure I have time for that right now.\"", + "text": "The druids face remains unchanged, but her voice grows a touch more quiet. \"The forest will remember this.\" (-1 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + } + ], + "text": "You return to town, and find the druid waiting for you just outside of it. \"Balance has been restored.\" (+3 Local Reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + }, + { + "grantRewards": [ + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Green" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Green" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Mythic Rare", + "Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ] + } + ] + } + ], + "name": "\"Almost. I believe there's a reward due to level the scales.\"", + "text": "(-1 Local Reputation) The druid frowns slightly, but hands you a bundle wrapped in small vines.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + } + ] + }, + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + { + "action": [ + { + "grantRewards": [ + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Green" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Green" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Mythic Rare", + "Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Green" + ] + }, + { + "type": "shards", + "count": 15 + } + ] + } + ], + "name": "You nod. \"As it should be.\"", + "text": "The druid hands you a bundle wrapped in small vines.", + "options": [ + { + "name": "(Complete Quest)" + } + ] + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "After some reflection, you decide that the rewards promised to you are not worth the effort of clearing out the current occupants of the $(poi_1). (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 1, + "name": "Clear", + "description": "Travel to the $(poi_1) and defeat all enemies inside. The target location is in the Forest biome.", + "mapFlag": "", + "mapFlagValue": 1, + "POITags": [ + "BiomeGreen", + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to town and report your success in clearing the $(poi_1).", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 11, + "isTemplate": true, + "name": "Eviction Notice", + "description": "Clear all enemies from a dungeon", + "offerDialog": { + "text": "As you approach the town square, a man climbs down from a packed wagon. He glances around, then walks toward you.\"You there, you look like a capable individual!\"", + "options": [ + { + "name": "\"Capable just so happens to be my middle name.\"", + "text": "He looks perplexed for a moment, but glances back at the wagon as though distracted by it. \"I was hoping you could handle some business for me\"", + "options": [ + { + "name": "Business? What sort of business?", + "text": "\"Well, some folks here in town are looking to move on, start fresh somewhere new. We had a spot picked out to settle down, but... it appears to be occupied.\"", + "options": [ + { + "name": "\"I see. And I suppose you're looking for the current occupants to be removed?", + "text": "\"Yes! Exactly that! After all, I have this deed right here stating that we own the land!\" He briefly flashes some papers, but you notice some of the ink has smeared ink on them.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Well then, I hope your new settlement will remember me fondly when setting prices.\" You make note of the location and promise to clear it out. (Accept Quest)" + }, + { + "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" + } + ] + }, + { + "name": "\"And why exactly do you need me?\"", + "text": "\"Well, because I'm no good in a fight while I'm protecting my belongings!\" He glances back at the cart once again.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Fine, fine... I hope you've got something in that wagon to make it worth my while.\" You make note of the location and set off to clear it. (Accept Quest)" + }, + { + "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" + } + ] + } + ] + } + ] + }, + { + "name": "\"Capable and in a hurry.\" You brush past him and continue on your way. (Decline Quest)" + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "As soon as you turn to head back to town, you hear the squeaking of wagon wheels in the distance. Your employer comes in to view, and drives his wagon right up to you.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "rarity": [ + "Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 2 + } + ] + } + ], + "name": "\"You might want to spend some time cleaning it before you move in, but it's all yours.\" (Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "You decide that the rewards promised to you are not worth clearing out the current occupants of the $(poi_1). They were here first anyway. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 1, + "name": "Clear", + "description": "Travel to the $(poi_1) and defeat all enemies inside.", + "mapFlag": "", + "mapFlagValue": 1, + "count2": 25, + "POITags": [ + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Leave", + "description": "Exit the newly cleared (but not yet cleaned) dungeon", + "mapFlag": "", + "mapFlagValue": 1, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 12, + "isTemplate": true, + "name": "A Freshly Plowed Field", + "description": "Clear out all enemies in the $(poi_2) and report back", + "offerDialog": { + "text": "\"We need a new field to increase our harvest.\" A weathered but intimidating man in simple farmer's garb addresses you directly. \"This town is growing faster than my grain.\"", + "options": [ + { + "name": "A no-nonsense man deserves a no-nonsense reply. \"I can handle that.\"", + "text": "\"Good. I just need you to clear the current occupants of the area I'd like to plant. They've been bothering our farm anyway.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Consider it done.\" (Accept Quest)" + }, + { + "name": "\"Ah, there's the catch. No thanks.\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"They'll be fertilizing your grain in no time.\" (Accept Quest)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "\"That doesn't seem to be my problem.\" (Decline Quest)", + "text": "(-1 Local Reputation) He shrugs and moves on. ", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "You've barely finished clearing the area, and the imposing farmer is already preparing to harness one of his animals to a plow outside. Seeing you approach, he tosses you a satchel. \"Thanks.\" He then gets back to work.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "White" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "White" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "White" + ], + "rarity": [ + "Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "White" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 2, + "colors": [ + "White" + ] + } + ] + } + ], + "name": "A man of few words, but he pays well enough. (Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "text": "After some reflection, you decide that the rewards promised to you are not worth the effort of clearing out the current occupants of the $(poi_2). (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 1, + "name": "Clear", + "description": "Travel to the $(poi_1) and defeat all enemies inside. The target location is in the Plains biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 35, + "POITags": [ + "BiomeWhite", + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Leave", + "description": "Leave town to begin your quest.", + "mapFlag": "", + "mapFlagValue": 1, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 13, + "isTemplate": true, + "name": "The Onyx Compass", + "description": "Clear out all enemies in the $(poi_2) and report back", + "offerDialog": { + "text": "\"You. Come here.\" The gnome speaking to you seems very out of place here. He wears a white pristine robe that was either a shirt or custom tailored for him. He acts like he belongs and that he owns the place, however.", + "options": [ + { + "name": "Walk over without a word.", + "text": "The gnome gives a satisfied grunt, and continues to issue orders. He puts a small satchel in your hands \"Here. Take this compass. Follow it, and remove anything it points to. When it no longer points, return to me.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "You can't resist slipping one thing in: \"Understood. I'll be back shortly.\" (Accept Quest)", + "text": "The joke goes over his head.", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "You glance down at the satchel, surprised at its weight.", + "text": "The gnome demands your attention again before you can investigate. \"Are you deaf and dumb, or just dumb? GET MOVING!!!\" (-1 Local Reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "You drop the satchel. \"What?\" (Decline Quest)", + "text": "He snatches up the cloth sack and walks away, cursing you the whole way. (-1 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 1, + "POIReference": "" + } + ], + "name": "You look back up (slightly) to meet his glare. \"Understood, sir.\" (Accept Quest)", + "text": "\"That's more like it.\" (+1 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "You hold out your hand. \"Sorry, must be this tall to give orders\" (Decline Quest)", + "text": "(-1 Local Reputation) He scowls and stomps away, one tiny step at a time.", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "name": "\"What do you need?\"", + "text": "\"No questions. Just listen.\" He puts a small satchel in your hands. \"Take this compass. Follow it, and remove anything it points to. When it no longer points, return to me.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "You glance down at the satchel, surprised at its weight.", + "text": "The gnome demands your attention again before you can investigate. \"Are you deaf and dumb, or just dumb? GET MOVING!!!\" (-1 Local Reputation)", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "You drop the satchel. \"What?\" (Decline Quest)", + "text": "He snatches up the cloth sack and walks away, cursing you the whole way. (-1 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 1, + "POIReference": "" + } + ], + "name": "You look back up (slightly) to meet his glare. \"Understood, sir.\" (Accept Quest)", + "text": "\"That's more like it.\" (+1 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "Got it. (Accept Quest)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + } + ], + "text": "Another gnome, slightly taller than the first but not dressed as finely, meets you at the gate. \"The compass, if you please.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Black" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Black" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Black" + ], + "rarity": [ + "Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Black" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 2, + "colors": [ + "Black" + ] + } + ], + "issueQuest": "", + "POIReference": "" + } + ], + "name": "You hand it over in its satchel, and he gives you a larger yet seemingly lighter bag in return. (Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -3, + "POIReference": "$(poi_2)" + } + ], + "text": "Despite the insistance of the needle you decide that you will not finish clearing the $(poi_2). As if it could sense this somehow, the onyx compass disappears. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 3, + "name": "Leave", + "description": "Leave town to begin your quest", + "mapFlag": "", + "mapFlagValue": 1, + "objective": "Leave", + "prologue": {}, + "epilogue": { + "text": "You retrieve the compass from its pouch as you approach the town's gate. It is made of a deeply dark stone, with a single red needle that indicates where to find your targets.", + "options": [ + { + "name": "You take note of the direction and head off that way, putting the device away for now. (Continue)" + }, + { + "name": "You take a closer look at the device.", + "text": "The 'compass' is unlike most you have ever seen before. There is not a single marking on it anywhere, nor any color other than onyx save the crimson needle.", + "options": [ + { + "name": "You put the compass away and carry on. (Continue)" + }, + { + "name": "You look closer at the needle.", + "text": "The needle points unerringly in a single direction, no matter which way or how quickly you turn the device. You reach out and touch it, and find that you are unable to move the needle yourself.", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + } + ] + }, + "POIToken": "" + }, + { + "id": 2, + "name": "Clear", + "description": "Travel to the $(poi_2) and defeat all enemies inside. The target location is in the Swamp biome", + "mapFlag": "", + "mapFlagValue": 1, + "count2": 25, + "POITags": [ + "BiomeBlack", + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": { + "text": "You check your compass, looking for your next target, only to find that the needle has disappeared entirely. Your task appears to be complete." + }, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 3, + "name": "Travel", + "description": "Return to town and report your success in clearing the $(poi_2).", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 14, + "isTemplate": true, + "name": "A Vision of Destruction", + "description": "Clear out all enemies in the $(poi_1) and report back", + "offerDialog": { + "text": "Walking in to the village, an old man looks up as if expecting you and rushes over (to the extent that he is able) \"$(playername). I need you to turn around and leave. NOW.\"", + "options": [ + { + "name": "You take a good look at the old man, but do not recognize his features. \"Should I know you?\"", + "text": "\"No.\" He shakes his head. \"But I have had a vision. A vision of destruction, fire, and ruin. And of you.\"", + "options": [ + { + "name": "You give a wry grin. \"I was under the impression that destruction, fire, and ruin were popular hobbies around here.\"", + "text": "(-1 Local Reputation) He scowls at you and continues. \"This is no laughing matter. A spawn of Lathliss seeks a new home, and will come here soon unless we intervene.\"", + "options": [ + { + "name": "\"A fight with a dragon? I look forward to the challenge!\"", + "text": "He shakes his head. \"It is a fight that you would win. But in the process, our village would be lost. We MUST prevent the beast from arriving.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"How do we do that?\"", + "text": "\"Before it comes here, the dragon will attempt to make a home at a $(poi_1) nearby. It will find several unfriendly occupants already there. But if you were to remove them in advance, I believe the creature will nest there and spare our village.\"", + "options": [ + { + "name": "\"Fight off all the creatures in a $(poi_1) and then a dragon too? No thanks. (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Point the way.\" (Accept Quest)" + } + ] + }, + { + "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" + } + ] + }, + { + "name": "\"A fight with a dragon? No thanks. I'll be leaving now.\" (Decline Quest)", + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "addMapReputation": 1, + "POIReference": "" + } + ], + "name": "\"Tell me more about this vision, elder.\"", + "text": "(+1 Local Reputation) He continues with great urgency. \"A spawn of Lathliss seeks a new home, and will come here soon unless we intervene.\"", + "options": [ + { + "name": "\"A fight with a dragon? No thanks. I'll be leaving now.\" (Decline Quest)" + }, + { + "name": "\"A fight with a dragon? I look forward to the challenge!\"", + "text": "He shakes his head. \"It is a fight that you would win. But in the process, our village would be lost. We MUST prevent the beast from arriving.\"", + "options": [ + { + "name": "\"How do we do that?\"", + "text": "\"Before it comes here, the dragon will attempt to make a home at a $(poi_1) nearby. It will find several unfriendly occupants already there. But if you were to remove them in advance, I believe the creature will nest there and spare our village.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Point the way.\" (Accept Quest)" + }, + { + "name": "\"Fight off all the creatures in a $(poi_1) and then a dragon too? No thanks. (Decline Quest)" + } + ] + } + ] + } + ] + } + ] + }, + { + "name": "\"I'll take my chances, thanks.\" (Decline Quest)", + "text": "He exclaims at you as you walk by. \"Leave. NOW!!! You must leave!!!\" (-2 local reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + } + ], + "text": "The elder meets you outside the gates of the village as you return, mounted on horseback. He hands you a pouch containing your rewards. (+3 Local Reputation)", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Red" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Red" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Red" + ], + "rarity": [ + "Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Red" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 2, + "colors": [ + "Red" + ] + } + ] + } + ], + "name": "\"Going somewhere?\"", + "text": "\"My vision was less than specific about whether or not it would be changed by your actions. So... yes.\"", + "options": [ + { + "name": "You glance around at a clear sky warily before going on in to town. (Complete Quest)" + } + ] + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -3, + "POIReference": "$(poi_2)" + } + ], + "text": "After some reflection, you decide to abandon clearing out $(poi_1). The villagers will be fine, right? (-3 Local Reputation)", + "options": [ + { + "name": "(continue)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 1, + "name": "Clear", + "description": "Travel to the $(poi_1) and defeat all enemies inside. The target location is in the Mountain biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 35, + "POITags": [ + "BiomeRed", + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": { + "text": "The $(poi_1) falls silent as you remove the last creature. You consider staying to welcome the dragon, should it appear, but something tells you that leaving would be a much better idea for now.", + "options": [ + { + "name": "(Continue)" + } + ] + }, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to town and report your success in clearing the $(poi_1).", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 15, + "isTemplate": true, + "name": "A Private Island", + "description": "Clear out all enemies in the $(poi_1) and report back", + "offerDialog": { + "text": "\"Excuse me, adventurer, but I'm in need of assistance.\" The man appears of modest means at first glance, but a closer inspection reveals that his average looking clothing is may as well be made for a king.", + "options": [ + { + "name": "\"Of course, what can I do for you?\"", + "text": "He bows to you slightly. \"My name is Lazarus, a humble servant of nobles who wish to remain anonymous.\"", + "options": [ + { + "name": "\"And what do these anonymous nobles desire?\"", + "text": "\"To remain anonymous. And to take an island vacation. I have identified a spot which meets all of their requirements, but it is currently occupied.\"", + "options": [ + { + "name": "\"I see. And I suppose you're looking for the current occupants to be removed?\"", + "text": "\"Precisely. Do so, and you will be well rewarded.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Well I've always wanted to be owed a favor from the rich and powerful. I'll do it.\" (Accept Quest)" + }, + { + "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" + } + ] + }, + { + "name": "\"I must decline. I respect the local inhabitants far more than faceless nobility.\" (Decline Quest)", + "text": "He gives you the smallest bow imaginable, just enough to say that one was given without indicating respect.", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + { + "name": "\"I don't like not knowing who I am working for\"", + "text": "\"You know me, and you will work for me.\"", + "options": [ + { + "name": "\"Sorry, but knowing your name is not knowing you. I'm not interested.\" (Decline Quest)" + }, + { + "name": "\"Very well, Lazarus, what can I do for you?\"", + "text": "\"My lieges wish for a private island location for a vacation, far from prying eyes and ears. I wish for you to make certain that it will be clear for them.\"", + "options": [ + { + "name": "\"Sorry, that sounds like more than I have time for at the moment.\" (Decline Quest)", + "text": "He simply nods and walks away in search of someone else to aid him." + }, + { + "name": "\"Consider it done.\" (Accept Quest)" + } + ] + } + ] + } + ] + }, + { + "name": "You can't put your finger on it, but something seems off about the man. \"This isn't a good time.\" (Decline Quest)", + "text": "He gives you the smallest bow imaginable, just enough to say that one was given without indicating respect. (-1 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "With gentrification of the area on the behalf of nobility complete, you console your conscience with the rewards that materialize in front of you.", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Blue" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Blue" + ], + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "colors": [ + "Blue" + ], + "rarity": [ + "Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "colors": [ + "Blue" + ], + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 2, + "colors": [ + "Blue" + ] + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "After some reflection, you decide that the rewards promised to you are not worth the effort of clearing out the current occupants of the $(poi_1). (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 1, + "name": "Clear", + "description": "Travel to the $(poi_1) and defeat all enemies inside. The target location is in the Island biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count2": 25, + "POITags": [ + "BiomeBlue", + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to town and report your success in clearing the $(poi_1).", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 16, + "isTemplate": true, + "name": "Clearing the ledger", + "description": "Clear out all enemies in the $(poi_1)and report back", + "offerDialog": { + "text": "As you introduce yourself to the inside of the local inn for the night, another patron approaches you.", + "options": [ + { + "name": "\"Can I help you?\"", + "text": "He nods. \"I hope so. You have the air of a powerful sorcerer, and I'm hoping that means that you can.\"", + "options": [ + { + "name": "You wait for him to continue.", + "text": "\"I've come in to an inheritance of a small estate that I've been expecting for years. Recently, I've had some hard times, and I've convinced some individuals to let me borrow against the land.\" ", + "options": [ + { + "name": "\"I see.\" You think you know where this is headed.", + "text": "The man looks sheepish. \"Unfortunately, I found that the land isn't exactly usable at the moment. Because it's occupied. Would you be willing to clear it for me in exchange for other parts of the inheritance?\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"So long as I get to keep whatever I find along the way too.\" (Accept Quest)." + }, + { + "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" + } + ] + }, + { + "name": "\"And why exactly do you need me?\"", + "text": "\"Well, it seems the land isn't empty, and I need someone to fix that. You look like the sort that could handle it.\" ", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"I'll do it. But I hope the dearly departed left you something else of value.\" (Accept Quest)" + }, + { + "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" + } + ] + } + ] + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "\"You have the wrong person. I promise you that.\" (Decline Quest)", + "text": "(-1 Local Reputation) A few other people in the inn stop and look, but the man walks away without making a scene.", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "True to his word, the man provides you with a reward from his inheritance. It's worth far less than the land (now that it's been cleared), but it's still valuable in addition to what loot you already recovered in the $(poi_1).", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "count": 2, + "rarity": [ + "Uncommon" + ] + }, + { + "type": "card", + "count": 2, + "rarity": [ + "Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 1, + "rarity": [ + "Rare", + "Mythic Rare" + ] + }, + { + "type": "card", + "probability": 0.5, + "count": 2 + }, + { + "type": "gold", + "count": 200, + "addMaxCount": 200 + } + ] + } + ], + "name": "\"If you're not settling there, I suggest you sell the land quickly before anyone else moves in.\" (Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "You decide not to clear out the $(poi_1). Surely your client's creditors will understand. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "rewardDescription": "Mana Shards, Uncommon & Rare cards", + "stages": [ + { + "id": 1, + "name": "Clear", + "description": "Travel to the $(poi_1) and defeat all enemies inside. The target location is in the Wasteland biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 35, + "POITags": [ + "BiomeColorless", + "Hostile", + "SideQuest" + ], + "objective": "Clear", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to town and report your success in clearing the $(poi_1).", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 17, + "isTemplate": true, + "name": "Bone Collector", + "description": "Defeat 3 $(enemy_1)s", + "offerDialog": { + "text": "A job board has been constructed outside the local inn, and you see that it is covered in various papers and posters.", + "options": [ + { + "name": "You walk away, having your own goals in mind already. (Decline Quest)" + }, + { + "name": "You take a moment to look over the board.", + "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", + "options": [ + { + "name": "You look at what seems to be an advertisment of some sort off to one side.", + "text": "It reads: \"Gimgee's self-replicating paper. When you need unlimited paper or to clear a forest from afar, it's got to be Gimgee's\".", + "options": [ + { + "name": "\"I'll file that away under things that make sense yet don't.\" (Decline Quest)" + } + ] + }, + { + "name": "A folded piece of paper is nailed to the board. ", + "text": "The visible portion says 'Take one' in clear and measured handwriting.", + "options": [ + { + "name": "Something isn't right about this, so you walk away instead. (Decline Quest)" + }, + { + "name": "\"One?\" You pull on the nail but it is firmly embedded, so you rip the paper off of it instead.", + "text": "You could swear it wasn't there before, but an identical copy remains as you pull down the note.", + "options": [ + { + "name": "This is just weird. You drop the paper and leave. (Decline Quest)" + }, + { + "name": "Undeterred and intrigued, you open the paper and begin to read.", + "text": "\"$(playername): Please harvest at least three $(enemy_1) and bring them to the tavern on a Tuesday\".", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Well that's not weird at all. Why not?\" (Accept Quest)" + }, + { + "name": "\"Nope. Nope nope nope...\" You drop the paper and walk away, casting a small spell to cause the letter to fall apart and blow away on the wind. (Decline Quest)" + } + ] + } + ] + } + ] + }, + { + "name": "A short note is written in red ink.", + "text": "\"{COLOR=red}Have fangs, will travel{ENDCOLOR}\"." + "options": [ + {"name": "You suddenly realize that isn't ink, and step away. (Decline Quest)"} + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "You feel awkward pulling your $(enemy_1)s in to town, but it doesn't actually seem that out of place here with other macabre scenes around. (This quest will only given in black biome in the future).", + "options": [ + { + "name": "You look around for someone that seems to be expecting bodies.", + "text": "Sure enough, a necromancer stands outside the inn with two wagons behind him, he is obviously your contact.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + }, + { + "grantRewards": [ + { + "type": "card", + "count": 2, + "addMaxCount": 2, + "colors": [ + "Black" + ], + "rarity": [ + "Common" + ] + }, + { + "type": "gold", + "count": 500 + }, + { + "type": "shards", + "count": 15 + } + ] + } + ], + "name": "You dump the $(enemy_1s) on to one of the wagons and collect your rewards. (+3 Local Reputation)" + }, + { + "name": "You take a closer look at the carts.", + "text": "$(enemy_1)s and a few random creatures are filling most of one cart., while the other holds a few identical satchels of goods.", + "options": [ + { + "name": "Turn your attention to the carts' attendant.", + "text": "The pale skinned man speaks with a crackling voice, but the words seem reflexive to him at this point. \"Place them there. Take one package.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + }, + { + "grantRewards": [ + { + "type": "gold", + "count": 500 + }, + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "count": 2, + "addMaxCount": 2, + "colors": [ + "Black" + ], + "rarity": [ + "Common" + ] + } + ] + } + ], + "name": "You dump the $(enemy_1s) and collect your rewards. (+3 Local Reputation)" + } + ] + } + ] + } + ] + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "3 $(enemy1)s are proving to be too much trouble to be worth your time, whether your mysterious client knows your name or not. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "rewardDescription": "Gold and Mana Shards", + "stages": [ + { + "id": 1, + "name": "Defeat $(enemy_1)s", + "description": "Sometimes it pays to specialize. Cash in on a local bounty by bringing in the remains of 3 $(enemy_1)s. They can usually be found in the Swamp biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 3, + "objective": "Defeat", + "enemyTags": [ + "BiomeBlack" + ], + "prologue": {}, + "epilogue": { + "text": "With the necessary $(enemy_1)s handled, it's time to go collect your rewards. (Don't forget you can track the quest to get directions back to town)", + "options": [ + { + "name": "(Continue)" + } + ] + }, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to where the quest began to turn in the reagents and collect your rewards.", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + } + ] +}, +{ + "id": 18, + "isTemplate": true, + "name": "A Focused Mind", + "description": "Defeat 3 $(enemy_2)s", + "offerDialog": { + "text": "A job board has been constructed outside the local inn, and you see that it is covered in various papers and posters.", + "options": [ + { + "name": "You walk away, having your own goals in mind already. (Decline Quest)" + }, + { + "name": "You take a moment to look over the board.", + "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", + "options": [ + { + "name": "You look at what seems to be an advertisment of some sort off to one side.", + "text": "\"A focused mind receives great rewards. Focus on defeating 3 $(enemy_2)s, and be rewarded.\"", + "options": [ + { + "name": "With no further information listed, you doubt this is actually worth your time. (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"What else was I going to do? Go save the world?\" (Accept Quest)" + } + ] + }, + { + "name": "A short note is written in blue ink.", + "text": "You read the note: '3 $(enemy_2)s? What for?'", + "options": [ + { + "name": "\"What for, indeed?\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "One way to find out. (Accept Quest)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "\"Well done.\" You turn quickly to find a Djinn floating behind you. \"You have demonstrated great focus.\" A collection of treasures float over to you from his outstretched hand.", + "options": [ + { + "action": [ + + { + "grantRewards": [ + { + "type": "card", + "count": 2, + "addMaxCount": 2, + "colors": [ + "Blue" + ], + "rarity": [ + "Common" + ] + }, + { + "type": "gold", + "count": 500 + }, + { + "type": "shards", + "count": 15 + } + ] + } + ], + "name": "Warily take the items.", + "text": "No sooner than you do, the Djinn dissapears in a puff of smoke. When you turn back, the $(enemy_2) you just defeated has vanished as well.", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "$(poi_1)" + } + ], + "name": "Was this all a test of some sort? (+3 Local Reputation) (Complete Quest)" + } + ] + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_1)" + } + ], + "text": "You decide you have better things to do than hunt $(enemy_2)s, and you scratch this entry out of your logbook. (-2 local reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "stages": [ + { + "id": 1, + "name": "Leave", + "description": "Leave town to begin your quest", + "mapFlag": "", + "mapFlagValue": 1, + "objective": "Leave", + "prologue": {}, + "epilogue": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Defeat $(enemy_2)s", + "description": "Sometimes it pays to specialize. Cash in on a local bounty by bringing in the remains of 3 $(enemy_2)s. They can usually be found in the Island biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 3, + "objective": "Defeat", + "enemyTags": [ + "BiomeBlue" + ], + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + } + + ] +}, +{ + "id": 19, + "isTemplate": true, + "name": "Population Control", + "description": "Defeat 3 $(enemy_1)s", + "offerDialog": { + "text": "A haggard and tired looking elf puts down his bow at the door of the tavern. Another elf calls out to him. \"Long day's hunt?\"", + "options": [ + { + "name": "You're not a part of the conversation, so you tune it out. (Decline Quest)" + }, + { + "name": "Perhaps paying attention could be useful.", + "text": "The newcomer simply nods and slumps into a chair for a moment before actually replying. \"Yes, and more of them to come.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -1, + "POIReference": "" + } + ], + "name": "\"And what are you hunting, exactly?\"", + "text": "Both elves turn to face you before they move to a table to continue their conversation more privately. It appears that your interjection was undesired. (-1 Local Reputation)", + "options": [ + { + "name": "Mind your own business from there. (Decline Quest)" + } + ] + }, + { + "name": "You wait for one of them to say more.", + "text": "He continues, \"There's just too many $(enemy_1)s for the area to sustain. We need to thin their numbers, but there's always more. I don't suppose you've learned to aim?\"", + "options": [ + { + "name": "As his friend shakes his head with a hint of mirth, you decide to speak up. \"I could handle some of them for you.\"", + "text": "The hunter turns his attention to you and looks you over. It's most likely a formality, he would have sized you up coming in the door. \"I'll take you up on that.\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Keep the loot coming and you won't find another $(enemy_1) on its feet again.\" (Accept Quest)" + }, + { + "name": "\"On second thought, I have better things to do.\" (Decline Quest)", + "text": "Obviously annoyed at the pointless distraction, the two return to their conversation without you. (-1 Local Reputation)" + }, + { + "name": "\"What's the bounty?\"", + "text": "He thinks for a moment. \"Let's start with three as a trial. And we will pay well for them, as I doubt you're doing this for the village.\"", + "options": [ + { + "name": "\"That's awfully ambiguous. Why don't I come back when you have an actual offer?\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"And if I bring in six?\" (Accept Quest)", + "text": "\"Then you will have brought down too many.We're thinning them, not removing them.\"", + "options": [ + { + "name": "You nod and prepare to leave. (Continue)" + } + ] + } + ] + } + ] + }, + { + "name": "You don't especially feel like volunteering to take on a $(enemy_1) today. (Decline Quest)" + } + + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "POIReference": "" + } + ], + "name": "Bored with the conversation already, you finish your meal and leave. (Decline Quest)" + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": 3, + "POIReference": "" + } + ], + "text": "As promised, the village pays well, by way of their gamekeeper. \"The bounty of nature is priceless, but keeping populations in check increases the bounty of all goods.\" (+3 Local Reputation)", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "gold", + "count": 500 + }, + { + "type": "card", + "count": 2, + "addMaxCount": 2, + "colors": [ + "Green" + ], + "rarity": [ + "Common" + ] + } + ] + } + ], + "name": "(Continue)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "You decide you have better things to do than hunt $(enemy_1)s, and you scratch this entry out of your logbook. (-2 local reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "stages": [ + { + "id": 1, + "name": "Defeat $(enemy_1)s", + "description": "Sometimes it pays to specialize. Cash in on a local bounty by bringing in the remains of 3 $(enemy_1)s. They can usually be found in the Forest biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 3, + "objective": "Defeat", + "enemyTags": [ + "Animal", + "BiomeGreen" + ], + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to the hunter and report your success,", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + } + ] +}, +{ + "id": 201, + "isTemplate": true, + "name": "Proving Yourself Worthy", + "description": "Defeat 3 $(enemy_1)s", + "offerDialog": { + "text": "\"Are you worthy, citizen?\" A heavily armored soldier stands at the center of the town square and is occasionally calling out at bypassers. \"Are YOU?\", he calls out to another one.", + "options": [ + { + "name": "You approach the man, curious.", + "text": "\"You look worthy. I believe you are. Are you?\"", + "options": [ + { + "name": "\"Worthy of what, exactly?\"", + "text": "\"Are you worthy of being trained as a squire of Sir Kallus. Do you believe yourself to be?\"", + "options": [ + { + "name": "\"Who is Sir Kallus?\"", + "text": "He laughs as if the question was ridiculous. \"I am, of course. And I need someone to prove that they are worthy of my teachings!\"", + "options": [ + { + "name": "You decide to humor him. \"Let's say that I am, what then?\"", + "text": "He looks at you again, as though he hadn't actually paid attention to you before. \"Then you prove it. Defeat 3 $(enemy_1)s with honor.\"", + "options": [ + { + "name": "\"That was purely hypothetical. I am not worthy, and will be leaving now.\" (Decline Quest)" + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Challenge accepted.\" (Accept Quest)" + } + ] + }, + { + "name": "\"I'll let you know if I think of anyone.\" (Decline Quest)" + } + ] + }, + { + "name": "\"No.\" (Decline Quest)", + "text": "Your flat answer seems to stun him for a moment until another person walks into the square. He begins to call out to them, and he forgets you exist.", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + { + "name": "\"Who are you?\"", + "text": "\"Sir Kallus, of course!\"", + "options": [ + { + "name": "\"Are you?\"", + "text": "He looks at you with a hint of frustration. \"Yes, I am Sir Kallus.\"", + "options": [ + { + "name": "\"I mean are you worthy, Sir Kallus?\"", + "text": "He thinks for a moment, clearly working through this. \"I must be worthy of myself! I am! I am and I shall prove it.\"", + "options": [ + { + "name": "\"And how will you prove it, Sir Kallus?\"", + "text": "\"I shall do the same as I ask of you. Defeat 3 $(enemy_1)s. Once I finish, I will return here and wait for you.\"", + "options": [ + { + "name": "\"There. You're solving your own problems. Now get out of here.\" (Decline Quest)", + "text": "His brow furrows, not accustomed to being spoken to in such a manner. (-1 Local Reputation)", + "options": [ + { + "name": "\"Go on. Off with you!\" (Continue)" + } + ] + }, + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Challenge accepted.\" (Accept Quest)" + } + ] + } + ] + }, + { + "name": "\"Good\" You nod. \"I'm glad we've established that.\"", + "text": "He blinks several times, then resumes the conversation on his terms. \"Will you accept my challenge to defeat 3 $(enemy_1)s?\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Challenge accepted.\" (Accept Quest)" + }, + { + "name": "\"I am not interested.\" (Decline Quest)" + } + ] + }, + { + "name": "\"Then good day to you, Sir Kallus.\" (Decline Quest)" + } + ] + } + ] + } + ] + }, + { + "name": "You shake your head and keep walking, uninterested in whatever this is. (Decline Quest)" + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + { + "grantRewards": [ + { + "type": "shards", + "count": 15 + }, + { + "type": "gold", + "count": 500 + }, + { + "type": "card", + "count": 2, + "addMaxCount": 2, + "rarity": [ + "Common" + ] + } + ] + } + ], + "text": "You return to town, and see no sign of Sir Kallus. Instead, the merchants from the shops near the town square rush over to give you a hero's welcome and rewards of their own.", + "options": [ + { + "name": "\"Are $(enemy_1)s so despised around here?\"", + "text": "One of the merchants laughs. \"No, but Sir Kallus is. And since you left, we haven't seen him. Thank you, from the bottom of our hearts.\" (+3 Local Reputation)", + "options": [ + { + "name": "(Continue)" + } + ] + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "Sir Kallus isn't worth you being worthy of him. You scratch this item out of your notes. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "stages": [ + { + "id": 1, + "name": "Defeat $(enemy_1)s", + "description": "Sometimes it pays to specialize. Cash in on a local bounty by bringing in the remains of 3 $(enemy_1)s. They can usually be found in the Plains biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 3, + "objective": "Defeat", + "enemyTags": [ + "BiomeWhite" + ], + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to where the quest began to turn in the reagents and collect your rewards.", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + } + ] +}, +{ + "id": 21, + "isTemplate": true, + "name": "In the Name of Science", + "description": "Defeat 3 $(enemy_1)s", + "offerDialog": { + "text": "\"...but it's for SCIENCE!!!\" A young woman leaves the tavern in a hurry, with someone yelling at her back from the other side of the doorway. A dwarf in a labcoat with goggles on his head comes shuffling after.", + "options": [ + { + "name": "You observe the scene as it develops before you.", + "text": "With little hope of catching the damsel, he turns his attention to you. \"Can I interest you in assisting me with some scientific experiments?\"", + "options": [ + { + "name": "\"It really depends on what they are.\" You look at him suspsiciously.", + "text": "\"You're not a farmhand, so it will have to be.\" He thinks for a moment, pulling out a well worn notebook and flipping through the pages.", + "options": [ + { + "name": "\"Another time perhaps, I need to keep moving.\" (Decline Quest)", + "text": "The dwarf makes no effort to stop you. He seems so lost in his notes that he may not have even heard you.", + "options": [ + { + "name": "You leave while you can. (Continue)" + } + ] + }, + { + "name": "You wait to see what he comes up with.", + "text": "\"Aha! I have it! I still need to investigate the internal structure of $(enemy_1)s. I think they can be found in this area. Can you bring me some? Mostly intact?\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"I can do that.\" (Accept Quest)" + }, + { + "name": "\"Sorry, I just decided that I'm more in to magic than science.\" (Decline Quest)" + } + ] + } + ] + } + ] + }, + { + "name": "It's none of your business, keep moving. (Decline Quest)" + } + ] + }, + "prologue": {}, + "epilogue": { + "action": [ + {} + ], + "text": "You walk back into the town with the requested cargo of $(enemy_1)s. You're unsure if they will be useful, but the dwarf seems extremely excited to begin his work. (+3 Local Reputation) ", + "options": [ + { + "name": "You try to talk to him, but the dwarf is completely lost in his work already. You take the bundle he was carrying, assuming it to be your rewards. (Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "The scientist can find his $(enemy_1)s to experiment on some other way, you're done with this. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "stages": [ + { + "id": 1, + "name": "Defeat $(enemy_1)s", + "description": "Sometimes it pays to specialize. Cash in on a local bounty by bringing in the remains of 3 $(enemy_1)s. They can usually be found in the Wasteland biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 3, + "objective": "Defeat", + "enemyTags": [ + "BiomeColorless" + ], + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Return to where the quest began to turn in the reagents and collect your rewards.", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + } + ] +}, +{ + "id": 22, + "isTemplate": true, + "name": "Shamanic Totems", + "description": "Defeat 3 $(enemy_1)s", + "offerDialog": { + "text": "A job board has been constructed outside the local inn, and you see that it is covered in various papers and posters.", + "options": [ + { + "name": "You walk away, having your own goals in mind already. (Decline Quest)" + }, + { + "name": "You take a moment to look over the board.", + "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", + "options": [ + { + "name": "You look at what seems to be an advertisment of some sort off to one side.", + "text": "It reads: \"Gimgee's rocks. When you need a good rock, think Gimgee's\".", + "options": [ + { + "name": "\"I'll file that away under things that make sense yet don't.\" (Decline Quest)" + } + ] + }, + { + "name": "A short note is written in red ink.", + "text": "The note says \"{COLOR=red}Wanted: 3 $(enemy_1)s. Dead. Contact town shaman for reward.{ENDCOLOR}\"", + "options": [ + { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "1", + "POIReference": "" + } + ], + "name": "\"Seems simple enough\" (Accept Quest)" + }, + { + "name": "\"I'm not sure I want to meet 3 $(enemy_1)s. Or the shaman, for that matter.\" (Decline Quest)" + } + ] + } + ] + } + ] + }, + "prologue": {}, + "epilogue": { + "text": "The village shaman grins as you enter their tent. \"Yes, this is good. This is good. The spirits have been satisfied.\" (+3 Local Reputation)", + "options": [ + { + "action": [ + { + "grantRewards": [ + { + "type": "gold", + "count": 500 + }, + { + "type": "shards", + "count": 15 + }, + { + "type": "card", + "count": 2, + "addMaxCount": 2, + "colors": [ + "Red" + ], + "rarity": [ + "Common" + ] + } + ] + } + ], + "name": "(Complete Quest)" + } + ] + }, + "failureDialog": { + "action": [ + { + "removeItem": "", + "setColorIdentity": "", + "advanceQuestFlag": "", + "advanceMapFlag": "", + "setQuestFlag": { + "key": "" + }, + "setMapFlag": { + "key": "" + }, + "issueQuest": "", + "addMapReputation": -2, + "POIReference": "$(poi_2)" + } + ], + "text": "Having spent as much time searching for $(enemy_1)s as you care to, you scratch this item out of your notes. (-2 Local Reputation)", + "options": [ + { + "name": "(Quest Failed)" + } + ] + }, + "declinedDialog": { + "text": "Come back tomorrow and perhaps I'll have something that you'll actually be willing to do.", + "options": [ + { + "name": "(Catching the not so subtle hint, you leave.)" + } + ] + }, + "reward": {}, + "stages": [ + { + "id": 1, + "name": "Defeat $(enemy_1)s", + "description": "At the request of a village shaman, defeat 3 $(enemy_1)s. They can usually be found in the Mountain biome.", + "mapFlag": "", + "mapFlagValue": 1, + "count1": 3, + "objective": "Defeat", + "enemyTags": [ + "BiomeRed" + ], + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + }, + { + "id": 2, + "name": "Travel", + "description": "Find the shaman and collect your rewards.", + "mapFlag": "", + "mapFlagValue": 1, + "here": true, + "objective": "Travel", + "prologue": {}, + "epilogue": {}, + "failureDialog": {}, + "POIToken": "" + } + ] +} +] \ No newline at end of file diff --git a/forge-gui/res/adventure/Shandalar/world/shops.json b/forge-gui/res/adventure/Shandalar/world/shops.json index 518bbd9da23..7bab1c0eb94 100644 --- a/forge-gui/res/adventure/Shandalar/world/shops.json +++ b/forge-gui/res/adventure/Shandalar/world/shops.json @@ -72,12 +72,12 @@ "rewards": [ { "count":6, - "cardText": "deathtouch|fear|intimidate|menace|ninjitsu|regenerate\b", + "cardText": "deathtouch|fear|intimidate|menace|ninjutsu|regenerate\b", "colors": ["black"] }, { "count":2, - "cardText": "deathtouch|fear|intimidate|menace|ninjitsu|regenerate\b" + "cardText": "deathtouch|fear|intimidate|menace|ninjutsu|regenerate\b" }] },{ "name":"Black6", @@ -136,12 +136,12 @@ "rewards": [ { "count":6, - "cardText": "flying|prowess|unblockable|hexproof|shroud|morph|ninjitsu|phas(ing|(es (in|out)))", + "cardText": "flying|prowess|unblockable|hexproof|shroud|morph|ninjutsu|phas(ing|(es (in|out)))", "colors": ["blue"] }, { "count":2, - "cardText": "flying|prowess|unblockable|hexproof|shroud|morph|ninjitsu|phas(ing|(es (in|out)))" + "cardText": "flying|prowess|unblockable|hexproof|shroud|morph|ninjutsu|phas(ing|(es (in|out)))" }] },{ "name":"Blue4", diff --git a/forge-gui/res/cardsfolder/p/prowl_stoic_strategist_prowl_pursuit_vehicle.txt b/forge-gui/res/cardsfolder/p/prowl_stoic_strategist_prowl_pursuit_vehicle.txt index ce22c2c9787..d659ae5728e 100644 --- a/forge-gui/res/cardsfolder/p/prowl_stoic_strategist_prowl_pursuit_vehicle.txt +++ b/forge-gui/res/cardsfolder/p/prowl_stoic_strategist_prowl_pursuit_vehicle.txt @@ -12,7 +12,7 @@ T:Mode$ LandPlayed | ValidCard$ Card.ExiledWithSourceLKI | TriggerZones$ Battlef SVar:TrigDraw:DB$ Draw | SubAbility$ DBConvert SVar:DBConvert:DB$ SetState | Mode$ Transform AlternateMode:DoubleFaced -Oracle:More Than Meets the Eye {1}{R}{G} (You may cast this card converted for {1}{R}{G}.)\nWhenever Prowl attacks, exile up to one other target tapped creature or Vehicle. For as long as that card remains exiled, its owner may play it.\nWhenever a player plays a card exiled with Prowl, you draw a card and convert Prowl. +Oracle:More Than Meets the Eye {2}{W} (You may cast this card converted for {2}{W}}.)\nWhenever Prowl attacks, exile up to one other target tapped creature or Vehicle. For as long as that card remains exiled, its owner may play it.\nWhenever a player plays a card exiled with Prowl, you draw a card and convert Prowl. ALTERNATE