Custom Race System

Scripts

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]

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:

  • 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

  • player Player  - The player with a custom race. Players without a custom race associated with them will return an empty string.


Demo Map Credits
Playtesters

  • Alevice
  • Anopob
  • cosmicat
  • Dragoon
  • Michael Peppers
Models

  • 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
Icons

  • Anachron  - Water Shockwave
  • bigapple90  - Water Tornado
  • Technomancer  - Drown
Scripts

  • 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
Code (jass) Select
1
// ==============================================================================
2
 //  Custom Race System by Archmage Owenalacaster
3
 // ==============================================================================
4
 // 
5
 //  Purpose:
6
 //       - Creates the starting units for custom races and replaces the standard
7
 //        Melee Initialization trigger. Custom races are selected by race and
8
 //        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 with
14
 //        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 workers
19
 //                        typically spawn.
20
 //                      c.NEAR_HALL spawns workers near the town hall, where
21
 //                        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 a
26
 //                     player are created, and its purpose is to provide enhanced
27
 //                     initial behaviour for a race. A good example of this with the
28
 //                     standard races would be the Undead Goldmine Haunting and
29
 //                     Night Elves Goldmine Entangling.
30
 //                     The callback function passes as arguments all the units
31
 //                     generated in addition to the nearest goldmine detected.
32
 //                     Please note that if a random hero is not created, the last
33
 //                     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 SECTION
58
 // ===========================================================================
59
    globals
60
         // Unit Type Constants
61
        private constant integer MAX_WORKERTYPES = 4
62
        private constant integer MAX_HEROTYPES = 4
63
    endglobals
64
 // ===========================================================================
65
 //                                END CONFIGURATION SECTION
66
 // ===========================================================================
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
        endif
80
        return "Unknown"
81
    endfunction
82
    
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
        endif
93
        return 5
94
    endfunction
95
    
96
    globals
97
         // Victory Defeat Variables
98
        private string array KEY_STRUCTURE
99
        private integer KEY_STRUCTURE_COUNT = 0
100
    endglobals
101
    
102
 // ===========================================================================
103
 //                                STRUCT DATA
104
 // ===========================================================================
105
    private keyword createStartingUnits
106
    
107
    struct CustomRace
108
        string name
109
        
110
         // Town Hall Variable
111
        integer townhallType = 0
112
       // string  townhallName
113
         // Town Hall name is not currently supported.
114
        
115
         // Worker Variables
116
        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 Variables
122
        integer totalHeroTypes = 0
123
        integer array heroType[MAX_HEROTYPES]
124
        
125
         // AI Script Directory String Variable
126
        string aiscript = ""
127
        
128
         // Callback Variable
129
        private CustomRaceCall c
130
        
131
         // Registry Variable
132
        static integer array REGISTRY
133
        
134
         // Spawn Priority Variables
135
        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
        endmethod
141
        
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
            endif
148
            set .REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))] = integer(this)
149
            return true
150
        endmethod
151
        
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
            endif
159
            return c
160
        endmethod
161
        
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
            endif
169
            return c
170
        endmethod
171
        
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
        endmethod
177
        
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
        endmethod
184
        
185
        method addHeroType takes integer heroid returns nothing
186
            local integer i = 0
187
            set .heroType[.totalHeroTypes] = heroid
188
            set .totalHeroTypes = .totalHeroTypes + 1
189
            loop
190
                call SetPlayerTechMaxAllowed(Player(i), heroid, 1)
191
                set i = i + 1
192
                exitwhen i == bj_MAX_PLAYERS
193
            endloop
194
        endmethod
195
        
196
        private method getRandomHeroType takes nothing returns integer
197
            local integer randomindex = GetRandomInt(0, .totalHeroTypes - 1)
198
            return .heroType[randomindex]
199
        endmethod
200
        
201
        method setAIScript takes string s returns nothing
202
            set .aiscript = s
203
        endmethod
204
        
205
        method setCallback takes CustomRaceCall callb returns nothing
206
            set .c = callb
207
        endmethod
208
        
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
            endif
214
            return h
215
        endmethod
216
        
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
            endif
235
            set myTownhall = CreateUnitAtLoc(p, .townhallType, startLoc, bj_UNIT_FACING)
236
            loop
237
                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
                    endif
244
                    loop
245
                        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
                    endloop
249
                    call RemoveLocation(spawnLoc)
250
                    set workerqty = 0
251
                set workertypeindex = workertypeindex + 1
252
            endloop
253
            if (IsMapFlagSet(MAP_RANDOM_HERO) and .totalHeroTypes>0 ) then
254
                set myRandHero = .createRandomHero(p,heroLoc)
255
            else
256
                call SetPlayerState(p,PLAYER_STATE_RESOURCE_HERO_TOKENS,bj_MELEE_STARTING_HERO_TOKENS)
