Spellscript

From Hack/Mine Wiki
Revision as of 00:47, 28 February 2013 by Frizzil (Talk | contribs)

Jump to: navigation, search

Spellscript is a programming language for Minecraft, introduced in Hack/Mine v0.6.4 and planned for eventual status as an independent Minecraft mod. Spellscript allows you to mod Minecraft while in-game, no recompilation required. In other words, there's no installation hassle, no fiddling with jars, etc-- just writing or copy/pasting code into an in-game text editor, and you're done. Minecraft objects are represented in Spellscript as first-class objects, just like they are in Java, allowing for all sorts of amazing possibilities...

Spellscript is still in the early development stages, so:

  • Note that the amount of things that can be modded is still growing (see the section below for currently available hooks)
  • Be prepared to report bugs!

Contents

Important Places

  • For a tutorial on how to use Spellscript, see Spellscript Tutorial.
  • For details on how to use the IDE (i.e. code editor), see Spellscript IDE.
  • For a list of bugs, see Spellscript Bugs (since we're in the initial release, this is a must read!)
  • For a Sublime Text 2 language package, see here.
  • For a general-purpose language reference, read on...

Spellscript Hooks in Hack/Mine

Hack/Mine outsources a growing amount of its functionality to Spellscript, such that users can thoroughly customize their RPG experience. Currently, all of the spells and item effects are implemented with Spellscript. Also, currently, only admins can create Spellscript scripts! Of course, users can run these scripts, provided they've been setup to do so.

Admins may currently employ Spellscript in the following ways:

In-Game Hooks

  • Spellscript-Blocks (placeable from the Creative Inventory)
  • Script-Scheduling (brought up by pressing 'tilde' by default)

External Hooks

  • Command-line Access
  • Files in the ".minecraft/spells" directory

Language Mechanics

Spellscript is best construed as a healthy mix of Java and Python. Like Java, it is statically typed and features single-inheritance. Like Python, lexical scopes are signified by indentation and the syntax is relatively terse. Where Java would normally require explicit typing, Spellscript does its best to infer type implicitly. Where Python's flexibility allows unsound behavior, Spellscript introduces robustness to keep things sane.

Basic Syntax

As mentioned, lexical scoping is accomplished in Spellscript via indentation. Additionally, statements in Spellscript are delimited with newlines-- semi-colons may be used to concatenate statements into a single-line snippet of code.

The general structure of control-flow statements is as follows: <control_statement> ':' (<one-liner> | <suite>), where a suite consisted of an indented block of code. Control statements may be any of the following:

<control_statement> => ('while' | 'until') <expression>
                       ('if' | 'elif') <expression>
                       'else'
                       'for' (<variable_decl> | <variable>) 'in' (<list> | <int_bound_1> ['to' <int_bound_2>])

And here are some use cases:

int i = 0

while i < 15:
    print "loop!"
    i += 1

until i <= 0:
    print "more loop!"
    i -= 1

if i == 0:
    print "yep, i sure is 0!"
elif i > 0:
    print "i is positive!"
else:
    print "i is negative!"

for i in [3, 4, 2, 17]:
    print i

for i in 0 to 15:
    print i

Careful with the looping constructs: as with any programming language, getting stuck in an infinite loop is a real possibility.

The for loop can iterate over either a list or a range of integers over [b1..b2). Note that if the second bound is less than or equal to the first, no iteration will take place. Also, note that if the for loop's optional component is omitted and the first bound is an integer, it will treat the first bound as "0" and the second bound as the one it was given.

break and continue statements are available within the while, until, and for control-statements. The break statement will exit the inner-most applicable construct, whereas continue will skip to its next iteration. The return statement has the effect of ceasing all control-statements immediately, up until the "top level" is reached (generally, the current function declaration's lowest scope). Naturally, if the function has a return type, return <value> may be used to return that value. (If no return type is specified but values are given, one will be inferred for the function declaration.)

Type System

Spellscript features classes, strings, lists, and function pointers (which follow a pass-by-reference model), and primitive types (which follow a pass-by-value model). The primitive types are bool, int, float, and double. To understand their usage, it's best to look at some code.

def someFunction(int i, float j):
    return i * 2

class MyClass:
    def __init__(self):
        pass

    def someMethod(self):
        return 2

    def String someOtherMethod(self):
        return "Derpalerp!"

bool b = True
int i = 3
float f = 2f
double d = 1d
string s = "Hello world!"
string t = None
MyClass m = MyClass()
int$(int, float) funcPtr = someFunction

A notable distinction from Python is that variables must be declared before they are used. A variable declaration simply goes <type> <name> ['=' <expression>].

Function and class definitions are similar to those of Python; def signifies a function declaration, and class signifies a class declaration. Function return types may optionally be specified (unless it couldn't be inferred, in which case it is required.)

Literals

Possible literals for Spellscript types go as follows:

Type Literals
bool True, False
int 0, -15, 2, 0xf931, etc
float 3.1412f, 1f, -3.f, 0F, .1F, etc
double 3.1412, 1., -3d, 0D, etc
string None, "hello world", "oh hai!", "", etc

Note that all reference-types (strings, class instances, function pointers, etc) may be assigned None.

Operations

The operations available in Spellscript, and their order of precedence, are as follows:

Operator Description Order
= += -= *= /= %= Assignment, Compound Assignment Right-to-Left
is, is not Identity Tests Right-to-Left
== != Equality Tests Right-to-Left
instanceof, as Type Test, Safe-Cast --
or Boolean OR Left-to-Right
and Boolean AND Left-to-Right
< <= >= > Comparison Right-to-Left
+ - Addition, Subtraction Left-to-Right
* / % Multiplication, Division, Modulo Left-to-Right
not x, -x Inversion, Negation Right-to-Left
** Exponentiation Right-to-Left
x.y, x[y], (x) Method/Field Reference, Array Indexing, Parenthesis Left-to-Right

Lists

A list is an ordered collection of objects. Lists have an "inner type", which limits which kinds of objects can be added to them (but also guarantee which kind of objects we retrieve from them.) Here's an example of list creation and usage:

int[] numbers = [2, 4, 5, 4]
float[] reals = [float: 0f, 1f, 4f]
int[] emptyList = [int:]

for int n in numbers:
    print n

numbers.append(17)

for int i in numbers.size():
    print numbers[i]

for int j in [1, 2, 3, 4, 5]:
    print j

At the beginning, you'll notice the list type may be either explicitly given or omitted-- if the list is empty, the type must be made explicit, or else the compiler won't be able to deduce it's type and will throw an error.

We then iterate through each element of the list, and print it to the player's chat display. Next, we append '17' to the list, then print the whole thing again.

Finally, we show a handy way of iterating through some specific numbers, simply by instantiating the list in the for loop's declaration.

Function Pointers

Function pointers' syntax is highly subject to change at the time of this writing, so if things stop working (or there's any general doubt), consult this section of the wiki for the current syntax.

The type of a function pointer is declared as follows: <param_type> '$' ([<param_type> {',' <param_type>}])

The simplest way to reference a function declaration as a function pointer is to use its name, without parenthesis at the end. However, this will cause a compilation error if the call is ambiguous (suppose the function/method is overloaded)-- thus, to clarify, one can use the following syntax: <function name> ('$' '(' ')' | ['$'] '(' '$' <param_type> {',' '$' <param_type>} ')') Notice how a lack of parameter types requires a '$' to be included before the parenthesis, otherwise it would be ambiguous with a function call. This complexity is part of the reason the syntax may be revised in the future.

Here's some code to demonstrate valid usage:

def globalFunc(int i, int j):
  return i + j

def execute(int$(int, int) func, int a, int b):
  return func(a, b)
  
print execute(globalFunc($int, $int), 4, 5)

Function pointers can also have variables bound to them, resulting in a function pointer with the newly bound parameters removed. Consider the following:

def globalFunc(int i, int j):
  return i + j

int$(int) ptr = globalFunc$($int, 3)
print ptr(4)
#7 should be printed to the console

This is implicitly what happens when a function pointer is referenced from a class instance (i.e. the instance is bound to the function pointer as the self parameter).

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox