Maps avaliable as mentioned in Jip Campagin Development are listed here
Currently there is no campaign editor for creating custom missions for Supreme Commander, but every mission is written in lua and it's easy to edit. You don't need to have too much experience with code since most of it is already used in the official campaign missions. In this tutorial I will explain how to create a mission and as an example we will use Prothyon - 16.
You can always look in one of the original campaign files to find the right code. There is a list of them. In order to open them in Map Editor, use files from this link. Don't forget you can also look at custom map scripts for useful code snippets, some of which aren't present in the original campaign. Additionally, you can look at in game lua files, and take apart the functions there for your own uses.
It is better to look at Forged Alliance missions because the code there is simpler and easier to read and understand. Also AI was improved a lot there. So for creating AI we will use FA missions.
Original Missions |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
C:\Program Files (x86)\THQ\Gas Powered Games\Supreme Commander\maps
All functions used in missions are defined in lua files that are in your FA installation. You can study then to find out more option you can use in your missions. They are commented and usually easy to understand. ".scd" files are just renamed zip.
Supreme Commander - Forged Alliance\gamedata\lua.scd
Supreme Commander - Forged Alliance\gamedata\mohodata.scd
Each mission is basically a map with extra files for AI and extended script file. In our example we can see several files. Always name these files same as the name of the folder they're in.
Folder is called Prothyon16 (followed by version, for example Prothyon16.v0004. Versioning is done automatically on the server so you don't need to care about it) and these are the files in it:
You can test your mission offline, which allows restarting and even saving, so its usually faster. In order to test your mission start the FAF version of FA from
C:\ProgramData\FAForever\bin\
You can create a shortcut on your desktop of that executable, go into its properties (right click) and change the target to include a command line switch:
ForgedAlliance.exe /init init_coop.lua
Game uses this file to get basic information about the map. Name, path to other files, armies, etc. It is created with map, once you save your map. You can edit these information.
All armies have to be listed here. This part is created once Games configuration is set in Map
Editor. Example:
Configurations = {`
['standard'] = {`
teams = {`
{ name = 'FFA', armies = {'Player1','UEF','UEFAlly','Objective','Seraphim','Player2','Player3','Player4',} },`
},`
customprops = {`
},`
factions = { {'uef'}, {'uef'}, {'uef'}, {'uef'} },`
},`
}}`
One coop addition that you find here is the table with available factions per spot, this restricts the lobby and offers the player to pick only from the factions you specify here. There are 4 tables with 'uef' as Prothyon mission is for 4 players and it's UEF only.
This file contains all data about armies and markers that was created in the Map Editor.
Alliances among armies can be changed in Armies tab by clicking on the army you want to edit. They need to be set for every army. By default all alliances are set to Neutral.
You also need to set AI faction here else AI won't do anything in it's base. Since Map Editor is from vanilla times it supports only original factions. If your AI is Seraphim, you will have to edit save.lua file, open it and find Seraphim army and change the value.
faction = 3,
If the faction isn't set correctly, the AI won't build any units!!
The last thing that needs to be changed is the AI Plan. Since we script our AI using functions from Forged Alliance we need to set it to no plan. We can't do this directly in Map Editor so we need to edit our save.lua file again. Do this for every army you have.
plans = '/lua/ai/opai/defaultblankplanlist.lua',
This part will take most of the time to create. Script file is in control of everything. Here we will import other files we will use, set how the mission will start, end and everything what will happen in between. As an example we will use Prothyon16_script.lua. Open that file while reading this tutorial because there won't be every line in this tutorial. I'm going to highlight only the most important parts of it.
First thing to do is import other files. That will be at the very beginning of script. We can add these files as we're adding new functions to our script. Basic ones that we will surely use are:
local Objectives = import('/lua/ScenarioFramework.lua').Objectives
local ScenarioFramework = import('/lua/ScenarioFramework.lua')
local ScenarioPlatoonAI = import('/lua/ScenarioPlatoonAI.lua')
local ScenarioUtils = import('/lua/sim/ScenarioUtilities.lua')
local Utilities = import('/lua/utilities.lua')
Other files to import are AI files, Voice overs and if we use more function, we add those files later.
We can also set up variables that will be accessible anywhere in the script. For example for debugging cinematics.
Other variables that you can see in the vanilla mission sctips are usually for objectives and attack triggers. They can be set at the beginning of the script or right away when adding the objective or script. It's up to you what you what you prefer. I followed the format of FA missions where it's set directly.
Beginning of the mission is handled by two functions, they are called by the game engine. One will spawn all the initial units (OnPopulate), second one will take care of everything else OnStart).
First one is caller OnPopulate.
function OnPopulate(scenario)
-- body
end
There are two ways to set color. We can choose from preset colors that are defines in ScenarioFramework or pick our own color by using RGB Color Code. We need to choose army that we're setting color to as we defined them at the beginnig of script
ScenarioFramework.SetUEFAllyColor(UEF)
SetArmyColor('UEFAlly', 71, 134, 226)
Easy way of setting custom colors for all coop players:
local colors = {
['Player2'] = {255, 200, 0},
['Player3'] = {189, 116, 16},
['Player4'] = {89, 133, 39}
}
local tblArmy = ListArmies()
for army, color in colors do
if tblArmy[ScenarioInfo[army]] then
ScenarioFramework.SetArmyColor(ScenarioInfo[army], unpack(color))
end
end
Army unit capacity can be changed using the SetArmyUnitCap function, we need to specify the army and the value that we want it to be. Example:
SetArmyUnitCap(UEF, 1000)
The easy way to restrict unit capacity for players is to use the SetSharedUnitCap function, it will divide the number among the current human players.
ScenarioFramework.SetSharedUnitCap(1000)
There are several functions to spawn units. We can either spawn only one unit specified in editor/save file or whole group. Depending on chosen function we can just spawn them, spawn as wreckage, add veterancy level, etc...
This is an example of basic function to spawn unit group
ScenarioUtils.CreateArmyGroup('Player1', 'Starting Base')
This code will create specific unit from editor and also allow us to work with it, in this case set it so it can't be reclaimed. If we would add parameter true in the function, our group would get spawned as wreckage.
ScenarioInfo.Gate = ScenarioUtils.CreateArmyUnit('Player1', 'Gate')
ScenarioInfo.Gate:SetReclaimable(false)
If we just want to spawn a unit(s) we call the function we want. If we need to give some orders or change atributes of the unit we need to set a variable, either local or global. Example of local variable can be seen few lines above when spawning a patrol. Local variable means that we can work with it only in current function (OnPopulate). Global variable can be used anywhere in the script and also in other lua files. Example of global variable is above when spawning the Gate.
AI uses platoons to control units. When AI builds units from factories, it will form a platoon out of them and then give order to the platoon. This way it keeps track on what units already have orders and what units are free to be used. We need to keep this in mind when spawning units. With the 4 most common ways:
ScenarioUtils.CreateArmyUnit()
ScenarioUtils.CreateArmyGroup()
ScenarioUtils.CreateArmyGroupAsPlatoon()
ScenarioUtils.CreateArmyGroupAsPlatoonVeteran()
For detailed description of spawn fucntions, look in ScenarioUtilities
Supreme Commander - Forged Alliance\gamedata\mohodata.scd\lua\sim\ScenarioUtilities.lua
You can add initial patrols that don't count into attack platoons you have ready in your AI file.
local units = ScenarioUtils.CreateArmyGroupAsPlatoon('UEF', 'EastBaseAirDef', 'GrowthFormation')
for k, v in units:GetPlatoonUnits() do
ScenarioFramework.GroupPatrolRoute({v}, ScenarioPlatoonAI.GetRandomPatrolRoute(ScenarioUtils.ChainToPositions('M1_East_Base_Air_Defence_Chain')))
end
These are the 3 most common ways of setting up a unit patrol. I will show it on the same example group.
First one will put the platoon on the patrol in a formation. The platoon will follow the chain as it's set in the map editor.
local platoon = ScenarioUtils.CreateArmyGroupAsPlatoon('UEF', 'M1_Patrol_Group', 'AttackFormation')
ScenarioFramework.PlatoonPatrolChain(platoon, 'M1_UEF_Base_Patrol_Chain')
Second example will put each unit of the platoon on the patrol. Each unit will recieve it's own order.
local platoon = ScenarioUtils.CreateArmyGroupAsPlatoon('UEF', 'M1_LandPatrol', 'AttackFormation')
for _, unit in platoon:GetPlatoonUnits() do
ScenarioFramework.GroupPatrolChain({unit}, 'M1_UEF_Base_Patrol_Chain')
end
Last one will put each unit of the platoon on it's own randomly generated patrol. It will use markers from the chain we specify. This one is mostly used for air patrols over bases. The same as in the example above applies to the formation.
local units = ScenarioUtils.CreateArmyGroupAsPlatoon('UEF', 'M1_Patrol_Group', 'AttackFormation')
for k, v in units:GetPlatoonUnits() do
ScenarioFramework.GroupPatrolRoute({v}, ScenarioPlatoonAI.GetRandomPatrolRoute(ScenarioUtils.ChainToPositions('M1_UEF_Base_Patrol_Chain')))
end
Unit's don't need to just patrol, you can also set move, attack move commands. It can be combined as well. Typical example where you would want to give more than one type of a command is when you have bases on the islands and you want to send some experimental unit on attack. If you put it just on patrol you might end up with unit staying close to shore, still under water and trying to attack on it's maximum gun range. It can't shoot because it's still under water so it will stay there forever.
To go around this you set up a chain that ends at the island and second chain that is on the island. First you put the unit on move using the first chain and then you set either patrol or attack move on the second chain.
This doesn't need to be used just for experimentals but for any group of unit that you first want to arrive somewhere and not attak everyone on the way there.
If we've already created AI, now is the time to activate it for the first mission. We need to import it's file at the beginning of the script,
local M1UEFAI = import('/maps/Prothyon16_DEMO/Prothyon16_DEMO_m1uefai.lua')
so we can call for functions that will activate it, there are two main bases in Prothyon - 16 DEMO, therefore two functions:
M1UEFAI.UEFM1WestBaseAI()
M1UEFAI.UEFM1EastBaseAI()
We can also add some starting recources to AI by using this code:
ArmyBrains[UEF]:GiveResource('MASS', 4000)
ArmyBrains[UEF]:GiveResource('ENERGY', 6000)
AI that is used in missions has already set buff for build power. It was 2x more build power on factories and 3x more build power on engineers. This is causing AI to use much more resources. Good way how to add more resources to AI the code below that allow are to multiply mass and energy income as much as we want. It can be set for every AI differently and changed during mission. You can ask why should be AI allowed to have more economy than player? The answer is simple, AI can't use resources efficiently so it needs to have more than player to be able to give a challenge. As you will learn in Creating AI part, everything AI builds are just scripted, predefined attacks, using marker chains we defined. AI can react to certain situations we add, but it will never be as good as player.
In order to use this buff we need to import file with buff definitions at the beginning of the script
local Buff = import('/lua/sim/Buff.lua')
This code will allow us to change multipliers to economy income. We just need to set our values and specify army that will be affected.
buffDef = Buffs['CheatIncome']
buffAffects = buffDef.Affects
buffAffects.EnergyProduction.Mult = 1
buffAffects.MassProduction.Mult = 1.5
for _, u in GetArmyBrain(UEF):GetPlatoonUniquelyNamed('ArmyPool'):GetPlatoonUnits() do
Buff.ApplyBuff(u, 'CheatIncome')
end
For changing values later in the mission or setting them for another army we need to use only this part:
buffAffects.EnergyProduction.Mult = 1
buffAffects.MassProduction.Mult = 1.8
for _, u in GetArmyBrain(UEF):GetPlatoonUniquelyNamed('ArmyPool'):GetPlatoonUnits() do
Buff.ApplyBuff(u, 'CheatIncome')
end
Second function we will use on start up is OnStart funciton
function OnStart(scenario)
-- body
end
The function for restricting units looks like this
ScenarioFramework.AddRestriction(army, categories.TECH2 + categories.TECH3 + categories.EXPERIMENTAL)
For restricting the same units for all players, you can use this function
ScenarioFramework.AddRestrictionForAllHumans(categories.TECH2)
ScenarioFramework.RemoveRestrictionForAllHumans(categories.TECH2)
To restrict ACU upgrades there is this function:
ScenarioFramework.RestrictEnhancements({})
We can set here camera angle so it doesn't start all the way zoomed out, revealing how the whole map looks.
Cinematics.CameraMoveToMarker('Cam_1_1', 0)
Last thing to do is to set which function will follow, if we didn't spawn ACU yet, we can do it during cinematics or after first objective is assigned. In our example next function is IntroMission1NIS. But before we get there we need to set what will happen at the end of the game.
The classic case is that the mission can end in 3 ways. Either player wins, dies or loses (this means failing a primary objective). You can set different ending depending on what objectives were or were not finished or failed.
We'll start with the simpliest one.
There is simple function to handle this situation as we usually want to do the same thing. Zoom on the dying ACU, play some dialogue "informing" player about his death and end the mission. For all this to happen you will need a new function in your script. Usually it's named PlayerDeath
function PlayerDeath(commander)
ScenarioFramework.PlayerDeath(commander, OpStrings.PlayerDies1)
end
Everything else is handeled by the funcion and after the dialogues finished it will show the "Operation Failed" pop up to quit.
This part can be as complicated as you want. You can include several camera movements, showing different part of the maps with different units or anything that comes to your mind to end the mission in a nice way.
The minimum is two functions, PlayerWin and KillGame
function PlayerWin()
if not ScenarioInfo.OpEnded then
ScenarioFramework.EndOperationSafety()
ScenarioInfo.OpComplete = true
ScenarioFramework.Dialogue(OpStrings.PlayerWins, KillGame, true)
end
end
function KillGame()
local bonus = Objectives.IsComplete(ScenarioInfo.M1B1Objective) and Objectives.IsComplete(ScenarioInfo.M1B2Objective) and Objectives.IsComplete(ScenarioInfo.M2B2Objective)
local secondary = Objectives.IsComplete(ScenarioInfo.M1S1Objective) and Objectives.IsComplete(ScenarioInfo.M2S1Objective) and Objectives.IsComplete(ScenarioInfo.M3S1Objective)
ScenarioFramework.EndOperation(ScenarioInfo.OpComplete, ScenarioInfo.OpComplete, secondary, bonus)
end
What this does is that first it checks if the mission hasn't ended yet, for example finishing the final objective right after you get killed or any other unlikely but possible case. ScenarioInfo.OpEnded is variable set inside ScenarioFramework.EndOperationSafety function. Now what this fucntion does, it sets alliances between armies to neutral, so there is no more shooting, it also sets the player's ACU unkillable. Next thing is to set variable ScenarioInfo.OpComplete, this is used in the KillGame function. Last part is to play the winning dialogue which is set to call the KillGame function after it's done.
The final function that has to be called to end the mission and show the "Operation Complete" window is ScenarioFramework.EndOperation. As you can see it can take up to 4 parameters, first 2 are minimum. All of them are true/false. First one is if the operation was succesful. Inside ScenarioInfo.OpComplete variable we saved true in the previous function. You can see it's used as the second parameter as well since that is if all primary objective are completed. When player wins it means that they are so there is no need for other check. Third and fourth parameters are for secondary and bonus objectives. To find out if all objevtives of a type are completed we need to check them all.
Cinematics are usually used as intro to main objectives, but it can be used anywhere. First thing to do is to import file with their functions we will use. As with every file importing, put it at the beginning of the script.
local Cinematics = import('/lua/cinematics.lua')
If we haven't set playable area yet, now is the time to do so.
ScenarioFramework.SetPlayableArea('M1_Area', false)
Setting camera angle is happening in map editor.
At the beginning we prepared variable to skip this upcoming cinematics. Now it's time to set it. Beacuse we will be spawning ACUs during cinematics, we need to set it twice, once with camera movent in first part, then just the ACU spawning in second part.
if not SkipNIS1 then
-- First part, everything that will happen during cinematics
else
-- Second Part, everything that will happen if we're skipping cinematics
end
First we need to enter Cinematic mode
Cinematics.EnterNISMode()
We can set several visual markers to get Vision on area we're looking. Any Blank Marker can be used for this. But it's recommended to create a new one.
local VisMarker1_1 = ScenarioFramework.CreateVisibleAreaLocation(30, 'M1_Vis_1_1', 0, ArmyBrains[Player1])
After camera moves away from these markers, we can destroy them by using this code:
ForkThread(
function()
WaitSeconds(2)
VisMarker1_1:Destroy()
WaitSeconds(2)
ScenarioFramework.ClearIntel(ScenarioUtils.MarkerToPosition('M1_Vis_1_1'), 40)
end
)
If we have Camera Info Markers ready from Editor, we can use them now.
It's handles by one function:
Cinematics.CameraMoveToMarker(ScenarioUtils.GetMarker('Cam_1_2'), 15)
After the last Camero Info marker we need to leave cinematics:
Cinematics.ExitNISMode()
There is a function in called SpawnCommander in ScenarioFramework.lua for easy ACU spawning. It's good to put the ACU into global variable ScenarioInfo Depending on how he want to ACU to be spawned we will use the parametrs of this function. The function has 7 parametrs.
SpawnCommander(brain, unit, effect, name, PauseAtDeath, DeathTrigger, enhancements)
Example from Prothyon - 16:
ScenarioInfo.PlayerCDR = ScenarioFramework.SpawnCommander('Player', 'Commander', false, true)
ScenarioInfo.SeraACU = ScenarioFramework.SpawnCommander('Seraphim', 'M5_Sera_ACU', false, 'Zottoo-Zithutin', false, false,
{'AdvancedEngineering', 'DamageStabilization', 'DamageStabilizationAdvanced', 'RateOfFire})
For spawning all coop ACUs at once we can use this for example. It's up to you if you want to set death trigger also on Coop ACU's.
ScenarioInfo.CoopCDR = {}
local tblArmy = ListArmies()
coop = 1
for iArmy, strArmy in pairs(tblArmy) do
if iArmy >= ScenarioInfo.Coop1 then
ScenarioInfo.CoopCDR[coop] = ScenarioFramework.SpawnCommander(strArmy, 'Commander', Warp', true)
coop = coop + 1
WaitSeconds(0.5)
end
end
Spawning Coop ACUs is slightly different in Prothyon since they get loaded onto transport and moved to the starting position. You can do different things with ACU spawning. All ACUs don't need to be spawned at the same time. You don't even need to give an ACU to the player, it can be a sACU, an enginner or just few combat units. Keep in mind that missions run in 'Sandbox' so it's just up to you how you design your mission.
Now let's get back to the Prothyon - 16. We are inside IntroMissionNIS() function and now we have to specify what will happen next, at the last line we call this function:
IntroMission1()
Before we get to first objective there is one more function in which we'll set mission number.
function IntroMission1()
ScenarioInfo.MissionNumber = 1
StartMission1()
end
There are several objective types, each require different target data to work.
Basic data every objective needs:
-- Name of our objective for script. M1P1 for Mission 1 Primary 1 etc...
ScenarioInfo.Name = Objectives.Type( -- Now we choose type of the objective
'primary', --Objective priority. Others are secondary, bonus.
'incomplete', -- Objective isn't completed yet. Other is **complete**.
'Title', -- Will be shown with objective on UI.
'Description', -- Will be shown with objective on UI.
'Action', -- This is required by only some objective types. Others can't have it.
{
target -- Last one is table with target. There are several data that can be here depending on objective type
}
)
FlashVisible = true, -- Intel flash over a target to show it on the map.
AlwaysVisible = true, -- Constant vision on the target.
MarkUnits = true, -- Highlights target units on the map.
MarkArea = true, -- Highlighs target area on the map.
ShowProgress = true, -- Shows progress of the objective. (2/8, etc...)
PercentProgress = true, -- Shows progress of the objective in percents.
ShowFaction = 'Faction', -- Icon of objective is defined by units that are targeted, this over rides it and whows faction icon instead.
NumRequired = 'Number', -- How many units are required to complete objective, usually used with capture
Units = {}, -- Table with target units
Area = 'Area', -- Name of the area as we named it in Map Editor/save.lua file.
Requirements = { -- Used for CategoriesInArea objective type.
{Area = 'Area', -- Name of the area as we named it in Map Editor/save.lua file.
Category = categories.type, -- Unit categories, it can be one of [these](/Mission-Scripting#categories) or unit IDs.
CompareOp = '', -- Options are: <=, >=, <, >, ==
Value = Number, -- Number we require.
ArmyIndex = Army -- Army we defined at the beginning of script.
},
},
Category = categories.UnitID, -- Forces icon of this unit.
Timer = Number, -- if nil, requires a manual update for completion ( Number in seconds.)
ExpireResult = 'complete', -- What happens when time runs up. Options are: complete, failed
Detailed description is in SimObjectives file
Supreme Commander - Forged Alliance\gamedata\lua.scd\lua\SimObjectives.lua
Now we will set up our first objective:
ScenarioInfo.M1P1 = Objectives.CategoriesInArea(
'primary', -- type
'incomplete', -- complete
'Destroy UEF Forward Bases', -- title
'Eliminate the marked UEF structures to establish a foothold on the main island.', -- description
'kill', -- action
{ -- target
MarkUnits = true,
Requirements = {
{
Area = 'M1_UEF_WestBase_Area',
Category = categories.FACTORY,
CompareOp = '<=',
Value = 0,
ArmyIndex = UEF,
},
},
}
)
Then we set what will happen when objective is complete. In our example we will move to another mission.
ScenarioInfo.M1P1:AddResultCallback(
function(result)
if(result) then
ScenarioFramework.Dialogue(OpStrings.BeachBaseDestroyed, IntroMission2, true)
end
end
)
This part is very important when you're designing your mission. It brings on option of non-linear story where player can decide what will happen. It can even lead to different endings, if you want to.
You can make a pop up dialogue and offers choices to the player. It looks similar to the pop-up you get when you want to exis and it asks you if you are sure. To get this into you mission you need just few simple lines. Random example of use:
local dialogue = CreateDialogue('Who do you want to help?', {'Rhiza', 'Fletcher'})
dialogue.OnButtonPressed = function(self, info)
dialogue:Destroy()
if info.buttonID == 1 then
AllyWithRhiza()
elseif info.buttonID == 2 then
AllyWithFletcher()
end
end
Pressing any of the buttons calls OnButtonPressed funtion of the dialogue, but we need to adjust this function for our needs. That is what the rest of the code does. For each button we need to create if/elseif' statement with info.buttonID == number as a
condition where number is an ID of the button. As you can expect it's in the order as you put the options into the table.
Campaign AI is different from skirmish AI. Everything that AI will do is scripted, from base design, expantions to every attack platoon. Each army AI has it's own file for every mission unless it survives to another mission. In that case, platoons will get different orders in
each mission.
We need to use Map Editor to design base and set up markers and chains AI will use. Lets start with base template
Go to Armies tab. Create new unit group in your army. It's better to have several subgroups so you always find the right one fast.
With group selected, right click on map and add structures.
First letter indicates unit pack |
Second letter is faction |
Third letter for type |
First number for general role |
Second number for tech level |
Third/Fourth number for individual unit |
d |
Units added in one of the Supreme Commander patches |
a |
Aeon |
a |
Air |
o |
Units for objectives in Supreme Commancder |
e |
UEF |
b |
Base |
u |
Units from Supreme Commancder |
r |
Cybran |
c |
Civilian |
x |
Units added in Forged Alliance |
s |
Seraphim |
l |
Land |
z |
Units added in FAF |
o |
Operation |
There are many exceptions to this rule. |
|
s |
Naval |
Each base requires two markers to work and a lot more for sending units. There are two marker types we are going to use. Rally Point and Blank Marker.
First thing to to is to set up Base Marker around which the base will be defined.
Another one is Rally Point.
Next step is set up all kinds of chains for units. Click on Chains tab and create a new chain . While having this chain selected, put Blank Markers on the map and they will create a chain. You can also add existing marker to chain by clicking on . marker can be also deleted from chain, but save your map first before doing so, editor might sometimes crash during it!
Once you have your base and markers ready, we can move to next part which will be putting the code together.
We need to create new lua file. Name this file same as your folder name plus specific name for this file. It's good to follow some simple format to know what this file is on the first sight. For example:
Prothyon16_m1uefai.lua
At the beginning of this file we will import other lua files.
This one will manage our base. Build and rebuild buildings, assembly attack platoons and send then on the field.
local BaseManager = import('/lua/ai/opai/basemanager.lua')
We will use functions from this file to give out platoons orders. Attack, patrol etc...
local SPAIFileName = '/lua/scenarioplatoonai.lua'
Next step is setting locals, here we will give index to our army, it's the same number as in the script file.
local UEF = 2
Now we will create base managers to all of our bases. Every base needs i t's own base manager. In Prothyon there are two bases in first part ofthe mission. The name of base managers doesn't matter but it's better to stick to one format. I'm using similar name format as in FA missions.
local UEFM1WestBase = BaseManager.CreateBaseManager()
local UEFM1EastBase = BaseManager.CreateBaseManager()
Next step is to set up specific base. This function will be called from out script file.
function UEFM1WestBaseAI()
UEFM1WestBase:Initialize(ArmyBrains[UEF], 'M1_WestLand_Base', 'M1_West_Base_Marker', 40, {M1_WestBase = 100})
UEFM1WestBase:StartNonZeroBase(``)
UEFM1WestBase:SetActive('AirScouting', true)
UEFM1WestBase:SetActive('LandScouting', true)
UEFM1WestBase:AddBuildGroup('M1_WestBaseExtended', 90, false)
UEFM1WestBaseAirAttacks()
UEFM1WestBaseLandAttacks()
end
Now, when we have base working we want to set up attack that will be built and sent from this base. There are two way to set up platoons. We either use already existing platoon from lua files or create completely new one. For basic attacks like, group of tanks or bombers we can use the easy variation and using already created platoon. I didn't manage to get naval platoons working properly so I'm using custom ones.
Files with templates we can use are located in:
Supreme Commander - Forged Alliance\gamedata\lua.scd\lua\AI\OpAI
If we want to used unit types from these files, only thing we need is to set which file to use and child type
Available Templates |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
There is an example of simple attack. AI will build platoon of 4 bombers and send them on patrol over selected marker chain.
function UEFM1WestBaseAirAttacks()
local opai = nil
local quantity = {}
-- Builds platoon of 2/3/4 Bombers
quantity = {2, 3, 4}
opai = UEFM1WestBase:AddOpAI('AirAttacks', 'M1_WestAirAttack1',
{
MasterPlatoonFunction = {SPAIFileName, 'PatrolThread'},
PlatoonData = {
PatrolChain = 'M1_Land_Attack_Chain'
},
Priority = 100,
}
)
opai:SetChildQuantity('Bombers', quantity[Difficulty])
end
We can also add build condition. In this example, platoon will be built only if player has more than 20 air mobile units.
opai:AddBuildCondition('/lua/editor/otherarmyunitcountbuildconditions.lua', 'BrainsCompareNumCategory',
{'default_brain', {'HumanPlayers'}, 20, categories.ALLUNITS * categories.MOBILE, '>='})
Lua files with all build conditins available are in this folder:
Supreme Commander - Forged Alliance\gamedata\lua.scd\lua\editor
In our custom template we can set as many unit types and unit count as we want. We will need to know ID's of the units so Unit Database will come in handy.
Temp = {
'NavalAttackTemp2',
'NoPlan',
{ 'ues0201', 1, 4, 'Attack', 'GrowthFormation' }, -- Destroyers
{ 'ues0202', 1, 2, 'Attack', 'GrowthFormation' }, -- Cruisers
{ 'xes0205', 1, 2, 'Attack', 'GrowthFormation' }, -- Shield Boat
}
Builder = {
BuilderName = 'NavyAttackBuilder2',
PlatoonTemplate = Temp,
InstanceCount = 1,
Priority = 400,
PlatoonType = 'Sea',
RequiresConstruction = true,
LocationType = 'SouthNavalBase',
BuildConditions = {
{ '/lua/editor/otherarmyunitcountbuildconditions.lua', 'BrainGreaterThanOrEqualNumCategory',
{'default_brain', 'Player', 20, categories.NAVAL * categories.MOBILE + categories.uel0203}},
},
PlatoonAIFunction = {SPAIFileName, 'PatrolChainPickerThread'},
PlatoonData = {
PatrolChains = {'M3_Air_Base_NavalAttack_Chain2', 'M3_Air_Base_NavalAttack_Chain4}
},
}
ArmyBrains[UEF]:PBMAddPlatoon( Builder )
Now when we have our AI ready, we need to import it's file to the beginning of script to use it.
local M1UEFAI = import('/maps/Prothyon16/Prothyon16_m1uefai.lua')
Next step is to activate it. This line will handle that:
M1UEFAI.UEFM1WestBaseAI()
Voice overs are a table of several variables. They could be defined it script file but to keep better track on them, they have their own file called _string.lua and are imported into script. In our example it is.
Prothyon16_DEMO_strings.lua
Right now there in no other way where to have videos and sound files than in Forged Alliance installation folder:
Supreme Commander - Forged Alliance\movies
Supreme Commander - Forged Alliance\sounds\Voice\us
One VO can contain more than one videos, they will be played one after another. The format is:
VOname = {
{text = `*`,`` ``vid`` ``=`*`, bank = `*`,`` ``cue`` ``=`*`, faction = ''},
{text = `*`,`` ``vid`` ``=`*`, bank = `*`,`` ``cue`` ``=`*`, faction = ''},
{text = `*`,`` ``vid`` ``=`*`, bank = `*`,`` ``cue`` ``=`*`, faction = ''},
}
And in our example:
-- Third objective intro 1 / Actor: Gyle / Update 22/05/2015 / VO Ready
airbase1 = {
{text = '[Gyle]: The island is now secure.', vid = 'Pro_16_airbase1.sfd', bank = 'G_VO1', cue = '21airbase1', faction = 'UEF'},
}
In order to use VO's we created in string file, we need to import this file at the beginning of the script
local OpStrings = import('/maps/Prothyon16_DEMO/Prothyon16_DEMO_strings.lua')
Function with VO is called Dialogue and it's defined in ScenarioFramework.lua so we need to import this file as well.
local ScenarioFramework = import('/lua/ScenarioFramework.lua')
Now we have everything ready to use VOs in our code. The format for is following:
ScenarioFramework.Dialogue(OpStrings.VOname, callback, critical)
Example without and with callback fucntion:
ScenarioFramework.Dialogue(OpStrings.intro1, nil, true)
ScenarioFramework.Dialogue(OpStrings.airbase1, IntroMission3)
Adding videos to supcom can be very troublesome if you don't know how, and don't have the right tools. Luckily, this wiki explains it all for you!
Required Tools:
The required tools are very difficult to find, so both of them are available for download
here.
Supreme Commander - Forged Alliance\movies
To add audio to your mission, the audio files first need to be prepared - supcom only accepts audio as wavebank (.xwb) and soundbank (.xsb) formats, both of which are needed to work.
Some useful words you should know about:
Here is a list of tools you require to add sounds to your map (or mod):
Everything else can be done from inside the tool. If you mess around with it, you should be able to work out how it works in 5 minutes, there is also a pretty good documentation of the tool here. Once you have made your wavebank (.xwb) and soundbank (.xsb) files, you need to place them into:s
Supreme Commander - Forged Alliance\sounds\Voice\us
Note that the sounds will only work if you are running the us localization of supcom. For the sounds to work with other languages, you need to put the files into their respective localization folders.
You can add compression to your sounds, which makes them take up roughly 4 times less space.
Supcom uses various categories for each sound to work out how loud they should be, and set their volumes correctly in the options menu. So you need to set them in the XACT project beforehand. Known categories are:
ACU Upgrades | |||
---|---|---|---|
Aeon | Cybran | UEF | Seraphim |
AdvancedEngineering T3Engineering ChronoDampener CrysalisBeam (Range) EnhancedSensors HeatSink (Fire Rate) ResourceAllocationAdvanced Shield ShieldHeavy ResourceAllocation Teleporter |
AdvancedEngineering T3Engineering CloakingGenerator CoolingUpgrade (Gun) MicrowaveLaserGenerator NaniteTorpedoTube StealthGenerator ResourceAllocation Teleporter |
AdvancedEngineering T3Engineering DamageStabilization (Nano) HeavyAntiMatterCannon (Gun) LeftPod RightPod Shield ShieldGeneratorField TacticalMissile TacticalNukeMissile ResourceAllocation Teleporter |
AdvancedEngineering T3Engineering AdvancedRegenAura BlastAttack (Splash Gun) DamageStabilization (Nano) DamageStabilizationAdvanced Missile RateOfFire (Gun) RegenAura ResourceAllocationAdvanced ResourceAllocation Teleporter |
sACU Upgrades | |||
Aeon | Cybran | UEF | Seraphim |
EngineeringFocusingModule ResourceAllocation Shield ShieldHeavy StabilitySuppressant (Gun) SystemIntegrityCompensator (Nano) Sacrifice Teleporter |
CloakingGenerator EMPCharge FocusConvertor (Gun) NaniteMissileSystem (AA) ResourceAllocation SelfRepairSystem StealthGenerator Switchback (Engineering) |
AdvancedCoolingUpgrade (Rate of Fire) HighExplosiveOrdnance (Splash) Pod RadarJammer ResourceAllocation SensorRangeEnhancer ShieldGeneratorField Shield |
DamageStablization (Nano) EngineeringThroughput EnhancedSensors Missile Overcharge Shield Teleporter |
'attack'
'support'
'artillery'
'scout'
'guard'
This part will contain mistakes that are made quite often, example warning in the log is taken a random mission so it won't be 100% as you see it in your mission. But the file and the name of function will be the same.
WARNING: Error running lua script: ...ments\github\fa\lua\ai\aiarchetype-managerloader.lua(52): attempt to call method `HasBuilderList'
(a nil value)
stack traceback:
...ments\github\fa\lua\ai\aiarchetype-managerloader.lua(52): in function `ExecutePlan'
...users\multimedia\documents\github\fa\lua\aibrain.lua(712): in function `ExecutePlan'
Solution: Set AI Plan for each army
If you managed to spawn a base with BaseManager, engineers are assisting factories, patrol in the base, but the base is not producing any units that it should.
Solution: Set Faction for AI Army
WARNING: SCR_LuaDoFileConcat: Loading ...
If the map loads all zoomed out and nothing happens, you might see this error in the log, it means you have a syntax error in the file it's showing. The number behind the file in brackets is suggestion on which line the problem is.
WARNING: BeginSession() failed: attempt to yield across metamethod/C-call boundary
stack traceback:
[C]: in function `WaitTicks'
c:\users\odstr\documents\github\fa\lua\siminit.lua(28): in function `WaitSeconds'
This error happens when you try to use WaitSeconds() function in the main flow of the script. In order to solve that you need to ForkThread() the function where you used WaitSeconds() or just the part where you're using WaitSeconds()
Usually it occurs in the cinematics, then you need to ForkThread() the whole function, or in some part of the code where you're using while cycle.