Bukkit Persistance reimplemented - no code changes required!

Discussion in 'Plugin Development' started by LennardF1989, Jul 6, 2011.

Thread Status:
Not open for further replies.
  1. Offline

    LennardF1989

    The problem
    The current implementend Avaje Ebean Persistance Layer has quite some shortcomings:
    • It doesn't work (properly) in threads.
    • You can't issue a /reload without breaking your plugin
    • One configuration file (bukkit.yml) tells all plugins which database to use, even the ones that were made with a SQLite database in mind.
    • I've also seen people switching away from SQLite because persisting lists with @OneToMany and alike annotations throws an exception when loading the database.
    Especially the last two are troublesome, because SQLite is - as the name implies - much lighter and faster than MySQL. Besides that, MySQL is overkill for just two tables which don't need any exposure to a website or anything.

    The solution
    Unlike Bukkit's implementation of Ebean inside JavaPlugin, my implementation is done in a separate class. This allows you to load multiple different databases at the same time, without relying on the bukkit.yml file, but rather your preference.

    Also, my class comes with 2 events: beforeDropDatabase and afterCreateDatabase. Whats the use of this, you might ask? Well, it allows you to update databases to a new version manually! Say v1.0 of your plugin had 2 persistent classes, but v2.0 has 3 persistent classes and some changed fields, in that case you want to upgrade the users old database to the new database, but without losing any data! You can could combine this upgrade-mechanism in your plugin or even better: create a one-time-only plugin to upgrade the database.

    A feature quite important to me was being able to work-around the relational problem in SQLite. You can actually persists lists with this implementation, without Ebean throwing nasty "foreign key" exceptions at you!

    A nice feature besides that, you can disable all logging from Ebean during the initialization-period, which usually flood your console when launching the server (annoying). Fear not, you will still get a lovely exception if they occur.

    Downloads
    Source code
    Jar

    You can find Maven instructions below. As for the source-code, I did my best to (over-)comment it, so everyone can understand whats going on in there!

    How do you use it?
    You basically do the following:
    1. Create an instance of MyDatabase (or extension) and pass it the plugin which is instancing it (usually "this")
    2. Put all classes that should be persisted in the "getDatabaseClasses"-method
    3. Call the "initializeDatabase" method on your new instance. If you set logging to false, all logging (including the "ebean.properties file not found") will be disabled. If you set rebuild to true, all tables will be dropped and recreated (and the events beforeDropDatabase and afterCreateDatabase will be fired).
    Take, for example, the following implementation (you can find the complete example here):
    Code:java
    1. private void initializeDatabase() {
    2. Configuration config = getConfiguration();
    3.  
    4. database = new MyDatabase(this) {
    5. protected java.util.List<Class<?>> getDatabaseClasses() {
    6. List<Class<?>> list = new ArrayList<Class<?>>();
    7. list.add(User.class);
    8. list.add(Comment.class);
    9.  
    10. return list;
    11. };
    12. };
    13.  
    14. database.initializeDatabase(
    15. config.getString("database.driver"),
    16. config.getString("database.url"),
    17. config.getString("database.username"),
    18. config.getString("database.password"),
    19. config.getString("database.isolation"),
    20. config.getBoolean("database.logging", false),
    21. config.getBoolean("database.rebuild", true)
    22. );
    23.  
    24. config.setProperty("database.rebuild", false);
    25. config.save();
    26. }
    In this example I am using my own config.yml for database settings and also use it to determine whether or not to rebuild the database. Ofcourse, this is only one of the many possibilities, implementation comes down to the needs of your plugin:
    • If you don't want your users to change the database to something else, hardcode the url, driver, username, etcetera rather than picking it from config.yml.
    • If you need more than one database, you want to create seperate "getDatabase" methods.
    • If you want to use the events, it's recommended to create a seperate class extending MyDatabase rather than short-instancing it (like on line 4 above).
    Does that sound easy? Good! My aim was to make it as painless as setting "database: true" in your plugin.yml!

    The "no code changes required"-part
    If your plugin was already using Bukkit's built-in persistance implementation, you will not have to rewrite any of that logic with this implementation!

    Instead of using "database: true" in your plugin.yml (be sure to remove that line if you're using this), you will have to write about 10 lines of code to initialize the database the way you want it (like how it is done above), nothing more, nothing less.

    Also overwrite the original "getDatabase" method to prevent you from using the wrong database:
    Code:java
    1. @Override
    2. public EbeanServer getDatabase() {
    3. return database.getDatabase();
    4. }
    Be sure to backup your database! You could accidentally rebuild the database and all records will be irreversible lost!

    Maven Instructions
    I love Maven, I hope you too, because it will save you quite some work!

    In normal occasions without using Maven, you would have to:
    • Compile my code inside you're code
    • Export your plugin to a JAR and drag and drop my code into your JAR
    • Make people to use a Libs-directory in order to use your plugin*
    * Which I actually recommend all server owners should do!

    But, if you are using Maven, you can use a "shading"-plugin, to automatically do this for you! If you're not using Maven for your plugins yet, I can highly recommend it to give it a try!
    I want to use Maven! Tell me how! (open)
    In your pom.xml, to repositories add (be sure to remove the URL tags with brackets):
    Code:xml
    1. <repository>
    2. <id>lennardf1989-repo</id>
    3. <url>[URL]http://github.lennardf1989.com/repository/[/URL]</url>
    4. </repository>
    To your dependencies:
    Code:xml
    1. <dependency>
    2. <groupId>com.lennardf1989</groupId>
    3. <artifactId>bukkitex</artifactId>
    4. <version>0.0.1-SNAPSHOT</version>
    5. </dependency>
    Last but not least, add the following Maven-plugin, it will copy the class file into your JAR automatically:
    Code:xml
    1. <plugin>
    2. <groupId>org.apache.maven.plugins</groupId>
    3. <artifactId>maven-shade-plugin</artifactId>
    4. <version>1.4</version>
    5. <executions>
    6. <execution>
    7. <phase>package</phase>
    8. <goals>
    9. <goal>shade</goal>
    10. </goals>
    11. <configuration>
    12. <artifactSet>
    13. <includes>
    14. <include>com.lennardf1989:bukkitex</include>
    15. </includes>
    16. </artifactSet>
    17. </configuration>
    18. </execution>
    19. </executions>
    20. </plugin>
    Final words
    This code has gone a long way and has been changed and moved around a lot in the last 2 weeks (I didn't work on it daily, but it took some thinking on how to get it to work as it does now). I'm happy with the result and will be using it a lot in the plugins I am going to develop, I hope it will serve the community as much as it will me.

    I also almost forgot to mention the following: There is no license on this, at all! Do note though that I would highly appreciate it if you share changes you've made to the code with me. The best way to do this is forking the project over at GitHub and doing a pull request.

    If you feel obliged to credit me, please use my full name (Lennard Fonteijn) and have a link to my website (http://www.lennardf1989.com). Either way, I would appreciate it if you leave a note (reply, PM, whatever) whether or not you are using this in your plugins.

    In case your interested in how this piece of code actually fixes the SQLite problem, read on! Otherwise stop here and start coding already!
    I want to read more! (open)
    Background-story
    After testing multiple database-solutions with no real success - in the sense: way too much hassle for users of my plugins to get them to work - I started digging through the Ebean source-code. After trying numerous of hacks, there simply was no easy solution to fixing the relational problem with SQLite.

    What is actually going wrong when using SQLite, you might wonder? It's rather stupid actually, but it has to do with SQLite's ALTER TABLE statement being incomplete. Due to the way Ebean works, this poses a problem. This is what happens in Ebean when you run the "Create tables"-script:
    1. Create all tables with their primary keys and other stuff
    2. Create sequences
    3. Alter tables to add foreign keys
    After what I told you, it comes with no surprise the process fails on step 3. If you look at this page, it clearly states:
    Unfortunately, you cannot modify Ebean to support this "exception", it's just the way it works. What you can do, though, is work around it.

    Workaround
    Say we have two classes, User and Comment, bound together with a Foreign Key. When you register these with Ebean, the following code will be generated:
    Everything will go fine, until it hits the alter table line. So, how can we solve this problem?

    Luckily you can add constraints directly in the create table statement. On top of that, adding the constrain to the create table statement is a mere copy/paste action! This is how it should look:
    This is where my piece of code will come in!
    Changelog (open)
    v1.2
    Fixed the "java.io.IOException: Class not found"-exception thrown by Ebean on /reload

    v1.1.1
    Fixed indentation of empty lines

    v1.1
    Added a few sanity checks and cleaned up the comments a little

    v1.0
    Initial commit
    Take care,
    Lennard
     
  2. Offline

    Jaker232

    That is very well explained and I could not understand a few words, but.. wow. That was quite some reading.

    I find this interesting, thanks for your research, if I ever have a database support, I'm going to SQLite.
     
  3. Offline

    LennardF1989

    I updated the post to get straight to the point, preventing a long read (unless you want to). I also added a small section describing WHY you want to have a database in your plugin. Basically any plugin which has to remember stuff between server restarts, can use a database!

    EDIT: And didn't I originally posted this in plugin resources?

    Updated Github and Maven repository to include a few sanity checks which should prevent you to lose data if something goes wrong - it will cancel out before actually dropping any data. I wasn't sure how to solve this until a few hours ago.

    I think I'll do a YouTube video on how to write a simple plugin using Maven and Persistence with MyDatabase, because that seems to be quite a popular thing to do. Have to go with the flow, of course.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  4. Offline

    Jaker232

    This will help with greifing check tools and storing data on a database rather than flat file.
     
  5. Offline

    LennardF1989

    Bump, did a mayor overhaul on this first post. As for "no code changes required", you will have to add about 10 lines to load your database (you can do much less), but you do not have to change your existing persistent classes or anything.

    What? I have to do something to get attention, don't I?
     
  6. Offline

    triggerhapp

    This appears to have done the trick, although I had to go as far as adding your own package into my source code. Using a lib/ directory and adding to classpath had no effect.

    Will keep you updated incase I see anything odd in this. Looking good though
     
  7. Offline

    LennardF1989

    I'm glad it worked out. And of course, I will maintain this in any way I can! If you have any issues, please report them back and I'll look into it.

    Classpath should work though, I will retest that. In the meantime, I can recommend the Maven method, it's easy and painless and you will always have the latest version of the code.

    Just tested, classpath works fine for me. I have a libs directory in which I placed bukkitex-0.0.1-SNAPSHOT.jar, then in my .bat file I have:
    Note that you HAVE to use this way of launching Bukkit. If you use -jar Bukkit.jar, classpath is ignored. I'll add that warning to my other topic. If you were already doing this, any specific error or just a ClassNotFoundException?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  8. Offline

    triggerhapp

    I'm avoiding maven like a plague ;)

    Anyways, came back after some more testing, and have realised a snag. On reload when I've uploaded a new version of the jar file... I hit my old nemesis
    Code:
    21:10:05 [SEVERE] Error creating subclass for [com.Trigg.RespawnStore]
    java.io.IOException: Class not found
        at com.avaje.ebean.enhance.asm.ClassReader.readClass(ClassReader.java:382)
        at com.avaje.ebean.enhance.asm.ClassReader.<init>(ClassReader.java:359)
        at com.avaje.ebeaninternal.server.subclass.SubClassFactory.subclassBytes(SubClassFactory.java:115)
        at com.avaje.ebeaninternal.server.subclass.SubClassFactory.create(SubClassFactory.java:83)
        at com.avaje.ebeaninternal.server.subclass.SubClassManager.createClass(SubClassManager.java:113)
        at com.avaje.ebeaninternal.server.subclass.SubClassManager.resolve(SubClassManager.java:100)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.checkSubclass(BeanDescriptorManager.java:1571)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.setEntityBeanClass(BeanDescriptorManager.java:1518)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.createByteCode(BeanDescriptorManager.java:1204)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.readDeployAssociations(BeanDescriptorManager.java:1124)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.readEntityDeploymentAssociations(BeanDescriptorManager.java:630)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.deploy(BeanDescriptorManager.java:277)
        at com.avaje.ebeaninternal.server.core.InternalConfiguration.<init>(InternalConfiguration.java:150)
        at com.avaje.ebeaninternal.server.core.DefaultServerFactory.createServer(DefaultServerFactory.java:209)
        at com.avaje.ebeaninternal.server.core.DefaultServerFactory.createServer(DefaultServerFactory.java:64)
        at com.avaje.ebean.EbeanServerFactory.create(EbeanServerFactory.java:78)
        at com.lennardf1989.bukkitex.MyDatabase.loadDatabase(MyDatabase.java:142)
        at com.lennardf1989.bukkitex.MyDatabase.initializeDatabase(MyDatabase.java:75)
        at com.Trigg.TriggClaim.setupDatabase(TriggClaim.java:134)
        at com.Trigg.TriggClaim.onEnable(TriggClaim.java:115)
        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:126)
        at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:857)
        at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:264)
        at org.bukkit.craftbukkit.CraftServer.loadPlugin(CraftServer.java:151)
        at org.bukkit.craftbukkit.CraftServer.enablePlugins(CraftServer.java:136)
        at org.bukkit.craftbukkit.CraftServer.reload(CraftServer.java:358)
        at org.bukkit.command.SimpleCommandMap$ReloadCommand.execute(SimpleCommandMap.java:281)
        at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:129)
        at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:290)
        at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:480)
        at net.minecraft.server.MinecraftServer.h(MinecraftServer.java:465)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:361)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:422)
    21:10:05 [SEVERE] Error in deployment
    javax.persistence.PersistenceException: Error creating subclass for [com.Trigg.RespawnStore]
        at com.avaje.ebeaninternal.server.subclass.SubClassManager.createClass(SubClassManager.java:117)
        at com.avaje.ebeaninternal.server.subclass.SubClassManager.resolve(SubClassManager.java:100)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.checkSubclass(BeanDescriptorManager.java:1571)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.setEntityBeanClass(BeanDescriptorManager.java:1518)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.createByteCode(BeanDescriptorManager.java:1204)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.readDeployAssociations(BeanDescriptorManager.java:1124)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.readEntityDeploymentAssociations(BeanDescriptorManager.java:630)
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager.deploy(BeanDescriptorManager.java:277)
        at com.avaje.ebeaninternal.server.core.InternalConfiguration.<init>(InternalConfiguration.java:150)
        at com.avaje.ebeaninternal.server.core.DefaultServerFactory.createServer(DefaultServerFactory.java:209)
        at com.avaje.ebeaninternal.server.core.DefaultServerFactory.createServer(DefaultServerFactory.java:64)
        at com.avaje.ebean.EbeanServerFactory.create(EbeanServerFactory.java:78)
        at com.lennardf1989.bukkitex.MyDatabase.loadDatabase(MyDatabase.java:142)
        at com.lennardf1989.bukkitex.MyDatabase.initializeDatabase(MyDatabase.java:75)
        at com.Trigg.TriggClaim.setupDatabase(TriggClaim.java:134)
        at com.Trigg.TriggClaim.onEnable(TriggClaim.java:115)
        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:126)
        at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:857)
        at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:264)
        at org.bukkit.craftbukkit.CraftServer.loadPlugin(CraftServer.java:151)
        at org.bukkit.craftbukkit.CraftServer.enablePlugins(CraftServer.java:136)
        at org.bukkit.craftbukkit.CraftServer.reload(CraftServer.java:358)
        at org.bukkit.command.SimpleCommandMap$ReloadCommand.execute(SimpleCommandMap.java:281)
        at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:129)
        at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:290)
        at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:480)
        at net.minecraft.server.MinecraftServer.h(MinecraftServer.java:465)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:361)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:422)
    Caused by: java.io.IOException: Class not found
        at com.avaje.ebean.enhance.asm.ClassReader.readClass(ClassReader.java:382)
        at com.avaje.ebean.enhance.asm.ClassReader.<init>(ClassReader.java:359)
        at com.avaje.ebeaninternal.server.subclass.SubClassFactory.subclassBytes(SubClassFactory.java:115)
        at com.avaje.ebeaninternal.server.subclass.SubClassFactory.create(SubClassFactory.java:83)
        at com.avaje.ebeaninternal.server.subclass.SubClassManager.createClass(SubClassManager.java:113)
        ... 28 more
    
    It appears to be an issue in eBean server, maybe?
    Once again, if I actually read the file, or just stop/start the server the class is found, only on reload is it not seen. My guess continues to be that ebean probably caches the old file handle, and this file handle is no longer valid (Atleast under linux)
     
  9. Offline

    LennardF1989

    I asked the guys at Ebean about that, they do not have any internal cache. When updating the JAR, do you actually update your persistent classes, or just some logic classes?

    Either way, I think I have to add a method which you can us in onDisable to "destroy" the database prior to reloading it.
     
  10. Offline

    triggerhapp

    Well to get that error above, I added one extra log to the code, then compiled and uploaded.

    I'll give onDisable a shot, why not. (Infact, shouldnt reloading the plugin do the same thing anyway?)
     
  11. Offline

    LennardF1989

    Ok, so the JAR just has to change to trigger it. I'll try it out, give me a moment. I'm bored anyway :p
     
  12. Offline

    triggerhapp

    Thanks. I've pretty much gotten used to save-all/stop/run process, which annoys my users no end.

    Well, having found no de-init, destroy etc commands that seem to do the job, and setting database=null; in onDisable does as much as expected, I'm lost on something to do on this end ;P

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  13. Offline

    LennardF1989

    Ok, I know where the problem is. I'm going to prepare a server with only one plugin so I can easily test.Will report back soon.

    Also, apparently there is something in Bukkit which allows you to update plugins by throwing them in a "update" directory, are you using that method? Or are you releasing the JAR file manually and replacing it?
     
  14. Offline

    triggerhapp

    Hah, theres an update directory? News to me.
    I've got none but I'm going to give a shot making it upload into there

    No apparent effect, It doesnt use anything placed into the update directory, for me

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  15. Offline

    LennardF1989

    Update folder has to be inside the plugin directory. But you will still get the error, but it seems an easier way of updating your plugins runtime ;)

    I'm debugging the ClassLoader as we speak, on reload, it craps out at some point.

    Ok, I digged through all the code and guess what? It's not a Bukkit bug nor a Ebean bug! That's true, it's a bug inside of Java, this one to be exact: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6207022

    It basically comes down to the fact that we are the bug: Java is not designed to replace JAR's at runtime! Inside of Ebean, getResourceAsStream is called. Everything goes fine until it hits the "openStream" method. There seems to be a workaround, so I'll see if I can implement it.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  16. Offline

    Acrobot

    @LennardF1989
    Will you try to merge your implementation with Bukkit?
     
  17. Offline

    LennardF1989

    You mean like a pull request? If so, eventually. Once it is merged inside Bukkit, I lose control over it and maintaining it becomes quite hard. Therefore It should first prove to work "perfectly" before I can put it up as a pull request to replace to current implementation.

    I can confirm I fixed the /reload bug, for everyones information.

    @triggerhapp Pushed v1.2 to BukkitEx on GitHub and updated Maven Repository. This should fix the infamous error ;)

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  18. Offline

    xpansive

    I'm a bit of a noob at this, after all the initialization code, how exactly would I save/load something from a database?
     
  19. Offline

    LennardF1989

    @xpansive You will have to prepare your classes you want to save/load like the example implementation (User/Comment), then you can do something like:
    Code:java
    1. List<User> listUsers = getDatabase().find(User.class).findList(); //Does a SELECT * FROM Users
    or
    Code:java
    1. User aUser= getDatabase().find(User.class, 1); //Does a SELECT * FROM Users WHERE ID=1
    To save something, you would do something like:
    Code:java
    1. User aUser = new User("A User");
    2. aUser.getComments().add(new Comment("Hello World"));
    3. getDatabase().persist(aUser); //This will save both the the user and his comment to the database
    I hope that's of use. If you have a specific case I can provide better examples.
     
  20. Offline

    triggerhapp

    Absolutely beautiful. That's every single problem I hated about bukkit persistence fixed.

    Now to get on with my actual programming :)

    Thanks so very much, have this thread bookmarked now.
     
  21. Offline

    LennardF1989

    Just a note, be sure to have this piece of code in your JavaPlugin:
    Code:java
    1. @Override
    2. public EbeanServer getDatabase() {
    3. return database.getDatabase();
    4. }
    Otherwise you'll end up trying to access Bukkit's version of the database, which is null, obviously. I'll add this to the first post to raise the awareness.
     
  22. Offline

    Acrobot

    @LennardF1989
    Does it fix the bug with server in path with special characters, like C:/ąśServer?
     
  23. Offline

    LennardF1989

    @Acrobot
    Haven't tested that. Did Ebean threw errors if you did that? Or Bukkit in general?

    EDIT: I see what you mean. I'll see if I can work-around it. I'll report back!
     
  24. Offline

    triggerhapp

    I have to say I'm very impressed with the response time to questions and bugs. Just stopping in again to thank you, I've already done a LOT more coding than I would normally manage, without a single server reset makes it all much easier to test :D
     
  25. Offline

    LennardF1989

    @triggerhapp Glad to be of service.

    @Acrobot I fixed the issue and it's not (necessarily) related to Bukkit. Run you're server with "-Dfile.encoding=utf-8" in the command-line and it should be fixed. Also, if you pass "nojline" as an argument to Bukkit, you can type UTF-8 characters into the console without making it crash.

    This is my command-line:
     
  26. Offline

    xpansive

    The EbeanServer.persist method doesn't seem to exist... did you mean save?
     
  27. Offline

    LennardF1989

    @xpansive Yes, it should take the same parameters as my example :)
     
  28. Offline

    xpansive

  29. Offline

    Acrobot

    @LennardF1989
    Yeah, but is it fixed in your version? Or do user still have to add that to launch line? Because people in my topic often have that problem, and I just tell them to move it to somewhere w/o special chars.
    And really, really thanks for help.
    I'll use your implementation for sure, and you'll be credited in the topic :)
    Again, thanks :)
     
  30. Offline

    LennardF1989

    @xpansive Do you have some code you can (privately) share with me? PM me.

    @Acrobot It's more of a bug in Java than in anything else. To fix it, you have to pass that argument to Java (which is a lot easier than moving around your server-directory). As far as I can see, that's the most reliable solution. I'll see if I can "workaround" it like I did with the /reload issue, but can't promise anything.
     
Thread Status:
Not open for further replies.

Share This Page