Mythruna Scripted Objects

In the next release, I’ve done some extensive work on the ability to script custom objects. Since most of us monkeys are probably also coders and since scripting in JME comes up from time to time, I thought there might be some interested here.



There is a more detailed thread on the Mythruna forum: Scripted Objects: WIP status...



But I will try to summarize some of the salient parts here.



This video shows an example of a test mod that I’ve made that implements a simple firewood/campfire system complete with match and being able to put out the fire with a bucket of sand or water:

http://www.youtube.com/watch?v=0R82bKkHtjY



This isn't how real fire will work in Mythruna. Real fire will be a thing all its own that burns any flammable material, jumps from object to object, burns down forests, etc.. This is just an example of how a modder can make their own custom objects and have them interact.

Here is the script mod I used to make those objects:
[java]

/**
* Testing and demonstrating how action-able
* scripted objects can be added to the game.
*/

import mythruna.MaterialType;
import mythruna.es.*;
import mythruna.item.*;

loadBlueprint( "firewood.bp" );

createModel( "logs", 0, "firewood.bp" )
createModel( "campfire", 0, "campfire.bp" )
createModel( "match", 2, "match.bp", Scale.Item );
createModel( "bucket", 0, "bucket.bp", Scale.Item );
createModel( "bucket-sand", 0, "bucket-sand.bp", Scale.Item );
createModel( "bucket-water", 0, "bucket-water.bp", Scale.Item );


// Now create the object templates (classes) for some items

def firewood = objectTemplate( "Firewood" ) {

setModel( "logs" );
addParents( "BaseItem" );

action( ":light" ) { self, tool ->
def obj = entityObject(self);
if( obj.locals.getInt("lit") == 0 ) {
obj.setModel("campfire");
obj.locals.setInt("lit", 1);
echo( "A fire crackles to life." );
} else {
echo( "These logs are already lit." );
}
}

action( ":douse" ) { self, tool ->
def obj = entityObject(self);
if( obj.locals.getInt("lit") == 0 ) {
echo( "The fire is not lit." );
} else {
obj.setModel("logs");
obj.locals.setInt("lit", 0);
echo( "The fire goes out." );
}
}
}

def match = objectTemplate( "Match" ) {

setModel( "match" );
addParents( "BaseTool" );

defaultAction( "Light" ) { self, hit ->
if( hit == null || hit.object == null )
return;

entityObject(hit.object).execute( ":light", self );

}.onlyIf() { self, hit ->

// Right now only if it has a ":light" action
return entityObject(hit.object).hasAction( ":light" );
}
}

def bucket = objectTemplate( "Bucket" ) {

setModel( "bucket" );
addParents( "BaseTool" );

action( ":empty" ) { self ->
def obj = entityObject(self);

// Empty the bucket
obj.locals.setInt("filled", 0);
obj.setModel("bucket");
}

defaultAction( "Empty" ) { self, hit ->

entityObject(self).execute( ":empty" );
echo "The bucket is now empty.";

}.onlyIf() { self, hit ->

if( hit == null || hit.object != null )
return false;

def material = hit.material;
if( material != MaterialType.WATER && material != MaterialType.SAND )
return false;

// Else make sure we are trying to dump the material
// back into its own type
def obj = entityObject(self);
int holds = obj.locals.getInt("filled");
return material.id == holds;
}

defaultAction( "Fill" ) { self, hit ->

def material = hit.material;
def obj = entityObject(self);
int holds = obj.locals.getInt("filled");

if( holds != 0 ) {
echo "The bucket is already full.";
return;
}

// Else the bucket is empty so we can fill it
if( material == MaterialType.WATER ) {
obj.locals.setInt("filled", material.id);
obj.setModel( "bucket-water" );
echo "You filled the bucket with water."
} else if( material == MaterialType.SAND ) {
obj.locals.setInt("filled", material.id);
obj.setModel( "bucket-sand" );
echo "You fill the bucket with sand."
}

}.onlyIf() { self, hit ->
if( hit == null || hit.object != null )
return false;

def material = hit.material;
return material == MaterialType.WATER || material == MaterialType.SAND;
}

defaultAction( "Douse" ) { self, hit ->

entityObject(self).execute( ":empty" );
entityObject(hit.object).execute( ":douse", self );

}.onlyIf() { self, hit ->
if( hit == null )
return false;

// If the target can't be doused, then we won't
// worry about it
if( !entityObject(hit.object).hasAction( ":douse" ) )
return false;

// See if we even contain anything
return entityObject(self).locals.getInt("filled") != 0;
}
}


on( [playerJoined] ) {
type, event ->

println "Making sure the player" + player + " has some standard test tools...";

def items = getContainedItems( player );
def classes = items.collect {
it[ClassInstance.class].classEntity;
}

if( !classes.contains(firewood.classEntity) ) {
println "Need to create firewood in the player's inventory...";

firewood.createInstanceOn( player );
}

if( !classes.contains(match.classEntity) ) {
println "Need to create a match in the player's inventory...";

match.createInstanceOn( player );
}

if( !classes.contains(bucket.classEntity) ) {
println "Need to create a bucket in the player's inventory...";
bucket.createInstanceOn( player );
}
} [/java]

It's a self-contained script that adds the items to the players inventory as well as completely defining their graphics and behaviors. A little Groovy scripting and some API magic.
4 Likes

All still a work in progress, of course. I think I can tighten up the API a little bit more and clean up the above a little.

Great work as usual Paul. :smiley:

@madjack said:
Great work as usual Paul. :D


Thanks. :) I'm trying to get the game to the point where impatient people can implement stuff without me while I drag my feet getting the real systems in place. :)

Script-pluggable AI will be the next big one... and I'll have to come up with some stop-gap solution for creatures just to have something to hang the AI on. Even if they are just un-animated models.

man that’s really nice :slight_smile:



Why did you choose to go with that scripting over LUA or similar ? and any tips of tricks you picked up along the way worth sharing ?

Because Groovy and Java go hand in hand like a glove. Some of the ways it lets you add things to even existing Java classes at runtime is pretty mindblowing. Also, groovy can be compiled directly to Java class files so I could even precompile performance critical sections.



Really happy with it so far. Plus, it has a scripting console built into the language. If you run Mythtuna in -script mode you can open it up as a separate window and type Groovy right in while the game is running:

http://groovy.codehaus.org/Groovy+Console



And in Mythruna’s case, it’s already setup with the full scripting environment + API so works just like any mod script you’d have written.