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 [lcode=jass]CustomRace[/lcode], with the following constructor:
[lcode=jass]static method create takes string Name, race Race, real Handicap returns CustomRace[/lcode]

Each of the parameters are described below:

  • [lcode=jass]string Name[/lcode]  - The name for your race
  • [lcode=jass]race Race[/lcode]  - The standard race it will be based on (eg, [lcode=jass]RACE_HUMAN[/lcode]).
  • [lcode=jass]real Handicap[/lcode]  - 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:
[lcode=jass]static method createAll takes string Name, race Race returns CustomRace[/lcode]


You can extend the registration of a custom race to more than one handicap with:
[lcode=jass]method register takes race Race, real Handicap returns nothing[/lcode]




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:

[lcode=jass]method setTownHall takes integer TownHallId returns nothing[/lcode]

  • [lcode=jass]integer TownHallId [/lcode]  - The rawcode of the race's Townhall (eg [lcode=jass]'ogre'[/lcode] for Orc Great Hall)

[lcode=jass]method addWorkerType takes integer WorkerId, integer Priority, integer Quantity returns nothing[/lcode]

  • [lcode=jass]integer WorkerId [/lcode]  - The rawcode of the race's Worker (eg [lcode=jass]'hpea'[/lcode] for Human Peasant)
  • [lcode=jass]integer Priority[/lcode]  - The priority where the worker type unit will be created. Acceptable values are

    • [lcode=jass]CustomRace.NEAR_MINE[/lcode]  - Spawns them near the closest mine. Normally all worker units should use this value.
    • [lcode=jass]CustomRace.NEAR_HALL[/lcode]  - 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.
  • [lcode=jass]integer Quantity [/lcode]  - The amount of initial workers of that type.

[lcode=jass]method addHeroType takes integer HeroId returns nothing[/lcode]

  • [lcode=jass]integer HeroId[/lcode]  - The rawcode of the race's hero (eg [lcode=jass]'Hpal'[/lcode] 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).

[lcode=jass]method setAIScript takes string FilePath returns nothing[/lcode]

  • [lcode=jass]string FilePath[/lcode]  - 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:

[lcode=jass]method setCallback takes CustomRaceCall callback returns nothing[/lcode]

  • [lcode=jass]CustomRaceCall callback[/lcode]  - 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:

[lcode=jass]function interface CustomRaceCall takes player Player, group Workers, unit Goldmine, unit Townhall, unit Randhero returns nothing[/lcode]

  • [lcode=jass]player Player[/lcode]  - The owning player of the units passed to the callback function.
  • [lcode=jass]group Workers[/lcode]  - A unit group with all the created workers created, regardless of type.
  • [lcode=jass]unit Goldmine[/lcode]  - The nearest Goldmine found at the start location. If there isn't a Goldmine nearby, this parameter is null.
  • [lcode=jass]unit Townhall[/lcode]  - The townhall created on initialization.
  • [lcode=jass]unit Randhero[/lcode]  - 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.

[lcode=jass]function GetPlayerCustomRaceName takes player Player returns string[/lcode]

  • [lcode=jass]player Player[/lcode]  - 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
// ==============================================================================
//  Custom Race System by Archmage Owenalacaster
// ==============================================================================
//
//  Purpose:
//       - Creates the starting units for custom races and replaces the standard
//        Melee Initialization trigger. Custom races are selected by race and
//        handicap.
//
//  Usage:
//       - Register a new custom race with CustomRace.create(name, RACE, handicap)
//          Handicaps: Valid handicap values are 1.0, 0.9, 0.8, 0.7, 0.6 and 0.5.
//       - Register a new custom race for all handicaps of a single race with
//        CustomRace.createAll(name, RACE)
//       - Extend the registration of a race with c.register(RACE, handicap)
//       - Set the townhall type with c.setTownHall(unitid)
//       - Add a new worker type with c.addWorkerType(unitid, priority, qty)
//          Priorities: c.NEAR_MINE spawns workers near the mine, where workers
//                        typically spawn.
//                      c.NEAR_HALL spawns workers near the town hall, where
//                        Ghouls spawn.
//       - Add a random hero type with c.addHeroType(unitid)
//       - Set the ai script used by computer players with c.setAIScript(stringpath)
//       - Set a callback function with c.setCallback(CustomRaceCall.function)
//          Callbacks: The callback is executed after all the starting units for a
//                     player are created, and its purpose is to provide enhanced
//                     initial behaviour for a race. A good example of this with the
//                     standard races would be the Undead Goldmine Haunting and
//                     Night Elves Goldmine Entangling.
//                     The callback function passes as arguments all the units
//                     generated in addition to the nearest goldmine detected.
//                     Please note that if a random hero is not created, the last
//                     argument will have a null value, so always do a check.
//        - Get a player's custom race name string with GetPlayerCustomRaceName(player)
//
//  Notes:
//       - Supports a maximum of 24 custom races.
//       - Maximum for worker and hero types are configurable.
//
//  Requirements:
//       - JassHelper version 0.9.E.0 or newer (older versions may still work).
//
//  Installation:
//       - Create a new trigger called CustomRaceSystem.
//       - Convert it to custom text and replace all the code with this code.
//
//  Special Thanks:
//       - Alevice:  He practically co - wrote the code.
//       - cosmicat: His formula for circular unit formation.
//                  Co - developing the single - array registry.
//
// ==============================================================================

library CustomRaceSystem initializer Init
   
// ===========================================================================
//                                CONFIGURATION SECTION
// ===========================================================================
    globals
         // Unit Type Constants
        private constant integer MAX_WORKERTYPES = 4
        private constant integer MAX_HEROTYPES = 4
    endglobals
// ===========================================================================
//                                END CONFIGURATION SECTION
// ===========================================================================

    function interface CustomRaceCall takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
   
    private function r2S takes race r returns string
        if r == RACE_HUMAN then
            return "Human"
        elseif r == RACE_ORC then
            return "Orc"
        elseif r == RACE_UNDEAD then
            return "Undead"
        elseif r == RACE_NIGHTELF then
            return "Night Elf"
        endif
        return "Unknown"
    endfunction
   
    private function r2I takes race r returns integer
        if r == RACE_HUMAN then
            return 1
        elseif r == RACE_ORC then
            return 2
        elseif r == RACE_UNDEAD then
            return 3
        elseif r == RACE_NIGHTELF then
            return 4
        endif
        return 5
    endfunction
   
    globals
         // Victory Defeat Variables
        private string array KEY_STRUCTURE
        private integer KEY_STRUCTURE_COUNT = 0
    endglobals
   
// ===========================================================================
//                                STRUCT DATA
// ===========================================================================
    private keyword createStartingUnits
   
    struct CustomRace
        string name
       
         // Town Hall Variable
        integer townhallType = 0
       // string  townhallName
         // Town Hall name is not currently supported.
       
         // Worker Variables
        integer totalWorkerTypes = 0
        integer array workerType[MAX_WORKERTYPES]
        integer array workerPriority[MAX_WORKERTYPES]
        integer array workerQty[MAX_WORKERTYPES]
       
         // Random Hero Variables
        integer totalHeroTypes = 0
        integer array heroType[MAX_HEROTYPES]
       
         // AI Script Directory String Variable
        string aiscript = ""
       
         // Callback Variable
        private CustomRaceCall c
       
         // Registry Variable
        static integer array REGISTRY
       
         // Spawn Priority Variables
        static integer NEAR_MINE     = 0
        static integer NEAR_HALL     = 1
       
        static method get takes race r, real h returns CustomRace
            return CustomRace(.REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))])
        endmethod
       
        method register takes race r, real h returns boolean
            local CustomRace c = CustomRace.get(r, h)
            if c != 0 then
                debug call BJDebugMsg("|cffff0000Registration of " + .name + " failed due to conflict with " + c.name + " registered for " + r2S(r) + " race Handicap " + R2S(h))
                return false
            endif
            set .REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))] = integer(this)
            return true
        endmethod
       
        static method create takes string name, race r, real h returns CustomRace
            local CustomRace c = CustomRace.allocate()
            set c.name = name
            if not c.register(r, h) then
                call c.destroy()
                return 0
            endif
            return c
        endmethod
       
        static method createAll takes string name, race r returns CustomRace
            local CustomRace c = CustomRace.allocate()
            set c.name = name
            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
                call c.destroy()
                return 0
            endif
            return c
        endmethod
       
        method setTownHall takes integer hallid returns nothing
            set .townhallType = hallid
            set KEY_STRUCTURE[KEY_STRUCTURE_COUNT] = UnitId2String(hallid)
            set KEY_STRUCTURE_COUNT = KEY_STRUCTURE_COUNT + 1
        endmethod
       
        method addWorkerType takes integer workerid, integer priority, integer quantity returns nothing
            set .workerType[.totalWorkerTypes] = workerid
            set .workerPriority[.totalWorkerTypes] = priority
            set .workerQty[.totalWorkerTypes] = quantity
            set .totalWorkerTypes = .totalWorkerTypes + 1
        endmethod
       
        method addHeroType takes integer heroid returns nothing
            local integer i = 0
            set .heroType[.totalHeroTypes] = heroid
            set .totalHeroTypes = .totalHeroTypes + 1
            loop
                call SetPlayerTechMaxAllowed(Player(i), heroid, 1)
                set i = i + 1
                exitwhen i == bj_MAX_PLAYERS
            endloop
        endmethod
       
        private method getRandomHeroType takes nothing returns integer
            local integer randomindex = GetRandomInt(0, .totalHeroTypes - 1)
            return .heroType[randomindex]
        endmethod
       
        method setAIScript takes string s returns nothing
            set .aiscript = s
        endmethod
       
        method setCallback takes CustomRaceCall callb returns nothing
            set .c = callb
        endmethod
       
        private method createRandomHero takes player p, location loc returns unit
            local unit h = CreateUnitAtLoc(p, .getRandomHeroType(), loc, bj_UNIT_FACING)
            if bj_meleeGrantHeroItems then
                call MeleeGrantItemsToHero(h)
            endif
            return h
        endmethod
       
        method createStartingUnits takes player p returns nothing
            local location   startLoc        = GetPlayerStartLocationLoc(p)
            local location   nearMineLoc     = startLoc
            local location   nearTownLoc     = startLoc
            local location   spawnLoc        = startLoc
            local location   heroLoc         = startLoc
            local unit       nearestMine     = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
            local unit       myTownhall      = null
            local unit       myRandHero      = null
            local group      workerGroup     = CreateGroup()
            local integer    workertypeindex = 0
            local integer    workerqty       = 0
            local integer    spawnPriority   = 0
            if nearestMine != null then
                set nearMineLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 320, 0)
                set nearTownLoc = MeleeGetProjectedLoc(startLoc, GetUnitLoc(nearestMine), 288, 0)
                set heroLoc     = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 384, 45)
            endif
            set myTownhall = CreateUnitAtLoc(p, .townhallType, startLoc, bj_UNIT_FACING)
            loop
                exitwhen workertypeindex == .totalWorkerTypes
                    set spawnPriority = .workerPriority[workertypeindex]
                    if (spawnPriority==.NEAR_HALL) then
                        set spawnLoc = nearTownLoc
                    elseif(spawnPriority==.NEAR_MINE) then
                        set spawnLoc = nearMineLoc
                    endif
                    loop
                        call GroupAddUnit(workerGroup, CreateUnitAtLoc(p,.workerType[workertypeindex],PolarProjectionBJ(spawnLoc,65,(I2R(workerqty) * (360.00 / I2R(.workerQty[workertypeindex])))  +  90),bj_UNIT_FACING))
                        set workerqty = workerqty  +  1
                        exitwhen workerqty >= .workerQty[workertypeindex]
                    endloop
                    call RemoveLocation(spawnLoc)
                    set workerqty = 0
                set workertypeindex = workertypeindex + 1
            endloop
            if (IsMapFlagSet(MAP_RANDOM_HERO) and .totalHeroTypes>0 ) then
                set myRandHero = .createRandomHero(p,heroLoc)
            else
                call SetPlayerState(p,PLAYER_STATE_RESOURCE_HERO_TOKENS,bj_MELEE_STARTING_HERO_TOKENS)
            endif
            if(.c!=0) then
                call .c.evaluate(p,workerGroup,nearestMine,myTownhall,myRandHero)
            else
                call DestroyGroup(workerGroup)
            endif
            if nearMineLoc != startLoc then
                call RemoveLocation(nearMineLoc)
                call RemoveLocation(nearTownLoc)
                call RemoveLocation(heroLoc)
            endif
            call RemoveLocation(startLoc)
            set startLoc    = null
            set nearMineLoc = null
            set nearTownLoc = null
            set spawnLoc    = null
            set heroLoc     = null
            set nearestMine = null
            set myTownhall  = null
            set myRandHero  = null
            set workerGroup = null
        endmethod
     endstruct
     
     globals
        private string array PLAYER_RACE
     endglobals
     
     function GetPlayerCustomRaceName takes player p returns string
        return PLAYER_RACE[GetPlayerId(p)]
     endfunction
   
