Intro

OA Scripts are running in a supervised environment allowing to :

  • have access to OA API: OA internal data structures and low-level functions

  • stop/start/create scripts at any moment

  • create User Interfaces scripts

  • auto import of Java jar files (you can use virtually any Java lib in your scripts)

  • schedule tasks

For scripting Groovy Programming Language is used. Without entering into details, Groovy is :

  • is a mix of features available in languages like Python, Ruby, Perl, and Smalltalk

  • very simple and elegant syntax

  • accepts 99% of Java syntax (most of Java code is also syntactically valid Groovy code)

  • seamlessly integrates with all existing Java classes and libraries

Programming Basics

Below you will find a gentile introduction to Programming Basics, created for beginners not knowing anything about programming.

Whatever the programming language is, basics are similar: variable declaration, functions, conditional statements, loops, primitive data types etc.

After reading sections below, hopefully you will able to start reading script’s code and trying to come up with yours.

Code Comments

There will be always moments when you have to return to your code, perhaps it’s to fix a bug, or to add a new feature. Looking at your own code after several months is almost as bad as looking at someone else’s code. What one needs is a means to leave reminders to yourself as to what you were doing.

For this purpose, you leave comments. Comments are little snippets of text embedded inside your code that are ignored by the compiler (software that translates your readable code into executable on CPU code).

You have several ways to leave comments:

// All below are comments and are not executed. This one is so called "one line comment", it comment out everything after '//' symbol

/*
Next one is
 several
  lines
   comment
*/

/* another version on one line comment */
Important

Coders from non-English speaking countries: please write your comments in English, unless you are 200% sure that your code will never be read by people who don’t speak your language.

Variables

A variable is a storage location and an associated symbolic name (an identifier) which contains some known or unknown quantity or information, a value. The variable name is the usual way to reference the stored value; this separation of name and content allows the name to be used independently of the exact information it represents.

Variables have to be declared before being referenced somewhere. The simple way to declare a variable is to provide a name along with some value.

For example, the following code declares a variable called 'name' with the value 'Johny'.

// Defining a variable name called here 'name' and assigning it with a value
def name = "Johny"

The value of variable can evolve over time by assigning to it a new value

Example
// Defining a variable name called here 'name' and assigning it with a value
def name = "Johny"
// Changing the value of variable from 'Johny' to 'Tommy' by assigning (=) a new value
name = "Tommy"

In the examples above, the keyword def stands for define.

[[Null pointer]]

Note

Variables can have a special value : null (Null pointer), a null pointer has a value reserved for indicating that the pointer does not refer to a valid value.

Because a null pointer does not point to a meaningful object, an attempt to reference (using in mathematics operations) a null pointer usually (but not always) causes a run-time error or immediate program crash.

Closures

In previous section we have declared a variable which references a value. But you can also declare a variable which will retain a reference to some calculation, returning a value (= result) or returning nothing.

Closure is a function that can be assigned to a variable, means you can pass calculation function(s) to other functions, it’s a easy way to customize your calculation not only on functions parameters but also incude some behavoir.

Syntax definition of a Closure
def referenceToClosure = { ArgumentsList ->
	Set of Statements
}
ArgumentsList

defines the list of arguments (or parameters) that can be passed to the closure. Each argument is separated by a comma.

The symbol

is used to separate the Arguments List with the set of statements in the Closure.

Simply defining a closure doesn’t serve any purpose until they are called and their functionality used by someone. Following is the syntax to call a Closure Definition.

Declaring and calling a Closure
// Defining a variable called 'printing', value is a Closure which accepts one parameter of any type
def printing = { parameter ->
	// Print out a message: I'm printing to the output : value of 'parameter'
	printOut "I'm printing to the output the value of 'parameter' variable: " + parameter
}
printing 'Johny'	// using a Closure, invoking Closure with as parameter a String 'Johny'

You can omit symbol, it would mean that there are no parameters defined, for instance one default argument will be available: it (see examples below).

Closures with different number of arguments
def a = { -> printOut 'No arguments at all' }
def b = { x -> printOut "One argument x = $x" }
def c = { x, y -> printOut "Two arguments x = $x, y = $y" }
def d = { printOut "One implicit argument it = $it (it can be null if no argument passed by caller)"}
// testing different closures
a()
b('too')
c('too', 'coucou')
d()
d('rocky')

When the Closure is invoked with the value rocky (above: d('rocky')), it is substituted with 'rocky'. If no value is passed onto the closure definition (above: d()), then null gets printed.

As example, let’s express via a script the following formula : \$z=x+y\$

def z = { x, y ->
	return x + y
}

Now let’s use it :

def z = { x, y ->
	return x + y
}
printOut z(2, 3)	// prints out the result of z = 2 + 3
printOut(z(2, 3))	// same as above with parentheses, prints out the result of z = 2 + 3

return word is optional, you can put it or not, in both cases closure will return last value in block of statements, so you can rewrite it as :

def z = { x, y -> x + y }
printOut z(2, 3)	// prints out the result of z = 2 + 3
printOut(z(2, 3))	// same as above with parentheses, prints out the result of z = 2 + 3

As you see you can omit parentheses

Practical usefulness of closures is when used to inject some behaviour to the calculation, below an example of how you decide which element of the list should be selected (you will see more examples in List and Map sections how to deal with these structures).

Accepting a Closure as parameter
// Declaring a list of numeric values
def list = [1,2,3,4,5,6,7,8,9,10]
// List have default function 'findAll' accepting a closure to filter out values, that allow to test each value to meet some condition and retain only needed values
def newList = list.findAll { it > 5 }
// Prints out the value of 'newList'
printOut newList

It will print out a list of elements strictly bigger than value 5: [6,7,8,9,10]

Simple Types

In examples above, we have used def keyword to declare a variables/closures/return type of functions. def keyword means the type of value is undefined or, in other words, can be of any type.

Basically, in order to do operations on a CPU in efficient way (read it: fast), it’s batter to provide the type. Actually in many programming languages it’s mandatory, e.g C/C++, Java, C# etc. With the language you are dealing here (Groovy), it’s optional to provide the type of variables, e.g JavaScript, PHP, Perl, Python etc. Types been optional means if you don’t define them, Type will be defined (inferred) at runtime (during code execution), which cost some CPU time (slower execution).

The result of not writing types of values you are manipulating, the code becomes more readable.

Below you will see some common types and operations you may do on them.

Numeric

In order to do arithmetic operations, you have numeric types. You can use them as you do it on your Calculator.

On computers Numeric types have different length to reduce the size of data and optimize the speed of operations, adding 2 values of 32 bits is much faster than 2 values of 64 bits.

But you don’t need to deal with it at all, you can let the language decide which type is better to used by simply using def keyword. (more here about supported Numeric types, but you will need it only if you are doing optimizations: information Primitive Data Types)

Example
def a = 2                    // declaration of variable 'a', value 2, type: numeric (likely Integer)
def b = 10.5                 // declaration of variable 'b', value 10.5, type: floating point number (likely Float or Double)
def c = a + b * 2            // declaration of variable 'c', value is a result of computation: a + b * 2 (as 'a' is Integer, 'b' is Float or Double and '2' is Integer, the rusult 'c' will be Float or Double)
printOut 'c = ' + c          // prints out 'c = 23[result of a + b * 2]'
printOut 'd = ' + (a + b)    // prints out 'd = 12.5[result of a + b]'
Important

Last line prints out d = 12.5, as you see there are parentheses for (a + b)

Indeed, operation plus is left-associative (meaning the operations are grouped from the left).

Operator Associativity is only needed when the operators in expression have the same precedence. Usually + and - have the same precedence. Consider the expression 7 - 4 + 2. The result could be either (7 - 4) + 2 = 5 or 7 - (4 + 2) = 1. The former result corresponds to the case when + and - are left-associative, the latter to when + and - are right-associative.

In order to reflect normal mathematical usage: addition, subtraction, multiplication, and division operators are usually left-associative while an exponentiation operator (if present) is right-associative. Any assignment operators are also typically right-associative.

If you remove parentheses, last line will be similar to:

def a = 2
def b = 10.5
def c = a + b * 2
printOut( ('d = ' + a) + b )
// or to check equivalence of 2 expressions
assert ('d = ' + a) + b == 'd = ' + a + b

( ('d = ' + a) + b ) ⇒ ( \'d = 2' + b) ⇒ 'd = 210.5'

Common list of operators :

Operator Function

+

plus

-

minus

*

multiply

**

power

/

divide

%

modulo (the remaining part after a division: 13 % 10 = 3)

String

String type is a sequence of characters, either as a literal constant or a variable.

Our first example in this manual was a String declaration
// Type of variable 'name' is dynamically inferred
def name = 'Johny'
// Type of variable 'name2' is String
String name2 = 'Johny'
assert name == name2	// tests that both values are equivalent (via == operator)

String variable types can be concatenated with "+" (plus) keyword, concatenation means append 2 sequences of characters.

Concatenation
def a = 'world'
printOut 'hello ' + a + ' !'
Multi-line String
def text = '''
hello there
how are you today?
'''
printOut text

Boolean

Boolean type is a data type, having two values (usually denoted true and false), intended to represent the truth values of logic.

Collections

Collections are very important in any scripting language, they provide an easy way to iterate over an ordered(or not) collection of data.

List

A list is an ordered group of values (items or elements). It is a very general structure, and list elements don’t have to be of the same type, but good practice suggests to use the same type for all elements in the list.

You can create lists as follows.

Example
// Defining a list holding METALSTORAGE, CRYSTALSTORAGE and DEUTERIUMTANK
def b = [METALSTORAGE, CRYSTALSTORAGE, DEUTERIUMTANK]
Note Remark that [] is the empty list declaration.

Below some easy to use operations on values of List. The idea is to apply some operation on each value of List.

Examples
// defining list
def list = [27, 11, 33]
// finding First value bigger than 15
printOut list.find { it > 15 }
// finding All values bigger than 15
printOut list.findAll { it > 15 }
// sort values
printOut list.sort()
// reverse order of values in the list
printOut list.reverse()
// appy some operations to each value (loop)
list.each { value ->
  printOut "value is : $value"
}

Map

A Map is a set of distinct keys (or indexes) mapped (or associated) to values. The association is such that the map will return the value when given the key.

// Defining a variable 'b', initialised to: string 'a' mapped to numeric value 1
def b = [a: 1]
// Accessing the associated value to 'a'
printOut 'Value of a: '+b.get('a')
// Second variant of accessing the associated value to 'a'
printOut 'Value of a: '+b['a']

Map keys are strings by default: [a:1] is equivalent to [a:1]. But if you really want a variable to become the key, you have to wrap it between parentheses: [(a):1].

Maps containing several key:values can be created using the following syntax. .Example

// Define a Map (providing mapping from Ship types to Numbers), map will hold SMALLCARGO=10, LARGECARGO=5 and ESPIONAGEPROBE=5
def map = [
  SMALLCARGO: 10,
  LARGECARGO: 5,
  ESPIONAGEPROBE: 5
]

As List, Map is a dynamic structure, so you can add, remove, replace elements at any moment

Example of getting size of Map and adding, removing entries
// defining a map
def entriesMap = [:]
// testing size of map equals 0 (normal, it's empty map)
assert entriesMap.size() == 0
// adding key mapped to value : 'foo' = 10
entriesMap.put('foo', 5)
// testing size of map equals 1
assert entriesMap.size() == 1
// removing key 'foo' from Map
assert entriesMap.remove('foo')
// testing size of map equals 0
assert entriesMap.size() == 0

Below some easy to use operations on entry of Map. The idea is same as for List, you apply some operation on each entry (key associated to value) of Map.

Examples
// defining an empty map
def entriesMap = [foo: 27, toto: 11, coucou: 33]
// finding First entry with value bigger than 15
printOut entriesMap.find { key, value -> value > 15 }
// finding All entries with value bigger than 15
printOut entriesMap.findAll { key, value -> value > 15 }
// sort entries by key
printOut entriesMap.sort { it.key }
// sort entries by value
printOut entriesMap.sort { a, b -> a.value <=> b.value }
// appy some operations to each entry (loop)
entriesMap.each { key, value ->
  printOut "$key = $value"
}

Conditional Statement

A Decision is when a program has more than one choice of actions depending on a variable’s value. Think of a traffic light. When it is green, we continue our drive. When we see the light turn yellow, we reduce our speed, and when it is red, we stop. These are logical decisions that depend on the value of the traffic light.

The if-then-(else) construct is common across many programming languages.

If-Then(-Else) construct perform different computations or actions depending on whether a boolean condition evaluates to true or false.

If-Then-Else Statement
if ( booleanExpression ) {
	// Block of Statements
} else if ( booleanExpression ) {
	// Other Block of Statements
} else {
	// Yet Other Block of Statements
}

When if statement is evaluated, it expects a boolean condition - for example, x > 0, which means "the variable x contains a number greater than zero ? yes (=true), no (=no)" - and evaluates that condition at execution. If the condition is true, the block of statements { …​ } just after if ( booleanExpression ) + is executed. Otherwise, the execution continues in the following branch - either in the +else { …​ } block (which is optional), or if there is no else branch, then after the end { …​ } block.

Expressions can be tested in several different ways, returning each time a Boolean value (true or false). Here is a table of different expressions:

Operator Function

<

less than

less than or equal to

>

greater than

>=

greater than or equal to

==

equal

!=

not equal

Boolean Expressions can be chained to create more complex decision tables with operators and (&&) and or (||):

def a = 15
def b = 30
if ( a > 10 && b >= 30) {
	// 'a' is bigger than 10 AND b is bigger or equals 30
}
Tip

Refering to Null pointer values, you can check that a variable is not refering to null before using it in several ways:

def toto	// without defining an initial value, the value will be null, same as: def toto = null

if(toto != null) printOut 'toto is not null'
if(toto) printOut 'toto is not null'

if(toto == null) printOut 'toto is null'
if(!toto) printOut 'toto is null'
Important

Variables should be declared before been used and been visible in the context of the execution. Let’s look at the following example:

if(true){
    def a = 10
}
def f = { ->
    def b = 'toto'
}
printOut a
f()
printOut b

Variable a is visible only in the if-then statement and variable b is only visible inside the function f.

So the output will be:

null
null

Let’s use Conditional Statement with Closure to express the following formula : \$x={(((a*b))/c,if c!=0),(text{0},if c=0):}\$

def x = { a, b, c ->
  if(c != 0) return (a*b)/c
  else return 0
}
// Let's print some results
printOut x(2,3,0)
printOut x(2,3,2)
Output :
0
3

Groovy String

Strings that are declared inside double-quotes (i.e. either single double-quotes or triple double-quotes for multi-line strings) can contain arbitrary expressions inside them using the $\{ expression \} syntax.

Basically Groovy String (or GString) represents a String which contains embedded values such as "hello there ${user}, how are you?" which can be evaluated lazily (during code execution).

Example
def n = 10
printOut "n is ${ if(n > 5) { 'bigger than 5' } else { 'less or equals to 5' } }"

Common usage of Groovy String (same constructs exist in: PHP, Perl, Python) is when you want inject some varibales in the output without using String concatenation

Example
def name = 'Johny'
def age = 18
printOut "${name}'s age is $age"
// it's easier to read than
printOut name + "'s age is " + age

As for String type, you can create multi-line GString by using """ …​ """ instead of " …​ "

Multi-line GString
def n = 10
printOut """n is ${
	if(n > 5) {
		'bigger than 5'
	} else {
		'less or equals to 5'
	}
}"""

If we want to take the value of a variable then the '$' sign should be prefixed along with the variable name.

For example, the following code will print "Hello Johny" in the console
def name = 'Johny'
printOut "Hello $name"

Loops

Ordinarily, the computer starts with the first line and then goes down from there. Control structures, including if statements, change the order that statements are executed or decide if a certain statement will be run.

Loops allow to loop execution on a block of statements while some condition is meet, otherwise exit the loop.

while

while loops are similar to if-then statement, the difference is that the while loop repeats the execution of { …​ } block of statements while boolean expression is evaluated to true.

while ( booleanExpression ) {
    // Block of Statements
}
// After Loop Statements below

for

TODO

Iterate over elements of a List using for loop
def list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
def sum = 0
for (element in list) {
    sum += element
}
printOut "Sum is: $sum"
Iterate over elements of a List using each and Closure which is processing each element of list
def list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
def sum = 0
list.each { element ->
    sum += element
}
printOut "Sum is: $sum"
for ( int i = 0; i < 10; i++ ) {
    // Block of Statements
}

Errors in scripts

Compilation errors

Compilation errors: programming language syntax is not respected and the line having the issue is shown

Example: Test1
def a = {
  printOut "Coucou"
)
Log:
Script Test1 has compilation errors:                1
startup failed:
Test1: 3: unexpected token: ) @ line 3, column 1.   2
   )                                                3
   ^

