[Lua] UI-Custom Race System

This bundle is marked as pending. It has not been reviewed by a staff member yet.
An in-game race faction selection system which seamlessly integrates to the classic melee gameplay.
Useful for Techtree Contests and custom melee scenarios.

Features:
  • Unlimited number of factions per race. (practically)
  • Race Description Text Box
  • Race Preview Box
  • Delayed melee game start (up to 3 seconds. This can be configured as you wish.).
Pros:
  • Custom UI Frames for race selection (not dialog-based)
Cons:
  • Requires 1.31.1 or higher
  • Visual settings based on 1.31.1
  • Requires the map to be in Lua mode

Lua Code

How to Import

How to Use

Lua API

Changelog

Credits



Note: All of these libraries here are required. Their order is dictated from left to right, top to bottom.

Core

Initializer

UI

Victory Conditions

Setup

Defaults


Lua:
do
    local tb            = {}
    local raceContainer = {
        [RACE_HUMAN]        = {},
        [RACE_ORC]          = {},
        [RACE_UNDEAD]       = {},
        [RACE_NIGHTELF]     = {},
        [RACE_DEMON]        = {},
        [RACE_OTHER]        = {},
    }
    CustomRaceSystem    = setmetatable({}, tb)
    --  Define __index and __newindex metamethods.
    tb.__index          = function(t, k)
        if type(k) == 'string' and 
           (k:sub(1,1) == '_') and
           (k:sub(1,2) ~= '__') then
            return nil
        end
        return tb[k]
    end
    tb.__newindex       = function(t, k, v)
        if tb[k] then
            return
        end
        rawset(t, k, v)
    end
    tb._hall            = {}
    tb._hallptr         = {}
    tb._hero            = {}
    tb._heroptr         = {}
    tb._container       = raceContainer
    tb._DEBUG           = false
   
    local function printAfter(delay, ...)
        if not tb._DEBUG then
            return
        end
        local t = {...}
        TimerStart(CreateTimer(), delay or 0, false, function()
            PauseTimer(GetExpiredTimer())
            DestroyTimer(GetExpiredTimer())
            --  Ensure that the entry is
            if t then
                print(table.unpack(t))
            end
            t = nil
        end)
    end
    local function addFaction(race, faction)
        local t             = raceContainer[race] or {}
        raceContainer[race] = t
        t[#t+1]             = faction
    end
    function tb.create(race, name)
        local o     = {}
        o.race      = race
        o.name      = name or ""
        o.hall      = {}
        o.hallptr   = {}
        o.hero      = {}
        o.heroptr   = {}
        addFaction(race, o)
        setmetatable(o, tb)
        return o
    end
    function tb:addHall(...)
        local t = {...}
        if #t < 1 then
            return
        end
        for i = 1, #t do
            local id    = (type(t[i]) == 'string' and FourCC(t[i])) or t[i]
            if not self.hallptr[id] then
                self.hall[#self.hall + 1]   = id
                self.hallptr[id]            = #self.hall
            end
            if not tb._hallptr[id] then
                tb._hall[#tb._hall + 1]     = id
                tb._hallptr[id]             = #tb._hall
            end
        end
    end
    function tb:addHero(...)
        local t = {...}
        if #t < 1 then
            return
        end
        for i = 1, #t do
            local id    = (type(t[i]) == 'string' and FourCC(t[i])) or t[i]
            if not self.heroptr[id] then
                self.hero[#self.hero + 1]   = id
                self.heroptr[id]            = #self.hero
            end
            if not tb._heroptr[id] then
                tb._hero[#tb._hero + 1]     = id
                tb._heroptr[id]             = #tb._hero
            end
        end
    end
    function tb:defSetup(func)
        self.setup          = func
    end
    function tb:defAISetup(func)
        self.aiSetup        = func
    end
    function tb:defRacePic(picPath)
        self.racePic        = picPath or ""
    end
    function tb:defDescription(desc)
        self.description    = desc or ""
    end
    function tb:defName(name)
        self.name           = name or ""
    end
    function tb:defPlaylist(playlist)
        self.playlist       = playlist
    end
end

Lua:
do
    local tb                = getmetatable(CustomRaceSystem)
    local melee             = {}
    CustomRace              = setmetatable({}, tb)
    CustomRace.faction      = {}
    CustomRace.countdown    = {
        TICKS               = 3,
        INTERVAL            = 1.00,
        INCLUDE_EXTRA_TICK  = true,
        Z_OFFSET            = 800.00,
    }
    function melee.generateTick()
        local snd   = CreateSound( "Sound\\Interface\\BattleNetTick.wav", false, false, false, 10, 10, "" )
        SetSoundParamsFromLabel( snd, "ChatroomTimerTick" )
        SetSoundDuration( snd, 476 )
        return snd
    end
    function melee.generateHorn()
        local snd   = CreateSound( "Sound\\Ambient\\DoodadEffects\\TheHornOfCenarius.wav", false, false, false, 10, 10, "DefaultEAXON" )
        SetSoundParamsFromLabel( snd, "HornOfCenariusSound" )
        SetSoundDuration( snd, 12120 )
        return snd
    end
    function melee.playSound(snd)
        StartSound(snd)
        KillSoundWhenDone(snd)
    end
    function melee.grantItemsToHero(whichhero)
        if not IsUnitType(whichhero, UNIT_TYPE_HERO) then
            return
        end
        local owner = GetPlayerId(GetOwningPlayer(whichhero))
        -- If we haven't twinked N heroes for this player yet, twink away.
        if (bj_meleeTwinkedHeroes[owner] < bj_MELEE_MAX_TWINKED_HEROES) then
            UnitAddItemById(whichhero, FourCC('stwp'))
            bj_meleeTwinkedHeroes[owner] = bj_meleeTwinkedHeroes[owner] + 1
        end
    end
    function melee.clearNearbyUnits(locx, locy, range)
        local grp   = CreateGroup()
        GroupEnumUnitsInRange(grp, locx, locy, range, nil)
        ForGroup(grp, function()
            local filterUnit    = GetEnumUnit()
            local owner         = GetOwningPlayer(filterUnit)
            if (owner == Player(PLAYER_NEUTRAL_AGGRESSIVE)) or 
               ((owner == Player(PLAYER_NEUTRAL_PASSIVE)) and
               not IsUnitType(filterUnit, UNIT_TYPE_STRUCTURE)) then
                -- Remove any Neutral Hostile units from the area.
                -- Remove non-structure Neutral Passive units from the area.
                RemoveUnit(filterUnit)
            end
        end)
        DestroyGroup(grp)
    end
    local function forAllPlayers(func)
        for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
            func(i, Player(i))
        end
    end
    local function startingVis()
        SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
    end
    local function startingHeroLimit()
        forAllPlayers(function(id, player)
            SetPlayerTechMaxAllowed(player, FourCC('HERO'), bj_MELEE_HERO_LIMIT)
            for i = 1, #tb._hero do
                SetPlayerTechMaxAllowed(player, tb._hero[i], bj_MELEE_HERO_TYPE_LIMIT)
            end
        end)
    end
    local function grantHeroItems()
        local trig  = CreateTrigger()
        TriggerAddCondition(trig, Condition(function()
            melee.grantItemsToHero(GetTrainedUnit())
        end))
        forAllPlayers(function(id, player)
            if id >= bj_MAX_PLAYERS then
                return
            end
            TriggerRegisterPlayerUnitEvent(trig, player, EVENT_PLAYER_UNIT_TRAIN_FINISH, nil)
        end)
        trig        = CreateTrigger()
        TriggerRegisterPlayerUnitEvent(trig, Player(PLAYER_NEUTRAL_PASSIVE), EVENT_PLAYER_UNIT_SELL, filterMeleeTrainedUnitIsHeroBJ)
        TriggerAddAction(trig, function()
            melee.grantItemsToHero(GetSoldUnit())
        end)
       
        bj_meleeGrantHeroItems  = true
    end
   
    local slotStatus    = {}
    local function startingResources()
        if (VersionGet() == VERSION_REIGN_OF_CHAOS) then
            startingGold    = bj_MELEE_STARTING_GOLD_V0
            startingLumber  = bj_MELEE_STARTING_LUMBER_V0
        else
            startingGold    = bj_MELEE_STARTING_GOLD_V1
            startingLumber  = bj_MELEE_STARTING_LUMBER_V1
        end
        forAllPlayers(function(id, player)
            slotStatus[id]  = GetPlayerSlotState(player)
            if id >= bj_MAX_PLAYERS or 
               (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
                return
            end
            SetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD, startingGold)
            SetPlayerState(player, PLAYER_STATE_RESOURCE_LUMBER, startingLumber)
        end)
    end
    local function clearExcessUnits()
        forAllPlayers(function(id, player)
            if id >= bj_MAX_PLAYERS or (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
                return
            end
            local locid         = GetPlayerStartLocation(player)
            local locx, locy    = GetStartLocationX(locid), GetStartLocationY(locid)
            melee.clearNearbyUnits(locx, locy, bj_MELEE_CLEAR_UNITS_RADIUS)
        end)
    end
    local function startRaceSelection()
        CustomRaceUI.init()
        forAllPlayers(function(id, player)
            if id >= bj_MAX_PLAYERS or (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
                return
            end
            CustomRaceUI.feedPlayerData(id, player)
            CustomRaceUI.processPlayer(player)
        end)
        CustomRaceUI.checkFactionSelection()
    end
    local function pauseAll()
        SuspendTimeOfDay(true)
        CustomRace.pauseFlag    = true
        CustomRace.pauseTrig    = CreateTrigger()
        TriggerAddCondition(CustomRace.pauseTrig, Condition(function()
            if CustomRace.pauseFlag then
                PauseUnit(GetTriggerUnit(), true)
            end
        end))       
        local rect, reg         = GetWorldBounds(), CreateRegion()
        RegionAddRect(reg, rect)
        TriggerRegisterEnterRegion(CustomRace.pauseTrig, reg, nil)
        local grp               = CreateGroup()
        GroupEnumUnitsInRect(grp, rect, nil)
        ForGroup(grp, function()
            PauseUnit(GetEnumUnit(), true)
        end)
        DestroyGroup(grp)
    end
    local function unpauseAll()
        SuspendTimeOfDay(false)
        CustomRace.pauseFlag    = nil
        DestroyTrigger(CustomRace.pauseTrig)
        CustomRace.pauseTrig    = nil
        local grp               = CreateGroup()
        local rect              = GetWorldBounds()
        GroupEnumUnitsInRect(grp, rect, nil)
        ForGroup(grp, function()
            PauseUnit(GetEnumUnit(), false)
        end)
        DestroyGroup(grp)
        RemoveRect(rect)
    end
    local function startingCamera()
        local player    = GetLocalPlayer()
        local id        = GetPlayerId(player)
        local dur       = (CustomRace.countdown.TICKS + 1)*CustomRace.countdown.INTERVAL
        local fixCamera = not (IsPlayerObserver(player)) and 
                          (slotStatus[id] == PLAYER_SLOT_STATE_PLAYING)
        EnableUserControl(false)
        if fixCamera then
            SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 2650.00, 0)
            SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 1750.00, dur)
        end
        local checkVictorTimer  = CreateTimer()
        TimerStart(checkVictorTimer, 2.00, false, function()
            CustomRaceConditions.checkVictors()
        end)
        TimerStart(CreateTimer(), dur, false, function()
            DestroyTimer(GetExpiredTimer())
            EnableUserControl(true)
            if fixCamera then
                CameraSetSmoothingFactor(0.00)
                SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 1650.00, CustomRace.countdown.INTERVAL)
            end
            ResumeTimer(checkVictorTimer)
        end)
        PauseTimer(checkVictorTimer)
    end
    local function startingUnits()
        Preloader("scripts\\SharedMelee.pld")
        forAllPlayers(function(id, player)
            if id >= bj_MAX_PLAYERS or (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
                return
            end
            local race      = GetPlayerRace(player)
            local faction   = tb._container[race][CustomRace.faction[id]]
            local startloc  = GetStartLocationLoc(GetPlayerStartLocation(player))
            if faction then
                if (not faction.setup) or (faction.setup) and
                   (not pcall(faction.setup, player, startloc, true, true, true))
                    and (tb._DEBUG) then
                    print("Warning! Setup function failed for the following player:", GetPlayerName(player))
                    print("Faction ID:", faction.name or ("Faction(" .. tostring(CustomRace.faction[id]) .. ")"))
                end
                RemoveLocation(startloc)
                if GetPlayerController(player) == MAP_CONTROL_COMPUTER and 
                    ((not faction.aiSetup) or (not pcall(faction.aiSetup, player))) and
                    (tb._DEBUG) then
                    print("Warning! AI-Setup function failed for the following computer player:", GetPlayerName(player))
                    print("Faction ID:", faction.name or ("Faction(" .. tostring(CustomRace.faction[id]) .. ")"))
                end
                --  Copied from Riki's custom race setup function
                if faction.playlist and (faction.playlist ~= "") then
                    ClearMapMusic()
                    StopMusic(false)
                    if not pcall(PlayMusic, faction.playlist) and 
                       (tb._DEBUG) then
                        print("Warning! Custom music playlist failed to play")
                        print("Faction ID:", faction.name or ("Faction(" .. tostring(CustomRace.faction[id]) .. ")"))
                        print("Faction playlist type:", type(faction.playlist))
                        print("CustomRaceSystem >> Playlist must be a string value!\n")
                    end
                end
            end
        end)
    end
    local function startingCountdown()
        local timer = CreateTimer()
        local ticks = CustomRace.countdown.TICKS + 1
        local disp  = ticks
        if CustomRace.countdown.INCLUDE_EXTRA_TICK then
            ticks   = ticks + 1
        end
        TimerStart(timer, CustomRace.countdown.INTERVAL, true, function()
            disp    = disp - 1
            ticks   = ticks - 1
            if ticks <= 0 then
                PauseTimer(timer)
                DestroyTimer(timer)
            end
            if disp == 0 then
                CustomRaceUI.mainScreen.display("START!")
                melee.playSound(melee.generateHorn())
            elseif disp > 0 then
                CustomRaceUI.mainScreen.display(disp)
                melee.playSound(melee.generateTick())
            end
            if ticks <= 0 then
                unpauseAll()
                xpcall(CustomRaceConditions.init, print)
            end
        end)
    end
    function CustomRace.initialization()
        startingVis()
        startingHeroLimit()
        grantHeroItems()
        startingResources()
        clearExcessUnits()
        pauseAll()
        startRaceSelection()
    end
    function CustomRace.start()
        startingUnits()
        startingCountdown()
        startingCamera()
    end
end

Lua:
do
    --[[
        HEIGHT  -> with respect to y-axis
        WIDTH   -> with respect to x-axis
    ]]
    local tb                        = getmetatable(CustomRaceSystem)
    local internal                  = {}
    CustomRaceUI                    = setmetatable({}, tb)
    CustomRaceUI.toc                = "war3mapImported\\CustomRaceTOC.toc"
    --  Configure this if you know what you are doing
    --  Otherwise, try to leave this as is
    local config                    = {
        RACE_MAX_OPTIONS            = 4,
        MAIN_WIDTH                  = 0.50,
        MAIN_HEIGHT                 = 0.44,
        BACKUP_WIDTH                = 0.28,
        BACKUP_HEIGHT               = 0.196,
        UPPER_BOX_HEIGHT            = 0.20,     -- Must be >= DESC_BOX_HEIGHT
        LOWER_BOX_HEIGHT            = 0.24,     -- Must be < MAIN_HEIGHT
        DESC_BOX_WIDTH              = 0.28,
        DESC_BOX_HEIGHT             = 0.14,
        ICON_BOX_WIDTH              = 0.16,
        ICON_BOX_HEIGHT             = 0.16,
        ICON_DISP_WIDTH             = 0.148,
        ICON_DISP_HEIGHT            = 0.148,
        RACE_TEXT_WIDTH             = 0.28,
        RACE_TEXT_HEIGHT            = 0.02,
        SELECTION_WIDTH             = 0.44,
        SELECTION_HEIGHT            = 0.18,
        SLIDER_WIDTH                = 0.01,
        CONFIRM_WIDTH               = 0.14,
        CONFIRM_HEIGHT              = 0.04,
        SELECTION_TEXT_WIDTH        = 0.24,
        SELECTION_TEXT_HEIGHT       = 0.02,
        TEXTFRAME_WIDTH             = 0.20,
        TEXTFRAME_HEIGHT            = 0.04,
        WAITTEXT_WIDTH              = 0.16,
        WAITTEXT_HEIGHT             = 0.055,
        PCOUNT_WIDTH                = 0.166,
        PCOUNT_HEIGHT               = 0.0275,
        margin                      = {
            UPPER_UPPER_MARGIN          = 0.02,
            UPPER_LOWER_MARGIN          = 0.02,
            UPPER_LEFT_MARGIN           = 0.02,
            UPPER_RIGHT_MARGIN          = 0.02,
            LOWER_UPPER_MARGIN          = 0.00,
            LOWER_LOWER_MARGIN          = 0.02,
            LOWER_LEFT_MARGIN           = 0.02,
            LOWER_RIGHT_MARGIN          = 0.02,
            RACE_LEFT_MARGIN            = 0.005,
           
            SELECTION_LOWER_MARGIN      = 0.04,
            SELECTION_TEXT_LEFT_MARGIN  = 0.02,
            WAIT_UPPER_MARGIN           = -0.014,
            WAIT_LEFT_MARGIN            = 0,
            PCOUNT_UPPER_MARGIN         = -0.008,
            PCOUNT_LEFT_MARGIN          = 0.017,
        },
        scale                       = {
            RACE_TEXT               = 1.35,
            SELECTION_TEXT          = 1.25,
            WAIT_TEXT               = 1.90,
            PCOUNT_TEXT             = 1.75,
        },
        framename                   = {
            main                    = "EscMenuBackdrop",
            descMain                = "BattleNetTextAreaTemplate",
            iconMain                = "QuestButtonBaseTemplate",
            selectMain              = "QuestButtonBaseTemplate",
            sliderMain              = "QuestMainListScrollBar",
            buttonFrame             = "ScriptDialogButton",
            --  List buttons
            listButtons             = "EscMenuButtonTemplate",
            listHighlight           = "QuestButtonMouseOverHighlightTemplate",
            --  Backup
            backupMain              = "EscMenuBackdrop",
        },
        --  An exception to the above suggestion
        --  you can freely configure this (as long as it's not less than 0)
        WAIT_DURATION               = 15.00,
        CRITICAL_DURATION           = 5.00,
    }
    function internal.loadTOC()
        if not BlzLoadTOCFile(CustomRaceUI.toc) then
            error("CustomRaceSystemUI >> toc file was not loaded.")
        end
    end
    function internal.generateSmallClick()
        local snd   = CreateSound("Sound\\Interface\\MouseClick1.wav", false, false, false, 10, 10, "")
        SetSoundParamsFromLabel( gg_snd_MouseClick1, "InterfaceClick")
        SetSoundDuration(snd, 390)
        SetSoundVolume(snd, 127)
        return snd
    end
    function internal.generateBigClick()
        local snd   = CreateSound("Sound\\Interface\\BigButtonClick.wav", false, false,
                                  false, 10, 10, "" )
        SetSoundParamsFromLabel(snd, "MouseClick1")
        SetSoundDuration(snd, 239)
        SetSoundVolume(snd, 127)
        return snd
    end
    function internal.generateCriticalSound()
        local snd   = CreateSound("Sound\\Interface\\Warning.wav", false, false,
                                  false, 10, 10, "" )
        SetSoundParamsFromLabel(snd, "Warning")
        SetSoundDuration(snd, 1903)
        SetSoundVolume(snd, 127)
        return snd
    end
    function internal.playSound(player, snd)
        if GetLocalPlayer() ~= player then
            SetSoundVolume(snd, 0)
        end
        StartSound(snd)
        KillSoundWhenDone(snd)
    end
   
    config.UPPER_BOX_WIDTH      = config.MAIN_WIDTH
    config.LOWER_BOX_WIDTH      = config.MAIN_WIDTH
    config.SLIDER_HEIGHT        = config.SELECTION_HEIGHT
    function internal.prepareAllFrames()
        local world             = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
        CustomRaceUI.main       = BlzCreateFrame(config.framename.main, world, 0, 0)
        BlzFrameSetSize(CustomRaceUI.main, config.MAIN_WIDTH, config.MAIN_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.main)
        BlzFrameSetAbsPoint(CustomRaceUI.main, FRAMEPOINT_TOP, 
                            0.4, 0.80 - config.MAIN_HEIGHT/2)
        CustomRaceUI.descMain   = BlzCreateFrame(config.framename.descMain, CustomRaceUI.main, 0, 0)
        BlzFrameSetSize(CustomRaceUI.descMain, config.DESC_BOX_WIDTH, config.DESC_BOX_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.descMain)
        BlzFrameSetPoint(CustomRaceUI.descMain, FRAMEPOINT_TOPRIGHT,
                         CustomRaceUI.main, FRAMEPOINT_TOPRIGHT,
                         -config.margin.UPPER_RIGHT_MARGIN, -config.margin.UPPER_RIGHT_MARGIN*2)
        CustomRaceUI.iconMain   = BlzCreateFrame(config.framename.iconMain, CustomRaceUI.main, 0, 0)
        BlzFrameSetSize(CustomRaceUI.iconMain, config.ICON_BOX_WIDTH, config.ICON_BOX_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.iconMain)
        BlzFrameSetPoint(CustomRaceUI.iconMain, FRAMEPOINT_TOPLEFT,
                         CustomRaceUI.main, FRAMEPOINT_TOPLEFT, 
                         config.margin.UPPER_LEFT_MARGIN, -config.margin.UPPER_UPPER_MARGIN)
        CustomRaceUI.raceMain   = BlzCreateFrameByType("TEXT", "CustomRaceHeaderText",
                                                       CustomRaceUI.main, "", 0)
        BlzFrameSetSize(CustomRaceUI.raceMain, 
                        (config.RACE_TEXT_WIDTH-config.margin.RACE_LEFT_MARGIN)/config.scale.RACE_TEXT,
                        config.RACE_TEXT_HEIGHT/config.scale.RACE_TEXT)
        BlzFrameSetScale(CustomRaceUI.raceMain, config.scale.RACE_TEXT)
        BlzFrameSetTextAlignment(CustomRaceUI.raceMain, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
        BlzFrameClearAllPoints(CustomRaceUI.raceMain)
        BlzFrameSetPoint(CustomRaceUI.raceMain, FRAMEPOINT_BOTTOMLEFT,
                         CustomRaceUI.descMain, FRAMEPOINT_TOPLEFT,
                         config.margin.RACE_LEFT_MARGIN, 0)
        CustomRaceUI.selectMain = BlzCreateFrame(config.framename.selectMain, CustomRaceUI.main, 0, 0)
        BlzFrameSetSize(CustomRaceUI.selectMain, config.SELECTION_WIDTH, config.SELECTION_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.selectMain)
        BlzFrameSetPoint(CustomRaceUI.selectMain, FRAMEPOINT_BOTTOMLEFT,
                         CustomRaceUI.main, FRAMEPOINT_BOTTOMLEFT,
                         config.margin.LOWER_LEFT_MARGIN,
                         config.margin.LOWER_LOWER_MARGIN + config.margin.SELECTION_LOWER_MARGIN)
        CustomRaceUI.sliderMain = BlzCreateFrameByType("SLIDER", "CustomRaceSlider",
                                                       CustomRaceUI.main, config.framename.sliderMain, 0)
        BlzFrameSetSize(CustomRaceUI.sliderMain, config.SLIDER_WIDTH, config.SLIDER_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.sliderMain)
        BlzFrameSetPoint(CustomRaceUI.sliderMain, FRAMEPOINT_LEFT,
                         CustomRaceUI.selectMain, FRAMEPOINT_RIGHT,
                         0.00, 0.00)
        CustomRaceUI.buttonFrame    = BlzCreateFrameByType("GLUETEXTBUTTON", "CustomRaceConfirmButton",
                                                           CustomRaceUI.main, config.framename.buttonFrame, 0)
        BlzFrameSetSize(CustomRaceUI.buttonFrame, config.CONFIRM_WIDTH, config.CONFIRM_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.buttonFrame)
        BlzFrameSetPoint(CustomRaceUI.buttonFrame, FRAMEPOINT_BOTTOMRIGHT,
                         CustomRaceUI.main, FRAMEPOINT_BOTTOMRIGHT,
                         -config.margin.LOWER_RIGHT_MARGIN, config.margin.LOWER_LOWER_MARGIN)
        BlzFrameSetText(CustomRaceUI.buttonFrame, "Confirm Race")
    end
    function internal.prepareBackupFrames()
        local world             = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
        CustomRaceUI.backupMain = BlzCreateFrame(config.framename.backupMain, world, 0, 0)
        BlzFrameSetSize(CustomRaceUI.backupMain, config.BACKUP_WIDTH, config.BACKUP_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.backupMain)
        BlzFrameSetAbsPoint(CustomRaceUI.backupMain, FRAMEPOINT_CENTER, 
                            0.4, 0.4)
        CustomRaceUI.waitText   = BlzCreateFrameByType("TEXT", "CustomRaceBackupDisplayText",
                                                       CustomRaceUI.backupMain, "", 0)
        BlzFrameSetSize(CustomRaceUI.waitText, config.WAITTEXT_WIDTH, config.WAITTEXT_HEIGHT)
        BlzFrameSetScale(CustomRaceUI.waitText, config.scale.WAIT_TEXT)
        BlzFrameSetPoint(CustomRaceUI.waitText, FRAMEPOINT_TOP, 
                         CustomRaceUI.backupMain, FRAMEPOINT_TOP,
                         config.margin.WAIT_LEFT_MARGIN,
                         config.margin.WAIT_UPPER_MARGIN)
        BlzFrameSetTextAlignment(CustomRaceUI.waitText, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
        BlzFrameSetText(CustomRaceUI.waitText, "|cffffcc00Please wait|r")
        CustomRaceUI.pCountText = BlzCreateFrameByType("TEXT", "CustomRaceBackupPlayerText",
                                                       CustomRaceUI.backupMain, "", 0)
        BlzFrameSetSize(CustomRaceUI.pCountText, config.PCOUNT_WIDTH, config.PCOUNT_HEIGHT)
        BlzFrameSetScale(CustomRaceUI.pCountText, config.scale.PCOUNT_TEXT)
        BlzFrameSetPoint(CustomRaceUI.pCountText, FRAMEPOINT_TOP, 
                         CustomRaceUI.waitText, FRAMEPOINT_BOTTOM,
                         config.margin.PCOUNT_LEFT_MARGIN,
                         config.margin.PCOUNT_UPPER_MARGIN)
        BlzFrameSetVisible(CustomRaceUI.backupMain, false)
    end
    function internal.prepareDispFrame()
        local world                         = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
        CustomRaceUI.mainScreen             = {
            update      = 1/60,
            fadeout     = 1.20,
            timer       = CreateTimer(),
            startScale  = 8.50,
            endScale    = 4.50,
            startAlpha  = 255,
            endAlpha    = 0,
        }
        CustomRaceUI.mainScreen.diffAlpha   = CustomRaceUI.mainScreen.endAlpha - CustomRaceUI.mainScreen.startAlpha
        CustomRaceUI.mainScreen.diffScale   = CustomRaceUI.mainScreen.endScale - CustomRaceUI.mainScreen.startScale
        CustomRaceUI.mainScreen.medScale    = (CustomRaceUI.mainScreen.startScale + CustomRaceUI.mainScreen.endScale)/2
        CustomRaceUI.mainScreen.textFrame   = BlzCreateFrameByType("TEXT", "CustomRaceTextDisplay", 
                                                                    world, "", 0)
        BlzFrameClearAllPoints(CustomRaceUI.mainScreen.textFrame)
        BlzFrameSetSize(CustomRaceUI.mainScreen.textFrame, config.TEXTFRAME_WIDTH, config.TEXTFRAME_HEIGHT)
        BlzFrameSetTextAlignment(CustomRaceUI.mainScreen.textFrame, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
        BlzFrameSetAbsPoint(CustomRaceUI.mainScreen.textFrame, FRAMEPOINT_CENTER, 0.40, 0.35)
        BlzFrameSetScale(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.medScale)
        BlzFrameSetFocus(CustomRaceUI.mainScreen.textFrame, false)
        BlzFrameSetEnable(CustomRaceUI.mainScreen.textFrame, false)
        BlzFrameSetAlpha(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.endAlpha)
        CustomRaceUI.mainScreen._ticksMax   = CustomRaceUI.mainScreen.fadeout // CustomRaceUI.mainScreen.update
        CustomRaceUI.mainScreen._ticks      = 0
        CustomRaceUI.mainScreen._frameArt   = function()
            CustomRaceUI.mainScreen._ticks  = CustomRaceUI.mainScreen._ticks - 1
            local curAlpha                  = CustomRaceUI.mainScreen._ticks/CustomRaceUI.mainScreen._ticksMax
            curAlpha                        = curAlpha*CustomRaceUI.mainScreen.diffAlpha
            curAlpha                        = (CustomRaceUI.mainScreen.endAlpha - curAlpha) // 1
           
            local curScale                  = CustomRaceUI.mainScreen._ticks/CustomRaceUI.mainScreen._ticksMax
            curScale                        = (curScale*CustomRaceUI.mainScreen.diffScale)
            curScale                        = CustomRaceUI.mainScreen.endScale - curScale
            BlzFrameSetAlpha(CustomRaceUI.mainScreen.textFrame, math.tointeger(curAlpha))
            BlzFrameSetScale(CustomRaceUI.mainScreen.textFrame, curScale)
            if CustomRaceUI.mainScreen._ticks <= 0 then
                PauseTimer(CustomRaceUI.mainScreen.timer)
            end
        end
        CustomRaceUI.mainScreen.display     = function(...)
            local tt    = {...}
            tt.str      = ""
            if #tt > 0 then
                for i = 1, #tt do
                    tt.str  = tt.str .. tostring(tt[i]) .. " "
                end
            end
            BlzFrameSetText(CustomRaceUI.mainScreen.textFrame, tt.str)
            BlzFrameSetAlpha(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.startAlpha)
            BlzFrameSetScale(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.startScale)
            CustomRaceUI.mainScreen._ticks  = CustomRaceUI.mainScreen._ticksMax
           
            PauseTimer(CustomRaceUI.mainScreen.timer)
            TimerStart(CustomRaceUI.mainScreen.timer, CustomRaceUI.mainScreen.update, true,
                       CustomRaceUI.mainScreen._frameArt)
        end
    end
    function internal.prepareSubFrames()
        CustomRaceUI.icon       = BlzCreateFrameByType("BACKDROP", "", CustomRaceUI.iconMain, "", 0)
        BlzFrameSetSize(CustomRaceUI.icon, config.ICON_DISP_WIDTH, config.ICON_DISP_HEIGHT)
        BlzFrameClearAllPoints(CustomRaceUI.icon)
        BlzFrameSetPoint(CustomRaceUI.icon, FRAMEPOINT_CENTER,
                         CustomRaceUI.iconMain, FRAMEPOINT_CENTER,
                         0, 0)
        BlzFrameSetVisible(CustomRaceUI.icon, false)
        CustomRaceUI.selectList     = {}
        CustomRaceUI.framePosData   = {}
        CustomRaceUI.playerData     = {pointer={}}
        for i = 1, config.RACE_MAX_OPTIONS do
            local t     = {}
            t.main      = BlzCreateFrameByType("GLUETEXTBUTTON", "CustomRaceSelectOption",
                                               CustomRaceUI.selectMain, config.framename.listButtons,
                                               0)
            BlzFrameSetSize(t.main, config.SELECTION_WIDTH, 
                            config.SELECTION_HEIGHT/config.RACE_MAX_OPTIONS)
            if i ~= 1 then
                BlzFrameSetPoint(t.main, FRAMEPOINT_TOP,
                                 CustomRaceUI.selectList[i-1].main, FRAMEPOINT_BOTTOM,
                                 0, 0)
            else
                BlzFrameSetPoint(t.main, FRAMEPOINT_TOP,
                                 CustomRaceUI.selectMain, FRAMEPOINT_TOP,
                                 0, 0)
            end
            t.nameframe = BlzCreateFrameByType("TEXT", "", t.main, "", 0)
            t.highlight = BlzCreateFrameByType("HIGHLIGHT", "CustomRaceSelectLight", t.main,
                                               config.framename.listHighlight, i)
            BlzFrameSetEnable(t.highlight, false)
            BlzFrameSetFocus(t.highlight, false)
            BlzFrameSetEnable(t.nameframe, false)
            BlzFrameSetFocus(t.nameframe, false)
            BlzFrameSetSize(t.nameframe,
                            config.SELECTION_TEXT_WIDTH/config.scale.SELECTION_TEXT,
                            config.SELECTION_TEXT_HEIGHT/config.scale.SELECTION_TEXT)
            BlzFrameSetScale(t.nameframe, config.scale.SELECTION_TEXT)
            BlzFrameClearAllPoints(t.nameframe)
            BlzFrameSetPoint(t.nameframe, FRAMEPOINT_LEFT,
                             t.main, FRAMEPOINT_LEFT,
                             config.margin.SELECTION_TEXT_LEFT_MARGIN, 0)
            BlzFrameClearAllPoints(t.highlight)
            BlzFrameSetAllPoints(t.highlight, t.main)
            BlzFrameSetVisible(t.highlight, false)
            --  Debug text
            CustomRaceUI.selectList[i]          = t
            CustomRaceUI.framePosData[t.main]   = i
        end
        BlzFrameSetVisible(CustomRaceUI.main, false)
    end
    function internal.addButtonAudioFeedback()
        local trig  = CreateTrigger()
        TriggerAddCondition(trig, Condition(function()
            local player        = GetTriggerPlayer()
            BlzFrameSetEnable(CustomRaceUI.buttonFrame, false)
            BlzFrameSetEnable(CustomRaceUI.buttonFrame, true)
            internal.playSound(GetTriggerPlayer(), internal.generateBigClick())
            local id            = CustomRaceUI.playerData.pointer[player]
            local data          = CustomRaceUI.playerData[id]
            if (not data) or
               (data.selectPos == 0) then
                return
            end
            --  Feed back the data
            CustomRace.faction[data.playerID]   = data.selectPos
            CustomRaceUI.display(player, false)
            CustomRaceUI.removeSelectingPlayer(player)
        end))
        BlzTriggerRegisterFrameEvent(trig, CustomRaceUI.buttonFrame, FRAMEEVENT_CONTROL_CLICK)
        local trig  = CreateTrigger()
        TriggerAddCondition(trig, Condition(function()
            local frame         = BlzGetTriggerFrame()
            local player        = GetTriggerPlayer()
            BlzFrameSetEnable(frame, false)
            BlzFrameSetEnable(frame, true)
            internal.playSound(player, internal.generateSmallClick())
            local id            = CustomRaceUI.playerData.pointer[player]
            local data          = CustomRaceUI.playerData[id]
            local pos           = CustomRaceUI.framePosData[frame]
            if not data then
                return
            end
            if (data.selectPos ~= data.hoverOffset + pos) then
                local pos2      = data.selectPos - data.hoverOffset
                if (pos2 > 0) and (pos2 <= config.RACE_MAX_OPTIONS) and (GetLocalPlayer() == player) then
                    BlzFrameSetVisible(CustomRaceUI.selectList[pos2].highlight, false)
                end
                if GetLocalPlayer() == player then
                    BlzFrameSetVisible(CustomRaceUI.selectList[pos].highlight, true)
                end
                data.selectPos  = data.hoverOffset + pos
            else
                if GetLocalPlayer() == player then
                    BlzFrameSetVisible(CustomRaceUI.selectList[pos].highlight, false)
                end
                data.selectPos  = 0
            end
            CustomRaceUI.updateSelection(player, true)
        end))
        for i = 1, config.RACE_MAX_OPTIONS do
            BlzTriggerRegisterFrameEvent(trig, CustomRaceUI.selectList[i].main, FRAMEEVENT_CONTROL_CLICK)
        end
    end
    function internal.addSelectionProcess()
        local trig  = {CreateTrigger(), CreateTrigger()}
        TriggerAddCondition(trig[1], Condition(function()
            local frame     = BlzGetTriggerFrame()
            local player    = GetTriggerPlayer()
            local id        = CustomRaceUI.playerData.pointer[player]
            local data      = CustomRaceUI.playerData[id]
            local pos       = CustomRaceUI.framePosData[frame]
            if (not data) then
                return
            end
            --  When a frame is selected, the info will
            --  be locked to displaying the details of
            --  said frame.
            if data.selectPos   ~= 0 then
                return
            end
            if data.hoverPos - data.hoverOffset ~= pos then
                data.hoverPos   = data.hoverOffset + pos
                CustomRaceUI.updateSelection(player, false)
            end
        end))
        TriggerAddCondition(trig[2], Condition(function()
            local frame     = BlzGetTriggerFrame()
            local player    = GetTriggerPlayer()
            local id        = CustomRaceUI.playerData.pointer[player]
            local data      = CustomRaceUI.playerData[id]
            local pos       = CustomRaceUI.framePosData[frame]
            if (not data) or
               (GetLocalPlayer() ~= player) then
                return
            end
            if data.selectPos   ~= 0 then
                return
            end
            if data.hoverPos - data.hoverOffset == pos then
                data.hoverPos   = 0
                CustomRaceUI.updateSelection(player, false)
            end
        end))
        for i = 1, config.RACE_MAX_OPTIONS do
            BlzTriggerRegisterFrameEvent(trig[1], CustomRaceUI.selectList[i].main, FRAMEEVENT_MOUSE_ENTER)
            BlzTriggerRegisterFrameEvent(trig[2], CustomRaceUI.selectList[i].main, FRAMEEVENT_MOUSE_LEAVE)
        end
    end
    function internal.addScrollCallback()
        local trig  = {CreateTrigger(), CreateTrigger()}
        --  This trigger is for taking note of changed values
        TriggerAddCondition(trig[1], Condition(function()
            local player        = GetTriggerPlayer()
            local id            = CustomRaceUI.playerData.pointer[player]
            local data          = CustomRaceUI.playerData[id]
            local value         = (BlzGetTriggerFrameValue() + 0.5)//1
            if (not data) then
                return
            end
            data.hoverPos       = data.hoverPos - data.hoverOffset
            data.hoverOffset    = data.hoverMax - value
            data.hoverPos       = data.hoverPos + data.hoverOffset
            if data.selectPos ~= 0 then
                return
            end
            CustomRaceUI.updateHoverList(player)
            CustomRaceUI.updateSelection(player, false)
        end))
        TriggerAddCondition(trig[2], Condition(function()
            local player    = GetTriggerPlayer()
            local value     = BlzGetTriggerFrameValue()
            local incr      = 0
            if value > 0 then
                incr        = 1
            else
                incr        = -1
            end
            if GetLocalPlayer() == player then
                BlzFrameSetValue(CustomRaceUI.sliderMain, BlzFrameGetValue(CustomRaceUI.sliderMain) + incr)
            end
        end))
        BlzTriggerRegisterFrameEvent(trig[1], CustomRaceUI.sliderMain, FRAMEEVENT_SLIDER_VALUE_CHANGED)
        BlzTriggerRegisterFrameEvent(trig[2], CustomRaceUI.sliderMain, FRAMEEVENT_MOUSE_WHEEL)
    end
    function CustomRaceUI.updateSelection(player, useSelectedFrame)
        local id        = CustomRaceUI.playerData.pointer[player]
        if not id then
            return
        end
        local data      = CustomRaceUI.playerData[id]
        local factionID
        if useSelectedFrame then
            factionID   = (data.selectPos ~= 0 and data.selectPos) or data.hoverPos
        else
            factionID   = data.hoverPos
        end
        --  If factionID is 0, the user didn't select any race.
        if factionID == 0 then
            if GetLocalPlayer() == player then
                BlzFrameSetText(CustomRaceUI.descMain, "")
                BlzFrameSetText(CustomRaceUI.raceMain, "")
                BlzFrameSetTexture(CustomRaceUI.icon, "", 0, true)
                if BlzFrameIsVisible(CustomRaceUI.icon) then
                    BlzFrameSetVisible(CustomRaceUI.icon, false)
                end
            end
        else
            local faction       = tb._container[data.playerRace][factionID]
            local texture       = faction.racePic or "ReplaceableTextures\\CommandButtons\\BTNTemp.blp"
            if GetLocalPlayer() == player then
                BlzFrameSetText(CustomRaceUI.descMain, faction.description or "")
                BlzFrameSetText(CustomRaceUI.raceMain, "|cffffcc00" .. (faction.name or "") .. "|r")
                BlzFrameSetTexture(CustomRaceUI.icon, texture, 0, true)
                if texture == "" then
                    if BlzFrameIsVisible(CustomRaceUI.icon) then
                        BlzFrameSetVisible(CustomRaceUI.icon, false)
                    end
                else
                    if not BlzFrameIsVisible(CustomRaceUI.icon) then
                        BlzFrameSetVisible(CustomRaceUI.icon, true)
                    end
                end
            end
        end
    end
    function CustomRaceUI.updateHoverList(player)
        local id        = CustomRaceUI.playerData.pointer[player]
        if not id then
            return
        end
        local data      = CustomRaceUI.playerData[id]
        local j         = 1
        for i = 1, config.RACE_MAX_OPTIONS do
            if i + data.hoverOffset > data.factionChoices then
                break
            end
            local t         = CustomRaceUI.selectList[i]
            j               = j + 1
            local faction   = tb._container[data.playerRace][i + data.hoverOffset]
            if GetLocalPlayer() == player then
                if not BlzFrameIsVisible(t.main) then
                    BlzFrameSetVisible(t.main, true)
                end
                BlzFrameSetText(t.nameframe, "|cffffcc00" .. (faction.name or "") .. "|r")
            end
        end
        if j > config.RACE_MAX_OPTIONS then
            return
        end
        while j <= config.RACE_MAX_OPTIONS do
            local t         = CustomRaceUI.selectList[j]
            if GetLocalPlayer() == player then
                BlzFrameSetText(t.nameframe, "")
                if BlzFrameIsVisible(t.main) then
                    BlzFrameSetVisible(t.main, false)
                end
            end
            j = j + 1
        end
    end
    function CustomRaceUI.defSliderBounds(player)
        local id        = CustomRaceUI.playerData.pointer[player]
        if not id then
            return
        end
        local data      = CustomRaceUI.playerData[id]
        local j         = 1
        if data.factionChoices <= config.RACE_MAX_OPTIONS then
            if GetLocalPlayer() == player then
                BlzFrameSetVisible(CustomRaceUI.sliderMain, false)
            end
        else
            if GetLocalPlayer() == player then
                BlzFrameSetMinMaxValue(CustomRaceUI.sliderMain, 0, data.hoverMax)
                BlzFrameSetStepSize(CustomRaceUI.sliderMain, 1)
                BlzFrameSetValue(CustomRaceUI.sliderMain, data.hoverMax)
            end
        end
    end
    --  This just tells the players how many players
    --  are still selecting their factions.
    function CustomRaceUI.updatePlayerCountText()
        BlzFrameSetText(CustomRaceUI.pCountText, 
                        "|cffffcc00Number of players:|r " ..
                        tostring(CustomRaceUI.selectionCount) .. "/" .. 
                        tostring(CustomRaceUI.selectionTotal))
    end
    function CustomRaceUI.removeSelectingPlayer(player)
        local pos       = 1
        while pos <= #CustomRaceUI.playerSelectData do
            if CustomRaceUI.playerSelectData[pos] == player then
                break
            end
            pos = pos + 1
        end
        if pos > #CustomRaceUI.playerSelectData then
            return
        end
        table.remove(CustomRaceUI.playerSelectData, pos)
        CustomRaceUI.selectionCount = CustomRaceUI.selectionCount - 1
        CustomRaceUI.updatePlayerCountText()
        if CustomRaceUI.selectionCount <= 0 then
            CustomRaceUI.inSelectionStage   = false
            CustomRaceUI.clearSelectionTrigger()
            CustomRace.start()
            BlzFrameSetVisible(CustomRaceUI.main, false)
            BlzFrameSetVisible(CustomRaceUI.backupMain, false)
            if CustomRaceUI.criticalTimer then
                PauseTimer(CustomRaceUI.criticalTimer)
                DestroyTimer(CustomRaceUI.criticalTimer)
                CustomRaceUI.criticalTimer = nil
            end
        elseif GetLocalPlayer() == player then
            BlzFrameSetVisible(CustomRaceUI.backupMain, true)
        end
    end
    function CustomRaceUI.clearSelectionTrigger()
        if not CustomRaceUI.playerSelectTrig then
            return
        end
        DisableTrigger(CustomRaceUI.playerSelectTrig)
        DestroyTrigger(CustomRaceUI.playerSelectTrig)
        CustomRaceUI.playerSelectTrig   = nil
    end
    function CustomRaceUI.startSelectTimer()
        CustomRaceUI.selectTimer        = CreateTimer()
        CustomRaceUI.criticalTimer      = CreateTimer()
        TimerStart(CustomRaceUI.selectTimer, config.WAIT_DURATION, false, function()
            while CustomRaceUI.inSelectionStage do
                local player    = CustomRaceUI.playerSelectData[CustomRaceUI.selectionCount]
                local id        = CustomRaceUI.playerData.pointer[player]
                local data      = CustomRaceUI.playerData[id]
                CustomRace.faction[data.playerID]   = 1
                CustomRaceUI.removeSelectingPlayer(player)
            end
            DestroyTimer(CustomRaceUI.selectTimer)
            CustomRaceUI.selectTimer = nil
        end)
        TimerStart(CustomRaceUI.criticalTimer, config.WAIT_DURATION - config.CRITICAL_DURATION, false, function()
            internal.playSound(GetLocalPlayer(), internal.generateCriticalSound())
            DestroyTimer(CustomRaceUI.criticalTimer)
            CustomRaceUI.criticalTimer = nil
        end)
    end
    function CustomRaceUI.checkFactionSelection()
        CustomRaceUI.inSelectionStage   = CustomRaceUI.selectionCount > 0
        CustomRaceUI.selectionTotal     = CustomRaceUI.selectionCount
        CustomRaceUI.updatePlayerCountText()
        if not CustomRaceUI.inSelectionStage then
            CustomRaceUI.display(GetLocalPlayer(), false)
            CustomRace.start()
            return
        end
        local trig                      = CreateTrigger()
        CustomRaceUI.playerSelectData   = {}
        CustomRaceUI.playerSelectTrig   = trig
        CustomRaceUI.startSelectTimer()
        TriggerAddCondition(trig, Condition(function()
            local player    = GetTriggerPlayer()
            if CustomRaceUI.inSelectionStage then
                DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 20.00,
                                         GetPlayerName(player) .. " has abandoned the game")
            end
            CustomRaceUI.removeSelectingPlayer(player)
        end))
        for i = 1, #CustomRaceUI.playerData do
            local player    = CustomRaceUI.playerData[i].player
            CustomRaceUI.display(player, true)
            if GetPlayerController(player) == MAP_CONTROL_USER then
                table.insert(CustomRaceUI.playerSelectData, player)
               
                CustomRaceUI.updateHoverList(player)
                CustomRaceUI.updateSelection(player, false)
                TriggerRegisterPlayerEvent(trig, player, EVENT_PLAYER_LEAVE)
            end
        end
    end
    function CustomRaceUI.processPlayer(player)
        local id    = CustomRaceUI.playerData.pointer[player]
        local data  = CustomRaceUI.playerData[id]
        if (not data) then
            if (GetLocalPlayer() == player) then
                BlzFrameSetVisible(CustomRaceUI.backupMain, true)
            end
            return
        end
        if GetPlayerController(player) == MAP_CONTROL_USER then
            --  Computer Players will always select default race.
            CustomRaceUI.selectionCount = CustomRaceUI.selectionCount + 1
        end
    end
    function CustomRaceUI.feedPlayerData(id, player)
        local race  = GetPlayerRace(player)
        if (#tb._container[race] <= 1) or
           (GetPlayerController(player) == MAP_CONTROL_COMPUTER) then
            CustomRace.faction[id]  = 1
            return
        end
        local data  = {
            player          = player,
            playerID        = id,
            playerRace      = race,
            factionChoices  = 0,
            hoverOffset     = 0,
            hoverPos        = 0,
            selectPos       = 0,
        }
        data.factionChoices = #tb._container[data.playerRace]
        data.hoverMax       = math.max(data.factionChoices - config.RACE_MAX_OPTIONS, 0)
        CustomRaceUI.playerData[#CustomRaceUI.playerData + 1]   = data
        CustomRaceUI.playerData.pointer[player]                 = #CustomRaceUI.playerData
        CustomRaceUI.defSliderBounds(player)
    end
    function CustomRaceUI.display(player, flag)
        if GetLocalPlayer() == player then
            BlzFrameSetVisible(CustomRaceUI.main, flag)
        end
    end
    function CustomRaceUI.init()
        internal.loadTOC()
        internal.prepareAllFrames()
        internal.prepareBackupFrames()
        internal.prepareDispFrame()
        internal.prepareSubFrames()
        internal.addButtonAudioFeedback()
        internal.addSelectionProcess()
        internal.addScrollCallback()
        CustomRaceUI.selectionCount     = 0
    end
end

This is the most difficult part of the system to port over to lua.
As such, it is not recommended for one to tinker with this.
Lua:
do
    local tb                = getmetatable(CustomRaceSystem)
    local internal          = {}
    local teams             = {
        allies              = {},
        enemies             = {}
    }
    local buildingGroup     = {}
    local triggers          = {}
    CustomRaceConditions    = setmetatable({}, tb)
    function internal.initGlobalVars(index, player)
        bj_meleeDefeated[index]         = false
        bj_meleeVictoried[index]        = false
       
        -- Create a timer and timer window in case the player is crippled.
        bj_playerIsCrippled[index]      = false
        bj_playerIsExposed[index]       = false
        bj_crippledTimer[index]         = CreateTimer()
        bj_crippledTimerWindows[index]  = CreateTimerDialog(bj_crippledTimer[index])
        TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(player))
    end
    local victorEnemyCount  = 0
    function internal.checkVictorsEnum2()
        victorEnemyCount    = victorEnemyCount + 1
    end
    function internal.checkVictorsEnum()
        local player    = GetEnumPlayer()
        local id        = GetPlayerId(player)
        ForForce(teams.enemies[id], internal.checkVictorsEnum2)
    end
    function internal.checkVictors()
        if bj_meleeGameOver then
            return
        end
        victorEnemyCount    = 0
        ForForce(teams.players, internal.checkVictorsEnum)
        if victorEnemyCount > 0 then
            return
        end
        --  Award victory to all team players
        --  In this case, use a closure instead.
        ForForce(teams.players, function()
            local player    = GetEnumPlayer()
            local id        = GetPlayerId(player)
            if bj_meleeGameOver then
                return
            elseif bj_meleeVictoried[id] then
                return
            end
            bj_meleeVictoried[id]   = true
            internal.exposePlayerTeam(player, false)
            PauseTimer(bj_crippledTimer[id])
            if GetLocalPlayer() == player then
                TimerDialogDisplay(bj_crippledTimerWindows[id], false)
            end
            CachePlayerHeroData(player)
            RemovePlayerPreserveUnitsBJ(player, PLAYER_GAME_RESULT_VICTORY, false)
        end)
        bj_meleeGameOver    = true
    end
    --  Obtaining structure count and key structure count.
    local structCount       = {}
    function internal.getKeyStructureCountGroup(player, id)
        id                      = id or GetPlayerId(player)
        local size              = BlzGroupGetSize(buildingGroup[id]) - 1
        for i = 0, size do
            local unit  = BlzGroupUnitAt(buildingGroup[id], i)
            if tb._hallptr[GetUnitTypeId(unit)] ~= nil then
                structCount.keyCount    = structCount.keyCount + 1
            end
        end
    end
    function internal.getKeyStructureCountEnum()
        internal.getKeyStructureCountGroup(GetEnumPlayer())
    end
    function internal.getKeyStructureCount(excludeAllies, player, id)
        id                      = id or GetPlayerId(player)
        structCount.keyCount    = 0
        internal.getKeyStructureCountGroup(player, id)
        if (not excludeAllies) then
            ForForce(teams.allies[id], internal.getKeyStructureCountEnum)
        end
        return structCount.keyCount
    end
    function internal.getStructureCountEnum()
        local player        = GetEnumPlayer()
        local id            = GetPlayerId(player)
        structCount.count   = structCount.count + BlzGroupGetSize(buildingGroup[id])
    end
    function internal.getStructureCount(excludeAllies, player, id)
        id                  = id or GetPlayerId(player)
        structCount.count   = BlzGroupGetSize(buildingGroup[id])
        if (not excludeAllies) then
            ForForce(teams.allies[id], internal.getStructureCountEnum)
        end
        return structCount.count
    end
    function internal.getForceSizeEx()
        structCount.forceCount  = structCount.forceCount + 1
    end
    function internal.getForceSize(force)
        structCount.forceCount  = 0
        ForForce(force, internal.getForceSizeEx)
        return structCount.forceCount
    end
    --  This section handles exposure of bases.
    local exposeTable       = {}
    function internal.exposePlayerTeamEnum()
        ForceAddPlayer(exposeTable.allyforce, GetEnumPlayer())
    end
    function internal.exposePlayerTeamEx()
        local player    = GetEnumPlayer()
        local id        = GetPlayerId(player)
        CripplePlayer(player, exposeTable.force, exposeTable.flag)
        if exposeTable.updateFlag then
            bj_playerIsExposed[id]  = exposeTable.flag
        end
    end
    function internal.exposePlayerTeam(player, expose)
        local id                = GetPlayerId(player)
        exposeTable.force       = CreateForce()
        --  NOTE: The player by default is not included among
        --  the list of allies, so this behavior should be
        --  taken advantage of.
        --  Temporarily add the player to the list of allies
        ForceAddPlayer(teams.allies[id], player)
        exposeTable.flag        = false
        exposeTable.updateFlag  = false
        ForForce(teams.allies[id], internal.exposePlayerTeamEx)
        DestroyForce(exposeTable.force)
        exposeTable.flag        = expose
        exposeTable.updateFlag  = true
        exposeTable.force       = teams.enemies[id]
        ForForce(teams.allies[id], internal.exposePlayerTeamEx)
        ForceRemovePlayer(teams.allies[id], player)
    end
    --  This section handles exposure of crippling mechanics.
    local crippleTable  = {
        flag            = false,
        timerData       = {},
        active          = {}
    }
    function internal.crippleTeamHideWindows()
        local player                                = GetEnumPlayer()
        local id                                    = GetPlayerId(player)
        crippleTable.active[bj_crippledTimer[id]]   = nil
        if GetLocalPlayer() == player then
            TimerDialogDisplay(bj_crippledTimerWindows[id], false)
            DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                     MeleeGetCrippledWarningMessage(player))
        end
    end
    function internal.crippleTeamCallback()
        local player                                = crippleTable.timerData[GetExpiredTimer()]
        local id                                    = GetPlayerId(player)
        DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                 MeleeGetCrippledRevealedMessage(player))
        if GetLocalPlayer() == player then
            TimerDialogDisplay(bj_crippledTimerWindows[id], false)
        end
        if crippleTable.active[bj_crippledTimer[id]] then
            internal.exposePlayerTeam(player, true)
            ForForce(teams.allies, internal.crippleTeamHideWindows)
        end
        crippleTable.active[bj_crippledTimer[id]]   = nil
    end
    function internal.cripplePlayerTeamDummy()
        local player                                        = GetEnumPlayer()
        local id                                            = GetPlayerId(player)
        bj_playerIsCrippled[id]                             = crippleTable.flag
        crippleTable.timerData[bj_crippledTimer[id]]        = crippleTable.timerData[bj_crippledTimer[id]] or 
                                                              player
        if crippleTable.flag then
            crippleTable.active[bj_crippledTimer[id]]       = false
            TimerStart(bj_crippledTimer[id], bj_MELEE_CRIPPLE_TIMEOUT, false, internal.crippleTeamCallback)
            if GetLocalPlayer() == player then
                TimerDialogDisplay(bj_crippledTimerWindows[id], true)
                DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                        MeleeGetCrippledWarningMessage(player))
            end
            return
        end
        crippleTable.active[bj_crippledTimer[id]]    = nil
        PauseTimer(bj_crippledTimer[id])
        if GetLocalPlayer() ~= player then
            return
        end
        if IsTimerDialogDisplayed(bj_crippledTimerWindows[id]) then
            TimerDialogDisplay(bj_crippledTimerWindows[id], false)
        end
        if (bj_playerIsExposed[id]) then
            DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                        GetLocalizedString("CRIPPLE_UNREVEALED"))
        else
            DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                        GetLocalizedString("CRIPPLE_UNCRIPPLED"))
        end
    end
    function internal.cripplePlayerTeam(player, cripple)
        local id                    = GetPlayerId(player)
        if bj_playerIsCrippled[id] == cripple then
            --  Player's cripple state is the same as the flag.
            --  Ignore
            return
        end
        crippleTable.flag           = cripple
        bj_playerIsCrippled[id]     = cripple
        if cripple then
            --  Map the active timer id of each
            crippleTable.timerData[bj_crippledTimer[id]]        = crippleTable.timerData[bj_crippledTimer[id]] or 
                                                                  player
            crippleTable.active[bj_crippledTimer[id]]           = true
            TimerStart(bj_crippledTimer[id], bj_MELEE_CRIPPLE_TIMEOUT, false, internal.crippleTeamCallback)
            if GetLocalPlayer() == player then
                TimerDialogDisplay(bj_crippledTimerWindows[id], true)
                DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                         MeleeGetCrippledWarningMessage(player))
            end
            ForForce(teams.allies[id], internal.cripplePlayerTeamDummy)
            return
        end
        player                                              = crippleTable.timerData[bj_crippledTimer[id]]
        id                                                  = GetPlayerId(player)
        crippleTable.active[bj_crippledTimer[id]]           = nil
        PauseTimer(bj_crippledTimer[id])
        if GetLocalPlayer() == player then
            TimerDialogDisplay(bj_crippledTimerWindows[id], false)
            if (bj_playerIsExposed[id]) then
                DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                            GetLocalizedString("CRIPPLE_UNREVEALED"))
            else
                DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
                                            GetLocalizedString("CRIPPLE_UNCRIPPLED"))
            end
        end
        ForForce(teams.allies[id], internal.cripplePlayerTeamDummy)
        internal.exposePlayerTeam(player, false)
    end
    --  Handle Defeat mechanics
    function internal.defeatPlayer(player, leftGame)
        leftGame                = leftGame == true
        local id                = GetPlayerId(player)
        local flag              = false
        if bj_meleeVictoried[id] or
           bj_meleeDefeated[id] then
            return
        end
        bj_meleeDefeated[id]    = true
        bj_playerIsCrippled[id] = false
        bj_playerIsExposed[id]  = false
        crippleTable.timerData[bj_crippledTimer[id]]    = nil
        crippleTable.active[bj_crippledTimer[id]]       = nil
        PauseTimer(bj_crippledTimer[id])
        DestroyTimer(bj_crippledTimer[id])
        DestroyTimerDialog(bj_crippledTimerWindows[id])
        ForForce(teams.enemies[id], function()
            local player2           = GetEnumPlayer()
            local jd                = GetPlayerId(player2)
            ForceRemovePlayer(teams.enemies[jd], player)
        end)
        ForForce(teams.allies[id], function()
            local player2           = GetEnumPlayer()
            local jd                = GetPlayerId(player2)
            ForceRemovePlayer(teams.allies[jd], player)
            if flag then
                crippleTable.active[bj_crippledTimer[id]]       = true
            end
        end)
        DestroyForce(teams.enemies[id])
        DestroyForce(teams.allies[id])
        teams.enemies[id]       = nil
        teams.allies[id]        = nil
        ForceRemovePlayer(teams.players, player)
        ForceAddPlayer(teams.loser, player)
        CachePlayerHeroData(player)
        RemovePlayerPreserveUnitsBJ(player, PLAYER_GAME_RESULT_DEFEAT, leftGame)
    end
    function internal.defeatPlayerTeam(player, leftGame)
        local id                = GetPlayerId(player)
        --  Since this function is going to be called only a few times
        --  (at most 23 times), a closure is used instead.
        ForForce(teams.allies[id], function()
            local player2       = GetEnumPlayer()
            internal.defeatPlayer(player2, leftGame)
        end)
        internal.defeatPlayer(player, false)
    end
    function internal.neutralizePlayer(player)
        local group = CreateGroup()
        GroupEnumUnitsOfPlayer(group, player, nil)
        ForGroup(group, MakeUnitsPassiveForPlayerEnum)
        DestroyGroup(group)
    end
    function internal.neutralizePlayerTeam(player)
        local id    = GetPlayerId(player)
        internal.neutralizePlayer(player)
        ForForce(teams.allies[id], function()
            internal.neutralizePlayer(GetEnumPlayer())
        end)
    end
    function internal.shareControl(player)
        local id    = GetPlayerId(player)
        ForForce(teams.allies[id], function()
            local player2   = GetEnumPlayer()
            SetPlayerAlliance(player, player2, ALLIANCE_SHARED_VISION, true)
            SetPlayerAlliance(player, player2, ALLIANCE_SHARED_CONTROL, true)
            SetPlayerAlliance(player2, player, ALLIANCE_SHARED_CONTROL, true)
            SetPlayerAlliance(player, player2, ALLIANCE_SHARED_ADVANCED_CONTROL, true)
        end)
    end
    function internal.checkTeamStatus(player)
        local teamCount     = internal.getStructureCount(false, player)
        local teamKeyCount  = internal.getKeyStructureCount(false, player)
        if (teamKeyCount > 0) then
            --  A town hall might have been reconstructed.
            internal.cripplePlayerTeam(player, false)
            return
        end
        if teamCount > 0 then
            internal.cripplePlayerTeam(player, true)
            return
        end
        internal.neutralizePlayerTeam(player)
        internal.defeatPlayerTeam(player)
        internal.checkVictors()
    end
    --  Update structure count
    function internal.removeStructure(unit, player, id)
        id  = id or GetPlayerId(player)
        GroupRemoveUnit(buildingGroup[id], unit)
        internal.checkTeamStatus(player)
    end
    function internal.addStructure(unit, player, id)
        id  = id or GetPlayerId(player)
        GroupAddUnit(buildingGroup[id], unit)
        internal.checkTeamStatus(player)
    end
    --  Unit Trigger callbacks
    function internal.registerDeath(player)
        if not triggers.death then
            triggers.death  = CreateTrigger()
            TriggerAddAction(triggers.death, function()
                local unit      = GetTriggerUnit()
                local player    = GetOwningPlayer(unit)
                local id        = GetPlayerId(player)
                if not IsUnitInGroup(unit, buildingGroup[id]) then
                    return
                end
                internal.removeStructure(unit, player, id)
            end)
        end
        TriggerRegisterPlayerUnitEvent(triggers.death, player, EVENT_PLAYER_UNIT_DEATH)
    end
    function internal.registerConstruct(player)
        if not triggers.construct then
            triggers.construct  = CreateTrigger()
            TriggerAddAction(triggers.construct, function()
                local unit      = GetConstructingStructure()
                local player    = GetOwningPlayer(unit)
                internal.addStructure(unit, player, id)
            end)
        end
        TriggerRegisterPlayerUnitEvent(triggers.construct, player, EVENT_PLAYER_UNIT_CONSTRUCT_START)
    end
    function internal.registerChangeOwner(player)
        if not triggers.ownership then
            triggers.ownership  = CreateTrigger()
            TriggerAddAction(triggers.ownership, function()
                local unit          = GetTriggerUnit()
                local player        = GetOwningPlayer(unit)
                local prevPlayer    = GetChangingUnitPrevOwner()
                local prevID        = GetPlayerId(prevPlayer)
                if not IsUnitInGroup(unit, buildingGroup[prevID]) then
                    return
                end
                internal.removeStructure(unit, prevPlayer, prevID)
                internal.addStructure(unit, player)
            end)
        end
        TriggerRegisterPlayerUnitEvent(triggers.ownership, player, EVENT_PLAYER_UNIT_CHANGE_OWNER)
    end
    --  Player trigger callbacks
    function internal.registerLeave(player)
        if not triggers.leave then
            triggers.leave  = CreateTrigger()
            TriggerAddAction(triggers.leave, function()
                local player        = GetTriggerPlayer()
                local teamCount     = internal.getStructureCount(false, player)
                if teamCount > 0 then
                    internal.shareControl(player)
                    internal.defeatPlayer(player, true)
                    internal.checkVictors()
                    return
                end
                internal.defeatPlayerTeam(player)
                internal.checkVictors()
            end)
        end
        TriggerRegisterPlayerEvent(triggers.leave, player, EVENT_PLAYER_LEAVE)
    end
    function internal.registerAllianceChange(player)
        if not triggers.allyChange then
            triggers.allyChange  = CreateTrigger()
            TriggerAddAction(triggers.allyChange, function()
                local player        = GetTriggerPlayer()
                local id            = GetPlayerId(player)
                ForForce(teams.allies[id], function()
                    local player2   = GetEnumPlayer()
                    local jd        = GetPlayerId(player2)
                    if not IsPlayerAlly(player, player2) then
                        ForceAddPlayer(teams.enemies[id], player2)
                        ForceRemovePlayer(teams.allies[id], player2)
                    end
                end)
                ForForce(teams.enemies[id], function()
                    local player2   = GetEnumPlayer()
                    local jd        = GetPlayerId(player2)
                    if not IsPlayerEnemy(player, player2) then
                        ForceAddPlayer(teams.allies[id], player2)
                        ForceRemovePlayer(teams.enemies[id], player2)
                    end
                end)
            end)
        end
        TriggerRegisterPlayerAllianceChange(triggers.allyChange, player, ALLIANCE_PASSIVE)
    end
    function internal.registerAlliedVictory(player)
        if not triggers.allyVictory then
            triggers.allyVictory  = CreateTrigger()
            TriggerAddAction(triggers.allyVictory, function()
                DisableTrigger(triggers.allyVictory)
                EnableTrigger(triggers.allyVictory)
            end)
        end
    end
    --  Observer callbacks
    function internal.registerObserverLeave(player)
        if not triggers.observerLeave then
            triggers.observerLeave  = CreateTrigger()
            TriggerAddAction(triggers.observerLeave, function()
                RemovePlayerPreserveUnitsBJ(GetTriggerPlayer(), PLAYER_GAME_RESULT_NEUTRAL, false)
            end)
        end
        TriggerRegisterPlayerEvent(triggers.observerLeave, player, EVENT_PLAYER_LEAVE)
    end
    function internal.initTeams()
        teams.victor        = CreateForce()
        teams.loser         = CreateForce()
        teams.players       = CreateForce()
        for id = 0, bj_MAX_PLAYERS - 1 do
            local player    = Player(id)
            if GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
                teams.allies[id]    = CreateForce()
                teams.enemies[id]   = CreateForce()
                buildingGroup[id]   = CreateGroup()
                ForceAddPlayer(teams.players, player)
            elseif IsPlayerObserver(player) then
                internal.registerObserverLeave(player)
            end
        end
    end
    function internal.enumPreplacedStructures()
        local rect      = GetWorldBounds()
        local grp       = CreateGroup()
        GroupEnumUnitsInRect(grp, rect, nil)
        ForGroup(grp, function()
            local unit      = GetEnumUnit()
            local player    = GetOwningPlayer(unit)
            local id        = GetPlayerId(player)
            if (not IsUnitType(unit, UNIT_TYPE_STRUCTURE)) or
               (id >= bj_MAX_PLAYERS) then
                return
            end
            --  Bypass checking directly
            GroupAddUnit(buildingGroup[id], unit)
        end)
        ForForce(teams.players, function()
            internal.checkTeamStatus(GetEnumPlayer())
        end)
        DestroyGroup(grp)
        RemoveRect(rect)
    end
    function internal.initTriggers()
        local tempForce     = CreateForce()
        ForForce(teams.players, function()
            ForceAddPlayer(tempForce, GetEnumPlayer())
        end)
        ForForce(teams.players, function()
            local player    = GetEnumPlayer()
            local id        = GetPlayerId(player)
            ForForce(tempForce, function()
                local player2   = GetEnumPlayer()
                local jd        = GetPlayerId(player)
                if player == player2 then
                    return
                end
                if IsPlayerAlly(player2, player) then
                    ForceAddPlayer(teams.allies[id], player2)
                end
                if IsPlayerEnemy(player2, player) then
                    ForceAddPlayer(teams.enemies[id], player2)
                end
            end)
            internal.initGlobalVars(id, player)
            internal.registerDeath(player)
            internal.registerConstruct(player)
            internal.registerChangeOwner(player)
            internal.registerLeave(player)
            internal.registerAllianceChange(player)
            internal.registerAlliedVictory(player)
        end)
        DestroyForce(tempForce)
        internal.enumPreplacedStructures()
        TimerStart(CreateTimer(), 2.00, false, function()
            DestroyTimer(GetExpiredTimer())
            ForForce(teams.players, function()
                internal.checkTeamStatus(GetEnumPlayer())
            end)
        end)
    end
    -- MeleeInitVictoryDefeat
    function CustomRaceConditions.init()
        internal.initTeams()
        internal.initTriggers()
    end
    CustomRaceConditions.checkVictors   = internal.checkVictors
end

Lua:
do
    local setupTable        = {
        unitSpacing             = 64.00,

        mineOffset              = 320.00,
        mineDeltaAngle          = 0.00,
        heroOffset              = 384.00,
        heroDeltaAngle          = 45.00,

        defXOffset              = 0.00,
        defYOffset              = -224.00,
        defHeroXOffset          = 0.00,
        defHeroYOffset          = 2.00,

        peonOffsetX             = {0.00, 1.00, -1.00,  0.60, -0.60},
        peonOffsetY             = {1.00, 0.15,  0.15, -1.00, -1.00},

        defPeonOffsetX          = {-2.00, -1.00, 0.00, 1.00, 2.00},
        defPeonOffsetY          = { 0.00,  0.00, 0.00, 0.00, 0.00},
    }
    setupTable.defHeroXOffset   = setupTable.defHeroXOffset*setupTable.unitSpacing
    setupTable.defHeroYOffset   = setupTable.defHeroYOffset*setupTable.unitSpacing

    setupTable.__index      = function(t, k)
        if type(k) == 'string' and k:sub(1,1) == '_' and k:sub(1,2) ~= '__' then
            return nil
        end
        return setupTable[k]
    end
    setupTable.__newindex   = function(t, k, v)
        if setupTable[k] then
            return
        end
        rawset(t, k, v)
    end
    setupTable.__metatable  = true
    CustomRaceSetup         = setmetatable({}, setupTable)

    local function isFunction(func)
        return type(func) == 'function' or
             ((type(func) == 'table') and isFunction(getmetatable(func).__call))
    end
    function setupTable._getRandomHero(whichPlayer, heroLoc, factionList)
        if #factionList <= 0 then
            return 0
        end
        return factionList[math.random(1, #factionList)]
    end

    function setupTable.createSetupHelper(func, hallID, peonID, peonCount, offsetX, offsetY)
        peonCount   = peonCount or 5
        hallID      = (type(hallID) == 'string' and FourCC(hallID)) or hallID
        peonID      = (type(peonID) == 'string' and FourCC(peonID)) or peonID
        offsetX     = offsetX or setupTable.peonOffsetX
        offsetY     = offsetY or setupTable.peonOffsetY
        return function(whichPlayer, startLoc, mine, peonX, peonY)
            local hall  = CreateUnitAtLoc(whichPlayer, hallID, startLoc, bj_UNIT_FACING)
            func(whichPlayer, hall, mine, peonX, peonY)

            local iter  = 1
            while iter <= peonCount do
                if mine then
                    CreateUnit(whichPlayer, peonID,
                               peonX + (offsetX[iter] or 0)*setupTable.unitSpacing,
                               peonY + (offsetY[iter] or 0)*setupTable.unitSpacing,
                               bj_UNIT_FACING)
                else
                    CreateUnit(whichPlayer, peonID,
                               peonX + (setupTable.defPeonOffsetX[iter] or 0)*setupTable.unitSpacing,
                               peonY + (setupTable.defPeonOffsetY[iter] or 0)*setupTable.unitSpacing,
                               bj_UNIT_FACING)
                end
                iter = iter + 1
            end
        end
    end
    function setupTable.createSetup(func, preloader, faction)
        if not isFunction(func) then
            return nil
        end
        preloader   = preloader or ""
        return function(whichPlayer, startLoc, doHeroes, doCamera, doPreload)
            if (doPreload) then
                Preloader(preloader)
            end
            local mine          = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
            local peon          = {}
            local heroLoc
            local nearMineLoc
            if mine ~= nil then
                local mineLoc   = GetUnitLoc(mine)
                heroLoc         = MeleeGetProjectedLoc(mineLoc, startLoc,
                                                       setupTable.heroOffset, setupTable.heroDeltaAngle)
                nearMineLoc     = MeleeGetProjectedLoc(mineLoc, startLoc,
                                                       setupTable.mineOffset, setupTable.mineDeltaAngle)
                peon.x          = GetLocationX(nearMineLoc)
                peon.y          = GetLocationY(nearMineLoc)
                RemoveLocation(mineLoc)
                RemoveLocation(nearMineLoc)
            else
                peon.x          = GetLocationX(startLoc) + setupTable.defXOffset
                peon.y          = GetLocationY(startLoc) + setupTable.defYOffset
                heroLoc         = Location(peon.x + setupTable.defHeroXOffset,
                                           peon.y + setupTable.defHeroYOffset)
            end
            func(whichPlayer, startLoc, mine, peon.x, peon.y)

            if (doHeroes) then
                if IsMapFlagSet(MAP_RANDOM_HERO) then
                    setupTable._getRandomHero(whichPlayer, heroLoc, faction.hero)
                else
                    SetPlayerState(whichPlayer, PLAYER_STATE_RESOURCE_HERO_TOKENS, bj_MELEE_STARTING_HERO_TOKENS)
                end
            end
            RemoveLocation(heroLoc)
   
            if (doCamera) then
                SetCameraPositionForPlayer(whichPlayer, peon.x, peon.y)
                SetCameraQuickPositionForPlayer(whichPlayer, peon.x, peon.y)
            end
        end
    end
end

Lua:
do
    local raceSetup         = {}
    local setupTable        = CustomRaceSetup
 
    raceSetup.human         = CustomRaceSystem.create(RACE_HUMAN, "The Alliance")
    raceSetup.human:addHall('htow', 'hkee', 'hcas')
    raceSetup.human:addHero('Hamg', 'Hmkg', 'Hpal', 'Hblm')
    raceSetup.human:defDescription("The Human Alliance is a conglomeration of Humans, Elves, and Dwarves."
                                .. "They are the most versatile army in Warcraft III, with good ground and "
                                .. "air troops, excellent siege capability, and powerful spellcasters.")
    raceSetup.human:defRacePic("war3mapImported\\humanseal.blp")
    raceSetup.human:defSetup(setupTable.createSetup(
        setupTable.createSetupHelper(function(whichPlayer, hall, mine)
            if (hall ~= nil) then
                UnitAddAbility(hall, FourCC('Amic'))
                UnitMakeAbilityPermanent(hall, true, FourCC('Amic'))
            end
        end, 'htow', 'hpea'),
        "scripts\\HumanMelee.pld", raceSetup.human))
    raceSetup.human:defAISetup(function(whichPlayer)
        PickMeleeAI(whichPlayer, "human.ai", nil, nil)
    end)

    raceSetup.orc           = CustomRaceSystem.create(RACE_ORC, "The Horde")
    raceSetup.orc:addHall('ogre', 'ostr', 'ofrt')
    raceSetup.orc:addHero('Obla', 'Ofar', 'Otch', 'Oshd')
    raceSetup.orc:defDescription("The Orcs, who once cultivated a quiet Shamanistic society upon the world"
                              .. "of Draenor, were corrupted by the chaos magics of the Burning Legion and"
                              .. " formed into a voracious, unstoppable Horde. Lured to the world of "
                              .. "Azeroth through a dimensional gateway, the Horde was manipulated into "
                              .. "waging war against the human nations of Azeroth and Lordaeron. Hoping that"
                              .. " the Horde would conquer the mortal armies of Azeroth, the Burning Legion "
                              .. "made ready for its final invasion of the unsuspecting world.")
    raceSetup.orc:defRacePic("war3mapImported\\orcseal.blp")
    raceSetup.orc:defSetup(setupTable.createSetup(
        setupTable.createSetupHelper(function(hall, mine)
        end, 'ogre', 'opeo'),
        "scripts\\OrcMelee.pld", raceSetup.orc))
    raceSetup.orc:defAISetup(function(whichPlayer)
        PickMeleeAI(whichPlayer, "orc.ai", nil, nil)
    end)

    raceSetup.undead         = CustomRaceSystem.create(RACE_UNDEAD, "The Scourge")
    raceSetup.undead:addHall('unpl', 'unp1', 'unp2')
    raceSetup.undead:addHero('Udea', 'Ulic', 'Udre', 'Ucrl')
    raceSetup.undead:defDescription("The horrifying Undead army, called the Scourge, consists of thousands of "
                                 .. "walking corpses, disembodied spirits, damned mortal men and insidious "
                                 .. "extra-dimensional entities. The Scourge was created by the Burning Legion "
                                 .. "for the sole purpose of sowing terror across the world in anticipation of "
                                 .. "the Legion's inevitable invasion. The Undead are ruled by Ner'zhul, the "
                                 .. "Lich King, who lords over the icy realm of Northrend from his frozen throne. "
                                 .. "Ner'zhul commands the terrible plague of undeath, which he sends ever "
                                 .. "southward into the human lands. As the plague encroaches on the southlands, "
                                 .. "more and more humans fall prey to Ner'zhul's mental control and life-draining "
                                 .. "sickness every day. In this way, Ner'zhul has swelled the ranks of the already "
                                 .. "considerable Scourge. The Undead employ necromantic magics and the elemental "
                                 .. "powers of the cold north against their enemies.")
    raceSetup.undead:defRacePic("war3mapImported\\undeadseal.blp")
    raceSetup.undead:defSetup(setupTable.createSetup(
        setupTable.createSetupHelper(function(whichPlayer, hall, mine, peonX, peonY)
            local cx    = peonX + 1.00*setupTable.unitSpacing
            local cy    = peonY + 0.00*setupTable.unitSpacing
            if mine and GetUnitTypeId(mine) == FourCC('ngol') then
                local mineX, mineY  = GetUnitX(mine), GetUnitY(mine)
                local mineGold      = GetResourceAmount(mine)
                local theta         = math.atan(GetUnitY(hall)-mineY,
                                                GetUnitX(hall)-mineX)
                cx                  = mineX + 288*math.cos(theta)
                cy                  = mineY + 288*math.sin(theta)
                RemoveUnit(mine)

                --  Hide everyone first, then create the gold mine
                mine            = CreateBlightedGoldmine(whichPlayer, mineX, mineY, bj_UNIT_FACING)
                SetResourceAmount(mine, mineGold)

                ShowUnit(mine, false)
                SetUnitPosition(mine, mineX, mineY)
                ShowUnit(mine, true)
            end
            bj_ghoul[GetPlayerId(whichPlayer)]  = CreateUnit(whichPlayer, FourCC('ugho'),
                                                             cx, cy, bj_UNIT_FACING)
        end, 'unpl', 'uaco', 3, {0.00, 0.65, -0.65}, {0.50, -0.50, -0.50}),
        "scripts\\UndeadMelee.pld", raceSetup.undead))
    raceSetup.undead:defAISetup(function(whichPlayer)
        PickMeleeAI(whichPlayer, "undead.ai", nil, nil)
        RecycleGuardPosition(bj_ghoul[GetPlayerId(whichPlayer)])
    end)

    raceSetup.nightelf         = CustomRaceSystem.create(RACE_NIGHTELF, "The Sentinel")
    raceSetup.nightelf:addHall('etol', 'etoa', 'etoe')
    raceSetup.nightelf:addHero('Ekee', 'Emoo', 'Edem', 'Ewar')
    raceSetup.nightelf:defDescription("The reclusive Night Elves were the first race to awaken in the World "
                                   .. "of Warcraft. These shadowy, immortal beings were the first to study "
                                   .. "magic and let it loose throughout the world nearly ten thousand years "
                                   .. "before Warcraft I. The Night Elves' reckless use of magic drew the "
                                   .. "Burning Legion into the world and led to a catastrophic war between the "
                                   .. "two titanic races. The Night Elves barely managed to banish the Legion "
                                   .. "from the world, but their wondrous homeland was shattered and drowned by "
                                   .. "the sea. Ever since, the Night Elves refused to use magic for fear that "
                                   .. "the dreaded Legion would return. The Night Elves closed themselves off "
                                   .. "from the rest of the world and remained hidden atop their holy mountain "
                                   .. "of Hyjal for many thousands of years. As a race, Night Elves are typically "
                                   .. "honorable and just, but they are very distrusting of the 'lesser races' of "
                                   .. "the world. They are nocturnal by nature and their shadowy powers often elicit "
                                   .. "the same distrust that they have for their neighbors.")
    raceSetup.nightelf:defRacePic("war3mapImported\\nightelfseal.blp")
    raceSetup.nightelf:defSetup(setupTable.createSetup(
        setupTable.createSetupHelper(function(whichPlayer, hall, mine, peonX, peonY)
            if mine then
                local cx, cy    = GetUnitX(mine), GetUnitY(mine)
                local theta     = math.atan(GetUnitY(hall) - cy, GetUnitX(hall) - cx)
                ShowUnit(hall, false)
                SetUnitPosition(hall, cx + 600*math.cos(theta), cy + 600*math.sin(theta))
                ShowUnit(hall, true)

                IssueTargetOrder(hall, "entangleinstant", mine)
            end
        end, 'etol', 'ewsp', 5),
        "scripts\\NightElfMelee.pld", raceSetup.nightelf))
    raceSetup.nightelf:defAISetup(function(whichPlayer)
        PickMeleeAI(whichPlayer, "elf.ai", nil, nil)
    end)

    raceSetup.other         = CustomRaceSystem.create(RACE_OTHER, "Neutrals")
    raceSetup.other:addHero('Npbm','Nbrn','Nngs','Nplh','Nbst','Nalc','Ntin','Nfir')
end


Simply follow the instructions below:​
  1. Import the toc file included in the downloadable map, CustomRaceTOC.toc. Leave the file path as-is.

  2. Import the corresponding race seal blp files from the downloadable map. Without these files, the default preview images will display a green texture.

  3. Copy and paste the Custom Race System folder into your Trigger Editor.


At any point in your script, you can call the following function in order to retrieve a faction "object".
Lua:
-- race takes a JASS race type, such as RACE_HUMAN or RACE_ORC
-- name is a string that represents the faction's name. (This is optional, more or less)
local faction = CustomRaceSystem.create(race [, name])

One can, at any point, redefine the faction name if it wasn't defined already at the moment of creation.
Lua:
faction.name = "Some String"

One can now include the town hall ids, as well as the custom Hero ids, in the faction.
Lua:
-- The parameters can either be strings or integers. (In the case of strings, they must
-- be rawcodes).
faction:addHall(...)
faction:addHero(...)

Note: When adding a town hall id to a faction, it is also added to the global list of town hall ids. The same goes for hero ids. This extends the default behavior as seen in normal melee maps.

Now, the race has some handy town hall ids and hero ids. Let's include some description and an image preview for our race.

Lua:
-- In case of any ambiguities, secretFilePath is a string
-- containing the path to the image texture.
faction:defDescription("This is some juicy handtext")
faction:defRacePic( <secretFilePath>)

Alright. It looks like we're almost finished now. Let's now define our setup function.
Lua:
faction:defSetup(function(whichPlayer, startLoc, doHeroes, doCamera, doPreload)
    CreateUnitAtLoc(whichPlayer, <townHallID>, startLoc, bj_UNIT_FACING)
    CreateUnitAtLoc(whichPlayer, <unitID>, startLoc, bj_UNIT_FACING)end)
    CreateUnitAtLoc(whichPlayer, <unitID>, startLoc, bj_UNIT_FACING)
    CreateUnitAtLoc(whichPlayer, <unitID>, startLoc, bj_UNIT_FACING)

Wait .. where did the function arguments come from? Let's take a look at MeleeStartingUnitsHuman to figure it out.

JASS:
function MeleeStartingUnitsHuman takes player whichPlayer, location startLoc, boolean doHeroes, boolean doCamera, boolean doPreload returns nothing
    local boolean  useRandomHero = IsMapFlagSet(MAP_RANDOM_HERO)
    local real     unitSpacing   = 64.00
    local unit     nearestMine
    local location nearMineLoc
    local location heroLoc
    local real     peonX
    local real     peonY
    local unit     townHall = null

    if (doPreload) then
        call Preloader( "scripts\\HumanMelee.pld" )
    endif

    set nearestMine = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
    if (nearestMine != null) then
        // Spawn Town Hall at the start location.
        set townHall = CreateUnitAtLoc(whichPlayer, 'htow', startLoc, bj_UNIT_FACING)
 
        // Spawn Peasants near the mine.
        set nearMineLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 320, 0)
        set peonX = GetLocationX(nearMineLoc)
        set peonY = GetLocationY(nearMineLoc)
        call CreateUnit(whichPlayer, 'hpea', peonX + 0.00 * unitSpacing, peonY + 1.00 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX + 1.00 * unitSpacing, peonY + 0.15 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX - 1.00 * unitSpacing, peonY + 0.15 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX + 0.60 * unitSpacing, peonY - 1.00 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX - 0.60 * unitSpacing, peonY - 1.00 * unitSpacing, bj_UNIT_FACING)

        // Set random hero spawn point to be off to the side of the start location.
        set heroLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 384, 45)
    else
        // Spawn Town Hall at the start location.
        set townHall = CreateUnitAtLoc(whichPlayer, 'htow', startLoc, bj_UNIT_FACING)
 
        // Spawn Peasants directly south of the town hall.
        set peonX = GetLocationX(startLoc)
        set peonY = GetLocationY(startLoc) - 224.00
        call CreateUnit(whichPlayer, 'hpea', peonX + 2.00 * unitSpacing, peonY + 0.00 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX + 1.00 * unitSpacing, peonY + 0.00 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX + 0.00 * unitSpacing, peonY + 0.00 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX - 1.00 * unitSpacing, peonY + 0.00 * unitSpacing, bj_UNIT_FACING)
        call CreateUnit(whichPlayer, 'hpea', peonX - 2.00 * unitSpacing, peonY + 0.00 * unitSpacing, bj_UNIT_FACING)

        // Set random hero spawn point to be just south of the start location.
        set heroLoc = Location(peonX, peonY - 2.00 * unitSpacing)
    endif

    if (townHall != null) then
        call UnitAddAbilityBJ('Amic', townHall)
        call UnitMakeAbilityPermanentBJ(true, 'Amic', townHall)
    endif

    if (doHeroes) then
        // If the "Random Hero" option is set, start the player with a random hero.
        // Otherwise, give them a "free hero" token.
        if useRandomHero then
            call MeleeRandomHeroLoc(whichPlayer, 'Hamg', 'Hmkg', 'Hpal', 'Hblm', heroLoc)
        else
            call SetPlayerState(whichPlayer, PLAYER_STATE_RESOURCE_HERO_TOKENS, bj_MELEE_STARTING_HERO_TOKENS)
        endif
    endif

    if (doCamera) then
        // Center the camera on the initial Peasants.
        call SetCameraPositionForPlayer(whichPlayer, peonX, peonY)
        call SetCameraQuickPositionForPlayer(whichPlayer, peonX, peonY)
    endif
endfunction

It we look closely at the function arguments, the argument names appear to be identical, down to the letter (ignoring syntax). This means that the setup function must be based on the above function. This will be the case for all setup functions for each race, so a few helper functions are included for your convenience.

Lua:
faction:defSetup(CustomRaceSetup.createSetup(
        CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine)
        end, 'htow', 'hpea'),
        "scripts\\HumanMelee.pld", faction))

