Saving a list of portals to file

Discussion in 'Plugin Development' started by Kevinzuman22, Sep 1, 2017.

Thread Status:
Not open for further replies.
  1. I am currently making a plugin in which enables people to have a unique spawn for every world on the server, as well as a hub.

    As an extra feature, I want to add the possibility to add portals to be used to teleport to any of those spawns or the hub. However, I am having a hard time with coming up with a good idea on how to in the first place create them, but also how to efficiently save them to some kind of file, if that would be useful to do.

    I have a Portal class, a PortalListener class for checking if a person steps into a block that belongs to a portal, and a PortalSaver class to save the data of the portal to a file.

    This is how the classes currently look:
    Portal.java (open)

    Code:
    package me.greenadine.worldspawns.portals;
    
    import com.sk89q.worldedit.bukkit.WorldEditPlugin;
    import com.sk89q.worldedit.bukkit.selections.Selection;
    import me.greenadine.worldspawns.Lang;
    import me.greenadine.worldspawns.Main;
    import me.greenadine.worldspawns.SettingsManager;
    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.block.Block;
    import org.bukkit.configuration.file.FileConfiguration;
    import org.bukkit.configuration.file.YamlConfiguration;
    import org.bukkit.entity.Player;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Portal {
    
        /*
         * WARNING: W.I.P.
         * TODO: Send informative messages.
         * TODO: Test methods.
         */
    
        private Main main;
    
        private File file;
    
        private List<String> portalList = new ArrayList<String>();
        private List<Block> blocks = new ArrayList<Block>();
    
        public boolean enabled;
        private String name;
        private World world;
        private String type;
        private String owner;
        private int blocksCount;
    
        private Block mainBlock;
    
        WorldEditPlugin worldEdit = (WorldEditPlugin) Bukkit.getServer().getPluginManager().getPlugin("WorldEdit");
    
        private String prefix = ChatColor.translateAlternateColorCodes('&', Lang.PREFIX.toString());
    
        SettingsManager settings;
    
        public Portal(Main main, File file) {
            this.main = main;
            this.file = file;
            this.settings = SettingsManager.getInstance();
        }
    
        // TODO: fix warning
        public void createNew(Player player, String name, String type) {
            enabled = main.getConfig().getBoolean("components.portals");
            if (type.equals("hub")) {
                if (enabled == true) {
                    Selection selection = worldEdit.getSelection(player);
    
                    if (selection != null) {
                        Location min = selection.getMinimumPoint();
                        Location max = selection.getMaximumPoint();
    
                        List<Block> blocks = getPortalBlocks(min, max);
    
                        if (blocks.size() > 200) {
                            player.sendMessage(
                                    prefix + ChatColor.translateAlternateColorCodes('&', Lang.PORTAL_SELECTION_TOO_BIG
                                            .toString().replaceAll("%blocks%", String.valueOf(blocks.size()))));
                            return;
                        } else {
                            if (file != null) {
                                if (portalList.contains(name)) {
                                    player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                            Lang.COMMAND_SETPORTAL_EXISTS.toString().replaceAll("%pluginname%", name)));
                                    return;
                                }
                                player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&', Lang.PORTAL_CREATING
                                        .toString().replaceAll("%name%", name).replaceAll("%type%", "hub")));
                                portalList.add(name);
                                if (!file.exists()) {
                                    try {
                                        file.mkdirs();
                                    } catch (Exception e) {
                                        player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                                Lang.PORTAL_FILE_CREATE_FAIL.toString().replaceAll("%name%", name)
                                                        .replaceAll("%type%", "hub")));
                                        e.printStackTrace();
                                        return;
                                    }
                                }
    
                                FileConfiguration portalConf = YamlConfiguration.loadConfiguration(file);
    
                                portalConf.set("name", name);
                                portalConf.set("type", "hub");
                                portalConf.set("owner", player.getName());
    
                                portalConf.createSection("blocks");
    
                                int i = 0;
                                for (Block block : blocks) {
                                    double x = block.getX();
                                    double y = block.getY();
                                    double z = block.getZ();
    
                                    portalConf.set("blocks." + i + ".x", x);
                                    portalConf.set("blocks." + i + ".y", y);
                                    portalConf.set("blocks." + i + ".z", z);
                                    i++;
                                }
                                try {
                                    portalConf.save(file);
                                } catch (IOException e) {
                                    player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                            Lang.PORTAL_FILE_SAVE_FAIL.toString().replaceAll("%name%", name)));
                                    e.printStackTrace();
                                    return;
                                }
                                this.type = type;
                                this.name = name;
                                this.owner = player.getName();
                                this.blocksCount = i;
                                player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.PORTAL_CREATED_HUB.toString().replaceAll("%name%", name)));
                            } else {
                                player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.PORTAL_FILE_NULL.toString().replaceAll("%name%", name)));
    
                                try {
                                    file.mkdirs();
                                } catch (Exception e) {
                                    player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                            Lang.PORTAL_FILE_CREATE_FAIL.toString().replaceAll("%name%", name)));
                                    e.printStackTrace();
                                }
                            }
                        }
                    } else {
                        player.sendMessage(prefix
                                + ChatColor.translateAlternateColorCodes('&', Lang.PORTAL_SELECTION_NULL.toString()));
                        return;
                    }
                } else {
                    player.sendMessage(ChatColor.RED + "Portals feature disabled! Check if WorldEdit is installed.");
                    main.consoleMessage(ChatColor.RED + "Portals feature disabled! Check if WorldEdit is installed.");
                    return;
                }
            } else {
                return;
            }
        }
    
        public void createNew(Player player, String name, String type, String spawn) {
            enabled = main.getConfig().getBoolean("components.portals");
            if (type.equals("spawn")) {
                if (enabled == true) {
                    Selection selection = worldEdit.getSelection(player);
    
                    if (selection != null) {
                        Location min = selection.getMinimumPoint();
                        Location max = selection.getMaximumPoint();
    
                        List<Block> blocks = getPortalBlocks(min, max);
    
                        if (blocks.size() > 200) {
                            player.sendMessage(
                                    prefix + ChatColor.translateAlternateColorCodes('&', Lang.PORTAL_SELECTION_TOO_BIG
                                            .toString().replaceAll("%blocks%", String.valueOf(blocks.size()))));
                            return;
                        } else {
                            if (file != null) {
                                if (portalList.contains(name)) {
                                    player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                            Lang.COMMAND_SETPORTAL_EXISTS.toString().replaceAll("%pluginname%", name)));
                                    return;
                                }
                                player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&', Lang.PORTAL_CREATING
                                        .toString().replaceAll("%name%", name).replaceAll("%type%", "hub")));
                                portalList.add(name);
                                if (!file.exists()) {
                                    try {
                                        file.mkdirs();
                                    } catch (Exception e) {
                                        player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                                Lang.PORTAL_FILE_CREATE_FAIL.toString().replaceAll("%name%", name)
                                                        .replaceAll("%type%", "hub")));
                                        e.printStackTrace();
                                        return;
                                    }
                                }
    
                                FileConfiguration portalConf = YamlConfiguration.loadConfiguration(file);
    
                                portalConf.set("name", name);
                                portalConf.set("type", "hub");
                                portalConf.set("owner", player.getName());
                                portalConf.set("world", world);
    
                                portalConf.createSection("blocks");
    
                                int i = 0;
                                for (Block block : blocks) {
                                    double x = block.getX();
                                    double y = block.getY();
                                    double z = block.getZ();
    
                                    portalConf.set("blocks." + i + ".x", x);
                                    portalConf.set("blocks." + i + ".y", y);
                                    portalConf.set("blocks." + i + ".z", z);
                                    try {
                                        portalConf.save(file);
                                    } catch (IOException e) {
                                        player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                                Lang.PORTAL_FILE_SAVE_FAIL.toString()));
                                        e.printStackTrace();
                                    }
                                    i++;
                                    return;
                                }
                                this.type = type;
                                this.name = name;
                                this.owner = player.getName();
                                player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.PORTAL_CREATED_HUB.toString().replaceAll("%name%", name)));
                            } else {
                                player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.PORTAL_FILE_NULL.toString().replaceAll("%name%", name)));
    
                                try {
                                    file.mkdirs();
                                } catch (Exception e) {
                                    player.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                            Lang.PORTAL_FILE_CREATE_FAIL.toString().replaceAll("%name%", name)));
                                    e.printStackTrace();
                                }
                            }
                        }
                    } else {
                        player.sendMessage(prefix
                                + ChatColor.translateAlternateColorCodes('&', Lang.PORTAL_SELECTION_NULL.toString()));
                        return;
                    }
                } else {
                    player.sendMessage(ChatColor.RED + "Portals feature disabled! Check if WorldEdit is installed.");
                    main.consoleMessage(ChatColor.RED + "Portals feature disabled! Check if WorldEdit is installed.");
                    return;
                }
            } else {
                return;
            }
        }
    
        public String getName() {
            return name;
        }
    
        public List<Block> getBlocks() {
            return blocks;
        }
    
        public List<Block> getPortalBlocks(String portalName) {
            FileConfiguration conf = YamlConfiguration.loadConfiguration(this.getFile(portalName));
            @SuppressWarnings("unchecked")
            List<Block> list = (List<Block>) conf.getList("blocks");
            return list;
        }
    
        public World getWorld() {
            return world;
        }
    
        public String getType(String portalName) {
            FileConfiguration conf = YamlConfiguration.loadConfiguration(this.getFile(portalName));
            String type = conf.getString("type");
            return type;
        }
    
        public Location getPortalLocation() {
            Location location = new Location(world, mainBlock.getX(), mainBlock.getY(), mainBlock.getZ());
            return location;
        }
    
        public List<String> getPortals() {
            return portalList;
        }
    
        public Block getMainBlock() {
            return mainBlock;
        }
    
        public String getType() {
            return type;
        }
    
        public String getOwner() {
            return owner;
        }
    
        public File getFile() {
            return file;
        }
    
        public File getFile(String portalName) {
            File f = new File(main.getDataFolder(), File.separator + "portals" + File.separator + portalName);
            return f;
        }
    
        private List<Block> getPortalBlocks(Location loc1, Location loc2) {
            int topBlockX = (loc1.getBlockX() < loc2.getBlockX() ? loc2.getBlockX() : loc1.getBlockX());
            int bottomBlockX = (loc1.getBlockX() > loc2.getBlockX() ? loc2.getBlockX() : loc1.getBlockX());
    
            int topBlockY = (loc1.getBlockY() < loc2.getBlockY() ? loc2.getBlockY() : loc1.getBlockY());
            int bottomBlockY = (loc1.getBlockY() > loc2.getBlockY() ? loc2.getBlockY() : loc1.getBlockY());
    
            int topBlockZ = (loc1.getBlockZ() < loc2.getBlockZ() ? loc2.getBlockZ() : loc1.getBlockZ());
            int bottomBlockZ = (loc1.getBlockZ() > loc2.getBlockZ() ? loc2.getBlockZ() : loc1.getBlockZ());
    
            for (int x = bottomBlockX; x <= topBlockX; x++) {
                for (int z = bottomBlockZ; z <= topBlockZ; z++) {
                    for (int y = bottomBlockY; y <= topBlockY; y++) {
                        Block block = loc1.getWorld().getBlockAt(x, y, z);
    
                        blocks.add(block);
                    }
                }
            }
    
            Location loc = new Location(loc1.getWorld(), loc1.getX(), loc1.getY(), loc1.getZ());
            Block b = loc.getBlock();
            this.mainBlock = b;
            return blocks;
        }
    }
    


    PortalListener.java (open)

    Code:
    package me.greenadine.worldspawns.portals;
    
    import me.greenadine.worldspawns.Lang;
    import me.greenadine.worldspawns.Main;
    import me.greenadine.worldspawns.SettingsManager;
    import me.greenadine.worldspawns.SpawnFirework;
    import net.md_5.bungee.api.ChatColor;
    import org.bukkit.Location;
    import org.bukkit.Sound;
    import org.bukkit.World;
    import org.bukkit.block.Block;
    import org.bukkit.configuration.file.FileConfiguration;
    import org.bukkit.configuration.file.YamlConfiguration;
    import org.bukkit.entity.Player;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.player.PlayerMoveEvent;
    
    import java.io.File;
    import java.util.List;
    
    public class PortalListener implements Listener {
    
        /*
         * WARNING: W.I.P.
         */
    
        private Main main;
        SettingsManager settings;
        private Portal portal;
        private String prefix = ChatColor.translateAlternateColorCodes('&', Lang.PREFIX.toString());
    
        public PortalListener(Main main) {
            this.main = main;
            settings = SettingsManager.getInstance();
        }
    
        @EventHandler
        public void onPortalEnter(PlayerMoveEvent e) {
            Player p = e.getPlayer();
            Location ploc = p.getLocation();
            Block b = ploc.getBlock();
    
            for (String portalName : portal.getPortals()) {
                File portalFile = new File(main.getDataFolder(), File.separator + "portals" + File.separator + portalName);
                FileConfiguration portals = YamlConfiguration.loadConfiguration(portalFile);
                List<Block> blocks = portal.getPortalBlocks(portalName);
    
                for (Object block : blocks) {
                    if (block == b) {
                        if (portal.getType().equals("hub")) {
                            World w = main.getServer().getWorld(settings.getHub().getString("hub.world"));
                            double x2 = settings.getHub().getDouble("hub.x");
                            double y2 = settings.getHub().getDouble("hub.y");
                            double z2 = settings.getHub().getDouble("hub.z");
                            float yaw = settings.getHub().getInt("hub.yaw");
                            float pitch = settings.getHub().getInt("hub.pitch");
                            p.teleport(new Location(w, x2, y2, z2, yaw, pitch));
                            SpawnFirework firework = new SpawnFirework(p, main);
                            if (main.getConfig().getBoolean("firework.enable")) {
                                if (main.getConfig().getBoolean("firework.random")) {
                                    firework.spawnRandomFirework();
                                } else {
                                    firework.spawnFirework();
                                }
                            } else {
                                // Do nothing.
                            }
                            main.consoleMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                    Lang.COMMAND_HUB_TARGET_TELEPORTED.toString().replaceAll("%target%", p.getName())));
                            p.sendMessage(prefix
                                    + ChatColor.translateAlternateColorCodes('&', Lang.COMMAND_HUB_TELEPORTED.toString()));
                            if (enableSounds()) {
                                p.playSound(p.getLocation(), Sound.ENTITY_ENDERMEN_TELEPORT, 10F, 1.0F);
                            } else {
                                // Nothing.
                            }
                        }
                        if (portal.getType().equals("spawn")) {
                            String worldname = portals.getString("PORTALNAME.spawn");
                            World w = main.getServer().getWorld(worldname);
                            if (w == null) {
                                p.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.COMMAND_SPAWN_WORLD_NULL.toString().replaceAll("%worldname%", worldname)));
                                return;
                            }
                            String xs = settings.getSpawns().getString("spawns." + worldname + ".x");
                            String ys = settings.getSpawns().getString("spawns." + worldname + ".y");
                            String zs = settings.getSpawns().getString("spawns." + worldname + ".z");
                            String yaws = settings.getSpawns().getString("spawns." + worldname + ".yaw");
                            String pitchs = settings.getSpawns().getString("spawns." + worldname + ".pitch");
                            if (xs == null || ys == null || zs == null || yaws == null || pitchs == null) {
                                p.sendMessage(prefix + ChatColor.translateAlternateColorCodes('7',
                                        Lang.COMMAND_SPAWN_NULL.toString().replaceAll("%worldname%", worldname)));
                                return;
                            } else {
                                double x2 = settings.getSpawns().getDouble("spawns." + worldname + ".x");
                                double y2 = settings.getSpawns().getDouble("spawns." + worldname + ".y");
                                double z2 = settings.getSpawns().getDouble("spawns." + worldname + ".z");
                                float yaw = settings.getSpawns().getInt("spawns." + worldname + ".yaw");
                                float pitch = settings.getSpawns().getInt("spawns." + worldname + ".pitch");
                                p.teleport(new Location(w, x2, y2, z2, yaw, pitch));
                                SpawnFirework firework = new SpawnFirework(p, main);
                                if (main.getConfig().getBoolean("firework.enable")) {
                                    if (main.getConfig().getBoolean("firework.random")) {
                                        firework.spawnRandomFirework();
                                    } else {
                                        firework.spawnFirework();
                                    }
                                } else {
                                    // Do nothing.
                                }
                                main.consoleMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.COMMAND_SPAWN_TELEPORT_PLAYER.toString().replaceAll("%target%", p.getName())));
                                p.sendMessage(prefix + ChatColor.translateAlternateColorCodes('&',
                                        Lang.COMMAND_SPAWN_TELEPORT.toString()));
                                if (enableSounds()) {
                                    p.playSound(p.getLocation(), Sound.ENTITY_ENDERMEN_TELEPORT, 10F, 1.0F);
                                } else {
                                    // Nothing.
                                }
                            }
                        } else {
                            p.sendMessage(prefix + ChatColor.RED + "INVALID TYPE.");
                            return;
                        }
                    }
                }
            }
        }
    
        private boolean enableSounds() {
            if (main.getConfig().getBoolean("settings.enablesounds"))
                return true;
            if (!main.getConfig().getBoolean("settings.enableSounds")) {
                return false;
            } else {
                main.getConfig().set("settings.enableSounds", Boolean.valueOf(false));
                return false;
            }
        }
    
    }
    


    PortalSaver.java (open)

    Code:
    package me.greenadine.worldspawns.portals;
    
    import java.io.File;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.PrintWriter;
    import java.util.ArrayList;
    import java.util.Scanner;
    
    import org.bukkit.Server;
    import org.bukkit.block.Block;
    
    import me.greenadine.worldspawns.Main;
    
    public class PortalSaver {
      
        private static Main main;
        private static String file;
      
        public PortalSaver(Main main) {
            PortalSaver.main = main;
        }
    
        public static void write(ArrayList<Portal> portals) {
            try {
                PrintWriter out = new PrintWriter(new FileWriter(new File(main.getDataFolder(), "portals" + File.separator + file)));
                for(Portal portal : portals) {
                    file = portal.getName() + ".yml";
                    out.print(portal.getName() + ",");
                    Block b_main = portal.getMainBlock();
                    out.write(b_main.getWorld().getName() + ",");
                    out.write("MB,");
                    out.write(b_main.getX() + ",");
                    out.write(b_main.getY() + ",");
                    out.write(b_main.getZ() + ",");
                  
                    for (int i = 0; i < portal.getPortalBlocks(portal.getName()).size(); i++) {
                        Block block = portal.getPortalBlocks(portal.getName()).get(i);
                        out.write(i + ",");
                        out.write(block.getX() + ",");
                        out.write(block.getY() + ",");
                        out.write(block.getZ() + ",");
                    }
                }
              
                out.flush();
                out.close();
            } catch(Exception e) {
                main.log.severe("Could not save portals to file!");
                e.printStackTrace();
            }
        }
      
        @SuppressWarnings("resource")
        public static ArrayList<Portal> read(Server server) {
            ArrayList<Portal> portals = new ArrayList<Portal>();
          
            try {
                for(Portal portal : portals) {
                    Scanner in = new Scanner(new FileReader(new File(main.getDataFolder(), portal.getName() + ".yml")));
                    while(in.hasNextLine()) {
                        String[] line = in.nextLine().split(",");
                      
                        for (int i = 0; i < portal.getPortalBlocks(portal.getName()).size(); i++) {
                            int x = Integer.parseInt(line[i+7]);
                            int y = Integer.parseInt(line[i+8]);
                            int z = Integer.parseInt(line[i+9]);
                            @SuppressWarnings("unused")
                            Block b = server.getWorld(line[1]).getBlockAt(x, y, z);
                        }
                    }
                  
                }
            } catch(Exception e) {
                main.log.severe("Could not load portals from file!");
                e.printStackTrace();
            }
          
            return portals;
        }
    }
    


    Here are Pastebin versions if you prefer those:
    Portal.java: https://pastebin.com/8mHwjNy2
    PortalListener.java: https://pastebin.com/huEn0sgA
    PortalSaver.java: https://pastebin.com/3KKtYrHB

    All of these classes are not finished yet, mainly because I do not know how to continue with these. So they might make no sense to you on how I am trying do to this.

    I made these classes knowing barely what I was doing. I am very new to this (making a portals feature), but I wanted to try it, so that I can improve a bit.
     
  2. Offline

    leduyquang753

  3. @Kevinzuman22
    It seems that your portal class already contains a field with 'File' as type, as well as a method for it.

    For saving it, I would use code like this:

    ByteBuffer buffer = ByteBuffer.allocate(the amount of bytes you will need to use);
    buffer.putInt(portalX);
    buffer.putInt(portalY);
    buffer.putInt(portalZ);
    buffer.putLong(world.getUID().getMostSignificantBits());
    buffer.putLong(world.getUID().getLeastSignificantBits());
    buffer.putInt(destinationX);
    buffer.putInt(destinationY);
    buffer.putInt(destinationZ);
    //and so on until you have all relevant data...
    Files.write(file,buffer.array());
     
  4. I first though about using the config, but I wanted the portals to be saved in a different file. And even with FileConfiguration, I still didn't know a good way to save the data, as in in which order, what format etc.

    Ok, so with what you gave me, I now had the idea to save the first and last block of the portal (the 2 positions made with WorldEdit when creating the plugin), and save the data of those 2. With that I could just get those 2 of the saved file, and get all the blocks within that selection with this method:

    Code:
    private List<Block> getSelection(Location loc1, Location loc2) {
            int topBlockX = (loc1.getBlockX() < loc2.getBlockX() ? loc2.getBlockX() : loc1.getBlockX());
            int bottomBlockX = (loc1.getBlockX() > loc2.getBlockX() ? loc2.getBlockX() : loc1.getBlockX());
    
            int topBlockY = (loc1.getBlockY() < loc2.getBlockY() ? loc2.getBlockY() : loc1.getBlockY());
            int bottomBlockY = (loc1.getBlockY() > loc2.getBlockY() ? loc2.getBlockY() : loc1.getBlockY());
    
            int topBlockZ = (loc1.getBlockZ() < loc2.getBlockZ() ? loc2.getBlockZ() : loc1.getBlockZ());
            int bottomBlockZ = (loc1.getBlockZ() > loc2.getBlockZ() ? loc2.getBlockZ() : loc1.getBlockZ());
    
            for (int x = bottomBlockX; x <= topBlockX; x++) {
                for (int z = bottomBlockZ; z <= topBlockZ; z++) {
                    for (int y = bottomBlockY; y <= topBlockY; y++) {
                        Block block = loc1.getWorld().getBlockAt(x, y, z);
    
                        blocks.add(block);
                    }
                }
            }
            return blocks;
        }
    Would this work?

    And also, could you please explain in summary what ByteBuffer does, and how it saves, in what format etc.?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited: Sep 2, 2017
  5. Offline

    leduyquang753

    Come on, man. You don't have to save all of your stuff into just that config.yml. You can read the "Using multiple configuration files", create a file lile "portals.yml". It's OK! And speaking of a good way to save the data, you can make a portal config with sub-configs containing all of its attributes... Like this:
    Code:
    portal1:
      x: 526
      y: 62
      z: 48
    
    portal2:
      x: 475
      y: 72
      z: 962
     
Thread Status:
Not open for further replies.

Share This Page