1 error
1 error is located in script Test1
2 and located at line 3, column 1
3 faulty symbole is )

Runtime error

Runtime error: variable/function is unknown or call parameters are not valid, timeout during communication etc.

Example:
def a = { it -> printOut "Text: " + it }
a("Coucou", 5)
Log:
15:04:33 : Script Test2 has been stopped, MissingMethodException: No signature of method: Test2$_run_closure7.doCall() is applicable for argument types: (java.lang.String, java.lang.Integer) values: [Coucou, 5]	1
15:04:33 : Possible solutions: doCall(java.lang.Object), call(), call([Ljava.lang.Object;), call(java.lang.Object), findAll()
15:04:33 : Below full stack trace:
15:04:33 :
15:04:33 : groovy.lang.MissingMethodException: No signature of method: Test2$_run_closure7.doCall() is applicable for argument types: (java.lang.String, java.lang.Integer) values: [Coucou, 5]
15:04:33 : Possible solutions: doCall(java.lang.Object), call(), call([Ljava.lang.Object;), call(java.lang.Object), findAll()
15:04:33 : 	at Test2.run(Test2:2)		2
1 indicates that there is no method/closure accepting argument, types (java.lang.String, java.lang.Integer), values: [Coucou, 5]
2 it’s a track trace indicating that error is located on line 2 of the script Test2 Error is that function a is accepting only 1 parameter, not 2
Debugging Script

In case of Runtime error, if you don’t understand why you have the error, from the stack trace identify the line of the script that is faulty and put just before that line some additional code to print out variables used in faulty line.

To print out internal state (values) of a variable, use function dump()

Example
def f = { a , b ->
    return a * b
}
f(10, true)
Log:
22:29:19 : groovy.lang.MissingMethodException: No signature of method: java.lang.Integer.multiply() is applicable for argument types: (java.lang.Boolean) values: [true]
22:29:19 : Possible solutions: multiply(java.lang.Character), multiply(java.lang.Number)
22:29:19 : 	at Test3$_run_closure7.doCall(Test3:2)
22:29:19 : 	at Test3.run(Test3:4)	1
1 it’s a track trace indicating that error is located on line 2 of the script Test3

Now we will dump variables used on line 2

Example
def f = { a , b ->
    printOut "a = ${a.dump()}"	1
    printOut "b = ${b.dump()}"	2
    return a * b
}
f(10, true)
1 first output : a = <java.lang.Integer@xyz value=10>
2 second output : b = <java.lang.Boolean@xyz value=true>

first value has type Integer (numeric) and second value is Boolean (not numeric), so multiplying them to have numeric result will be difficult…​

Naming Conventions

Naming conventions make code easier to read for yourself and for other programmers. Readability of code is important because it means less time is spent trying to figure out what the code does, leaving more time to fix or modify it.

Note

Sources of notes below: Source 1 Source 2

When choosing a name for an identifier make sure it’s meaningful. For instance, if your program deals with customer accounts then choose names that make sense to dealing with customers and their accounts (e.g., customerName, accountDetails). Don’t worry about the length of the name. A longer name that sums up the identifier perfectly is preferable to a shorter name that might be quick to type but ambiguous.

A Few Words About Cases

Using the right letter case is the key to following a naming convention:

  • Lowercase is where all the letters in a word are written without any capitalization (e.g., while, if, mypackage).

  • Uppercase is where all the letters in a word are written in capitals. When there are more than two words in the name use underscores to separate them (e.g., MAX_HOURS, FIRST_DAY_OF_WEEK).

  • CamelCase (also known as Upper CamelCase) is where each new word begins with a capital letter (e.g., CamelCase, CustomerAccount, PlayingCard).

  • Mixed case (also known as Lower CamelCase) is the same as CamelCase except the first letter of the name is in lowercase (e.g., hasChildren, customerFirstName, customerLastName).

The below list outlines the standard Java naming conventions for each identifier type:

  • Variables: Names should be in mixed case. Use full English descriptions for names. Avoid using abbreviations. You should write your code with the aim of making it understandable to others. Others will need to read and understand your code and one of the major keys to understanding is through the use of meaningful identifier names.

By using meaningful names, you go a long ways towards writing self-documenting code (code that is understandable on its own without requiring accompanying comments).

For example, use names like firstName, lastName, and middleInitial rather than the shorter versions fName, lName, and mi.

double tax1;       // sales tax rate (example of poor variable name)
double tax2;       // income tax rate (example of poor variable name)

double salesTaxRate;      // no comments required due to
double incomeTaxRate;     // self-documenting variable names

// Only use very short names when the variables are short lived, such as in for loops:

for (int i=0; i<20;i++) {
  // i only lives in here
}
  • Constants: Names should be in uppercase.

     static final int DEFAULT_WIDTH
     static final int MAX_HEIGHT
  • Classes: Names should be in CamelCase. Try to use nouns because a class is normally representing something in the real world:

     class Customer
     class Account
  • Methods: Names should be in mixed case. Use verbs to describe what the method does:

     void calculateTax()
     String getSurname()

Scripting API

Importing Java libs

You can import any Java lib in OA by placing them in ./ext folder. Libraries are imported automatically just before executing a script; for ease of management you can be place libs in subfolders.

When loading is happening, OA Overview output will print something like this:

22:11:48 : Added a lib to the classpath: E:\OGameAutomizer\ext\common\commons-codec-1.3.jar

Constants

4 list constants: ships, defences, buildings, technologies. No need to declare them.
//The following lists are build-in, no need to define them, they are globally accessible
def ships = [SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER]
def defences = [ROCKETLAUNCHER, LIGHTLASER, HEAVYLASER, GAUSSCANNON, IONCANNON, PLASMATURRET, SMALLSHIELDDOME, LARGESHIELDDOME, ANTIBALLISTICMISSILE, INTERPLANETARYMISSILE]
def buildings = [METALMINE, CRYSTALMINE, DEUTERIUMSYNTHESIZER, SOLARPLANT, FUSIONREACTOR, ROBOTICSFACTORY, NANITEFACTORY, SHIPYARD, METALSTORAGE, CRYSTALSTORAGE, DEUTERIUMTANK, METALDEN, CRYSTALDEN, DEUTERIUMDEN, RESEARCHLAB, TERRAFORMER, ALLIANCEDEPOT, LUNARBASE, SENSORPHALANX, JUMPGATE, MISSILESILO, SOLARSATELLITE]
def technologies = [ESPIONAGETECHNOLOGY, COMPUTERTECHNOLOGY, WEAPONSTECHNOLOGY, SHIELDINGTECHNOLOGY, ARMOURTECHNOLOGY, ENERGYTECHNOLOGY, HYPERSPACETECHNOLOGY, COMBUSTIONDRIVE, IMPULSEDRIVE, HYPERSPACEDRIVE, LASERTECHNOLOGY, IONTECHNOLOGY, PLASMATECHNOLOGY, INTERGALACTICRESEARCHNETWORK, GRAVITONTECHNOLOGY, ASTROPHYSICS]
Example: prints out account technologies
for(technology in technologies){
  if(getNumberOfTech(technology) != 0) printOut getEntityString(technology) + ": " + getNumberOfTech(technology)
}
Example: Let’s check which ships and defences can be built on your first planet: canBuild should be true and getPrice should be bigger than resources on your planet
def planet = getPlanets()[0]
for(entity in ships+defences){
  if(!isShipyardInProgress(planet) &&
       canBuild(entity, planet) &&
       (getPrice(entity, planet).getMetalValue() <= planet.getMetal()) &&
       (getPrice(entity, planet).getCrystalValue() <= planet.getCrystal()) &&
       (getPrice(entity, planet).getDeutValue() <= planet.getDeuterium()))
  {
    printOut "You can build at least one " + getEntityString(entity)
  }
}
Some constants to make your code more readable
static final Boolean YES = Boolean.TRUE
static final Boolean DONE = Boolean.TRUE
static final Boolean NO = Boolean.FALSE
static final Boolean FAILED = Boolean.FALSE

static final String MCD = "MCD"
static final String MDC = "MDC"
static final String CDM = "CDM"
static final String CMD = "CMD"
static final String DMC = "DMC"
static final String DCM = "DCM"
Example
def verbose = YES   // YES = Print additional outputs, NO = don't print
if(verbose) printOut 'Print additional outputs...'

Internal Types

Coordinates

Internal to OA Coordinates representation, contains:

  • address: galaxy:system:planet

  • type: planet [default], moon, debris

Coordinates Class
public class Coordinates implements Comparable<Coordinates>, Cloneable {

	public static final byte PLANET_TYPE = 1;
	public static final byte DEBRIS_TYPE = 2;
	public static final byte MOON_TYPE = 3;

	public byte getGalaxy()
	public short getSystem()
	public byte getPlanet()
	public byte getPlanetType()

	// Constructors
	public Coordinates(String string, byte planetType)
	public Coordinates(byte galaxy, short system, byte planet, byte planetType)
	public Coordinates(int galaxy, int system, int planet, byte planetType)

	public boolean equals(Object coord)

	public String toString()

	public boolean isMoon()

	public Object clone()

	/**
	 * Parses String param containing coordinates, e.g 'X:YY:Z', '[X:YY:Z]', '[X:YY:Z] Moon'
	 * @param coord
	 * @return Coordinates in case coord was parsed correctly, null otherwise
	 */
	public static Coordinates get(String coord)

	public static boolean isCoordinates(String temp)
}
Example
// Coordinates manipulation
printOut new Coordinates(2,125,10,Coordinates.MOON_TYPE)  // PLANET_TYPE, DEBRIS_TYPE, MOON_TYPE
def coord = Coordinates.get("[2:125:10] SuperMoon...")  // valid values: "1:1:1", "[1:1:1]", "[1:1:1] M..." (the last one is for Moon coordinates)
printOut coord
printOut new Coordinates(coord.galaxy,coord.system,coord.planet,Coordinates.PLANET_TYPE)
printOut "["+coord.galaxy+":"+coord.system+":"+coord.planet+"]"+(coord.isMoon() ? " Moon" : "")

PlayerPlanet

Contains player’s planet information

PlayerPlanet
public class PlayerPlanet extends api.model.Planet {

	public int getPlanetIndex()

	public long getMetalInAir()
	public long getCrystalInAir()
	public long getDeuteriumInAir()

	public String getName()
	public Coordinates getCoordinates()

	public long getMetal()
	public long getCrystal()
	public long getDeuterium()

	public long getMetalDelta()
	public long getCrystalDelta()
	public long getDeuteriumDelta()

	public long getEnergyUsage()
	public long getEnergyTotal()

	public long getMetalProduction()
	public long getCrystalProduction()
	public long getDeuteriumProduction()

	public ConstructionMap getEntities()

	public boolean hasShips()
	public String getShipsString()
	public boolean hasDefences()
	public String getDefencesString()
	public boolean hasBuildings()
	public String getBuildingsString()
	public boolean hasTechnologies()
	public String getTechnologiesString()
	public boolean hasShipsDefences()
	public String getShipsDefencesString()
	public boolean hasBuildingsTechnologies()
	public String getBuildingsTechnologiesString()

	public ConstructionMap copyEntities()

	public String toString()
}

TODO:FleetInMovement

OGamePlayerLiteType

public class OGamePlayerLiteType {
	public String getPlayerName()
	public String getPlanetName()
	public String getAllianceName()
	public Coordinates getCoordinates()

	public String getActivityStatusString()

	public short getRank()
	public short getAllianceRank()

	public int getMetalDebris()
	public int getCrystalDebris()

	public boolean isAllyInAvoidAttackList()
	public boolean isInAvoidAttackList()
	public boolean isPlanetAvoidAttack()
	public boolean isUnderSurveillance()
	public boolean isTarget()
}

ConstructionMap

Contains buildings, techs, ships and defences levels

ConstructionMap (Trove 3.0 doc: TByteObjectHashMap)
public class ConstructionMap extends TByteObjectHashMap<ConstructionType> {

	public boolean hasShips()
	public String getShipsString()
	public boolean hasDefences()
	public String getDefencesString()
	public boolean hasBuildings()
	public String getBuildingsString()
	public boolean hasTechnologies()
	public String getTechnologiesString()
	public boolean hasShipsDefences()
	public String getShipsDefencesString()
	public boolean hasBuildingsTechnologies()
	public String getBuildingsTechnologiesString()

	public ConstructionMap copyEntities()

	public String toString()
}

Fluid API

Following API allow you to build dynamically a Fleet composition for sending or/and recalling it.

Note

Some proposed features are only available via scripting API and not in OA GUI.

Fleet Builder

By creating a new fleet you can completely configure it, send it (immediately, when possible, in future or as scheduled recurrent task) and recall it afterwards (immediately, at fixed percentage (0-100%) of the trip or in future)

Getting Fleet Builder
def fleet = getBuilderFactory().newFleet()
Building a Fleet
/**
 * Below several versions how you can build and send an attacking fleet from '1:XXX:4' to '1:XXX:10' composed of [HEAVYFIGHTER: 10, SMALLCARGO: 30]
 */
// Version 1
def fleet = getBuilderFactory().newFleet()
fleet.setFrom( '1:XXX:4' )
fleet.setTarget( '1:XXX:10' )
fleet.setMission( ATTACK_MISSION )
fleet.addShips(HEAVYFIGHTER: 10, SMALLCARGO: 30)
printOut "Result of sending: " + fleet.sendNow()

// Version 2
def fleet1 = getBuilderFactory().newFleet()
 .setFrom( '1:XXX:4' )
 .setTarget( '1:XXX:10' )
 .setMission( ATTACK_MISSION )
 .addShips(HEAVYFIGHTER: 10, SMALLCARGO: 30)

printOut "Result of sending: " + fleet1.sendNow()

// Version 3
printOut "Result of sending: " + getBuilderFactory().newFleet().setFrom( '1:XXX:4' ).setTarget( '1:XXX:10' ).setMission( ATTACK_MISSION ).addShips(HEAVYFIGHTER: 10, SMALLCARGO: 30).sendNow()

The version 2 is interesting because it’s easily readable and you can add a call to recall the fleet.

Fleet sending, then recall
// Version 2
def fleet1 = getBuilderFactory().newFleet()
 .setFrom( '1:XXX:4' )
 .setTarget( '1:XXX:10' )
 .setMission( ATTACK_MISSION )
 .addShips(HEAVYFIGHTER: 10, SMALLCARGO: 30)

printOut "Result of sending: " + fleet1.sendNow()

printOut "Recall order was added: " + fleet1.recallIn( 1.minute + 15.seconds )
Available Functions for a new Fleet
/**
 * Fluent Fleet Builder API
 */
def fleet = getBuilderFactory().newFleet()
 .setFrom( 'X:YYY:Z' )                      // String (e.g '1:152:10', '[2:125:6] Moon') / Coordinates (e.g getPlanet('3:352:8').coordinates) / Planet (e.g getPlanets().get(0)) / Planet Name (e.g 'MyPlanetName')
 .setTarget( 'A:BBB:C' )                    // String (e.g '1:152:10', '[2:125:6] Moon') / Coordinates (e.g getPlanet('3:352:8').coordinates) / Planet (e.g getPlanets().get(0)) / Planet Name (e.g 'MyPlanetName')
 .setSpeed( 100 )                           // speed in %, possible values: [10,20,30,...,80,90,100], default: 100
 .setMetal( 0 ).setCrystal( 2000 )          // by default, deuterium is set automatically to allow the trip, you can overide the value by setDeuterium( xxx )
 //.setSelector( MCD )                      // Take all resources (Resources Selector: DCM, D=deuterium, C=crystal, M=metal), if some resource are set explicitly, do not override them
 //.setMission( ATTACK_MISSION )            // Missions: ATTACK_MISSION, TRANSPORT_MISSION, DEPLOYMENT_MISSION, ESPIONAGE_MISSION, COLONIZATION_MISSION, RECYCLE_MISSION, EXPEDITION_MISSION
 .setMission(EXPEDITION_MISSION)            // if you set EXPEDITION_MISSION, following options are available:
   .setExpeditionTime( 8 )                  //   Expedition time: depends on your astrophysics tech level, default: 1
   .setTarget( '1:248:16' )                 //   Target should be planet 16
   .setExpeditionRandomSolarSystemRadius(10)//   Random target around the target
 .setShips(                                 // Sets initial quantity of ships, in case you don't set any ships for the fleet, then all ships on the planet 'from' will be sent
    SMALLCARGO: 2, LARGECARGO: 1            // SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER
 )
 .addShips(ESPIONAGEPROBE: 5, SMALLCARGO: 30)// Adds ships to initial quantity
 .addShip(SMALLCARGO, 1)                    // Adds a ship to initial quantity
 //.setRecallIn( 20.seconds )               // Recall the fleet 20 seconds after the fleet been sent (sending can be done in future)
 //.setRecallIn( 20 )                       // Equivalent to above, recall the fleet 20 seconds after the fleet been sent
 //.setRecallAt( 1 )                        // Recall the fleet at x% of the trip after the fleet been sent (sending can be done in future), x% [1-100%]
 .onSendSuccess {                           // Closure is called once the fleet is successfully sent
   printOut "Send done !!!"
 }
 .onSendFailure {                           // Closure is called if fleet sending failed
   printOut "Send failed !!!"
 }
 .onRecallSuccess {                         // Closure is called once the fleet is successfully recalled
   printOut "Recall done !!!"
 }
 .onRecallFailure {                         // Closure is called if fleet recalling failed
   printOut "Recall failed !!!"
 }

printOut "fleet trip duration: " + fleet.getDuration() + " sec"
printOut "fleet consumption: " + fleet.getConsumption()

// Send fleet now (blocking call)
boolean result = fleet.sendNow()
printOut "Result of send: $result"
if (fleet.isSent()) {
  printOut "Fleet was sent at: " + new Date( fleet.getFleet().getSendTimestamp() )
  // Recall the fleet now, timeout/wait max 60 seconds (blocking call)
  /*
  result = fleet.recallNow( 60.seconds )
  printOut "Result of recall: $result"
  */
  // Recall in 30 seconds from now (not blocking call)
  printOut "Recall order was added: " + fleet.recallIn( 30.seconds )
}

// Send everyday at 19:48 (not blocking call)
//fleet.schedulerSendAtCronExpr( "48, 19, *, *, *" )

// Send today (or tomorrow) at 19:48, send only once (not blocking call)
//fleet.schedulerSendAtCronExpr( "48, 19, *, *, *", 1 )

// Send in 15 seconds (not blocking call)
//fleet.schedulerSendIn( 10.seconds + 5.seconds )

// Send when possible (not blocking call), timeout/wait 60 seconds to be done
//printOut "Result of send: " + fleet.sendWhenPossible().waitTillDone( 60.seconds )

// Fork a thread and wait within it till the fleet is sent
/*
Thread.start {
  printOut "Result of send: " + fleet.sendWhenPossible().waitTillDone( 60.seconds )
}
*/

Fleet Recaller

Fleet recaller has a limited scope and allows you only to configure and recall a fleet (immediately, at fixed pourcentage (0-100%) of the trip or in future)

def recaller = getBuilderFactory().newRecall()
 .setFrom( 'X:YYY:Z' )                      // String (e.g '1:152:10', '[2:125:6] Moon') / Coordinates (e.g getPlanet('3:352:8').coordinates) / Planet (e.g getPlanets().get(0)) / Planet Name (e.g 'MyPlanetName')
 .setTarget( 'A:BBB:C' )                    // String (e.g '1:152:10', '[2:125:6] Moon') / Coordinates (e.g getPlanet('3:352:8').coordinates) / Planet (e.g getPlanets().get(0)) / Planet Name (e.g 'MyPlanetName')
 .setShips(                                 // Sets initial quantity of ships, in case you don't set any ships for the fleet, then all ships on the planet 'from' will be sent
    SMALLCARGO: 2, LARGECARGO: 1            // SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER
 )
 .addShips(ESPIONAGEPROBE: 5, SMALLCARGO: 30)// Adds ships to initial quantity
 .addShip(SMALLCARGO, 1)                    // Adds a ship to initial quantity

// Recall the fleet now, timeout/wait max 60 seconds (blocking call)
boolean result = fleet.recallNow( 60.seconds )
printOut "Result of recall: $result"

// Recall in 30 seconds from now (not blocking call)
printOut "Recall order was added: " + fleet.recallIn( 30.seconds )

// Send everyday at 19:48 (not blocking call)
//fleet.schedulerSendAtCronExpr( "48, 19, *, *, *" )

// Send today (or tomorrow) at 19:48, send only once (not blocking call)
//fleet.schedulerSendAtCronExpr( "48, 19, *, *, *", 1 )

// Send in 15 seconds (not blocking call)
//fleet.schedulerSendIn( 10.seconds + 5.seconds )

// Send when possible (not blocking call), timeout/wait 60 seconds to be done
//printOut "Result of send: " + fleet.sendWhenPossible().waitTillDone( 60.seconds )

// Fork a thread and wait within it till the fleet is sent
/*
Thread.start {
  printOut "Result of send: " + fleet.sendWhenPossible().waitTillDone( 60.seconds )
}
*/
Note

If you want to recall a fleet going from [x:yyy:z] to [x2:yyy2:z2] and you don’t know the fleet composition, just do not set any ships in recaller.

Example
def recaller = getBuilderFactory().newRecall()
 .setFrom( 'x:yyy:z' )
 .setTarget( 'x2:yyy2:z2' )

printOut "Recall order was added: " + recaller.recallNow()

Globale API

printOut()

/**
 * Prints a newline
 */
void printOut(Object msg)

clearOut()

/**
 * Clears the output
 */
void clearOut()

sleep()

/**
 * Sleeps a defined number of milliseconds
 */
void sleep(long ms)

getHTTPProxyPort()

/**
 * Returns the port number of the local proxy
 */
int getHTTPProxyPort()

getOGameAddress()

/**
 * Returns OGame Server address
 */
String getOGameAddress()

getLogin()

/**
 * Returns OGame Server login
 */
String getLogin()

isLoggedIn()

/**
 * Returns true in case OA is currently logged-in, otherwise false
 */
boolean isLoggedIn()

isConnected()

/**
 * Returns true in case OA is currently connected (communication between OA and OGame is possible), otherwise false
 */
boolean isConnected(){

isJobCurrentlyRunning()

/**
 * Returns true in case there is currently running job, otherwise false
 */
boolean isJobCurrentlyRunning()

isAttacked()

/**
 * Returns true in case the account is attacked, otherwise false
 */
boolean isAttacked()

isSpied()

/**
 * Returns true in case the account is spied, otherwise false
 */
boolean isSpied()

getLastUpdatedPlanet()

/**
 * Returns coordinates of last updated planet by OA
 */
Coordinates getLastUpdatedPlanet()

hasFreeFleetSlot()

/**
 * Returns true in case of there is free slots, otherwise false
 */
boolean hasFreeFleetSlot()

hasAvailableFleetSlot()

/**
 * Returns true in case of there is available fleet slots(= free slots - reserved slots by user), otherwise false
 */
boolean hasAvailableFleetSlot()

hasAvailableExpeditionFleetSlot()

/**
 * Returns true in case of there is available expedition slots(= free slots - reserved slots by user), otherwise false
 */
boolean hasAvailableExpeditionFleetSlot()

hasFreeExpeditionFleetSlot()

/**
 * Returns true in case of there is free expedition fleet slots, otherwise false
 */
boolean hasFreeExpeditionFleetSlot()

getFleetSlotsInUse()

/**
 * Returns the number of used slots, between 0 and getFleetSlotsMax()
 */
int getFleetSlotsInUse()

getFleetSlotsMax()

/**
 * Returns the max. number fleet slots
 */
int getFleetSlotsMax()

getFleetSlotsReserved()

/**
 * Returns the number of reserved slots by user
 */
int getFleetSlotsReserved()

setFleetSlotsReserved()

/**
 * Sets the number of reserved slots
 */
void setFleetSlotsReserved(Integer fleetSlotsReserved)

/**
 * Sets the number of reserved slots and enables/disables the algorithm of maximization of fleet slots usage
 */
void setFleetSlotsReserved(Integer fleetSlotsReserved, Boolean disableMaxSlotUsage)

enableOA()

/**
 * Enables communications with OGame Server
 */
void enableOA()

disableOA()

/**
 * Disables communications with OGame Server
 */
void disableOA()

isOAEnabled()

/**
 * Returns true in case OA is enabled, otherwise false
 */
boolean isOAEnabled()

setFarmingBotPlanet()

/**
 * Sets Farming bot parameters: planet name/coordinates, from solar system, to solar system
 */
void setFarmingBotPlanet(Object fromPlanet, Integer Integer from, Integer to)

/**
 * Sets Farming bot parameters: planet name/coordinates, target galaxy, from solar system, to solar system
 */
void setFarmingBotPlanet(Object fromPlanet, Integer galaxy, Integer from, Integer to)

startFarmingBot()

/**
 * Starts Farming Bot
 */
void startFarmingBot()

/**
 * Starts Farming Bot: fast farming true/false
 */
void startFarmingBot(Boolean FastFarming)

stopFarmingBot()

/**
 * Stops Farming Bot
 */
void stopFarmingBot()

isRunningFarmingBot()

/**
 * Returns true in case Farming Bot is running, otherwise false
 */
boolean isRunningFarmingBot()

getPlanet()

/**
 * Returns PlayerPlanet, parameter: planet name, Coordinates type, coordinates string
 */
PlayerPlanet getPlanet(Object o)

getPlanetFromName()

/**
 * Returns PlayerPlanet, parameter: planet name
 */
PlayerPlanet getPlanetFromName(Object planetName)

getPlanetFromCoordinates()

/**
 * Returns PlayerPlanet, parameter: Coordinates type, coordinates string
 */
PlayerPlanet getPlanetFromCoordinates(Object coordString)

sendFleetResourcesSelector()

Note selector possible values: any combination of the next 3 letters: M, C, D

Sends a fleet with resources using resources selector

/**
 * Returns true in case fleet has been sent, otherwise false
 */
@Deprecated: Please, use upwards for any complex fleet sending/recalling OA fluid API: Fleet Builder
boolean sendFleetResourcesSelector(Object from, Object target, Byte mission, Integer speed, Object selector, Object ships)

Sends a fleet with resources: metal, crystal, deuterium

/**
 * Returns true in case fleet has been sent, otherwise false
 */
@Deprecated: Please, use upwards for any complex fleet sending/recalling OA fluid API: Fleet Builder
boolean sendFleetWithResources(Object from, Object target, Byte mission, Integer speed, Long metal, Long crystal, Long deuterium, Object ships)

sendFleet()

Sends a fleet without resources

/**
 * Returns true in case fleet has been sent, otherwise false
 */
@Deprecated: Please, use upwards for any complex fleet sending/recalling OA fluid API: Fleet Builder
boolean sendFleet(Object from, Object target, Byte mission, Integer speed, Object ships)

sendEspionageProbeFromGalaxyPage()

/**
 * Returns true in case espionage probe was successfully sent from near to target planet, otherwise false
 */
boolean sendEspionageProbeFromGalaxyPage(Object target)
/**
 * Returns true in case espionage probe was successfully sent from precise planet, otherwise false
 */
boolean sendEspionageProbeFromGalaxyPage(Object from, Object target)

addScheduledFleet()

/**
 * Adds a Scheduled Fleet: resources to transport 0/0/0, use free slots if needed, send when possible (slots/ships available)
 * params: from, toTarget, mission, speed, ships
 */
addScheduledFleet('[X:XX:X]', '[Y:YY:Y]', ATTACK_MISSION, 100, [SMALLCARGO: 5])
Note TODO: add example in Fleet Sending script

getEntityString()

/**
 * Returns human readable, in account's language
 * Entities are: buildings/techs/ships/defences
 */
String getEntityString(Object entity)
Example
// Print technologies
printOut "Techs:"
for(technology in technologies){
  if(getNumberOfTech(technology) != 0) printOut getEntityString(technology) + ": " + getNumberOfTech(technology)
}

getFleetInAir()

/**
 * Returns a list of FleetInMovement
 */
List<FleetInMovement> getFleetInAir()

getNumberOfShipsInAir()

/**
 * Returns the number of ships for 'entity', 'entity' should be a ship
 */
 int getNumberOfShipsInAir(Object entity, SendableFleet fleet)
Example
def i = 1
for(fleet in getFleetInAir()){
  printOut "Fleet entry: " + i
  printOut Time.getTime(fleet.time) + ": " + fleet.fromPlanet + " -> " + fleet.toPlanet + " : " + fleet.mission + ", type: " + fleet.type
  printOut "Fleet composition:"
  for(ship in ships){
    if(getNumberOfShipsInAir(ship, fleet.getEntities()) != 0) printOut getEntityString(ship) + ": " + getNumberOfShipsInAir(ship, fleet.getEntities())
  }
  printOut "Metal: " + fleet.getEntities().metal + ", Crystal: " + fleet.getEntities().crystal + ", Deuterium: " + fleet.getEntities().deuterium
  i++
}
if(i == 1) printOut "There are no fleets in air"

recallFleet()

/**
 * Recalls a fleet composed of 'ships', travelling 'from' to 'to'
 */
@Deprecated: Please, use upwards for any complex fleet sending/recalling OA fluid API: Fleet Builder
void recallFleet(Object from, Object to, Integer speed, Object ships)
void recallFleet(Object from, Object to, Integer speed, Object ships, Double when)

getTripDuration()

/**
 * Returns one way duration of the trip in seconds composed of 'ships', travelling 'from' to 'to'
 */
long getTripDuration(Object from, Object to, Integer speed, Object ships)

getTripConsumption()

/**
 * Returns one way consumption of the trip composed of 'ships', travelling 'from' to 'to'
 * To recall all/any ships travelling 'from' to 'to', put as ships: [:]
 */
long getTripConsumption(Object from, Object to, Integer speed, Object ships)

getFleetMaxTransportableWeigh()

/**
 * Returns the max transportable weigh for 'ships'
 */
long getFleetMaxTransportableWeigh(Object ships)

getPlanets()

/**
 * Returns a list of Planets
 */
List<PlayerPlanet> getPlanets()

getNumberOf()

/**
 * Returns the number of 'entity', 'entity' should be a technology
 */
int getNumberOf(Object entity)
/**
 * Returns the number of 'entity' on a 'planet'
 */
int getNumberOf(Object entity, Object coordString)

getNumberOfTech()

/**
 * Returns the number of 'entity', 'entity' should be a technology
 */
int getNumberOfTech(Object entity)

getPrice()

/**
 * Returns the price of 'entity' on a 'coordinates'
 */
Price getPrice(Object entity, Object coordinates)

canBuild()

/**
 * Returns true if you can build 'entity' on 'coordinates', otherwise false
 */
boolean canBuild(Object entity, Object coordinates)

isShipyardInProgress()

/**
 * Returns true if Shipyard is busy on 'coordinates', otherwise false
 */
boolean isShipyardInProgress(Object coordinates)

getShipyardOccupationTime()

/**
 * Returns 0 if Shipyard is not busy, otherwise the time left, in seconds, when Shipyard on 'coordinates' would be free again
 */
long getShipyardOccupationTime(Object coordinates)

isBuildingInProgress()

/**
 * Returns true if Building is on progress on 'coordinates', otherwise false
 */
boolean isBuildingInProgress(Object coordinates)

getBuildingOccupationTime()

/**
 * Returns 0 if Building is not in progress, otherwise the time left, in seconds, when Building on 'coordinates' would be free again
 */
long getBuildingOccupationTime(Object coordinates)

isTechnologyInProgress()

/**
 * Returns true if Research Lab is busy, otherwise false
 */
boolean isTechnologyInProgress()

getTechnologyOccupationTime()

/**
 * Returns 0 if Research Lab is not busy, otherwise the time left, in seconds, when Research Lab would be free again
 */
long getTechnologyOccupationTime()

investInBuildingTechnology()

/**
 * Returns true if investment in 'entity' (building/technology) on 'coordinates' was done, otherwise false
 */
boolean investInBuildingTechnology(Object entity, Object coordinates)

investInShipsDefences()

/**
 * Returns true if investment in ships_defs on 'coordinates' was done, otherwise false
 */
boolean investInShipsDefences(Map ships_defs, Object coordinates)

investIn()

/**
 * If obj is building/technology: Returns true if investment in 'entity' (building/technology) on 'coordinates' was done, otherwise false
 * If obj is Map<ships_defs, Integer>: Returns true if investment in ships_defs on 'coordinates' was done, otherwise false
 */
boolean investIn(Object obj, Object coordinates)

getBuildingQueue()

/**
 * Returns current Building/Techs Queue on 'coordinates' as a list of Investment
 */
List<Investment> getBuildingQueue(Object coordinates)

getShipyardQueue()

/**
 * Returns current Shipyard Queue on 'coordinates' as a list of InvestmentShipyard
 */
List<InvestmentShipyard> getShipyardQueue(Object coordinates)

getPlayers()

/**
 * Returns ALL players currently in OA Database
 */
List<OGamePlayerLiteType> getPlayers()

getHunterTargets()

/**
 * Returns players currently tracked by OA Hunter
 */
List<OGamePlayerLiteType> getHunterTargets()

getLastSpyReport()

/**
 * Returns from OA Database the last Spy Report for the target on 'coordinates', in case there is none, a null is returned
 */
SendableFleet getLastSpyReport(Object coordinates)

parseMessages()

/**
 * Returns true is OA has parsed OGame messages for new Spy Reports, otherwise false
 * 'search_spy_reports' should be a Map<coordinates, planet name> (/!\ you should know the planet name of the target you are spying)
 * Spy Reports themselves are dispatched to the listener SPY_REPORT, see addEventListener()
 */
boolean parseMessages(Map search_spy_reports)

startSyncGalaxyScan()

/**
 * Orders to scan galaxy:system
 */
void startSyncGalaxyScan(Integer galaxy, Integer system)

startAsyncGalaxyScan()

/**
 * Orders to scan galaxy from 'from' solar system to 'to' solar system. Not blocking call.
 */
void startAsyncGalaxyScan(Integer galaxy, Integer from, Integer to)

stopAsyncGalaxyScan()

/**
 * Stops started galaxy scan
 */
void stopAsyncGalaxyScan()

isRunningAsyncGalaxyScan()

/**
 * Returns true is galaxy scan is in progress, otherwise false
 */
boolean isRunningAsyncGalaxyScan()

updateEvents()

/**
 * Updates just OGame Event page and if needed your fleets in movement (for recall refs)
 * Fast call, maybe could be used for ninja technique implementation
 */
boolean updateEvents()

updatePlanet()

/**
 * Updates planet infos: OGame Event page, buildings levels, researches levels, ships/defs
 * Very slow call, use it if really necessary
 */
boolean updatePlanet(Object coordinates)

updatePlanetName()

/**
 * TODO: remove
 */
boolean updatePlanetName(Object planetName)

updatePlanetCoord()

/**
 * TODO: remove
 */
boolean updatePlanetCoord(Object coordString)

getDBConn()

/**
 * Returns SQL connection for OA Database
 */
java.sql.Connection getDBConn()

closeDBConn()

/**
 * Closes the SQL connection
 */
void closeDBConn(Connection conn)

TODO: Remove DB time manipulation, abstract it

getRealTime2DBSec()

/**
 * Transforms real time (in milliseconds, type long) to seconds registered in Database (truncated to limited value which can hold up to 3 years).
 */
long getRealTime2DBSec(long time)

getRealTimeFromDBSec()

/**
 *
 */
long getRealTimeFromDBSec(long time)

getRealTime2DBMs()

/**
 *
 */
long getRealTime2DBMs(long time)

getRealTimeFromDBMs()

/**
 *
 */
long getRealTimeFromDBMs(long time)

addPlayer2DB()

/**
 * Adds a Player to OA Database
 */
boolean addPlayer2DB(
		String playerName,
		Integer playerID,
		Short rank,
    	String allianceName,
		Short allianceRank,
		Integer allianceID,
		Short allianceMembers,
		String planetName,
		boolean isMoon,
		Byte galaxy,
		Short system,
		Byte planet,
		String status)

battleSim()

/**
 * Does a Battle Simulation
 * attacker/defender can be of type: Map<ships_defs, number>, entities (Planet, PlayerPlanet, SendableFleet, ConstructionMap, etc.)
 */
BattleSimResult battleSim(Object attacker, Object defender)

getInput()

/**
 * Shows a 'question' to the user and returns the user answer in String type
 */
String getInput(String question)

showAlert()

/**
 * Shows an alert with 'title' and core message of the 'alert'. User should acknowledge on that message: click on 'ok' button
 */
void showAlert(String title, String alert)

readFile()

/**
 * Reads a text file and returns content as a String
 */
String readFile(Object fileName)

writeFile()

/**
 * Writes 'input' String into a 'file' using UTF-8 encoding
 */
void writeFile(String file, String input)

/**
 * Writes 'input' String into a 'file' using 'encoding'
 */
void writeFile(String file, String input, String encoding)

appendToFile()

/**
 * Appends 'input' String to the 'file'
 */
void appendToFile(String file, String input)

enableFS()

/**
 * Enables built-in OA Fleet Saver
 */
void enableFS()

disableFS()

/**
 * Disables built-in OA Fleet Saver
 */
void disableFS()

isFSEnabled()

/**
 * Returns true if built-in OA Fleet Saver is enabled, otherwise false
 */
boolean isFSEnabled()

enableBuildingsTechnologiesBuilder()

/**
 * Enables built-in OA Buildings/Technologies Builder
 */
void enableBuildingsTechnologiesBuilder()

disableBuildingsTechnologiesBuilder()

/**
 * Disables built-in OA Buildings/Technologies Builder
 */
void disableBuildingsTechnologiesBuilder()

isBuildingsTechnologiesBuilderEnabled()

/**
 * Returns true if built-in OA Buildings/Technologies Builder is enabled, otherwise false
 */
boolean isBuildingsTechnologiesBuilderEnabled()

enableShipsDefensesBuilder()

/**
 * Enables built-in OA Ships/Defenses Builder
 */
void enableShipsDefensesBuilder()

disableShipsDefensesBuilder()

/**
 * Disables built-in OA Ships/Defenses Builder
 */
void disableShipsDefensesBuilder()

isShipsDefensesBuilderEnabled()

/**
 * Returns true if built-in OA Ships/Defenses Builder is enabled, otherwise false
 */
boolean isShipsDefensesBuilderEnabled()

enableRecycler()

/**
 * Enables built-in OA Recycler Builder
 */
void enableRecycler()

disableRecycler()

/**
 * Disables built-in OA Recycler Builder
 */
void disableRecycler()

isRecyclerEnabled()

/**
 * Returns true if built-in OA Recycler Builder is enabled, otherwise false
 */
boolean isRecyclerEnabled()

startScript()

/**
 * Starts a 'script'
 */
boolean startScript(String script)

stopScript()

/**
 * Stops a 'script'
 */
boolean stopScript(String script)

isScriptRunning()

/**
 * Returns true if 'script' is running, otherwise false
 */
boolean isScriptRunning(String script)

getThisScriptName()

/**
 * Returns current script name
 */
String getThisScriptName()

getAllScriptsNames()

/**
 * Returns a list of all scripts available to the user
 */
ArrayList<String> getAllScriptsNames()

sendSMS()

/**
 * Returns true if SMS via GMail service was sent, otherwise false
 */
boolean sendSMS(String gmailLogin, String gmailPassword, String message)

sendEmail()

/**
 * Returns true if Email via GMail service was sent, otherwise false
 */
boolean sendEmail(String emailTo, String emailFrom, String title, String message, String smtpServer, Integer smtpPort, String smtpLogin, String smtpPassword, boolean enableSTARTTLS)

stop()

/**
 * Stops current script
 */
void stop()

getTimestamp()

/**
 * Returns current timestamp
 */
long getTimestamp()

addEventListener()

/**
 * Registers a listener on a 'eventType'
 * 'eventType' can be: GALAXY_SCAN, SPY_REPORT, MESSAGE_HTML
 */
void addEventListener(Object eventType, Closure closure)

addCronExec()

/**
 * Schedules an execution via crontab expression
 * TODO: ref to crontab doc
 */
void addCronExec(String cron, Closure closure)

addIntervalExec()

/**
 * Schedules an execution on regular intervals
 */
void addIntervalExec(String interval, Closure closure)

addExecIn()

/**
 * Schedules an execution after 'execIn' seconds
 */
void addExecIn(String execIn, Closure closure)

/**
 * Schedules an execution after 'execIn' TimeDuration
 * TODO: ref to crontab doc
 */
void addExecIn(TimeDuration execIn, Closure closure)

addExecInMs()

/**
 * Schedules an execution after 'execIn' milliseconds
 */
void addExecInMs(String execIn, Closure closure)

pauseScriptWhenOADisabled()

/**
 * Invoke that method at the beginning of the script to pause automatically current script when OA is disabled/logged out
 */
void pauseScriptWhenOADisabled()

Examples

Below examples showing OA Scripting usage, all functions in previous section are cover by at least one usage example below. Following scripts can be imported in OA by clicking on: [Scripts] Tab → Update Built-in OA Scripts

Fleets in Air

// Let's print fleets in air with fleet composition
def printFleets = { ->
  def i = 1
  for (fleet in getFleetInAir()) {
    printOut "Fleet entry: " + i
    printOut Time.getTime(fleet.time) + ": " + fleet.fromPlanet + " -> " + fleet.toPlanet + " : " + fleet.mission + ", type: " + fleet.type
    printOut "Fleet composition:"
    for (ship in ships) {
      if (getNumberOfShipsInAir(ship, fleet.getEntities()) != 0) printOut getEntityString(ship) + ": " + getNumberOfShipsInAir(ship, fleet.getEntities())
    }
    printOut "Metal: " + fleet.getEntities().metal + ", Crystal: " + fleet.getEntities().crystal + ", Deuterium: " + fleet.getEntities().deuterium

    // Let's make a Battle Sim against incoming fleet, just for fun ;)
    //BattleSimResult bsr = battleSim transformOAFleetDefinition2Map(fleet.getEntities()), transformOAFleetDefinition2Map(getPlanets()[0].getEntities())
    //printOut "Attacker Victory: "+bsr.victory
    //printOut "Draws: "+bsr.draws
    //printOut "Attacker Defeat: "+bsr.defeat

    i++
  }
  if (i == 1) printOut "There are no fleets in air"
}

def printSlots = { ->
  printOut "Fleet slots: " + getFleetSlotsInUse() + "/" + getFleetSlotsMax()
  printOut "Has Available Fleet slots: " + hasAvailableFleetSlot()  // = free slots - slots reserved by the user
  printOut "Has Available Expedition Fleet slots: " + hasAvailableExpeditionFleetSlot()
  printOut "Has Free Fleet slots: " + hasFreeFleetSlot()
  printOut "Reserved Fleet Slots for Player: "+getFleetSlotsReserved()
  //setFleetSlotsReserved Reserved_Slots[, disableMaxSlotUsageOptimization]
  //setFleetSlotsReserved 1, true
}

// Let's print it continuously
while (true) {
  printFleets()
  printSlots()
  sleep 1000
  clearOut()
}

Planets Info

// Let's list your planets
def i = 1
for (planet in getPlanets()) {
  printOut "Planet: " + i
  printOut "Last updated: " + Time.getTime(timestamp - planet.getLastUpdateTime()) + ": " + planet.name + " " + planet.coordinates + "\t" +
           (planet.getMetal()+planet.getMetalDelta()) + "(+" + planet.getMetalProduction() + "h)\t" +
           (planet.getCrystal()+planet.getCrystalDelta()) + "(+" + planet.getCrystalProduction() + "h)\t" +
           (planet.getDeuterium()+planet.getDeuteriumDelta()) + "(+" + planet.getDeuteriumProduction() + "h)\t" +
           "(" + planet.getEnergyUsage() + "/" + planet.getEnergyTotal() + ")"
  i++
}
printOut ""

// What is the latest updated Planet by OA ?
printOut "The latest updated Planet by OA is "+getLastUpdatedPlanet()

// The following lists are build-in, no need to define them, they are globally accessible
// def ships = [SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER]
// def defences = [ROCKETLAUNCHER, LIGHTLASER, HEAVYLASER, GAUSSCANNON, IONCANNON, PLASMATURRET, SMALLSHIELDDOME, LARGESHIELDDOME, ANTIBALLISTICMISSILE, INTERPLANETARYMISSILE]
// def buildings = [METALMINE, CRYSTALMINE, DEUTERIUMSYNTHESIZER, SOLARPLANT, FUSIONREACTOR, ROBOTICSFACTORY, NANITEFACTORY, SHIPYARD, METALSTORAGE, CRYSTALSTORAGE, DEUTERIUMTANK, METALDEN, CRYSTALDEN, DEUTERIUMDEN, RESEARCHLAB, TERRAFORMER, ALLIANCEDEPOT, LUNARBASE, SENSORPHALANX, JUMPGATE, MISSILESILO, SOLARSATELLITE]
// def technologies = [ESPIONAGETECHNOLOGY, COMPUTERTECHNOLOGY, WEAPONSTECHNOLOGY, SHIELDINGTECHNOLOGY, ARMOURTECHNOLOGY, ENERGYTECHNOLOGY, HYPERSPACETECHNOLOGY, COMBUSTIONDRIVE, IMPULSEDRIVE, HYPERSPACEDRIVE, LASERTECHNOLOGY, IONTECHNOLOGY, PLASMATECHNOLOGY, INTERGALACTICRESEARCHNETWORK, GRAVITONTECHNOLOGY, ASTROPHYSICS]
// Moreover, in case you want join severals lists, like having all buildings and technologies in one list, just do a sum, e.g buildings + technologies

// More complex: with buildings/ships/defs/techs
// Let's list your planets
i = 1;
for (p in getPlanets()) {
  printOut "Planet: " + i
  printOut "Last updated: " + Time.getTime(timestamp - p.getLastUpdateTime()) + ": " + p.name + " " + p.coordinates + "\t" + Numbers.getNumber(p.getMetal()) + "(+" + p.getMetalProduction() + "h)\t" + Numbers.getNumber(p.getCrystal()) + "(+" + p.getCrystalProduction() + "h)\t" + Numbers.getNumber(p.getDeuterium()) + "(+" + p.getDeuteriumProduction() + "h)\t(" + p.getEnergyUsage() + "/" + p.getEnergyTotal() + ")"
  for (ship in ships) {
    if (getNumberOf(ship, p.coordinates) != 0) printOut getEntityString(ship) + ": " + getNumberOf(ship, p.coordinates)
  }
  for (defence in defences) {
    if (getNumberOf(defence, p.coordinates) != 0) printOut getEntityString(defence) + ": " + getNumberOf(defence, p.coordinates)
  }
  for (building in buildings) {
    if (getNumberOf(building, p.coordinates) != 0) printOut getEntityString(building) + ": " + getNumberOf(building, p.coordinates)
  }
  i++
}

// Print technologies
printOut "Techs:"
for (technology in technologies) {
  if (getNumberOfTech(technology) != 0) printOut getEntityString(technology) + ": " + getNumberOfTech(technology)
}

// Getting first planet coordinates, first planet will be used as an example
def coord = getPlanets()[0].coordinates  //you can also use the planet name in all following calls, e.g getPlanets()[0].name
// Next level price
Price p = getPrice(METALMINE, coord)
// Print price
printOut "Next level of Metal Mine is " + (getNumberOf(METALMINE, coord) + 1) + ", price: " + p
// To access distinct price values
printOut "Distinct price values: " + p.getMetalValue() + ", " + p.getCrystalValue() + ", " + p.getDeutValue()
// Works too with buildings/techs/ships/defs
printOut "Price of DESTROYER is " +  getPrice(DESTROYER, coord)

// The following is to check do you have all needed buildings&techs to build a building/tech/ship/def & that this building can be built on that type of planet [Planet, Moon]
// Can you build a BATTLECRUISER ?
if (canBuild(BATTLECRUISER, coord)) printOut "Yes, you can build "+getEntityString(BATTLECRUISER)
else printOut "No, you cannot build "+getEntityString(BATTLECRUISER)
// Can you build a COMPUTER TECHNOLOGY ?
if (canBuild(COMPUTERTECHNOLOGY, coord)) printOut "Yes, you can build "+getEntityString(COMPUTERTECHNOLOGY)
else printOut "No, you cannot build "+getEntityString(COMPUTERTECHNOLOGY)
// Can you build a GRAVITON TECHNOLOGY ?
if (canBuild(GRAVITONTECHNOLOGY, coord)) printOut "Yes, you can build "+getEntityString(GRAVITONTECHNOLOGY)
else printOut "No, you cannot build "+getEntityString(GRAVITONTECHNOLOGY)

// For test, let's use your first planet
def planet = getPlanets()[0]  // some examples of planet definition: "[3:150:5]", "[3:150:5] Moon", "3:150:5"

// Get Occupation Time (in seconds) of Building, Research, Shipyard
if (isTechnologyInProgress()) printOut "Research Occupation Time (in seconds) : " + getTechnologyOccupationTime() + " sec"
if (isBuildingInProgress(planet)) printOut "Building Occupation Time (in seconds) : " + getBuildingOccupationTime(planet) + " sec"
if (isShipyardInProgress(planet)) printOut "Shipyard Occupation Time (in seconds) : " + getShipyardOccupationTime(planet) + " sec"

// Let's check which building can be built on your first planet: canBuild should be 'true' and getPrice should be bigger than resources on your planet
for (building in buildings) {
  if (//!isBuildingInProgress(planet) && !isTechnologyInProgress() &&
       canBuild(building, planet) &&
       (getPrice(building, planet).getMetalValue() <= planet.getMetal()) &&
       (getPrice(building, planet).getCrystalValue() <= planet.getCrystal()) &&
       (getPrice(building, planet).getDeutValue() <= planet.getDeuterium()))
  {
    printOut "You can build " + getEntityString(building) + " level " + (getNumberOf(building, planet) + 1)
    //you can also directly invest in it
    //def done = investInBuildingTechnology(building, planet)
    //if (done) printOut "Investment Done !"
    //else printOut "Investment Failed !"
  }
}

// Same as above for Shipyard
for (entity in ships+defences) {
  if (//!isShipyardInProgress(planet) &&
       canBuild(entity, planet) &&
       (getPrice(entity, planet).getMetalValue() <= planet.getMetal()) &&
       (getPrice(entity, planet).getCrystalValue() <= planet.getCrystal()) &&
       (getPrice(entity, planet).getDeutValue() <= planet.getDeuterium()))
  {
    printOut "You can build at least one " + getEntityString(entity)
  }
}

// Let's print current queue for OA Builder/Shipyard for the planet
printOut ""
def list = getBuildingQueue(planet)
i = 1
for (investment in list) {
  printOut "${i++}. " + getEntityString(investment) + " lvl " + (getNumberOf(investment, planet) + 1) + "\tCan build = " + canBuild(investment, planet) + "\tPrice: " + getPrice(investment, planet)
}

printOut ""
list = getShipyardQueue(planet)
i = 1
for (investment in list) {
  printOut "${i++}. " + getEntityString(investment)
  printOut "Is maintain quantity enabled: " + investment.isMaintainQuantityEnabled()
  printOut "Quatity to build: " + investment.getQuatity2Build()
  printOut "Quatity to build at once: " + investment.getQuatity2BuildAtOnce()
  printOut ""
}

Update Planet

// Planet Update is slow as it updates: resources & fleets & fleet movements
def planet = getPlanets()[0]  //Let's take your first planet
// In case there are several Planets having the same Name, the following call can be ambiguous
if ( updatePlanetName(planet.name) ) printOut "update 1: ok"
else printOut "update 1: failed"
// Coordinates are not ambiguous: [X:YY:Z] or X:YY:Z, for Moons: [X:YY:Z] Moon
if ( updatePlanetCoord(planet.coordinates) ) printOut "update 2: ok"
else printOut "update 2: failed"
// Better solution: You can also use updatePlanet() function with Planet name or Coordinates, OA resolves it first as Planet name, if it doesn't work as Coordinates
// calling  updatePlanet(planet)  equals  updatePlanet(planet.coordinates)  equals  updatePlanetCoord(planet.coordinates)
if ( updatePlanet(planet.name) ) printOut "update 3: ok"
else printOut "update 3: failed"

// Build a building/technology on a planet, check 'Planets Info' script for a more complete example
//def done = investInBuildingTechnology(building, planet)
//if (done) printOut "Investment Done !"
//else printOut "Investment Failed !"

// Build ships/defences on a planet
// Example: build 5 ESPIONAGEPROBE, 30 SMALLCARGO, 10 LARGECARGO on your first planet
// if at least one ship/defences has been built, the function returns 'true'
/*def done = investInShipsDefences(
  [ ESPIONAGEPROBE: 5,
    SMALLCARGO: 30,
    LARGECARGO: 10,
    ROCKETLAUNCHER: 10 ],
  getPlanets()[0]
)
if (done) printOut "Investment Done !"
else printOut "Investment Failed !"*/

// Instead of investInBuildingTechnology/investInShipsDefences you can use investIn function

Fleet Sending

// Let's try to send out a fleet: one Small Cargo and one Espionage Probe
def planetName = getPlanets()[0].name
def sent = sendFleet(  // sendFleet function returns a boolean [true,false], Groovy infers the type of 'sent' variable at compile time
  planetName,  // Planet Name: "Main Planet"
  getPlanets()[1].coordinates,      // Target Coordinates (yours or enemy), e.g valid values: "1:1:1", "[1:1:1]", "[1:1:1] M..." (the last one is for Moon coordinates)
  TRANSPORT_MISSION,  // Missions: ATTACK_MISSION, TRANSPORT_MISSION, DEPLOYMENT_MISSION, ESPIONAGE_MISSION, COLONIZATION_MISSION, RECYCLE_MISSION, EXPEDITION_MISSION
  100,          // Speed [10-100] means 10%-100%, speed step is 10% => 10,20,30,...,100
  [SMALLCARGO : 1, ESPIONAGEPROBE : 1]  // SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER
);
if (sent) printOut "Fleet sent !"
else printOut "Fleet sent failed !"

// Coordinates creation/manipulation
printOut new Coordinates((byte)1,(short)2,(byte)3,MOON_TYPE)  //PLANET_TYPE, DEBRIS_TYPE, MOON_TYPE
// Parse a coordinates from String presentation
def coord = Coordinates.getCoordinates("[5:5:10] SuperMoon...")  //valid values: "1:1:1", "[1:1:1]", "[1:1:1] Anything_is_treated_as_Moon" (the last one is for Moon coordinates)
printOut coord
printOut "Is coordinates ? "+Coordinates.isCoordinates("[5:5:10] SuperMoon...")
printOut new Coordinates(coord.galaxy,coord.system,coord.planet,PLANET_TYPE)
printOut "["+coord.galaxy+":"+coord.system+":"+coord.planet+"]"+(coord.isMoon() ? " Moon" : "")

// More fleet sending

// Fast sending of Espionage Probes from Galaxy page to Coordinates:
//- you shell configure OGame option 'Number of espionage probes:' to something different to 0
//- and set the same value in OA -> Farming Bot: 'Probes to use at start'
// from_planet is decided internally as been the nearest to to_target
//sendEspionageProbeFromGalaxyPage(to_target)
// in next call from_planet is set manually
//sendEspionageProbeFromGalaxyPage(from_planet, to_target)
// After sending it's updates the planet only if there no more probes on the planet

// To send all ships use [:] for ships
//sendFleet planetName,"5:35:5",FleetMission.TRANSPORT_MISSION,100,[:]

// To send ships with resources
//sendFleetWithResources planetName,"5:35:5",FleetMission.TRANSPORT_MISSION,100,10000,10000,0,[:]

// To send ships with on planet resources
//def planetName = "Main Planet"  //Planet Name: "Main Planet"
//sendFleetWithResources planetName,"5:35:5",FleetMission.TRANSPORT_MISSION,100,getPlanet(planetName).getMetal(),getPlanet(planetName).getCrystal(),getPlanet(planetName).getDeuterium(),[:]

// To send ships using a Resources Selector: DCM, D=deuterium, C=crystal, M=metal
//sendFleetResourcesSelector planetName, "5:35:5", FleetMission.TRANSPORT_MISSION, 100, "DCM", [:]

// Let's get the trip Duration&Consumption from your first planet to the second one, one small cargo
def from = getPlanets()[0].coordinates
def to = getPlanets()[1].coordinates    //coordinates could be a String value, valid values: "1:1:1", "[1:1:1]", "[1:1:1] M..."
def speed = 100
printOut "Trip Duration: " + getTripDuration( from, to, speed, [SMALLCARGO : 1] ) + " seconds"
printOut "Trip Consumption: " + getTripConsumption( from, to, speed, [SMALLCARGO : 1] ) + " deuterium"
printOut "Max Transportable Weigh: " + getFleetMaxTransportableWeigh( [SMALLCARGO : 1] ) + " resources"

// Let's play with explicite fleet recalling
// Sending a fleet from first planet to the second one: one small cargo without resources, speed 10%, transport mission
if ( sendFleet( getPlanets()[0], getPlanets()[1], FleetMission.TRANSPORT_MISSION, 10, [SMALLCARGO : 1] ) ) {
  printOut "Fleet sent"
  // Getting fleet duration
  printOut "Trip Duration: " + getTripDuration( getPlanets()[0].coordinates, getPlanets()[1].coordinates, 10, [SMALLCARGO : 1] ) + " seconds"
  printOut "Trip Consumption: " + getTripConsumption( getPlanets()[0].coordinates, getPlanets()[1].coordinates, 10, [SMALLCARGO : 1] ) + " deuterium"
  printOut "Max Transportable Weigh: " + getFleetMaxTransportableWeigh( [SMALLCARGO : 1] ) + " resources"
  // Wait a little: 30 secs
  sleep 30000
  // Recalling it: the following function places a recall order in OA, which should be executed shortly after
  //recallFleet from coordinates, to coordinates, speed[10-100], ships list [, when]
  recallFleet getPlanets()[0].coordinates, getPlanets()[1].coordinates, 10, [SMALLCARGO : 1]
  printOut "Recall done."
  // In order to recall regardingless what are the ships in the fleet composition, use [:] as ships list, so the recall order will be coordinates based
  //recallFleet getPlanets()[0].coordinates, getPlanets()[1].coordinates, 10, [:]
  // To place a recall order in future, at 50% of the trip
  //recallFleet getPlanets()[0].coordinates, getPlanets()[1].coordinates, 10, [:], 50.0
} else printOut "Fleet Sending failed !"

Battle Simulator

// Let's play with the Battle Simulator

// Internally defined variables, these objects are implicit in the scope of the script
// def ships = [SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER]
// def defences = [ROCKETLAUNCHER, LIGHTLASER, HEAVYLASER, GAUSSCANNON, IONCANNON, PLASMATURRET, SMALLSHIELDDOME, LARGESHIELDDOME, ANTIBALLISTICMISSILE, INTERPLANETARYMISSILE]
// def buildings = [METALMINE, CRYSTALMINE, DEUTERIUMSYNTHESIZER, SOLARPLANT, FUSIONREACTOR, ROBOTICSFACTORY, NANITEFACTORY, SHIPYARD, METALSTORAGE, CRYSTALSTORAGE, DEUTERIUMTANK, METALDEN, CRYSTALDEN, DEUTERIUMDEN, RESEARCHLAB, TERRAFORMER, ALLIANCEDEPOT, LUNARBASE, SENSORPHALANX, JUMPGATE, MISSILESILO, SOLARSATELLITE]
// def technologies = [ESPIONAGETECHNOLOGY, COMPUTERTECHNOLOGY, WEAPONSTECHNOLOGY, SHIELDINGTECHNOLOGY, ARMOURTECHNOLOGY, ENERGYTECHNOLOGY, HYPERSPACETECHNOLOGY, COMBUSTIONDRIVE, IMPULSEDRIVE, HYPERSPACEDRIVE, LASERTECHNOLOGY, IONTECHNOLOGY, PLASMATECHNOLOGY, INTERGALACTICRESEARCHNETWORK, GRAVITONTECHNOLOGY, ASTROPHYSICS]

def attacker =
[
  SMALLCARGO : 0,
  LARGECARGO : 200,
  LIGHTFIGHTER : 0,
  HEAVYFIGHTER : 0,
  // attacker can only have ships which can fly, the next line will be ignored
  SOLARSATELLITE: 1,
  // attacker can only have ships, the next line will be ignored
  ROCKETLAUNCHER: 1,
  // you can specify technologies, it will be used during the simulation
  WEAPONSTECHNOLOGY : 1
]

// Defender can have everything: ships/buildings/defences/technologies
def defender =
[
  SMALLCARGO : 0,
  LARGECARGO : 0,
  LIGHTFIGHTER : 10,
  HEAVYFIGHTER : 30,

  ROCKETLAUNCHER: 5,

  SHIELDINGTECHNOLOGY: 2
]

try {
  //Battle Simulation, blocking call
  BattleSimResult bsr = battleSim attacker, defender
  //next one is battle sim with first incoming fleet against defences on your first planet
  //battleSim getFleetInAir()[0].getEntities(), getPlanets()[0].getEntities()
  //same as before, but we use a Map representation
  //BattleSimResult bsr = battleSim( ['CRUISER': 10], ['LIGHTFIGHTER': 100] )
  printOut "Attacker Victory: "+bsr.victory
  printOut "Draws: "+bsr.draws
  printOut "Attacker Defeat: "+bsr.defeat
  printOut ""
  printOut "Attacker Losses Price: "+bsr.attackerLossesPrice  //returned type is Price: bsr.attackerLossesPrice.getMetalValue(), bsr.attackerLossesPrice.getCrystalValue(), bsr.attackerLossesPrice.getDeutValue()
  printOut "Attacker Total Losses: "+bsr.attackerTotalLosses
  printOut ""
  printOut "Defender Losses Price: "+bsr.defenderLossesPrice
  printOut "Defender Total Losses: "+bsr.defenderTotalLosses
  printOut ""
  // Note: For the ratio metal/crystal transformation to debris, setted values in Advanced Options are used
  printOut "Metal Debris: "+bsr.metalDebris
  printOut "Crystal Debris: "+bsr.crystalDebris
  printOut ""
  printOut "Needed Recyclers: "+bsr.neededRecyclers
  printOut ""
  printOut "Attacker Remaining ships:"
  for (entity in ships) {
    if (bsr.attackerEntities.get(entity).getValue() != 0) printOut getEntityString(entity)+" = "+bsr.attackerEntities.get(entity).getValue()
  }
  printOut ""
  printOut "Defender Remaining ships/defences:"
  for (entity in ships+defences) {
    if (bsr.defenderEntities.get(entity).getValue() != 0) printOut getEntityString(entity)+" = "+bsr.defenderEntities.get(entity).getValue()
  }
} catch (BattleSimEmptyAttackerException bseae) {
  printOut "Attacker has not ships :(, simulation is aborted"
} catch (BattleSimEmptyDefenderException bsede) {
  printOut "Defender has not ships/defenses :(, simulation is aborted"
}

Fleet Saver Alt

// Let's make an alternative Fleet Saver
// This script is provided as an example of OA API usage, should be used with cation on a real account
pauseScriptWhenOADisabled()  //pause this Script when OA is Disabled or is not Logged-in

// Config
def fsTime = 15  //mins
def fsRadius = 50  //solar systems
def sendOneSpyOnEnemy = YES  //might help to show that you are see what is going on...

// In case there is some time ahead to FS and you cannot transport all resources, then try to invest
def fsInvestInBuildingsIfPossible = [
  TERRAFORMER,
  SOLARPLANT,
  CRYSTALMINE,
  METALMINE,
  //DEUTERIUMSYNTHESIZER,
  //FUSIONREACTOR,
  NANITEFACTORY,
  ROBOTICSFACTORY,
  SHIPYARD,
  METALSTORAGE,
  CRYSTALSTORAGE,
  //DEUTERIUMTANK,
  //METALDEN,
  //CRYSTALDEN,
  //DEUTERIUMDEN,
  RESEARCHLAB,
  ALLIANCEDEPOT,
  LUNARBASE,
  SENSORPHALANX,
  JUMPGATE,
  MISSILESILO
]
def fsInvestInTechnologiesIfPossible = [
  GRAVITONTECHNOLOGY,
  INTERGALACTICRESEARCHNETWORK,
  ASTROPHYSICS,
  ESPIONAGETECHNOLOGY,
  COMPUTERTECHNOLOGY,
  COMBUSTIONDRIVE,
  IMPULSEDRIVE,
  HYPERSPACEDRIVE,
  ARMOURTECHNOLOGY, WEAPONSTECHNOLOGY, SHIELDINGTECHNOLOGY,
  ENERGYTECHNOLOGY,
  HYPERSPACETECHNOLOGY,
  PLASMATECHNOLOGY,
  IONTECHNOLOGY,
  LASERTECHNOLOGY
]
def fsInvestInShipsDefences = [
  DEATHSTAR: 1,
  BATTLESHIP: 5,
  BATTLECRUISER: 5,
  SMALLCARGO: 10,
  LARGECARGO: 5,
  ESPIONAGEPROBE: 5,
  //LIGHTFIGHTER: 10,

  PLASMATURRET: 5,
  GAUSSCANNON: 3,
  IONCANNON: 3,
  LIGHTLASER: 10,
  ROCKETLAUNCHER: 10
  //HEAVYLASER: 10,
]

def needFS = { fleet, planet ->
  if (fleet.getEntities().hasShips() && planet.getEntities().hasShips()) {
    try {
      // Make a Battle Sim
      BattleSimResult bsr = battleSim fleet.getEntities(), planet.getEntities()
      if (bsr.defenderTotalLosses < 1) return false  //Incoming fleet looses, bypass FS ...
      printOut "Defender Losses Price: "+bsr.defenderLossesPrice
      printOut "Defender Total Losses: "+bsr.defenderTotalLosses
      printOut "Metal Debris: "+bsr.metalDebris
      printOut "Crystal Debris: "+bsr.crystalDebris
    } catch (BattleSimEmptyAttackerException bseae) {
      return true
    } catch (BattleSimEmptyDefenderException bsede) {
      return false
    }
  }
  return true
}
def burnResources = { attackedPlt ->
  return getFleetMaxTransportableWeigh(attackedPlt.getEntities()) < attackedPlt.getMetal()+attackedPlt.getMetalDelta() + attackedPlt.getCrystal()+attackedPlt.getCrystalDelta() + attackedPlt.getDeuterium()+attackedPlt.getDeuteriumDelta()
}

def spySent = false
// Main FS loop
while (true) {
  if ((isAttacked() || isSpied())) {
    for (f in getFleetInAir()) {
      if ((f.type == "enemyflight" || f.type == "enemyflightEspionage") && Coordinates.isCoordinates(f.toPlanet) && needFS(f, getPlanetFromCoordinates(f.toPlanet))) {  //Enemy fleet, attacked planet has ships, battle is not in your advantage
        def attackedPlt = getPlanetFromCoordinates(f.toPlanet)
        if (f.time > fsTime*60*1000 && f.time < 2*fsTime*60*1000) {  //we have some time, beetween fsTime and fsTime * 2 => try to burn/invest some resources if needed
          if (sendOneSpyOnEnemy && !spySent && (getNumberOf('ESPIONAGEPROBE', attackedPlt.coordinates) != 0) && Coordinates.isCoordinates(f.fromPlanet)) {
            spySent = sendFleet( attackedPlt.coordinates, f.fromPlanet, FleetMission.ESPIONAGE_MISSION, 50, [ESPIONAGEPROBE: 1] )
          }
          if (burnResources(attackedPlt)) {
            // You cannot transport all resource during FS => burn them
            for (invest in fsInvestInBuildingsIfPossible) {
              if (!isBuildingInProgress(attackedPlt) &&
                   canBuild(invest, attackedPlt) &&
                   (getPrice(invest, attackedPlt).getMetalValue() < attackedPlt.getMetal()) &&
                   (getPrice(invest, attackedPlt).getCrystalValue() < attackedPlt.getCrystal()) &&
                   (getPrice(invest, attackedPlt).getDeutValue() < attackedPlt.getDeuterium()))
              {
                if ( investInBuildingTechnology(invest, attackedPlt) ) {
                  printOut "Investment in " + invest + " is done on planet " + attackedPlt
                  break;  //Breaking the for loop in order to reevaluate the condition to burn resources
                } else printOut "Investment in " + invest + " Failed !"
              }
            }
          }
          if (burnResources(attackedPlt)) {
            for (invest in fsInvestInTechnologiesIfPossible) {
              if (!isTechnologyInProgress() &&
                   canBuild(invest, attackedPlt) &&
                   (getPrice(invest, attackedPlt).getMetalValue() < attackedPlt.getMetal()) &&
                   (getPrice(invest, attackedPlt).getCrystalValue() < attackedPlt.getCrystal()) &&
                   (getPrice(invest, attackedPlt).getDeutValue() < attackedPlt.getDeuterium()))
              {
                if ( investInBuildingTechnology(invest, attackedPlt) ) {
                  printOut "Investment in " + invest + " is done on planet " + attackedPlt
                  break;  //Breaking the for loop in order to reevaluate the condition to burn resources
                } else printOut "Investment in " + invest + " Failed !"
              }
            }
          }
          if (burnResources(attackedPlt)) {
            if ( investInShipsDefences(fsInvestInShipsDefences, attackedPlt) ) {
              printOut "Ships/Defences Investment is done on planet " + attackedPlt
            } else printOut "Ships/Defences Investment Failed !"
          }
        } else if (f.time < fsTime*60*1000 && attackedPlt.getEntities().hasShips()) {  //Time to FS and attacked planet still has ships
          printOut "Starting FS"
          def safePlanet = ""

          // Start Galaxy Scan
          startAsyncGalaxyScan attackedPlt.coordinates.galaxy, attackedPlt.coordinates.system-10, attackedPlt.coordinates.system+10

          // Find some debris
          Connection conn = getDBConn()
          Statement statement = conn.createStatement();
          ResultSet result = statement.executeQuery("SELECT * FROM PLANET, GALAXY_SCAN WHERE PLANET.Galaxy=GALAXY_SCAN.Galaxy AND PLANET.System=GALAXY_SCAN.System AND PLANET.Planet=GALAXY_SCAN.Planet AND PLANET.IsMoon=GALAXY_SCAN.IsMoon AND PLANET.Galaxy="+attackedPlt.coordinates.galaxy+" AND PLANET.System>="+(attackedPlt.coordinates.system-fsRadius)+" AND PLANET.System<="+(attackedPlt.coordinates.system+fsRadius)+" AND (DebrisMetal>0 OR DebrisCrystal>0) AND ScanTimeSec>"+getRealTime2DBSec(timestamp-3600*1000)+" order by ScanTimeSec");
          def minDuration = Long.MAX_VALUE;
          while (result.next()) {
            printOut result.getInt("Galaxy") + ":" + result.getInt("System") + ":" + result.getInt("Planet") + " " + result.getBoolean("IsMoon") + " " + result.getInt("DebrisMetal") + " " + result.getInt("DebrisCrystal")
            if (getTripDuration( attackedPlt.coordinates, result.getInt("Galaxy") + ":" + result.getInt("System") + ":" + result.getInt("Planet"), 10, [:] ) < minDuration) {
              safePlanet = result.getInt("Galaxy") + ":" + result.getInt("System") + ":" + result.getInt("Planet")
              minDuration = getTripDuration( attackedPlt.coordinates, safePlanet, 10, [:] )
            }
          }
          statement.close()
          result.close()
          closeDBConn(conn)

          // Try to save on Debris if the planet has some recyclers
          if (getNumberOf('RECYCLER', attackedPlt.coordinates) != 0 && !safePlanet.isEmpty()) {
            printOut "Safe Planet is " + safePlanet
            // Send a all your ships on debris
            if (sendFleetResourcesSelector( attackedPlt.coordinates, safePlanet, FleetMission.RECYCLE_MISSION, 10, "DCM", [:] ) ) {
              recallFleet attackedPlt.coordinates, safePlanet, 10, [:], 0.0 //Recall time cannot be calculated as OA doesn't know about the exact fleet composition
              printOut "FS done."
              break
            } else printOut "Failed to send FS fleet: RECYCLE"
          }
          // Try to save with attack/transport mission
          if (attackedPlt.getEntities().hasShips() && !safePlanet.isEmpty()) {
            printOut "Safe Planet is " + safePlanet
            // Send a all your ships as an attack
            if ( sendFleetResourcesSelector( attackedPlt.coordinates, safePlanet, FleetMission.ATTACK_MISSION, 10, "DCM", [:] ) ) {
              recallFleet attackedPlt.coordinates, safePlanet, 10, [:], 0.0 //Recall time cannot be calculated as OA doesn't know about the exact fleet composition
              printOut "FS done."
              break
            } else {
              printOut "Failed to send FS fleet: ATTACK"
              // Send a all your ships as a transport
              if ( sendFleetResourcesSelector( attackedPlt.coordinates, safePlanet, FleetMission.TRANSPORT_MISSION, 10, "DCM", [:] ) ) {
                recallFleet attackedPlt.coordinates, safePlanet, 10, [:], 0.0 //Recall time cannot be calculated as OA doesn't know about the exact fleet composition
                printOut "FS done."
                break
              } else {
                printOut "Failed to send FS fleet: TRANSPORT"
                if (Coordinates.isCoordinates(f.fromPlanet) && sendFleetResourcesSelector( attackedPlt.coordinates, Coordinates.parseCoordinates(f.fromPlanet), FleetMission.ATTACK_MISSION, 10, "DCM", [:] )) {
                  recallFleet attackedPlt.coordinates, Coordinates.parseCoordinates(f.fromPlanet), 10, [:], 0.0 //Recall time cannot be calculated as OA doesn't know about the exact fleet composition
                  printOut "FS done."
                  break
                } else printOut "Failed to send FS fleet: ATTACK on enemy"
              }
            }
          }
        }
      }
    }
  }
  sleep 1000
}

Farming Bot

def waitTillBotIsWorking = { ->
  sleep 30000  // sleep 1/2min to allow the bot to start(mainly in case of startFastFarmingBot())
  while (isJobCurrentlyRunning() || isRunningAsyncGalaxyScan() || !hasAvailableFleetSlot()) {
    sleep 5000
  }
  // more strict condition, waits till some fleets are back
  while (isJobCurrentlyRunning() || isRunningAsyncGalaxyScan() || !hasAvailableFleetSlot() || (getFleetSlotsInUse() > getFleetSlotsMax()/2)) {
    sleep 5000
  }
  // condition for Fast Farming Bot
  while (isRunningFarmingBot() || !hasAvailableFleetSlot() || (getFleetSlotsInUse() > getFleetSlotsMax()/2)) {
    sleep 5000
  }
}

// Farming Bot from some of your planets
def radius = 50    // any radius you want from your planet
def planetNames = ["Main Planet","Another Planet"]  // Use your planet names
for (planetName in planetNames) {
  Planet p = getPlanet(planetName);
  stopFarmingBot()  // just in case of

  //setFarmingBotPlanet coordinates/planet name, [galaxy,] from solar system(min. 1), to solar system(max. 499)
  //setFarmingBotPlanet p.name, p.coordinates.galaxy, p.coordinates.system-radius, p.coordinates.system+radius
  setFarmingBotPlanet p.name, p.coordinates.system-radius, p.coordinates.system+radius
  startFarmingBot()   //= startFarmingBot(false)
  //startFarmingBot(true) or startFarmingBot(FastFarming=true) are equivalent to startFastFarmingBot(), means without galaxy scan
  waitTillBotIsWorking()
}

// Farming Bot from all Planets, starting from your first planet
/*for (p in getPlanets()) {
  stopFarmingBot()  // just in case of

  setFarmingBotPlanet p.name, p.coordinates.system-radius, p.coordinates.system+radius
  startFarmingBot()  // There is also startFastFarmingBot() = w/o galaxy scan
  waitTillBotIsWorking()
}*/

Hunter

// This script is provided as an example of OA API usage:

/*
*  It selects all known players in OA Database, and sends espionage probes 'spy2send' on planets/moons near 'fromPlanet'
*  than it waits a probes trip period and asks OA to parse messages.
*  When spy report is parsed, it's dispatched to the listener for a treatment...
*/
// -------- Config starts --------
def fromPlanet = Coordinates.getCoordinates("[4:328:7]")
def radius = 20
def speed = 100
def spy2send = 2
// -------- Config ends ----------

final def mapCoordsPlanetName = [:]  // 'final' in order to get it available to other threads

addEventListener SPY_REPORT,
  { scan ->
    printOut "New Spy Report:"
    printOut scan.playerName + '\t' +
      scan.playerID + '\t' +
      scan.planetName + '\t' +
      scan.coordinates + '\t' +
      scan.globalActivityStatus + '\t' + // compare it to : ACTIVE_STATUS, INACTIVE_STATUS, LONGINACTIVE_STATUS, NOOB_STATUS, VACATION_STATUS, STRONG_STATUS, BANNED_STATUS, UNKNOWN_STATUS
      scan.rank + '\t' +
      scan.allianceName + '\t' +
      scan.allianceRank + '\t' +
      scan.allianceID + '\t' +
      scan.metal + '\t' +
      scan.crystal + '\t' +
      scan.deuterium + '\t' +
      scan.energy + '\t' +
      scan.isProbeLost + '\t' +
      scan.shipsDetected + '\t' +
      scan.defensesDetected + '\t' +
      scan.buildingsDetected + '\t' +
      scan.technologiesDetected
    for (entity in ships+defences+buildings+technologies) {
      if (scan.entities.get(entity).getValue() != 0) printOut getEntityString(entity)+" = "+scan.entities.get(entity).getValue()
    }
    if (mapCoordsPlanetName.get(scan.coordinates.toString()) && mapCoordsPlanetName.remove(scan.coordinates.toString())) {
      printOut "Removed "+scan.coordinates+" from spy reports to parse"
      try {
        //Battle Simulation, blocking call
        BattleSimResult bsr = battleSim getPlanet(fromPlanet), scan.entities
        printOut "Attacker Victory: "+bsr.victory
        printOut "Draws: "+bsr.draws
        printOut "Attacker Defeat: "+bsr.defeat
        // Decide to attack or not ?
        // ...
      } catch (BattleSimEmptyAttackerException bseae) {
        printOut "Attacker has not ships :(, simulation is aborted"
      } catch (BattleSimEmptyDefenderException bsede) {
        printOut "Defender has not ships/defenses :(, simulation is aborted"
        // rip him :)
        // ...
      }
    }
  }

Thread.start {
  for (player in getPlayers()) {
    if (player.galaxy==fromPlanet.galaxy && player.system>=fromPlanet.system-radius && player.system<=fromPlanet.system+radius && player.rank>=100 &&
      (player.globalActivityStatus == ACTIVE_STATUS || player.globalActivityStatus == INACTIVE_STATUS || player.globalActivityStatus == LONGINACTIVE_STATUS))
    {
      printOut "Sending fleet: "+player.getCoordinates()
      def sent = sendFleet(  //sendFleet function returns a boolean [true,false], Groovy infers the type of 'sent' variable at compile time
        fromPlanet,  //Planet Name: "Main Planet"
        player.getCoordinates(),      //Target Coordinates (yours or enemy), e.g valid values: "1:1:1", "[1:1:1]", "[1:1:1] M..." (the last one is for Moon coordinates)
        ESPIONAGE_MISSION,  //Missions: ATTACK_MISSION, TRANSPORT_MISSION, DEPLOYMENT_MISSION, ESPIONAGE_MISSION, COLONIZATION_MISSION, RECYCLE_MISSION, EXPEDITION_MISSION
        speed,          //Speed [10-100] means 10%-100%, speed step is 10% => 10,20,30,...,100
        [ESPIONAGEPROBE : spy2send]  //SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER
      )
      if (sent) {
        mapCoordsPlanetName.put player.getCoordinates().toString(),player.planetName
        printOut "Fleet is sent, wait spy reports from "+mapCoordsPlanetName
      } else continue
      def tripDuration = getTripDuration( fromPlanet, player.getCoordinates(), speed, [ESPIONAGEPROBE : spy2send] )
      printOut "Trip Duration: " + tripDuration + " seconds"
      printOut "Start sleeping..."
      sleep tripDuration * 1000 + 5000
      printOut "Start parsing messages"
      parseMessages mapCoordsPlanetName
    }
  }
  printOut "Finished"
  stopScript()
}

/*
// Definition of function to print a player info
def printPlayer = { player ->
  printOut player.playerID + "|" + player.playerName + "|" + player.planetName + "\t" +
    player.rank + "\t" +
    player.galaxy + ":" + player.system + ":" + player.planet + " " + (player.isMoon ? "Moon" : "") + "\t" +
    player.globalActivityStatus + "\t" +  // compare it to : ACTIVE_STATUS, INACTIVE_STATUS, LONGINACTIVE_STATUS, NOOB_STATUS, VACATION_STATUS, STRONG_STATUS, BANNED_STATUS, UNKNOWN_STATUS
    player.metalDebris + "\t" +
    player.crystalDebris + "\t" +
    (player.allianceID == 0 ? "No_Ally" : player.allianceID + "|" + player.allianceName + "|" + player.allianceRank) + "\t" +
    player.isAllyInAvoidAttackList + "\t" +
    player.isInAvoidAttackList + "\t" +
    player.isUnderSurveillance + "\t" +
    player.isTarget + "\t" +
    player.triggerTimeMin;
}

// Print all players known by OA
for (p in getPlayers()) {
  printPlayer p  // when printing too much things, it becomes very slow
}
printOut ""
// Print players in the Hunter
for (p in getHunterTargets()) {
  printPlayer p
}

// Let's spy Moons from your first planet
def coord = getPlanets()[0].coordinates  // selecting the first planet
def radius = 20
def spy2send = 2

class CoordComparator implements Comparator {
  public int compare(Object o1, Object o2) {
    return o1.getCoordinates().compareTo(o2.getCoordinates())
  }
}

def players = getPlayers()
Collections.sort(players, new CoordComparator())  // sort list of players according to Coordinates order
for (player in players) {
  if (player.galaxy==coord.galaxy && player.system>=coord.system-radius && player.system<=coord.system+radius && player.isMoon) {
    // Print player
    printPlayer player
    // Wait till have needed spies
    while (getNumberOf(ESPIONAGEPROBE, coord) < spy2send || !hasAvailableFleetSlot()) {
      printOut "waiting for spies or free fleet slots..."
      printOut "Has Available Fleet slots: " + hasAvailableFleetSlot()
      printOut "ESPIONAGE PROBEs on planet: " + getNumberOf(ESPIONAGEPROBE, coord)
      sleep 30000
    }
    // Send a spy ...
    def sent = sendFleet(  // sendFleet function returns a boolean [true,false], Groovy infers the type of 'sent' variable at compile time
      coord,  // Planet Name: "Main Planet" or Coordinates
      new Coordinates(player.galaxy,player.system,player.planet,MOON_TYPE),      // Target Coordinates (yours or enemy)player.galaxy+":"+player.system+":"+player.planet
      ESPIONAGE_MISSION,  // Missions: ATTACK_MISSION, TRANSPORT_MISSION, DEPLOYMENT_MISSION, ESPIONAGE_MISSION, COLONIZATION_MISSION, RECYCLE_MISSION, EXPEDITION_MISSION
      100,          // Speed [10-100] means 10%-100%, speed step is 10% => 10,20,30,...,100
      [ESPIONAGEPROBE : spy2send]  // SMALLCARGO, LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, COLONYSHIP, RECYCLER, ESPIONAGEPROBE, BOMBER, DESTROYER, DEATHSTAR, BATTLECRUISER
    );
    //Faster version
    //send Espionage Probe from Galaxy page to Coordinates: pay attention, you shell configure OGame option 'Number of espionage probes:' to something different to 0
    //sendEspionageProbeFromGalaxyPage [your planet name or coordinates,] Object target
    //def sent = sendEspionageProbeFromGalaxyPage( new Coordinates(player.galaxy,player.system,player.planet,MOON_TYPE) )
    //same as above, but forcing OA to use a defined planet
    //def sent = sendEspionageProbeFromGalaxyPage( coord, new Coordinates(player.galaxy,player.system,player.planet,MOON_TYPE) )
    if (sent) printOut "Fleet sent !"
    else printOut "Fleet sent failed !"
  }
}
*/

/* Uncomment this part to test out a research in old spy reports
// Let's play with a spy report in OA Database
try {
  def spyReport = getLastSpyReport("[4:304:5] Moon")
  printOut "A spy report was found:"
  printOut "Matal: "+spyReport.getMetal()
  printOut "Matal: "+spyReport.getCrystal()
  printOut "Matal: "+spyReport.getDeuterium()
  def entities = spyReport.getEntities();
  printOut "Ships detected: "+spyReport.hasShips()
  printOut "Defences detected: "+spyReport.hasDefences()
  printOut "Buildings detected: "+spyReport.hasBuildings()
  printOut "Technologies detected: "+spyReport.hasTechnologies()
  for (entity in ships+defences+buildings+technologies) {
    if (entities.get(entity).getValue()) printOut getEntityString(entity)+'\t=\t'+entities.get(entity).getValue()
  }

  printOut "\nSimulate a battle with your first planet as attacker:"
  try {
    //Battle Simulation, blocking call
    BattleSimResult bsr = battleSim getPlanets()[0], spyReport
    //next one is battle sim with first incoming fleet against defences on your first planet
    //battleSim getFleetInAir()[0].getEntities(), getPlanets()[0].getEntities()
    //same as before, but we use a Map representation
    //BattleSimResult bsr = battleSim( ['CRUISER': 10], ['LIGHTFIGHTER': 100] )
    printOut "Attacker Victory: "+bsr.victory
    printOut "Draws: "+bsr.draws
    printOut "Attacker Defeat: "+bsr.defeat
    printOut ""
    printOut "Attacker Losses Price: "+bsr.attackerLossesPrice  //returned type is Price: bsr.attackerLossesPrice.getMetalValue(), bsr.attackerLossesPrice.getCrystalValue(), bsr.attackerLossesPrice.getDeutValue()
    printOut "Attacker Total Losses: "+bsr.attackerTotalLosses
    printOut ""
    printOut "Defender Losses Price: "+bsr.defenderLossesPrice
    printOut "Defender Total Losses: "+bsr.defenderTotalLosses
    printOut ""
    // Note: For the ratio metal/crystal transformation to debris, setted values in Advanced Options are used
    printOut "Metal Debris: "+bsr.metalDebris
    printOut "Crystal Debris: "+bsr.crystalDebris
    printOut ""
    printOut "Needed Recyclers: "+bsr.neededRecyclers
    printOut ""
    printOut "Attacker Remaining ships:"
    for (entity in ships) {
      if (bsr.attackerEntities.get(entity).getValue() != 0) printOut getEntityString(entity)+" = "+bsr.attackerEntities.get(entity).getValue()
    }
    printOut ""
    printOut "Defender Remaining ships/defences:"
    for (entity in ships+defences) {
      if (bsr.defenderEntities.get(entity).getValue() != 0) printOut getEntityString(entity)+" = "+bsr.defenderEntities.get(entity).getValue()
    }
  } catch (BattleSimEmptyAttackerException bseae) {
    printOut "Attacker has not ships :(, simulation is aborted"
  } catch (BattleSimEmptyDefenderException bsede) {
    printOut "Defender has not ships/defenses :(, simulation is aborted"
  }
} catch (EmptyResultException e) {
  printOut "Spy report was not found, choose another planet !"
}
*/

Database Access

Entity Schema
Figure 1. Entity Schema
Example
// Let's test OA Database connection: Entity Schema: http://ogameautomizer.com/images/OA_DBv4.PNG
Connection conn = getDBConn()
Statement statement = conn.createStatement();
ResultSet result = statement.executeQuery("SELECT COUNT(*), SUM(Metal), SUM(Crystal), SUM(Deuterium) FROM ATTACK_STATS WHERE AttackedInactive=1");
if (result.next()) {
  printOut "Attacking Stats : "+Numbers.getNumber(result.getInt(1))+" attacked, "+Numbers.getNumber(result.getLong(2))+" metal, "+Numbers.getNumber(result.getLong(3))+" crystal, "+Numbers.getNumber(result.getLong(4))+" deuterium";
}
statement.close()
result.close()
closeDBConn(conn)  // DO NO FORGET TO CLOSE THE CONNECTION, if it doesn't, connection pool for OA Scripting will be exhausted (max. 4 opened connections by script)


// Let's print some info about planets
conn = getDBConn()
statement = conn.createStatement();
result = statement.executeQuery("SELECT * FROM PLANET WHERE Galaxy=1 AND System=1");
while (result.next()) {
  printOut result.getInt("Galaxy") + ":" + result.getInt("System") + ":" + result.getInt("Planet") + " " + result.getBoolean("IsMoon") + " " + result.getInt("DebrisMetal") + " " + result.getInt("DebrisCrystal")
}
statement.close()
result.close()
closeDBConn(conn)  // DO NO FORGET TO CLOSE THE CONNECTION, if it doesn't, connection pool for OA Scripting will be exhausted (max. 4 opened connections by script)

// This one lists all Hunter's targets
conn = getDBConn()
statement = conn.createStatement();
result = statement.executeQuery(
  "SELECT OGAME_PLAYER.Player_id, OGAME_PLAYER.PlayerName, OGAME_PLAYER.PlayerRank, ActivityStatus, OGAME_PLAYER.PlayerAvoidAttack, UnderSurveillance, ALLIANCE.Alliance_id, ALLIANCE.AllianceName, ALLIANCE.AllyRank, ALLIANCE.AllyAvoidAttack, IsTarget, OGAME_PLAYER.PlayerAvoidAttack, PLANET.PlanetAvoidAttack, PLANET.Galaxy, PLANET.System, PLANET.Planet, PLANET.IsMoon, PLANET.PlanetName, PLANET.TriggerTimeMin, DebrisMetal, DebrisCrystal " +
  "FROM ALLIANCE, OGAME_PLAYER, PLANET " +
  "WHERE " +
  "ALLIANCE.Alliance_id=OGAME_PLAYER.Alliance_id AND " +
  "OGAME_PLAYER.Player_id=PLANET.Player_id AND " +
  "UnderSurveillance=1 AND " +
  "(ActivityStatus="+OGamePlayerAbstract.ACTIVE_ACTIVITY_STATUS+") AND " +  //ACTIVE_ACTIVITY_STATUS, INACTIVE_ACTIVITY_STATUS, LONGINACTIVE_ACTIVITY_STATUS, NOOB_ACTIVITY_STATUS, VACATION_ACTIVITY_STATUS, STRONG_ACTIVITY_STATUS, BANNED_ACTIVITY_STATUS, UNKNOWN_ACTIVITY_STATUS
  "ALLIANCE.AllyAvoidAttack=0 AND OGAME_PLAYER.PlayerAvoidAttack=0"
);
def activePlayersString = "";
while (result.next()) {
  activePlayersString += result.getInt("Player_id") + ";" +
  result.getString("PlayerName") + ";" +
  result.getString("PlanetName") + ";" +
  result.getByte("ActivityStatus") + ";" +
  result.getInt("DebrisMetal") + ";" +
  result.getInt("DebrisCrystal") + ";" +
  result.getShort("PlayerRank") + ";" +
  result.getShort("Alliance_id") + ";" +
  result.getString("AllianceName") + ";" +
  result.getShort("AllyRank") + ";" +
  result.getBoolean("AllyAvoidAttack") + ";" +
  result.getBoolean("PlayerAvoidAttack") + ";" +
  result.getBoolean("PlanetAvoidAttack") + ";" +
  result.getBoolean("UnderSurveillance") + ";" +
  result.getBoolean("IsTarget") + ";" +
  result.getShort("TriggerTimeMin") + ";" +
  result.getByte("Galaxy") + ";" +
  result.getShort("System") + ";" +
  result.getByte("Planet") + ";" +
  result.getBoolean("IsMoon") + "\r\n";
}
statement.close()
result.close()
closeDBConn(conn)
def fileName = "ActivePlayers.csv"
writeFile fileName, activePlayersString
printOut "done. checkout: " + fileName

Events Listener

// Registering a listener for GALAXY_SCAN event
addEventListener GALAXY_SCAN,
  { scan ->
    printOut "New Galaxy Scan:"
    for (p in scan.planets) {
      printOut p.playerName + '\t' +
        p.playerID + '\t' +
        p.planetName + '\t' +
        p.coordinates + '\t' +
        p.globalActivityStatus + '\t' + // compare it to : ACTIVE_STATUS, INACTIVE_STATUS, LONGINACTIVE_STATUS, NOOB_STATUS, VACATION_STATUS, STRONG_STATUS, BANNED_STATUS, UNKNOWN_STATUS
        p.rank + '\t' +
        p.metalDebris + '\t' +
        p.crystalDebris + '\t' +
        p.allianceName + '\t' +
        p.allianceRank + '\t' +
        p.allianceID + '\t' +
        p.allianceMembers + '\t' +
        p.lastActivityTime
    }
  }

// Registering a listener for SPY_REPORT event
addEventListener SPY_REPORT,
  { scan ->
    printOut "New Spy Report:"
    printOut scan.playerName + '\t' +
      scan.playerID + '\t' +
      scan.planetName + '\t' +
      scan.coordinates + '\t' +
      scan.globalActivityStatus + '\t' + // compare it to : ACTIVE_STATUS, INACTIVE_STATUS, LONGINACTIVE_STATUS, NOOB_STATUS, VACATION_STATUS, STRONG_STATUS, BANNED_STATUS, UNKNOWN_STATUS
      scan.rank + '\t' +
      scan.allianceName + '\t' +
      scan.allianceRank + '\t' +
      scan.allianceID + '\t' +
      scan.metal + '\t' +
      scan.crystal + '\t' +
      scan.deuterium + '\t' +
      scan.energy + '\t' +
      scan.isProbeLost + '\t' +
      scan.shipsDetected + '\t' +
      scan.defensesDetected + '\t' +
      scan.buildingsDetected + '\t' +
      scan.technologiesDetected
      for (entity in ships+defences+buildings+technologies) {
        if (scan.entities.get(entity).getValue() != 0) printOut getEntityString(entity)+" = "+scan.entities.get(entity).getValue()
      }
  }

// Let's scan several galaxies
// Scan 10 Galaxies
// Solution 1
for (g in 1..10) {  // = for (int g = 1; g <= 10; g++) {
  for (s in 1..499) {  // = for (int s = 1; s <= 499; s++) {
    startSyncGalaxyScan g, s
    printOut "Scanned: "+g+":"+s
  }
}
// Solution 2
/*for (g in 1..10) {  // = for (int g = 1; g <= 10; g++) {
  startAsyncGalaxyScan g, 1, 499
  //and wait till it's finished
  while (isRunningAsyncGalaxyScan()) {
    sleep 10000  //wait 10 secs
  }
  printOut "Scanned: "+g+":[1-499]"
}*/

Scheduler

/* Cron is the time-based job scheduler. Cron enables users to schedule jobs (commands) to run periodically at certain times or dates.
   A CRON expression is a string comprising 5 or 6 fields comma-separated values that represents a set of times, normally as a schedule to execute some routine.
   The syntax is: minutes, hours, days of month, months, days of week [, seconds]
   The value for seconds is optional. The asterisk ( * ) indicates that the cron expression will match for all values of the field; e.g., using an asterisk in the 4th field (month) would indicate every month.
   Each element allows wildcard * values. Ranges can be specified by means of lower bounds then a colon ':' then the upper bound.
   The division operator * /x (without space between * and /) can be used to specify that every x-th value is valid.
   Combinations of these operators can be used by placing these into square brackets([])
   ----------
   Field Name     Mandatory?        Allowed Values             Additional Keywords
   Minutes        yes               0 - 59
   Hours          yes               0 - 23
   Days Of Month  yes               1 - 31                     last, weekday, lastweekday
   Months         yes               1 - 12
   Days Of Week   yes               0 (Sunday) - 6 (Saturday)  last
   Seconds        no                0 - 59

  *,    *,   *,    *,    *,   *     (command to be executed)
  |     |    |     |     |    |
  |     |    |     |     |    |
  |     |    |     |     |     ---- second (0 - 59)
  |     |    |     |      --------- day of week (0 - 6) (0 is Sunday, or use names)
  |     |    |      --------------- month (1 - 12)
  |     |     --------------------- day of month (1 - 31)
  |      -------------------------- hour (0 - 23)
   -------------------------------- min (0 - 59)
   Examples:
   *, *, *, *, *, *  =  execute every second
   *, *, *, *, *     =  execute every minute
   5, *, *, *, *     =  execute every 5 minutes past the hour (00:05, 01:05, 02:05, 03:05, ..., 23:05)
   30, 5, *, *, *    =  execute every day at 05:30
*/
// Execute every 10 seconds (0:00, 0:10, 0:20, 0:30, 0:40, 0:50)
addCronExec "*, *, *, *, *, */10", {
    printOut "executed every 10 secs."
  }

/* Execute regularly a command at defined interval.
   Some examples of time intervals:
   10 seconds
   10 minutes 30 seconds
   20 sec 100 msec
   1 day 2 hours 20 minutes 15 seconds 110 milliseconds
   0.5 minutes
   1 year
   1 year 1 month
*/
// Execute every 30 seconds
addIntervalExec "30 sec", {
    printOut "executed every 30 secs."
  }
// Execute every 7.5 seconds
addIntervalExec 7.seconds+500.milliseconds, {
    printOut "executed every 7.5 secs."
  }

// Execute once in 5 seconds
addExecIn 5.seconds+500.milliseconds, {
    printOut "executed once after 5.5 secs"
  }
// Execute once in 10000 milliseconds (= 10 seconds)
addExecInMs 10000, {
    printOut "executed once after 10000 milliseconds"
  }
addExecInMs 3.seconds, {
    printOut "executed once after 3000 milliseconds"
  }

// Start a new supervised Thread
Thread.start {
  printOut "Thread is running... till it's running you cannot stop the script"
  sleep 20000
  printOut "Thread finishing"
}

OGame API

// More about XML parsing can be found here: http://groovy.codehaus.org/Reading+XML+using+Groovy's+XmlParser

// On the Forum you can find a script importing to OA Database all OGame date from XML files below, on regular basis

def playersXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/players.xml").player
def universeXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/universe.xml").planet
def alliancesXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/alliances.xml").alliance
def pHighscoresXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/highscore.xml?category=1&type=1").player
def aHighscoresXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/highscore.xml?category=2&type=1").alliance

def findPlayerData = { findName ->
  def p = playersXMLRecords.findAll{ it.@name.equals(findName) }
  if (p.size() > 0) {
    def u = universeXMLRecords.findAll{ it.@player == p[0].@id }
    for (int i = 0; i < u.size(); i++) {
      def a = alliancesXMLRecords.findAll{ it.@id == p[0].@alliance }
      printOut p[0].@name + "\t" + (p[0].@status == null ? "active" : p[0].@status) + "\t" +
        (a[0] == null ? "no ally" : a[0].@tag + "/" + a[0].@name) + "\t" +
        "planet_name="+u[i].@name + "\t[" + u[i].@coords + "]\t" +
        (pHighscoresXMLRecords.findAll{ it.@id == p[0].@id }[0] == null ? "" : "p_rank="+pHighscoresXMLRecords.findAll{ it.@id == p[0].@id }[0].@position + "\t" + "p_score="+pHighscoresXMLRecords.findAll{ it.@id == p[0].@id }[0].@score) + "\t" +
        (a[0] == null || aHighscoresXMLRecords.findAll{ it.@id == a[0].@id }[0] == null ? "no ally" : "a_rank="+aHighscoresXMLRecords.findAll{ it.@id == a[0].@id }[0].@position + "\t" + "a_score="+aHighscoresXMLRecords.findAll{ it.@id == a[0].@id }[0].@score)
      // Print Moon, if any
      if (universeXMLRecords.findAll{ it.@player == p[0].@id }[i].moon.@name.size() > 0)
        printOut p[0].@name + "\t" + (p[0].@status == null ? "active" : p[0].@status) + "\t" +
          (a[0] == null ? "no ally" : a[0].@tag + "/" + a[0].@name) + "\t" +
          "planet_name="+u[i].moon.@name + "\t[" + u[i].@coords+"] Moon" + "\t" +
          (pHighscoresXMLRecords.findAll{ it.@id == p[0].@id }[0] == null ? "" : "p_rank="+pHighscoresXMLRecords.findAll{ it.@id == p[0].@id }[0].@position + "\t" + "p_score="+pHighscoresXMLRecords.findAll{ it.@id == p[0].@id }[0].@score) + "\t" +
          (a[0] == null || aHighscoresXMLRecords.findAll{ it.@id == a[0].@id }[0] == null ? "no ally" : "a_rank="+aHighscoresXMLRecords.findAll{ it.@id == a[0].@id }[0].@position + "\t" + "a_score="+aHighscoresXMLRecords.findAll{ it.@id == a[0].@id }[0].@score)
    }
  }
}
// Let's find information about you in XML files
findPlayerData getLogin()

// Print all data in OGame XMLs files
/*
def playersXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/players.xml").player
for (int i = 0; i < playersXMLRecords.size(); i++) {
  def r = playersXMLRecords[i]
  printOut "player_id="+r.@id + "\t" +
    r.@name + "\t" +
    (r.@status == null ? "" : r.@status) + "\t" +
    (r.@alliance == null ? "" : "alliance_id="+r.@alliance)
}
def universeXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/universe.xml").planet
for (int i = 0; i < universeXMLRecords.size(); i++) {
  def r = universeXMLRecords[i]
  printOut "planet_id="+r.@id + "\t" +
    "player_id="+r.@player + "\t" +
    r.@name + "\t" +
    "["+r.@coords+"]"
}
def alliancesXMLRecords = new XmlParser().parse("http://"+getOGameAddress()+"/api/alliances.xml").alliance
for (int i = 0; i < alliancesXMLRecords.size(); i++) {
  def r = alliancesXMLRecords[i]
  printOut "alliance_id="+r.@id + "\t" +
    "tag="+r.@tag + "\t" +
    r.@name + "\t" +
    (r.@logo == null ? "" : r.@logo) + "\t" +
    "open="+r.@open
}
*/

GUI Swing

// Let's create a GUI for your script
//http://groovy.codehaus.org/Swing+Builder
//http://groovy.codehaus.org/Alphabetical+Widgets+List  //Click on needed component to see all available properties
//http://groovy.codehaus.org/Categorical+Widget+List
//http://groovy.codehaus.org/GUI+Programming+with+Groovy

// OA Wrapper is work in Script mode, allowing global access (from created Threads) to all OA API
// but:
// - call to a missing method will stop the thread/script
// - in some cases OA will not able to stop the script: e.g GUI Thread created by SwingBuilder or by yourself
// - as above, exceptions/errors produced by threads will not be printed on the console but in system output

def swing = new SwingBuilder()

def sharedPanel = {
  swing.panel() {
    label("Shared Panel")
  }
}

def printFunc = { String msg ->
  printOut msg
}
printFunc "Click on Button"

def count = 0
swing.edt {
  frame(title:'My Frame', defaultCloseOperation:JFrame.DISPOSE_ON_CLOSE, size:[200,200], pack:false, show:true) {
    vbox {
      textlabel = label("Click the button!")
      button(
        text:'Click Me',
        actionPerformed: {
          count++
          textlabel.text = "Clicked ${count} time(s)."
          printFunc "Button Clicked"
          printOut "Button Clicked 2"
        }
      )
      widget(sharedPanel())
      widget(sharedPanel())
    }
  }
}

// Another Frame with a Bindable variable and some GUI elements
class MyModel {
   @Bindable int count = 0
}

def model = new MyModel()
def myTable
new SwingBuilder().edt {
  frame(title: "Frame", defaultCloseOperation:JFrame.DISPOSE_ON_CLOSE, size: [300, 400], locationRelativeTo: null, show: true) {
    gridLayout(cols: 1, rows: 7)
    //flowLayout()
    label(text: bind(source: model, sourceProperty: "count", converter: { v ->  v? "Clicked $v times": ''}))
    button(
      "Click me!",
      actionPerformed: {
        model.count++;
        progress.value = model.count
      }
    )
    progress = progressBar(minimum: 0, maximum: 20)
    panel(layout:new FlowLayout()) {
      for (name in ["Tom", "Dick", "Harry", "Bill"]) {
        checkBox(text:name);
      }
    }
    panel(layout:new FlowLayout()) {
      comboBox(items:["Red", "Green", "Blue", "Orange"], selectedIndex:2);  //As many thing in programming it's starts from 0
    }
    scrollPane(constraints:BorderLayout.CENTER, autoscrolls: true) {
      textArea(rows: 2)
    }
    scrollPane(constraints:BorderLayout.CENTER, autoscrolls: true) {
      myTable = table() {
        tableModel(list: [ [firstName: "Bob", lastName: "Stevens", address: "1st Street"], [firstName: "Lucy", lastName: "House"] ] ) {
          propertyColumn( header:"First Name", propertyName:"firstName" )
          propertyColumn( header:"Last Name", propertyName:"lastName" )
          propertyColumn( header:"Address", propertyName:"address" )
        }
      }
    }
  }
}
// Let's add a new row to the table
def rows = myTable.getModel().getRowsModel().getValue()
rows.add( [ firstName: "Reese", lastName: "Smith" ] )
myTable.getModel().getRowsModel().setValue( rows )
myTable.getModel().fireTableDataChanged()

// Frame with Tabs
def print = {printOut "Printing"}
def save = {printOut "Saving"}
def getSearchPanel = { ->
  JPanel panel = new JPanel()
  panel.add(new JLabel("Search Panel"))
  return panel
}
def frame = new SwingBuilder().frame(title: "Frame with Tabs", defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE, size: [400, 300], show: true) {
  // add a menu bar
  menuBar {
    menu("File", mnemonic: 'F') {
      menuItem ('Print', actionPerformed: { print() })
      separator()
      menuItem ('Save', actionPerformed: { save() })
      menuItem ("Exit", mnemonic: 'X', actionPerformed: { printOut "Exit Button clicked" })
    }
    menu("Options", mnemonic: 'O') {
      checkBoxMenuItem ('Create Debugging Info', state:true)
    }
    menu(mnemonic:'D', 'Debug') {
      group = buttonGroup()
      radioButtonMenuItem ('Log Level 1', buttonGroup:group, selected:true)
      radioButtonMenuItem ('Log Level 2', buttonGroup:group)
      radioButtonMenuItem ('Log Level 3', buttonGroup:group)
    }
    menu(mnemonic:'F', 'Format') {
      menu('Font') {
        group = buttonGroup()
        radioButtonMenuItem ('Times Roman', buttonGroup:group, selected:true)
        radioButtonMenuItem ('Courier', buttonGroup:group)
      }
    }
    menu(mnemonic:'E', 'Edit') {
      menuItem (actionPerformed:{}, 'Copy')
      menuItem (actionPerformed:{}, 'Cut')
      menuItem (actionPerformed:{}, 'Paste')
      menuItem (actionPerformed:{}, 'Delete')
      separator()
      menu('Object ...') {
        menuItem (actionPerformed:{}, 'Circle')
        menuItem (actionPerformed:{}, 'Square')
        menuItem (actionPerformed:{}, 'Point')
      }
    }
  }
  // add a tabbed pane
  tabbedPane() {
    panel(title:"Panel 1", tabBackground:java.awt.Color.GREEN, tabToolTip:"ToolTip: Panel 1")
    // add custom swing component
    widget(title:"Search", getSearchPanel())
  }
}

// Let's create a Chart for your script
//http://groovy.codehaus.org/Plotting+graphs+with+JFreeChart
@ImportsDefinitionStart
import org.jfree.chart.ChartFactory
import org.jfree.chart.ChartPanel
import org.jfree.data.category.DefaultCategoryDataset
import org.jfree.chart.plot.PlotOrientation as Orientation
@ImportsDefinitionEnd

def dataset = new DefaultCategoryDataset()
dataset.with {
    //Value, Grouping Category for data, Horizontal axe
    addValue 150, "no.1", "Jan"
    addValue 210, "no.1", "Feb"
    //addValue 390, "no.1", "Mar"
    addValue 300, "no.2", "Jan"
    addValue 400, "no.2", "Feb"
    addValue 200, "no.2", "Mar"
}

def chart = ChartFactory.createLineChart("Title", "Month", "Count", dataset, Orientation.VERTICAL, true, true, true)
new SwingBuilder().frame(title:'LineChart', defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE, pack: true, show: true) {
    panel() { widget(new ChartPanel(chart)) }
}

Programming

// Below you will find short examples of OA API usage and more specific examples:
// user interaction, file manipulation, networking, sounds, email, jar's loading, external database access...

// Get something from user, blocking call
def input = getInput "How are you ?\nDoes OA Scripting rocks ?"
if (input != null && !input.isEmpty()) printOut "User entered: " + input
else printOut "User has not entered anything :("
// Show a simple alert to user, blocking call
showAlert "Title", "Alert"

printOut "OGame Server address: "+getOGameAddress()
printOut "User OGame Login: "+getLogin()
printOut "OA HTTP Proxy Port (between 8080 and 10000): "+getHTTPProxyPort()
printOut "isLoggedIn: "+isLoggedIn()
printOut "isOADisabled: "+isOADisabled()
printOut "isJobCurrentlyRunning: "+isJobCurrentlyRunning()  //OA Job sheduler is executing a job ?
printOut "isAttacked: "+isAttacked()
printOut "isSpied: "+isSpied()
if (isLoggedIn()) printOut "Currently logged-in OGame"
else printOut "Currently NOT logged-in OGame"
printOut "Last Updated/Checked Planet: "+getLastUpdatedPlanet()

// Debugging: in case you don't know what does Object contains use dump() method on it and print the result
printOut "Dumped Object returned by getPrice(METALMINE, <first planet>) : " + getPrice(METALMINE, getPlanets()[0]).dump()
// => output: api.model.Price@XYZ metal=XXX crystal=YYY deuterium=ZZZ

// Enable/Disable OA
printOut "Is OA enabled: "+isOAEnabled()
disableOA()
sleep 5000
enableOA()

// Built-in OA Fleet Saver: enable/disable
printOut "Is FS enabled: "+isFSEnabled()
disableFS()
sleep 5000
enableFS()

// Enable/Disable Built-in OA Buildings/Technologies Builder
printOut "Is Buildings/Technologies Builder enabled: "+isBuildingsTechnologiesBuilderEnabled()
disableBuildingsTechnologiesBuilder()
sleep 5000
enableBuildingsTechnologiesBuilder()

// Enable/Disable Built-in OA Ships/Defenses Builder
printOut "Is Ships/Defenses Builder enabled: "+isShipsDefensesBuilderEnabled()
disableShipsDefensesBuilder()
sleep 5000
enableShipsDefensesBuilder()

// Enable/Disable Built-in OA Recycler
printOut "Is Recycler enabled: "+isRecyclerEnabled()
disableRecycler()
sleep 5000
enableRecycler()

// Iter over a ship 'List' (in reality it's a Map: String -> Integer), but who cares ;)
def expofleet =
[
  SMALLCARGO : 0,
  LARGECARGO : 200,
  LIGHTFIGHTER : 0,
  HEAVYFIGHTER : 0
]
def iter = expofleet.keySet().iterator()
while (iter.hasNext()) {
  String key = iter.next()
  printOut key + ": " + expofleet.get(key)
}

// Getting Time
printOut "OA start timestamp: " + oaStartTimestamp
printOut "Timestamp: " + timestamp   // in context of multi-threading(GUI), you should use getTimestamp()
printOut "Time: " + Time.getTime()
printOut "More Time: " + Time.getYear() + "-" + Time.getMonth() + "-" + Time.getDayOfMonth() + " " + Time.getHour() + ":" + Time.getMinute() + ":" + Time.getSecond()
// Some built-in Java Utils
printOut Math.max(0,50)
// Let's print 10 random [0-9] values
Random random = new Random()
for (int i = 0; i < 10; i++) {
  printOut random.nextInt(10)
}

// Let's manipulate another OA Script, no blocking mode
def scriptName = "Fleets in Air"
if ( !isScriptRunning(scriptName) && startScript(scriptName) ) {
  printOut "Script Started: you can check it in 'Fleets in Air' Tab, and it's running"
  sleep 10000
  if ( stopScript(scriptName) ) printOut "Script Stopped"
} else printOut "Script Start failed ! Script name error ?"

// File IO
def fileName = "OAScript.txt"
writeFile fileName, "Coucou\n"  //write the file in UTF-8, \n is a newline in Linux, \r\n is a newline in Windows
appendToFile fileName, "Coucou2"  //append to file something
printOut fileName + ":\n" + readFile(fileName)  //print the file

// Send SMS (via Google Calendar)
def isSMSSent = sendSMS("your_email@gmail.com", "*********", "API Test")  //login/email address, passwd, SMS Test(should be quite short)

// Send email using, as example, GMail smtp server
def isEmailSent = sendEmail(
  "your_email@gmail.com", "your_email@gmail.com",  //email addressed to and from
  "API Test", "Does it work",  //email Title and core message
  "smtp.gmail.com", 465,  //SMTP Server + port
  "your_email@gmail.com", "*********",  //login/email address + passwd
  true  //enableSTARTTLS ?
)

// Get a web page, easy man :)
printOut "http://ogameautomizer.com".toURL().text.substring(0,250)  //prints first 250 chars

// Execute External Program and print the output
//def output = "program args".execute().text
def proc = "\"C:\\Program Files\\Internet Explorer\\iexplore.exe\" http:\\\\ogame.org".execute()
proc.waitForOrKill(5000)  // we are hurry, kill it after 5 secs :)
printOut proc.text
// You can wait till it's finished to execute
//def proc = "program args".execute()
//proc.waitfor ()

// Let's play with Sounds
// Repeat sound 3 times
for (i in 1..3) {
  // Play mp3 from OA 'Sounds' folder, not blocking call
  Sound.playSound("Enemy_Sighted.mp3")
  while (Sound.stillPlaying()) sleep 100
}
// Play mp3 from OA 'MySoundsWhenI'mAttacked' folder, not blocking call
Sound.playPersonelMp3("07 - The Souls Of Distortion.mp3")
sleep 60000
// Stop playing
Sound.stopPlayingNow()
// Play a file, not blocking call
Sound.play("E:\\Java\\eclipse\\workspace\\OGameAutomizer\\MySoundsWhenI'mAttacked\\07 - The Souls Of Distortion.mp3")

// Let's try to connect to MySQL Database
// Download JDBC Driver: http://dev.mysql.com/downloads/connector/j/
// and place it in OA folder
// Load dynamically the jar
URL url = new File("mysql-connector-java-5.1.18-bin.jar").toURL()
Thread.currentThread().getContextClassLoader().addURL( url )
// Make a new connection to the Database and SQL requests
def sql = Sql.newInstance("jdbc:mysql://localhost:3306/db_schema", "login", "passwd", "com.mysql.jdbc.Driver")
sql.eachRow("select * from TABLE") { row ->
  printOut row.COLUMN_NAME + " " + row.ANOTHER_COLUMN_NAME
}
sql.close()

// Let's test a Class Definition
class TestClass0 {
  int value
}
class TestClass1 extends TestClass0 {
  String chars
}
// Create Object
TestClass1 ts = new TestClass1(value : 5, chars: "works")
printOut "Testing class"
printOut ts.value * 2
printOut "Does it " + ts.chars + " ?"

// Let's Stop Script execution
while (true) {
  sleep 1000
  stop()  // to stop the execution of the script immediately
}
printOut "Script passed, How it's possible ?"

Manual Authors

Devil’s Hand: initial release

Contribute to the Manual improvements

Manual was written using markup language Asciidoc

       .o.                           o8o   o8o  oooooooooo.
      .888.                          `"'   `"'  `888'   `Y8b
     .8"888.      .oooo.o  .ooooo.  oooo  oooo   888      888  .ooooo.   .ooooo.
    .8' `888.    d88(  "8 d88' `"Y8 `888  `888   888      888 d88' `88b d88' `"Y8
   .88ooo8888.   `"Y88b.  888        888   888   888      888 888   888 888
  .8'     `888.  o.  )88b 888   .o8  888   888   888     d88' 888   888 888   .o8
 o88o     o8888o 8""888P' `Y8bod8P' o888o o888o o888bood8P'   `Y8bod8P' `Y8bod8P'

In case you want to do improvements to this manual, just edit that the Source file and send it by email at : ogameautomizer [at] gmail [dot] com

Manual Update History

2014.09.27 v1.1.0 : Fluid API for fleet sending/recalling
2014.06.01 v1.0.5 : initial public release