That's a lot to absorb, but not to worry. Let's look at what each function does, starting from the inner closure:

Lua:
-- The closure function expects a player whichPlayer, a unit hall and a unit mine
CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine)
end, 'htow', 'hpea', 5)

What's the hall got to do with our initializer, and what's with the last three arguments?
The hall is the town hall that's created at game start, while the mine is the nearest mine from the player starting location. In most situations, the hall is also situated at the player starting location.

Now, the second argument is the hall id that's going to be created; the third argument is our worker id, and the fourth argument specifies the number of workers we'll create. In most cases, it's best to just ignore the last argument, since it defaults to 5.

Okay, we're done with the inner function. How about the outer function?
Lua:
faction:defSetup(CustomRaceSetup.createSetup(function()
end, "scripts\\HumanMelee.pld", faction))

Assuming that our closure exists, we look at the last two parameters.
The second parameter appears to be a string to be passed to a Preloader function. One can pass an empty string or a nil value to it, and it'll still work. The third parameter appears to refer back to the faction object. This implies that the faction object can't be used to create a setup function that directly refers to itself.

Interestingly, the parameter can also be any faction object, since the generated setup function will throw a random Hero if the appropriate map flag bit is set. It will throw a random Hero based on the faction's list of hero ids.

If you want to know more about using Custom Race System, you can check out the Setup chunk of the library.​

