Up to this point we have created both the campaign file, and the map that will make up our campaign. In this tutorial, we go over some of the basics of the SCAR scripting system, and how it ties into markers, entities, and entity groups.
Basically, what we have at this point is a map with a pyramid like structure where the Space Marines are located and entrenched. We now want to add the eldar (player side), and make things a bit more interesting.
One of the first things that you should do before looking at this tutorial is take a look at the template SCAR docs that were released at the same time that this document was and try to understand them. These template files outline a basic structure that your missions should take. While you don’t have to follow the formatting presented in the template file, it is a good idea, as if everyone that reads this tutorial follows it, it’ll be easier for you to look at their scripts and know what’s going on and where you can find certain functions, this will obviously also be true for people looking at you scripts. You will also find the the Scar Doc library is a very useful tool as it includes a listing of all the accessible Scar functions.
Markers are one of the fundamental building blocks that you will use in mission creation. They are often used as triggers, to detect player proximity, and so forth. There are basically 3 different markers available to you:
Setting up and placing makers it not too difficult and is basically about determining, where you want events to be triggered, and what kind of events you want triggered. As can be seen on our map, we have a bridge, and we will trigger an event when any of the eldar units pass over the bridge. Note that we place the marker further back and extend the proximity radius of the marker. This is done in order to ensure that any elder units that happen to cross to the other side are detected when they get within some distance of the marines. If we had placed the marker at the end of the bridge, we would run into a problem, namely that of jumping units, and the eldar player being able to jump over the water with a unit, and not setting off the appropriate event. Unit movement, and the jump ability is something that you should keep in mind when designing your maps and placing down markers.
Once placed down, our marker looks like this:

Name the marker whatever you want, but in general it's a good idea to have a naming convention setup so as to not confuse groups, markers and so forth. We will be prefixing markers with mk_ squad group with sg_ and e groups with eg_, it’s suggested that you follow this convention.
Another use for markers is in NISes and IE's. We will setup a marker near the relic point found on the top of the hill, which we will use to unveil the relic point to the user. We can set it up in the following way:

