14 years
edited 8 years
Documentation
The purpose of this system is to provide a simple framework for the implementation of custom melee races. It accomplishes this by setting up key components Blizzard should have given since the beginning (at least in a more portable state): the race initialization and victory / defeat conditions.
The approach chosen was one discussed a long time ago, probably on the Power of Corruption boards: letting the user pick their race by means of choosing one of the standard races and setting the handicap to a given value, which would be translated by the system into a particular custom race.
[btable]
[r]
[/btable]
Demo Map Credits
Playtesters
Things to take into consideration
Custom Race System
Here is an example of implementation from the demo map. Naga are a playable race for Night Elf, Handicap 90%.
Custom Race Setup
The purpose of this system is to provide a simple framework for the implementation of custom melee races. It accomplishes this by setting up key components Blizzard should have given since the beginning (at least in a more portable state): the race initialization and victory / defeat conditions.
The approach chosen was one discussed a long time ago, probably on the Power of Corruption boards: letting the user pick their race by means of choosing one of the standard races and setting the handicap to a given value, which would be translated by the system into a particular custom race.
[btable]
API
You create a struct of type [lcode=jass]CustomRace[/lcode], with the following constructor:
[lcode=jass]static method create takes string Name, race Race, real Handicap returns CustomRace[/lcode]
Each of the parameters are described below:
You can also create and register a custom race for all handicaps of a single race with:
[lcode=jass]static method createAll takes string Name, race Race returns CustomRace[/lcode]
You can extend the registration of a custom race to more than one handicap with:
[lcode=jass]method register takes race Race, real Handicap returns nothing[/lcode]
Once created, you still need to register its townhall, the workers types (you can have more than one type of worker) and hero(es). You can do it by using the following methods:
[lcode=jass]method setTownHall takes integer TownHallId returns nothing[/lcode]
[lcode=jass]method addWorkerType takes integer WorkerId, integer Priority, integer Quantity returns nothing[/lcode]
[lcode=jass]method addHeroType takes integer HeroId returns nothing[/lcode]
If you've got an AI ready to be implemented, then you've already done the hard part. All this takes is a string for the file path and it takes care of the rest. Custom races that were registered but don't have AI scripts cannot be played by the computer. Of course, you can circumvent this (for whatever silly reason) by using this with a string that doesn't refer to an .ai file ("poop" for example).
[lcode=jass]method setAIScript takes string FilePath returns nothing[/lcode]
Finally, if your race needs extended initialization beyond the creation of worker units, you can do so by assigning a custom callback function that will run after all the initial units are created; this might be useful to replicate behaviour like the initial Haunted Goldmine the Undead start with, or the Entagled Goldmine for the Night Elves. For that you use the following method:
[lcode=jass]method setCallback takes CustomRaceCall callback returns nothing[/lcode]
The CustomRaceCall function interface is used for the aforementioned callback. It is defined as follows:
[lcode=jass]function interface CustomRaceCall takes player Player, group Workers, unit Goldmine, unit Townhall, unit Randhero returns nothing[/lcode]
If you need to get the custom race of a player, GetPlayerCustomRaceName will return a name string of the custom race. Hopefully, you aren't silly enough to name your custom races identically.
[lcode=jass]function GetPlayerCustomRaceName takes player Player returns string[/lcode]
[lcode=jass]static method create takes string Name, race Race, real Handicap returns CustomRace[/lcode]
Each of the parameters are described below:
- [lcode=jass]string Name[/lcode] - The name for your race
- [lcode=jass]race Race[/lcode] - The standard race it will be based on (eg, [lcode=jass]RACE_HUMAN[/lcode]).
- [lcode=jass]real Handicap[/lcode] - The handicap value users need to set so they can pick the race. Accpetable values are 0.5, 0.6, onwards up to 1.0 (which means you can overwrite the standard races too)
You can also create and register a custom race for all handicaps of a single race with:
[lcode=jass]static method createAll takes string Name, race Race returns CustomRace[/lcode]
You can extend the registration of a custom race to more than one handicap with:
[lcode=jass]method register takes race Race, real Handicap returns nothing[/lcode]
Once created, you still need to register its townhall, the workers types (you can have more than one type of worker) and hero(es). You can do it by using the following methods:
[lcode=jass]method setTownHall takes integer TownHallId returns nothing[/lcode]
- [lcode=jass]integer TownHallId [/lcode] - The rawcode of the race's Townhall (eg [lcode=jass]'ogre'[/lcode] for Orc Great Hall)
[lcode=jass]method addWorkerType takes integer WorkerId, integer Priority, integer Quantity returns nothing[/lcode]
- [lcode=jass]integer WorkerId [/lcode] - The rawcode of the race's Worker (eg [lcode=jass]'hpea'[/lcode] for Human Peasant)
- [lcode=jass]integer Priority[/lcode] - The priority where the worker type unit will be created. Acceptable values are
- [lcode=jass]CustomRace.NEAR_MINE[/lcode] - Spawns them near the closest mine. Normally all worker units should use this value.
- [lcode=jass]CustomRace.NEAR_HALL[/lcode] - Spawns them near the starting Town Hall unit type. This is included if you want additional workers, in the same vein Ghouls are for the Undead race.
- [lcode=jass]integer Quantity [/lcode] - The amount of initial workers of that type.
[lcode=jass]method addHeroType takes integer HeroId returns nothing[/lcode]
- [lcode=jass]integer HeroId[/lcode] - The rawcode of the race's hero (eg [lcode=jass]'Hpal'[/lcode] for Human Paladin)
If you've got an AI ready to be implemented, then you've already done the hard part. All this takes is a string for the file path and it takes care of the rest. Custom races that were registered but don't have AI scripts cannot be played by the computer. Of course, you can circumvent this (for whatever silly reason) by using this with a string that doesn't refer to an .ai file ("poop" for example).
[lcode=jass]method setAIScript takes string FilePath returns nothing[/lcode]
- [lcode=jass]string FilePath[/lcode] - The filepath of the .ai script, either in an MPQ or imported to the map
Finally, if your race needs extended initialization beyond the creation of worker units, you can do so by assigning a custom callback function that will run after all the initial units are created; this might be useful to replicate behaviour like the initial Haunted Goldmine the Undead start with, or the Entagled Goldmine for the Night Elves. For that you use the following method:
[lcode=jass]method setCallback takes CustomRaceCall callback returns nothing[/lcode]
- [lcode=jass]CustomRaceCall callback[/lcode] - A function that matches the CustomRaceCall function interface. More on that below.
The CustomRaceCall function interface is used for the aforementioned callback. It is defined as follows:
[lcode=jass]function interface CustomRaceCall takes player Player, group Workers, unit Goldmine, unit Townhall, unit Randhero returns nothing[/lcode]
- [lcode=jass]player Player[/lcode] - The owning player of the units passed to the callback function.
- [lcode=jass]group Workers[/lcode] - A unit group with all the created workers created, regardless of type.
- [lcode=jass]unit Goldmine[/lcode] - The nearest Goldmine found at the start location. If there isn't a Goldmine nearby, this parameter is null.
- [lcode=jass]unit Townhall[/lcode] - The townhall created on initialization.
- [lcode=jass]unit Randhero[/lcode] - If the "Use a Random Hero" option is checked before starting the game, this parameter points to the randomly created Hero; otherwise, it will be null.
If you need to get the custom race of a player, GetPlayerCustomRaceName will return a name string of the custom race. Hopefully, you aren't silly enough to name your custom races identically.
[lcode=jass]function GetPlayerCustomRaceName takes player Player returns string[/lcode]
- [lcode=jass]player Player[/lcode] - The player with a custom race. Players without a custom race associated with them will return an empty string.
Demo Map Credits
Playtesters
- Alevice
- Anopob
- cosmicat
- Dragoon
- Michael Peppers
- Callahan - Mur'gul House
- DonDustin - Blue Basilisk Missile
- JetFangInferno - Aqua Spike
- jigrael - Reptilian Sanctuary, Sea Drake
- Phoenix - IV - Naga Town Hall
- MasterHaosis - Mur'gul Labor Mill
- Szythe - Water Buff
- Anachron - Water Shockwave
- bigapple90 - Water Tornado
- Technomancer - Drown
- Anitarf - Stack
- Vexorian - SimError, Table, TimerUtils
Things to take into consideration
- If a Goldmine cant be found within default melee lookup radius, all created workers will be located near the Town Hall. this is done accodirng to default melee behaviour.
- You can now register custom races in their own separate libraries. They should require CustomRaceSystem and must run on initialization.
- The system now supports AI. And the Naga are a great example of it, if I may say so.
- If you define two or more custom races with the same base race and handicap, a debug message will be displayed in - game and whichever races were defined beyond the first will not be registered.
Custom Race System
// ==============================================================================
// Custom Race System by Archmage Owenalacaster
// ==============================================================================
//
// Purpose:
// - Creates the starting units for custom races and replaces the standard
// Melee Initialization trigger. Custom races are selected by race and
// handicap.
//
// Usage:
// - Register a new custom race with CustomRace.create(name, RACE, handicap)
// Handicaps: Valid handicap values are 1.0, 0.9, 0.8, 0.7, 0.6 and 0.5.
// - Register a new custom race for all handicaps of a single race with
// CustomRace.createAll(name, RACE)
// - Extend the registration of a race with c.register(RACE, handicap)
// - Set the townhall type with c.setTownHall(unitid)
// - Add a new worker type with c.addWorkerType(unitid, priority, qty)
// Priorities: c.NEAR_MINE spawns workers near the mine, where workers
// typically spawn.
// c.NEAR_HALL spawns workers near the town hall, where
// Ghouls spawn.
// - Add a random hero type with c.addHeroType(unitid)
// - Set the ai script used by computer players with c.setAIScript(stringpath)
// - Set a callback function with c.setCallback(CustomRaceCall.function)
// Callbacks: The callback is executed after all the starting units for a
// player are created, and its purpose is to provide enhanced
// initial behaviour for a race. A good example of this with the
// standard races would be the Undead Goldmine Haunting and
// Night Elves Goldmine Entangling.
// The callback function passes as arguments all the units
// generated in addition to the nearest goldmine detected.
// Please note that if a random hero is not created, the last
// argument will have a null value, so always do a check.
// - Get a player's custom race name string with GetPlayerCustomRaceName(player)
//
// Notes:
// - Supports a maximum of 24 custom races.
// - Maximum for worker and hero types are configurable.
//
// Requirements:
// - JassHelper version 0.9.E.0 or newer (older versions may still work).
//
// Installation:
// - Create a new trigger called CustomRaceSystem.
// - Convert it to custom text and replace all the code with this code.
//
// Special Thanks:
// - Alevice: He practically co - wrote the code.
// - cosmicat: His formula for circular unit formation.
// Co - developing the single - array registry.
//
// ==============================================================================
library CustomRaceSystem initializer Init
// ===========================================================================
// CONFIGURATION SECTION
// ===========================================================================
globals
// Unit Type Constants
private constant integer MAX_WORKERTYPES = 4
private constant integer MAX_HEROTYPES = 4
endglobals
// ===========================================================================
// END CONFIGURATION SECTION
// ===========================================================================
function interface CustomRaceCall takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
private function r2S takes race r returns string
if r == RACE_HUMAN then
return "Human"
elseif r == RACE_ORC then
return "Orc"
elseif r == RACE_UNDEAD then
return "Undead"
elseif r == RACE_NIGHTELF then
return "Night Elf"
endif
return "Unknown"
endfunction
private function r2I takes race r returns integer
if r == RACE_HUMAN then
return 1
elseif r == RACE_ORC then
return 2
elseif r == RACE_UNDEAD then
return 3
elseif r == RACE_NIGHTELF then
return 4
endif
return 5
endfunction
globals
// Victory Defeat Variables
private string array KEY_STRUCTURE
private integer KEY_STRUCTURE_COUNT = 0
endglobals
// ===========================================================================
// STRUCT DATA
// ===========================================================================
private keyword createStartingUnits
struct CustomRace
string name
// Town Hall Variable
integer townhallType = 0
// string townhallName
// Town Hall name is not currently supported.
// Worker Variables
integer totalWorkerTypes = 0
integer array workerType[MAX_WORKERTYPES]
integer array workerPriority[MAX_WORKERTYPES]
integer array workerQty[MAX_WORKERTYPES]
// Random Hero Variables
integer totalHeroTypes = 0
integer array heroType[MAX_HEROTYPES]
// AI Script Directory String Variable
string aiscript = ""
// Callback Variable
private CustomRaceCall c
// Registry Variable
static integer array REGISTRY
// Spawn Priority Variables
static integer NEAR_MINE = 0
static integer NEAR_HALL = 1
static method get takes race r, real h returns CustomRace
return CustomRace(.REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))])
endmethod
method register takes race r, real h returns boolean
local CustomRace c = CustomRace.get(r, h)
if c != 0 then
debug call BJDebugMsg("|cffff0000Registration of " + .name + " failed due to conflict with " + c.name + " registered for " + r2S(r) + " race Handicap " + R2S(h))
return false
endif
set .REGISTRY[((r2I(r) - 1) * 6) + (10 - R2I(h * 10.))] = integer(this)
return true
endmethod
static method create takes string name, race r, real h returns CustomRace
local CustomRace c = CustomRace.allocate()
set c.name = name
if not c.register(r, h) then
call c.destroy()
return 0
endif
return c
endmethod
static method createAll takes string name, race r returns CustomRace
local CustomRace c = CustomRace.allocate()
set c.name = name
if not c.register(r, 1.0) and not c.register(r, 0.9) and not c.register(r, 0.8) and not c.register(r, 0.7) and not c.register(r, 0.6) and not c.register(r, 0.5) then
call c.destroy()
return 0
endif
return c
endmethod
method setTownHall takes integer hallid returns nothing
set .townhallType = hallid
set KEY_STRUCTURE[KEY_STRUCTURE_COUNT] = UnitId2String(hallid)
set KEY_STRUCTURE_COUNT = KEY_STRUCTURE_COUNT + 1
endmethod
method addWorkerType takes integer workerid, integer priority, integer quantity returns nothing
set .workerType[.totalWorkerTypes] = workerid
set .workerPriority[.totalWorkerTypes] = priority
set .workerQty[.totalWorkerTypes] = quantity
set .totalWorkerTypes = .totalWorkerTypes + 1
endmethod
method addHeroType takes integer heroid returns nothing
local integer i = 0
set .heroType[.totalHeroTypes] = heroid
set .totalHeroTypes = .totalHeroTypes + 1
loop
call SetPlayerTechMaxAllowed(Player(i), heroid, 1)
set i = i + 1
exitwhen i == bj_MAX_PLAYERS
endloop
endmethod
private method getRandomHeroType takes nothing returns integer
local integer randomindex = GetRandomInt(0, .totalHeroTypes - 1)
return .heroType[randomindex]
endmethod
method setAIScript takes string s returns nothing
set .aiscript = s
endmethod
method setCallback takes CustomRaceCall callb returns nothing
set .c = callb
endmethod
private method createRandomHero takes player p, location loc returns unit
local unit h = CreateUnitAtLoc(p, .getRandomHeroType(), loc, bj_UNIT_FACING)
if bj_meleeGrantHeroItems then
call MeleeGrantItemsToHero(h)
endif
return h
endmethod
method createStartingUnits takes player p returns nothing
local location startLoc = GetPlayerStartLocationLoc(p)
local location nearMineLoc = startLoc
local location nearTownLoc = startLoc
local location spawnLoc = startLoc
local location heroLoc = startLoc
local unit nearestMine = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
local unit myTownhall = null
local unit myRandHero = null
local group workerGroup = CreateGroup()
local integer workertypeindex = 0
local integer workerqty = 0
local integer spawnPriority = 0
if nearestMine != null then
set nearMineLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 320, 0)
set nearTownLoc = MeleeGetProjectedLoc(startLoc, GetUnitLoc(nearestMine), 288, 0)
set heroLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startLoc, 384, 45)
endif
set myTownhall = CreateUnitAtLoc(p, .townhallType, startLoc, bj_UNIT_FACING)
loop
exitwhen workertypeindex == .totalWorkerTypes
set spawnPriority = .workerPriority[workertypeindex]
if (spawnPriority==.NEAR_HALL) then
set spawnLoc = nearTownLoc
elseif(spawnPriority==.NEAR_MINE) then
set spawnLoc = nearMineLoc
endif
loop
call GroupAddUnit(workerGroup, CreateUnitAtLoc(p,.workerType[workertypeindex],PolarProjectionBJ(spawnLoc,65,(I2R(workerqty) * (360.00 / I2R(.workerQty[workertypeindex]))) + 90),bj_UNIT_FACING))
set workerqty = workerqty + 1
exitwhen workerqty >= .workerQty[workertypeindex]
endloop
call RemoveLocation(spawnLoc)
set workerqty = 0
set workertypeindex = workertypeindex + 1
endloop
if (IsMapFlagSet(MAP_RANDOM_HERO) and .totalHeroTypes>0 ) then
set myRandHero = .createRandomHero(p,heroLoc)
else
call SetPlayerState(p,PLAYER_STATE_RESOURCE_HERO_TOKENS,bj_MELEE_STARTING_HERO_TOKENS)
endif
if(.c!=0) then
call .c.evaluate(p,workerGroup,nearestMine,myTownhall,myRandHero)
else
call DestroyGroup(workerGroup)
endif
if nearMineLoc != startLoc then
call RemoveLocation(nearMineLoc)
call RemoveLocation(nearTownLoc)
call RemoveLocation(heroLoc)
endif
call RemoveLocation(startLoc)
set startLoc = null
set nearMineLoc = null
set nearTownLoc = null
set spawnLoc = null
set heroLoc = null
set nearestMine = null
set myTownhall = null
set myRandHero = null
set workerGroup = null
endmethod
endstruct
globals
private string array PLAYER_RACE
endglobals
function GetPlayerCustomRaceName takes player p returns string
return PLAYER_RACE[GetPlayerId(p)]
endfunction
//===========================================================================
// UNIT CREATION SECTION
//===========================================================================
private function CreateStartingUnitsForAllPlayers takes nothing returns nothing
local integer index = 0
local player indexPlayer
local race playerRace
local CustomRace c
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set playerRace = GetPlayerRace(indexPlayer)
set c = CustomRace.get(playerRace,GetPlayerHandicap(indexPlayer) + 0.01)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_USER or (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER and c.aiscript != "" )) and c != 0 then
set PLAYER_RACE[index] = c.name
call c.createStartingUnits(indexPlayer)
elseif playerRace == RACE_HUMAN then
call MeleeStartingUnitsHuman(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_ORC then
call MeleeStartingUnitsOrc(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_NIGHTELF then
call MeleeStartingUnitsNightElf(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_UNDEAD then
call MeleeStartingUnitsUndead(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
else
call MeleeStartingUnitsUnknownRace(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
endfunction
//===========================================================================
// CUSTOM MELEE AI SECTION
//===========================================================================
private function CustomMeleeStartingAI takes nothing returns nothing
local integer index = 0
local player indexPlayer
local race indexRace
local CustomRace c
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set indexRace = GetPlayerRace(indexPlayer)
set c = CustomRace.get(indexRace,GetPlayerHandicap(indexPlayer) + 0.01)
call SetPlayerHandicap(indexPlayer,1.0)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER) then
// Run a race-specific melee AI script.
if c != 0 and c.aiscript != "" then
call StartMeleeAI(indexPlayer, c.aiscript)
elseif (indexRace == RACE_HUMAN) then
call PickMeleeAI(indexPlayer, "human.ai", null, null)
elseif (indexRace == RACE_ORC) then
call PickMeleeAI(indexPlayer, "orc.ai", null, null)
elseif (indexRace == RACE_UNDEAD) then
call PickMeleeAI(indexPlayer, "undead.ai", null, null)
call RecycleGuardPosition(bj_ghoul[index])
elseif (indexRace == RACE_NIGHTELF) then
call PickMeleeAI(indexPlayer, "elf.ai", null, null)
else
// Unrecognized race.
endif
call ShareEverythingWithTeamAI(indexPlayer)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
endfunction
//===========================================================================
// VICTORY DEFEAT SECTION
//===========================================================================
private function CustomGetAllyKeyStructureCount takes player whichPlayer returns integer
local integer i = 0
local integer keyStructs = 0
local integer playerIndex = 0
local player indexPlayer
loop
set indexPlayer = Player(playerIndex)
if (PlayersAreCoAllied(whichPlayer, indexPlayer)) then
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "townhall", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "greathall", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "necropolis", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "treeoflife", true, true)
loop
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, KEY_STRUCTURE[i], true, true)
set i = i + 1
exitwhen i == KEY_STRUCTURE_COUNT
endloop
endif
set playerIndex = playerIndex + 1
exitwhen playerIndex == bj_MAX_PLAYERS
endloop
return keyStructs
endfunction
private function CustomPlayerIsCrippled takes player whichPlayer returns boolean
local integer allyStructures = MeleeGetAllyStructureCount(whichPlayer)
local integer allyKeyStructures = CustomGetAllyKeyStructureCount(whichPlayer)
return (allyStructures > 0) and (allyKeyStructures <= 0)
endfunction
private function CustomCheckForCrippledPlayers takes nothing returns nothing
local integer playerIndex
local player indexPlayer
local boolean isNowCrippled
call MeleeCheckForLosersAndVictors()
if bj_finishSoonAllExposed then
return
endif
set playerIndex = 0
loop
set indexPlayer = Player(playerIndex)
set isNowCrippled = CustomPlayerIsCrippled(indexPlayer)
if (not bj_playerIsCrippled[playerIndex] and isNowCrippled) then
set bj_playerIsCrippled[playerIndex] = true
call TimerStart(bj_crippledTimer[playerIndex], bj_MELEE_CRIPPLE_TIMEOUT, false, function MeleeCrippledPlayerTimeout)
if (GetLocalPlayer() == indexPlayer) then
call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], true)
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_WARNING_HUMAN"))
endif
elseif (bj_playerIsCrippled[playerIndex] and not isNowCrippled) then
set bj_playerIsCrippled[playerIndex] = false
call PauseTimer(bj_crippledTimer[playerIndex])
if (GetLocalPlayer() == indexPlayer) then
call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], false)
if (MeleeGetAllyStructureCount(indexPlayer) > 0) then
if (bj_playerIsExposed[playerIndex]) then
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
else
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
endif
endif
endif
call MeleeExposePlayer(indexPlayer, false)
endif
set playerIndex = playerIndex + 1
exitwhen playerIndex == bj_MAX_PLAYERS
endloop
endfunction
private function CustomInitVictoryDefeat takes nothing returns nothing
local trigger checker = CreateTrigger()
local trigger trig
local integer index
local player indexPlayer
set bj_finishSoonTimerDialog = CreateTimerDialog(null)
call TriggerAddAction(checker, function CustomCheckForCrippledPlayers)
set trig = CreateTrigger()
call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_SOON)
call TriggerAddAction(trig, function MeleeTriggerTournamentFinishSoon)
set trig = CreateTrigger()
call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_NOW)
call TriggerAddAction(trig, function MeleeTriggerTournamentFinishNow)
set index = 0
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set bj_meleeDefeated[index] = false
set bj_meleeVictoried[index] = false
set bj_playerIsCrippled[index] = false
set bj_playerIsExposed[index] = false
set bj_crippledTimer[index] = CreateTimer()
set bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
call TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(indexPlayer))
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
call TriggerRegisterPlayerAllianceChange(checker, indexPlayer, ALLIANCE_PASSIVE)
call TriggerRegisterPlayerStateEvent(checker, indexPlayer, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_DEFEAT)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerDefeated)
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
else
set bj_meleeDefeated[index] = true
set bj_meleeVictoried[index] = false
if (IsPlayerObserver(indexPlayer)) then
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
call TimerStart(CreateTimer(), 2.0, false, function CustomCheckForCrippledPlayers)
endfunction
private function TimerAction takes nothing returns nothing
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
call MeleeStartingHeroLimit()
call MeleeGrantHeroItems()
call MeleeStartingResources()
call MeleeClearExcessUnits()
call CreateStartingUnitsForAllPlayers()
call CustomMeleeStartingAI()
call CustomInitVictoryDefeat()
call DestroyTimer(GetExpiredTimer())
endfunction
private function Init takes nothing returns nothing
call TimerStart(CreateTimer(),0,false,function TimerAction)
endfunction
endlibrary Here is an example of implementation from the demo map. Naga are a playable race for Night Elf, Handicap 90%.
Custom Race Setup
//====================================================================
// HUMAN SETUP
//====================================================================
library HumanSetup initializer Init requires CustomRaceSystem
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Human",RACE_HUMAN,1.0)
call c.setTownHall('htow') // Town Hall
call c.addWorkerType('hpea',c.NEAR_MINE,5) // Peasant
call c.addHeroType('Hpal') // Paladin
call c.addHeroType('Hamg') // Archmage
call c.addHeroType('Hmkg') // Mountain King
call c.addHeroType('Hblm') // Blood Mage
call c.setAIScript("human.ai")
endfunction
endlibrary
//====================================================================
// ORC SETUP
//====================================================================
library OrcSetup initializer Init requires CustomRaceSystem
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Orc",RACE_ORC,1.0)
call c.setTownHall('ogre') // Great Hall
call c.addWorkerType('opeo',c.NEAR_MINE,5) // Peon
call c.addHeroType('Obla') // Blademaster
call c.addHeroType('Ofar') // Far Seer
call c.addHeroType('Otch') // Tauren Chieftain
call c.addHeroType('Oshd') // Shadow Hunter
call c.setAIScript("orc.ai")
endfunction
endlibrary
//====================================================================
// UNDEAD SETUP
//====================================================================
library UndeadSetup initializer Init requires CustomRaceSystem
private function WorkerHideToggle takes nothing returns nothing
call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
endfunction
private function HauntGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
call ForGroup(workers,function WorkerHideToggle)
call BlightGoldMineForPlayerBJ(goldmine,play)
call ForGroup(workers,function WorkerHideToggle)
call DestroyGroup(workers)
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Undead",RACE_UNDEAD,1.0)
call c.setTownHall('unpl') // Necropolis
call c.addWorkerType('uaco',c.NEAR_MINE,3) // Acolyte
call c.addWorkerType('ugho',c.NEAR_HALL,1) // Ghoul
call c.addHeroType('Udea') // Death Knight
call c.addHeroType('Ulic') // Lich
call c.addHeroType('Udre') // Dreadlord
call c.addHeroType('Ucrl') // Crypt Lord
call c.setCallback(CustomRaceCall.HauntGoldMine)
call c.setAIScript("undead.ai")
endfunction
endlibrary
//====================================================================
// NIGHT ELF SETUP
//====================================================================
library NightElfSetup initializer Init requires CustomRaceSystem
private function EntangleGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
call SetUnitPosition(townhall,GetUnitX(goldmine),GetUnitY(goldmine))
call IssueTargetOrder(townhall, "entangleinstant", goldmine)
call DestroyGroup(workers)
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Night Elf",RACE_NIGHTELF,1.0)
call c.setTownHall('etol') // Tree of Life
call c.addWorkerType('ewsp',c.NEAR_MINE,5) // Wisp
call c.addHeroType('Ekee') // Keeper of the Grove
call c.addHeroType('Emoo') // Priestess of the Moon
call c.addHeroType('Edem') // Demon Hunter
call c.addHeroType('Ewar') // Warden
call c.setCallback(CustomRaceCall.EntangleGoldMine)
call c.setAIScript("elf.ai")
endfunction
endlibrary
//====================================================================
// NAGA SETUP
//====================================================================
library NagaSetup initializer Init requires CustomRaceSystem
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Naga",RACE_NIGHTELF,0.9)
call c.setTownHall('nntt') // Temple of Tides
call c.addWorkerType('nmpe',c.NEAR_MINE,5) // Murgul Slave
call c.addHeroType('Hvsh') // Lady Vashj
call c.setAIScript("naga.ai")
endfunction
endlibrary