/******************************************************************************* * RPGMaker is a plugin which imports RPGMaker 2000 or 2003 maps into Rhythos. * Use at your own risk, this plugin is not feature-complete, and requires some * fixing up of maps and project structure after import. * * The RPGMaker import code was inspired by EasyRPG * * Copyright (C) 2013 David Maletz * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mrpg.plugin.rpgmaker; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import org.w3c.dom.Document; import org.w3c.dom.Element; import mrpg.editor.MapEditor; import mrpg.editor.MenuItem; import mrpg.editor.resource.AutoTile; import mrpg.editor.resource.AutotileFormat; import mrpg.editor.resource.CroppedImage; import mrpg.editor.resource.Folder; import mrpg.editor.resource.Image; import mrpg.editor.resource.Map; import mrpg.editor.resource.Project; import mrpg.editor.resource.Resource; import mrpg.editor.resource.TileResource; import mrpg.editor.resource.Tileset; import mrpg.export.Graphic; import mrpg.plugin.Plugin; import mrpg.world.AutoTileFormat; import mrpg.world.BasicTilemap; import mrpg.world.Cell; import mrpg.world.Direction; import mrpg.world.World; public class ImportProject implements Plugin, ActionListener { private MenuItem item; public void install(boolean init) throws Exception { item = MapEditor.menu_bar.addMenu("Tools", null).addItem("Import RPGMaker Project", null, this); MapEditor.instance.updateMenuBar(); } public void uninstall() throws Exception { if(item != null){ MapEditor.menu_bar.addMenu("Tools", null).remove(item); MapEditor.instance.updateMenuBar(); } } public void saveSettings(Document doc, Element e) throws Exception { Element element = doc.createElement("rpgmakerDirectory"); element.setTextContent(rpgmakerChooser.getCurrentDirectory().toString()); e.appendChild(element); } public void readSettings(Document doc, Element e)throws Exception { rpgmakerChooser.setCurrentDirectory(new File(e.getElementsByTagName("rpgmakerDirectory").item(0).getTextContent())); } public static JFileChooser rpgmakerChooser = new JFileChooser(); static { rpgmakerChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); rpgmakerChooser.setDialogTitle("Choose RPGMaker Project"); } private static File selectProject(Component parent) throws Exception { if(rpgmakerChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION){ return rpgmakerChooser.getSelectedFile(); } else throw new Exception(); } private static class ImportDialog extends JDialog implements ActionListener { private static final long serialVersionUID = -9167142368732222315L; private static final String BROWSE_IN = "browse_in", BROWSE_OUT = "browse_out"; private final Component component; private final JTextField in, out; private File in_f, out_f; public ImportDialog(Component com){ super(JOptionPane.getFrameForComponent(com), "Import RPGMaker Project", true); component = com; setResizable(false); Container c = getContentPane(); c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS)); JPanel settings = new JPanel(); settings.setLayout(new BoxLayout(settings, BoxLayout.Y_AXIS)); settings.setBorder(BorderFactory.createRaisedBevelBorder()); JPanel inner = new JPanel(new BorderLayout()); inner.setBorder(BorderFactory.createTitledBorder("Project to Import")); in = new JTextField("", 20); inner.add(in, BorderLayout.CENTER); settings.add(inner); JButton b = new JButton("..."); b.setActionCommand(BROWSE_IN); b.addActionListener(this); inner.add(b, BorderLayout.EAST); inner = new JPanel(new BorderLayout()); inner.setBorder(BorderFactory.createTitledBorder("New Project Directory")); out = new JTextField("", 20); inner.add(out, BorderLayout.CENTER); settings.add(inner); b = new JButton("..."); b.setActionCommand(BROWSE_OUT); b.addActionListener(this); inner.add(b, BorderLayout.EAST); c.add(settings); inner = new JPanel(); b = new JButton("Ok"); b.setActionCommand(MapEditor.OK); b.addActionListener(this); inner.add(b); b = new JButton("Cancel"); b.setActionCommand(MapEditor.CANCEL); b.addActionListener(this); inner.add(b); c.add(inner); setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); pack(); setVisible(true); } public void checkIn() throws Exception { File ldb = new File(in_f, "RPG_RT.ldb"); File lmt = new File(in_f, "RPG_RT.lmt"); if(!ldb.exists() || !lmt.exists()){ JOptionPane.showMessageDialog(this, "\""+in_f.getAbsolutePath()+"\" is not an RPGMaker project.", "Import RPGMaker Project", JOptionPane.ERROR_MESSAGE); throw new Exception(); } } public void checkOut() throws Exception { if(out_f.exists() && out_f.listFiles().length != 0){ JOptionPane.showMessageDialog(this, "\""+out_f.getAbsolutePath()+"\" must be an empty folder.", "Import RPGMaker Project", JOptionPane.ERROR_MESSAGE); throw new Exception(); } } public void actionPerformed(ActionEvent e){ String command = e.getActionCommand(); try{ if(command == MapEditor.OK){ in_f = new File(in.getText()); checkIn(); out_f = new File(out.getText()); checkOut(); if(!out_f.exists() && !out_f.mkdirs()){ JOptionPane.showMessageDialog(this, "Unable to create folder: \""+out_f.getAbsolutePath()+"\" for new project.", "Import RPGMaker Project", JOptionPane.ERROR_MESSAGE); throw new Exception(); } dispose(); Import(component, in_f, out_f); } else if(command == BROWSE_IN){ in_f = selectProject(this); checkIn(); in.setText(in_f.getAbsolutePath()); } else if(command == BROWSE_OUT){ out_f = Project.selectProject(this); checkOut(); out.setText(out_f.getAbsolutePath()); } else dispose(); }catch(Exception ex){} } } public void actionPerformed(ActionEvent e){ new ImportDialog((Component)e.getSource()); } public static File getAvailable(File f, String name, String ext){ File file = new File(f, name+ext); int i=2; while(file.exists()){file = new File(f, name+ext); i++;} return file; } private static AutoTileFormat getAutoTileFormat() throws Exception { byte fmt[] = new byte[256*8]; for(int i=0; i<256; i++){ int x1, y1, x2, y2, x3, y3, x4, y4; boolean left = Direction.left(i), right = Direction.right(i), up = Direction.up(i), down = Direction.down(i), ul = Direction.upper_left(i), ur = Direction.upper_right(i), ll = Direction.lower_left(i), lr = Direction.lower_right(i); if(!left && !up){x1 = 0; y1 = (right || down)?2:0;} else if(!up){x1 = (right)?2:4; y1 = 2;} else if(!left){x1 = 0; y1 = (down)?4:6;} else if(ul){x1 = (right)?2:4; y1 = (down)?4:6;} else {x1 = 4; y1 = 0;} if(!right && !up){x2 = (left || down)?5:1; y2 = (left || down)?2:0;} else if(!up){x2 = (left)?3:1; y2 = 2;} else if(!right){x2 = 5; y2 = (down)?4:6;} else if(ur){x2 = (left)?3:1; y2 = (down)?4:6;} else {x2 = 5; y2 = 0;} if(!left && !down){x3 = 0; y3 = (right || up)?7:1;} else if(!down){x3 = (right)?2:4; y3 = 7;} else if(!left){x3 = 0; y3 = (up)?5:3;} else if(ll){x3 = (right)?2:4; y3 = (up)?5:3;} else {x3 = 4; y3 = 1;} if(!right && !down){x4 = (left || up)?5:1; y4 = (left || up)?7:1;} else if(!down){x4 = (left)?3:1; y4 = 7;} else if(!right){x4 = 5; y4 = (up)?5:3;} else if(lr){x4 = (left)?3:1; y4 = (up)?5:3;} else {x4 = 5; y4 = 1;} int j = i*8; fmt[j++] = (byte)x1; fmt[j++] = (byte)y1; fmt[j++] = (byte)x2; fmt[j++] = (byte)y2; fmt[j++] = (byte)x3; fmt[j++] = (byte)y3; fmt[j++] = (byte)x4; fmt[j++] = (byte)y4; } return new AutoTileFormat(fmt,3,4); } private static AutoTileFormat getWaterFormat() throws Exception { byte fmt[] = new byte[256*8]; for(int i=0; i<256; i++){ int x1, y1, x2, y2, x3, y3, x4, y4; boolean left = Direction.left(i), right = Direction.right(i), up = Direction.up(i), down = Direction.down(i), ul = Direction.upper_left(i), ur = Direction.upper_right(i), ll = Direction.lower_left(i), lr = Direction.lower_right(i); if(!left && !up){x1 = 0; y1 = 0;} else if(!up){x1 = 0; y1 = 4;} else if(!left){x1 = 0; y1 = 2;} else if(ul){x1 = 0; y1 = 8;} else {x1 = 0; y1 = 6;} if(!right && !up){x2 = 1; y2 = 0;} else if(!up){x2 = 1; y2 = 4;} else if(!right){x2 = 1; y2 = 2;} else if(ur){x2 = 1; y2 = 8;} else {x2 = 1; y2 = 6;} if(!left && !down){x3 = 0; y3 = 1;} else if(!down){x3 = 0; y3 = 5;} else if(!left){x3 = 0; y3 = 3;} else if(ll){x3 = 0; y3 = 9;} else {x3 = 0; y3 = 7;} if(!right && !down){x4 = 1; y4 = 1;} else if(!down){x4 = 1; y4 = 5;} else if(!right){x4 = 1; y4 = 3;} else if(lr){x4 = 1; y4 = 9;} else {x4 = 1; y4 = 7;} int j = i*8; fmt[j++] = (byte)x1; fmt[j++] = (byte)y1; fmt[j++] = (byte)x2; fmt[j++] = (byte)y2; fmt[j++] = (byte)x3; fmt[j++] = (byte)y3; fmt[j++] = (byte)x4; fmt[j++] = (byte)y4; } return new AutoTileFormat(fmt,3,8); } private static AutoTileFormat getDeepWaterFormat() throws Exception { byte fmt[] = new byte[256*8]; for(int i=0; i<256; i++){ int x1, y1, x2, y2, x3, y3, x4, y4; boolean left = Direction.left(i), right = Direction.right(i), up = Direction.up(i), down = Direction.down(i); if(!left && !up){x1 = 0; y1 = 12;} else {x1 = 0; y1 = 14;} if(!right && !up){x2 = 1; y2 = 12;} else {x2 = 1; y2 = 14;} if(!left && !down){x3 = 0; y3 = 13;} else {x3 = 0; y3 = 15;} if(!right && !down){x4 = 1; y4 = 13;} else {x4 = 1; y4 = 15;} int j = i*8; fmt[j++] = (byte)x1; fmt[j++] = (byte)y1; fmt[j++] = (byte)x2; fmt[j++] = (byte)y2; fmt[j++] = (byte)x3; fmt[j++] = (byte)y3; fmt[j++] = (byte)x4; fmt[j++] = (byte)y4; } return new AutoTileFormat(fmt,3,8); } public static void Import(Component c, File from, File to){ LDBReader db = null; LMTReader tree = null; ArrayList<LMUReader> maps = new ArrayList<LMUReader>(); try{ File ldb = new File(from, "RPG_RT.ldb"); if(!ldb.exists()) JOptionPane.showMessageDialog(c, "\""+from.getAbsolutePath()+"\" is not an RPGMaker project: Missing RPG_RT.ldb.", "Unable to Import!", JOptionPane.ERROR_MESSAGE); File lmt = new File(from, "RPG_RT.lmt"); if(!lmt.exists()) JOptionPane.showMessageDialog(c, "\""+from.getAbsolutePath()+"\" is not an RPGMaker project: Missing RPG_RT.lmt.", "Unable to Import!", JOptionPane.ERROR_MESSAGE); db = new LDBReader(ldb); tree = new LMTReader(lmt); for(int i=0; i<tree.maps.size(); i++){ LMTReader.Map m = tree.maps.get(i); if(m == null) continue; try{ LMUReader lmu = new LMUReader(new File(from, "Map"+String.format("%04d", i)+".lmu")); lmu.chipset = db.chipsets.get(lmu.chipset_id); if(lmu.chipset != null){ lmu.map = m; maps.add(lmu); } }catch(Exception e){} } }catch(Exception e){ JOptionPane.showMessageDialog(c, "Could not read RPGMaker project at \""+from.getAbsolutePath()+"\".", "Unable to Import!", JOptionPane.ERROR_MESSAGE); return; } try{ MapEditor e = MapEditor.instance; Project p = new Project(to, e); File code = new File("project/com"), project = p.getFile(); if(code.exists()){ Resource.copyDir(code, new File(project, "com")); p.refresh(); } p.init(16); File f = new File(project, "images"); Resource images = Folder.create(f, e); p.add(images); f = new File(images.getFile(), "chipsets"); Resource chipsets = Folder.create(f, e); images.add(chipsets); f = new File(images.getFile(), "cropped"); Resource cropped = Folder.create(f, e); images.add(cropped); f = new File(project, "tilesets"); Resource tilesets = Folder.create(f, e); p.add(tilesets); f = new File(project, "autotiles"); Resource autotiles = Folder.create(f, e); p.add(autotiles); f = new File(project, "maps"); Resource map = Folder.create(f, e); p.add(map); AutotileFormat autotile = AutotileFormat.create(p, e, p, "RM2KAuto", getAutoTileFormat()); AutotileFormat water = AutotileFormat.create(p, e, p, "RM2KWater", getWaterFormat()); AutotileFormat deepwater = AutotileFormat.create(p, e, p, "RM2KDeepWater", getDeepWaterFormat()); for(LDBReader.Chipset chipset : db.chipsets){ if(chipset == null) continue; File file = getAvailable(chipsets.getFile(), chipset.name, "."+Image.EXT); chipset._image = Image.createImage(new Graphic(chipset.chipset), file, e, p); String name = chipset._image.getName(); CroppedImage crop = CroppedImage.create(p, chipset._image, cropped, name, 4*16*3, 0, 18*16, 16*16); chipsets.add(chipset._image); TileResource t = Tileset.createTileset(tilesets, e, p, name, crop); chipset.tileset = t; //TODO: Water animations int j = 1; crop = CroppedImage.create(p, chipset._image, cropped, name+"_"+j, 0, 0, 16*3, 16*8); t = AutoTile.createAutoTile(autotiles, e, p, water, name+"_"+j, crop); chipset.autotiles.add(t); j++; chipset.autotiles.add(null); j++; //TODO: water type 2 t = AutoTile.createAutoTile(autotiles, e, p, deepwater, name+"_"+j, crop); chipset.autotiles.add(t); j++; //TODO: animated tiles. chipset.autotiles.add(null); j++; chipset.autotiles.add(null); j++; chipset.autotiles.add(null); j++; for(int y=2; y<4; y++) for(int x=0; x<2; x++){ crop = CroppedImage.create(p, chipset._image, cropped, name+"_"+j, x*16*3, y*16*4, 16*3, 16*4); t = AutoTile.createAutoTile(autotiles, e, p, autotile, name+"_"+j, crop); chipset.autotiles.add(t); j++; } for(int y=0; y<4; y++) for(int x=2; x<4; x++){ crop = CroppedImage.create(p, chipset._image, cropped, name+"_"+j, x*16*3, y*16*4, 16*3, 16*4); t = AutoTile.createAutoTile(autotiles, e, p, autotile, name+"_"+j, crop); chipset.autotiles.add(t); j++; } } int sz = maps.size(), i=0; for(LMUReader lmu : maps){ World w = new World(lmu.width, lmu.height); BasicTilemap tilemap = ((BasicTilemap)lmu.chipset.tileset.getTilemap()); for(int y=0; y<lmu.height; y++){ for(int x=0; x<lmu.width; x++){ Cell cell = w.addCell(x, y); int idx = (y*lmu.width+x)*2; int dx = lmu.lower.get(idx); int dy = lmu.lower.get(idx+1); if(dx >= 12 && dy >= 0) cell.setTile(tilemap.getTile(dx-12, dy), 0, false); else if(dx == -1 && dy >= 0){ TileResource auto = lmu.chipset.autotiles.get(dy); if(auto != null) cell.setTile(auto.getTilemap().getTile(0), 0, false); } dx = lmu.upper.get(idx); dy = lmu.upper.get(idx+1); if(dx >= 12 && dy >= 0) cell.setTile(tilemap.getTile(dx-12, dy), 1, false); } } w.updateNeighbors(); Resource parent; String n = MapEditor.safeName(lmu.map.name); if(lmu.map.parentMap == null) parent = map; else{ if(lmu.map.parentMap.resource != null) parent = lmu.map.parentMap.resource.getParent(); else parent = map; } if(lmu.map.children.size() > 0){ File file = getAvailable(parent.getFile(), n, ""); Resource folder = Folder.create(file, e); parent.add(folder); parent = folder; } File file = getAvailable(parent.getFile(), n, "."+Map.EXT); n = file.getName(); System.out.println(n+" "+i+"/"+sz); i++; //TODO: Would be nice to have a progress monitor. Map m = Map.createMap(parent, e, p, n.substring(0, n.length()-Map.EXT.length()-1), w); lmu.map.resource = m; } MapEditor.instance.getBrowser().addProject(p); }catch(Exception e){ e.printStackTrace(); JOptionPane.showMessageDialog(null, "Could not create a new project at \""+to.getAbsolutePath()+"\".", "Unable to Import!", JOptionPane.ERROR_MESSAGE); } } }