14 years
edited 8 years
Documentation
The purpose of this system is to provide a simple framework for the implementation of custom melee races. It accomplishes this by setting up key components Blizzard should have given since the beginning (at least in a more portable state): the race initialization and victory / defeat conditions.
The approach chosen was one discussed a long time ago, probably on the Power of Corruption boards: letting the user pick their race by means of choosing one of the standard races and setting the handicap to a given value, which would be translated by the system into a particular custom race.
[btable]
[r]
[/btable]
Demo Map Credits
Playtesters
Things to take into consideration
Custom Race System
Here is an example of implementation from the demo map. Naga are a playable race for Night Elf, Handicap 90%.
Custom Race Setup
The purpose of this system is to provide a simple framework for the implementation of custom melee races. It accomplishes this by setting up key components Blizzard should have given since the beginning (at least in a more portable state): the race initialization and victory / defeat conditions.
The approach chosen was one discussed a long time ago, probably on the Power of Corruption boards: letting the user pick their race by means of choosing one of the standard races and setting the handicap to a given value, which would be translated by the system into a particular custom race.
[btable]
API
You create a struct of type CustomRace, with the following constructor:
static method create takes string Name, race Race, real Handicap returns CustomRace
Each of the parameters are described below:
You can also create and register a custom race for all handicaps of a single race with:
static method createAll takes string Name, race Race returns CustomRace
You can extend the registration of a custom race to more than one handicap with:
method register takes race Race, real Handicap returns nothing
Once created, you still need to register its townhall, the workers types (you can have more than one type of worker) and hero(es). You can do it by using the following methods:
method setTownHall takes integer TownHallId returns nothing
method addWorkerType takes integer WorkerId, integer Priority, integer Quantity returns nothing
method addHeroType takes integer HeroId returns nothing
If you've got an AI ready to be implemented, then you've already done the hard part. All this takes is a string for the file path and it takes care of the rest. Custom races that were registered but don't have AI scripts cannot be played by the computer. Of course, you can circumvent this (for whatever silly reason) by using this with a string that doesn't refer to an .ai file ("poop" for example).
method setAIScript takes string FilePath returns nothing
Finally, if your race needs extended initialization beyond the creation of worker units, you can do so by assigning a custom callback function that will run after all the initial units are created; this might be useful to replicate behaviour like the initial Haunted Goldmine the Undead start with, or the Entagled Goldmine for the Night Elves. For that you use the following method:
method setCallback takes CustomRaceCall callback returns nothing
The CustomRaceCall function interface is used for the aforementioned callback. It is defined as follows:
function interface CustomRaceCall takes player Player, group Workers, unit Goldmine, unit Townhall, unit Randhero returns nothing
If you need to get the custom race of a player, GetPlayerCustomRaceName will return a name string of the custom race. Hopefully, you aren't silly enough to name your custom races identically.
function GetPlayerCustomRaceName takes player Player returns string
static method create takes string Name, race Race, real Handicap returns CustomRace
Each of the parameters are described below:
- string Name - The name for your race
- race Race - The standard race it will be based on (eg, RACE_HUMAN).
- real Handicap - The handicap value users need to set so they can pick the race. Accpetable values are 0.5, 0.6, onwards up to 1.0 (which means you can overwrite the standard races too)
You can also create and register a custom race for all handicaps of a single race with:
static method createAll takes string Name, race Race returns CustomRace
You can extend the registration of a custom race to more than one handicap with:
method register takes race Race, real Handicap returns nothing
Once created, you still need to register its townhall, the workers types (you can have more than one type of worker) and hero(es). You can do it by using the following methods:
method setTownHall takes integer TownHallId returns nothing
- integer TownHallId - The rawcode of the race's Townhall (eg 'ogre' for Orc Great Hall)
method addWorkerType takes integer WorkerId, integer Priority, integer Quantity returns nothing
- integer WorkerId - The rawcode of the race's Worker (eg 'hpea' for Human Peasant)
- integer Priority - The priority where the worker type unit will be created. Acceptable values are
- CustomRace.NEAR_MINE - Spawns them near the closest mine. Normally all worker units should use this value.
- CustomRace.NEAR_HALL - Spawns them near the starting Town Hall unit type. This is included if you want additional workers, in the same vein Ghouls are for the Undead race.
- integer Quantity - The amount of initial workers of that type.
method addHeroType takes integer HeroId returns nothing
- integer HeroId - The rawcode of the race's hero (eg 'Hpal' for Human Paladin)
If you've got an AI ready to be implemented, then you've already done the hard part. All this takes is a string for the file path and it takes care of the rest. Custom races that were registered but don't have AI scripts cannot be played by the computer. Of course, you can circumvent this (for whatever silly reason) by using this with a string that doesn't refer to an .ai file ("poop" for example).
method setAIScript takes string FilePath returns nothing
- string FilePath - The filepath of the .ai script, either in an MPQ or imported to the map
Finally, if your race needs extended initialization beyond the creation of worker units, you can do so by assigning a custom callback function that will run after all the initial units are created; this might be useful to replicate behaviour like the initial Haunted Goldmine the Undead start with, or the Entagled Goldmine for the Night Elves. For that you use the following method:
method setCallback takes CustomRaceCall callback returns nothing
- CustomRaceCall callback - A function that matches the CustomRaceCall function interface. More on that below.
The CustomRaceCall function interface is used for the aforementioned callback. It is defined as follows:
function interface CustomRaceCall takes player Player, group Workers, unit Goldmine, unit Townhall, unit Randhero returns nothing
- player Player - The owning player of the units passed to the callback function.
- group Workers - A unit group with all the created workers created, regardless of type.
- unit Goldmine - The nearest Goldmine found at the start location. If there isn't a Goldmine nearby, this parameter is null.
- unit Townhall - The townhall created on initialization.
- unit Randhero - If the "Use a Random Hero" option is checked before starting the game, this parameter points to the randomly created Hero; otherwise, it will be null.
If you need to get the custom race of a player, GetPlayerCustomRaceName will return a name string of the custom race. Hopefully, you aren't silly enough to name your custom races identically.
function GetPlayerCustomRaceName takes player Player returns string
Demo Map Credits
Playtesters
- Alevice
- Anopob
- cosmicat
- Dragoon
- Michael Peppers
- Callahan - Mur'gul House
- DonDustin - Blue Basilisk Missile
- JetFangInferno - Aqua Spike
- jigrael - Reptilian Sanctuary, Sea Drake
- Phoenix - IV - Naga Town Hall
- MasterHaosis - Mur'gul Labor Mill
- Szythe - Water Buff
- Anachron - Water Shockwave
- bigapple90 - Water Tornado
- Technomancer - Drown
- Anitarf - Stack
- Vexorian - SimError, Table, TimerUtils
Things to take into consideration
- If a Goldmine cant be found within default melee lookup radius, all created workers will be located near the Town Hall. this is done accodirng to default melee behaviour.
- You can now register custom races in their own separate libraries. They should require CustomRaceSystem and must run on initialization.
- The system now supports AI. And the Naga are a great example of it, if I may say so.
- If you define two or more custom races with the same base race and handicap, a debug message will be displayed in - game and whichever races were defined beyond the first will not be registered.
Custom Race System
1 // ==============================================================================2 // Custom Race System by Archmage Owenalacaster3 // ==============================================================================4 // 5 // Purpose:6 // - Creates the starting units for custom races and replaces the standard7 // Melee Initialization trigger. Custom races are selected by race and8 // handicap.9 // 10 // Usage:11 // - Register a new custom race with CustomRace.create(name, RACE, handicap)12 // Handicaps: Valid handicap values are 1.0, 0.9, 0.8, 0.7, 0.6 and 0.5.13 // - Register a new custom race for all handicaps of a single race with14 // CustomRace.createAll(name, RACE)15 // - Extend the registration of a race with c.register(RACE, handicap)16 // - Set the townhall type with c.setTownHall(unitid)17 // - Add a new worker type with c.addWorkerType(unitid, priority, qty)18 // Priorities: c.NEAR_MINE spawns workers near the mine, where workers19 // typically spawn.20 // c.NEAR_HALL spawns workers near the town hall, where21 // Ghouls spawn.22 // - Add a random hero type with c.addHeroType(unitid)23 // - Set the ai script used by computer players with c.setAIScript(stringpath)24 // - Set a callback function with c.setCallback(CustomRaceCall.function)25 // Callbacks: The callback is executed after all the starting units for a26 // player are created, and its purpose is to provide enhanced27 // initial behaviour for a race. A good example of this with the28 // standard races would be the Undead Goldmine Haunting and29 // Night Elves Goldmine Entangling.30 // The callback function passes as arguments all the units31 // generated in addition to the nearest goldmine detected.32 // Please note that if a random hero is not created, the last33 // argument will have a null value, so always do a check.34 // - Get a player's custom race name string with GetPlayerCustomRaceName(player)35 // 36 // Notes:37 // - Supports a maximum of 24 custom races.38 // - Maximum for worker and hero types are configurable.39 // 40 // Requirements:41 // - JassHelper version 0.9.E.0 or newer (older versions may still work).42 // 43 // Installation:44 // - Create a new trigger called CustomRaceSystem.45 // - Convert it to custom text and replace all the code with this code.46 // 47 // Special Thanks:48 // - Alevice: He practically co - wrote the code.49 // - cosmicat: His formula for circular unit formation.50 // Co - developing the single - array registry.51 // 52 // ==============================================================================53 54 library CustomRaceSystem initializer Init
55
56 // ===========================================================================57 // CONFIGURATION SECTION58 // ===========================================================================59 globals60 // Unit Type Constants61 private constant integer MAX_WORKERTYPES = 4
62 private constant integer MAX_HEROTYPES = 4
63 endglobals64 // ===========================================================================65 // END CONFIGURATION SECTION66 // ===========================================================================67 68 function interface CustomRaceCall takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
69
70 private function r2S takes race r returns string
71 if r == RACE_HUMAN then
72 return "Human"
73 elseif r == RACE_ORC then
74 return "Orc"
75 elseif r == RACE_UNDEAD then
76 return "Undead"
77 elseif r == RACE_NIGHTELF then
78 return "Night Elf"
79 endif80 return "Unknown"
81 endfunction82
83 private function r2I takes race r returns integer
84 if r == RACE_HUMAN then
85 return 1
86 elseif r == RACE_ORC then
87 return 2
88 elseif r == RACE_UNDEAD then
89 return 3
90 elseif r == RACE_NIGHTELF then
91 return 4
92 endif93 return 5
94 endfunction95
96 globals97 // Victory Defeat Variables98 private string array KEY_STRUCTURE
99 private integer KEY_STRUCTURE_COUNT = 0
100 endglobals101
102 // ===========================================================================103 // STRUCT DATA104 // ===========================================================================105 private keyword createStartingUnits
106
107 struct CustomRace108 string name
109
110 // Town Hall Variable111 integer townhallType = 0
112 // string townhallName113 // Town Hall name is not currently supported.114
115 // Worker Variables116 integer totalWorkerTypes = 0
117 integer array workerType[MAX_WORKERTYPES]
118 integer array workerPriority[MAX_WORKERTYPES]
119 integer array workerQty[MAX_WORKERTYPES]
120
121 // Random Hero Variables122 integer totalHeroTypes = 0
123 integer array heroType[MAX_HEROTYPES]
124
125 // AI Script Directory String Variable126 string aiscript = ""
127
128 // Callback Variable129 private CustomRaceCall c130
131 // Registry Variable132 static integer array REGISTRY
133
134 // Spawn Priority Variables135 static integer NEAR_MINE = 0
136 static integer NEAR_HALL = 1
137
138 static method get takes race r, real h returns CustomRace
139 return CustomRace(.REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))])
140 endmethod141
142 method register takes race r, real h returns boolean
143 local CustomRace c = CustomRace.get(r, h)
144 if c != 0 then
145 debug call BJDebugMsg("|cffff0000Registration of " + .name + " failed due to conflict with " + c.name + " registered for " + r2S(r) + " race Handicap " + R2S(h))
146 return false
147 endif148 set .REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))] = integer(this)
149 return true
150 endmethod151
152 static method create takes string name, race r, real h returns CustomRace
153 local CustomRace c = CustomRace.allocate()
154 set c.name = name
155 if not c.register(r, h) then
156 call c.destroy()
157 return 0
158 endif159 return c160 endmethod161
162 static method createAll takes string name, race r returns CustomRace
163 local CustomRace c = CustomRace.allocate()
164 set c.name = name
165 if not c.register(r, 1.0) and not c.register(r, 0.9) and not c.register(r, 0.8) and not c.register(r, 0.7) and not c.register(r, 0.6) and not c.register(r, 0.5) then
166 call c.destroy()
167 return 0
168 endif169 return c170 endmethod171
172 method setTownHall takes integer hallid returns nothing
173 set .townhallType = hallid
174 set KEY_STRUCTURE[KEY_STRUCTURE_COUNT] = UnitId2String(hallid)
175 set KEY_STRUCTURE_COUNT = KEY_STRUCTURE_COUNT + 1
176 endmethod177
178 method addWorkerType takes integer workerid, integer priority, integer quantity returns nothing
179 set .workerType[.totalWorkerTypes] = workerid
180 set .workerPriority[.totalWorkerTypes] = priority
181 set .workerQty[.totalWorkerTypes] = quantity
182 set .totalWorkerTypes = .totalWorkerTypes + 1
183 endmethod184
185 method addHeroType takes integer heroid returns nothing
186 local integer i = 0
187 set .heroType[.totalHeroTypes] = heroid
188 set .totalHeroTypes = .totalHeroTypes + 1
189 loop190 call SetPlayerTechMaxAllowed(Player(i), heroid, 1)
191 set i = i + 1
192 exitwhen i == bj_MAX_PLAYERS
193 endloop194 endmethod195
196 private method getRandomHeroType takes nothing returns integer
197 local integer randomindex = GetRandomInt(0, .totalHeroTypes - 1)
198 return .heroType[randomindex]
199 endmethod200
201 method setAIScript takes string s returns nothing
202 set .aiscript = s
203 endmethod204
205 method setCallback takes CustomRaceCall callb returns nothing
206 set .c = callb
207 endmethod208
209 private method createRandomHero takes player p, location loc returns unit
210 local unit h = CreateUnitAtLoc(p, .getRandomHeroType(), loc, bj_UNIT_FACING)
211 if bj_meleeGrantHeroItems then
212 call MeleeGrantItemsToHero(h)
213 endif214 return h215 endmethod216
217 method createStartingUnits takes player p returns nothing
218 local location startLoc = GetPlayerStartLocationLoc(p)
219 local location nearMineLoc = startLoc
220 local location nearTownLoc = startLoc
221 local location spawnLoc = startLoc
222 local location heroLoc = startLoc
223 local unit nearestMine = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
224 local unit myTownhall = null
225 local unit myRandHero = null
226 local group workerGroup = CreateGroup()
227 local integer workertypeindex = 0
228 local integer workerqty = 0
229 local integer spawnPriority = 0
230 if nearestMine != null then
231 set nearMineLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 320, 0)
232 set nearTownLoc = MeleeGetProjectedLoc(startLoc, GetUnitLoc(nearestMine), 288, 0)
233 set heroLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 384, 45)
234 endif235 set myTownhall = CreateUnitAtLoc(p, .townhallType, startLoc, bj_UNIT_FACING)
236 loop237 exitwhen workertypeindex == .totalWorkerTypes
238 set spawnPriority = .workerPriority[workertypeindex]
239 if (spawnPriority==.NEAR_HALL) then
240 set spawnLoc = nearTownLoc
241 elseif(spawnPriority==.NEAR_MINE) then
242 set spawnLoc = nearMineLoc
243 endif244 loop245 call GroupAddUnit(workerGroup, CreateUnitAtLoc(p,.workerType[workertypeindex],PolarProjectionBJ(spawnLoc,65,(I2R(workerqty) * (360.00 / I2R(.workerQty[workertypeindex]))) + 90),bj_UNIT_FACING))
246 set workerqty = workerqty + 1
247 exitwhen workerqty >= .workerQty[workertypeindex]
248 endloop249 call RemoveLocation(spawnLoc)
250 set workerqty = 0
251 set workertypeindex = workertypeindex + 1
252 endloop253 if (IsMapFlagSet(MAP_RANDOM_HERO) and .totalHeroTypes>0 ) then
254 set myRandHero = .createRandomHero(p,heroLoc)
255 else256 call SetPlayerState(p,PLAYER_STATE_RESOURCE_HERO_TOKENS,bj_MELEE_STARTING_HERO_TOKENS)
257 endif258 if(.c!=0) then
259 call .c.evaluate(p,workerGroup,nearestMine,myTownhall,myRandHero)
260 else261 call DestroyGroup(workerGroup)
262 endif263 if nearMineLoc != startLoc then
264 call RemoveLocation(nearMineLoc)
265 call RemoveLocation(nearTownLoc)
266 call RemoveLocation(heroLoc)
267 endif268 call RemoveLocation(startLoc)
269 set startLoc = null
270 set nearMineLoc = null
271 set nearTownLoc = null
272 set spawnLoc = null
273 set heroLoc = null
274 set nearestMine = null
275 set myTownhall = null
276 set myRandHero = null
277 set workerGroup = null
278 endmethod279 endstruct280
281 globals282 private string array PLAYER_RACE
283 endglobals284
285 function GetPlayerCustomRaceName takes player p returns string
286 return PLAYER_RACE[GetPlayerId(p)]
287 endfunction288
289 //===========================================================================290 // UNIT CREATION SECTION291 //===========================================================================292 private function CreateStartingUnitsForAllPlayers takes nothing returns nothing
293 local integer index = 0
294 local player indexPlayer
295 local race playerRace
296 local CustomRace c297 loop298 set indexPlayer = Player(index)
299 if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
300 set playerRace = GetPlayerRace(indexPlayer)
301 set c = CustomRace.get(playerRace,GetPlayerHandicap(indexPlayer) + 0.01)
302 if (GetPlayerController(indexPlayer) == MAP_CONTROL_USER or (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER and c.aiscript != "" )) and c != 0 then
303 set PLAYER_RACE[index] = c.name
304 call c.createStartingUnits(indexPlayer)
305 elseif playerRace == RACE_HUMAN then
306 call MeleeStartingUnitsHuman(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
307 elseif playerRace == RACE_ORC then
308 call MeleeStartingUnitsOrc(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
309 elseif playerRace == RACE_NIGHTELF then
310 call MeleeStartingUnitsNightElf(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
311 elseif playerRace == RACE_UNDEAD then
312 call MeleeStartingUnitsUndead(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
313 else314 call MeleeStartingUnitsUnknownRace(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
315 endif316 endif317 set index = index + 1
318 exitwhen index == bj_MAX_PLAYERS
319 endloop320 endfunction321
322 //===========================================================================323 // CUSTOM MELEE AI SECTION324 //===========================================================================325 private function CustomMeleeStartingAI takes nothing returns nothing
326 local integer index = 0
327 local player indexPlayer
328 local race indexRace
329 local CustomRace c330 loop331 set indexPlayer = Player(index)
332 if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
333 set indexRace = GetPlayerRace(indexPlayer)
334 set c = CustomRace.get(indexRace,GetPlayerHandicap(indexPlayer) + 0.01)
335 call SetPlayerHandicap(indexPlayer,1.0)
336 if (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER) then
337 // Run a race-specific melee AI script.338 if c != 0 and c.aiscript != "" then
339 call StartMeleeAI(indexPlayer, c.aiscript)
340 elseif (indexRace == RACE_HUMAN) then
341 call PickMeleeAI(indexPlayer, "human.ai", null, null)
342 elseif (indexRace == RACE_ORC) then
343 call PickMeleeAI(indexPlayer, "orc.ai", null, null)
344 elseif (indexRace == RACE_UNDEAD) then
345 call PickMeleeAI(indexPlayer, "undead.ai", null, null)
346 call RecycleGuardPosition(bj_ghoul[index])
347 elseif (indexRace == RACE_NIGHTELF) then
348 call PickMeleeAI(indexPlayer, "elf.ai", null, null)
349 else350 // Unrecognized race.351 endif352 call ShareEverythingWithTeamAI(indexPlayer)
353 endif354 endif355 356 set index = index + 1
357 exitwhen index == bj_MAX_PLAYERS
358 endloop359 endfunction 360 361 //===========================================================================362 // VICTORY DEFEAT SECTION363 //===========================================================================364 private function CustomGetAllyKeyStructureCount takes player whichPlayer returns integer
365 local integer i = 0
366 local integer keyStructs = 0
367 local integer playerIndex = 0
368 local player indexPlayer
369 loop370 set indexPlayer = Player(playerIndex)
371 if (PlayersAreCoAllied(whichPlayer, indexPlayer)) then
372 set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "townhall", true, true)
373 set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "greathall", true, true)
374 set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "necropolis", true, true)
375 set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "treeoflife", true, true)
376 loop377 set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, KEY_STRUCTURE[i], true, true)
378 set i = i + 1
379 exitwhen i == KEY_STRUCTURE_COUNT
380 endloop381 endif382 set playerIndex = playerIndex + 1
383 exitwhen playerIndex == bj_MAX_PLAYERS
384 endloop385 return keyStructs386 endfunction387 388 private function CustomPlayerIsCrippled takes player whichPlayer returns boolean
389 local integer allyStructures = MeleeGetAllyStructureCount(whichPlayer)
390 local integer allyKeyStructures = CustomGetAllyKeyStructureCount(whichPlayer)
391 return (allyStructures > 0) and (allyKeyStructures <= 0)
392 endfunction393 394 private function CustomCheckForCrippledPlayers takes nothing returns nothing
395 local integer playerIndex
396 local player indexPlayer
397 local boolean isNowCrippled
398 call MeleeCheckForLosersAndVictors()
399 if bj_finishSoonAllExposed then
400 return401 endif402 set playerIndex = 0
403 loop404 set indexPlayer = Player(playerIndex)
405 set isNowCrippled = CustomPlayerIsCrippled(indexPlayer)
406 if (not bj_playerIsCrippled[playerIndex] and isNowCrippled) then
407 set bj_playerIsCrippled[playerIndex] = true
408 call TimerStart(bj_crippledTimer[playerIndex], bj_MELEE_CRIPPLE_TIMEOUT, false, function MeleeCrippledPlayerTimeout)
409 if (GetLocalPlayer() == indexPlayer) then
410 call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], true)
411 call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_WARNING_HUMAN"))
412 endif413 elseif (bj_playerIsCrippled[playerIndex] and not isNowCrippled) then
414 set bj_playerIsCrippled[playerIndex] = false
415 call PauseTimer(bj_crippledTimer[playerIndex])
416 if (GetLocalPlayer() == indexPlayer) then
417 call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], false)
418 if (MeleeGetAllyStructureCount(indexPlayer) > 0) then
419 if (bj_playerIsExposed[playerIndex]) then
420 call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
421 else422 call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
423 endif424 endif425 endif426 call MeleeExposePlayer(indexPlayer, false)
427 endif428 set playerIndex = playerIndex + 1
429 exitwhen playerIndex == bj_MAX_PLAYERS
430 endloop431 endfunction432 433 private function CustomInitVictoryDefeat takes nothing returns nothing
434 local trigger checker = CreateTrigger()
435 local trigger trig
436 local integer index
437 local player indexPlayer
438 set bj_finishSoonTimerDialog = CreateTimerDialog(null)
439 call TriggerAddAction(checker, function CustomCheckForCrippledPlayers)
440 set trig = CreateTrigger()
441 call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_SOON)
442 call TriggerAddAction(trig, function MeleeTriggerTournamentFinishSoon)
443 set trig = CreateTrigger()
444 call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_NOW)
445 call TriggerAddAction(trig, function MeleeTriggerTournamentFinishNow)
446 set index = 0
447 loop448 set indexPlayer = Player(index)
449 if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
450 set bj_meleeDefeated[index] = false
451 set bj_meleeVictoried[index] = false
452 set bj_playerIsCrippled[index] = false
453 set bj_playerIsExposed[index] = false
454 set bj_crippledTimer[index] = CreateTimer()
455 set bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
456 call TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(indexPlayer))
457 call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
458 call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
459 call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
460 call TriggerRegisterPlayerAllianceChange(checker, indexPlayer, ALLIANCE_PASSIVE)
461 call TriggerRegisterPlayerStateEvent(checker, indexPlayer, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
462 set trig = CreateTrigger()
463 call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_DEFEAT)
464 call TriggerAddAction(trig, function MeleeTriggerActionPlayerDefeated)
465 set trig = CreateTrigger()
466 call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
467 call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
468 else469 set bj_meleeDefeated[index] = true
470 set bj_meleeVictoried[index] = false
471 if (IsPlayerObserver(indexPlayer)) then
472 set trig = CreateTrigger()
473 call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
474 call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
475 endif476 endif477 set index = index + 1
478 exitwhen index == bj_MAX_PLAYERS
479 endloop480 call TimerStart(CreateTimer(), 2.0, false, function CustomCheckForCrippledPlayers)
481 endfunction482
483 private function TimerAction takes nothing returns nothing
484 call SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
485 call MeleeStartingHeroLimit()
486 call MeleeGrantHeroItems()
487 call MeleeStartingResources()
488 call MeleeClearExcessUnits()
489 call CreateStartingUnitsForAllPlayers()
490 call CustomMeleeStartingAI()
491 call CustomInitVictoryDefeat()
492 call DestroyTimer(GetExpiredTimer())
493 endfunction494
495 private function Init takes nothing returns nothing
496 call TimerStart(CreateTimer(),0,false,function TimerAction)
497 endfunction498
499 endlibrary
Here is an example of implementation from the demo map. Naga are a playable race for Night Elf, Handicap 90%.
Custom Race Setup
1 2 //====================================================================3 // HUMAN SETUP4 //====================================================================5 6 library HumanSetup initializer Init requires CustomRaceSystem
7
8 private function Init takes nothing returns nothing
9 local CustomRace c = CustomRace.create("Human",RACE_HUMAN,1.0)
10 call c.setTownHall('htow') // Town Hall
11 call c.addWorkerType('hpea',c.NEAR_MINE,5) // Peasant
12 call c.addHeroType('Hpal') // Paladin
13 call c.addHeroType('Hamg') // Archmage
14 call c.addHeroType('Hmkg') // Mountain King
15 call c.addHeroType('Hblm') // Blood Mage
16 call c.setAIScript("human.ai")
17 endfunction18
19 endlibrary20 21 //====================================================================22 // ORC SETUP23 //====================================================================24 25 library OrcSetup initializer Init requires CustomRaceSystem
26
27 private function Init takes nothing returns nothing
28 local CustomRace c = CustomRace.create("Orc",RACE_ORC,1.0)
29 call c.setTownHall('ogre') // Great Hall
30 call c.addWorkerType('opeo',c.NEAR_MINE,5) // Peon
31 call c.addHeroType('Obla') // Blademaster
32 call c.addHeroType('Ofar') // Far Seer
33 call c.addHeroType('Otch') // Tauren Chieftain
34 call c.addHeroType('Oshd') // Shadow Hunter
35 call c.setAIScript("orc.ai")
36 endfunction37
38 endlibrary39 40 //====================================================================41 // UNDEAD SETUP42 //====================================================================43 44 library UndeadSetup initializer Init requires CustomRaceSystem
45
46 private function WorkerHideToggle takes nothing returns nothing
47 call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
48 endfunction49
50 private function HauntGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
51 call ForGroup(workers,function WorkerHideToggle)
52 call BlightGoldMineForPlayerBJ(goldmine,play)
53 call ForGroup(workers,function WorkerHideToggle)
54 call DestroyGroup(workers)
55 endfunction56
57 private function Init takes nothing returns nothing
58 local CustomRace c = CustomRace.create("Undead",RACE_UNDEAD,1.0)
59 call c.setTownHall('unpl') // Necropolis
60 call c.addWorkerType('uaco',c.NEAR_MINE,3) // Acolyte
61 call c.addWorkerType('ugho',c.NEAR_HALL,1) // Ghoul
62 call c.addHeroType('Udea') // Death Knight
63 call c.addHeroType('Ulic') // Lich
64 call c.addHeroType('Udre') // Dreadlord
65 call c.addHeroType('Ucrl') // Crypt Lord
66 call c.setCallback(CustomRaceCall.HauntGoldMine)
67 call c.setAIScript("undead.ai")
68 endfunction69
70 endlibrary71 72 //====================================================================73 // NIGHT ELF SETUP74 //====================================================================75 76 library NightElfSetup initializer Init requires CustomRaceSystem
77
78 private function EntangleGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
79 call SetUnitPosition(townhall,GetUnitX(goldmine),GetUnitY(goldmine))
80 call IssueTargetOrder(townhall, "entangleinstant", goldmine)
81 call DestroyGroup(workers)
82 endfunction83
84 private function Init takes nothing returns nothing
85 local CustomRace c = CustomRace.create("Night Elf",RACE_NIGHTELF,1.0)
86 call c.setTownHall('etol') // Tree of Life
87 call c.addWorkerType('ewsp',c.NEAR_MINE,5) // Wisp
88 call c.addHeroType('Ekee') // Keeper of the Grove
89 call c.addHeroType('Emoo') // Priestess of the Moon
90 call c.addHeroType('Edem') // Demon Hunter
91 call c.addHeroType('Ewar') // Warden
92 call c.setCallback(CustomRaceCall.EntangleGoldMine)
93 call c.setAIScript("elf.ai")
94 endfunction95
96 endlibrary97 98 //====================================================================99 // NAGA SETUP100 //====================================================================101 102 library NagaSetup initializer Init requires CustomRaceSystem
103
104 private function Init takes nothing returns nothing
105 local CustomRace c = CustomRace.create("Naga",RACE_NIGHTELF,0.9)
106 call c.setTownHall('nntt') // Temple of Tides
107 call c.addWorkerType('nmpe',c.NEAR_MINE,5) // Murgul Slave
108 call c.addHeroType('Hvsh') // Lady Vashj
109 call c.setAIScript("naga.ai")
110 endfunction111
112 endlibrary