Name | Type | is_array | initial_value |
CustomRace_AIScript | string | No | |
CustomRace_Description | string | No | |
CustomRace_HallID | unitcode | Yes | |
CustomRace_HeroID | unitcode | Yes | |
CustomRace_InitEvent | real | No | |
CustomRace_MainHallID | unitcode | No | |
CustomRace_Name | string | No | |
CustomRace_Playlist | string | No | |
CustomRace_PLDScript | string | No | |
CustomRace_Race | race | No | |
CustomRace_RacePic | string | No | |
CustomRace_SetupHall | unit | No | |
CustomRace_SetupMine | unit | No | |
CustomRace_SetupPlayer | player | No | |
CustomRace_SetupTrig | trigger | No | |
CustomRace_WorkerCount | integer | No | -1 |
CustomRace_WorkerID | unitcode | No |
do
local tb = {}
local raceContainer = {
[RACE_HUMAN] = {},
[RACE_ORC] = {},
[RACE_UNDEAD] = {},
[RACE_NIGHTELF] = {},
[RACE_DEMON] = {},
[RACE_OTHER] = {},
}
CustomRaceSystem = setmetatable({}, tb)
-- Define __index and __newindex metamethods.
tb.__index = function(t, k)
if type(k) == 'string' and
(k:sub(1,1) == '_') and
(k:sub(1,2) ~= '__') then
return nil
end
return tb[k]
end
tb.__newindex = function(t, k, v)
if tb[k] then
return
end
rawset(t, k, v)
end
tb._hall = {}
tb._hallptr = {}
tb._hero = {}
tb._heroptr = {}
tb._container = raceContainer
tb._DEBUG = false
local function printAfter(delay, ...)
if not tb._DEBUG then
return
end
local t = {...}
TimerStart(CreateTimer(), delay or 0, false, function()
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
-- Ensure that the entry is
if t then
print(table.unpack(t))
end
t = nil
end)
end
local function addFaction(race, faction)
local t = raceContainer[race] or {}
raceContainer[race] = t
t[#t+1] = faction
end
function tb.create(race, name)
local o = {}
o.race = race
o.name = name or ""
o.hall = {}
o.hallptr = {}
o.hero = {}
o.heroptr = {}
addFaction(race, o)
setmetatable(o, tb)
return o
end
function tb:addHall(...)
local t = {...}
if #t < 1 then
return
end
for i = 1, #t do
local id = (type(t[i]) == 'string' and FourCC(t[i])) or t[i]
if not self.hallptr[id] then
self.hall[#self.hall + 1] = id
self.hallptr[id] = #self.hall
end
if not tb._hallptr[id] then
tb._hall[#tb._hall + 1] = id
tb._hallptr[id] = #tb._hall
end
end
end
function tb:addHero(...)
local t = {...}
if #t < 1 then
return
end
for i = 1, #t do
local id = (type(t[i]) == 'string' and FourCC(t[i])) or t[i]
if not self.heroptr[id] then
self.hero[#self.hero + 1] = id
self.heroptr[id] = #self.hero
end
if not tb._heroptr[id] then
tb._hero[#tb._hero + 1] = id
tb._heroptr[id] = #tb._hero
end
end
end
function tb:defSetup(func)
self.setup = func
end
function tb:defAISetup(func)
self.aiSetup = func
end
function tb:defRacePic(picPath)
self.racePic = picPath or ""
end
function tb:defDescription(desc)
self.description = desc or ""
end
function tb:defName(name)
self.name = name or ""
end
function tb:defPlaylist(playlist)
self.playlist = playlist
end
end
do
local tb = getmetatable(CustomRaceSystem)
local melee = {}
CustomRace = setmetatable({}, tb)
CustomRace.faction = {}
CustomRace.countdown = {
TICKS = 3,
INTERVAL = 1.00,
INCLUDE_EXTRA_TICK = true,
Z_OFFSET = 800.00,
}
function melee.generateTick()
local snd = CreateSound( "Sound\\Interface\\BattleNetTick.wav", false, false, false, 10, 10, "" )
SetSoundParamsFromLabel( snd, "ChatroomTimerTick" )
SetSoundDuration( snd, 476 )
return snd
end
function melee.generateHorn()
local snd = CreateSound( "Sound\\Ambient\\DoodadEffects\\TheHornOfCenarius.wav", false, false, false, 10, 10, "DefaultEAXON" )
SetSoundParamsFromLabel( snd, "HornOfCenariusSound" )
SetSoundDuration( snd, 12120 )
return snd
end
function melee.playSound(snd)
StartSound(snd)
KillSoundWhenDone(snd)
end
function melee.grantItemsToHero(whichhero)
if not IsUnitType(whichhero, UNIT_TYPE_HERO) then
return
end
local owner = GetPlayerId(GetOwningPlayer(whichhero))
-- If we haven't twinked N heroes for this player yet, twink away.
if (bj_meleeTwinkedHeroes[owner] < bj_MELEE_MAX_TWINKED_HEROES) then
UnitAddItemById(whichhero, FourCC('stwp'))
bj_meleeTwinkedHeroes[owner] = bj_meleeTwinkedHeroes[owner] + 1
end
end
function melee.clearNearbyUnits(locx, locy, range)
local grp = CreateGroup()
GroupEnumUnitsInRange(grp, locx, locy, range, nil)
ForGroup(grp, function()
local filterUnit = GetEnumUnit()
local owner = GetOwningPlayer(filterUnit)
if (owner == Player(PLAYER_NEUTRAL_AGGRESSIVE)) or
((owner == Player(PLAYER_NEUTRAL_PASSIVE)) and
not IsUnitType(filterUnit, UNIT_TYPE_STRUCTURE)) then
-- Remove any Neutral Hostile units from the area.
-- Remove non-structure Neutral Passive units from the area.
RemoveUnit(filterUnit)
end
end)
DestroyGroup(grp)
end
local function forAllPlayers(func)
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
func(i, Player(i))
end
end
local function startingVis()
SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
end
local function startingHeroLimit()
forAllPlayers(function(id, player)
SetPlayerTechMaxAllowed(player, FourCC('HERO'), bj_MELEE_HERO_LIMIT)
for i = 1, #tb._hero do
SetPlayerTechMaxAllowed(player, tb._hero[i], bj_MELEE_HERO_TYPE_LIMIT)
end
end)
end
local function grantHeroItems()
local trig = CreateTrigger()
TriggerAddCondition(trig, Condition(function()
melee.grantItemsToHero(GetTrainedUnit())
end))
forAllPlayers(function(id, player)
if id >= bj_MAX_PLAYERS then
return
end
TriggerRegisterPlayerUnitEvent(trig, player, EVENT_PLAYER_UNIT_TRAIN_FINISH, nil)
end)
trig = CreateTrigger()
TriggerRegisterPlayerUnitEvent(trig, Player(PLAYER_NEUTRAL_PASSIVE), EVENT_PLAYER_UNIT_SELL, filterMeleeTrainedUnitIsHeroBJ)
TriggerAddAction(trig, function()
melee.grantItemsToHero(GetSoldUnit())
end)
bj_meleeGrantHeroItems = true
end
local slotStatus = {}
local function startingResources()
if (VersionGet() == VERSION_REIGN_OF_CHAOS) then
startingGold = bj_MELEE_STARTING_GOLD_V0
startingLumber = bj_MELEE_STARTING_LUMBER_V0
else
startingGold = bj_MELEE_STARTING_GOLD_V1
startingLumber = bj_MELEE_STARTING_LUMBER_V1
end
forAllPlayers(function(id, player)
slotStatus[id] = GetPlayerSlotState(player)
if id >= bj_MAX_PLAYERS or
(slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
return
end
SetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD, startingGold)
SetPlayerState(player, PLAYER_STATE_RESOURCE_LUMBER, startingLumber)
end)
end
local function clearExcessUnits()
forAllPlayers(function(id, player)
if id >= bj_MAX_PLAYERS or (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
return
end
local locid = GetPlayerStartLocation(player)
local locx, locy = GetStartLocationX(locid), GetStartLocationY(locid)
melee.clearNearbyUnits(locx, locy, bj_MELEE_CLEAR_UNITS_RADIUS)
end)
end
local function startRaceSelection()
CustomRaceUI.init()
forAllPlayers(function(id, player)
if id >= bj_MAX_PLAYERS or (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
return
end
CustomRaceUI.feedPlayerData(id, player)
CustomRaceUI.processPlayer(player)
end)
CustomRaceUI.checkFactionSelection()
end
local function pauseAll()
SuspendTimeOfDay(true)
CustomRace.pauseFlag = true
CustomRace.pauseTrig = CreateTrigger()
TriggerAddCondition(CustomRace.pauseTrig, Condition(function()
if CustomRace.pauseFlag then
PauseUnit(GetTriggerUnit(), true)
end
end))
local rect, reg = GetWorldBounds(), CreateRegion()
RegionAddRect(reg, rect)
TriggerRegisterEnterRegion(CustomRace.pauseTrig, reg, nil)
local grp = CreateGroup()
GroupEnumUnitsInRect(grp, rect, nil)
ForGroup(grp, function()
PauseUnit(GetEnumUnit(), true)
end)
DestroyGroup(grp)
end
local function unpauseAll()
SuspendTimeOfDay(false)
CustomRace.pauseFlag = nil
DestroyTrigger(CustomRace.pauseTrig)
CustomRace.pauseTrig = nil
local grp = CreateGroup()
local rect = GetWorldBounds()
GroupEnumUnitsInRect(grp, rect, nil)
ForGroup(grp, function()
PauseUnit(GetEnumUnit(), false)
end)
DestroyGroup(grp)
RemoveRect(rect)
end
local function startingCamera()
local player = GetLocalPlayer()
local id = GetPlayerId(player)
local dur = (CustomRace.countdown.TICKS + 1)*CustomRace.countdown.INTERVAL
local fixCamera = not (IsPlayerObserver(player)) and
(slotStatus[id] == PLAYER_SLOT_STATE_PLAYING)
EnableUserControl(false)
if fixCamera then
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 2650.00, 0)
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 1750.00, dur)
end
local checkVictorTimer = CreateTimer()
TimerStart(checkVictorTimer, 2.00, false, function()
CustomRaceConditions.checkVictors()
end)
TimerStart(CreateTimer(), dur, false, function()
DestroyTimer(GetExpiredTimer())
EnableUserControl(true)
if fixCamera then
CameraSetSmoothingFactor(0.00)
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 1650.00, CustomRace.countdown.INTERVAL)
end
ResumeTimer(checkVictorTimer)
end)
PauseTimer(checkVictorTimer)
end
local function startingUnits()
Preloader("scripts\\SharedMelee.pld")
forAllPlayers(function(id, player)
if id >= bj_MAX_PLAYERS or (slotStatus[id] ~= PLAYER_SLOT_STATE_PLAYING) then
return
end
local race = GetPlayerRace(player)
local faction = tb._container[race][CustomRace.faction[id]]
local startloc = GetStartLocationLoc(GetPlayerStartLocation(player))
if faction then
if (not faction.setup) or (faction.setup) and
(not pcall(faction.setup, player, startloc, true, true, true))
and (tb._DEBUG) then
print("Warning! Setup function failed for the following player:", GetPlayerName(player))
print("Faction ID:", faction.name or ("Faction(" .. tostring(CustomRace.faction[id]) .. ")"))
end
RemoveLocation(startloc)
if GetPlayerController(player) == MAP_CONTROL_COMPUTER and
((not faction.aiSetup) or (not pcall(faction.aiSetup, player))) and
(tb._DEBUG) then
print("Warning! AI-Setup function failed for the following computer player:", GetPlayerName(player))
print("Faction ID:", faction.name or ("Faction(" .. tostring(CustomRace.faction[id]) .. ")"))
end
-- Copied from Riki's custom race setup function
if faction.playlist and (faction.playlist ~= "") then
ClearMapMusic()
StopMusic(false)
if not pcall(PlayMusic, faction.playlist) and
(tb._DEBUG) then
print("Warning! Custom music playlist failed to play")
print("Faction ID:", faction.name or ("Faction(" .. tostring(CustomRace.faction[id]) .. ")"))
print("Faction playlist type:", type(faction.playlist))
print("CustomRaceSystem >> Playlist must be a string value!\n")
end
end
end
end)
end
local function startingCountdown()
local timer = CreateTimer()
local ticks = CustomRace.countdown.TICKS + 1
local disp = ticks
if CustomRace.countdown.INCLUDE_EXTRA_TICK then
ticks = ticks + 1
end
TimerStart(timer, CustomRace.countdown.INTERVAL, true, function()
disp = disp - 1
ticks = ticks - 1
if ticks <= 0 then
PauseTimer(timer)
DestroyTimer(timer)
end
if disp == 0 then
CustomRaceUI.mainScreen.display("START!")
melee.playSound(melee.generateHorn())
elseif disp > 0 then
CustomRaceUI.mainScreen.display(disp)
melee.playSound(melee.generateTick())
end
if ticks <= 0 then
unpauseAll()
xpcall(CustomRaceConditions.init, print)
end
end)
end
function CustomRace.initialization()
startingVis()
startingHeroLimit()
grantHeroItems()
startingResources()
clearExcessUnits()
pauseAll()
startRaceSelection()
end
function CustomRace.start()
startingUnits()
startingCountdown()
startingCamera()
end
end
do
--[[
HEIGHT -> with respect to y-axis
WIDTH -> with respect to x-axis
]]
local tb = getmetatable(CustomRaceSystem)
local internal = {}
CustomRaceUI = setmetatable({}, tb)
CustomRaceUI.toc = "war3mapImported\\CustomRaceTOC.toc"
-- Configure this if you know what you are doing
-- Otherwise, try to leave this as is
local config = {
RACE_MAX_OPTIONS = 4,
MAIN_WIDTH = 0.50,
MAIN_HEIGHT = 0.44,
BACKUP_WIDTH = 0.28,
BACKUP_HEIGHT = 0.196,
UPPER_BOX_HEIGHT = 0.20, -- Must be >= DESC_BOX_HEIGHT
LOWER_BOX_HEIGHT = 0.24, -- Must be < MAIN_HEIGHT
DESC_BOX_WIDTH = 0.28,
DESC_BOX_HEIGHT = 0.14,
ICON_BOX_WIDTH = 0.16,
ICON_BOX_HEIGHT = 0.16,
ICON_DISP_WIDTH = 0.148,
ICON_DISP_HEIGHT = 0.148,
RACE_TEXT_WIDTH = 0.28,
RACE_TEXT_HEIGHT = 0.02,
SELECTION_WIDTH = 0.44,
SELECTION_HEIGHT = 0.18,
SLIDER_WIDTH = 0.01,
CONFIRM_WIDTH = 0.14,
CONFIRM_HEIGHT = 0.04,
SELECTION_TEXT_WIDTH = 0.24,
SELECTION_TEXT_HEIGHT = 0.02,
TEXTFRAME_WIDTH = 0.20,
TEXTFRAME_HEIGHT = 0.04,
WAITTEXT_WIDTH = 0.16,
WAITTEXT_HEIGHT = 0.055,
PCOUNT_WIDTH = 0.166,
PCOUNT_HEIGHT = 0.0275,
margin = {
UPPER_UPPER_MARGIN = 0.02,
UPPER_LOWER_MARGIN = 0.02,
UPPER_LEFT_MARGIN = 0.02,
UPPER_RIGHT_MARGIN = 0.02,
LOWER_UPPER_MARGIN = 0.00,
LOWER_LOWER_MARGIN = 0.02,
LOWER_LEFT_MARGIN = 0.02,
LOWER_RIGHT_MARGIN = 0.02,
RACE_LEFT_MARGIN = 0.005,
SELECTION_LOWER_MARGIN = 0.04,
SELECTION_TEXT_LEFT_MARGIN = 0.02,
WAIT_UPPER_MARGIN = -0.014,
WAIT_LEFT_MARGIN = 0,
PCOUNT_UPPER_MARGIN = -0.008,
PCOUNT_LEFT_MARGIN = 0.017,
},
scale = {
RACE_TEXT = 1.35,
SELECTION_TEXT = 1.25,
WAIT_TEXT = 1.90,
PCOUNT_TEXT = 1.75,
},
framename = {
main = "EscMenuBackdrop",
descMain = "BattleNetTextAreaTemplate",
iconMain = "QuestButtonBaseTemplate",
selectMain = "QuestButtonBaseTemplate",
sliderMain = "QuestMainListScrollBar",
buttonFrame = "ScriptDialogButton",
-- List buttons
listButtons = "EscMenuButtonTemplate",
listHighlight = "QuestButtonMouseOverHighlightTemplate",
-- Backup
backupMain = "EscMenuBackdrop",
},
-- An exception to the above suggestion
-- you can freely configure this (as long as it's not less than 0)
WAIT_DURATION = 15.00,
CRITICAL_DURATION = 5.00,
}
function internal.loadTOC()
if not BlzLoadTOCFile(CustomRaceUI.toc) then
error("CustomRaceSystemUI >> toc file was not loaded.")
end
end
function internal.generateSmallClick()
local snd = CreateSound("Sound\\Interface\\MouseClick1.wav", false, false, false, 10, 10, "")
SetSoundParamsFromLabel( gg_snd_MouseClick1, "InterfaceClick")
SetSoundDuration(snd, 390)
SetSoundVolume(snd, 127)
return snd
end
function internal.generateBigClick()
local snd = CreateSound("Sound\\Interface\\BigButtonClick.wav", false, false,
false, 10, 10, "" )
SetSoundParamsFromLabel(snd, "MouseClick1")
SetSoundDuration(snd, 239)
SetSoundVolume(snd, 127)
return snd
end
function internal.generateCriticalSound()
local snd = CreateSound("Sound\\Interface\\Warning.wav", false, false,
false, 10, 10, "" )
SetSoundParamsFromLabel(snd, "Warning")
SetSoundDuration(snd, 1903)
SetSoundVolume(snd, 127)
return snd
end
function internal.playSound(player, snd)
if GetLocalPlayer() ~= player then
SetSoundVolume(snd, 0)
end
StartSound(snd)
KillSoundWhenDone(snd)
end
config.UPPER_BOX_WIDTH = config.MAIN_WIDTH
config.LOWER_BOX_WIDTH = config.MAIN_WIDTH
config.SLIDER_HEIGHT = config.SELECTION_HEIGHT
function internal.prepareAllFrames()
local world = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
CustomRaceUI.main = BlzCreateFrame(config.framename.main, world, 0, 0)
BlzFrameSetSize(CustomRaceUI.main, config.MAIN_WIDTH, config.MAIN_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.main)
BlzFrameSetAbsPoint(CustomRaceUI.main, FRAMEPOINT_TOP,
0.4, 0.80 - config.MAIN_HEIGHT/2)
CustomRaceUI.descMain = BlzCreateFrame(config.framename.descMain, CustomRaceUI.main, 0, 0)
BlzFrameSetSize(CustomRaceUI.descMain, config.DESC_BOX_WIDTH, config.DESC_BOX_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.descMain)
BlzFrameSetPoint(CustomRaceUI.descMain, FRAMEPOINT_TOPRIGHT,
CustomRaceUI.main, FRAMEPOINT_TOPRIGHT,
-config.margin.UPPER_RIGHT_MARGIN, -config.margin.UPPER_RIGHT_MARGIN*2)
CustomRaceUI.iconMain = BlzCreateFrame(config.framename.iconMain, CustomRaceUI.main, 0, 0)
BlzFrameSetSize(CustomRaceUI.iconMain, config.ICON_BOX_WIDTH, config.ICON_BOX_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.iconMain)
BlzFrameSetPoint(CustomRaceUI.iconMain, FRAMEPOINT_TOPLEFT,
CustomRaceUI.main, FRAMEPOINT_TOPLEFT,
config.margin.UPPER_LEFT_MARGIN, -config.margin.UPPER_UPPER_MARGIN)
CustomRaceUI.raceMain = BlzCreateFrameByType("TEXT", "CustomRaceHeaderText",
CustomRaceUI.main, "", 0)
BlzFrameSetSize(CustomRaceUI.raceMain,
(config.RACE_TEXT_WIDTH-config.margin.RACE_LEFT_MARGIN)/config.scale.RACE_TEXT,
config.RACE_TEXT_HEIGHT/config.scale.RACE_TEXT)
BlzFrameSetScale(CustomRaceUI.raceMain, config.scale.RACE_TEXT)
BlzFrameSetTextAlignment(CustomRaceUI.raceMain, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
BlzFrameClearAllPoints(CustomRaceUI.raceMain)
BlzFrameSetPoint(CustomRaceUI.raceMain, FRAMEPOINT_BOTTOMLEFT,
CustomRaceUI.descMain, FRAMEPOINT_TOPLEFT,
config.margin.RACE_LEFT_MARGIN, 0)
CustomRaceUI.selectMain = BlzCreateFrame(config.framename.selectMain, CustomRaceUI.main, 0, 0)
BlzFrameSetSize(CustomRaceUI.selectMain, config.SELECTION_WIDTH, config.SELECTION_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.selectMain)
BlzFrameSetPoint(CustomRaceUI.selectMain, FRAMEPOINT_BOTTOMLEFT,
CustomRaceUI.main, FRAMEPOINT_BOTTOMLEFT,
config.margin.LOWER_LEFT_MARGIN,
config.margin.LOWER_LOWER_MARGIN + config.margin.SELECTION_LOWER_MARGIN)
CustomRaceUI.sliderMain = BlzCreateFrameByType("SLIDER", "CustomRaceSlider",
CustomRaceUI.main, config.framename.sliderMain, 0)
BlzFrameSetSize(CustomRaceUI.sliderMain, config.SLIDER_WIDTH, config.SLIDER_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.sliderMain)
BlzFrameSetPoint(CustomRaceUI.sliderMain, FRAMEPOINT_LEFT,
CustomRaceUI.selectMain, FRAMEPOINT_RIGHT,
0.00, 0.00)
CustomRaceUI.buttonFrame = BlzCreateFrameByType("GLUETEXTBUTTON", "CustomRaceConfirmButton",
CustomRaceUI.main, config.framename.buttonFrame, 0)
BlzFrameSetSize(CustomRaceUI.buttonFrame, config.CONFIRM_WIDTH, config.CONFIRM_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.buttonFrame)
BlzFrameSetPoint(CustomRaceUI.buttonFrame, FRAMEPOINT_BOTTOMRIGHT,
CustomRaceUI.main, FRAMEPOINT_BOTTOMRIGHT,
-config.margin.LOWER_RIGHT_MARGIN, config.margin.LOWER_LOWER_MARGIN)
BlzFrameSetText(CustomRaceUI.buttonFrame, "Confirm Race")
end
function internal.prepareBackupFrames()
local world = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
CustomRaceUI.backupMain = BlzCreateFrame(config.framename.backupMain, world, 0, 0)
BlzFrameSetSize(CustomRaceUI.backupMain, config.BACKUP_WIDTH, config.BACKUP_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.backupMain)
BlzFrameSetAbsPoint(CustomRaceUI.backupMain, FRAMEPOINT_CENTER,
0.4, 0.4)
CustomRaceUI.waitText = BlzCreateFrameByType("TEXT", "CustomRaceBackupDisplayText",
CustomRaceUI.backupMain, "", 0)
BlzFrameSetSize(CustomRaceUI.waitText, config.WAITTEXT_WIDTH, config.WAITTEXT_HEIGHT)
BlzFrameSetScale(CustomRaceUI.waitText, config.scale.WAIT_TEXT)
BlzFrameSetPoint(CustomRaceUI.waitText, FRAMEPOINT_TOP,
CustomRaceUI.backupMain, FRAMEPOINT_TOP,
config.margin.WAIT_LEFT_MARGIN,
config.margin.WAIT_UPPER_MARGIN)
BlzFrameSetTextAlignment(CustomRaceUI.waitText, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
BlzFrameSetText(CustomRaceUI.waitText, "|cffffcc00Please wait|r")
CustomRaceUI.pCountText = BlzCreateFrameByType("TEXT", "CustomRaceBackupPlayerText",
CustomRaceUI.backupMain, "", 0)
BlzFrameSetSize(CustomRaceUI.pCountText, config.PCOUNT_WIDTH, config.PCOUNT_HEIGHT)
BlzFrameSetScale(CustomRaceUI.pCountText, config.scale.PCOUNT_TEXT)
BlzFrameSetPoint(CustomRaceUI.pCountText, FRAMEPOINT_TOP,
CustomRaceUI.waitText, FRAMEPOINT_BOTTOM,
config.margin.PCOUNT_LEFT_MARGIN,
config.margin.PCOUNT_UPPER_MARGIN)
BlzFrameSetVisible(CustomRaceUI.backupMain, false)
end
function internal.prepareDispFrame()
local world = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
CustomRaceUI.mainScreen = {
update = 1/60,
fadeout = 1.20,
timer = CreateTimer(),
startScale = 8.50,
endScale = 4.50,
startAlpha = 255,
endAlpha = 0,
}
CustomRaceUI.mainScreen.diffAlpha = CustomRaceUI.mainScreen.endAlpha - CustomRaceUI.mainScreen.startAlpha
CustomRaceUI.mainScreen.diffScale = CustomRaceUI.mainScreen.endScale - CustomRaceUI.mainScreen.startScale
CustomRaceUI.mainScreen.medScale = (CustomRaceUI.mainScreen.startScale + CustomRaceUI.mainScreen.endScale)/2
CustomRaceUI.mainScreen.textFrame = BlzCreateFrameByType("TEXT", "CustomRaceTextDisplay",
world, "", 0)
BlzFrameClearAllPoints(CustomRaceUI.mainScreen.textFrame)
BlzFrameSetSize(CustomRaceUI.mainScreen.textFrame, config.TEXTFRAME_WIDTH, config.TEXTFRAME_HEIGHT)
BlzFrameSetTextAlignment(CustomRaceUI.mainScreen.textFrame, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
BlzFrameSetAbsPoint(CustomRaceUI.mainScreen.textFrame, FRAMEPOINT_CENTER, 0.40, 0.35)
BlzFrameSetScale(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.medScale)
BlzFrameSetFocus(CustomRaceUI.mainScreen.textFrame, false)
BlzFrameSetEnable(CustomRaceUI.mainScreen.textFrame, false)
BlzFrameSetAlpha(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.endAlpha)
CustomRaceUI.mainScreen._ticksMax = CustomRaceUI.mainScreen.fadeout // CustomRaceUI.mainScreen.update
CustomRaceUI.mainScreen._ticks = 0
CustomRaceUI.mainScreen._frameArt = function()
CustomRaceUI.mainScreen._ticks = CustomRaceUI.mainScreen._ticks - 1
local curAlpha = CustomRaceUI.mainScreen._ticks/CustomRaceUI.mainScreen._ticksMax
curAlpha = curAlpha*CustomRaceUI.mainScreen.diffAlpha
curAlpha = (CustomRaceUI.mainScreen.endAlpha - curAlpha) // 1
local curScale = CustomRaceUI.mainScreen._ticks/CustomRaceUI.mainScreen._ticksMax
curScale = (curScale*CustomRaceUI.mainScreen.diffScale)
curScale = CustomRaceUI.mainScreen.endScale - curScale
BlzFrameSetAlpha(CustomRaceUI.mainScreen.textFrame, math.tointeger(curAlpha))
BlzFrameSetScale(CustomRaceUI.mainScreen.textFrame, curScale)
if CustomRaceUI.mainScreen._ticks <= 0 then
PauseTimer(CustomRaceUI.mainScreen.timer)
end
end
CustomRaceUI.mainScreen.display = function(...)
local tt = {...}
tt.str = ""
if #tt > 0 then
for i = 1, #tt do
tt.str = tt.str .. tostring(tt[i]) .. " "
end
end
BlzFrameSetText(CustomRaceUI.mainScreen.textFrame, tt.str)
BlzFrameSetAlpha(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.startAlpha)
BlzFrameSetScale(CustomRaceUI.mainScreen.textFrame, CustomRaceUI.mainScreen.startScale)
CustomRaceUI.mainScreen._ticks = CustomRaceUI.mainScreen._ticksMax
PauseTimer(CustomRaceUI.mainScreen.timer)
TimerStart(CustomRaceUI.mainScreen.timer, CustomRaceUI.mainScreen.update, true,
CustomRaceUI.mainScreen._frameArt)
end
end
function internal.prepareSubFrames()
CustomRaceUI.icon = BlzCreateFrameByType("BACKDROP", "", CustomRaceUI.iconMain, "", 0)
BlzFrameSetSize(CustomRaceUI.icon, config.ICON_DISP_WIDTH, config.ICON_DISP_HEIGHT)
BlzFrameClearAllPoints(CustomRaceUI.icon)
BlzFrameSetPoint(CustomRaceUI.icon, FRAMEPOINT_CENTER,
CustomRaceUI.iconMain, FRAMEPOINT_CENTER,
0, 0)
BlzFrameSetVisible(CustomRaceUI.icon, false)
CustomRaceUI.selectList = {}
CustomRaceUI.framePosData = {}
CustomRaceUI.playerData = {pointer={}}
for i = 1, config.RACE_MAX_OPTIONS do
local t = {}
t.main = BlzCreateFrameByType("GLUETEXTBUTTON", "CustomRaceSelectOption",
CustomRaceUI.selectMain, config.framename.listButtons,
0)
BlzFrameSetSize(t.main, config.SELECTION_WIDTH,
config.SELECTION_HEIGHT/config.RACE_MAX_OPTIONS)
if i ~= 1 then
BlzFrameSetPoint(t.main, FRAMEPOINT_TOP,
CustomRaceUI.selectList[i-1].main, FRAMEPOINT_BOTTOM,
0, 0)
else
BlzFrameSetPoint(t.main, FRAMEPOINT_TOP,
CustomRaceUI.selectMain, FRAMEPOINT_TOP,
0, 0)
end
t.nameframe = BlzCreateFrameByType("TEXT", "", t.main, "", 0)
t.highlight = BlzCreateFrameByType("HIGHLIGHT", "CustomRaceSelectLight", t.main,
config.framename.listHighlight, i)
BlzFrameSetEnable(t.highlight, false)
BlzFrameSetFocus(t.highlight, false)
BlzFrameSetEnable(t.nameframe, false)
BlzFrameSetFocus(t.nameframe, false)
BlzFrameSetSize(t.nameframe,
config.SELECTION_TEXT_WIDTH/config.scale.SELECTION_TEXT,
config.SELECTION_TEXT_HEIGHT/config.scale.SELECTION_TEXT)
BlzFrameSetScale(t.nameframe, config.scale.SELECTION_TEXT)
BlzFrameClearAllPoints(t.nameframe)
BlzFrameSetPoint(t.nameframe, FRAMEPOINT_LEFT,
t.main, FRAMEPOINT_LEFT,
config.margin.SELECTION_TEXT_LEFT_MARGIN, 0)
BlzFrameClearAllPoints(t.highlight)
BlzFrameSetAllPoints(t.highlight, t.main)
BlzFrameSetVisible(t.highlight, false)
-- Debug text
CustomRaceUI.selectList[i] = t
CustomRaceUI.framePosData[t.main] = i
end
BlzFrameSetVisible(CustomRaceUI.main, false)
end
function internal.addButtonAudioFeedback()
local trig = CreateTrigger()
TriggerAddCondition(trig, Condition(function()
local player = GetTriggerPlayer()
BlzFrameSetEnable(CustomRaceUI.buttonFrame, false)
BlzFrameSetEnable(CustomRaceUI.buttonFrame, true)
internal.playSound(GetTriggerPlayer(), internal.generateBigClick())
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
if (not data) or
(data.selectPos == 0) then
return
end
-- Feed back the data
CustomRace.faction[data.playerID] = data.selectPos
CustomRaceUI.display(player, false)
CustomRaceUI.removeSelectingPlayer(player)
end))
BlzTriggerRegisterFrameEvent(trig, CustomRaceUI.buttonFrame, FRAMEEVENT_CONTROL_CLICK)
local trig = CreateTrigger()
TriggerAddCondition(trig, Condition(function()
local frame = BlzGetTriggerFrame()
local player = GetTriggerPlayer()
BlzFrameSetEnable(frame, false)
BlzFrameSetEnable(frame, true)
internal.playSound(player, internal.generateSmallClick())
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
local pos = CustomRaceUI.framePosData[frame]
if not data then
return
end
if (data.selectPos ~= data.hoverOffset + pos) then
local pos2 = data.selectPos - data.hoverOffset
if (pos2 > 0) and (pos2 <= config.RACE_MAX_OPTIONS) and (GetLocalPlayer() == player) then
BlzFrameSetVisible(CustomRaceUI.selectList[pos2].highlight, false)
end
if GetLocalPlayer() == player then
BlzFrameSetVisible(CustomRaceUI.selectList[pos].highlight, true)
end
data.selectPos = data.hoverOffset + pos
else
if GetLocalPlayer() == player then
BlzFrameSetVisible(CustomRaceUI.selectList[pos].highlight, false)
end
data.selectPos = 0
end
CustomRaceUI.updateSelection(player, true)
end))
for i = 1, config.RACE_MAX_OPTIONS do
BlzTriggerRegisterFrameEvent(trig, CustomRaceUI.selectList[i].main, FRAMEEVENT_CONTROL_CLICK)
end
end
function internal.addSelectionProcess()
local trig = {CreateTrigger(), CreateTrigger()}
TriggerAddCondition(trig[1], Condition(function()
local frame = BlzGetTriggerFrame()
local player = GetTriggerPlayer()
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
local pos = CustomRaceUI.framePosData[frame]
if (not data) then
return
end
-- When a frame is selected, the info will
-- be locked to displaying the details of
-- said frame.
if data.selectPos ~= 0 then
return
end
if data.hoverPos - data.hoverOffset ~= pos then
data.hoverPos = data.hoverOffset + pos
CustomRaceUI.updateSelection(player, false)
end
end))
TriggerAddCondition(trig[2], Condition(function()
local frame = BlzGetTriggerFrame()
local player = GetTriggerPlayer()
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
local pos = CustomRaceUI.framePosData[frame]
if (not data) or
(GetLocalPlayer() ~= player) then
return
end
if data.selectPos ~= 0 then
return
end
if data.hoverPos - data.hoverOffset == pos then
data.hoverPos = 0
CustomRaceUI.updateSelection(player, false)
end
end))
for i = 1, config.RACE_MAX_OPTIONS do
BlzTriggerRegisterFrameEvent(trig[1], CustomRaceUI.selectList[i].main, FRAMEEVENT_MOUSE_ENTER)
BlzTriggerRegisterFrameEvent(trig[2], CustomRaceUI.selectList[i].main, FRAMEEVENT_MOUSE_LEAVE)
end
end
function internal.addScrollCallback()
local trig = {CreateTrigger(), CreateTrigger()}
-- This trigger is for taking note of changed values
TriggerAddCondition(trig[1], Condition(function()
local player = GetTriggerPlayer()
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
local value = (BlzGetTriggerFrameValue() + 0.5)//1
if (not data) then
return
end
data.hoverPos = data.hoverPos - data.hoverOffset
data.hoverOffset = data.hoverMax - value
data.hoverPos = data.hoverPos + data.hoverOffset
if data.selectPos ~= 0 then
return
end
CustomRaceUI.updateHoverList(player)
CustomRaceUI.updateSelection(player, false)
end))
TriggerAddCondition(trig[2], Condition(function()
local player = GetTriggerPlayer()
local value = BlzGetTriggerFrameValue()
local incr = 0
if value > 0 then
incr = 1
else
incr = -1
end
if GetLocalPlayer() == player then
BlzFrameSetValue(CustomRaceUI.sliderMain, BlzFrameGetValue(CustomRaceUI.sliderMain) + incr)
end
end))
BlzTriggerRegisterFrameEvent(trig[1], CustomRaceUI.sliderMain, FRAMEEVENT_SLIDER_VALUE_CHANGED)
BlzTriggerRegisterFrameEvent(trig[2], CustomRaceUI.sliderMain, FRAMEEVENT_MOUSE_WHEEL)
end
function CustomRaceUI.updateSelection(player, useSelectedFrame)
local id = CustomRaceUI.playerData.pointer[player]
if not id then
return
end
local data = CustomRaceUI.playerData[id]
local factionID
if useSelectedFrame then
factionID = (data.selectPos ~= 0 and data.selectPos) or data.hoverPos
else
factionID = data.hoverPos
end
-- If factionID is 0, the user didn't select any race.
if factionID == 0 then
if GetLocalPlayer() == player then
BlzFrameSetText(CustomRaceUI.descMain, "")
BlzFrameSetText(CustomRaceUI.raceMain, "")
BlzFrameSetTexture(CustomRaceUI.icon, "", 0, true)
if BlzFrameIsVisible(CustomRaceUI.icon) then
BlzFrameSetVisible(CustomRaceUI.icon, false)
end
end
else
local faction = tb._container[data.playerRace][factionID]
local texture = faction.racePic or "ReplaceableTextures\\CommandButtons\\BTNTemp.blp"
if GetLocalPlayer() == player then
BlzFrameSetText(CustomRaceUI.descMain, faction.description or "")
BlzFrameSetText(CustomRaceUI.raceMain, "|cffffcc00" .. (faction.name or "") .. "|r")
BlzFrameSetTexture(CustomRaceUI.icon, texture, 0, true)
if texture == "" then
if BlzFrameIsVisible(CustomRaceUI.icon) then
BlzFrameSetVisible(CustomRaceUI.icon, false)
end
else
if not BlzFrameIsVisible(CustomRaceUI.icon) then
BlzFrameSetVisible(CustomRaceUI.icon, true)
end
end
end
end
end
function CustomRaceUI.updateHoverList(player)
local id = CustomRaceUI.playerData.pointer[player]
if not id then
return
end
local data = CustomRaceUI.playerData[id]
local j = 1
for i = 1, config.RACE_MAX_OPTIONS do
if i + data.hoverOffset > data.factionChoices then
break
end
local t = CustomRaceUI.selectList[i]
j = j + 1
local faction = tb._container[data.playerRace][i + data.hoverOffset]
if GetLocalPlayer() == player then
if not BlzFrameIsVisible(t.main) then
BlzFrameSetVisible(t.main, true)
end
BlzFrameSetText(t.nameframe, "|cffffcc00" .. (faction.name or "") .. "|r")
end
end
if j > config.RACE_MAX_OPTIONS then
return
end
while j <= config.RACE_MAX_OPTIONS do
local t = CustomRaceUI.selectList[j]
if GetLocalPlayer() == player then
BlzFrameSetText(t.nameframe, "")
if BlzFrameIsVisible(t.main) then
BlzFrameSetVisible(t.main, false)
end
end
j = j + 1
end
end
function CustomRaceUI.defSliderBounds(player)
local id = CustomRaceUI.playerData.pointer[player]
if not id then
return
end
local data = CustomRaceUI.playerData[id]
local j = 1
if data.factionChoices <= config.RACE_MAX_OPTIONS then
if GetLocalPlayer() == player then
BlzFrameSetVisible(CustomRaceUI.sliderMain, false)
end
else
if GetLocalPlayer() == player then
BlzFrameSetMinMaxValue(CustomRaceUI.sliderMain, 0, data.hoverMax)
BlzFrameSetStepSize(CustomRaceUI.sliderMain, 1)
BlzFrameSetValue(CustomRaceUI.sliderMain, data.hoverMax)
end
end
end
-- This just tells the players how many players
-- are still selecting their factions.
function CustomRaceUI.updatePlayerCountText()
BlzFrameSetText(CustomRaceUI.pCountText,
"|cffffcc00Number of players:|r " ..
tostring(CustomRaceUI.selectionCount) .. "/" ..
tostring(CustomRaceUI.selectionTotal))
end
function CustomRaceUI.removeSelectingPlayer(player)
local pos = 1
while pos <= #CustomRaceUI.playerSelectData do
if CustomRaceUI.playerSelectData[pos] == player then
break
end
pos = pos + 1
end
if pos > #CustomRaceUI.playerSelectData then
return
end
table.remove(CustomRaceUI.playerSelectData, pos)
CustomRaceUI.selectionCount = CustomRaceUI.selectionCount - 1
CustomRaceUI.updatePlayerCountText()
if CustomRaceUI.selectionCount <= 0 then
CustomRaceUI.inSelectionStage = false
CustomRaceUI.clearSelectionTrigger()
CustomRace.start()
BlzFrameSetVisible(CustomRaceUI.main, false)
BlzFrameSetVisible(CustomRaceUI.backupMain, false)
if CustomRaceUI.criticalTimer then
PauseTimer(CustomRaceUI.criticalTimer)
DestroyTimer(CustomRaceUI.criticalTimer)
CustomRaceUI.criticalTimer = nil
end
elseif GetLocalPlayer() == player then
BlzFrameSetVisible(CustomRaceUI.backupMain, true)
end
end
function CustomRaceUI.clearSelectionTrigger()
if not CustomRaceUI.playerSelectTrig then
return
end
DisableTrigger(CustomRaceUI.playerSelectTrig)
DestroyTrigger(CustomRaceUI.playerSelectTrig)
CustomRaceUI.playerSelectTrig = nil
end
function CustomRaceUI.startSelectTimer()
CustomRaceUI.selectTimer = CreateTimer()
CustomRaceUI.criticalTimer = CreateTimer()
TimerStart(CustomRaceUI.selectTimer, config.WAIT_DURATION, false, function()
while CustomRaceUI.inSelectionStage do
local player = CustomRaceUI.playerSelectData[CustomRaceUI.selectionCount]
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
CustomRace.faction[data.playerID] = 1
CustomRaceUI.removeSelectingPlayer(player)
end
DestroyTimer(CustomRaceUI.selectTimer)
CustomRaceUI.selectTimer = nil
end)
TimerStart(CustomRaceUI.criticalTimer, config.WAIT_DURATION - config.CRITICAL_DURATION, false, function()
internal.playSound(GetLocalPlayer(), internal.generateCriticalSound())
DestroyTimer(CustomRaceUI.criticalTimer)
CustomRaceUI.criticalTimer = nil
end)
end
function CustomRaceUI.checkFactionSelection()
CustomRaceUI.inSelectionStage = CustomRaceUI.selectionCount > 0
CustomRaceUI.selectionTotal = CustomRaceUI.selectionCount
CustomRaceUI.updatePlayerCountText()
if not CustomRaceUI.inSelectionStage then
CustomRaceUI.display(GetLocalPlayer(), false)
CustomRace.start()
return
end
local trig = CreateTrigger()
CustomRaceUI.playerSelectData = {}
CustomRaceUI.playerSelectTrig = trig
CustomRaceUI.startSelectTimer()
TriggerAddCondition(trig, Condition(function()
local player = GetTriggerPlayer()
if CustomRaceUI.inSelectionStage then
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 20.00,
GetPlayerName(player) .. " has abandoned the game")
end
CustomRaceUI.removeSelectingPlayer(player)
end))
for i = 1, #CustomRaceUI.playerData do
local player = CustomRaceUI.playerData[i].player
CustomRaceUI.display(player, true)
if GetPlayerController(player) == MAP_CONTROL_USER then
table.insert(CustomRaceUI.playerSelectData, player)
CustomRaceUI.updateHoverList(player)
CustomRaceUI.updateSelection(player, false)
TriggerRegisterPlayerEvent(trig, player, EVENT_PLAYER_LEAVE)
end
end
end
function CustomRaceUI.processPlayer(player)
local id = CustomRaceUI.playerData.pointer[player]
local data = CustomRaceUI.playerData[id]
if (not data) then
if (GetLocalPlayer() == player) then
BlzFrameSetVisible(CustomRaceUI.backupMain, true)
end
return
end
if GetPlayerController(player) == MAP_CONTROL_USER then
-- Computer Players will always select default race.
CustomRaceUI.selectionCount = CustomRaceUI.selectionCount + 1
end
end
function CustomRaceUI.feedPlayerData(id, player)
local race = GetPlayerRace(player)
if (#tb._container[race] <= 1) or
(GetPlayerController(player) == MAP_CONTROL_COMPUTER) then
CustomRace.faction[id] = 1
return
end
local data = {
player = player,
playerID = id,
playerRace = race,
factionChoices = 0,
hoverOffset = 0,
hoverPos = 0,
selectPos = 0,
}
data.factionChoices = #tb._container[data.playerRace]
data.hoverMax = math.max(data.factionChoices - config.RACE_MAX_OPTIONS, 0)
CustomRaceUI.playerData[#CustomRaceUI.playerData + 1] = data
CustomRaceUI.playerData.pointer[player] = #CustomRaceUI.playerData
CustomRaceUI.defSliderBounds(player)
end
function CustomRaceUI.display(player, flag)
if GetLocalPlayer() == player then
BlzFrameSetVisible(CustomRaceUI.main, flag)
end
end
function CustomRaceUI.init()
internal.loadTOC()
internal.prepareAllFrames()
internal.prepareBackupFrames()
internal.prepareDispFrame()
internal.prepareSubFrames()
internal.addButtonAudioFeedback()
internal.addSelectionProcess()
internal.addScrollCallback()
CustomRaceUI.selectionCount = 0
end
end
do
local tb = getmetatable(CustomRaceSystem)
local internal = {}
local teams = {
allies = {},
enemies = {}
}
local buildingGroup = {}
local triggers = {}
CustomRaceConditions = setmetatable({}, tb)
function internal.initGlobalVars(index, player)
bj_meleeDefeated[index] = false
bj_meleeVictoried[index] = false
-- Create a timer and timer window in case the player is crippled.
bj_playerIsCrippled[index] = false
bj_playerIsExposed[index] = false
bj_crippledTimer[index] = CreateTimer()
bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(player))
end
local victorEnemyCount = 0
function internal.checkVictorsEnum2()
victorEnemyCount = victorEnemyCount + 1
end
function internal.checkVictorsEnum()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
ForForce(teams.enemies[id], internal.checkVictorsEnum2)
end
function internal.checkVictors()
if bj_meleeGameOver then
return
end
victorEnemyCount = 0
ForForce(teams.players, internal.checkVictorsEnum)
if victorEnemyCount > 0 then
return
end
-- Award victory to all team players
-- In this case, use a closure instead.
ForForce(teams.players, function()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
if bj_meleeGameOver then
return
elseif bj_meleeVictoried[id] then
return
end
bj_meleeVictoried[id] = true
internal.exposePlayerTeam(player, false)
PauseTimer(bj_crippledTimer[id])
if GetLocalPlayer() == player then
TimerDialogDisplay(bj_crippledTimerWindows[id], false)
end
CachePlayerHeroData(player)
RemovePlayerPreserveUnitsBJ(player, PLAYER_GAME_RESULT_VICTORY, false)
end)
bj_meleeGameOver = true
end
-- Obtaining structure count and key structure count.
local structCount = {}
function internal.getKeyStructureCountGroup(player, id)
id = id or GetPlayerId(player)
local size = BlzGroupGetSize(buildingGroup[id]) - 1
for i = 0, size do
local unit = BlzGroupUnitAt(buildingGroup[id], i)
if tb._hallptr[GetUnitTypeId(unit)] ~= nil then
structCount.keyCount = structCount.keyCount + 1
end
end
end
function internal.getKeyStructureCountEnum()
internal.getKeyStructureCountGroup(GetEnumPlayer())
end
function internal.getKeyStructureCount(excludeAllies, player, id)
id = id or GetPlayerId(player)
structCount.keyCount = 0
internal.getKeyStructureCountGroup(player, id)
if (not excludeAllies) then
ForForce(teams.allies[id], internal.getKeyStructureCountEnum)
end
return structCount.keyCount
end
function internal.getStructureCountEnum()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
structCount.count = structCount.count + BlzGroupGetSize(buildingGroup[id])
end
function internal.getStructureCount(excludeAllies, player, id)
id = id or GetPlayerId(player)
structCount.count = BlzGroupGetSize(buildingGroup[id])
if (not excludeAllies) then
ForForce(teams.allies[id], internal.getStructureCountEnum)
end
return structCount.count
end
function internal.getForceSizeEx()
structCount.forceCount = structCount.forceCount + 1
end
function internal.getForceSize(force)
structCount.forceCount = 0
ForForce(force, internal.getForceSizeEx)
return structCount.forceCount
end
-- This section handles exposure of bases.
local exposeTable = {}
function internal.exposePlayerTeamEnum()
ForceAddPlayer(exposeTable.allyforce, GetEnumPlayer())
end
function internal.exposePlayerTeamEx()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
CripplePlayer(player, exposeTable.force, exposeTable.flag)
if exposeTable.updateFlag then
bj_playerIsExposed[id] = exposeTable.flag
end
end
function internal.exposePlayerTeam(player, expose)
local id = GetPlayerId(player)
exposeTable.force = CreateForce()
-- NOTE: The player by default is not included among
-- the list of allies, so this behavior should be
-- taken advantage of.
-- Temporarily add the player to the list of allies
ForceAddPlayer(teams.allies[id], player)
exposeTable.flag = false
exposeTable.updateFlag = false
ForForce(teams.allies[id], internal.exposePlayerTeamEx)
DestroyForce(exposeTable.force)
exposeTable.flag = expose
exposeTable.updateFlag = true
exposeTable.force = teams.enemies[id]
ForForce(teams.allies[id], internal.exposePlayerTeamEx)
ForceRemovePlayer(teams.allies[id], player)
end
-- This section handles exposure of crippling mechanics.
local crippleTable = {
flag = false,
timerData = {},
active = {}
}
function internal.crippleTeamHideWindows()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
crippleTable.active[bj_crippledTimer[id]] = nil
if GetLocalPlayer() == player then
TimerDialogDisplay(bj_crippledTimerWindows[id], false)
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
MeleeGetCrippledWarningMessage(player))
end
end
function internal.crippleTeamCallback()
local player = crippleTable.timerData[GetExpiredTimer()]
local id = GetPlayerId(player)
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
MeleeGetCrippledRevealedMessage(player))
if GetLocalPlayer() == player then
TimerDialogDisplay(bj_crippledTimerWindows[id], false)
end
if crippleTable.active[bj_crippledTimer[id]] then
internal.exposePlayerTeam(player, true)
ForForce(teams.allies, internal.crippleTeamHideWindows)
end
crippleTable.active[bj_crippledTimer[id]] = nil
end
function internal.cripplePlayerTeamDummy()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
bj_playerIsCrippled[id] = crippleTable.flag
crippleTable.timerData[bj_crippledTimer[id]] = crippleTable.timerData[bj_crippledTimer[id]] or
player
if crippleTable.flag then
crippleTable.active[bj_crippledTimer[id]] = false
TimerStart(bj_crippledTimer[id], bj_MELEE_CRIPPLE_TIMEOUT, false, internal.crippleTeamCallback)
if GetLocalPlayer() == player then
TimerDialogDisplay(bj_crippledTimerWindows[id], true)
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
MeleeGetCrippledWarningMessage(player))
end
return
end
crippleTable.active[bj_crippledTimer[id]] = nil
PauseTimer(bj_crippledTimer[id])
if GetLocalPlayer() ~= player then
return
end
if IsTimerDialogDisplayed(bj_crippledTimerWindows[id]) then
TimerDialogDisplay(bj_crippledTimerWindows[id], false)
end
if (bj_playerIsExposed[id]) then
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
GetLocalizedString("CRIPPLE_UNREVEALED"))
else
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
GetLocalizedString("CRIPPLE_UNCRIPPLED"))
end
end
function internal.cripplePlayerTeam(player, cripple)
local id = GetPlayerId(player)
if bj_playerIsCrippled[id] == cripple then
-- Player's cripple state is the same as the flag.
-- Ignore
return
end
crippleTable.flag = cripple
bj_playerIsCrippled[id] = cripple
if cripple then
-- Map the active timer id of each
crippleTable.timerData[bj_crippledTimer[id]] = crippleTable.timerData[bj_crippledTimer[id]] or
player
crippleTable.active[bj_crippledTimer[id]] = true
TimerStart(bj_crippledTimer[id], bj_MELEE_CRIPPLE_TIMEOUT, false, internal.crippleTeamCallback)
if GetLocalPlayer() == player then
TimerDialogDisplay(bj_crippledTimerWindows[id], true)
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
MeleeGetCrippledWarningMessage(player))
end
ForForce(teams.allies[id], internal.cripplePlayerTeamDummy)
return
end
player = crippleTable.timerData[bj_crippledTimer[id]]
id = GetPlayerId(player)
crippleTable.active[bj_crippledTimer[id]] = nil
PauseTimer(bj_crippledTimer[id])
if GetLocalPlayer() == player then
TimerDialogDisplay(bj_crippledTimerWindows[id], false)
if (bj_playerIsExposed[id]) then
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
GetLocalizedString("CRIPPLE_UNREVEALED"))
else
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION,
GetLocalizedString("CRIPPLE_UNCRIPPLED"))
end
end
ForForce(teams.allies[id], internal.cripplePlayerTeamDummy)
internal.exposePlayerTeam(player, false)
end
-- Handle Defeat mechanics
function internal.defeatPlayer(player, leftGame)
leftGame = leftGame == true
local id = GetPlayerId(player)
local flag = false
if bj_meleeVictoried[id] or
bj_meleeDefeated[id] then
return
end
bj_meleeDefeated[id] = true
bj_playerIsCrippled[id] = false
bj_playerIsExposed[id] = false
crippleTable.timerData[bj_crippledTimer[id]] = nil
crippleTable.active[bj_crippledTimer[id]] = nil
PauseTimer(bj_crippledTimer[id])
DestroyTimer(bj_crippledTimer[id])
DestroyTimerDialog(bj_crippledTimerWindows[id])
ForForce(teams.enemies[id], function()
local player2 = GetEnumPlayer()
local jd = GetPlayerId(player2)
ForceRemovePlayer(teams.enemies[jd], player)
end)
ForForce(teams.allies[id], function()
local player2 = GetEnumPlayer()
local jd = GetPlayerId(player2)
ForceRemovePlayer(teams.allies[jd], player)
if flag then
crippleTable.active[bj_crippledTimer[id]] = true
end
end)
DestroyForce(teams.enemies[id])
DestroyForce(teams.allies[id])
teams.enemies[id] = nil
teams.allies[id] = nil
ForceRemovePlayer(teams.players, player)
ForceAddPlayer(teams.loser, player)
CachePlayerHeroData(player)
RemovePlayerPreserveUnitsBJ(player, PLAYER_GAME_RESULT_DEFEAT, leftGame)
end
function internal.defeatPlayerTeam(player, leftGame)
local id = GetPlayerId(player)
-- Since this function is going to be called only a few times
-- (at most 23 times), a closure is used instead.
ForForce(teams.allies[id], function()
local player2 = GetEnumPlayer()
internal.defeatPlayer(player2, leftGame)
end)
internal.defeatPlayer(player, false)
end
function internal.neutralizePlayer(player)
local group = CreateGroup()
GroupEnumUnitsOfPlayer(group, player, nil)
ForGroup(group, MakeUnitsPassiveForPlayerEnum)
DestroyGroup(group)
end
function internal.neutralizePlayerTeam(player)
local id = GetPlayerId(player)
internal.neutralizePlayer(player)
ForForce(teams.allies[id], function()
internal.neutralizePlayer(GetEnumPlayer())
end)
end
function internal.shareControl(player)
local id = GetPlayerId(player)
ForForce(teams.allies[id], function()
local player2 = GetEnumPlayer()
SetPlayerAlliance(player, player2, ALLIANCE_SHARED_VISION, true)
SetPlayerAlliance(player, player2, ALLIANCE_SHARED_CONTROL, true)
SetPlayerAlliance(player2, player, ALLIANCE_SHARED_CONTROL, true)
SetPlayerAlliance(player, player2, ALLIANCE_SHARED_ADVANCED_CONTROL, true)
end)
end
function internal.checkTeamStatus(player)
local teamCount = internal.getStructureCount(false, player)
local teamKeyCount = internal.getKeyStructureCount(false, player)
if (teamKeyCount > 0) then
-- A town hall might have been reconstructed.
internal.cripplePlayerTeam(player, false)
return
end
if teamCount > 0 then
internal.cripplePlayerTeam(player, true)
return
end
internal.neutralizePlayerTeam(player)
internal.defeatPlayerTeam(player)
internal.checkVictors()
end
-- Update structure count
function internal.removeStructure(unit, player, id)
id = id or GetPlayerId(player)
GroupRemoveUnit(buildingGroup[id], unit)
internal.checkTeamStatus(player)
end
function internal.addStructure(unit, player, id)
id = id or GetPlayerId(player)
GroupAddUnit(buildingGroup[id], unit)
internal.checkTeamStatus(player)
end
-- Unit Trigger callbacks
function internal.registerDeath(player)
if not triggers.death then
triggers.death = CreateTrigger()
TriggerAddAction(triggers.death, function()
local unit = GetTriggerUnit()
local player = GetOwningPlayer(unit)
local id = GetPlayerId(player)
if not IsUnitInGroup(unit, buildingGroup[id]) then
return
end
internal.removeStructure(unit, player, id)
end)
end
TriggerRegisterPlayerUnitEvent(triggers.death, player, EVENT_PLAYER_UNIT_DEATH)
end
function internal.registerConstruct(player)
if not triggers.construct then
triggers.construct = CreateTrigger()
TriggerAddAction(triggers.construct, function()
local unit = GetConstructingStructure()
local player = GetOwningPlayer(unit)
internal.addStructure(unit, player, id)
end)
end
TriggerRegisterPlayerUnitEvent(triggers.construct, player, EVENT_PLAYER_UNIT_CONSTRUCT_START)
end
function internal.registerChangeOwner(player)
if not triggers.ownership then
triggers.ownership = CreateTrigger()
TriggerAddAction(triggers.ownership, function()
local unit = GetTriggerUnit()
local player = GetOwningPlayer(unit)
local prevPlayer = GetChangingUnitPrevOwner()
local prevID = GetPlayerId(prevPlayer)
if not IsUnitInGroup(unit, buildingGroup[prevID]) then
return
end
internal.removeStructure(unit, prevPlayer, prevID)
internal.addStructure(unit, player)
end)
end
TriggerRegisterPlayerUnitEvent(triggers.ownership, player, EVENT_PLAYER_UNIT_CHANGE_OWNER)
end
-- Player trigger callbacks
function internal.registerLeave(player)
if not triggers.leave then
triggers.leave = CreateTrigger()
TriggerAddAction(triggers.leave, function()
local player = GetTriggerPlayer()
local teamCount = internal.getStructureCount(false, player)
if teamCount > 0 then
internal.shareControl(player)
internal.defeatPlayer(player, true)
internal.checkVictors()
return
end
internal.defeatPlayerTeam(player)
internal.checkVictors()
end)
end
TriggerRegisterPlayerEvent(triggers.leave, player, EVENT_PLAYER_LEAVE)
end
function internal.registerAllianceChange(player)
if not triggers.allyChange then
triggers.allyChange = CreateTrigger()
TriggerAddAction(triggers.allyChange, function()
local player = GetTriggerPlayer()
local id = GetPlayerId(player)
ForForce(teams.allies[id], function()
local player2 = GetEnumPlayer()
local jd = GetPlayerId(player2)
if not IsPlayerAlly(player, player2) then
ForceAddPlayer(teams.enemies[id], player2)
ForceRemovePlayer(teams.allies[id], player2)
end
end)
ForForce(teams.enemies[id], function()
local player2 = GetEnumPlayer()
local jd = GetPlayerId(player2)
if not IsPlayerEnemy(player, player2) then
ForceAddPlayer(teams.allies[id], player2)
ForceRemovePlayer(teams.enemies[id], player2)
end
end)
end)
end
TriggerRegisterPlayerAllianceChange(triggers.allyChange, player, ALLIANCE_PASSIVE)
end
function internal.registerAlliedVictory(player)
if not triggers.allyVictory then
triggers.allyVictory = CreateTrigger()
TriggerAddAction(triggers.allyVictory, function()
DisableTrigger(triggers.allyVictory)
EnableTrigger(triggers.allyVictory)
end)
end
end
-- Observer callbacks
function internal.registerObserverLeave(player)
if not triggers.observerLeave then
triggers.observerLeave = CreateTrigger()
TriggerAddAction(triggers.observerLeave, function()
RemovePlayerPreserveUnitsBJ(GetTriggerPlayer(), PLAYER_GAME_RESULT_NEUTRAL, false)
end)
end
TriggerRegisterPlayerEvent(triggers.observerLeave, player, EVENT_PLAYER_LEAVE)
end
function internal.initTeams()
teams.victor = CreateForce()
teams.loser = CreateForce()
teams.players = CreateForce()
for id = 0, bj_MAX_PLAYERS - 1 do
local player = Player(id)
if GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
teams.allies[id] = CreateForce()
teams.enemies[id] = CreateForce()
buildingGroup[id] = CreateGroup()
ForceAddPlayer(teams.players, player)
elseif IsPlayerObserver(player) then
internal.registerObserverLeave(player)
end
end
end
function internal.enumPreplacedStructures()
local rect = GetWorldBounds()
local grp = CreateGroup()
GroupEnumUnitsInRect(grp, rect, nil)
ForGroup(grp, function()
local unit = GetEnumUnit()
local player = GetOwningPlayer(unit)
local id = GetPlayerId(player)
if (not IsUnitType(unit, UNIT_TYPE_STRUCTURE)) or
(id >= bj_MAX_PLAYERS) then
return
end
-- Bypass checking directly
GroupAddUnit(buildingGroup[id], unit)
end)
ForForce(teams.players, function()
internal.checkTeamStatus(GetEnumPlayer())
end)
DestroyGroup(grp)
RemoveRect(rect)
end
function internal.initTriggers()
local tempForce = CreateForce()
ForForce(teams.players, function()
ForceAddPlayer(tempForce, GetEnumPlayer())
end)
ForForce(teams.players, function()
local player = GetEnumPlayer()
local id = GetPlayerId(player)
ForForce(tempForce, function()
local player2 = GetEnumPlayer()
local jd = GetPlayerId(player)
if player == player2 then
return
end
if IsPlayerAlly(player2, player) then
ForceAddPlayer(teams.allies[id], player2)
end
if IsPlayerEnemy(player2, player) then
ForceAddPlayer(teams.enemies[id], player2)
end
end)
internal.initGlobalVars(id, player)
internal.registerDeath(player)
internal.registerConstruct(player)
internal.registerChangeOwner(player)
internal.registerLeave(player)
internal.registerAllianceChange(player)
internal.registerAlliedVictory(player)
end)
DestroyForce(tempForce)
internal.enumPreplacedStructures()
TimerStart(CreateTimer(), 2.00, false, function()
DestroyTimer(GetExpiredTimer())
ForForce(teams.players, function()
internal.checkTeamStatus(GetEnumPlayer())
end)
end)
end
-- MeleeInitVictoryDefeat
function CustomRaceConditions.init()
internal.initTeams()
internal.initTriggers()
end
CustomRaceConditions.checkVictors = internal.checkVictors
end
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
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
do
local tb = {}
CustomRaceGUI = tb
function CustomRaceGUI.cleanVars()
udg_CustomRace_Race = nil
udg_CustomRace_Name = ""
udg_CustomRace_WorkerCount = -1
for k, v in pairs(udg_CustomRace_HeroID) do
udg_CustomRace_HeroID[k] = 0
end
for k, v in pairs(udg_CustomRace_HallID) do
udg_CustomRace_HeroID[k] = 0
end
udg_CustomRace_WorkerID = 0
udg_CustomRace_MainHallID = 0
udg_CustomRace_RacePic = ""
udg_CustomRace_Description = ""
udg_CustomRace_Playlist = ""
udg_CustomRace_PLDScript = ""
udg_CustomRace_AIScript = ""
udg_CustomRace_SetupTrig = nil
udg_CustomRace_SetupPlayer = nil
udg_CustomRace_SetupHall = nil
udg_CustomRace_SetupMine = nil
end
function CustomRaceGUI.doSetup()
local faction = CustomRaceSystem.create(udg_CustomRace_Race, udg_CustomRace_Name)
udg_CustomRace_WorkerCount = ((udg_CustomRace_WorkerCount <= 0) and 5) or udg_CustomRace_WorkerCount
faction.setupTrigger = udg_CustomRace_SetupTrig
CustomRaceGUI.lastFaction = faction
faction:addHero(table.unpack(udg_CustomRace_HeroID))
faction:addHall(table.unpack(udg_CustomRace_HallID))
faction:defRacePic(udg_CustomRace_RacePic)
faction:defDescription(udg_CustomRace_Description or "Nondescript")
faction:defPlaylist(udg_CustomRace_Playlist)
faction:defSetup(CustomRaceSetup.createSetup(
CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine)
udg_CustomRace_SetupPlayer = whichPlayer
udg_CustomRace_SetupHall = hall
udg_CustomRace_SetupMine = mine
TriggerEvaluate(faction.setupTrigger)
TriggerExecute(faction.setupTrigger)
end, udg_CustomRace_MainHallID, udg_CustomRace_WorkerID, udg_CustomRace_WorkerCount),
udg_CustomRace_PLDScript or "", faction))
faction:defAISetup(function(whichPlayer)
PickMeleeAI(whichPlayer, udg_CustomRace_AIScript, "", "")
end)
CustomRaceGUI.cleanVars()
end
end
end
do
local elvens = CustomRaceSystem.create(RACE_HUMAN, "High Elves")
elvens:defDescription("They are former Night Elves who have kept their affinity to magic.")
elvens:addHall('h000')
elvens:defRacePic("war3mapImported\\nightelfseal.blp")
elvens:defSetup(CustomRaceSetup.createSetup(
CustomRaceSetup.createSetupHelper(function(whichPlayer, hall, mine, peonX, peonY)
end, 'h000', 'n000', 5),
"", elvens))
elvens:defAISetup(function(whichPlayer)
end)
end
do
local tb = {
ABIL_ID = FourCC("A002"),
ARMOR_ID = FourCC("A003"),
CONFIG = {
DURATION = {5},
FLAT_REDUCTION = {1},
},
}
tb.unitdata = {}
DesolationBeam = tb
function tb:__index(k)
return tb[k]
end
function tb:__newindex(k, v)
if tb[k] then
return
end
rawset(self, k, v)
end
function tb.create(targ, level)
local o = {}
o.target = targ
o.level = level
o.stack = 0
o.timer = CreateTimer()
o.callback = function()
PauseTimer(o.timer)
DestroyTimer(o.timer)
UnitRemoveAbility(o.target, tb.ARMOR_ID)
tb.unitdata[o.target] = nil
setmetatable(o, nil)
end
setmetatable(o, tb)
UnitAddAbility(targ, tb.ARMOR_ID)
UnitMakeAbilityPermanent(targ, true, tb.ARMOR_ID)
return o
end
function tb:apply(level)
self.level = level
self.stack = self.stack + tb.CONFIG.FLAT_REDUCTION[level]
local abil = BlzGetUnitAbility(self.target, tb.ARMOR_ID)
IncUnitAbilityLevel(self.target, tb.ARMOR_ID)
BlzSetAbilityIntegerLevelField(abil, ABILITY_ILF_DEFENSE_BONUS_IDEF, 0, -self.stack)
DecUnitAbilityLevel(self.target, tb.ARMOR_ID)
PauseTimer(self.timer)
TimerStart(self.timer, tb.CONFIG.DURATION[level], false, self.callback)
end
function DesolationBeam.onDamage()
local src, targ = GetEventDamageSource(), GetTriggerUnit()
local level = GetUnitAbilityLevel(src, tb.ABIL_ID)
-- Filter out any other source of damage
-- only consider attacks and when they have the ability
if (BlzGetEventDamageType() ~= DAMAGE_TYPE_NORMAL) or (level == 0) then
return;
end
tb.unitdata[targ] = tb.unitdata[targ] or tb.create(targ, level)
tb.unitdata[targ]:apply(level)
end
function DesolationBeam.init()
local trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DAMAGED)
TriggerAddCondition(trig, Condition(DesolationBeam.onDamage))
end
end