Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
bin
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Empire at War Abstraction Layer

Build Status

About

The Empire at War Abstraction Layer aims to be a drop in replacement for Empire at War's Lua functions, so Lua modules can be executed without launching the game itself. This not only saves time, but also helps with debugging, since the abstraction layer provides additional functioniality to configure the behavior of EaW's functions. The end goal is to provide a set of functions that can be used together in a unit testing framework. The library provides custom assertions for the test frameworks busted and u-test. We recommend using busted due to their included test runner and powerful assertions. Learn more in the Running Tests section

Installation

Either clone this repository or get it on luarocks using:

luarocks install eaw-abstraction-layer

This will install the necessary Lua files, an executable called eaw-abstraction-layer that allows you to generate pre-configured test projects as well as penlight, a powerful set of libraries that allows easy handling of files and directories.

Installation on Windows

If you are on Windows it can be tricky to get luarocks to run. I recommend installing the Windows Subsystem for Linux (WSL) with Ubuntu: https://docs.microsoft.com/en-us/windows/wsl/install-win10

Using this you will gain access to a Linux system with all its repositories. Setting up a Lua development environment here is much easier. To get up and running type the following commands in your Linux terminal:

sudo apt install luajit
sudo apt install luarocks
luarocks install eaw-abstraction-layer

From the terminal you can access your Windows drive through the mnt folder:

cd /mnt/c/Program Files (x86)/...

Usage

Project setup

Use eaw-abstraction-layer to generate a configured test project for you.

eaw-abstraction-layer --new-project

This will launch a setup routine that asks you for the target location of your test project and the location of your mod.