//===========================================================================
//                         UNIT CREATION SECTION
//===========================================================================
    private function CreateStartingUnitsForAllPlayers takes nothing returns nothing
        local integer    index = 0
        local player     indexPlayer
        local race       playerRace
        local CustomRace c
        loop
            set indexPlayer = Player(index)
            if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
                set playerRace = GetPlayerRace(indexPlayer)
                set c = CustomRace.get(playerRace,GetPlayerHandicap(indexPlayer) + 0.01)
                if (GetPlayerController(indexPlayer) == MAP_CONTROL_USER or (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER and c.aiscript != "" )) and c != 0 then
                    set PLAYER_RACE[index] = c.name
                    call c.createStartingUnits(indexPlayer)
                elseif playerRace == RACE_HUMAN then
                    call MeleeStartingUnitsHuman(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
                elseif playerRace == RACE_ORC then
                    call MeleeStartingUnitsOrc(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
                elseif playerRace == RACE_NIGHTELF then
                    call MeleeStartingUnitsNightElf(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
                elseif playerRace == RACE_UNDEAD then
                    call MeleeStartingUnitsUndead(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
                else
                    call MeleeStartingUnitsUnknownRace(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
                endif
            endif
            set index = index  +  1
            exitwhen index == bj_MAX_PLAYERS
        endloop
    endfunction
   
//===========================================================================
//                         CUSTOM MELEE AI SECTION
//===========================================================================
    private function CustomMeleeStartingAI takes nothing returns nothing
        local integer index = 0
        local player  indexPlayer
        local race    indexRace
        local CustomRace c
        loop
            set indexPlayer = Player(index)
            if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
                set indexRace = GetPlayerRace(indexPlayer)
                set c = CustomRace.get(indexRace,GetPlayerHandicap(indexPlayer) + 0.01)
                call SetPlayerHandicap(indexPlayer,1.0)
                if (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER) then
                    // Run a race-specific melee AI script.
                    if c != 0 and c.aiscript != "" then
                        call StartMeleeAI(indexPlayer, c.aiscript)
                    elseif (indexRace == RACE_HUMAN) then
                        call PickMeleeAI(indexPlayer, "human.ai", null, null)
                    elseif (indexRace == RACE_ORC) then
                        call PickMeleeAI(indexPlayer, "orc.ai", null, null)
                    elseif (indexRace == RACE_UNDEAD) then
                        call PickMeleeAI(indexPlayer, "undead.ai", null, null)
                        call RecycleGuardPosition(bj_ghoul[index])
                    elseif (indexRace == RACE_NIGHTELF) then
                        call PickMeleeAI(indexPlayer, "elf.ai", null, null)
                    else
                        // Unrecognized race.
                    endif
                    call ShareEverythingWithTeamAI(indexPlayer)
                endif
            endif

            set index = index  +  1
            exitwhen index == bj_MAX_PLAYERS
        endloop
    endfunction   

//===========================================================================
//                         VICTORY DEFEAT SECTION
//===========================================================================
    private function CustomGetAllyKeyStructureCount takes player whichPlayer returns integer
        local integer    i           = 0
        local integer    keyStructs  = 0
        local integer    playerIndex = 0
        local player     indexPlayer
        loop
            set indexPlayer = Player(playerIndex)
            if (PlayersAreCoAllied(whichPlayer, indexPlayer)) then
                set keyStructs = keyStructs  +  GetPlayerTypedUnitCount(indexPlayer, "townhall", true, true)
                set keyStructs = keyStructs  +  GetPlayerTypedUnitCount(indexPlayer, "greathall", true, true)
                set keyStructs = keyStructs  +  GetPlayerTypedUnitCount(indexPlayer, "necropolis", true, true)
                set keyStructs = keyStructs  +  GetPlayerTypedUnitCount(indexPlayer, "treeoflife", true, true)
                loop
                    set keyStructs = keyStructs  +  GetPlayerTypedUnitCount(indexPlayer, KEY_STRUCTURE[i], true, true)
                    set i = i + 1
                    exitwhen i == KEY_STRUCTURE_COUNT
                endloop
            endif
            set playerIndex = playerIndex  +  1
            exitwhen playerIndex == bj_MAX_PLAYERS
        endloop
        return keyStructs
    endfunction

    private function CustomPlayerIsCrippled takes player whichPlayer returns boolean
        local integer allyStructures    = MeleeGetAllyStructureCount(whichPlayer)
        local integer allyKeyStructures = CustomGetAllyKeyStructureCount(whichPlayer)
        return (allyStructures > 0) and (allyKeyStructures <= 0)
    endfunction

    private function CustomCheckForCrippledPlayers takes nothing returns nothing
        local integer    playerIndex
        local player     indexPlayer
        local boolean    isNowCrippled
        call MeleeCheckForLosersAndVictors()
        if bj_finishSoonAllExposed then
            return
        endif
        set playerIndex = 0
        loop
            set indexPlayer = Player(playerIndex)
            set isNowCrippled = CustomPlayerIsCrippled(indexPlayer)
            if (not bj_playerIsCrippled[playerIndex] and isNowCrippled) then
                set bj_playerIsCrippled[playerIndex] = true
                call TimerStart(bj_crippledTimer[playerIndex], bj_MELEE_CRIPPLE_TIMEOUT, false, function MeleeCrippledPlayerTimeout)
                if (GetLocalPlayer() == indexPlayer) then
                    call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], true)
                    call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_WARNING_HUMAN"))
                endif
            elseif (bj_playerIsCrippled[playerIndex] and not isNowCrippled) then
                set bj_playerIsCrippled[playerIndex] = false
                call PauseTimer(bj_crippledTimer[playerIndex])
                if (GetLocalPlayer() == indexPlayer) then
                    call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], false)
                    if (MeleeGetAllyStructureCount(indexPlayer) > 0) then
                        if (bj_playerIsExposed[playerIndex]) then
                            call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
                        else
                            call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
                        endif
                    endif
                endif
                call MeleeExposePlayer(indexPlayer, false)
            endif
            set playerIndex = playerIndex  +  1
            exitwhen playerIndex == bj_MAX_PLAYERS
        endloop
    endfunction

    private function CustomInitVictoryDefeat takes nothing returns nothing
        local trigger    checker = CreateTrigger()
        local trigger    trig
        local integer    index
        local player     indexPlayer
        set bj_finishSoonTimerDialog = CreateTimerDialog(null)
        call TriggerAddAction(checker, function CustomCheckForCrippledPlayers)
        set trig = CreateTrigger()
        call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_SOON)
        call TriggerAddAction(trig, function MeleeTriggerTournamentFinishSoon)
        set trig = CreateTrigger()
        call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_NOW)
        call TriggerAddAction(trig, function MeleeTriggerTournamentFinishNow)
        set index = 0
        loop
            set indexPlayer = Player(index)
            if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
                set bj_meleeDefeated[index] = false
                set bj_meleeVictoried[index] = false
                set bj_playerIsCrippled[index] = false
                set bj_playerIsExposed[index] = false
                set bj_crippledTimer[index] = CreateTimer()
                set bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
                call TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(indexPlayer))
                call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
                call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
                call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
                call TriggerRegisterPlayerAllianceChange(checker, indexPlayer, ALLIANCE_PASSIVE)
                call TriggerRegisterPlayerStateEvent(checker, indexPlayer, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
                set trig = CreateTrigger()
                call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_DEFEAT)
                call TriggerAddAction(trig, function MeleeTriggerActionPlayerDefeated)
                set trig = CreateTrigger()
                call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
                call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
             else
                set bj_meleeDefeated[index] = true
                set bj_meleeVictoried[index] = false
                if (IsPlayerObserver(indexPlayer)) then
                    set trig = CreateTrigger()
                    call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
                    call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
                endif
            endif
            set index = index  +  1
            exitwhen index == bj_MAX_PLAYERS
        endloop
        call TimerStart(CreateTimer(), 2.0, false, function CustomCheckForCrippledPlayers)
    endfunction
   
    private function TimerAction takes nothing returns nothing
        call SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
        call MeleeStartingHeroLimit()
        call MeleeGrantHeroItems()
        call MeleeStartingResources()
        call MeleeClearExcessUnits()
        call CreateStartingUnitsForAllPlayers()
        call CustomMeleeStartingAI()
        call CustomInitVictoryDefeat()
        call DestroyTimer(GetExpiredTimer())
    endfunction
   
    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(),0,false,function TimerAction)
    endfunction
   
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

