[TUTORIAL] Single Player Mission

I have been asked about making a single player mission tutorial on several occasions, so here goes…

I am not setting out to make a comprehensive guide, but rather to give an overview of the basics to help a newcomer to get started. I will not go into details of function arguments and usage. For that, see:

hw2wiki.net/wiki.hw2.info/FunctionReference.html

We will have a quick look at one of HW2’s stock missions to review the file structure and the content of each of the files required to make a single player mission.

Firstly, let’s have a look at the file structure for the first tutorial mission:

leveldata/campaign/tutorial/m01/

As with all Homeworld modding, the file structure must be respected or it will not work.

Within the mission directory, the following two files are the minimum necessary for a working mission:

  1. [mission_name].level
  2. [mission_name].lua

The following optional files may also be found:

  1. teamcolour.lua
  2. [mission_name].dat
  3. [name].nis
  4. [script].lua

We will look at these files in some detail to examine their roles and contents.

5 Likes

m01.level

The level file contains the map information and starting fleets and resources. The file is divided into two “chunks”.

1) The “DetermChunk”

This chunk contains anything that can change in the course of the mission, e.g. ships (could move or die), asteroids/salvage (could get harvested), dust clouds (could get charged). It also contains a few other odds and ends, e.g. volumes and set cameras.

Here are a few examples of the contents:

A ship. The following code adds a Hiigaran carrier belonging to player 0 in hyperspace at the coordinates 1614, 141, 5229 and rotation 0, 0, 0. The ship is added to the sobgroup “Hgn_Carrier”, which can later be interrogated by scripts (we will see this in the .lua file).

addSquadron("Hgn_Carrier", "Hgn_Carrier",	{1614, 141, 5229},	0, {0.000, 0.000, 0.000},0,1)
...
createSOBGroup("Carrier")
addToSOBGroup("Hgn_Carrier","Carrier")

A camera. The following line adds a camera that is looking at the point {1617.764, -176.950, 5221.494} from the point {2980.118, 726.802, 5173.691}. The camera can be called up by the .lua script during the mission.

addCamera("camera1",	{1617.764, -176.950, 5221.494},	{2980.118, 726.802, 5173.691})

An asteroid. The following line adds an asteroid:

addAsteroid("Asteroid_3",	{4366, 653, -3624},	100, 0.000, 0.000, 0.000, 0)

The following line is required. It sets the size of the map:

setWorldBoundsInner({0.00, 0.00, 0.00}, {30000.00, 30000.00, 30000.00})

2) The “NonDetermChunk”

This chunk contains static things, like pebbles (small asteroids that cannot be harvested or targetted), plus a few parameters.

This line adds a pebble:

addPebble("Pebble_0",	{-10757, -6195, -13868}, 0.000, 0.000, 0.000)

The following parameters do what they say on the tin. Playing with them is the best way to understand how they affect the level.

fogSetActive(0)
setGlareIntensity(0.000000)
setLevelShadowColour(0.000000, 0.000000, 0.000000, 1.0)
loadBackground("m03")
setSensorsManagerCameraDistances(12000,60000)
setDefaultMusic("Data:sound/music/AMBIENT/AMB_03")
4 Likes

m01.lua

OnInit()

The lua file contains the mission’s scripting and the events. The top of the file contains variables and some generic functions that will be used later on. Have a look for function OnInit() - this function must exist in the lua file. It is the function that will be called when the game loads the mission. The OnInit() function is normally used to set up the initial mission conditions. For the tutorial, the function is used mainly to restrict certain actions that the player isn’t supposed to do yet:

