Merge pull request #2880 from jjayers99/master

Adventure quests - initial implementation
This commit is contained in:
Anthony Calosa
2023-04-11 08:47:06 +08:00
committed by GitHub
76 changed files with 14292 additions and 550 deletions

View File

@@ -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;
}
}

View File

@@ -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<DialogData.ActionData> model = new DefaultListModel<>();
JList<DialogData.ActionData> 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<actions.length;i++) {
if (actions[i].grantRewards.length > 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<model.getSize();i++)
{
action[i]=model.get(i);
}
return action;
}
public void clear(){
updating = true;
model.clear();
updating = false;
}
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
}

View File

@@ -42,7 +42,7 @@ public class BiomeEdit extends FormPanel {
center.add("height:",height);
center.add("spriteNames:",spriteNames);
center.add("enemies:",enemies);
center.add("pointsOfInterest:",pointsOfInterest);
center.add("POITags:",pointsOfInterest);
center.add("color:",color);
center.add("collision:",collision);
center.add("terrain/structures:",new JLabel(""));

View File

@@ -0,0 +1,155 @@
package forge.adventure.editor;
import forge.adventure.data.DialogData;
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.*;
public class DialogEdit extends FormPanel {
private boolean updating=false;
DialogData currentData;
public JTextField name=new JTextField(80);
public JTextField text=new JTextField(80);
public JTextField locname=new JTextField(80);
public JTextField loctext=new JTextField(80);
JPanel namePanel;
JPanel locNamePanel;
public JButton addNode = new JButton("Add node");
public JButton removeNode = new JButton("Remove node");
public DialogEdit()
{
FormPanel center=new FormPanel() { };
name.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
if (!updating)
emitChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
if (!updating)
emitChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
if (!updating)
emitChanged();
}
});
text.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
if (!updating)
emitChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
if (!updating)
emitChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
if (!updating)
emitChanged();
}
});
JPanel editData = new JPanel();
editData.setLayout(new BoxLayout(editData, BoxLayout.Y_AXIS));
namePanel = new JPanel();
namePanel.setLayout(new FlowLayout());
namePanel.add(new JLabel("Name:"));
namePanel.add(name);
editData.add(namePanel);
JPanel textPanel = new JPanel();
textPanel.setLayout(new FlowLayout());
textPanel.add(new JLabel("Text:"));
textPanel.add(text);
editData.add(textPanel);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
buttonPanel.add(addNode);
buttonPanel.add(removeNode);
editData.add(buttonPanel);
editData.add(new JLabel("localization tokens for translation"));
locNamePanel = new JPanel();
locNamePanel.setLayout(new FlowLayout());
locNamePanel.add(new JLabel("Name Token:"));
locNamePanel.add(locname);
JPanel locTextPanel = new JPanel();
locTextPanel.setLayout(new FlowLayout());
locTextPanel.add(new JLabel("Text Token:"));
locTextPanel.add(loctext);
editData.add(locNamePanel);
editData.add(locTextPanel);
center.add(editData);
add(center);
refresh();
}
public void refresh(){
refresh(false);
}
public void refresh(boolean onRootNode) {
setEnabled(currentData!=null);
if(currentData==null)
{
return;
}
updating=true;
name.setText(currentData.name);
locname.setText(currentData.locname);
text.setText(currentData.text);
loctext.setText(currentData.loctext);
namePanel.setVisible(!onRootNode);
locNamePanel.setVisible(!onRootNode);
updating=false;
}
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);
}
}
}
}

View File

@@ -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<DialogData> 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<DialogData.ActionData> 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<DialogData> model = new DefaultListModel<>();
JList<DialogData> 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<options.length;i++) {
model.add(i,options[i]);
}
if (model.size() > 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<model.getSize();i++)
{
options[i]=model.get(i);
}
return options;
}
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
}

View File