It will the generate all the necessary folders for you (including all folders in your test project path that don't exist yet) and a file called config.lua that will be run before your tests. It defines the eaw global variable that represents the eaw-abstraction-layer.

Of course you can also set up a similar folder structure manually.

Running Tests

busted

If you have chosen busted in your project setup then the setup routine will have generated a .busted file that contains all the necessary information for busted to run your test suite. If you haven't installed busted yet you can do so by running

luarocks install busted

To run your tests simply open a terminal in your test root folder and run

busted

This command will run the previously mentioned config.lua that sets up the Empire at War environment and provide global variables for tests and assertions. In the end you'll end up with the following global variables:

Variable Description
eaw The EaW Abstraction Layer. This will run also run the eaw.init() function with the mod path from your config.lua file, if present.
context / describe A test context
test / it A function that defines a unit test
assert Access to assertions
spy Provides functionality to spy on functions

To learn more about busted check out their great documentation

u-test

u-test currently doesn't ship with a test runner, so you will have to run it manually by calling the necessary functions in a script. Moreover, their version on luarocks seems to be outdated by quite a bit, so you should get it directly from their GitHub site: u-test You'll also find the necessary documentation there. u-test is a way smaller test framework and fits in a single file. It has a great test result output that is inspired by Google Test.

Manual testing

To use the library manually, set the path to your mod folder, require() a file and choose an entry function.

local eaw = require "eaw-abstraction-layer"

eaw.init("./examples/Mod")

local function test_eaw_module()
    eaw.run(function()
        require "eaw_module"
        my_eaw_function()
    end)

end

test_eaw_module()

If your code uses EaW's built-in global functions you will need to configure them as explained in the following sections.

Currently available EaW functions and types

Functions

All of the following functions can be accessed via the EaW environment:

local env = eaw.environment
local the_function = env.the_function_name
Function Default return value
Find_First_Object_Of_Type(type_name) A game_object with requested type
Find_All_Objects_Of_Type A table with a single game_object with requested type
Find_Player A faction object with requested name
Find_Object_Type A type with requested name
FindPlanet A planet object with requested name
FindPlanet.Get_All_Planets A table with a single planet
Find_Nearest A game_object with requested type
Find_Hint A game_object with requested type
Get_Story_Plot A plot object
Get_Game_Mode String "Galactic"
Register_Timer -
Cancel_Timer -
Register_Death_Event -
Register_Attacked_Event -
Cancel_Attacked_Event -
Register_Prox -
Spawn_Unit A table with a game_object of requested type
SpawnList A table of multiple game_objects of requested types
Story_Event -
TestValid Boolean true
ScriptExit Unlike all other replacement functions ScriptExit is an actual Lua function and cannot receive a callback function
Add_Planet_Highlight -
Hide_Sub_Object -
Game_Random The first of the two provided input arguments
Game_Random.GetFloat number 0
BlockOnCommand -
DebugMessage Will print the message using print(string.format(...))

Types

faction

Usage

local faction = eaw.types.faction

local player = faction {
    name = "Empire",
    is_human = true
}
Method Default return value
Get_Faction_Name Value of field name
Is_Human Value of field is_human or false if field is not present
Make_Ally -
Get_Tech_Level number 1

type

Usage

local type = eaw.types.type
local my_type = type("My_Type")
Method Default return value
Get_Name String with provided name

fleet

Usage

local fleet = eaw.types.fleet
local my_fleet = fleet()
Method Default return value
Get_Contained_Object_Count number 1
Contains_Hero Boolean false
Contains_Object_Type Boolean true

game_object

Usage

local game_object = eaw.types.game_object
local faction = eaw.types.faction
local my_game_object = game_object {
    name = "type_name",
    owner = faction {
        name = "Empire"
    }
}
Method Default return value
Change_Owner -
Get_Owner Value of field owner
Get_Type A type with name of field name

planet

Inherits from game_object

Usage

local planet = eaw.types.planet
local faction = eaw.types.faction
local my_planet = planet {
    name = "planet_name",
    owner = faction {
        name = "Empire"
    }
}
Method Default return value
Remove_Planet_Highlight -
Get_Final_Blow_Player A faction object

unit_object

Inherits from game_object

Usage

local unit_object = eaw.types.unit_object
local faction = eaw.types.faction
local my_unit = unit_object {
    name = "type_name",
    owner = faction {
        name = "Empire"
    }
}
Method Default return value
Move_To -
Attack_Move -
Attack_Target -
Guard_Target -
Make_Invulnerable -
Teleport -
Teleport_And_Face -
Turn_To_Face -
Activate_Ability -
Despawn -
Enable_Behavior -
Hide -
In_End_Cinematic -
Lock_Current_Orders -
Override_Max_Speed -
Play_Animation -
Prevent_AI_Usage -
Prevent_Opportunity_Fire -
Reset_Ability_Counter -
Set_Single_Ability_Autofire -
Stop -
Suspend_Locomotor -
Event_Object_In_Range -
Cancel_Event_Object_In_Range -
Are_Engines_Online Boolean true
Get_Hull Number 1
Get_Shield Number 1
Get_Parent_Object A game_object
Has_Ability Booean true
Is_Ability_Active Boolean false
Is_Under_Effects_Of_Ability Boolean false
Get_Planet_Location A game_object
Get_Position -

plot

Usage

local plot = eaw.types.story.plot
local my_plot = plot()
Method Default return value
Get_Event An event
Activate -
Suspend -
Reset -

event

Usage

local event = eaw.types.story.event
local my_event = event()
Method Default return value
Set_Event_Parameter -
Set_Reward_Parameter -

Configuration

All functions and types are implemented as callable tables that can be configured to use a callback function when they're being called or to return a specified value.

If, for example, you want to configure FindPlanet to return a certain game object and print a message when being called you can do so like this:

local eaw = require "eaw-abstraction-layer"
local env = eaw.environment
local planet = eaw.types.planet
local faction = eaw.types.faction

function env.FindPlanet.return_value(planet_name)
    return planet {
        name = planet_name,
        owner = faction {
            name = "Empire",
            is_human = true
        }
    }
end

function env.FindPlanet.callback(planet_name)
    print("FindPlanet was called with "..planet_name)
end

Since return_value() is a function instead of a simple field it allows you to apply more complex logic to a return value.

Functions that are expected to return something will throw a warning if you don't provide a return_value() function. However, they will not crash the script. The return value is determined before the callback() function is called. Most functions provide a default return value instead of returning nil.

Writing tests

The following section describes unit testing with the busted test framework. Tests can simply be defined using the test or it (an alias) function as shown below:

test("My test name", function()
    -- requiring the abstraction layer will not be necessary with auto generated projects
    local eaw = require "eaw-abstraction-layer"

    local type = eaw.types.type
    local game_object = eaw.types.game_object
    local faction = eaw.types.faction

    function eaw.environment.Spawn_Unit.callback()
        print("I get called when Spawn_Unit is called")
    end

    -- this is a busted spy!
    local s = spy.on(eaw.environment, "Spawn_Unit")

    eaw.run(function()
        Spawn_Unit(Find_Object_Type("DummyType"), FindPlanet("DummyPlanet"), Find_Player("DummyFaction"))
    end)

    assert.spy(s).was.called()
end)

Assertions and matchers

The eaw-abstraction-layer defines some custom assertions and matchers that are registered with busted when calling eaw.use_busted(). This is done automatically in auto generated projects.

The same can be done for u-test with eaw.use_u_test(), but has to be done manually (by requiring config.lua), since u-test does not have a test runner.

Matchers are only available in busted.

List of assertions and matchers

The currently available assertions/matchers are not final and will be expanded. Contributions are welcome.

Assertions
busted u-test
assert.is.eaw_type(value)
assert.is_not.eaw_type(value)
test.is_eaw_type(value)
assert.is.game_object(value)
assert.is_not.game_object(value)
test.is_game_object(value)
assert.is.unit_object(value)
assert.is_not.unit_object(value)
test.is_unit_object(value)
assert.is.planet(value)
assert.is_not.planet(value)
test.is_planet(value)
assert.is.faction(value)
assert.is_not.faction(value)
test.is_faction(value)
assert.is.fleet(value)
assert.is_not.fleet(value)
test.is_fleet(value)
assert.is.task_force(value)
assert.is_not.task_force(value)
test.is_task_force(value)
assert.is.plot(value)
assert.is_not.plot(value)
test.is_plot(value)
assert.is.event(value)

assert.is_not.event(value)
test.is_event(value)
Matchers (busted only)
match.game_object { name = "", owner = eaw.types.faction{...}} owner is optional
match.faction(faction_name)
match.eaw_type(type_name)

Below is an example on how to use matchers.

local s = spy.on(eaw.environment, "Spawn_Unit")

eaw.run(function()
    Spawn_Unit(Find_Object_Type("DummyType"), Find_First_Object("Attacker Entry Location"), Find_Player("DummyFaction"))
end)

assert.spy(s).was.called_with(
    match.eaw_type("DummyType"),
    match.game_object { name = "Attacker Entry Location" },
    match.faction {name = "DummyFaction"}
)

Contributing

It's actually really easy to contribute to this project, you don't have to be a Lua expert. To create a reference for a new EaW function either place it in a fitting existing file or create a new one in eaw-abstraction-layer/functions. A function reference could look like this:

local metatables = require "eaw-abstraction-layer.core.metatables"
local method = metatables.method

local my_custom_function_creator()
    local My_New_EaW_Function_Reference = method("My_New_EaW_Function_Reference")
    My_New_EaW_Function_Reference.expected = {
        {"game_object", "number"},
        {"game_object"}
    }


    function My_New_EaW_Function_Reference.return_value()
        local something = 0
        return something
    end

    return { My_New_EaW_Function_Reference = My_New_EaW_Function_Reference }
end

return my_custom_function_creator

Use the function field return_value() to implement a default return value for your new function (only if it's supposed to return something of course). To the end user this is more useful than no default return value, because they won't have to define return values for every function this way. Make sure that your new function references are wrapped in a creator function as shown above. The creator function should be the files' return value. The field expected should be a table where you define the expected input for your function. This field is NOT optional! If it is not defined or an empty table the abstraction layer will assume that no input is expected and throw errors if something was received anyway. For multiple possible argument configurations define each variant in a sub table as shown above. Finally, if you have created a new file, all you need to is add it to the make_eaw_environment() function in environment.lua like this:

insert_into_env(env, require "eaw-abstraction-layer.functions.my_file_name" ())

About

No description, website, or topics provided.

Resources

License

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages