Basics
What is Spectator Mode?
Spectator mode allows spectator players to observer a live game with replay-like features like Fog of War disabled, ability to see player resources, commander, and what the player is producing. The script provided in this tutorial adds 4 spectator slots to a 1v1 map. This script is only designed for 1v1 maps.
How Spectator GUI looks with CPU players
Here's a YouTube video which explains and demonstrates all the features included in Spectator mode:
Thanks to A_E for helping me with testing and doing the Spectator mode video with the interview.
How Spectator GUI looks with CPU players
Here's a YouTube video which explains and demonstrates all the features included in Spectator mode:
Thanks to A_E for helping me with testing and doing the Spectator mode video with the interview.
Using the Spectator mode
- Always use fixed starting locations
- Players must always be in top slots of the teams
- Spectators must be spread evenly between teams
- If the amount of spectators is odd, the one extra spectator should be in team 1.
Getting started
Tools you need
In this tutorial you will learn how to add Spectator mode to your custom map. To do this, you will need a basic text editor. Microsoft Notepad is suitable for this job and you already have it. In case you'd like to consider getting a bit better tool for this, I would recommend using notepad++, which you can download from from notepad-plus-plus.org for free.
Important Note
One thing you should notice and understand before going any further:
Every time you save your map in WorldBuilder, _ID.scar will be re-generated by the WorldBuilder. This means you have to add the code every time after saving the map if you wish to export the package. If you haven't saved your map yet, do it now!
Every time you save your map in WorldBuilder, _ID.scar will be re-generated by the WorldBuilder. This means you have to add the code every time after saving the map if you wish to export the package. If you haven't saved your map yet, do it now!
Map Files
Have you ever looked into your map folder? Perhaps you have and you know there are bunch of files laying around related to your map. For example the .sgb file, which is the main file containing all the data of your map. There are also other files, such as .tga, .info, .options, and [b]_ID.scar.
The one we are interested in this tutorial is _ID.scar.
The one we are interested in this tutorial is _ID.scar.
_ID.scar file
Well, what is this file in particular and why are we so interested about it?
_ID.scar contains the list of Scar markers, entity groups aka. egroups, and squad groups aka. sgroups from your map. This file is sort of a directory of items listed above. This file also gives us the ability to include custom SCAR script to the map. In this tutorial we will use it for a very advanced implementation.
_ID.scar contains the list of Scar markers, entity groups aka. egroups, and squad groups aka. sgroups from your map. This file is sort of a directory of items listed above. This file also gives us the ability to include custom SCAR script to the map. In this tutorial we will use it for a very advanced implementation.
Code & Preparations
Map Preparation
Make sure you have 6 slots available in your map. You should place starting positions for players 2, 3, 5, and 6 outside of the playable area. This is recommended because spectator player HQs will be removed from the map. Unfortunately the HQ leaves a hole to ground. This hole does not cause issues but then again it does not look pretty. In this context "outside of playable area" stands for the area you paint with inactivity state tool. To find out more about inactivity state tool, see Map-making Tutorial Index.
Adding the Code
Open _ID.scar in notepad / notepad++. It should look something like this:
Normally, when no Scar markers are placed nor squad/entity groups assigned (sgroups and egroups) _ID.scar will look exactly like this.
Let's get into adding code which adds the Spectator Mode:
To make this a bit easier, you should remove ALL lines by pressing CTRL+A and Backspace.
This is fine as you don't need any sgroups, egroups, or markers for this mode. I'm sure you can figure it out by yourself in case you need them for something.
After removing all lines, copy and paste this code in:
Alternatively you can also obtain this code from pastebin.com.
Code
function OnInitID()
-- [[ Markers ]]
-- [[ Squad Groups ]]
-- [[ Entity Groups ]]
end
Normally, when no Scar markers are placed nor squad/entity groups assigned (sgroups and egroups) _ID.scar will look exactly like this.
Let's get into adding code which adds the Spectator Mode:
To make this a bit easier, you should remove ALL lines by pressing CTRL+A and Backspace.
This is fine as you don't need any sgroups, egroups, or markers for this mode. I'm sure you can figure it out by yourself in case you need them for something.
After removing all lines, copy and paste this code in:
Code
function OnInitID()
squads_german = {
{sbp = SBP.GERMAN.ASSAULT_GRENADIER_SQUAD_MP, name="Assault Grenadier Squad"},
{sbp = SBP.GERMAN.GRENADIER_SQUAD_MP, name="Grenadier Squad"},
{sbp = SBP.GERMAN.MG42_HEAVY_MACHINE_GUN_SQUAD_MP, name="HMG42 Squad"},
{sbp = SBP.GERMAN.MORTAR_TEAM_81MM_MP, name="Mortar Squad"},
{sbp = SBP.GERMAN.PANZER_GRENADIER_SQUAD_MP, name="Panzer Grenadier Squad"},
{sbp = SBP.GERMAN.PIONEER_SQUAD_MP, name="Pioneer Squad"},
{sbp = SBP.GERMAN.SNIPER_SQUAD_MP, name="Sniper Squad"},
{sbp = SBP.GERMAN.PAK40_75MM_AT_GUN_SQUAD_MP, name="Pak40 AT-Gun Squad"},
{sbp = SBP.GERMAN.BRUMMBAR_SQUAD_MP, name="Brummbär Squad"},
{sbp = SBP.GERMAN.SDKFZ_251_HALFTRACK_SQUAD_MP, name="Halftrack Squad"},
{sbp = SBP.GERMAN.OSTWIND_SQUAD_MP, name="Ostwind Squad"},
{sbp = SBP.GERMAN.PANTHER_SQUAD_MP, name="Panther Squad"},
{sbp = SBP.GERMAN.PANZERWERFER_SQUAD_MP, name="Panzerwerfer Squad"},
{sbp = SBP.GERMAN.PANZER_IV_SQUAD_MP, name="Panzer IV Squad"},
{sbp = SBP.GERMAN.SCOUTCAR_SDKFZ222_MP, name="Scout Car Squad"},
}
squads_soviet = {
{sbp = SBP.SOVIET.COMBAT_ENGINEER_SQUAD_MP, name="Combat Engineer Squad"},
{sbp = SBP.SOVIET.BASE_CONSCRIPT_SQUAD_MP, name="Conscript Squad"},
{sbp = SBP.SOVIET.CONSCRIPT_SQUAD_MP, name="Conscript Squad"},
{sbp = SBP.SOVIET.DSHK_38_HMG_SQUAD_MP, name="DSHK HMG Squad"},
{sbp = SBP.SOVIET.M1910_MAXIM_HEAVY_MACHINE_GUN_SQUAD_MP, name="Maxim Squad"},
{sbp = SBP.SOVIET.PM_82_41_MORTAR_SQUAD_MP, name="Mortar Squad"},
{sbp = SBP.SOVIET.SNIPER_TEAM_MP, name="Sniper Squad"},
{sbp = SBP.SOVIET.PENAL_BATTALION_MP, name="Penal Squad"},
{sbp = SBP.SOVIET.M3A1_SCOUT_CAR_SQUAD_MP, name="M3A1 Scout Car Squad"},
{sbp = SBP.SOVIET.M5_HALFTRACK_SQUAD_MP, name="M5 Halftrack Squad"},
{sbp = SBP.SOVIET.M1937_53_K_45MM_AT_GUN_SQUAD_MP, name="45mm AT-Gun Squad"},
{sbp = SBP.SOVIET.M1942_ZIS_3_76MM_AT_GUN_SQUAD_MP, name="ZiS AT-Gun Squad"},
{sbp = SBP.SOVIET.SU_76M_MP, name="SU-76 Squad"},
{sbp = SBP.SOVIET.SU_85_MP, name="SU-85 Squad"},
{sbp = SBP.SOVIET.T_70M_MP, name="T-70 Squad"},
{sbp = SBP.SOVIET.T_34_76_SQUAD_MP, name="T-34 Squad"},
}
filters = {EBP.GERMAN.BEREICH_FESTUNG_MP, EBP.GERMAN.DOLCH_AKTIONEN_MP, EBP.GERMAN.HINTERE_PANZERWERK_MP, EBP.GERMAN.SCHWERES_KRIEGSWERK_MP, EBP.GERMAN.GERMAN_HQ_MP, EBP.SOVIET.BARRACKS_MP, EBP.SOVIET.MOTORPOOL_MP, EBP.SOVIET.TANK_DEPOT_MP, EBP.SOVIET.WEAPON_SUPPORT_CENTER_MP, EBP.SOVIET.HQ_MP,}
g_german_commanders = {
{
upgrades = {
"armor_commander","panzer_tactician","stuka_close_air_support","recon_plane","fast_march",
},
commander_title = "Blitzkrieg Doctrine",
},
{
upgrades = {
"fast_march","light_artillery_support","stuka_fragmentation_bomb","assault_field_officer","relief_infantry",
},
commander_title = "German Infantry Doctrine",
},
{
upgrades = {
"armor_commander","stuka_smoke_bomb","mechanized_grenadier_group","stationary_los_gain","howitzer_105mm_emplacement",
},
commander_title = "German Mechanized Doctrine",
},
{
upgrades = {
"aerial_superiority_recon_plane","aerial_superiority_stuka_strafe","aerial_superiority_stuka_close_air_support","redistribute_resources","stuka_bombing_run_upgrade",
},
commander_title = "Close Air Support Doctrine",
},
{
upgrades = {
"supply_truck_lockdown","assault_field_officer","stuka_strafe","tiger_tank","stuka_fragmentation_bomb",
},
commander_title = "Assault Support Doctrine",
},
{
upgrades = {
"panzer_tactician","pak_43_emplacement","armor_commander","hull_down","railway_artillery_support",
},
commander_title = "Festung Armor Doctrine",
},
{
upgrades = {
"stuka_smoke_bomb","mortar_halftrack","relief_infantry","sector_artillery","howitzer_105mm_emplacement",
},
commander_title = "Festung Support Doctrine",
},
{
upgrades = {
"panzer_tactician","hull_down","elefant_unlock","armor_commander","recon_plane",
},
commander_title = "Fortified Armor Doctrine",
},
{
upgrades = {
"air_drop_medical_supplies","air_drop_resources","recon_plane","stuka_flame_strike","stuka_bombing_run_upgrade",
},
commander_title = "Luftwaffe Supply Doctrine",
},
{
upgrades = {
"sector_artillery","hull_down","trench","pak_43_emplacement","defensive_fortifications",
},
commander_title = "Defensive Doctrine (Community)",
},
{
upgrades = {
"troop_training","jaeger_light_infantry","panzer_tactician","tiger_tank_ace","blinding_grenades",
},
commander_title = "Elite Troops Doctrine",
},
{
upgrades = {
"breakthrough","supply_break","sprint","crush_the_pocket","stormtroopers",
},
commander_title = "Encirclement Doctrine",
},
{
upgrades = {
"tiger_tank","light_artillery_support","stug_short_barrel","assault_grenadiers","mechanized_group",
},
commander_title = "Mechanized Assault Doctrine",
},
{
upgrades = {
"railway_artillery_support","assault_field_officer","redistribute_resources","ostruppen","trench",
},
commander_title = "Osttruppen Doctrine",
},
{
upgrades = {
"urban_assault_panzer_grenadiers","forward_repair_station","munition_blitz","mortar_incendiary_barrage","strategic_bombing",
},
commander_title = "Urban Assault Doctrine",
},
{
upgrades = {
"heavy_at_mine","recon_plane","stationary_los_gain","stuka_bombing_run_upgrade","elefant_unlock",
},
commander_title = "Jaeger Armor Doctrine",
},
{
upgrades = {
"ambush_camouflage","stuka_close_air_support","jaeger_light_infantry","fast_march","light_artillery_support",
},
commander_title = "Jaeger Infantry Doctrine",
},
{
upgrades = {
"recon_plane","pak_43_emplacement","light_artillery_support","assault_field_officer","howitzer_105mm_emplacement",
},
commander_title = "Joint Operations Doctrine",
},
{
upgrades = {
"stuka_close_air_support","jaeger_light_infantry","fast_march","relief_infantry","tiger_tank",
},
commander_title = "Lightning War Doctrine",
},
{
upgrades = {
"panzer_tactician","mortar_halftrack","recon_plane","tiger_tank","stuka_fragmentation_bomb",
},
commander_title = "Spearhead Doctrine",
},
{
upgrades = {
"ambush_camouflage","heavy_at_mine","fast_march","stuka_bombing_run_upgrade","howitzer_105mm_emplacement",
},
commander_title = "Storm Doctrine",
},
}
g_soviet_commanders = {
{
upgrades = {
"ml_20_howitzer_unlock","il-2_bomb_strike","il-2_recon","anti_tank_gun_ambush_tactics","guard_troops",
},
commander_title = "Soviet Combined Arms Army",
},
{
upgrades = {
"ml_20_howitzer_unlock","conscript_assault_package","partisan_troops_tow","rapid_conscription","conscript_repair_kit",
},
commander_title = "Soviet Reserve Army",
},
{
upgrades = {
"shock_troops","ml_20_howitzer_unlock","il-2_sturmovik_attack","conscript_assault_package","hm120_mortar_unlock",
},
commander_title = "Soviet Shock Army",
},
{
upgrades = {
"t34_85_advanced_unlock","radio_intercept","il-2_sturmovik_attack_advanced","conscript_assault_package","conscript_repair_kit",
},
commander_title = "Advanced Warfare Tactics",
},
{
upgrades = {
"shock_troops","il-2_recon","fire_artillery","fear_propaganda","kv-8_unlock",
},
commander_title = "Anti-Infantry Tactics",
},
{
upgrades = {
"conscript_evasive_tactics","conscript_repair_kit","conscript_assault_package","rapid_conscription","fire_artillery",
},
commander_title = "Conscripts Support Tactics",
},
{
upgrades = {
"guard_troops","hm120_mortar_unlock","mark_vehicle","t34_85_unlock","vehicle_self_repair_training",
},
commander_title = "Guard Motor Coordination Tactics",
},
{
upgrades = {
"guard_troops","conscript_evasive_tactics","conscript_assault_package","il-2_sturmovik_attack","ml_20_howitzer_unlock",
},
commander_title = "Guard Rifle Combined Arms Tactics",
},
{
upgrades = {
"guard_troops","anti_tank_gun_ambush_tactics","mark_vehicle","il-2_bomb_strike","isu152_unlock",
},
commander_title = "Mechanized Support Tactics",
},
{
upgrades = {
"il-2_recon","fear_propaganda","rapid_conscription","radio_intercept","il-2_sturmovik_attack",
},
commander_title = "NKVD Rifle Disruption Tactics",
},
{
upgrades = {
"isu152_unlock","il-2_bomb_strike","conscript_repair_kit","tank_detection","shock_troops",
},
commander_title = "Shock Motor Heavy Tactics",
},
{
upgrades = {
"shock_troops","kv-8_unlock","anti_tank_gun_ambush_tactics","fire_artillery","is-2_support",
},
commander_title = "Shock Rifle Frontline Tactics",
},
{
upgrades = {
"dshk_machinegun","tank_traps","anti_personnel_mines","hm120_mortar_unlock","m-42_at_gun",
},
commander_title = "Defensive Tactics (Community)",
},
{
upgrades = {
"kv1_unlock","il-2_recon","shock_troops","howtizer_203mm","for_mother_russia",
},
commander_title = "Counterattack Tactics",
},
{
upgrades = {
"soviet_industry","kv2_unlock","repair_bunker","vehicle_self_repair_training","kv-8_unlock",
},
commander_title = "Soviet Industry Tactics",
},
{
upgrades = {
"order227","manpower_blitz","hold_the_line","scorched_earth_policy_mp","commissar_squad",
},
commander_title = "Not One Step Back Tactics",
},
{
upgrades = {
"partisan_commander_troops","partisan_commander_antivehicle_troops","spy_network","mark_vehicle","radio_intercept",
},
commander_title = "Partisan Tactics",
},
{
upgrades = {
"engineer_salvage_kits_unlock","il-2_anti_tank_bomb","conscript_at_grenade_assault","light_anti_vehicle_mines","conscript_ptrs",
},
commander_title = "Tank Hunter Tactics",
},
{
upgrades = {
"fire_artillery","tank_detection","m-42_at_gun","forward_hq","booby_trap",
},
commander_title = "Urban Defense Tactics",
},
{
upgrades = {
"radio_intercept","vehicle_self_repair_training","is-2_support","t34_85_unlock","il-2_sturmovik_attack",
},
commander_title = "Armored Assault Tactics",
},
{
upgrades = {
"shock_troops","il-2_bomb_strike","fear_propaganda","kv-8_unlock","ml_20_howitzer_unlock",
},
commander_title = "Terror Tactics",
},
}
commander_tables = {g_soviet_commanders, g_german_commanders}
g_debug = false
player1 = nil
player2 = nil
player1_commander = "N/A"
player2_commander = "N/A"
spectator1 = nil
spectator2 = nil
spectator_count = 0
g_spectators = {}
g_playerCount = World_GetPlayerCount()
g_rt = {RT_Manpower, RT_Fuel, RT_Munition, RT_Command, RT_Action}
player1 = World_GetPlayerAt(1)
player2 = World_GetPlayerAt(2)
g_players = {player1, player2}
if g_playerCount == 3 then
player1 = World_GetPlayerAt(1)
spectator1 = World_GetPlayerAt(2)
player2 = World_GetPlayerAt(3)
spectator_count = 1
g_spectators = {spectator1}
g_players = {player1, player2}
end
if g_playerCount == 4 then
player1 = World_GetPlayerAt(1)
spectator1 = World_GetPlayerAt(2)
player2 = World_GetPlayerAt(3)
spectator2 = World_GetPlayerAt(4)
spectator_count = 2
g_spectators = {spectator1, spectator2}
g_players = {player1, player2}
end
if g_playerCount == 5 then
player1 = World_GetPlayerAt(1)
spectator1 = World_GetPlayerAt(2)
spectator2 = World_GetPlayerAt(3)
player2 = World_GetPlayerAt(4)
spectator3 = World_GetPlayerAt(5)
spectator_count = 3
g_spectators = {spectator1, spectator2, spectator3}
g_players = {player1, player2}
end
if g_playerCount == 6 then
player1 = World_GetPlayerAt(1)
spectator1 = World_GetPlayerAt(2)
spectator2 = World_GetPlayerAt(3)
player2 = World_GetPlayerAt(4)
spectator3 = World_GetPlayerAt(5)
spectator4 = World_GetPlayerAt(6)
spectator_count = 4
g_spectators = {spectator1, spectator2, spectator3, spectator4}
g_players = {player1, player2}
end
res_rate_sync = false
Rule_AddOneShot(EnableSpectator, 0.125)
Rule_Add(ResourceSync)
Rule_Add(CommanderCheck)
Rule_AddInterval(SyncResRate, 1)
end
function EnableSpectator()
eg_spec = EGroup_CreateIfNotFound("eg_spec")
sg_spec = SGroup_CreateIfNotFound("sg_spec")
for key, spectator in ipairs(g_spectators) do
FOW_PlayerRevealAll(spectator)
World_EnableSharedLineOfSight(player1, spectator, false)
World_EnableSharedLineOfSight(player2, spectator, false)
Player_GetAll(spectator, sg_spec, eg_spec)
SGroup_DestroyAllSquads(sg_spec)
EGroup_DestroyAllEntities(eg_spec)
if Player_GetID(Game_GetLocalPlayer()) == Player_GetID(spectator) or g_debug then
Init_Objective()
title = Util_CreateLocString(Player_GetDisplayName(player1)[1].." is Producing:")
Objective_UpdateText(OBJ_Production1, title, title, false)
title = Util_CreateLocString(Player_GetDisplayName(player2)[1].." is Producing:")
Objective_UpdateText(OBJ_Production2, title, title, false)
end
end
res_rate_sync = true
end
function ResourceSync()
player_constructing_string = {{player = player1, constructing = ""}, {player = player2, constructing = ""}}
t_sources = {[0] = squads_german, [1] = squads_soviet}
eg_all_prod = EGroup_CreateIfNotFound("eg_all_prod")
added_squadIDs = {}
local is_added = function(sid)
for key, value in ipairs(added_squadIDs) do
if value == sid then
return true
end
end
return false
end
for pkey, player in ipairs(g_players) do
local t_source = t_sources[Player_GetRace(player)]
EGroup_Clear(eg_all_prod)
Player_GetAll(player, eg_all_prod)
EGroup_Filter(eg_all_prod, filters, FILTER_KEEP)
local first_added = false
local comma = ""
for sbpkey, sbp in ipairs(t_source) do
local result = Util_EgroupIsMaking(eg_all_prod, sbp.sbp)
if result[1] then
if first_added then comma = ", " else comma = "" end
if result[2] > 1 then
player_constructing_string[pkey].constructing = player_constructing_string[pkey].constructing..comma..sbp.name.." ("..result[2]..")"
else
player_constructing_string[pkey].constructing = player_constructing_string[pkey].constructing..comma..sbp.name
end
first_added = true
end
end
end
for key, spectator in ipairs(g_spectators) do
local source_player
if Player_GetTeam(spectator) == 0 then
source_player = player1
elseif Player_GetTeam(spectator) == 1 then
source_player = player2
end
for key, restype in ipairs(g_rt) do
Player_SetResource(spectator, restype, Player_GetResource(source_player, restype))
end
if Player_GetID(Game_GetLocalPlayer()) == Player_GetID(spectator) or g_debug then
--text = Player_GetDisplayName(player1)[1]..": "..player1_commander.."\n"..
-- Player_GetDisplayName(player2)[1]..": "..player2_commander.."\n"
local title = Util_CreateLocString(Player_GetDisplayName(player1)[1]..": "..player1_commander)
Objective_UpdateText(OBJ_Commander1, title, title, false)
local title = Util_CreateLocString(Player_GetDisplayName(player2)[1]..": "..player2_commander)
Objective_UpdateText(OBJ_Commander2, title, title, false)
--dr_clear("UI")
--dr_setautoclear("UI", false)
--dr_text2d("UI", 0.015, 0.02, text, 255, 255, 255)
--dr_clear("UIProd")
--dr_setautoclear("UIProd", false)
--constructing_completed = Player_GetDisplayName(player1)[1].." is producing:\n"..player_constructing_string[1].constructing.."\n\n"..
--Player_GetDisplayName(player2)[1].." is constructing:\n"..player_constructing_string[2].constructing
local title = Util_CreateLocString(player_constructing_string[1].constructing)
Objective_UpdateText(OBJ_Production1_Sub, title, title, false)
local title = Util_CreateLocString(player_constructing_string[2].constructing)
Objective_UpdateText(OBJ_Production2_Sub, title, title, false)
--dr_text2d("UIProd", 0.815, 0.02, constructing_completed, 255, 255, 255)
end
end
end
function SyncResRate()
for key, spectator in ipairs(g_spectators) do
local source_player
if Player_GetTeam(spectator) == 0 then
source_player = player1
elseif Player_GetTeam(spectator) == 1 then
source_player = player2
end
for key, restype in ipairs(g_rt) do
Player_SetResource(spectator, restype, Player_GetResource(source_player, restype))
if restype ~= RT_Command and restype ~= RT_Action then
in_plr = Player_GetResourceRate(source_player, restype)
in_spec = Player_GetResourceRate(spectator, restype)
local rate = in_plr / in_spec
Modify_PlayerResourceRate(spectator, restype, rate)
--dr_clear("UIG")
--dr_setautoclear("UIG", false)
--dr_text2d("UIG", 0.515, 0.52, "restype: "..key.." in_plr: "..in_plr.." / in_spec: "..in_spec.." = "..rate, 255, 0, 0)
end
end
end
end
function Util_EgroupIsMaking(egroup, queueItem)
local state = {false, 0}
local EntityMaking = function(egID, index, entity)
if Entity_GetProductionQueueSize(entity) > 0 then
local count = 0
for i = 0, Entity_GetProductionQueueSize(entity) - 1 do
if Entity_GetProductionQueueItem(entity, i) == queueItem then
count = count + 1
state = {true, count}
end
end
end
end
EGroup_ForEach(egroup, EntityMaking)
return state
end
function CommanderCheck()
for key, commander_table in ipairs(commander_tables) do
for key, commander in ipairs(commander_table) do
upg_count = 0;
for key, upg in ipairs(commander.upgrades) do
local upgrade = BP_GetUpgradeBlueprint(upg)
if Player_HasUpgrade(player1, upgrade) then
upg_count = upg_count + 1
end
end
if upg_count == 5 then
player1_commander = commander.commander_title
end
end
end
for key, commander_table in ipairs(commander_tables) do
for key, commander in ipairs(commander_table) do
upg_count = 0;
for key, upg in ipairs(commander.upgrades) do
local upgrade = BP_GetUpgradeBlueprint(upg)
if Player_HasUpgrade(player2, upgrade) then
upg_count = upg_count + 1
end
end
if upg_count == 5 then
player2_commander = commander.commander_title
end
end
end
if player1_commander ~= "N/A" and player2_commander ~= "N/A" then
Rule_RemoveMe()
end
end
function Init_Objective()
OBJ_Main = {
Title = Util_CreateLocString("Commanders"),
Type = OT_Primary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
OBJ_Commander1 = {
Title = Util_CreateLocString("Commanders"),
Type = OT_Secondary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Parent = OBJ_Main,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
OBJ_Commander2 = {
Title = Util_CreateLocString("Commanders"),
Type = OT_Secondary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Parent = OBJ_Main,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
OBJ_Production1 = {
Title = Util_CreateLocString("Productions"),
Type = OT_Primary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
OBJ_Production2 = {
Title = Util_CreateLocString("Productions"),
Type = OT_Primary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
OBJ_Production1_Sub = {
Title = Util_CreateLocString("Productions"),
Type = OT_Primary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Parent = OBJ_Production1,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
OBJ_Production2_Sub = {
Title = Util_CreateLocString("Productions"),
Type = OT_Primary,
Intel_Start = nil,
Intel_Start_SkipFunc = nil,
SetupUI = function()
end,
OnStart = function()
end,
Intel_Complete = nil,
Intel_Complete_SkipFunc = nil,
OnComplete = function()
end,
Parent = OBJ_Production2,
Intel_Fail = nil,
Intel_Fail_SkipFunc = nil,
OnFail = function()
end,
}
Objective_Register(OBJ_Main)
Objective_Register(OBJ_Commander1)
Objective_Register(OBJ_Commander2)
Objective_Register(OBJ_Production1)
Objective_Register(OBJ_Production2)
Objective_Register(OBJ_Production1_Sub)
Objective_Register(OBJ_Production2_Sub)
--Objective_Register(OBJ_Productions)
Objective_Start(OBJ_Main, false)
Objective_Start(OBJ_Commander1, false)
Objective_Start(OBJ_Commander2, false)
Objective_Start(OBJ_Production1, false)
Objective_Start(OBJ_Production2, false)
Objective_Start(OBJ_Production1_Sub, false)
Objective_Start(OBJ_Production2_Sub, false)
--Objective_Start(OBJ_Productions, false)
end
function Util_CreateLocString(text)
local tmpstr = LOC(text)
tmpstr[1] = text
return tmpstr
end
Alternatively you can also obtain this code from pastebin.com.
Testing the code
Save the _ID.scar and close the file. Go to WorldBuilder and DO NOT SAVE the map! You should have done that earlier. Just do File -> Export Package, run the game, start a match with your map. You should now be able to enjoy your map with the Spectator Mode!
Conclusions
In this tutorial you learned how to add Spectator Mode to your custom map. I tried to spend some time to explain the important details instead of making this tutorial as short as humanly possible. Hopefully this will give you a better understanding of what you did instead of just adding the code to a file and getting results without knowing why. If you have any additional questions, feedback, or suggestions for future tutorials, please let me know in this thread!