//====================================================================
//                           HUMAN SETUP
//====================================================================

library HumanSetup initializer Init requires CustomRaceSystem
   
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Human",RACE_HUMAN,1.0)
        call c.setTownHall('htow')                  // Town Hall
        call c.addWorkerType('hpea',c.NEAR_MINE,5)  // Peasant
        call c.addHeroType('Hpal')                  // Paladin
        call c.addHeroType('Hamg')                  // Archmage
        call c.addHeroType('Hmkg')                  // Mountain King
        call c.addHeroType('Hblm')                  // Blood Mage
        call c.setAIScript("human.ai")
    endfunction
   
endlibrary

//====================================================================
//                            ORC SETUP
//====================================================================

library OrcSetup initializer Init requires CustomRaceSystem
   
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Orc",RACE_ORC,1.0)
        call c.setTownHall('ogre')                  // Great Hall
        call c.addWorkerType('opeo',c.NEAR_MINE,5)  // Peon
        call c.addHeroType('Obla')                  // Blademaster
        call c.addHeroType('Ofar')                  // Far Seer
        call c.addHeroType('Otch')                  // Tauren Chieftain
        call c.addHeroType('Oshd')                  // Shadow Hunter
        call c.setAIScript("orc.ai")
    endfunction
   
endlibrary

//====================================================================
//                          UNDEAD SETUP
//====================================================================

library UndeadSetup initializer Init requires CustomRaceSystem
   
    private function WorkerHideToggle takes nothing returns nothing
        call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
    endfunction
   
    private function HauntGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
        call ForGroup(workers,function WorkerHideToggle)
        call BlightGoldMineForPlayerBJ(goldmine,play)
        call ForGroup(workers,function WorkerHideToggle)
        call DestroyGroup(workers)
    endfunction
   
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Undead",RACE_UNDEAD,1.0)
        call c.setTownHall('unpl')                  // Necropolis
        call c.addWorkerType('uaco',c.NEAR_MINE,3)  // Acolyte
        call c.addWorkerType('ugho',c.NEAR_HALL,1)  // Ghoul
        call c.addHeroType('Udea')                  // Death Knight
        call c.addHeroType('Ulic')                  // Lich
        call c.addHeroType('Udre')                  // Dreadlord
        call c.addHeroType('Ucrl')                  // Crypt Lord
        call c.setCallback(CustomRaceCall.HauntGoldMine)
        call c.setAIScript("undead.ai")
    endfunction
   
endlibrary

//====================================================================
//                          NIGHT ELF SETUP
//====================================================================

library NightElfSetup initializer Init requires CustomRaceSystem
   
    private function EntangleGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
        call SetUnitPosition(townhall,GetUnitX(goldmine),GetUnitY(goldmine))
        call IssueTargetOrder(townhall, "entangleinstant", goldmine)
        call DestroyGroup(workers)
    endfunction
   
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Night Elf",RACE_NIGHTELF,1.0)
        call c.setTownHall('etol')                  // Tree of Life
        call c.addWorkerType('ewsp',c.NEAR_MINE,5)  // Wisp
        call c.addHeroType('Ekee')                  // Keeper of the Grove
        call c.addHeroType('Emoo')                  // Priestess of the Moon
        call c.addHeroType('Edem')                  // Demon Hunter
        call c.addHeroType('Ewar')                  // Warden
        call c.setCallback(CustomRaceCall.EntangleGoldMine)
        call c.setAIScript("elf.ai")
    endfunction
   
endlibrary

//====================================================================
//                           NAGA SETUP
//====================================================================

library NagaSetup initializer Init requires CustomRaceSystem
   
    private function Init takes nothing returns nothing
        local CustomRace c = CustomRace.create("Naga",RACE_NIGHTELF,0.9)
        call c.setTownHall('nntt')                  // Temple of Tides
        call c.addWorkerType('nmpe',c.NEAR_MINE,5)  // Murgul Slave
        call c.addHeroType('Hvsh')                  // Lady Vashj
        call c.setAIScript("naga.ai")
    endfunction
   
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)