257
            endif
258
            if(.c!=0) then
259
                call .c.evaluate(p,workerGroup,nearestMine,myTownhall,myRandHero)
260
            else
261
                call DestroyGroup(workerGroup)
262
            endif
263
            if nearMineLoc != startLoc then
264
                call RemoveLocation(nearMineLoc)
265
                call RemoveLocation(nearTownLoc)
266
                call RemoveLocation(heroLoc)
267
            endif
268
            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
        endmethod
279
     endstruct
280
     
281
     globals
282
        private string array PLAYER_RACE
283
     endglobals
284
     
285
     function GetPlayerCustomRaceName takes player p returns string
286
        return PLAYER_RACE[GetPlayerId(p)]
287
     endfunction
288
    
289
//===========================================================================
290
//                         UNIT CREATION SECTION
291
//===========================================================================
292
    private function CreateStartingUnitsForAllPlayers takes nothing returns nothing
293
        local integer    index = 0
294
        local player     indexPlayer
295
        local race       playerRace
296
        local CustomRace c
297
        loop
298
            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
                else
314
                    call MeleeStartingUnitsUnknownRace(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
315
                endif
316
            endif
317
            set index = index  +  1
318
            exitwhen index == bj_MAX_PLAYERS
319
        endloop
320
    endfunction
321
    
322
//===========================================================================
323
//                         CUSTOM MELEE AI SECTION
324
//===========================================================================
325
    private function CustomMeleeStartingAI takes nothing returns nothing
326
        local integer index = 0
327
        local player  indexPlayer
328
        local race    indexRace
329
        local CustomRace c
330
        loop
331
            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
                    else
350
                        // Unrecognized race.
351
                    endif
352
                    call ShareEverythingWithTeamAI(indexPlayer)
353
                endif
354
            endif
355
356
            set index = index  +  1
357
            exitwhen index == bj_MAX_PLAYERS
358
        endloop
359
    endfunction    
360
361
//===========================================================================
362
//                         VICTORY DEFEAT SECTION
363
//===========================================================================
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
        loop
370
            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
                loop
377
                    set keyStructs = keyStructs  +  GetPlayerTypedUnitCount(indexPlayer, KEY_STRUCTURE[i], true, true)
378
                    set i = i + 1
379
                    exitwhen i == KEY_STRUCTURE_COUNT
380
                endloop
381
            endif
382
            set playerIndex = playerIndex  +  1
383
            exitwhen playerIndex == bj_MAX_PLAYERS
384
        endloop
385
        return keyStructs
386
    endfunction
387
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
    endfunction
393
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
            return
401
        endif
402
        set playerIndex = 0
403
        loop
404
            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
                endif
413
            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
                        else
422
                            call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
423
                        endif
424
                    endif
425
                endif
426
                call MeleeExposePlayer(indexPlayer, false)
427
            endif
428
            set playerIndex = playerIndex  +  1
429
            exitwhen playerIndex == bj_MAX_PLAYERS
430
        endloop
431
    endfunction
432
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
        loop
448
            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
             else
469
                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
                endif
476
            endif
477
            set index = index  +  1
478
            exitwhen index == bj_MAX_PLAYERS
479
        endloop
480
        call TimerStart(CreateTimer(), 2.0, false, function CustomCheckForCrippledPlayers)
481
    endfunction
482
    
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
    endfunction
494
    
495
    private function Init takes nothing returns nothing
496
        call TimerStart(CreateTimer(),0,false,function TimerAction)
497
    endfunction
498
    
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
Code (jass) Select
1
2
//====================================================================
3
//                           HUMAN SETUP
4
//====================================================================
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
    endfunction
18
    
19
endlibrary
20
21
//====================================================================
22
//                            ORC SETUP
23
//====================================================================
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
    endfunction
37
    
38
endlibrary
39
40
//====================================================================
41
//                          UNDEAD SETUP
42
//====================================================================
43
44
library UndeadSetup initializer Init requires CustomRaceSystem
45
    
46
    private function WorkerHideToggle takes nothing returns nothing
47
        call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
48
    endfunction
49
    
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
    endfunction
56
    
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
    endfunction
69
    
70
endlibrary
71
72
//====================================================================
73
//                          NIGHT ELF SETUP
74
//====================================================================
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
    endfunction
83
    
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
    endfunction
95
    
96
endlibrary
97
98
//====================================================================
99
//                           NAGA SETUP
100
//====================================================================
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
    endfunction
111
    
112
endlibrary
  • CRSscreen00.jpg (749.97 KB)
  • CRSscreen01.jpg (619.53 KB)
  • (2)CRSDemoEchoIsles.w3x (703.59 KB)
  • (4)CRSDemoIslands.w3x (774.86 KB)
  • (6)CRSDemoAndorhal.w3x (752.03 KB)