Lua:
-- race is a jass-defined race type
-- name is a string
-- This creates a faction object.
CustomRaceSystem.create(race, name) => faction

-- The vararg can take either a string or an integer.
-- This adds a hall id to the faction's list, as well as the global list of hall ids.
faction:addHall(...)

-- The vararg can take either a string or an integer.
-- This adds a hero id to the faction's list, as well as the global list of hero ids.
-- If MAP_RANDOM_HERO is set, this will produce a random hero based on the
-- faction's hero list.
faction:addHero(...)

-- func should always be something which behaves as a function.
-- func syntax: function(whichPlayer, startLoc, doHeroes, doCamera, doPreload) end
faction:defSetup(func)

-- func should always be something which behaves as a function.
-- func syntax: function(whichPlayer) end
function:defAISetup(func)

-- All functions below take a string as a parameter.
faction:defRacePic(racePath)
faction:defDescription(desc)
faction:defName(name)
faction:defPlaylist(playlist)

  • v.1.0.0 - Release

  • v.1.0.1 - Added a camera pan effect once the game starts.
    • User control is disabled during the transition to the game.
    • Added High Elves as a possible faction choice.
    • To play High Elves, select the Human Race.
    • Split the setup chunk into the main Setup chunk and the Default chunk for clarity.
  • v.1.0.2 - Fixed highlight bug where a selected frame would remain highlighted.
    • The countdown display is now moved to a text frame. Enjoy!
    • Test Map:
      • Swordsman's hitpoints reduced from 420 to 300.
      • Archer's hitpoints reduced from 310 to 220.
  • v.1.1.0 - Added a music playlist option.
    • Probably fixed the leave game bug where the victory is not given to the remaining player.
    • If a player who has an ally has left the game, the player is no longer given a victory when the ally wins the game.
    • Added GUI support.
    • Added a countdown timer mechanic when selecting your faction.
      • If you haven't chosen any faction or have not finalized your choice, it will default to the normal race)
      • Also added a warning sound that plays 5 seconds before the system automatically selects the normal race for you.

  • Blizzard - Race Seal Images
  • Tasyen - UI Tutorials
  • Hive - Without the Techtree Contests, this wouldn't have been created.
  • Riki - For the music playlist trick
