Name | Type | is_array | initial_value |
AfterDamageEvent | real | No | |
AgonyDummy | unit | No | |
AMSAmount | real | Yes | |
AOEDamageEvent | real | No | |
AOEString | string | No | |
ArcaneWillGroup | group | No | |
Arcanewillhp1 | real | Yes | |
Arcanewillhp2 | real | Yes | |
ArcaneWillLevel | integer | No | |
Arcanewillunit | unit | Yes | |
AssassinGroup | group | No | |
BanishStrength | unit | No | |
Barbarian | unit | No | |
BarbarianGroup | group | No | |
BlackDragonGroup | group | No | |
BlackDragonLordGroup | group | No | |
BlackRiderGroup | group | No | |
BloodTDummy | unit | No | |
BloodTourenamentEnemy | integer | No | |
BloodTournamentGroup | group | No | |
BloodWardenGroup | group | No | |
BrutelordGroup | group | No | |
BurningBladeGroup | group | No | |
BurningTouch | unit | No | |
CasterFacing | real | No | |
ChaosSeer | unitcode | No | O003 |
Choice | dialog | Yes | |
chuncks10 | integer | No | |
ClearDamageEvent | trigger | No | |
Color_Text | string | Yes | |
CONSTANT_FavourColour | string | No | |cFFAAAAAA |
CONSTANT_FavourEnd | string | No | Favor|r |
CONSTANT_FavoutCostHigh | integer | No | 40 |
CONSTANT_FavoutCostLow | integer | No | 1 |
CONSTANT_NotEnoughFavour | string | No | |cffffcc00More Dragon Favor required.|r |
Crossbow | unit | No | |
CunningSkills | integer | No | |
CunningSkillsUnit | unit | No | |
Curator | unit | No | |
CurseEssenceBoard | multiboard | No | |
DAMAGE_FACTOR_BRACERS | real | No | |
DAMAGE_FACTOR_ELUNES | real | No | |
DAMAGE_FACTOR_ETHEREAL | real | No | |
DamageEvent | real | No | |
DamageEventAmount | real | No | |
DamageEventAOE | integer | No | |
DamageEventAOEGroup | group | No | |
DamageEventLevel | integer | No | |
DamageEventOverride | boolean | No | |
DamageEventPrevAmt | real | No | |
DamageEventSource | unit | No | |
DamageEventsWasted | integer | No | |
DamageEventTarget | unit | No | |
DamageEventTrigger | trigger | No | |
DamageEventType | integer | No | |
DamageModifierEvent | real | No | |
DamageTypeBlocked | integer | No | |
DamageTypeCode | integer | No | |
DamageTypeCriticalStrike | integer | No | |
DamageTypeExplosive | integer | No | |
DamageTypeHeal | integer | No | |
DamageTypeReduced | integer | No | |
DarkHordeBuilding | group | No | |
DeathwingWill | integer | No | |
DiabolistGroup | group | No | |
DirtTricksBonus | boolean | No | |
DirtyTricksElementBonus | integer | No | |
DissipateDummy | unit | No | |
DmgEvBracers | itemcode | No | |
DmgEvMana | real | No | |
DmgEvManaMult | real | No | |
DmgEvMSlvl | integer | No | |
DmgEvQueued | boolean | No | |
DmgEvRecursionN | integer | No | |
DmgEvRunning | boolean | No | |
DmgEvStarted | boolean | No | |
DmgEvTimer | timer | No | |
DmgEvTrig | trigger | No | |
DragonBlood | integer | No | |
DragonFavourBoard | multiboard | Yes | |
DragonGroup | group | No | |
DragonscaleGifts | integer | No | |
DragonWalker | unit | No | |
DrainSoulInt | integer | No | |
DrakeRiderSpeed | unit | No | |
ElaborateVision | unit | No | |
EnhancedDamageTarget | unit | No | |
EssenceBoard | multiboard | Yes | |
Exarch | unit | No | |
Executioner | unit | No | |
ExecutionerGroup | group | No | |
ExploreWhelp | unit | No | |
FallenScream | unit | Yes | |
FavourGathered | integer | Yes | |
FavoursLearned | integer | Yes | |
FelHorde | button | Yes | |
FelRiderGroup | group | No | |
FelRuntDemonAffliction | unit | No | |
FireLord | unitcode | No | N001 |
FlameRipple | unit | No | |
FlashbangDummy | unit | No | |
Forces | force | Yes | |
GoliathGroup | group | No | |
GroundSlam | unit | Yes | |
HandoftheDragonGroup | group | No | |
Hatchling | unit | No | |
Heal | unit | Yes | |
HellSpawnTAke | unitcode | No | N000 |
Hero | unit | Yes | |
HeroHotD | unit | Yes | |
HeroWhelpPet | unit | Yes | |
Hexxer | unit | No | |
HideDamageFrom | boolean | Yes | |
hp10 | real | No | |
hpMax | real | No | |
hpMiss | real | No | |
IllidariGroup | group | No | |
ImpendingVictoryDummy | unit | No | |
ImpVicLS1 | group | No | |
ImpVicLS2 | group | No | |
ImpVicLS3 | group | No | |
InfernalStoneDummy | unit | No | |
InfernalStoneProc | integer | No | |
IsDamageSpell | boolean | No | |
IsUnitPreplaced | boolean | Yes | |
Kills | integer | Yes | |
LastDamageHP | real | No | |
LastDmgPrevAmount | real | Yes | |
LastDmgPrevType | integer | Yes | |
LastDmgSource | unit | Yes | |
LastDmgTarget | unit | Yes | |
LastDmgValue | real | Yes | |
LastDmgWasSpell | boolean | Yes | |
LeadAttackDummy | unit | No | |
List | integer | No | |
MAgicBlockae | unit | No | |
MagiDummyGroup | group | No | |
MagmaArmorEffect | effect | No | |
MaliceTerror | unit | No | |
MarcSavage | location | No | |
MarkBurningUnit | unit | No | |
MarkOfTheBurningLegion | group | No | |
MotherMistrust | unit | No | |
MovingUnits | group | No | |
Multiboard_Spots | integer | Yes | |
Necrolyte | unit | No | |
Necromancy | unit | No | |
NewEssence | string | No | |
NextDamageOverride | boolean | No | |
NextDamageType | integer | No | |
NixMagicDummy | unit | No | |
Other | button | Yes | |
Pickedhall | unit | No | |
PickedWorker | unit | No | |
Player | player | No | |
PlayerColour | player | Yes | |
PlayerGroup | force | No | |
PlayerId | integer | No | |
PlayerNum | integer | Yes | |
PlayerStructure | boolean | Yes | |
SabOreUnit | unit | No | |
Sacrifice_of_Nafarian | integer | No | |
Scarred | integer | No | |
SpellDamageAbility | abilcode | No | |
Stride | unit | No | |
tempLoc | location | Yes | |
TempReal | real | No | |
UDex | integer | No | |
UDexGen | integer | No | |
UDexNext | integer | Yes | |
UDexPrev | integer | Yes | |
UDexRecycle | integer | No | |
UDexUnits | unit | Yes | |
UDexWasted | integer | No | |
UMovNext | integer | Yes | |
UMovPrev | integer | Yes | |
UnitDamageRegistered | boolean | Yes | |
UnitIndexerEnabled | boolean | No | |
UnitIndexEvent | real | No | |
UnitIndexLock | integer | Yes | |
UnitMovementInterval | real | No | |
UnitMoving | boolean | Yes | |
UnitMovingEvent | real | No | |
UnitMovingX | real | Yes | |
UnitMovingY | real | Yes | |
Vendetta | integer | No | |
Vendetta_2 | integer | No | |
VendettaPurge | unit | No | |
VendettaSlow | unit | No | |
VendettaStun | unit | No | |
VisionsDummy | unit | No | |
WarLord | unitcode | No | O000 |
WarlordGroup | group | No | |
WarlordGroupAcro | group | No | |
WarlordHero | unit | No | |
WarlordHeroAcro | unit | No | |
WarlordU | unit | No | |
WarmasterAuraGroup | group | No | |
WarMasterHero | unit | No | |
WarShoutDummy | unit | No | |
Whelp_Training | boolean | No | |
WorgenGroup | force | No |
//===========================================================================
// Damage Engine lets you detect, amplify, block or nullify damage. It even
// lets you detect if the damage was physical or from a spell. Just reference
// DamageEventAmount/Source/Target or the boolean IsDamageSpell, to get the
// necessary damage event data.
//
// - Detect damage: use the event "DamageEvent Equal to 1.00"
// - To change damage before it's dealt: use the event "DamageModifierEvent Equal to 1.00"
// - Detect damage after it was applied, use the event "AfterDamageEvent Equal to 1.00"
// - Detect spell damage: use the condition "IsDamageSpell Equal to True"
// - Detect zero-damage: use the event "DamageEvent Equal to 2.00" (an AfterDamageEvent will not fire for this)
//
// You can specify the DamageEventType before dealing triggered damage, then run the trigger "ClearDamageEvent (Checking Conditions)" after dealing triggered damage from within a damage event:
// - Set NextDamageType = DamageTypeWhatever
// - Unit - Cause...
// - Trigger - Run ClearDamageEvent (Checking Conditions)
//
// You can modify the DamageEventAmount and the DamageEventType from a "DamageModifierEvent Equal to 1.00" trigger.
// - If the amount is modified to negative, it will count as a heal.
// - If the amount is set to 0, no damage will be dealt.
//
// If you need to reference the original in-game damage, use the variable "DamageEventPrevAmt".
//
//===========================================================================
// Programming note about "integer i" and "udg_DmgEvRecursionN": integer i
// ranges from -1 upwards. "udg_DmgEvRecursionN" ranges from 0 upwards.
// "integer i" is always 1 less than "udg_DmgEvRecursionN"
//
function DmgEvResetVars takes nothing returns nothing
local integer i = udg_DmgEvRecursionN - 2
set udg_DmgEvRecursionN = i + 1
if i >= 0 then
set udg_DamageEventPrevAmt = udg_LastDmgPrevAmount[i]
set udg_DamageEventAmount = udg_LastDmgValue[i]
set udg_DamageEventSource = udg_LastDmgSource[i]
set udg_DamageEventTarget = udg_LastDmgTarget[i]
set udg_IsDamageSpell = udg_LastDmgWasSpell[i]
set udg_DamageEventType = udg_LastDmgPrevType[i]
endif
endfunction
function ClearDamageEvent takes boolean clear returns nothing
if clear then
set udg_NextDamageOverride = false
set udg_NextDamageType = 0
endif
if udg_DmgEvQueued then
//The 0-second timer has expired, or there had been multiple sequential damage events running in parallel.
set udg_DmgEvQueued = false
set udg_AfterDamageEvent = 0.00
set udg_AfterDamageEvent = 1.00 //No longer fires with Unit State Event detection - this is
set udg_AfterDamageEvent = 0.00 //lighter on the system but will fire slightly later than before.
//It now only serves for counter-damage after the initial damage.
call ClearDamageEvent(true) //Check for any potential failed recursive damage from the previous event.
call DmgEvResetVars()
endif
endfunction
function DmgEvOnAOEEnd takes nothing returns nothing
if udg_DamageEventAOE > 1 then
set udg_AOEDamageEvent = 0.00
set udg_AOEDamageEvent = 1.00
set udg_AOEDamageEvent = 0.00
set udg_DamageEventAOE = 1
endif
set udg_DamageEventLevel = 1
set udg_EnhancedDamageTarget = null
call GroupClear(udg_DamageEventAOEGroup)
endfunction
function DmgEvOnExpire takes nothing returns nothing
set udg_DmgEvStarted = false //The timer has expired. Flag off to allow it to be restarted when needed.
call ClearDamageEvent(true) //Check for any lingering damage
//Reset things so they don't perpetuate for AoE/Level target detection
call DmgEvOnAOEEnd()
set udg_DamageEventTarget = null
set udg_DamageEventSource = null
endfunction
function PreClearDamageEvent takes nothing returns boolean
call ClearDamageEvent(true)
return false
endfunction
function OnUnitDamage takes nothing returns boolean
local boolean override = udg_DamageEventOverride
local integer i
local integer e = udg_DamageEventLevel
local integer a = udg_DamageEventAOE
local string s
local unit u
local unit f
call ClearDamageEvent(false) //in case the 0.00 second timer hasn't yet expired
set i = udg_DmgEvRecursionN - 1 //Had to be moved here due to false recursion tracking
if i < 0 then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
set u = udg_DamageEventTarget
set f = udg_DamageEventSource
elseif i < 16 then
set udg_LastDmgPrevAmount[i]= udg_DamageEventPrevAmt
set udg_LastDmgValue[i] = udg_DamageEventAmount
set udg_LastDmgSource[i] = udg_DamageEventSource
set udg_LastDmgTarget[i] = udg_DamageEventTarget
set udg_LastDmgWasSpell[i] = udg_IsDamageSpell
set udg_LastDmgPrevType[i] = udg_DamageEventType
else
set s = "WARNING: Recursion error when dealing damage! Make sure when you deal damage from within a DamageEvent trigger, do it like this:"
set s = s + "Trigger - Turn off (This Trigger)"
set s = s + "Unit - Cause..."
set s = s + "Trigger - Run ClearDamageEvent (Checking Conditions)"
set s = s + "Trigger - Turn on (This Trigger)"
//Delete the next couple of lines to disable the in-game recursion crash warnings
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.00, 0.00, 999.00, s)
return false
endif
set udg_DmgEvRecursionN = i + 2
set udg_DamageEventAmount = GetEventDamage()
set udg_DamageEventTarget = GetTriggerUnit()
set udg_DamageEventSource = GetEventDamageSource()
set udg_DamageEventType = udg_NextDamageType
set udg_NextDamageType = 0
set udg_DamageEventOverride = udg_NextDamageOverride
set udg_NextDamageOverride = false
if i < 0 then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
if udg_DamageEventType == 0 then
if f == udg_DamageEventSource then
//Source has damaged more than once
if IsUnitInGroup(udg_DamageEventTarget, udg_DamageEventAOEGroup) then
//Added 5 August 2017 to improve tracking of enhanced damage against, say, Pulverize
set udg_DamageEventLevel = udg_DamageEventLevel + 1
set udg_EnhancedDamageTarget = udg_DamageEventTarget
else
//Multiple targets hit by this source - flag as AOE
set udg_DamageEventAOE = udg_DamageEventAOE + 1
endif
else
//New damage source - unflag everything
set u = udg_DamageEventSource
set udg_DamageEventSource = f
call DmgEvOnAOEEnd()
set udg_DamageEventSource = u
endif
call GroupAddUnit(udg_DamageEventAOEGroup, udg_DamageEventTarget)
endif
if not udg_DmgEvStarted then
set udg_DmgEvStarted = true
call TimerStart(udg_DmgEvTimer, 0.00, false, function DmgEvOnExpire)
endif
endif
if udg_DamageEventAmount == 0.00 then
if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
set udg_DamageEventPrevAmt = 0.00
set udg_DamageEvent = 0.00
set udg_DamageEvent = 2.00
set udg_DamageEvent = 0.00
endif
call DmgEvResetVars()
else
set u = udg_DamageEventTarget
set udg_IsDamageSpell = udg_DamageEventAmount < 0.00
if udg_IsDamageSpell then
set udg_DamageEventAmount = -udg_DamageEventAmount
if IsUnitType(u, UNIT_TYPE_ETHEREAL) and not IsUnitType(u, UNIT_TYPE_HERO) then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_ETHEREAL //1.67
endif
if GetUnitAbilityLevel(u, 'Aegr') > 0 then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_ELUNES //0.80
endif
if udg_DmgEvBracers != 0 and IsUnitType(u, UNIT_TYPE_HERO) then
//Inline of UnitHasItemOfTypeBJ without the potential handle ID leak.
set i = bj_MAX_INVENTORY
loop
set i = i - 1
if GetItemTypeId(UnitItemInSlot(u, i)) == udg_DmgEvBracers then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_BRACERS //0.67
exitwhen true
endif
exitwhen i == 0
endloop
endif
endif
set udg_DamageEventPrevAmt = udg_DamageEventAmount
set udg_DamageModifierEvent = 0.00
if not udg_DamageEventOverride then
set udg_DamageModifierEvent = 1.00 //Primary modification - multiplication, blocking, reversing, etc.
set udg_DamageModifierEvent = 2.00 //Secondary Modification - addition/subtraction
set udg_DamageModifierEvent = 3.00 //Similar to 2.00 but with knowing all previous addition/subtraction have run.
endif
if udg_DamageEventAmount > 0.00 then
set udg_DamageModifierEvent = 4.00 //Apply finite damage modification such as Mana Shield or Anti-Magic Shell.
endif
set udg_DamageEventOverride = override
set udg_DamageModifierEvent = 0.00
if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
set udg_DamageEvent = 0.00
set udg_DamageEvent = 1.00
set udg_DamageEvent = 0.00
endif
call ClearDamageEvent(true) //in case the unit state event failed from a recursive damage event
//All events have run and the damage amount is finalized.
call BlzSetEventDamage(udg_DamageEventAmount)
set udg_DmgEvQueued = true
if udg_DamageEventType < 0 and GetWidgetLife(u) - udg_DamageEventAmount <= 0.405 then
call SetUnitExploded(u, true) //In 1.29 Finger of Death works as intended and triggering a fix is unnecessary.
endif //However, if you have a custom spell you want to be able to explode units with,
endif //then this negative damage type is still useful.
set u = null
set f = null
return false
endfunction
function CreateDmgEvTrg takes nothing returns nothing
set udg_DamageEventTrigger = CreateTrigger()
call TriggerAddCondition(udg_DamageEventTrigger, Filter(function OnUnitDamage))
endfunction
function SetupDmgEv takes nothing returns boolean
local integer i = udg_UDex
local unit u = udg_UDexUnits[i]
if GetUnitAbilityLevel(u, 'Aloc') == 0 and TriggerEvaluate(gg_trg_Damage_Engine_Config) then
set udg_UnitDamageRegistered[i] = true
call TriggerRegisterUnitEvent(udg_DamageEventTrigger, u, EVENT_UNIT_DAMAGED)
call UnitAddAbility(u, udg_SpellDamageAbility)
call UnitMakeAbilityPermanent(u, true, udg_SpellDamageAbility)
endif
set u = null
return false
endfunction
function RemoveDmgEv takes nothing returns boolean
local integer i = udg_UDex
set udg_HideDamageFrom[i] = false
if udg_UnitDamageRegistered[i] then
set udg_UnitDamageRegistered[i] = false
set udg_DamageEventsWasted = udg_DamageEventsWasted + 1
if udg_DamageEventsWasted == 32 then //After 32 registered units have been removed...
set udg_DamageEventsWasted = 0
//Rebuild the mass EVENT_UNIT_DAMAGED trigger:
call DestroyTrigger(udg_DamageEventTrigger)
call CreateDmgEvTrg()
set i = udg_UDexNext[0]
loop
exitwhen i == 0
if udg_UnitDamageRegistered[i] then
call TriggerRegisterUnitEvent(udg_DamageEventTrigger, udg_UDexUnits[i], EVENT_UNIT_DAMAGED)
endif
set i = udg_UDexNext[i]
endloop
endif
endif
return false
endfunction
//===========================================================================
function InitTrig_Damage_Engine takes nothing returns nothing
local unit u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), 'uloc', 0, 0, 0)
local integer i = bj_MAX_PLAYERS //Fixed in 3.8
//Create trigger to add units to the system.
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
call TriggerAddCondition(t, Filter(function SetupDmgEv))
//Create trigger to remove units from the system.
set t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
call TriggerAddCondition(t, Filter(function RemoveDmgEv))
set t = null
//Execute the configuration function to first set all GUI variables:
call ExecuteFunc("Trig_Damage_Engine_Config_Actions")
//Create trigger for storing all EVENT_UNIT_DAMAGED events.
call CreateDmgEvTrg()
//Create GUI-friendly trigger for cleaning up after UnitDamageTarget.
set udg_ClearDamageEvent = CreateTrigger()
call TriggerAddCondition(udg_ClearDamageEvent, Filter(function PreClearDamageEvent))
//Disable SpellDamageAbility for every player.
loop
set i = i - 1
call SetPlayerAbilityAvailable(Player(i), udg_SpellDamageAbility, false)
exitwhen i == 0
endloop
//Preload ability
call UnitAddAbility(u, udg_SpellDamageAbility)
call RemoveUnit(u)
set u = null
endfunction
//TESH.scrollpos=12
//TESH.alwaysfold=0
constant function GetAMSBuffId takes nothing returns integer
return 'Bams'
endfunction
constant function GetAMSAbilId takes nothing returns integer
return 'A002'
endfunction
constant function GetAMSShieldVal takes nothing returns real
return 300.00
endfunction
function Trig_Anti_Magic_Shield_Fix_Actions takes nothing returns nothing
local integer id = GetUnitUserData(udg_DamageEventTarget)
local real shield = udg_AMSAmount[id]- udg_DamageEventAmount
if shield <= 0.00 then
set udg_DamageEventAmount = -shield
set shield = 0.00
call UnitRemoveAbility(udg_DamageEventTarget, GetAMSBuffId())
else
set udg_DamageEventAmount = 0.00
if udg_DamageEventType == 0 then
set udg_DamageEventType = udg_DamageTypeBlocked
endif
endif
set udg_AMSAmount[id] = shield
endfunction
function Trig_Anti_Magic_Shield_Fix_Conditions takes nothing returns boolean
if udg_IsDamageSpell then
if GetUnitAbilityLevel(udg_DamageEventTarget, GetAMSBuffId()) > 0 then
call Trig_Anti_Magic_Shield_Fix_Actions()
else
set udg_AMSAmount[GetUnitUserData(udg_DamageEventTarget)] = 0.00
endif
endif
return false
endfunction
function AMS_Refresh_Conditions takes nothing returns boolean
if GetSpellAbilityId() == GetAMSAbilId() then
set udg_AMSAmount[GetUnitUserData(GetSpellTargetUnit())] = GetAMSShieldVal()
endif
return false
endfunction
function InitTrig_Anti_Magic_Shield_Fix takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_DamageModifierEvent", EQUAL, 4.00)
call TriggerAddCondition(t, Condition(function Trig_Anti_Magic_Shield_Fix_Conditions))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Filter(function AMS_Refresh_Conditions))
endfunction
//TESH.scrollpos=75
//TESH.alwaysfold=0
function IsUnitMovementTracked takes integer i returns boolean
return udg_UMovPrev[i] != 0 or udg_UMovNext[0] == i
endfunction
function UnitMovementRegister takes nothing returns boolean
local integer i = udg_UDex
if not IsUnitMovementTracked(i) and TriggerEvaluate(gg_trg_Is_Unit_Moving_Config) then
set udg_UMovPrev[udg_UMovNext[0]] = i
set udg_UMovNext[i] = udg_UMovNext[0]
set udg_UMovNext[0] = i
set udg_UnitMovingX[i] = GetUnitX(udg_UDexUnits[i])
set udg_UnitMovingY[i] = GetUnitY(udg_UDexUnits[i])
endif
return false
endfunction
function UnitMovementUnregister takes nothing returns boolean
local integer i = udg_UDex
if IsUnitMovementTracked(i) then
set udg_UnitMoving[i] = false
set udg_UMovNext[udg_UMovPrev[i]] = udg_UMovNext[i]
set udg_UMovPrev[udg_UMovNext[i]] = udg_UMovPrev[i]
set udg_UMovPrev[i] = 0
endif
return false
endfunction
function RunUnitMovementEvent takes integer i, real e returns nothing
local integer pdex = udg_UDex
if e == 1.00 then
set udg_UnitMoving[i] = true
else
set udg_UnitMoving[i] = false
endif
set udg_UDex = i
set udg_UnitMovingEvent = e
set udg_UnitMovingEvent = 0.00
set udg_UDex = pdex
endfunction
//===========================================================================
// This function runs periodically to check if units are actually moving.
//
function UnitMovementTracker takes nothing returns nothing
local integer i = 0
local integer n
local real x
local real y
loop
set i = udg_UMovNext[i]
exitwhen i == 0
set x = GetUnitX(udg_UDexUnits[i])
set y = GetUnitY(udg_UDexUnits[i])
if x != udg_UnitMovingX[i] or y != udg_UnitMovingY[i] then
set udg_UnitMovingX[i] = x
set udg_UnitMovingY[i] = y
if not udg_UnitMoving[i] then
if GetUnitTypeId(udg_UDexUnits[i]) != 0 then
call RunUnitMovementEvent(i, 1.00)
else
set n = udg_UDex
set udg_UDex = i
set i = udg_UMovPrev[i] //avoid skipping checks
call UnitMovementUnregister()
set udg_UDex = n
endif
endif
elseif udg_UnitMoving[i] then
call RunUnitMovementEvent(i, 2.00)
endif
endloop
endfunction
//===========================================================================
function InitTrig_Is_Unit_Moving takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
call TriggerAddCondition(t, Filter(function UnitMovementRegister))
set t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
call TriggerAddCondition(t, Filter(function UnitMovementUnregister))
if gg_trg_Is_Unit_Moving_Config != null then
call TriggerExecute(gg_trg_Is_Unit_Moving_Config)
else
call ExecuteFunc("Trig_Is_Unit_Moving_Config_Actions")
endif
call TimerStart(CreateTimer(), udg_UnitMovementInterval, true, function UnitMovementTracker)
endfunction
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = true
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
library Colour
globals
Colour COLOUR_PHYSICAL_DMG = 0
Colour COLOUR_MAGICAL_DMG = 0
Colour COLOUR_HEAL = 0
Colour COLOUR_SHIELD = 0
Colour COLOUR_GOLD = 0
Colour COLOUR_LUMBER = 0
endglobals
private module InitColour
private static method onInit takes nothing returns nothing
//Setup colours
set COLOUR_PHYSICAL_DMG = Colour.create(255, 0, 50, 255)
set COLOUR_MAGICAL_DMG = Colour.create(50, 200, 255, 255)
set COLOUR_HEAL = Colour.create(50, 255, 100, 255)
set COLOUR_SHIELD = Colour.create(255, 40, 225, 255)
set COLOUR_GOLD = Colour.create(255, 220, 0, 255)
set COLOUR_LUMBER = Colour.create(0, 200, 80, 255)
endmethod
endmodule
struct Colour
integer r
integer g
integer b
integer a
static method create takes integer red, integer green, integer blue, integer alpha returns thistype
local thistype this = allocate()
set .r = red
set .g = green
set .b = blue
set .a = alpha
return this
endmethod
implement InitColour
endstruct
endlibrary
library ResourceOverTime uses TimerUtils, Colour, DragonFavour
globals
integer DRAGON_FAVOUR = 0
ResourceOverTime array FavourGen
constant real FAVOUR_INT = 10.
constant integer FAVOUR_AMT = 1
endglobals
struct CustomResource
endstruct
struct ResourceOverTime
private static constant real SPEED = .0355
private static constant real ANGLE = bj_PI/2
private unit mine
private real interval
private timer clock
private playerstate resource
private integer amount
private player play
private Colour clr
private CustomResource custom
private boolean stopped
method destroy takes nothing returns nothing
set .mine = null
call ReleaseTimer(.clock)
set .clock = null
call this.deallocate()
endmethod
private static method callback takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local texttag tt
if not this.stopped then
if this.resource != null then
call SetPlayerState(this.play, this.resource, GetPlayerState(this.play, this.resource) + this.amount)
else
// Custom Stuff here
if this.custom == DRAGON_FAVOUR then
call AddFavour(this.play, this.amount)
endif
endif
if GetLocalPlayer() == this.play then
set tt = CreateTextTag()
call SetTextTagText(tt, "+" + I2S(this.amount), .023)
call SetTextTagPos(tt, GetUnitX(this.mine), GetUnitY(this.mine), GetUnitFlyHeight(this.mine) + BlzGetUnitCollisionSize(this.mine))
call SetTextTagColor(tt, clr.r, clr.g, clr.b, clr.a)
call SetTextTagVelocity(tt, SPEED * Cos(ANGLE), SPEED * Sin(ANGLE))
call SetTextTagLifespan(tt, 2.)
call SetTextTagFadepoint(tt, 1.)
call SetTextTagPermanent(tt, false)
set tt = null
endif
endif
endmethod
method mute takes boolean flag returns nothing
set this.stopped = flag
endmethod
method setAmount takes integer newAmount returns nothing
set this.amount = newAmount
endmethod
static method create takes unit goldmine, player play, real timeout, playerstate resource, integer amount, Colour clr returns thistype
local thistype this = allocate()
set this.mine = goldmine
set this.interval = timeout
set this.custom = 0
set this.resource = resource
set this.amount = amount
set this.play = play
set this.clr = clr
set this.stopped = false
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, timeout, true, function thistype.callback)
return this
endmethod
static method createCustom takes unit goldmine, player play, real timeout, CustomResource cr, integer amount, Colour clr returns thistype
local thistype this = allocate()
set this.mine = goldmine
set this.interval = timeout
set this.custom = cr
set this.resource = null
set this.amount = amount
set this.play = play
set this.clr = clr
set this.stopped = false
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, timeout, true, function thistype.callback)
return this
endmethod
private static method onInit takes nothing returns nothing
set DRAGON_FAVOUR = CustomResource.create()
endmethod
endstruct
endlibrary
library DragonFavour
function SetupFavourBoard takes player play returns nothing
local integer playerNum = GetPlayerId(play) + 1// player number in JASS starts at 0
call SetPlayerAbilityAvailable(play, 'A00I', false)
call CreateUnit(play, 'H00T', 9660., -9470., bj_UNIT_FACING)
call ForceAddPlayer(udg_WorgenGroup, play)
if udg_DragonFavourBoard[playerNum] == null then
set udg_DragonFavourBoard[playerNum] = CreateMultiboard()
call MultiboardSetRowCount(udg_DragonFavourBoard[playerNum], 1)
call MultiboardSetColumnCount(udg_DragonFavourBoard[playerNum], 1)
call MultiboardSetTitleText(udg_DragonFavourBoard[playerNum], "Dragon Favor Acrued")
endif
set udg_FavourGathered[playerNum] = 0
call MultiboardSetItemValueBJ(udg_DragonFavourBoard[playerNum], 1, 1, ( udg_CONSTANT_FavourColour + ( I2S(udg_FavourGathered[playerNum]) + udg_CONSTANT_FavourEnd ) ) )
call MultiboardSetItemWidthBJ(udg_DragonFavourBoard[playerNum], 1, 1, 10.00 )
call MultiboardSetItemStyleBJ(udg_DragonFavourBoard[playerNum], 1, 1, true, false)
call MultiboardDisplay(udg_DragonFavourBoard[playerNum], true)
call MultiboardMinimize(udg_DragonFavourBoard[playerNum], false)
endfunction
function AddFavour takes player play, integer amount returns nothing
local integer playerNum = GetPlayerId(play) + 1
set udg_FavourGathered[playerNum] = udg_FavourGathered[playerNum] + amount
call MultiboardSetItemValueBJ(udg_DragonFavourBoard[playerNum], 1, 1, ( udg_CONSTANT_FavourColour + ( I2S(udg_FavourGathered[playerNum]) + udg_CONSTANT_FavourEnd ) ) )
endfunction
endlibrary
scope MarchOftheSavages initializer init
globals
private constant integer SPELL_ID = 'A051'
private constant integer SAVAGE_ID = 'o016'
private constant integer COUNT = 4
private constant real OFFSET = 110.
private constant real DIST = 200.
private constant real DURATION = 60.
private constant string SPAWN_FX = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone.mdl"
endglobals
private function Conditions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
return true
endif
return false
endfunction
private function Actions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local player owner = GetOwningPlayer(caster)
local real facing = GetUnitFacing(caster)
local real a = (facing * bj_DEGTORAD) + bj_PI
local real x = GetUnitX(caster) + Cos(a) * DIST
local real y = GetUnitY(caster) + Sin(a) * DIST
local real offset = ((OFFSET * COUNT) * .5) - (OFFSET * .5)
local integer i = 0
local unit u
set a = a - (bj_PI * .5)
set x = x + Cos(a) * (offset)
set y = y + Sin(a) * (offset)
set a = a + bj_PI
loop
set i = i + 1
exitwhen i > COUNT
set u = CreateUnit(owner, SAVAGE_ID, x, y, facing)
call DestroyEffect(AddSpecialEffect(SPAWN_FX, x, y))
call UnitApplyTimedLife(u, 'BTLF', DURATION)
set x = x + Cos(a) * OFFSET
set y = y + Sin(a) * OFFSET
endloop
set u = null
set caster = null
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(trig, Condition( function Conditions ) )
call TriggerAddAction(trig, function Actions )
set trig = null
endfunction
endscope
function Trig_March_of_the_Savages_Copy_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'A051' ) ) then
return false
endif
return true
endfunction
function Trig_March_of_the_Savages_Copy_Actions takes nothing returns nothing
set udg_CasterFacing = GetUnitFacing(GetTriggerUnit())
set udg_TempReal = ( udg_CasterFacing + 90.00 )
set udg_tempLoc[0] = GetUnitLoc(GetTriggerUnit())
set udg_tempLoc[1] = PolarProjectionBJ(udg_tempLoc[0], 200.00, ( udg_CasterFacing + 180.00 ))
set udg_tempLoc[2] = PolarProjectionBJ(udg_tempLoc[1], ( ( 110.00 * 4.00 ) * 0.50 ), udg_TempReal)
call RemoveLocation(udg_tempLoc[0])
set udg_tempLoc[0] = PolarProjectionBJ(udg_tempLoc[2], 220.00, udg_TempReal)
set udg_TempReal = ( udg_CasterFacing - 90.00 )
set bj_forLoopAIndex = 1
set bj_forLoopAIndexEnd = 5
loop
exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd
call RemoveLocation(udg_tempLoc[0])
set udg_tempLoc[0] = PolarProjectionBJ(udg_tempLoc[2], ( 110.00 * I2R(GetForLoopIndexA()) ), udg_TempReal)
call CreateNUnitsAtLoc( 1, 'o016', GetOwningPlayer(GetSpellAbilityUnit()), udg_tempLoc[0], udg_CasterFacing )
call UnitApplyTimedLifeBJ( 60, 'BTLF', GetLastCreatedUnit() )
call AddSpecialEffectLocBJ( udg_tempLoc[0], "Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl" )
call DestroyEffectBJ( GetLastCreatedEffectBJ() )
set bj_forLoopAIndex = bj_forLoopAIndex + 1
endloop
call RemoveLocation(udg_tempLoc[0])
call RemoveLocation(udg_tempLoc[1])
call RemoveLocation(udg_tempLoc[2])
endfunction
//===========================================================================
function InitTrig_March_of_the_Savages_Copy takes nothing returns nothing
set gg_trg_March_of_the_Savages_Copy = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_March_of_the_Savages_Copy, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_March_of_the_Savages_Copy, Condition( function Trig_March_of_the_Savages_Copy_Conditions ) )
call TriggerAddAction( gg_trg_March_of_the_Savages_Copy, function Trig_March_of_the_Savages_Copy_Actions )
endfunction
//==============================================================================
// 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
//====================================================================
// 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, RaceLibraries
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
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")
// NB: this is done this way because SOMEONE wants dialog boxes.
call TriggerRegisterTimerEventSingle(trig, 0.01)
call TriggerAddAction(trig, function RaceLibraries_Choices)
endfunction
endlibrary
library FelHordeSetup initializer Init requires CustomRaceSystem
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Orc",RACE_ORC,.9)
call c.setTownHall('o009') // Rampart
call c.addWorkerType('n00D',c.NEAR_MINE,5) // Slave
call c.addHeroType('O000') // Warlord
call c.addHeroType('O003') // Necrolyte
call c.addHeroType('N000') // Barbarian
call c.addHeroType('N001') // Executioner
call c.setAIScript("orc.ai")
endfunction
endlibrary
library DarkHordeSetup initializer Init requires CustomRaceSystem
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Orc",RACE_ORC,.8)
call c.setTownHall('o00Q') // Crimson Camp
call c.addWorkerType('n00S',c.NEAR_MINE,5) // Menial
call c.addHeroType('H000') // Hand of the Dragon
call c.addHeroType('H001') // Blood Shaman
call c.addHeroType('H002') // War Master
call c.addHeroType('H003') // Bombadier
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
library RaceLibraries /*
*/ uses /*
*/ SetupOrc
globals
private string CHOICE_STRING = "Chose your race."
dialog array RaceChoice
hashtable DialogHash = InitHashtable()
trigger array DialogTrigger
public button array Default
// Orc races
public button array FelHorde
public button array DarkHorde
// Human races
// Undead races
// Night Elf races
endglobals
public function Choices takes nothing returns nothing
local integer i = 0 // player 1 starts at 0 in JASS
local player play
local dialog diag
local race playerrace
loop
set play = Player(i)
set playerrace = GetPlayerRace(play)
if GetPlayerController(play) == MAP_CONTROL_USER then
if playerrace == RACE_ORC then
set diag = DialogCreate()
set RaceChoice[i] = diag
call DialogSetMessage(diag, CHOICE_STRING)
set Default[i] = DialogAddButton(diag, "Default", 0)
set FelHorde[i] = DialogAddButton(diag, "Fel Horde", 0)
set DarkHorde[i] = DialogAddButton(diag, "Dark Horde", 0)
set DialogTrigger[i] = CreateTrigger()
call TriggerRegisterDialogEvent(DialogTrigger[i], diag)
call TriggerAddAction(DialogTrigger[i], function SetupOrc_DialogClicked)
call SaveInteger(DialogHash, GetHandleId(DialogTrigger[i]), 0, i)
call DialogDisplay(play, diag, true)
elseif playerrace == RACE_HUMAN then
elseif playerrace == RACE_UNDEAD then
elseif playerrace == RACE_NIGHTELF then
endif
endif
call SetPlayerMaxHeroesAllowed(3, play)
set i = i + 1
exitwhen i > bj_MAX_PLAYER_SLOTS
endloop
set diag = null
endfunction
endlibrary
library SetupOrc uses DragonFavour
globals
// CONFIG
private integer BASE_WORKER = 'opeo'
private integer NEW_WORKER_FEL = 'n00D'
private integer NEW_WORKER_DARK = 'n00S'
private integer BASE_HALL = 'ogre'
private integer NEW_HALL_FEL = 'o009'
private integer NEW_HALL_DARK = 'o00Q'
// END CONFIG
endglobals
public function DialogClicked takes nothing returns nothing
local trigger trig = GetTriggeringTrigger()
local integer trigId = GetHandleId(trig)
local integer i = LoadInteger(DialogHash, trigId, 0)
local player play = Player(i)
local group g
local unit u
// Fel Horde
if GetClickedButton() == RaceLibraries_FelHorde[i] then
set g = CreateGroup()
call GroupEnumUnitsOfPlayer(g, play, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if GetUnitTypeId(u) == BASE_WORKER and GetOwningPlayer(u) == play then
call ReplaceUnitBJ(u, NEW_WORKER_FEL, bj_UNIT_STATE_METHOD_RELATIVE)
elseif GetUnitTypeId(u) == BASE_HALL and GetOwningPlayer(u) == play then
call RemoveUnit(u)
call CreateUnit(play, NEW_HALL_FEL, GetPlayerStartLocationX(play), GetPlayerStartLocationY(play), bj_UNIT_FACING)
endif
endloop
call DestroyGroup(g)
// Dark Horde
elseif GetClickedButton() == RaceLibraries_DarkHorde[i] then
set g = CreateGroup()
call GroupEnumUnitsOfPlayer(g, play, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if GetUnitTypeId(u) == BASE_WORKER and GetOwningPlayer(u) == play then
call ReplaceUnitBJ(u, NEW_WORKER_DARK, bj_UNIT_STATE_METHOD_RELATIVE)
elseif GetUnitTypeId(u) == BASE_HALL and GetOwningPlayer(u) == play then
call RemoveUnit(u)
call CreateUnit(play, NEW_HALL_DARK, GetPlayerStartLocationX(play), GetPlayerStartLocationY(play), bj_UNIT_FACING)
endif
endloop
call DestroyGroup(g)
call SetupFavourBoard(play)
endif
call FlushChildHashtable(DialogHash, trigId)
call DialogDestroy(RaceChoice[i])
call DestroyTrigger(DialogTrigger[i])
endfunction
endlibrary