@@ -0,0 +1,121 @@
package forge.adventure.editor;
import forge.adventure.data.DialogData;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class DialogTree extends JPanel {
// private DefaultMutableTreeNode rootNode;
private JTree dialogTree;
private JScrollPane scrollPane;
public DialogTree(){
setLayout(new BorderLayout());
// Create the JTree component
dialogTree = new JTree();
dialogTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
addSelectionListener();
// Create a scroll pane to contain the JTree
scrollPane = new JScrollPane(dialogTree);
// Add the scroll pane to the panel
add(scrollPane, BorderLayout.CENTER);
}
public void loadDialog(DialogData dialogData){
// rootNode = buildBranches(dialogData);
// ((DefaultTreeModel)dialogTree.getModel()).setRoot(rootNode);
((DefaultTreeModel)dialogTree.getModel()).setRoot(buildBranches(dialogData));
}
public DefaultMutableTreeNode buildBranches(DialogData dialogData)
{
DefaultMutableTreeNode node = new DefaultMutableTreeNode(dialogData);
for (DialogData option : dialogData.options){
node.add(buildBranches(option));
}
return node;
}
private final List<TreeSelectionListener> 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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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<String> existingModel = new DefaultListModel<>();
DefaultListModel<String> enemyModel = new DefaultListModel<>();
JList<String> existingTags;
JList<String> 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<existingModel.size()?index:index-1);
}
}
});
existingModel = QuestController.getInstance().getEnemyTags(true);
existingTags.setModel(existingModel);
enemyTags = new JList<>();
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<String> filter){
DefaultListModel<String> toReturn = new DefaultListModel<>();
for (Enumeration<String> 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<String> 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<String> tags = new ArrayList<>();
for (Enumeration<String> 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;
}
}

View File

@@ -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<allEnemies.size;i++) {
model.add(i,allEnemies.get(i));

View File

@@ -67,6 +67,7 @@ public class ItemsEditor 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();
@@ -105,8 +106,7 @@ public class ItemsEditor extends JComponent {
FileHandle handle = Config.instance().getFile(Paths.ITEMS);
if (handle.exists())
{
Array readEnemies=json.fromJson(Array.class, ItemData.class, handle);
allEnemies = readEnemies;
allEnemies =json.fromJson(Array.class, ItemData.class, handle);
}
for (int i=0;i<allEnemies.size;i++) {
model.add(i,allEnemies.get(i));

View File

@@ -3,6 +3,12 @@ package forge.adventure.editor;
import forge.adventure.data.PointOfInterestData;
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;
public class PointOfInterestEdit extends JComponent {
@@ -18,16 +24,29 @@ public class PointOfInterestEdit extends JComponent {
JSpinner radiusFactor= new JSpinner(new SpinnerNumberModel(0.0f, 0.0f, 2.0f, 0.1f));
SwingAtlasPreview preview=new SwingAtlasPreview(256,2000);
JTextField manualEntry = new JTextField(20);
DefaultListModel<String> existingModel = new DefaultListModel<>();
DefaultListModel<String> POIModel = new DefaultListModel<>();
JList<String> existingTags;
JList<String> 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<existingModel.size()?index:index-1);
}
}
});
existingModel = QuestController.getInstance().getPOITags(true);
existingTags.setModel(existingModel);
POITags = new JList<>();
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<String> tags = new ArrayList<>();
for (Enumeration<String> 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<String> filter){
DefaultListModel<String> toReturn = new DefaultListModel<>();
for (Enumeration<String> 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<String> 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;
}
}

View File

@@ -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<allEnemies.size;i++) {
model.add(i,allEnemies.get(i));
}
QuestController.getInstance().load();
}
void addItem()
{

View File

@@ -0,0 +1,228 @@
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.data.EnemyData;
import forge.adventure.data.PointOfInterestData;
import forge.adventure.util.Config;
import forge.adventure.util.Paths;
import javax.swing.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class QuestController {
public final DefaultListModel<String> POITags = new DefaultListModel<>();
public final DefaultListModel<String> enemyTags = new DefaultListModel<>();
public final DefaultListModel<String> questEnemyTags = new DefaultListModel<>();
public final DefaultListModel<String> questTags = new DefaultListModel<>();
public final DefaultListModel<String> questPOITags = new DefaultListModel<>();
private final DefaultListModel<PointOfInterestData> allPOI = new DefaultListModel<>();
private final DefaultListModel<EnemyData> allEnemies = new DefaultListModel<>();
private final DefaultListModel<AdventureQuestData> allQuests = new DefaultListModel<>();
private static QuestController instance;
public static QuestController getInstance() {
if (instance == null)
instance = new QuestController();
return instance;
}
public DefaultListModel<AdventureQuestData> getAllQuests() {
return allQuests;
}
private QuestController(){
load();
}
public DefaultListModel<String> getEnemyTags(boolean includeQuest){
DefaultListModel<String> 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<Object> sortedObjects = Arrays.stream(toReturn.toArray()).sorted().collect(Collectors.toList());
toReturn.clear();
for (Object sortedObject : sortedObjects) {
toReturn.addElement((String) sortedObject);
}
return toReturn;
}
public DefaultListModel<String> getPOITags(boolean includeQuest){
DefaultListModel<String> 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<Object> 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<allEnemies.size();i++) {
for (String tag : allEnemies.get(i).questTags)
{
enemyTags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
enemyTags.addElement(tag);
}
}
for (int i=0;i<allPOI.size();i++) {
for (String tag : allPOI.get(i).questTags)
{
POITags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
POITags.addElement(tag);
}
}
for (int i=0;i<allQuests.size();i++) {
for (String tag : allQuests.get(i).questTags)
{
questTags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
questTags.addElement(tag);
}
for (String tag : allQuests.get(i).questEnemyTags)
{
questEnemyTags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
questEnemyTags.addElement(tag);
}
for (String tag : allQuests.get(i).questPOITags)
{
questPOITags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
questPOITags.addElement(tag);
}
}
}
public void load()
{
allEnemies.clear();
Array<EnemyData> 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<enemyJSON.size;i++) {
allEnemies.add(i,enemyJSON.get(i));
for (String tag : enemyJSON.get(i).questTags)
{
enemyTags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
enemyTags.addElement(tag);
}
}
allPOI.clear();
Array<PointOfInterestData> 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<POIJSON.size;i++) {
allPOI.add(i,POIJSON.get(i));
for (String tag : POIJSON.get(i).questTags)
{
POITags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
POITags.addElement(tag);
}
}
allQuests.clear();
Array<AdventureQuestData> 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<questJSON.size;i++) {
AdventureQuestData template = questJSON.get(i);
template.isTemplate = true;
allQuests.add(i,template);
for (String tag : template.questTags)
{
questTags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
questTags.addElement(tag);
}
for (String tag : template.questEnemyTags)
{
questEnemyTags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
questEnemyTags.addElement(tag);
}
for (String tag : template.questPOITags)
{
questPOITags.removeElement(tag); //Ensure uniqueness
if (tag!= null)
questPOITags.addElement(tag);
}
}
}
void save()
{
Json json = new Json(JsonWriter.OutputType.json);
FileHandle handle = Config.instance().getFile(Paths.QUESTS);
AdventureQuestData[] saveData = Arrays.stream(allQuests.toArray()).map(AdventureQuestData.class::cast).toArray(AdventureQuestData[]::new);
handle.writeString(json.prettyPrint(json.toJson(saveData,Array.class, AdventureQuestData.class)),false);
}
}

View File