This ensures that the relic, and some of its surrounding are visible but that none of the enemy structures or units become visible.
Now, with all makers set, let us create the core of our mission the single player scar script that will determine when we have won the mission, when to trigger NISes and when to trigger other events. If you haven’t looked at the SCAR template document, this would be the perfect time to do it, You may also want to download
SCiTE, as it’s an excellent text editor that with syntax highlighting that was used to develop the SP mission in Dawn of War.
Create a scar script in the SP campaign directory with the same name as the mission you're scripting (ie. For the first elder mission ME01.scar)
There are a few things that a Scar scripts needs to contain in order to make it work, these functions are necessary, and need to have the names specified here, or else the game will report a Fatal Scar Error:
The following function/statements are not necessary but are recommended:
Alright, now that you know what the SCAR file actually has to contain, and that you’ve looked through the SCAR template file, let's get down and dirty and start creating a single player mission.
The first thing you'll want to do is define the players on the races that the players control, whether it be the human player or the AI player, defining them is essential. This is what the OnGameSetup function is for. In here we will use the Setup_Player call to define the human player (the elder) and the AI player (Space Marines). This is simply accomplished by the following commands:
g_Player1 = Setup_Player(0, "Eldar ", "eldar_race", 1) g_Player2 = Setup_Player(1, "7th Assault Division", "space_marine_race", 2)
The first argument is the player index of the player. In single player 0 will be the index of the human player, all other indices are that of AI opponents, the second argument is the name that the game will use for the player. The third argument is the race of the player, the acceptable values for this are space_marine_race, elder_race, ork_race, chaos_race.
Thus OnGameSetup ends up looking like this:
function OnGameSetup() g_Player1 = Setup_Player (0, "Eldar ", "eldar_race", 1) g_Player2 = Setup_Player (1, "7th Assault Division", "space_marine_race", 2) end
From this point on, things get a little hairy. After OnGameSetup, the OnInit function is called, which branches off to various other functions (most of which only branch further down a level or two at most). We’re going to go through each of the steps of OnInit one at a time. If you’re interested in a certain part of mission creation, you might want to skip to that section of the documentation. Please note that some of the functions will be described in the next tutorial (specifically the and IE stuff), but as feel free to experiment
The Rule_SetResearchLevel function should be used to grant players research items and set the global research level for the mission. In our case, we’re going to grant the Eldar rangers infiltration research. We use the Player_GrantResarch function to do this, valid string sequences for this function are the filenames (obvious, without the lua extension) that can be found in : Data\Attrib\Research.
function Rule_SetResearchLevel( resLevel )
--[[ DoW Specific!! Restrict_SpaceMarines( g_Player1, resLevel ) Unrestrict_ResearchWithAlerts( g_Player1, "marine_requisition_research_2", "none", "none" ) Unrestrict_ResearchWithAlerts( g_Player1, "marine_power_research_2", "none", "none" ) ]] --[[ note: not using resLevel]] Player_GrantResearch(g_Player1, "eldar_ranger_infiltration_research") end
There are several function calls within OnInit that deal directly with the sequence, these are:
These functions will be described in the next tutorial. However, feel free to play around with these functions on your own.
This rule setups up the playlist for the mission. While it isn’t a necessary call obviously music adds a lot to the atmosphere of a mission and you should include it. First, before adding any songs, we need to clear the current playlist, this is done with the Sound_PlaylistClear command. The valid arguments for this function are PC_Music and PC_Ambient. Since we’re clearing the music we have something like this:
We then add a bunch of tracks with the Sound_PlaylistAddTrack function, set the order of the playlist to sequential using the Sound_PlaylistSetOrder function and set the silence between the tracks from 20 to 70 second using the Sound_PlaylistSetSilence.
We then setup the ambient sounds in a similar way.
Our Rule_SetupMusicPlaylist ends up looking something like this:
function Rule_SetupMusicPlaylist()
--[[ clear the current playlist ]]
Sound_PlaylistClear( PC_Music )
--[[ add tracks to the playlist ]]
Sound_PlaylistAddTrack( PC_Music, "music_ork_theme" )
Sound_PlaylistAddTrack( PC_Music, "music_invasion_theme" )
Sound_PlaylistAddTrack( PC_Music, "music_force_commander_theme" )
Sound_PlaylistAddTrack( PC_Music, "music_theme_spacemarines_01" )
Sound_PlaylistAddTrack( PC_Music, "battle_ingame_01" )
Sound_PlaylistAddTrack( PC_Music, "ambient_ingame_02" )
--[[ mark these tracks to play randomly ]]
Sound_PlaylistSetorder( PC_Music, true )
--[[ add 5 to 10 seconds silence between tracks ]]
Sound_PlaylistSetSilence( PC_Music, 20, 70 )
--[[ clear the ambient playlist ]]
Sound_PlaylistClear( PC_Ambient )
--[[ add tracks to the playlist ]]
Sound_PlaylistAddTrack( PC_Ambient, "ambient_wind" )
--[[ mark these tracks to play randomly ]]
Sound_PlaylistSetorder( PC_Ambient, false )
--[[ add 5 to 10 seconds silence between tracks ]]
Sound_PlaylistSetSilence( PC_Ambient, 5, 10 )
endThe Rule_PresetAI function is used, as it’s name suggests, presetting the AI. This is done in order to disable/enable certain AI’s at certain points in time, or to lock groups away from the AI, or for other various reasons. This problem arises since during mission creation that even though the units you control will respond to any orders you give them though the scripting system, they will also respond to any orders that the CPU gives them. This leads to some unwanted situations, such as AI players overwriting a mission designer’s command and going off to do their own thing, thus potentially breaking any script command that were relying on certain unit behaviors. To address this problem the SCAR scripting system has a set of CPU command that will lock the computer out from controlling any group of units that the designer of the mission chooses. This allows the player to have total control of the enemy AI, and provides commands to units without fear of the AI overwriting those commands.
Furthermore, while it's typically a good idea to restrict the AI's control of units during a single player mission, you certainly don't want to be managing the entire enemy army through SCAR, hence it's also a good idea when creating to add an AI that will handle certain aspects of the battle for you, for instance such as combat. An example of this would be of a mission with two AI's, with one disabled. The other AI would be used as a combat AI, units would be given to it periodically, and the AI would be able to do whatever it pleased, but told to attack (probably through the CPU_ForceAttack() command).
Note that locking units/groups is accomplished after they've been added to the group. That is, if you want to lock a group, you have to make sure that any units that you want to lock along with it, are locked too. For instance, if you create a new squad and wish it to add to a group that already has an existing locked squad group, what you would do is first unlock the locked group, then add the new squad, then re-lock the group. This way any units that are added to the group are also controlled by you, the player, and not the AI.
Following the above suggestions, we will lock the sg_SpaceMarines1 group, so that the AI doesn’t take over, and so that they continuously try to attack the Eldar Farseer. To do this, we use the Rule_PresetAI() function that you might have noticed in the example SCAR file. We basically take away control from the CPU of the space marine group as follows:
Cpu_LockSGroupAcrossPlayers( "sg_SpaceMarineSquad1")
This will prevent the CPU from overwriting the attack order that you give the FC and his accompanying squad. So, our Rule_StartAI() looks like this:
function Rule_PresetAI() Cpu_EnableAll(true) -- prevent the AI from control the space marine squad + force commander -- as we want them to be constantly attacking. Cpu_LockSGroupAcrossPlayers( "sg_SpaceMarineSquad1" ) end
Finally, includes firing off the Gamestart rule and defining the mission objectives. To fire off the Gamestart rule we simply add the following function to OnInit
Rule_Add( Rule_GameStart )
And our objectives are defined as follows:
Objective_KillSpaceMarines = {title_id = 200000, short_desc_id = 200001, help_tip_id = 200002 }
Objective_CaptureRelic = {title_id = 300000, short_desc_id = 300001, help_tip_id = 300002 }The objective structure is as follows: title_id – is the title that appears in the objective menus, short_desc_id – the short descritption that appears at the bottom of the page, help_tip_id – the help tip that appears when the objectives first appears.
Up to this point we’ve set everything up and have basically launched the scripting system, and the mission has started running. We just need to set several things up, get the AI running, and we’ll be set.
We’re now going to setup the AI, primarily we’ll set its difficulty level and enable it.
We setup difficulty level based on the level that the player chose at the campaign screen.We do this trough the Rule_SetDifficultyLevel call which looks like this:
function Rule_SetDifficultyLevel( difficultyLevel ) Difficulty_SetForAll( difficultyLevel ) end
This would basically set the different difficulties for different AI's according to the difficulty level the player choses. Thus, we call Rule_SetDifficultyLevel since it allows us to easily change these values, and have only one function that handles all our AI difficulty setup. You can also use this function for various other things like adding resource values, changing variables for the number of starting units a player begins (ie. 3 space marine squads on easy vs. 1 space marine squad on hard) and more.
You may be wondering why we're not just calling Difficult_SetForAll directly. The explanation is related to the number of AI's that we have to deal with. Typically in a mission, chances are that more then one AI will be involved in the battle, you'll naturally want to set the difficulty of each AI separately, so for instance we could have something like this:
-- now if we wanted to further adjust the difficulty level for the cpu -- we could do so by manually adjusting the difficulty level for each cpu player -- based on the selected difficulty leve, like so: -- easy if difficultyLevel == DIFFICULTY_EASY then Cpu_SetDifficulty( g_Player2, AD_Standard ) Cpu_SetDifficulty( g_Player3, AD_Easy ) -- medium elseif difficultyLevel == DIFFICULTY_NORMAL then Cpu_SetDifficulty( g_Player2, AD_Standard ) Cpu_SetDifficulty( g_Player3, AD_Standard ) -- hard elseif difficultyLevel == DIFFICULTY_HARD then Cpu_SetDifficulty( g_Player2, AD_Hard ) Cpu_SetDifficulty( g_Player3, AD_Standard ) end
Now that we’ve set the difficulty level, all that remains is to enable the AI, the Rule_StartAI function does just this. You could also do a similar setup to in the Rule_PresetAI function, whichever one you prefer, but the only thing that we’ll use this function for is enabling the AI, which should look familiar to you:
function Rule_StartAI() Cpu_EnableAll(true) end
Now we need to write the player wins and player loses rules, these are necessary for any single player mission, as the game will not know when the mission is complete without them. First the player loses rule:
function Rule_PlayerLoses()
if Event_IsAnyRunning() == false and SGroup_Count( SGroup_FromName("sg_Farseer")) == 0
then
Rule_RemoveAll()
Fade_Start(4, false)
World_SetTeamWin( g_Player2, "" )
Rule_AddIntervalEx( Rule_GameOver,5,1 )
Rule_Remove( Rule_EndGameLose )
end
endIn our mission, since the player has no buildings to speak of, the player loses if his farseer gets killed. This rule simply keeps checking the number of entities that are in the farseer group, when the number drops to 0 (the farseer is dead), the AI team is given the win, the win rule is called and the current rule, like always, is removed from the world. You may be asking why we call Rule_AddIntervalEx, this is to slightly delay the end of the mission, so that it doesn’t end abruptly when the Farseer dies as this just looks bad.
The player wins rule, is quite similar to the player loses rule, and can be described as follows:
function Rule_EndGameWin() if Objective_Exists( Objective_KillSpaceMarines.title_id) and Objective_GetState( Objective_KillSpaceMarines.title_id ) == OS_Complete and Objective_Exists( Objective_CaptureRelic.title_id) and Objective_GetState( Objective_CaptureRelic.title_id ) == OS_Complete then Rule_RemoveAll() Util_StartNIS(EVENTS.NIS_Closing) end end
Basically, the player wins when he has both captured the Relic as well as killed the space marine squad. You may be wondering what happens once we trigger the final, will the game just ends. In our case, yes, since we’re actually calling the function to end the game from within the sequence.
With that being said, we can now start setting up the mission. First we'll need win and lose rules. The win conditions will consist of two different events, the death of the Space Marines squad, and the capture of the relic at the top of the pyramid. We'll tackle the former objective first. We need to write a function that checks if the winning condition has been achieved. Remember, basically what you're doing is checking every second, whether the state of the world is such that the rule has completed. For instance, to check whether the marines are dead we can use the following rule:
--[[ Kill Marine Squad
]]
function Rule_KillSpaceMarineSquad()
if Event_IsAnyRunning() == false and SGroup_Count( SGroup_FromName("sg_SpaceMarineSquad1")) == 0
then
Util_ObjectiveComplete( Objective_KillSpaceMarines.title_id )
Rule_Remove( Rule_KillSpaceMarineSquad )
Rule_AddOneShot( Rule_IE_CaptureRelic, 10 )
elseif Event_IsAnyRunning() == false and Objective_Exists(Objective_KillSpaceMarines.title_id) == false then
Util_ObjectiveCreate(Objective_KillSpaceMarines, true)
end
endWhat this function does is first check if the given objective (to kill the space marines) already exists. If it does not, we create the objective, if it does, then we check whether the given objective has been completed. That is we check whether the group with group name sg_SpaceMarines1 is empty (all the units within it are dead, you did put units into the group, right?). We also check and ensure that no events are running. If these things hold the objective to kill the space marine is marked as completed, and the current rule is removed. Generally this is the way that you want to setup any objective rule that waits for a certain event to happen. Keep polling the event till it happens, and when it does, simply complete it.
The Capture Relic objective looks quite similar:
--[[ Capture Relic
]]
function Rule_CaptureRelic()
--[[ Objective Successfull ]]
if Event_IsAnyRunning() == false and EGroup_IsCapturedByPlayer("eg_RelicPoint", g_Player1, false) then
Util_ObjectiveComplete( Objective_CaptureRelic.title_id )
Rule_Remove( Rule_CaptureRelic )
--[[Objective doesn't exist, create it ]]
elseif Event_IsAnyRunning() == false and Objective_Exists(Objective_CaptureRelic.title_id) == false then
Util_ObjectiveCreate(Objective_CaptureRelic, true)
end
endThe main difference between the KillSpaceMarines objective and this objective is is obviously checking if the Relic Point has been captured by a given player. In this case g_Player1. If you look at the description of EGroup_IsCapturedByPlayer you will see that the first argument is the name of the EGroup that we want to capture, the second is the player that we’re checking for, and the third is a Boolean called “all”. Since we only have one entity in our egroup setting this to either true or false will not make a difference.
Finally we’ll be setting up the rule that watches for the player’s crossing of the bridge. We employ the previous played mk_ElminiateIncomingEldar market, and determine if any of the player’s squad are within its proximity radius, this can be done with the Player_AreSquadronNearMarker function for which the first argument is the player that we’re checking, and the second is the marker. If this happens, we will do two things. Launch an in game NIS sequence (which we will discuss next tutorial) as well as tell the space marine group to attack, which is done with the Cmd_AttackSGroup command, whose first argument is the Sgroup that’s being given the order and whose second argument is the Sgroup which is to be attacked.
function Rule_WatchForEldarTransgression()
if Player_AreSquadsNearMarker(g_Player1, "mk_EliminateIncomingEldar") then
-- FADE BLACK
EventCue_Enable(false)
Fade_Start( 0, false )
W40k_Letterbox( true, 0 )
Util_StartIntel( EVENTS.NIS_FCAlive )
Cmd_AttackSGroup("sg_SpaceMarineSquad1", "sg_Farseer");
Rule_Remove( Rule_WatchForEldarTransgression)
end
endOne of the last things that you’ll probably want to do is setup the autosave feature. This is a relatively simple task. A typical autosave function looks something like this:
function Rule_AutosaveAfterFCDead() if not Event_IsAnyRunning() then Util_Autosave( "Eldar M1" ) Rule_Remove(Rule_AutosaveAfterFCDead) end end
So for each autosave you’d have a similar function. To fire off the autosave simply call Rule_AddRule(<functionName>) from the appropriate place. We’ll place two autosaves, one right after the introductory (which is a good idea for all missions), the we’ll place after the Force Commander and his entourage are killed.
So, now we have a basic mission, with basically place holder NIS’es. In the following tutorial we will create a set of NISes and IE's that better tell the story of associated with the mission.
ME Tutorials
Mission Editor
RDN Wiki Home