So here's the first check-in for my take on how to use Groovy to load Block data in a very dynamic fashion, supporting both internal and external blocks and allowing us to do away with hard coded block IDs, the big terrain.png, assorted hard coded data in stuff like the plant growth simulator, and eventually create a way to make creatures, manage meta-block persistence, and all kinds of craziness.
A lot of puzzle pieces fell into place while working on this - what was just a quick take on how to switch the Groovy around so the applet could easily work again turned into "Oh, we can architect half the game around this stuff!"
Big disclaimer tho - it is still mostly a concept in example code, doesn't do anything other than print some stuff it loads out of tiny Groovy "properties" files (really classes with closures that look kind of like prop files), and actually doesn't even load the game as I'm not at that step yet. System.exit ftw! Look at the log output.
But the bigger the potential grew, the longer it was going to take before I can commit, so here's an early preview and a lot of fancy words - first the code: https://github.com/Cervator/Blockmania/ ... 0907e7cd2b
The idea is this: http://groovy.codehaus.org/ConfigSlurper is neat. It essentially allows you to very easily and intuitively write half prop files, half Groovy scripts, that can be compiled into readable classes. Behind the scenes closure magic is involved and actually compiles stuff into a rather ugly assortment of inner classes and junk like that, but the goal is to only use the magic on game startup - that way performance hits aren't an issue like some of the earlier attempts to get Groovier blocks.
By going back to a vastly more modular approach (similar to back when each block was its own Java class), yet gaining the flexibility and minimal code of Groovy, we end up loading blocks sort of like from "pretty" XML in a way. A sub-typing system for Blocks is also introduced, and the terrain.png is being sliced into tiny little pieces. Eventually the goal is to replace the Default.groovy and completely avoid hard coded block IDs and atlas positions in a static texture file. Instead we get a per-world Block Manifest file that simply ties IDs with block definitions (which are stored by Java in the end, not Groovy), along with a world-specific terrain.png
That does away with the hassle of manual block IDs, which are in the way for easy user contributed add-on blocks. I included a new add-on block that'll be loaded by this system when it is complete - and consists of a 151 byte Groovy script + a 930 byte PNG. That's all the user adds. Now, outside of "free build infinite blocks" mode the game will need a hook to put blocks to use, which is where the snazzy little ability of Groovy's come in - you can include code in these "Groovy Prop Files" to register the block as a certain type the game can recognize, like a plant. The growth simulator (https://github.com/begla/Blockmania/blo ... lator.java) for instance currently hard codes stuff like sunlight needed, biome details, etc, but you could change that to load block data and introduce a mushroom that overrides the default "needs sunlight" for its specific case.
By leaving defaults at each level we only indicate exactly what's needed for each block, with additional/changed defaults for subtypes - for instance liquids have a unique "viscosity" value, and plants have a "growth" section. Oh yeah, that's where we start moving from plain block definitions to game logic. Take a look at grass, for instance: https://github.com/Cervator/Blockmania/ ... 2b#diff-12
On there the quick new way of indicating faces is sketched out (the BlockManifestor would read the given props and overwrite defaults in the dynamic atlas collection), with the references resolving to individual image files under com.github.begla.blockmania.data.textures.blocks (might need better pathing / path indication). Then under the plant section (which could also include data on what a plant can be used for) there's a growth section detailing how and when grass will grow and even evolve (gotta catch em all!). In this case "grass" only has one growth stage, which is when it goes from dirt (growth simulator picks a spot and decides it is suitable for grass) to size 1 grass. However, it has an "evolve" section included, which will allow the growth simulator to again hit that block and create something else - in this case tall grass, yellow flowers, or red flowers, at differing weights. If the probability for that triggered (here 95% less likely than the initial growth from dirt to grass) the block would be transformed (or have a billboard placed on top of it) indicating the new growth.
Trees could be made to slowly grow the same way, I've put an example OakTree in there that admittedly currently would grow from nothing to its max stage of 50 in one go - since we (and Minecraft) spawn trees straight to adult size. But nothing prevents us from slowly having the tree grow in stages, using the growth simulator. Trees also tend to have leaves, so there's a reference to an "OakLeaf" block in there, which is both an attachment to a tree and its own independent block. Which is where the system starts getting hazy, as the potential bleeds into other areas. An OakTree is indeed a Tree, but an OakLeaf is not (yet they're in the same package). But you could start imagining OakTree instances with leaves and stuff attached stored in a BlockGrid actually containing its state in the world as it grows - and gets chopped down by having a key block removed, physics-enabling the rest of the tree in rapid succession rather than leave it standing...
Then you're half-way into defining blueprints for other things like lairs, you could use a similar system for creatures, and I'm about half-insane at this point (and it is a tad late too!). Start persisting the relevant BlockGrids and you can start saving data for Portals and such. The split can be maintained between binary default/native data (what comes with the game) and plain-text add-on user contributed content. Not quite sure yet how to best save everything, but there is also potential for keeping track of Serialization version levels with this stuff (for auto-upgrading worlds with newer versions of blocks / meta-blocks like trees or portals, or what not...)
I'm going to go back to working on a way to then splice together the terrain.png from individual blocks activated for the current world and actually loading the right data into BlockManager so the world will actually work again backed by the new system. I've only written some initial details on a few blocks, but hope to get Stuart involved in this to familiarize him with coding, in particular since the related content there down the road will be relevant to his interests (creature & lair design, etc). I'm also going to try pushing it to the Nanoware organization and see how two people collaborating on the same branch works. So while I make the system work more he can start putting together more details on the blocks and the plants etc that the blocks represent.
I know there's a lot more potential here, and it'll take a while to work through it and flesh out where it is going. But I wanted to get it out there early for review, architecture thoughts, and collaboration. Even if it probably sounds crazy right now
Oh, and on an even more technical note: Making peace between non-compiled Groovy scripts and fully compiled Groovy Classes to do the split between external/internal was fun! At first I didn't realize Groovy was just making sneaky Java classes look like text-based prop files, so I thought compiling them might screw them up. But trying to include plain *.groovy files as resources into the jar file (and be able to load them as classes/resources via the ClassLoader) would not allow any of the Groovy we do want compiled to also compile, with standard settings (could probably hack it with custom Ant etc, but that then sort of negates the ease of Groovy in the first place...). But now all that works! Internal classes can Groovy it up all day, while external scripts would be executed by the GroovyManager if need be to register their blocks etc to appropriate Java managers.
A lot of puzzle pieces fell into place while working on this - what was just a quick take on how to switch the Groovy around so the applet could easily work again turned into "Oh, we can architect half the game around this stuff!"
Big disclaimer tho - it is still mostly a concept in example code, doesn't do anything other than print some stuff it loads out of tiny Groovy "properties" files (really classes with closures that look kind of like prop files), and actually doesn't even load the game as I'm not at that step yet. System.exit ftw! Look at the log output.
But the bigger the potential grew, the longer it was going to take before I can commit, so here's an early preview and a lot of fancy words - first the code: https://github.com/Cervator/Blockmania/ ... 0907e7cd2b
The idea is this: http://groovy.codehaus.org/ConfigSlurper is neat. It essentially allows you to very easily and intuitively write half prop files, half Groovy scripts, that can be compiled into readable classes. Behind the scenes closure magic is involved and actually compiles stuff into a rather ugly assortment of inner classes and junk like that, but the goal is to only use the magic on game startup - that way performance hits aren't an issue like some of the earlier attempts to get Groovier blocks.
By going back to a vastly more modular approach (similar to back when each block was its own Java class), yet gaining the flexibility and minimal code of Groovy, we end up loading blocks sort of like from "pretty" XML in a way. A sub-typing system for Blocks is also introduced, and the terrain.png is being sliced into tiny little pieces. Eventually the goal is to replace the Default.groovy and completely avoid hard coded block IDs and atlas positions in a static texture file. Instead we get a per-world Block Manifest file that simply ties IDs with block definitions (which are stored by Java in the end, not Groovy), along with a world-specific terrain.png
That does away with the hassle of manual block IDs, which are in the way for easy user contributed add-on blocks. I included a new add-on block that'll be loaded by this system when it is complete - and consists of a 151 byte Groovy script + a 930 byte PNG. That's all the user adds. Now, outside of "free build infinite blocks" mode the game will need a hook to put blocks to use, which is where the snazzy little ability of Groovy's come in - you can include code in these "Groovy Prop Files" to register the block as a certain type the game can recognize, like a plant. The growth simulator (https://github.com/begla/Blockmania/blo ... lator.java) for instance currently hard codes stuff like sunlight needed, biome details, etc, but you could change that to load block data and introduce a mushroom that overrides the default "needs sunlight" for its specific case.
By leaving defaults at each level we only indicate exactly what's needed for each block, with additional/changed defaults for subtypes - for instance liquids have a unique "viscosity" value, and plants have a "growth" section. Oh yeah, that's where we start moving from plain block definitions to game logic. Take a look at grass, for instance: https://github.com/Cervator/Blockmania/ ... 2b#diff-12
On there the quick new way of indicating faces is sketched out (the BlockManifestor would read the given props and overwrite defaults in the dynamic atlas collection), with the references resolving to individual image files under com.github.begla.blockmania.data.textures.blocks (might need better pathing / path indication). Then under the plant section (which could also include data on what a plant can be used for) there's a growth section detailing how and when grass will grow and even evolve (gotta catch em all!). In this case "grass" only has one growth stage, which is when it goes from dirt (growth simulator picks a spot and decides it is suitable for grass) to size 1 grass. However, it has an "evolve" section included, which will allow the growth simulator to again hit that block and create something else - in this case tall grass, yellow flowers, or red flowers, at differing weights. If the probability for that triggered (here 95% less likely than the initial growth from dirt to grass) the block would be transformed (or have a billboard placed on top of it) indicating the new growth.
Trees could be made to slowly grow the same way, I've put an example OakTree in there that admittedly currently would grow from nothing to its max stage of 50 in one go - since we (and Minecraft) spawn trees straight to adult size. But nothing prevents us from slowly having the tree grow in stages, using the growth simulator. Trees also tend to have leaves, so there's a reference to an "OakLeaf" block in there, which is both an attachment to a tree and its own independent block. Which is where the system starts getting hazy, as the potential bleeds into other areas. An OakTree is indeed a Tree, but an OakLeaf is not (yet they're in the same package). But you could start imagining OakTree instances with leaves and stuff attached stored in a BlockGrid actually containing its state in the world as it grows - and gets chopped down by having a key block removed, physics-enabling the rest of the tree in rapid succession rather than leave it standing...
Then you're half-way into defining blueprints for other things like lairs, you could use a similar system for creatures, and I'm about half-insane at this point (and it is a tad late too!). Start persisting the relevant BlockGrids and you can start saving data for Portals and such. The split can be maintained between binary default/native data (what comes with the game) and plain-text add-on user contributed content. Not quite sure yet how to best save everything, but there is also potential for keeping track of Serialization version levels with this stuff (for auto-upgrading worlds with newer versions of blocks / meta-blocks like trees or portals, or what not...)
I'm going to go back to working on a way to then splice together the terrain.png from individual blocks activated for the current world and actually loading the right data into BlockManager so the world will actually work again backed by the new system. I've only written some initial details on a few blocks, but hope to get Stuart involved in this to familiarize him with coding, in particular since the related content there down the road will be relevant to his interests (creature & lair design, etc). I'm also going to try pushing it to the Nanoware organization and see how two people collaborating on the same branch works. So while I make the system work more he can start putting together more details on the blocks and the plants etc that the blocks represent.
I know there's a lot more potential here, and it'll take a while to work through it and flesh out where it is going. But I wanted to get it out there early for review, architecture thoughts, and collaboration. Even if it probably sounds crazy right now
Oh, and on an even more technical note: Making peace between non-compiled Groovy scripts and fully compiled Groovy Classes to do the split between external/internal was fun! At first I didn't realize Groovy was just making sneaky Java classes look like text-based prop files, so I thought compiling them might screw them up. But trying to include plain *.groovy files as resources into the jar file (and be able to load them as classes/resources via the ClassLoader) would not allow any of the Groovy we do want compiled to also compile, with standard settings (could probably hack it with custom Ant etc, but that then sort of negates the ease of Groovy in the first place...). But now all that works! Internal classes can Groovy it up all day, while external scripts would be executed by the GroovyManager if need be to register their blocks etc to appropriate Java managers.