@@ -0,0 +1,167 @@
package forge.adventure.editor;
import forge.adventure.data.AdventureQuestData;
import javax.swing.*;
public class QuestEdit extends FormPanel {
AdventureQuestData currentData;
//public JSpinner spawnWeight= new JSpinner(new SpinnerNumberModel(0.0f, 0.f, 1f, 0.1f));
public JLabel id = new JLabel();
public JTextField name=new JTextField();
public JTextField description=new JTextField();
public JTextField synopsis=new JTextField();
public JCheckBox storyQuest = new JCheckBox();
public JTextField rewardDescription=new JTextField();
public QuestStageEditor stages =new QuestStageEditor();
JTabbedPane tabs =new JTabbedPane();
public DialogEditor prologueEditor =new DialogEditor();
public DialogEditor epilogueEditor =new DialogEditor();
public DialogEditor offerEditor = new DialogEditor();
public DialogEditor failureEditor = new DialogEditor();
public DialogEditor declineEditor = new DialogEditor();
private boolean updating=false;
public QuestEdit()
{
FormPanel center=new FormPanel() { };
center.add("Quest ID:", id);
center.add("Name:",name);
//center.add("Synopsis (dev mode):",synopsis);
center.add("Description:",description);
center.add("Reward Description:",rewardDescription);
center.add("Storyline Quest", storyQuest);
add(center);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(tabs);
tabs.add("Quest Stages", getStagesTab());
tabs.add("Offer Dialog",getOfferTab());
tabs.add("Prologue",getPrologueTab());
tabs.add("Epilogue",getEpilogueTab());
tabs.add("Failure Dialog", getFailureTab());
tabs.add("Decline Dialog",getDeclineTab());
name.getDocument().addDocumentListener(new DocumentChangeListener(QuestEdit.this::updateQuest));
description.getDocument().addDocumentListener(new DocumentChangeListener(QuestEdit.this::updateQuest));
synopsis.getDocument().addDocumentListener(new DocumentChangeListener(QuestEdit.this::updateQuest));
storyQuest.getModel().addChangeListener(q -> 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;
}
}

View File

@@ -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<AdventureQuestData> 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<AdventureQuestData> allQuests=new Array<>();
for(int i=0;i<QuestController.getInstance().getAllQuests().getSize();i++) {
allQuests.add(QuestController.getInstance().getAllQuests().get(i));
}
Json json = new Json(JsonWriter.OutputType.json);
FileHandle handle = Config.instance().getFile(Paths.QUESTS);
handle.writeString(json.prettyPrint(json.toJson(allQuests,Array.class, AdventureQuestData.class)),false);
QuestController.getInstance().save();
}
void addStage()
{
AdventureQuestData data=new AdventureQuestData();
data.name="New Quest "+QuestController.getInstance().getAllQuests().getSize();
data.isTemplate = true;
QuestController.getInstance().getAllQuests().add(QuestController.getInstance().getAllQuests().size(),data);
}
void remove()
{
int selected=list.getSelectedIndex();
if(selected<0)
return;
QuestController.getInstance().getAllQuests().remove(selected);
edit.setVisible(false);
}
}

View File

@@ -0,0 +1,607 @@
package forge.adventure.editor;
import forge.adventure.data.*;
import forge.adventure.util.AdventureQuestController;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class QuestStageEdit extends FormPanel {
private boolean updating=false;
AdventureQuestStage currentData;
AdventureQuestData currentQuestData;
public JTextField name=new JTextField("", 25);
public JTextField description=new JTextField("", 25);
public TextListEdit itemNames =new TextListEdit();
public TextListEdit spriteNames =new TextListEdit();
public TextListEdit equipNames =new TextListEdit();
public TextListEdit prerequisites =new TextListEdit(currentQuestData!=null?(String[])Arrays.stream(currentQuestData.stages).filter(q -> !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<AdventureQuestController.ObjectiveTypes> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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());
}
}

View File

@@ -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<AdventureQuestStage> model = new DefaultListModel<>();
JList<AdventureQuestStage> 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<data.stages.length;i++) {
model.add(i,data.stages[i]);
}
if (model.size() > 0) {
list.setSelectedIndex(0);
}
}
public AdventureQuestStage[] getStages() {
AdventureQuestStage[] stages= new AdventureQuestStage[model.getSize()];
for(int i=0;i<model.getSize();i++)
{
stages[i]=model.get(i);
}
return stages;
}
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
}

View File

@@ -0,0 +1,107 @@
package forge.adventure.editor;
import javax.swing.*;
import java.awt.*;
public class QuestTagSelector extends JComponent {
DefaultListModel<String> allItems = new DefaultListModel<>();
DefaultListModel<String> selectedItems = new DefaultListModel<>();
JList<String> unselectedList;
JList<String> 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<String> 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.size();i++){
if (selectedNames.contains(allItems.get(i))){
selectedItems.addElement(allItems.get(i));
}
}
}
private boolean updating=false;
private void refresh() {
setEnabled(allItems!=null);
if(allItems==null)
{
return;
}
updating=true;
//unselectedList = new JList<>(allItems);
//selectedList = new JList<>(selectedItems);
updating=false;
}
}

View File

@@ -16,7 +16,7 @@ public class RewardsEditor extends JComponent{
JList<RewardData> 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);
}

View File

@@ -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();
}
}

View File

@@ -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<String> 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)
{

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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;

View File

@@ -46,7 +46,7 @@ public class RewardSprite extends CharacterSprite {
Array<Reward> ret = new Array<Reward>();
if(rewards == null) return ret;
for(RewardData rdata:rewards) {
ret.addAll(rdata.generate(false));
ret.addAll(rdata.generate(false, true));
}
return ret;
}

View File

@@ -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<String, PointOfInterest> poiTokens = new Hashtable<>();
Dictionary<String, EnemyData> enemyTokens = new Hashtable<>();
Dictionary<String, String> 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<AdventureQuestStage> getActiveStages(){
List<AdventureQuestStage> 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<AdventureQuestStage> getStagesForQuestLog(){
List<AdventureQuestStage> 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<String> e = poiTokens.keys(); e.hasMoreElements();){
String key = e.nextElement();
data = data.replace(key, poiTokens.get(key).getData().name);
}
for (Enumeration<String> enemy = enemyTokens.keys(); enemy.hasMoreElements();){
String enemyKey = enemy.nextElement();
data = data.replace(enemyKey, enemyTokens.get(enemyKey).getName());
}
for (Enumeration<String> 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<String> 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<String> 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<String> 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<String> 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<EnemyData> 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
}
}

View File

@@ -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<String> POITags = new ArrayList<>(); //Tags defining potential targets
public AdventureQuestController.ObjectiveTypes objective;
public List<String> prerequisiteNames = new ArrayList<>();
public List<String> enemyTags = new ArrayList<>(); //Tags defining potential targets
public List<String> itemNames = new ArrayList<>(); //Tags defining items to use
public List<String> 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<String, PointOfInterest> 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<PointOfInterest> 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<String> 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<String> 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<String> 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;
}
}

View File