Previews
Contents

Typhoon (Map)

Level 8
Joined
Feb 7, 2020
Messages
291
This is awesome. It was always clunky going through functional but convoluted command bar pages in the mash-up maps.

I think it's quite grand in its current state, but if you ever wanted a stretch goal: one of the things missing from the race compilations (for me) was unit previews. It wouldn't have to be fancy, but I think with some of the new 1.31+ natives it'd be feasible to fetch unit tooltips (not sure if icons are possible yet), making the end user work somewhat minimal--they just insert the rawcode into a table or something, and icon if needed. I envision the main UI widget being nudged to the left or right and having a column of unit icons that the player can hover over to read about. It could just be a plugin, too.

But, that was just a random idea I had. This current package still makes the mash-up potential so much better.
 
Level 1
Joined
Mar 9, 2010
Messages
6
Hi
This works well on the map you gave us, but when I import to an other map and try to save it i get an error messege.
I imported all the files and the triggers as well.
Can you help solve the problem?
error.jpg

nwN4QBd
 
Last edited:
Level 2
Joined
Oct 29, 2020
Messages
14
This is really cool! I would love to add it into my map but unfortunately I do not use Lua and I have way too many scripts using vJass :(

is there any similar kind of menus for vJass users?
 

MyPad

Spell Reviewer
Level 20
Joined
May 9, 2014
Messages
1,601
This is really cool! I would love to add it into my map but unfortunately I do not use Lua and I have way too many scripts using vJass :(

is there any similar kind of menus for vJass users?

I'm not sure about that. I'll consider adding vJASS compatibility once I have enough time on my hands. (Maybe throw in GUI support as well)
 
Level 1
Joined
Mar 9, 2010
Messages
6
Hi
Can you give me example how to set up an undead like custom race?
I couldn't make it work. I managed the nigth elven one, but for undead like race. Only hall appear, but none of the workers.
Can you tell me why is the ai mostly loose on start?
Will you add an add altar() code and add music() later on?
Thanks in advance.
 

MyPad

Spell Reviewer
Level 20
Joined
May 9, 2014
Messages
1,601
Hello Lusta.

I've copied and pasted the following code from the defaults section of the system:
Lua:
local faction                  = CustomRaceSystem.create(RACE_UNDEAD, "The Fallen Elves")
faction:addHall(<your rawcodes here>)
faction:addHero(<your Hero rawcodes here>)
-- [[
    Some interesting default sections
]]
faction:defSetup(CustomRaceSetup.createSetup(
    CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine, peonX, peonY)
        if mine then
            local cx, cy    = GetUnitX(mine), GetUnitY(mine)
            local theta     = math.atan(GetUnitY(hall) - cy, GetUnitX(hall) - cx)
            ShowUnit(hall, false)
            SetUnitPosition(hall, cx + 600*math.cos(theta), cy + 600*math.sin(theta))
            ShowUnit(hall, true)
             IssueTargetOrder(hall, "entangleinstant", mine)
        end
    end, <insert hall and worker rawcodes respectively>[,5]),
    "scripts\\NightElfMelee.pld", faction)
)
faction:defAISetup(function(whichPlayer)
    PickMeleeAI(whichPlayer, "elf.ai", nil, nil)
end)

