1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Head to the 33rd Modeling Contest Poll and drink to your heart's desire.
    Dismiss Notice
  3. Choose your means of doom in the 17th Mini Mapping Contest Poll.
    Dismiss Notice
  4. A slave to two rhythms, the 22nd Terraining Contest is here.
    Dismiss Notice
  5. The heavens smile on the old faithful. The 16th Techtree Contest has begun.
    Dismiss Notice
  6. The die is cast - the 6th Melee Mapping Contest results have been announced. Onward to the Hive Cup!
    Dismiss Notice
  7. The glory of the 20th Icon Contest is yours for the taking!
    Dismiss Notice
  8. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[Lua] UI-Custom Race System

Submitted by MyPad
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).
Pros:
  • Custom UI Frames for race selection
Cons:
  • Requires 1.31.1 or higher
  • Visual settings based on 1.31.1
  • Limited or non-existent GUI support.
  • Requires the map to be in Lua mode


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

    • Code (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
      end
       

    • Code (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)) then
                      -- Remove any Neutral Hostile units from the area.
                      RemoveUnit(filterUnit)
                  elseif (owner == Player(PLAYER_NEUTRAL_PASSIVE)) and
                          not IsUnitType(filterUnit, UNIT_TYPE_STRUCTURE) then
                      -- 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
              if IsPlayerObserver(player) then
                  return
              elseif slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING then
                  return
              end

              EnableUserControl(false)
              SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 2650.00, 0)
              SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 1750.00, dur)
              TimerStart(CreateTimer(), dur, false, function()
                  DestroyTimer(GetExpiredTimer())
                  CameraSetSmoothingFactor(0.00)
                  EnableUserControl(true)
                  SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 1650.00, CustomRace.countdown.INTERVAL)
              end)
          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
                      ((faction.aiSetup) and 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
                  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
       

    • Code (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"

          local config                    = {
              RACE_MAX_OPTIONS            = 4,

              MAIN_WIDTH                  = 0.50,
              MAIN_HEIGHT                 = 0.44,

              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,

              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,
              },

              scale                       = {
                  RACE_TEXT               = 1.35,
                  SELECTION_TEXT          = 1.25,
              },

              framename                   = {
                  main                    = "EscMenuBackdrop",
                  descMain                = "BattleNetTextAreaTemplate",
                  iconMain                = "QuestButtonBaseTemplate",
                  selectMain              = "QuestButtonBaseTemplate",
                  sliderMain              = "QuestMainListScrollBar",
                  buttonFrame             = "ScriptDialogButton",

                  --  List buttons
                  listButtons             = "EscMenuButtonTemplate",
                  listHighlight           = "QuestButtonMouseOverHighlightTemplate",
              },
          }

          function internal.loadTOC()
              if not BlzLoadTOCFile(CustomRaceUI.toc) then
                  error("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.playSound(player, snd)
              if GetLocalPlayer() ~= player then
                  SetSoundVolume(snd, 0)
              end
              StartSound(snd)
              KillSoundWhenDone(snd)
          end
       
          do
              config.UPPER_BOX_WIDTH      = config.MAIN_WIDTH
              config.LOWER_BOX_WIDTH      = config.MAIN_WIDTH
              config.SLIDER_HEIGHT        = config.SELECTION_HEIGHT
          end

          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.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
          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 then
                      return
                  end
                  if 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

          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
              if CustomRaceUI.selectionCount <= 0 then
                  CustomRaceUI.clearSelectionTrigger()
                  CustomRace.start()
              end
          end
          function CustomRaceUI.clearSelectionTrigger()
              if not CustomRaceUI.playerSelectTrig then
                  return
              end
              DisableTrigger(CustomRaceUI.playerSelectTrig)
              DestroyTrigger(CustomRaceUI.playerSelectTrig)
              CustomRaceUI.playerSelectTrig   = nil
          end
          function CustomRaceUI.checkFactionSelection()
              if CustomRaceUI.selectionCount <= 0 then
                  CustomRaceUI.display(GetLocalPlayer(), false)
                  CustomRace.start()
                  return
              end

              local trig                      = CreateTrigger()
              CustomRaceUI.playerSelectData   = {}
              CustomRaceUI.playerSelectTrig   = trig
              TriggerAddCondition(trig, Condition(function()
                  local player    = GetTriggerPlayer()
                  CustomRaceUI.removeSelectingPlayer(player)
              end))
              for i = 1, #CustomRaceUI.playerData do
                  local player    = CustomRaceUI.playerData[i].player
                  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
                  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 then
                  CustomRace.faction[id]  = 1
                  return
              end
              local data  = {
                  player          = player,
                  playerID        = id,
                  playerRace      = GetPlayerRace(player),
                  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.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.
      Code (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
          --  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)
              local id                = GetPlayerId(player)
              local flag              = false
              if bj_meleeVictoried[id] then
                  return
              elseif 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])
              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)
              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, false)
              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.registerDefeat(player)
              if not triggers.defeat then
                  triggers.defeat  = CreateTrigger()
                  TriggerAddAction(triggers.defeat, function()
                  end)
              end
          end
          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)
                          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) then
                      return
                  elseif 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.registerDefeat(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
      end
       

    • Code (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
       

    • Code (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".
    Code (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.
    Code (Lua):

    faction.name = "Some String"
     
    One can now include the town hall ids, as well as the custom Hero ids, in the faction.
    Code (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.

    Code (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.
    Code (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.

    Code (vJASS):

    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.

    Code (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:

    Code (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?
    Code (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.​

  • Code (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)
     

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

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

Typhoon (Map)

  1. Planetary

    Planetary

    Joined:
    Feb 7, 2020
    Messages:
    291
    Resources:
    1
    Maps:
    1
    Resources:
    1
    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.
     
  2. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,533
    Resources:
    9
    Models:
    1
    Icons:
    2
    Maps:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    9
    Thanks for the feedback. I might consider a variant of that idea in a later version. For now, I'll have to include tournament functionality first.