@@ -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<DialogData> 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).

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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<Reward> generate(boolean isForEnemy) {
return generate(isForEnemy, null);
public Array<Reward> generate(boolean isForEnemy, boolean useSeedlessRandom) {
return generate(isForEnemy, null, useSeedlessRandom);
}
public Array<Reward> generate(boolean isForEnemy, Iterable<PaperCard> cards) {
public Array<Reward> generate(boolean isForEnemy, Iterable<PaperCard> 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<Reward> 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<Reward> generateAll(Iterable<RewardData> dataList, boolean isForEnemy) {
Array<Reward> ret=new Array<Reward>();
for (RewardData data:dataList)
ret.addAll(data.generate(isForEnemy));
ret.addAll(data.generate(isForEnemy, false));
return ret;
}
static public List<PaperCard> rewardsToCards(Iterable<Reward> dataList) {

View File

@@ -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<String> inventoryItems=new Array<>();
private final HashMap<String,String> equippedItems=new HashMap<>();
private List<AdventureQuestData> 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<NUMBER_OF_DECKS;i++) {
if(!data.containsKey("deck_name_" + i)) {
@@ -372,6 +374,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
data.storeObject("questFlagsKey", questFlagsKey.toArray(new String[0]));
data.storeObject("questFlagsValue", questFlagsValue.toArray(new Byte[0]));
data.storeObject("quests", quests.toArray());
data.storeObject("deckCards",deck.getMain().toCardList("\n").split("\n"));
if(deck.get(DeckSection.Sideboard)!=null)
@@ -702,6 +705,37 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
questFlags.clear();
}
public void addQuest(String questID){
int id = Integer.parseInt(questID);
addQuest(id);
}
public void addQuest(int questID){
AdventureQuestData toAdd = AdventureQuestController.instance().generateQuest(questID);
if (toAdd != null){
addQuest(toAdd);
}
}
public void addQuest(AdventureQuestData q){
//TODO: add a config flag for this
boolean autoTrack = true;
for (AdventureQuestData existing : quests){
if (autoTrack && existing.isTracked)
{
autoTrack = false;
break;
}
}
q.isTracked = autoTrack;
quests.add(q);
}
public List<AdventureQuestData> 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);
}
}

View File

@@ -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<Sprite> 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;
}
}

View File

@@ -17,6 +17,7 @@ public class PointOfInterestChanges implements SaveFileContent {
private final java.util.Map<String, Byte> mapFlags = new HashMap<>();
private final java.util.Map<Integer, Long> shopSeeds = new HashMap<>();
private final java.util.Map<Integer, Float> shopModifiers = new HashMap<>();
private final java.util.Map<Integer, Integer> reputation = new HashMap<>();
public static class Map extends HashMap<String,PointOfInterestChanges> 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();
}
}

View File

@@ -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<PointOfInterest> getAllPointOfInterest() {
List<PointOfInterest> allPOI = new ArrayList<>();
for(List<PointOfInterest>[] poiList1:mapObjects)
{
for(List<PointOfInterest> poiList:poiList1)
{
allPOI.addAll(poiList);
}
}
return allPOI;
}
public List<PointOfInterest> pointsOfInterest(int chunkX, int chunkY) {
if (chunkX >= numberOfChunksX || chunkY >= numberOfChunksY || chunkX < 0 || chunkY < 0)
return new ArrayList<PointOfInterest>();
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<arraySize;i++)
{

View File

@@ -257,7 +257,7 @@ public class ArenaScene extends UIScene implements IAfterMatch {
Array<Reward> 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);

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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";
});

View File

@@ -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())

View File

@@ -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<Reward> 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<EnemySprite> 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) {

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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<Pair<Float, EnemySprite>> it = enemies.iterator();
while (it.hasNext()) {
Pair<Float, EnemySprite> 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> 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<Float, EnemySprite> 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);
}
}
}

View File

@@ -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<String, Long> nextQuestDate = new HashMap<>();
private int maximumSideQuests = 5; //todo: move to configuration file
private transient boolean inDialog = false;
private transient Array<AdventureQuestData> allQuests = new Array<>();
private transient Array<AdventureQuestData> allSideQuests = new Array<>();
private Queue<DialogData> dialogQueue = new LinkedList<>();
private Map<String,Date> questAvailability = new HashMap<>();
public PointOfInterest mostRecentPOI;
private List<EnemySprite> enemySpriteList= new ArrayList<>();
public void showQuestDialogs(GameStage stage) {
List<AdventureQuestData> 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<PointOfInterest>
{
//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<EnemySprite> 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<EnemySprite> 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<AdventureQuestData> existing){
int sideQuests = 0;
for (AdventureQuestData quest : existing){
if (quest.storyQuest || quest.completed || quest.failed)
continue;
sideQuests++;
}
return (sideQuests >= maximumSideQuests);
}
}

View File

@@ -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<PaperCard> generateCards(Iterable<PaperCard> cards,final RewardData data, final int count)
public static List<PaperCard> generateCards(Iterable<PaperCard> cards,final RewardData data, final int count, Random r)
{
final List<PaperCard> result = new ArrayList<>();
List<PaperCard> 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);
}

View File

@@ -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) {

View File

@@ -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<FileHandle, Music> 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<Reward> ret = new Array<Reward>();
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)) {

View File

@@ -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<Sprite> 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);
}
}

View File

@@ -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";

View File

@@ -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<PointOfInterest> 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++;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

@@ -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.

View File

@@ -7,17 +7,17 @@
<tileset firstgid="10113" source="../tileset/buildings.tsx"/>
<layer id="13" name="subsublaag" width="37" height="23">
<data encoding="base64" compression="zlib">
eJxjYBgFo4Ay4MY50C7ABF4D7KYPWMSQ3URqmD1jpcg5YPCBAbu7SHUHOqaGuyh1kyPUHY5UdBc6YOOFYGLcA7OfQwBBU9tdv3ggmFjALYDKh7kNOS4PsFPPfchgGlp6d6RB/AxVMJDlJ668NxjL9MHqJlq6C5RnYZhUN9HDXaS4iZaAVPeAAK3dFEaG+bR2EznmD8Z8NxgBAK1JHpE=
eJxjYBgFo4Ay4MY50C7ABF4D7KYPWMSQ3URqmD1jpcg5YPCBAbu7SHUHOqaGu6jtJmq4Cx2w8UIwKe7hEKCdu37xQDCxgFsAtzth+AA79dyHDKahpXdHGsTPUAUDWX7iynuDsUwfrG6ipbtAeRaGSXUTPdxFiptoCUh1DwjQ2k1hZJhPazeRY/5gzHeDEQAAOM4lLQ==
</data>
</layer>
<layer id="10" name="sublaag" width="37" height="23">
<data encoding="base64" compression="zlib">
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==
</data>
</layer>
<layer id="9" name="slim" width="37" height="23">
<data encoding="base64" compression="zlib">
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==
</data>
</layer>
<layer id="12" name="clutter" width="37" height="23">
@@ -25,7 +25,7 @@
<property name="spriteLayer" type="bool" value="true"/>
</properties>
<data encoding="base64" compression="zlib">
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=
</data>
</layer>
<objectgroup id="11" name="obj">

View File

@@ -0,0 +1,9 @@
compass.png
size: 50,50
format: RGBA8888
filter: Nearest,Nearest
repeat: none
compass
xy: 1,1
size: 48,48

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -420,3 +420,6 @@ GreenLeaf2
SearchPost
xy: 384, 63
size: 16, 16
LogBook
xy: 240, 912
size: 16, 16

View File

@@ -118,9 +118,9 @@
{
"type": "TextButton",
"name": "statistic",
"name": "logbook",
"style":"menu",
"text": "[%120][+Status]",
"text": "[%120][+Logbook]",
"binding": "Status",
"width": 48,
"height": 36,

View File

@@ -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,

View File

@@ -106,9 +106,9 @@
{
"type": "TextButton",
"name": "statistic",
"name": "logbook",
"style":"menu",
"text": "[%120][+Status]",
"text": "[%120][+Logbook]",
"binding": "Status",
"width": 64,
"height": 32,

View File

@@ -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
}
]

View File

@@ -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
}
]
}

View File

@@ -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
}
]
}

View File

@@ -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
}
]
}

View File

@@ -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",

File diff suppressed because it is too large Load Diff

View File

@@ -434,9 +434,9 @@
"equipmentSlot": "Left",
"iconName": "FlameSword",
"effect": {
"opponent": {
"lifeModifier": -5
}
"startBattleWithCardInCommandZone": [
"Flame Sword"
]
}
},
{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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