In your case, it might be likely that setupTable was used instead of the exposed global CustomRaceSetup. Try replacing all instances of setupTable in your script (except that from the system) with CustomRaceSetup.

About the music, I might consider adding it in the future, but for now, a work-around would have to do. Unfortunately, the computer players only pick the default faction, but I might change it in the future.

Lua:
-- Assume that faction exists as an upvalue, and is defined
local faction
local coreSetup = CustomRaceSetup.createSetup(
    CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine, peonX, peonY)
        if mine then
            local cx, cy    = GetUnitX(mine), GetUnitY(mine)
            local theta     = math.atan(GetUnitY(hall) - cy, GetUnitX(hall) - cx)
            ShowUnit(hall, false)
            SetUnitPosition(hall, cx + 600*math.cos(theta), cy + 600*math.sin(theta))
            ShowUnit(hall, true)
             IssueTargetOrder(hall, "entangleinstant", mine)
        end
    end, 'etol', 'ewsp', 5),
    "scripts\\NightElfMelee.pld", faction)
)

faction:defSetup(function(whichPlayer, startLoc, doHeroes, doCamera, doPreload)
    coreSetup(whichPlayer, startLoc, doHeroes, doCamera, doPreload)
    if GetLocalPlayer() == whichPlayer then
        -- Do some music stuff here.
    end
end)