function OnInit()
	SPRestrict()
	addObjective("learn")
	-- hide the popup that happens automatically
	UI_HideScreen("ObjectivesPopup")

	HW2_SetResearchLevel( 1 )
	
	Sound_EnableAllSpeech(1)
	
	Universe_EnableSkip(0)
	--scuttle OFF
	Universe_EnableCmd(0, MUI_ScuttleCommand)
	-- retire OFF
	Universe_EnableCmd(0, MUI_RetireCommand)
	--Camera off
	Camera_AllowControl(0)
	-- launch functions off
	UI_SetElementEnabled("NewLaunchMenu","launchButton",0)
	UI_SetElementEnabled("NewLaunchMenu","launchAllButton",0)
	UI_SetElementEnabled("NewLaunchMenu","stayDockedButton",0)
	UI_SetElementEnabled("NewLaunchMenu","autoLaunchButton",0)
	
	Sound_SpeechSubtitlePath("speech:missions/TUT_01/")
	-- initialize permissions
	-- panning off
	Camera_UsePanning(0)
	-- moving off
	Universe_EnableCmd(0, MUI_MoveCommand)
	-- (de)selecting off
	Universe_AllowSelect(0)
	
	SobGroup_Create("sob_BothFighters")
	SobGroup_Create("asteroidShip")

	SobGroup_CreateSubSystem("Carrier", "FighterProduction" )
	
	SobGroup_AbilityActivate( "Carrier", AB_Scuttle, 0 )
	SobGroup_AbilityActivate( "Carrier", AB_Retire, 0 )

	Event_Start("intelevent_SetupWorld")

	Rule_Add("Rule_Intro")
end

Events

The OnInit function also calls the first “event”, in this case intelevent_SetupWorld. Events are stored in a table called “Events” at the end of the file. An event is effectively an in-game a cutscene. This event is not very sophisticated - it simply hides some UI things, brings the carrier out of hyperspace and selects a camera:

Events = {} -- the name of this table must always be Events - this is what the game looks for

Events.intelevent_SetupWorld = 
{
	{
		{"Universe_EnableSkip(0)",""},
	},
	{
		{"UI_HideScreen('NewTaskbar')",""},
		{"UI_HideScreen('ObjectivesPopup')",""},
		{"UI_HideScreen('UnitsMenu')",""},
		{"UI_HideScreen('ResourceMenu')",""},
		{"UI_HideScreen('UnitCapInfoPopup')",""},
	},
	{
		{"SobGroup_ExitHyperSpace ('Carrier', 'Carrier_EnterVolume')", "" },
		{"Camera_UseCameraPoint('camera1')",""}, 
	},
}

Rules

The OnInit function also adds the first mission “Rule”. A Rule is a function that is continually executed until it is removed. The first rule in m01 is Rule_Intro:

function Rule_Intro()
	if (Event_IsDone("intelevent_SetupWorld")==1) then
		Event_Start("intelevent_intro")
		Rule_Add("Rule_IntroTaskbar")
		Rule_Remove("Rule_Intro")
	end
end

This rule simply waits for the event “intelevent_SetupWorld” to finish. If the event is finished, it kicks of another event (“intelevent_intro”) and adds another rule “Rule_IntroTaskbar”. Then it removes itself to prevent it from being called again.

Nothing very exciting happens in the tutorial, but obviously the main functions you will be using are the ones relating to ships (via SobGroups). The event “intelevent_dragselection” has a function that creates a scout from the carrier:

Scout_1 = SobGroup_CreateShip('Carrier', 'Hgn_Scout')

And in the Rule_PreMoveIssued, the code checks for whether this group is doing the “AB_Move” ability, i.e. is it moving?

local hasIssued1 = SobGroup_IsDoingAbility(Scout_1, AB_Move)

There are a ton of functions relating to SobGroups. They either perform a test on a group (is it dead? is it under attack?) or issue an order (move, guard, attack).


teamcolour.lua

This file does exactly what you would expect: it defines the paint scheme, badge and engine trail used by each team in the mission. Here is an example from the Taiidan Republic Mod:

teamcolours = {
	[0] = {{1, 1, 1},            {0, 0.502, 0.502},    "DATA:Badges/27th_black.tga",             {1, 1, 1},            "data:/effect/trails/tai_trail_clr.tga"}, 
	[1] = {{.900,.900,.900},	     {.100,.100,.100},     "DATA:Badges/Vaygr.tga",                  {.921,.75,.419},       "data:/effect/trails/vgr_trail_clr.tga"},
	[2] = {{0.365, 0.553, 0.667}, {0.8, 0.8, 0.8},       "DATA:Badges/Hiigaran.tga",               {0.365, 0.553, 0.667}, "data:/effect/trails/hgn_trail_clr.tga"},
}

Note that the British spelling of “colour” is used, who knows why…

4 Likes

Thanks for that - very informative… :slight_smile:

Suggestion: Add to that tutorial how missions get tested and whether some modders use 3D editors in assistance in order to figure out camera coordinates or if everything is simple pure in game testing and so on…

Yes I should have mentioned @PayDay 's excellent map editor:

3 Likes