From 605c232dea397ab99481b48fd9a46c20dafe0947 Mon Sep 17 00:00:00 2001 From: Grimm Date: Thu, 28 Jul 2022 04:12:17 +0200 Subject: [PATCH] wavefunction collapse first integration --- .../src/main/java/forge/adventure/Main.java | 8 +- .../forge/adventure/editor/BiomeEdit.java | 11 +- .../adventure/editor/BiomeStructureEdit.java | 98 +++++++++++++ .../adventure/editor/BiomeTerrainEdit.java | 80 ++++++++++- .../adventure/editor/EditorMainWindow.java | 2 +- .../forge/adventure/editor/EnemyEdit.java | 2 - .../forge/adventure/editor/FloatSpinner.java | 19 +++ .../forge/adventure/editor/IntSpinner.java | 20 +++ .../adventure/editor/StructureEditor.java | 133 ++++++++++++++++++ .../forge/adventure/editor/SwingAtlas.java | 21 ++- .../adventure/editor/SwingAtlasPreview.java | 43 ++++-- .../adventure/editor/TerrainsEditor.java | 36 ++--- .../forge/adventure/editor/WorldEditor.java | 56 ++++++++ forge-gui-mobile/pom.xml | 5 + forge-gui-mobile/src/forge/Forge.java | 1 + .../src/forge/adventure/data/BiomeData.java | 1 + .../adventure/data/BiomeStructureData.java | 38 +++++ .../forge/adventure/scene/NewGameScene.java | 5 + .../src/forge/adventure/scene/StartScene.java | 9 ++ .../src/forge/adventure/stage/GameStage.java | 10 +- .../src/forge/adventure/stage/WorldStage.java | 14 +- .../forge/adventure/world/BiomeStructure.java | 114 +++++++++++++++ .../forge/adventure/world/BiomeTexture.java | 31 +++- .../src/forge/adventure/world/World.java | 74 +++++++--- .../src/forge/adventure/world/WorldSave.java | 2 + .../src/forge/screens/SplashScreen.java | 8 ++ .../res/adventure/Shandalar/world/green.json | 8 ++ .../Shandalar/world/tilesets/autotiles.png | Bin 30516 -> 43326 bytes .../Shandalar/world/tilesets/forest.atlas | 20 +++ .../Shandalar/world/tilesets/forest.png | Bin 0 -> 16786 bytes 30 files changed, 786 insertions(+), 83 deletions(-) create mode 100644 forge-adventure/src/main/java/forge/adventure/editor/BiomeStructureEdit.java create mode 100644 forge-adventure/src/main/java/forge/adventure/editor/FloatSpinner.java create mode 100644 forge-adventure/src/main/java/forge/adventure/editor/IntSpinner.java create mode 100644 forge-adventure/src/main/java/forge/adventure/editor/StructureEditor.java create mode 100644 forge-gui-mobile/src/forge/adventure/data/BiomeStructureData.java create mode 100644 forge-gui-mobile/src/forge/adventure/world/BiomeStructure.java create mode 100644 forge-gui/res/adventure/Shandalar/world/tilesets/forest.atlas create mode 100644 forge-gui/res/adventure/Shandalar/world/tilesets/forest.png diff --git a/forge-adventure/src/main/java/forge/adventure/Main.java b/forge-adventure/src/main/java/forge/adventure/Main.java index e6f6d316166..b32aa6653f2 100644 --- a/forge-adventure/src/main/java/forge/adventure/Main.java +++ b/forge-adventure/src/main/java/forge/adventure/Main.java @@ -107,7 +107,13 @@ public class Main { } }); - + for(int i=0;i BiomeEdit.this.updateTerrain())); tilesetName.getDocument().addDocumentListener(new DocumentChangeListener(() -> BiomeEdit.this.updateTerrain())); @@ -81,6 +82,7 @@ public class BiomeEdit extends JComponent { currentData.tilesetAtlas = tilesetAtlas.edit.getText(); currentData.tilesetName = tilesetName.getName(); currentData.terrain = terrain.getBiomeTerrainData(); + currentData.structures = structures.getBiomeStructureData(); currentData.width = (Float) width.getValue(); currentData.height = (Float) height.getValue(); currentData.color = color.getText(); @@ -109,7 +111,8 @@ public class BiomeEdit extends JComponent { name.setText(currentData.name); tilesetAtlas.edit.setText( currentData.tilesetAtlas); tilesetName.setText(currentData.tilesetName); - terrain.setTerrains(currentData.terrain); + terrain.setTerrains(currentData); + structures.setStructures(currentData); width.setValue(currentData.width); height.setValue(currentData.height); color.setText(currentData.color); diff --git a/forge-adventure/src/main/java/forge/adventure/editor/BiomeStructureEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/BiomeStructureEdit.java new file mode 100644 index 00000000000..2fd8bcfb592 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/BiomeStructureEdit.java @@ -0,0 +1,98 @@ +package forge.adventure.editor; + +import forge.adventure.data.BiomeData; +import forge.adventure.data.BiomeStructureData; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; + +public class BiomeStructureEdit extends JComponent { + SwingAtlasPreview preview=new SwingAtlasPreview(128); + private boolean updating=false; + BiomeStructureData currentData; + BiomeData currentBiomeData; + public JTextField structureAtlasPath=new JTextField(); + public FloatSpinner x= new FloatSpinner(); + public FloatSpinner y= new FloatSpinner(); + public FloatSpinner size= new FloatSpinner(); + public JCheckBox randomPosition=new JCheckBox(); + public JCheckBox collision=new JCheckBox(); + + public BiomeStructureEdit() + { + JComponent center=new JComponent() { }; + center.setLayout(new GridLayout(6,2)); + + center.add(new JLabel("structureAtlasPath:")); center.add(structureAtlasPath); + center.add(new JLabel("x:")); center.add(x); + center.add(new JLabel("y:")); center.add(y); + center.add(new JLabel("size:")); center.add(size); + center.add(new JLabel("randomPosition:")); center.add(randomPosition); + center.add(new JLabel("collision:")); center.add(collision); + BorderLayout layout=new BorderLayout(); + setLayout(layout); + add(preview,BorderLayout.LINE_START); + add(center,BorderLayout.CENTER); + + structureAtlasPath.getDocument().addDocumentListener(new DocumentChangeListener(() -> BiomeStructureEdit.this.updateStructure())); + + + x.addChangeListener(e -> BiomeStructureEdit.this.updateStructure()); + y.addChangeListener(e -> BiomeStructureEdit.this.updateStructure()); + size.addChangeListener(e -> BiomeStructureEdit.this.updateStructure()); + randomPosition.addChangeListener(e -> BiomeStructureEdit.this.updateStructure()); + collision.addChangeListener(e -> BiomeStructureEdit.this.updateStructure()); + refresh(); + } + private void refresh() { + setEnabled(currentData!=null); + if(currentData==null) + { + return; + } + updating=true; + structureAtlasPath.setText(currentData.structureAtlasPath); + x.setValue(currentData.x); + y.setValue(currentData.y); + size.setValue(currentData.size); + randomPosition.setSelected(currentData.randomPosition); + collision.setSelected(currentData.collision); + preview.setSpritePath(currentBiomeData.tilesetAtlas,currentData.structureAtlasPath); + updating=false; + } + public void updateStructure() + { + + if(currentData==null||updating) + return; + currentData.structureAtlasPath=structureAtlasPath.getText(); + + currentData.x= x.floatValue(); + currentData.y= y.floatValue(); + currentData.size= size.floatValue(); + currentData.randomPosition=randomPosition.isSelected(); + currentData.collision=collision.isSelected(); + preview.setSpritePath(currentBiomeData.tilesetAtlas,currentData.structureAtlasPath); + emitChanged(); + } + public void setCurrentStructure(BiomeStructureData biomeTerrainData, BiomeData data) { + currentData =biomeTerrainData; + currentBiomeData=data; + 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); + } + } + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/BiomeTerrainEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/BiomeTerrainEdit.java index 24791aa585d..8986a2e6701 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/BiomeTerrainEdit.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/BiomeTerrainEdit.java @@ -1,14 +1,88 @@ package forge.adventure.editor; +import forge.adventure.data.BiomeData; import forge.adventure.data.BiomeTerrainData; import javax.swing.*; +import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import java.awt.*; public class BiomeTerrainEdit extends JComponent { - public void setCurrentTerrain(BiomeTerrainData biomeTerrainData) { - } - public void addChangeListener(ChangeListener listener) { + SwingAtlasPreview preview=new SwingAtlasPreview(128); + private boolean updating=false; + BiomeTerrainData currentData; + BiomeData currentBiomeData; + public JTextField spriteName=new JTextField(); + public JSpinner min= new JSpinner(new SpinnerNumberModel(0.0f, 0.f, 1f, 0.1f)); + public JSpinner max= new JSpinner(new SpinnerNumberModel(0.0f, 0.f, 1f, 0.1f)); + public JSpinner resolution= new JSpinner(new SpinnerNumberModel(0.0f, 0.f, 1f, 0.1f)); + public BiomeTerrainEdit() + { + JComponent center=new JComponent() { }; + center.setLayout(new GridLayout(4,2)); + + center.add(new JLabel("spriteName:")); center.add(spriteName); + center.add(new JLabel("min:")); center.add(min); + center.add(new JLabel("max:")); center.add(max); + center.add(new JLabel("resolution:")); center.add(resolution); + BorderLayout layout=new BorderLayout(); + setLayout(layout); + add(preview,BorderLayout.LINE_START); + add(center,BorderLayout.CENTER); + + spriteName.getDocument().addDocumentListener(new DocumentChangeListener(() -> BiomeTerrainEdit.this.updateTerrain())); + + min.addChangeListener(e -> BiomeTerrainEdit.this.updateTerrain()); + max.addChangeListener(e -> BiomeTerrainEdit.this.updateTerrain()); + resolution.addChangeListener(e -> BiomeTerrainEdit.this.updateTerrain()); + + + refresh(); + } + private void refresh() { + setEnabled(currentData!=null); + if(currentData==null) + { + return; + } + updating=true; + spriteName.setText(currentData.spriteName); + min.setValue(currentData.min); + max.setValue(currentData.max); + resolution.setValue(currentData.resolution); + preview.setSpritePath(currentBiomeData.tilesetAtlas,currentData.spriteName); + updating=false; + } + public void updateTerrain() + { + + if(currentData==null||updating) + return; + currentData.spriteName=spriteName.getText(); + currentData.min= (float) min.getValue(); + currentData.max= (float) max.getValue(); + currentData.resolution= (float) resolution.getValue(); + preview.setSpritePath(currentBiomeData.tilesetAtlas,currentData.spriteName); + emitChanged(); + } + public void setCurrentTerrain(BiomeTerrainData biomeTerrainData, BiomeData data) { + currentData =biomeTerrainData; + currentBiomeData=data; + 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); + } + } } } diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java b/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java index 50f0d877cc9..fdfe775d914 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java @@ -14,8 +14,8 @@ public class EditorMainWindow extends JFrame { BorderLayout layout=new BorderLayout(); setLayout(layout); add(tabs); - tabs.addTab("POI",new PointOfInterestEditor()); tabs.addTab("World",new WorldEditor()); + tabs.addTab("POI",new PointOfInterestEditor()); tabs.addTab("Items",new ItemsEditor()); tabs.addTab("Enemies",new EnemyEditor()); setVisible(true); diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java index 399d7c88334..3ef2d72a92c 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java @@ -10,8 +10,6 @@ import java.awt.*; */ public class EnemyEdit extends JComponent { EnemyData currentData; - - JTextField nameField=new JTextField(); JTextField colorField=new JTextField(); JSpinner lifeFiled= new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1)); diff --git a/forge-adventure/src/main/java/forge/adventure/editor/FloatSpinner.java b/forge-adventure/src/main/java/forge/adventure/editor/FloatSpinner.java new file mode 100644 index 00000000000..7fad0e3bad3 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/FloatSpinner.java @@ -0,0 +1,19 @@ +package forge.adventure.editor; + +import javax.swing.*; + +public class FloatSpinner extends JSpinner{ + + public FloatSpinner() + { + this( 0.f, 1f, 0.1f); + } + public FloatSpinner(float min,float max,float stepSize) + { + super(new SpinnerNumberModel(new Float(0.0f), new Float(min), new Float (max), new Float(stepSize))); + } + public float floatValue() + { + return ((Float)getValue()).floatValue(); + } +} diff --git a/forge-adventure/src/main/java/forge/adventure/editor/IntSpinner.java b/forge-adventure/src/main/java/forge/adventure/editor/IntSpinner.java new file mode 100644 index 00000000000..132817d3891 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/IntSpinner.java @@ -0,0 +1,20 @@ +package forge.adventure.editor; + +import javax.swing.*; + + +public class IntSpinner extends JSpinner { + + public IntSpinner() + { + this( 0, 100, 1); + } + public IntSpinner(int min,int max,int stepSize) + { + super(new SpinnerNumberModel(new Integer(0), new Integer(min), new Integer (max), new Integer(stepSize))); + } + public int intValue() + { + return ((Integer)getValue()).intValue(); + } +} \ No newline at end of file diff --git a/forge-adventure/src/main/java/forge/adventure/editor/StructureEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/StructureEditor.java new file mode 100644 index 00000000000..1b301ebebd7 --- /dev/null +++ b/forge-adventure/src/main/java/forge/adventure/editor/StructureEditor.java @@ -0,0 +1,133 @@ +package forge.adventure.editor; + +import forge.adventure.data.BiomeData; +import forge.adventure.data.BiomeStructureData; +import forge.adventure.data.BiomeTerrainData; + +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 StructureEditor extends JComponent{ + DefaultListModel model = new DefaultListModel<>(); + JList list = new JList<>(model); + JToolBar toolBar = new JToolBar("toolbar"); + BiomeStructureEdit edit=new BiomeStructureEdit(); + + BiomeData currentData; + + public class StructureDataRenderer 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 BiomeTerrainData)) + return label; + BiomeTerrainData structureData=(BiomeTerrainData) value; + StringBuilder builder=new StringBuilder(); + builder.append("Structure"); + builder.append(" "); + builder.append(structureData.spriteName); + 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 StructureEditor() + { + + list.setCellRenderer(new StructureDataRenderer()); + list.addListSelectionListener(e -> StructureEditor.this.updateEdit()); + addButton("add", e -> StructureEditor.this.addStructure()); + addButton("remove", e -> StructureEditor.this.remove()); + addButton("copy", e -> StructureEditor.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() { + 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; + BiomeStructureData data=new BiomeStructureData(model.get(selected)); + model.add(model.size(),data); + } + + private void updateEdit() { + + int selected=list.getSelectedIndex(); + if(selected<0) + return; + edit.setCurrentStructure(model.get(selected),currentData); + } + + void addStructure() + { + BiomeStructureData data=new BiomeStructureData(); + model.add(model.size(),data); + } + void remove() + { + int selected=list.getSelectedIndex(); + if(selected<0) + return; + model.remove(selected); + } + public void setStructures(BiomeData data) { + + currentData=data; + model.clear(); + if(data==null||data.structures==null) + return; + for (int i=0;i> images=new HashMap<>(); public HashMap> getImages() { return images; } - public SwingAtlas(FileHandle path) + public SwingAtlas(FileHandle path,int imageSize) { + this.imageSize=imageSize; if(!path.exists()||!path.toString().endsWith(".atlas")) return; TextureAtlas.TextureAtlasData data=new TextureAtlas.TextureAtlasData(path,path.parent(),false); @@ -37,17 +39,28 @@ public class SwingAtlas { images.put(name,new ArrayList<>()); } ArrayList imageList=images.get(name); - try { + try + { imageList.add(spriteToImage(region)); - } catch (IOException e) { + } + catch (IOException e) + { e.printStackTrace(); } } } + public SwingAtlas(FileHandle path) + { + this(path,32); + } private ImageIcon spriteToImage(TextureAtlas.TextureAtlasData.Region sprite) throws IOException { BufferedImage img = ImageIO.read(sprite.page.textureFile.file()); - return new ImageIcon(img.getSubimage(sprite.left,sprite.top, sprite.width, sprite.height).getScaledInstance(32,32,SCALE_FAST)); + if(sprite.width== sprite.height) + return new ImageIcon(img.getSubimage(sprite.left,sprite.top, sprite.width, sprite.height).getScaledInstance(imageSize,imageSize,SCALE_FAST)); + if(sprite.width>sprite.height) + return new ImageIcon(img.getSubimage(sprite.left,sprite.top, sprite.width, sprite.height).getScaledInstance(imageSize, (int) (imageSize*(sprite.height/(float)sprite.width)),SCALE_FAST)); + return new ImageIcon(img.getSubimage(sprite.left,sprite.top, sprite.width, sprite.height).getScaledInstance((int) (imageSize*(sprite.width/(float)sprite.height)),imageSize,SCALE_FAST)); } public ImageIcon get(String name) { diff --git a/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java b/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java index 3631e133a8c..63d08c62f24 100644 --- a/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java +++ b/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java @@ -13,11 +13,12 @@ import java.util.Map; * Editor class to edit configuration, maybe moved or removed */ public class SwingAtlasPreview extends Box { + int imageSize=32; private String sprite=""; + private String spriteName=""; Timer timer; public SwingAtlasPreview() { super(BoxLayout.Y_AXIS); - timer = new Timer(200, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { @@ -28,25 +29,51 @@ public class SwingAtlasPreview extends Box { } }); } + public SwingAtlasPreview(int size) { + this(); + imageSize=size; + } int counter=0; List>> labels=new ArrayList<>(); public void setSpritePath(String sprite) { - if(this.sprite==null||this.sprite.equals(sprite)) + setSpritePath(sprite,null); + } + public void setSpritePath(String sprite,String name) { + + if(this.sprite==null||name==null||sprite==null||(this.sprite.equals(sprite)&&(spriteName==null&&spriteName.equals(name)))) return; removeAll(); counter=0; labels.clear(); this.sprite=sprite; - SwingAtlas atlas=new SwingAtlas(Config.instance().getFile(sprite)); + this.spriteName=name; + SwingAtlas atlas=new SwingAtlas(Config.instance().getFile(sprite),imageSize); + int maxCount=0; for(Map.Entry> element:atlas.getImages().entrySet()) { - JLabel image=new JLabel(element.getValue().get(0)); - add(new JLabel(element.getKey())); - add(image); - labels.add(Pair.of(image, element.getValue())); + if(name==null||element.getKey().equals(name)) + { + JLabel image=new JLabel(element.getValue().get(0)); + if(maxCount0) - { - builder.append("-"); - builder.append(reward.count+reward.addMaxCount); - } + builder.append(terrainData.spriteName); label.setText(builder.toString()); return label; } @@ -58,7 +50,7 @@ public class TerrainsEditor extends JComponent{ list.setCellRenderer(new TerrainDataRenderer()); list.addListSelectionListener(e -> TerrainsEditor.this.updateEdit()); - addButton("add", e -> TerrainsEditor.this.addReward()); + addButton("add", e -> TerrainsEditor.this.addTerrain()); addButton("remove", e -> TerrainsEditor.this.remove()); addButton("copy", e -> TerrainsEditor.this.copy()); BorderLayout layout=new BorderLayout(); @@ -98,10 +90,10 @@ public class TerrainsEditor extends JComponent{ int selected=list.getSelectedIndex(); if(selected<0) return; - edit.setCurrentTerrain(model.get(selected)); + edit.setCurrentTerrain(model.get(selected),currentData); } - void addReward() + void addTerrain() { BiomeTerrainData data=new BiomeTerrainData(); model.add(model.size(),data); @@ -113,14 +105,16 @@ public class TerrainsEditor extends JComponent{ return; model.remove(selected); } - public void setTerrains(BiomeTerrainData[] terrain) { + public void setTerrains(BiomeData data) { + currentData=data; model.clear(); - if(terrain==null) + if(data==null||data.terrain==null) return; - for (int i=0;i WorldEditor.this.save()); toolBar.add(newButton); + newButton=new JButton("load"); newButton.addActionListener(e -> WorldEditor.this.load()); toolBar.add(newButton); + + toolBar.addSeparator(); + + newButton=new JButton("test map"); + newButton.addActionListener(e -> WorldEditor.this.test()); + toolBar.add(newButton); + } + + private void test() { + + String javaHome = System.getProperty("java.home"); + String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; + String classpath = System.getProperty("java.class.path"); + String className = forge.adventure.Main.class.getName(); + + ArrayList command = new ArrayList<>(); + command.add(javaBin); + command.add("-cp"); + command.add(classpath); + command.add(className); + + command.add("testMap"); + + ProcessBuilder build= new ProcessBuilder(command); + build .redirectInput(ProcessBuilder.Redirect.INHERIT) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT); + try { + Process process= build.start(); + } catch (IOException e) { + throw new RuntimeException(e); + } } void save() diff --git a/forge-gui-mobile/pom.xml b/forge-gui-mobile/pom.xml index 0c4ef128e59..e34f7d42fa7 100644 --- a/forge-gui-mobile/pom.xml +++ b/forge-gui-mobile/pom.xml @@ -55,6 +55,11 @@ gdx-freetype 1.11.0 + + com.github.sjcasey21 + wavefunctioncollapse + 0.2.2 + diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 7da28d0c317..0cc6f9dabbc 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -123,6 +123,7 @@ public class Forge implements ApplicationListener { private static Cursor cursor0, cursor1, cursor2, cursorA0, cursorA1, cursorA2; public static boolean forcedEnglishonCJKMissing = false; public static boolean adventureLoaded = false; + public static boolean createNewAdventureMap = false; private static Localizer localizer; public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM, boolean isTablet, int AndroidAPI, String AndroidRelease, String deviceName) { diff --git a/forge-gui-mobile/src/forge/adventure/data/BiomeData.java b/forge-gui-mobile/src/forge/adventure/data/BiomeData.java index 3aa7fb92d8f..61a2b1a5193 100644 --- a/forge-gui-mobile/src/forge/adventure/data/BiomeData.java +++ b/forge-gui-mobile/src/forge/adventure/data/BiomeData.java @@ -30,6 +30,7 @@ public class BiomeData implements Serializable { public String[] spriteNames; public List enemies; public List pointsOfInterest; + public BiomeStructureData[] structures; private ArrayList enemyList; private ArrayList pointOfInterestList; diff --git a/forge-gui-mobile/src/forge/adventure/data/BiomeStructureData.java b/forge-gui-mobile/src/forge/adventure/data/BiomeStructureData.java new file mode 100644 index 00000000000..50df63f6157 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/data/BiomeStructureData.java @@ -0,0 +1,38 @@ +package forge.adventure.data; + +import java.awt.image.BufferedImage; + +public class BiomeStructureData { + public int N = 3; + public float x; + public float y; + public float size; + public boolean randomPosition; + public boolean collision; + + public String structureAtlasPath; + public boolean periodicInput; + public float height; + public float width; + public int ground; + public int symmetry; + public boolean periodicOutput; + + public BiomeStructureData( ) + { + + } + public BiomeStructureData(BiomeStructureData biomeStructureData) { + this.structureAtlasPath=biomeStructureData.structureAtlasPath; + this.x=biomeStructureData.x; + this.y=biomeStructureData.y; + this.size=biomeStructureData.size; + this.randomPosition=biomeStructureData.randomPosition; + this.collision=biomeStructureData.collision; + } + + public BufferedImage sourceImage() { + + return null; + } +} diff --git a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java index 74e37b2d57a..6899204efdf 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java @@ -228,6 +228,11 @@ public class NewGameScene extends UIScene { public void enter() { updateAvatar(); Gdx.input.setInputProcessor(stage); //Start taking input from the ui + + if(Forge.createNewAdventureMap) + { + start(); + } } @Override diff --git a/forge-gui-mobile/src/forge/adventure/scene/StartScene.java b/forge-gui-mobile/src/forge/adventure/scene/StartScene.java index 7157a93e541..238af360f14 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/StartScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/StartScene.java @@ -7,9 +7,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.utils.Align; import forge.Forge; import forge.adventure.stage.GameHUD; +import forge.adventure.stage.GameStage; import forge.adventure.stage.MapStage; import forge.adventure.util.Config; import forge.adventure.util.Controls; +import forge.adventure.util.Current; import forge.adventure.world.WorldSave; import forge.screens.TransitionScreen; @@ -104,6 +106,13 @@ public class StartScene extends UIScene { } Gdx.input.setInputProcessor(stage); //Start taking input from the ui + + if(Forge.createNewAdventureMap) + { + this.NewGame(); + Current.setDebug(true); + GameStage.maximumScrollDistance=4f; + } } @Override diff --git a/forge-gui-mobile/src/forge/adventure/stage/GameStage.java b/forge-gui-mobile/src/forge/adventure/stage/GameStage.java index d904c379224..daa2b3370f6 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/GameStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/GameStage.java @@ -36,6 +36,8 @@ public abstract class GameStage extends Stage { private float touchY = -1; private final float timer = 0; private float animationTimeout = 0; + public static float maximumScrollDistance=1.5f; + public static float minimumScrollDistance=0.3f; public void startPause(float i) { startPause(i, null); @@ -222,10 +224,10 @@ public abstract class GameStage extends Stage { if (isPaused()) return true; camera.zoom += (amountY * 0.03); - if (camera.zoom < 0.3f) - camera.zoom = 0.3f; - if (camera.zoom > 1.5f) - camera.zoom = 1.5f; + if (camera.zoom < minimumScrollDistance) + camera.zoom = minimumScrollDistance; + if (camera.zoom > maximumScrollDistance) + camera.zoom = maximumScrollDistance; return super.scrolled(amountX, amountY); } diff --git a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java index c7e14a7f550..7fa6d0ce2f5 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java @@ -176,19 +176,7 @@ public class WorldStage extends GameStage implements SaveFileContent { public boolean isColliding(Rectangle boundingRect) { - World world = WorldSave.getCurrentSave().getWorld(); - int currentBiome = World.highestBiome(world.getBiome((int) boundingRect.getX() / world.getTileSize(), (int) boundingRect.getY() / world.getTileSize())); - if(currentBiome==0) - return true; - currentBiome = World.highestBiome(world.getBiome((int) (boundingRect.getX()+boundingRect.getWidth()) / world.getTileSize(), (int) boundingRect.getY() / world.getTileSize())); - if(currentBiome==0) - return true; - currentBiome = World.highestBiome(world.getBiome((int) (boundingRect.getX()+boundingRect.getWidth())/ world.getTileSize(), (int) (boundingRect.getY()+boundingRect.getHeight()) / world.getTileSize())); - if(currentBiome==0) - return true; - currentBiome = World.highestBiome(world.getBiome((int) boundingRect.getX() / world.getTileSize(), (int) (boundingRect.getY()+boundingRect.getHeight()) / world.getTileSize())); - - return (currentBiome==0); + return WorldSave.getCurrentSave().getWorld().collidingTile(boundingRect); } private void HandleMonsterSpawn(float delta) { diff --git a/forge-gui-mobile/src/forge/adventure/world/BiomeStructure.java b/forge-gui-mobile/src/forge/adventure/world/BiomeStructure.java new file mode 100644 index 00000000000..e991e52f183 --- /dev/null +++ b/forge-gui-mobile/src/forge/adventure/world/BiomeStructure.java @@ -0,0 +1,114 @@ +package forge.adventure.world; + +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.github.sjcasey21.wavefunctioncollapse.OverlappingModel; +import forge.adventure.data.BiomeStructureData; +import forge.adventure.util.Config; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +public class BiomeStructure { + + private BiomeStructureData data; + long seed; + private int biomeWidth; + private int biomeHeight; + private int dataMap[][]; + boolean init=false; + private TextureAtlas structureAtlas; + public BiomeStructure(BiomeStructureData data,long seed,int width,int height) + { + this.data=data; + this.seed=seed; + this.biomeWidth = width; + this.biomeHeight = height; + } + public TextureAtlas atlas() { + if(structureAtlas==null) + { + structureAtlas = Config.instance().getAtlas(data.structureAtlasPath); + } + return structureAtlas; + } + public int structureObjectCount() { + int count=0; + for(TextureAtlas.AtlasRegion region:atlas ().getRegions()) + { + if(region.name.startsWith("structure")) + { + count++; + } + } + return count; + } + + public int objectID(int x, int y) { + + if(!init) + { + init=true; + initialize(); + } + if(x>biomeWidth*data.width) + return -1; + if(y>biomeHeight*data.height) + return -1; + if(x colorIdMap=new HashMap<>(); + int counter=0; + for(TextureAtlas.AtlasRegion region:atlas ().getRegions()) + { + if(region.name.startsWith("structure")) + { + String[] split= region.name.split("_"); + if(split.length<2) + continue; + int rgb=Integer.parseInt(split[1],16); + colorIdMap.put(rgb,counter); + counter++; + } + } + BufferedImage image=model.graphics(); + dataMap=new int[image.getWidth()][image.getHeight()]; + for(int x=0;x> images = new ArrayList<>(); ArrayList> smallImages = new ArrayList<>(); ArrayList> edgeImages = new ArrayList<>(); @@ -45,7 +46,6 @@ public class BiomeTexture implements Serializable { FThreads.invokeInEdtNowOrLater(new Runnable() { @Override public void run() { - Pixmap completePicture = null; if (images != null) { for (ArrayList val : images) { @@ -79,22 +79,38 @@ public class BiomeTexture implements Serializable { edgeImages = new ArrayList<>(); ArrayList regions =new ArrayList<>(); + ArrayList source =new ArrayList<>(); regions.add(Config.instance().getAtlas(data.tilesetAtlas).findRegion(data.tilesetName)); + source.add(Config.instance().getAtlas(data.tilesetAtlas)); if(data.terrain!=null) { for(BiomeTerrainData terrain:data.terrain) { regions.add(Config.instance().getAtlas(data.tilesetAtlas).findRegion(terrain.spriteName)); + source.add(Config.instance().getAtlas(data.tilesetAtlas)); } } + if(data.structures!=null) + { + for(BiomeStructureData structureData:data.structures) + { + BiomeStructure structure=new BiomeStructure(structureData,0,0,0); + for(TextureAtlas.AtlasRegion region:structure.atlas ().getRegions()) + { + if(region.name.startsWith("structure")) + { + regions.add(region); + source.add(structure.atlas()); + } + } + } + } + for (TextureAtlas.AtlasRegion region : regions) { ArrayList pics = new ArrayList<>(); ArrayList spics = new ArrayList<>(); - if (completePicture == null) { region.getTexture().getTextureData().prepare(); - completePicture = region.getTexture().getTextureData().consumePixmap(); - } - + Pixmap completePicture = region.getTexture().getTextureData().consumePixmap(); for (int y = 0; y < 4; y++) { for (int x = 0; x < 3; x++) { int px = region.getRegionX() + (x * tileSize); @@ -117,6 +133,7 @@ public class BiomeTexture implements Serializable { smallImages.add(spics); edgeImages.add(new IntMap<>()); + completePicture.dispose(); } } }); @@ -124,6 +141,8 @@ public class BiomeTexture implements Serializable { public Pixmap getPixmap(int biomeSubIndex) { if (biomeSubIndex >= edgeImages.size() || biomeSubIndex < 0) { + if(emptyPixmap==null) + emptyPixmap=new Pixmap(1, 1, Pixmap.Format.RGBA8888); return emptyPixmap; } return images.get(biomeSubIndex).get(BigPictures.Center.value); diff --git a/forge-gui-mobile/src/forge/adventure/world/World.java b/forge-gui-mobile/src/forge/adventure/world/World.java index a7ecf515a65..c450f067507 100644 --- a/forge-gui-mobile/src/forge/adventure/world/World.java +++ b/forge-gui-mobile/src/forge/adventure/world/World.java @@ -9,11 +9,7 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Json; -import forge.adventure.data.BiomeData; -import forge.adventure.data.BiomeSpriteData; -import forge.adventure.data.BiomeTerrainData; -import forge.adventure.data.PointOfInterestData; -import forge.adventure.data.WorldData; +import forge.adventure.data.*; import forge.adventure.pointofintrest.PointOfInterest; import forge.adventure.pointofintrest.PointOfInterestMap; import forge.adventure.scene.Scene; @@ -24,10 +20,7 @@ import forge.adventure.util.SaveFileContent; import forge.adventure.util.SaveFileData; import org.apache.commons.lang3.tuple.Pair; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Random; +import java.util.*; /** * Class that will create the world from the configuration @@ -55,6 +48,26 @@ public class World implements Disposable, SaveFileContent { return (int) (Math.log(Long.highestOneBit(biome)) / Math.log(2)); } + public boolean collidingTile(Rectangle boundingRect) + { + Set> points=new HashSet<>(); + + int xLeft=(int) boundingRect.getX() / getTileSize(); + int yTop=(int) boundingRect.getY() / getTileSize(); + int xRight=(int) (boundingRect.getX()+boundingRect.getWidth()) / getTileSize(); + int yBottom= (int) (boundingRect.getY()+boundingRect.getHeight()) / getTileSize(); + + if(getBiome(xLeft,yTop)==0) + return true; + if(getBiome(xLeft,yBottom)==0) + return true; + if(getBiome(xRight,yBottom)==0) + return true; + if(getBiome(xRight,yTop)==0) + return true; + + return false; + } public void loadWorldData() { if(worldDataLoaded) return; @@ -85,6 +98,9 @@ public class World implements Disposable, SaveFileContent { biomeImage=saveFileData.readPixmap("biomeImage"); biomeMap=(long[][])saveFileData.readObject("biomeMap"); terrainMap=(int[][])saveFileData.readObject("terrainMap"); + + + width=saveFileData.readInt("width"); height=saveFileData.readInt("height"); mapObjectIds = new SpritesDataMap(getChunkSize(), this.data.tileSize, this.data.width / getChunkSize()); @@ -269,6 +285,7 @@ public class World implements Disposable, SaveFileContent { endX = width; endY = height; } + HashMap structureDataMap=new HashMap<>(); for (int x = beginX; x < endX; x++) { for (int y = beginY; y < endY; y++) { //value 0-1 based on noise @@ -288,16 +305,34 @@ public class World implements Disposable, SaveFileContent { pix.drawPixel(x, y); biomeMap[x][y] |= (1L << biomeIndex); int terrainCounter=1; - if(biome.terrain==null) - continue; - for(BiomeTerrainData terrain:biome.terrain) + if(biome.terrain!=null) { - float terrainNoise = ((float)noise.eval(x / (float) width * (noiseZoom*terrain.resolution), y / (float) height * (noiseZoom*terrain.resolution)) + 1) / 2; - if(terrainNoise>=terrain.min&&terrainNoise<=terrain.max) + for(BiomeTerrainData terrain:biome.terrain) { - terrainMap[x][y]=terrainCounter; + float terrainNoise = ((float)noise.eval(x / (float) width * (noiseZoom*terrain.resolution), y / (float) height * (noiseZoom*terrain.resolution)) + 1) / 2; + if(terrainNoise>=terrain.min&&terrainNoise<=terrain.max) + { + terrainMap[x][y]=terrainCounter; + } + terrainCounter++; + } + } + if(biome.structures!=null) + { + for(BiomeStructureData data:biome.structures) + { + BiomeStructure structure; + if(!structureDataMap.containsKey(data)) + { + structureDataMap.put(data,new BiomeStructure(data,seed,biomeWidth,biomeHeight)); + } + structure=structureDataMap.get(data); + int structureIndex=structure.objectID(x-biomeXStart,y-biomeYStart); + if(structureIndex>=0) + terrainMap[x][y]=terrainCounter+structureIndex; + + terrainCounter+=structure.structureObjectCount(); } - terrainCounter++; } } @@ -432,6 +467,9 @@ public class World implements Disposable, SaveFileContent { for (int y = (int) currentPoint.y - 1; y < currentPoint.y + 2; y++) { if(x<0||y<=0||x>=width||y>height)continue; biomeMap[x][height - y] |= (1L << biomeIndex); + terrainMap[x][height-y]=0; + + pix.drawPixel(x, height-y); } } @@ -465,7 +503,9 @@ public class World implements Disposable, SaveFileContent { if( (int)currentPoint.x<0|| (int)currentPoint.y<=0|| (int)currentPoint.x>=width|| (int)currentPoint.y>height)continue; biomeMap[(int) currentPoint.x][height - (int) currentPoint.y] |= (1L << biomeIndex); + terrainMap[(int) currentPoint.x][height - (int) currentPoint.y]=0; pix.drawPixel((int) currentPoint.x, height - (int) currentPoint.y); + } } @@ -482,6 +522,8 @@ public class World implements Disposable, SaveFileContent { BiomeSpriteData sprite = data.GetBiomeSprites().getSpriteData(name); double spriteNoise = (noise.eval(x / (double) width * noiseZoom*sprite.resolution, y / (double) invertedHeight * noiseZoom*sprite.resolution) + 1) / 2; if (spriteNoise >= sprite.startArea && spriteNoise <= sprite.endArea) { + if(terrainMap[x][invertedHeight]>biome.terrain.length) + continue; if (random.nextFloat() <= sprite.density) { String spriteKey = sprite.key(); int key; diff --git a/forge-gui-mobile/src/forge/adventure/world/WorldSave.java b/forge-gui-mobile/src/forge/adventure/world/WorldSave.java index 0bdbbd533c1..ca606512ecb 100644 --- a/forge-gui-mobile/src/forge/adventure/world/WorldSave.java +++ b/forge-gui-mobile/src/forge/adventure/world/WorldSave.java @@ -58,6 +58,8 @@ public class WorldSave { static public boolean load(int currentSlot) { String fileName = WorldSave.getSaveFile(currentSlot); + if(!new File(fileName).exists()) + return false; new File(getSaveDir()).mkdirs(); try { try(FileInputStream fos = new FileInputStream(fileName); diff --git a/forge-gui-mobile/src/forge/screens/SplashScreen.java b/forge-gui-mobile/src/forge/screens/SplashScreen.java index 504530999b8..5b9ffcb8e2b 100644 --- a/forge-gui-mobile/src/forge/screens/SplashScreen.java +++ b/forge-gui-mobile/src/forge/screens/SplashScreen.java @@ -265,6 +265,14 @@ public class SplashScreen extends FContainer { add(btnHome); btnAdventure.setBounds(btn_x, btn_y + height + padding / 2, btn_w, height); add(btnAdventure); + + if(Forge.createNewAdventureMap) + { + bgAnimation.progress = 1; + bgAnimation.openAdventure = true; + Forge.openAdventure(); + Forge.clearSplashScreen(); + } } } diff --git a/forge-gui/res/adventure/Shandalar/world/green.json b/forge-gui/res/adventure/Shandalar/world/green.json index bf0dca678ba..def5769876c 100644 --- a/forge-gui/res/adventure/Shandalar/world/green.json +++ b/forge-gui/res/adventure/Shandalar/world/green.json @@ -19,6 +19,14 @@ "resolution": 10 } ], + "structures":[ + { + "structureAtlasPath":"world/tilesets/forest.atlas", + "x": 0.5, + "y": 0.5, + "size": 0.3 + } + ], "width": 0.7, "height": 0.7, "color": "59a650", diff --git a/forge-gui/res/adventure/Shandalar/world/tilesets/autotiles.png b/forge-gui/res/adventure/Shandalar/world/tilesets/autotiles.png index 8c651dffb0897c7d0bfa57a3f936ebe3e38a04ec..c10104d2528f02593787acc2cf3f9c9e4be79fad 100644 GIT binary patch delta 12980 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 B5Iz6^ diff --git a/forge-gui/res/adventure/Shandalar/world/tilesets/forest.atlas b/forge-gui/res/adventure/Shandalar/world/tilesets/forest.atlas new file mode 100644 index 00000000000..bb08300ae21 --- /dev/null +++ b/forge-gui/res/adventure/Shandalar/world/tilesets/forest.atlas @@ -0,0 +1,20 @@ + +forest.png +size: 96,64 +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +Source + rotate: false + xy: 0, 0 + size: 16, 16 + orig: 0, 0 + offset: 0, 0 + index: 0 +structure_000000 + rotate: false + xy: 48, 0 + size: 48, 64 + orig: 0, 0 + offset: 0, 0 + index: 0 \ No newline at end of file diff --git a/forge-gui/res/adventure/Shandalar/world/tilesets/forest.png b/forge-gui/res/adventure/Shandalar/world/tilesets/forest.png new file mode 100644 index 0000000000000000000000000000000000000000..cf8dd71e0c9d2cb2327c33398a99d087c459cf34 GIT binary patch literal 16786 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 literal 0 HcmV?d00001