EDIT:
Music playlist feature added
 
Last edited:
Level 1
Joined
Mar 9, 2010
Messages
6
Hello MyPad
Thanks for the reply.
But you misunderstand. I managed to get nigth elfs working.
I can't make an undead faction that has to worker types (Acolyte, Ghoul) and a Haunted Gold Mine.
Only the Necropolis spawns, without units.
 
Level 1
Joined
Mar 9, 2010
Messages
6
Well I can, but models and sounds are gona be missing for lot of others stuff, becouse local files.
Problem is at undead Scourge of Lordearon faction. I am useing a copy of the undead units with original models.
Rest of the factions are working as intended.
Sorry for the late reply, internet connection decided to mess with me.
Thanks
 

Attachments

  • (L)Northshire2.w3x
    1.4 MB · Views: 12

MyPad

Spell Reviewer
Level 20
Joined
May 9, 2014
Messages
1,601
Turns out it was an attempt at referencing setupTable which caused the function to fail.
Try copy and pasting this script into your Scourge of Lordaeron script:

Lua:
do
    local elvens = CustomRaceSystem.create(RACE_UNDEAD, "Scourge of Lordearon")
    elvens:defDescription("After preparing for many long months, Kel'Thuzad and his Cult of the Damned finally struck the first blow by releasing the plague of undeath upon Lordaeron. Uther and his fellow paladins investigated the infected regions in the hope of finding a way to stop the plague. Despite their efforts, the plague continued to spread and threatened to tear the Alliance apart.")
    elvens:addHall('u005:unpl')
    elvens:addHall('u006:unp1')
    elvens:addHall('u007:unp2')
    elvens:addHero('U00W:Uktl')
    elvens:defRacePic("war3mapImported\\undeadseal.blp")
    elvens:defSetup(CustomRaceSetup.createSetup(
        CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine, peonX, peonY)
            local cx    = peonX + 1.00*CustomRaceSetup.unitSpacing
            local cy    = peonY + 0.00*CustomRaceSetup.unitSpacing
            if mine and GetUnitTypeId(mine) == FourCC('ngol') then
                local mineX, mineY  = GetUnitX(mine), GetUnitY(mine)
                local mineGold      = GetResourceAmount(mine)
                local theta         = math.atan(GetUnitY(hall)-mineY, GetUnitX(hall)-mineX)
                cx                  = mineX + 288*math.cos(theta)
                cy                  = mineY + 288*math.sin(theta)
                RemoveUnit(mine)
                mine            = CreateBlightedGoldmine(whichPlayer, mineX, mineY, bj_UNIT_FACING)
                SetResourceAmount(mine, mineGold)
                ShowUnit(mine, false)
                SetUnitPosition(mine, mineX, mineY)
                ShowUnit(mine, true)
            end
            bj_ghoul[GetPlayerId(whichPlayer)]  = CreateUnit(whichPlayer, FourCC('u00B'), cx, cy, bj_UNIT_FACING)
        end, 'u005', 'u004', 3), "scripts\\UndeadMelee.pld", elvens))
    elvens:defAISetup(function(whichPlayer)
        PickMeleeAI(whichPlayer, "undead.ai", nil, nil)
        RecycleGuardPosition(bj_ghoul[GetPlayerId(whichPlayer)])
    end)
end
 
Top