//TESH.scrollpos=0
//TESH.alwaysfold=1
function GetHost takes nothing returns nothing
local gamecache g = InitGameCache("Map.w3v")
call StoreInteger ( g, "Map", "Host", GetPlayerId(GetLocalPlayer ())+1)
call TriggerSyncStart ()
call SyncStoredInteger ( g, "Map", "Host" )
call TriggerSyncReady ()
set udg_Host = Player( GetStoredInteger ( g, "Map", "Host" )-1)
call FlushGameCache( g )
set g = null
endfunction
function DeclareCPUNames takes nothing returns nothing
set udg_ComputerNames[1] = "Jack Sparrow"
set udg_ComputerNames[2] = "Devin Townsend"
set udg_ComputerNames[3] = "Barack Obama"
set udg_ComputerNames[4] = "Katy Perry"
set udg_ComputerNames[5] = "Christopher Lee"
set udg_ComputerNames[6] = "Lemmy Kilmister"
set udg_ComputerNames[7] = "Tom Araya"
set udg_ComputerNames[8] = "Christian Bale"
set udg_ComputerNames[9] = "Lenny Kravitz"
set udg_ComputerNames[10] = "Tom Hanks"
set udg_ComputerNames[10] = "Master Yoda"
set udg_ComputerNames[11] = "Bugs Bunny"
set udg_ComputerNames[12] = "Tom"
set udg_ComputerNames[13] = "Jerry"
set udg_ComputerNames[14] = "Tony Hawk"
set udg_ComputerNames[15] = "Hans Zimmer"
set udg_ComputerNames[16] = "Count Douku"
set udg_ComputerNames[17] = "Peter Pan"
set udg_ComputerNames[18] = "Leeroy Jenkins"
set udg_ComputerNames[19] = "Google"
set udg_ComputerNames[20] = "Facebook"
set udg_ComputerNames[21] = "Ian Gillan"
set udg_ComputerNames[22] = "Morgan Freeman"
set udg_ComputerNames[23] = "Samurai Jack"
set udg_ComputerNames[24] = "Adele"
set udg_ComputerNames[25] = "Garry Kasparov"
set udg_ComputerNames[26] = "Magnus Carlsen"
set udg_ComputerNames[27] = "Abraham Lincoln"
set udg_ComputerNames[28] = "Bill Gates"
set udg_ComputerNames[29] = "Elvis Presley "
set udg_ComputerNames[30] = "Charles Darwin"
set udg_ComputerNames[31] = "Einstein"
set udg_ComputerNames[32] = "Plato"
set udg_ComputerNames[33] = "Walt Disney "
set udg_ComputerNames[34] = "Dalai Lama"
set udg_ComputerNames[35] = "Harry Potter"
set udg_ComputerNames[35] = "Angelina Jolie"
set udg_ComputerNames[36] = "Mr. Lonely"
set udg_ComputerNames[37] = "Superman"
set udg_ComputerNames[38] = "Batman"
set udg_ComputerNames[39] = "Bloody Mary"
set udg_ComputerNames[40] = "The Flash"
set udg_ComputerNames[41] = "Professor Evil"
set udg_ComputerNames[42] = "Captain America"
set udg_ComputerNames[43] = "Optimus Prime"
set udg_ComputerNames[44] = "Maximus"
set udg_ComputerNames[45] = "Legolas"
set udg_ComputerNames[46] = "Inspector Gatget"
set udg_ComputerNames[47] = "Groot"
set udg_ComputerNames[48] = "Gromit"
set udg_ComputerNames[49] = "Ethan Hunt"
set udg_ComputerNames[50] = "Bane"
set udg_ComputerNames[51] = "Donnie Darko"
set udg_ComputerNames[52] = "Tony Montana"
set udg_ComputerNames[53] = "Indiana Jones"
set udg_ComputerNames[54] = "Ace Ventura"
set udg_ComputerNames[55] = "Darth Vader"
set udg_ComputerNames[56] = "Dr. Strangelove"
set udg_ComputerNames[57] = "Edward Scissorhands"
set udg_ComputerNames[58] = "John McClane"
set udg_ComputerNames[59] = "Willy Wonka"
set udg_ComputerNames[60] = "Robin Hood"
set udg_ComputerNames[61] = "Hannibal Lecter "
set udg_ComputerNames[62] = "Gollum"
set udg_ComputerNames[63] = "Mordor"
set udg_ComputerNames[64] = "James Bond"
set udg_ComputerNames[65] = "The Joker"
set udg_ComputerNames[66] = "Mr. Smith"
set udg_ComputerNames[67] = "Forrest Gump "
set udg_ComputerNames[68] = "The One"
set udg_ComputerNames[69] = "Han Solo"
set udg_ComputerNames[70] = "Rocky"
set udg_ComputerNames[71] = "Count Dracula "
set udg_ComputerNames[72] = "Rambo"
set udg_ComputerNames[73] = "Mad Max"
set udg_ComputerNames[74] = "Austin Powers"
set udg_ComputerNames[75] = "Tarzan"
set udg_ComputerNames[76] = "Goku"
set udg_ComputerNames[77] = "Maggie"
set udg_ComputerNames[78] = "Dominic Toretto"
set udg_ComputerNames[79] = "Spider-Man"
set udg_ComputerNames[80] = "Tarja"
set udg_ComputerNames[81] = "James Hetfield"
set udg_ComputerNames[82] = "Ozzy Osbourne"
set udg_ComputerNames[83] = "Fallen Angel"
set udg_ComputerNames[84] = "Serj Tankian"
set udg_ComputerNames[85] = "Serj Tankian"
set udg_ComputerNames[86] = "Axl Rose"
set udg_ComputerNames[87] = "Alice Cooper"
set udg_ComputerNames[88] = "Eric Draven"
set udg_ComputerNames[89] = "Iron Man"
set udg_ComputerNames[90] = "Will Smith"
set udg_ComputerNames[91] = "Terminator"
set udg_ComputerNames[92] = "Sweeney Todd"
set udg_ComputerNames[93] = "The Dude"
set udg_ComputerNames[94] = "Marty McFly"
set udg_ComputerNames[95] = "John Coffey"
set udg_ComputerNames[96] = "Agent Smith"
set udg_ComputerNames[97] = "The Man With No Name"
set udg_ComputerNames[98] = "Jackie Chan"
set udg_ComputerNames[99] = "Jet Li"
set udg_ComputerNames[100] = "Bruce Lee"
set udg_ComputerNames[101] = "Neo"
set udg_ComputerNames[102] = "E.T."
set udg_ComputerNames[103] = "Aragorn"
set udg_ComputerNames[104] = "Ip Man"
set udg_ComputerNames[105] = "House"
set udg_ComputerNames[106] = "House M.D."
set udg_ComputerNames[107] = "Freddy Krueger"
set udg_ComputerNames[108] = "The Running Man"
set udg_ComputerNames[109] = "Gandalf"
set udg_ComputerNames[110] = "Marv"
set udg_ComputerNames[111] = "King Leonidas"
set udg_ComputerNames[112] = "Hellboy"
set udg_ComputerNames[113] = "Rorschach"
set udg_ComputerNames[114] = "Santa Claus"
set udg_ComputerNames[115] = "Mr. Incredible"
set udg_ComputerNames[116] = "Shrek"
set udg_ComputerNames[117] = "Jake Sully"
set udg_ComputerNames[118] = "Benjamin Button"
set udg_ComputerNames[119] = "Eli"
set udg_ComputerNames[120] = "Wolverine"
set udg_ComputerNames[121] = "Wall-e"
set udg_ComputerNames[122] = "Newton"
set udg_ComputerNames[123] = "Don Quixote"
set udg_ComputerNames[124] = "Sherlock Holmes"
set udg_ComputerNames[125] = "Frankenstein"
set udg_ComputerNames[126] = "Robinson Crusoe"
set udg_ComputerNames[127] = "Big Brother"
set udg_ComputerNames[128] = "Death"
set udg_ComputerNames[129] = "The Grim Reaper"
set udg_ComputerNames[130] = "Zeus"
set udg_ComputerNames[131] = "Hercules"
set udg_ComputerNames[132] = "Achilles"
set udg_ComputerNames[132] = "Ares"
set udg_ComputerNames[133] = "Hades"
set udg_ComputerNames[134] = "Thor"
set udg_ComputerNames[135] = "Justin Bieber"
set udg_ComputerNames[136] = "Eminem"
set udg_ComputerNames[137] = "The Sun"
set udg_ComputerNames[137] = "Slash"
set udg_ComputerNames[138] = "Killer Bee"
set udg_ComputerNames[139] = "Romeo"
set udg_ComputerNames[140] = "Ugly Betty"
set udg_ComputerNames[141] = "Mr. Robot"
set udg_ComputerNames[142] = "Daredevil"
set udg_ComputerNames[143] = "Moby-Dick"
set udg_ComputerNames[144] = "Carlson"
set udg_ComputerNames[145] = "Aku"
set udg_ComputerNames[146] = "Naruto"
set udg_ComputerNames[147] = "Mickey Mouse"
set udg_ComputerNames[148] = "Z"
set udg_ComputerNames[149] = "Kim"
set udg_ComputerNames[150] = "Satan"
set udg_ComputerNames[151] = "Roger Rabbit"
set udg_ComputerNames[152] = "Homer Simpson"
set udg_ComputerNames[153] = "Kung Fu Panda"
set udg_ComputerNames[154] = "Captain Hook"
set udg_ComputerNames[155] = "Genius"
set udg_ComputerNames[156] = "Hulk"
set udg_ComputerNames[157] = "Apocalypse"
set udg_ComputerNames[158] = "John Locke"
set udg_ComputerNames[159] = "Boogeyman"
set udg_ComputerNames[160] = "Dexter"
set udg_ComputerNames[161] = "Johnny Bravo"
set udg_ComputerNames[162] = "V"
set udg_ComputerNames[163] = "Serious Sam"
set udg_ComputerNames[164] = "Donkey Kong"
set udg_ComputerNames[165] = "Mr. X"
set udg_ComputerNames[166] = "Pac Man"
set udg_ComputerNames[167] = "Big Daddy"
set udg_ComputerNames[168] = "Scorpion"
set udg_ComputerNames[169] = "Nathan Drake"
set udg_ComputerNames[170] = "Agent 47"
set udg_ComputerNames[171] = "Duke Nukem"
set udg_ComputerNames[172] = "Alice"
set udg_ComputerNames[173] = "Solid Snake"
set udg_ComputerNames[174] = "Kratos"
set udg_ComputerNames[175] = "Sonic"
set udg_ComputerNames[176] = "Master Chief"
set udg_ComputerNames[177] = "Lara Croft"
set udg_ComputerNames[178] = "The Nameless One"
set udg_ComputerNames[179] = "Mario"
set udg_ComputerNames[180] = "Luigi"
set udg_ComputerNames[181] = "Megaman"
set udg_ComputerNames[182] = "Deadpool"
set udg_ComputerNames[183] = "The Thing"
set udg_ComputerNames[184] = "Private Ryan"
set udg_ComputerNames[185] = "Obelix"
set udg_ComputerNames[186] = "Napoleon"
set udg_ComputerNames[187] = "Jesus"
set udg_ComputerNames[188] = "Hitler"
set udg_ComputerNames[189] = "Caesar"
set udg_ComputerNames[190] = "Mozart"
set udg_ComputerNames[191] = "Elvis Presley"
set udg_ComputerNames[192] = "Lenin"
set udg_ComputerNames[193] = "Kim"
set udg_ComputerNames[194] = "R2-D2"
set udg_ComputerNames[195] = "Dr. Emmett Brown"
set udg_ComputerNames[196] = "Mr. Bean"
set udg_ComputerNames[197] = "King Kong"
set udg_ComputerNames[198] = "C-3PO"
set udg_ComputerNames[199] = "Frodo"
set udg_ComputerNames[200] = "Leon"
set udg_ComputerNames[201] = "Morpheus"
set udg_ComputerNames[202] = "Spartacus"
set udg_ComputerNames[203] = "Dirty Harry"
set udg_ComputerNames[204] = "Sauron"
set udg_ComputerNames[205] = "Sirius Black"
set udg_ComputerNames[206] = "Saruman"
set udg_ComputerNames[207] = "Data"
set udg_ComputerNames[208] = "Silent Bob"
set udg_ComputerNames[209] = "Blade"
set udg_ComputerNames[210] = "Mr. Pink"
set udg_ComputerNames[211] = "Mini-Me"
set udg_ComputerNames[212] = "Django"
set udg_ComputerNames[213] = "Steve Stifler"
set udg_ComputerNames[214] = "Magneto"
set udg_ComputerNames[215] = "Triple H"
set udg_ComputerNames[216] = "The Rock"
set udg_ComputerNames[217] = "John Cena"
set udg_ComputerNames[218] = "The Big Show"
set udg_ComputerNames[219] = "Sting"
set udg_ComputerNames[220] = "The Undertaker"
set udg_ComputerNames[221] = "Wikipedia"
set udg_ComputerNames[221] = "Kreator"
set udg_ComputerNames[222] = "Happy Camper "
set udg_ComputerNames[223] = "Sinner"
set udg_ComputerNames[224] = "Wishmaster"
set udg_ComputerNames[225] = "Nemo"
set udg_ComputerNames[226] = "Lady Gaga"
set udg_ComputerNames[227] = "Hikaru Nakamura"
set udg_ComputerNames[228] = "Bobby Fischer"
set udg_ComputerNames[229] = "Wreck-it Ralph"
set udg_ComputerNames[230] = "Paul Morphy"
set udg_ComputerNames[231] = "Levon Aronian"
set udg_ComputerNames[232] = "Katnis Everdeen"
set udg_ComputerNames[233] = "Popeye"
set udg_ComputerNames[234] = "Mighty Mouse"
set udg_ComputerNames[235] = "Wonder Woman"
set udg_ComputerNames[236] = "He-Man"
set udg_ComputerNames[237] = "Amelie Poulain"
set udg_ComputerNames[238] = "Mighty Mouse"
set udg_ComputerNames[239] = "Ghost Rider"
set udg_ComputerNames[240] = "Metallica"
set udg_ComputerNames[242] = "Exciter"
set udg_ComputerNames[243] = "Freddy Kruger"
set udg_ComputerNames[244] = "The Invisible Man"
set udg_ComputerNames[243] = "John Kramer"
set udg_ComputerNames[245] = "Kylo Ren"
set udg_ComputerNames[246] = "Meryl Streep"
set udg_ComputerNames[247] = "Doc Brown"
set udg_ComputerNames[248] = "Killmonger"
set udg_ComputerNames[249] = "Loki"
set udg_ComputerNames[250] = "John Wick"
set udg_ComputerNames[251] = "Anton Chigurh"
set udg_ComputerNames[252] = "Jack The Ripper"
set udg_ComputerNames[253] = "Jason Voorhees"
set udg_ComputerNames[254] = "Ghostface"
set udg_ComputerNames[255] = "Ted Bundy"
set udg_ComputerNames[256] = "George Washington"
set udg_ComputerNames[257] = "Julius Caesar"
set udg_ComputerNames[258] = "Karl Max"
set udg_ComputerNames[259] = "Napoleon Bonaparte"
set udg_ComputerNames[260] = "Michelangelo"
set udg_ComputerNames[261] = "Joseph Stalin"
set udg_ComputerNames[262] = "Anne Frank"
set udg_ComputerNames[263] = "Buddha"
set udg_ComputerNames[264] = "Confucius"
set udg_ComputerNames[265] = "Christopher Columbus"
set udg_ComputerNames[266] = "Marlon Brando"
endfunction
Name | Type | is_array | initial_value |
_DTime | dialog | No | |
_TimerVoteButton15 | button | No | |
_TimerVoteButton30 | button | No | |
_TimerVoteButton45 | button | No | |
_TimerVoteButton60 | button | No | |
_TPVote | integer | Yes | |
Ability | abilcode | Yes | |
AbyssOfTeam | unit | Yes | |
Bool_Will_Be_Replaced_By_CPU | boolean | Yes | |
Color_B | real | Yes | |
Color_G | real | Yes | |
Color_Player_NonString | playercolor | Yes | |
Color_Player_String | string | Yes | |
Color_R | real | Yes | |
Color_Team | string | Yes | |
Computer_Players_Gr | group | No | |
ComputerNames | string | Yes | |
Count_Cross_Votes_No | integer | No | |
Count_Cross_Votes_Yes | integer | No | |
Count_Voted | integer | No | |
CountedDeadHeroes_Team | integer | Yes | |
DAMAGE_DONE | integer | Yes | |
damageEventTrigger | real | No | |
DEAD_GR | group | Yes | |
Dialog_AllLeft | dialog | No | |
Dialog_AskDisconnected | dialog | No | |
Dialog_B_Shape | button | Yes | |
Dialog_LOS | dialog | No | |
Dialog_LOS_AA | button | No | |
Dialog_LOS_Both | button | No | |
Dialog_LOS_None | button | No | |
Dialog_LOS_Spells | button | No | |
Dialog_Shape | dialog | No | |
DialogDisc_Yes | button | No | |
EndGame_Timer | timer | No | |
EngGame_Poistion | integer | Yes | |
Fade_Visibility | real | No | 1.00 |
Fullfillment | real | No | |
Game_Ended | boolean | No | |
GameInProgress | boolean | No | |
GateIsClosed | boolean | Yes | |
GR_match | group | No | |
Group_Players_All | force | No | |
Group_Players_Computer | force | No | |
Group_Players_Human | force | No | |
Hardened_Skin | real | No | |
Hero | unit | Yes | |
Hero_Type | unitcode | Yes | |
Hero_Type_Busy | boolean | Yes | |
HINT | string | Yes | |
Host | player | No | |
Item_Dropped | item | Yes | |
Item_Dropped_Counter | integer | No | |
Item_Dropped_IsNotLabeled | boolean | No | true |
Item_Dropped_Text_Owner | texttag | Yes | |
Item_Health_Stone | boolean | Yes | |
Item_Mana_stone | boolean | Yes | |
item_unieiq_int | integer | Yes | |
item_unique | item | Yes | |
item_unique_integer | integer | Yes | |
ItemType | itemcode | Yes | |
ItemType_Check | integer | No | |
Leaderboard | leaderboard | Yes | |
Level | integer | No | 1 |
Level_Timer | timer | No | |
LoopInt | integer | No | |
LOS_Angle | real | No | |
LOS_Caster | unit | No | |
LOS_Distance | real | No | |
LOS_PointCaster | location | No | |
LOS_PointTarget | location | No | |
LOS_Region | rect | Yes | |
LOS_RegionBlock_Enabled | boolean | Yes | |
LOS_TempPoint | location | No | |
Multiboard | multiboard | No | |
Num_KIlled | integer | No | |
Num_Killer | integer | No | |
Number_of_computers | integer | No | |
PDD_allocatedAttacks | integer | No | |
PDD_allocCounter | integer | No | |
PDD_amount | real | No | |
PDD_ATTACK_TYPE_UNIVERSAL | attacktype | No | |
PDD_BRACERS_SPELL_DMG_RED | real | No | |
PDD_CODE | integer | No | |
PDD_DAMAGE_TYPE_DETECTOR | integer | No | |
PDD_damageEvent | trigger | No | |
PDD_damageEventTrigger | real | No | |
PDD_damageHandler | trigger | No | |
PDD_damageType | integer | No | |
PDD_ETHEREAL_DAMAGE_FACTOR | real | No | |
PDD_h | hashtable | No | |
PDD_PHYSICAL | integer | No | |
PDD_pureAmount | real | No | |
PDD_runAllocatedAttacks | trigger | No | |
PDD_SET_MAX_LIFE | integer | No | |
PDD_source | unit | No | |
PDD_SPELL | integer | No | |
PDD_SPELL_DMG_REDUCTION_ITEM | integer | No | |
PDD_SPELL_RESIST_AUTO_DETECT | boolean | No | |
PDD_target | unit | No | |
PDD_totalAllocs | integer | No | |
PDD_TRIGGER_CLEANUP_PERIOD | real | No | |
PDD_UNIT_MIN_LIFE | real | No | |
Pedestral_Pos_losing | location | Yes | |
Pedestral_Pos_Winning | location | Yes | |
Pets_Gr | group | No | |
Pick_Up_Types | group | No | |
Pick_Up_Types_Unit | unit | Yes | |
PickedShape | boolean | No | |
Player_Abyss | unit | Yes | |
Player_Busy | boolean | Yes | |
Player_Spectating | integer | Yes | |
PlayerCameraPos | location | No | |
Players_Camera_Height | real | Yes | |
Players_Picked | integer | No | |
Players_Team | force | Yes | |
Players_Total | integer | No | 10 |
PlayersLeft_Calc | integer | Yes | |
PlayersTeam | integer | Yes | |
QuickSelect_PersonalCounter | integer | Yes | |
QuickSelect_PersonalStatues | unit | Yes | |
QuickSelect_PlayerTeamNumber | integer | Yes | |
random | integer | No | |
random2 | integer | No | |
RandomUnit | unit | No | |
Result | integer | Yes | |
Round_Time | real | No | |
Selection_Dummy | unit | Yes | |
Shapes_Text | string | Yes | |
Sound_Leaver | sound | Yes | |
source | unit | No | |
Start_Loc | location | Yes | |
Statues_Temp | group | No | |
Team_Color | string | Yes | |
TEAM_HEROES | group | Yes | |
TeamLosing | integer | Yes | |
TeamWinning | integer | Yes | |
Temp_Upgrading_Player | player | No | |
TempCalc | integer | No | |
TempCharges | integer | No | |
TempGold | integer | Yes | |
TempGold_Real | real | No | |
TempItem | item | No | |
TempItemId | itemcode | No | |
TempLoop | integer | No | |
TempN | integer | No | |
TempNumS | string | No | |
TempP | location | No | |
TempP2 | location | No | |
TempPForCamera | location | No | |
TempPlayer | player | No | |
TempPosition | location | No | |
TempString | string | No | |
TempUnit | unit | No | |
TempUnitGr | group | No | |
TempUnitType | unitcode | No | |
Timer_Resurrect | timer | No | |
Timer_window | timerdialog | No | |
TimerEqualVotes | timer | No | |
TPVoteCheck | integer | No | |
Trigger_EndGame | trigger | No | |
Trigger_Shapes | trigger | Yes | |
UniqueItems | itemcode | Yes | |
Upgrade_Triggers | trigger | Yes | |
Voted_LOS | integer | No | |
Voted_Shapes | integer | No | |
Votes_Shapes | integer | Yes | |
Voting_LOS | integer | Yes | |
Wall_Bot_Right | destructable | No | |
Wall_Bottom | destructable | No | |
Wall_Mid_Right | destructable | No | |
Wall_Middle | destructable | No | |
Wall_Top | destructable | No | |
Wall_Top_Left | destructable | No | |
Wall_Top_Right | destructable | No | |
Wall_Vertical_Left | destructable | No | |
Wall_Vertical_Mid | destructable | No | |
Wall_Vertical_Right | destructable | No |
//TESH.scrollpos=0
//TESH.alwaysfold=1
function CountItemsById takes unit u, integer i returns integer
local integer x=0
local integer count=0
loop
exitwhen x > bj_MAX_INVENTORY
if GetItemTypeId(UnitItemInSlot(u,x))==i then
set count=count+1
endif
set x=x+1
endloop
return count
endfunction
//-------------------------------------------------------------------------
function ItemsStack takes unit u, integer itemId, integer limit, integer addLumber returns nothing
local integer charges = 0
local integer x = 0
local item it
local integer howMany = CountItemsById(u, itemId)
if howMany >1 then
loop
exitwhen x > bj_MAX_INVENTORY
set it = UnitItemInSlot(u, x)
if GetItemTypeId(it) == itemId then
set charges = charges + GetItemCharges(it)
endif
set it=null
set x=x+1
endloop
set x=0
loop
exitwhen x > bj_MAX_INVENTORY
set it = UnitItemInSlot(u, x)
if GetItemTypeId(it) == itemId then
call SetItemCharges(it, charges)
if howMany > 1 then
call RemoveItem(it)
endif
set howMany = howMany - 1
endif
set it=null
set x=x+1
endloop
set x = 0 // lumber refund:
loop
exitwhen x > bj_MAX_INVENTORY
set it = UnitItemInSlot(u, x)
if GetItemTypeId(it) == itemId and GetItemCharges(it) > limit then
call AdjustPlayerStateBJ( addLumber*(GetItemCharges(it)-limit), GetOwningPlayer(u), PLAYER_STATE_RESOURCE_LUMBER )
call SetItemCharges(it, limit)
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Items\\ResourceItems\\ResourceEffectTarget.mdl", u, "origin"))
endif
set it=null
set x=x+1
endloop
endif
endfunction
//--------------------------------------
function SetAbilityFromItem takes unit u, integer itemId returns nothing
local integer a=1
local integer x=0
local integer charges=0
loop
exitwhen a>144
if itemId==udg_ItemType[a] then
set x=0
loop //set charges:
exitwhen x > bj_MAX_INVENTORY
if GetItemTypeId(UnitItemInSlot(u, x)) == itemId then
set charges = GetItemCharges(UnitItemInSlot(u, x))
exitwhen true
endif
set x=x+1
endloop
if GetUnitAbilityLevel(u, udg_Ability[a]) < charges then
call UnitAddAbility(u, udg_Ability[a])
call SetUnitAbilityLevel(u, udg_Ability[a], charges)
endif
endif
set a=a+1
endloop
endfunction
//-------------------------------
function SetDevourAbilityFromItem takes unit u, integer itemId returns nothing
local integer x=0
local integer charges=0
local integer itemDevour = 'DEr3' //YOUR ITEM DEVOUR
local integer abilityDevour = 'deqC' //YOUR ABILITY DEVOUR
loop
exitwhen x > bj_MAX_INVENTORY
if GetItemTypeId(UnitItemInSlot(u, x)) == itemDevour then
set charges = GetItemCharges(UnitItemInSlot(u, x))
exitwhen true
endif
set x=x+1
endloop
if GetUnitAbilityLevel(u, abilityDevour) < charges then
call UnitAddAbility(u, abilityDevour)
call SetUnitAbilityLevel(u, abilityDevour, charges)
call DisplayTextToPlayer(GetOwningPlayer(u), 0.00, 0.00, "Ability " + GetObjectName(abilityDevour) + " level set to: " + I2S(charges))
endif
endfunction
//======================================================
function InitTrig_function takes nothing returns nothing
endfunction
//TESH.scrollpos=24
//TESH.alwaysfold=0
function IsUnique takes integer id returns boolean
set bj_forLoopBIndex = 0
set bj_forLoopBIndexEnd = 999
loop
exitwhen bj_forLoopBIndex > bj_forLoopBIndexEnd
if (udg_UniqueItems[bj_forLoopBIndex] == id) then
return true
endif
set bj_forLoopBIndex = bj_forLoopBIndex + 1
endloop
return false
endfunction
function PlainName takes item Item returns string
local string Name = GetItemName(Item)
local integer L = StringLength(Name)
local string nName = ""
if (SubStringBJ(Name, 1, 2) == "|c") then
if (SubStringBJ(Name, (L - 4), (L - 3)) == " +") then
set nName = SubStringBJ(Name, 11, (L - 5))
else
set nName = SubStringBJ(Name, 11, (L - 2))
endif
else
if (SubStringBJ(Name, (L - 2), (L - 1)) == " +") then
set nName = SubStringBJ(Name, 1, (L - 3))
else
set nName = Name
endif
endif
return nName
endfunction
function Unique_Item_System_Actions takes nothing returns nothing
local string Name1 = ""
local string Name2 = ""
local item Item = null
set bj_forLoopAIndex = 1
set bj_forLoopAIndexEnd = 6
loop
exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd
set Item = UnitItemInSlotBJ(GetTriggerUnit(), bj_forLoopAIndex)
set Name1 = PlainName(Item)
set Name2 = PlainName(GetManipulatedItem())
if (Item != GetManipulatedItem() and (GetItemTypeId(Item) == GetItemTypeId(GetManipulatedItem()) or Name1 == Name2)) then
if (IsUnique(GetItemTypeId(Item)) == true or IsUnique(GetItemTypeId(GetManipulatedItem()))) then
call UnitRemoveItemSwapped(GetManipulatedItem(), GetTriggerUnit())
call DisplayTextToPlayer(GetOwningPlayer(GetTriggerUnit()), 0, 0, "You can only carry 1 of that item!")
endif
endif
set bj_forLoopAIndex = bj_forLoopAIndex + 1
endloop
set Name1 = ""
set Name2 = ""
set Item = null
endfunction
//===========================================================================
function InitTrig_Unique_Item_System takes nothing returns nothing
set gg_trg_Unique_Item_System = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Unique_Item_System, EVENT_PLAYER_UNIT_PICKUP_ITEM)
call TriggerAddAction(gg_trg_Unique_Item_System, function Unique_Item_System_Actions)
endfunction
//TESH.scrollpos=644
//TESH.alwaysfold=0
////////////////////////////////////////////////////////////////////////////////////
//
// Physical Damage Detection Engine GUI v 1.3.0.0
// ----------------------------------------------
// By looking_for_help aka eey
//
// This system is able to detect, modify and deal damage. It can differentiate
// between physical and spell damage, as well as block, reduce or increase the
// applied damage to a desired value. You can also allocate damage events from
// running damage events.
//
// This is the GUI version of the system, meaning that you can use this with the
// standard editor and basically without any knowledge of JASS.
//
////////////////////////////////////////////////////////////////////////////////////
//
// Implementation
// --------------
// 1. Create all variables that this system uses (compare the variable editor).
// You don't have to set them to specific values, they get initialized
// automatically by the system so just create them.
// 2. Copy this trigger to your map. You can then add damage handlers by using
// the event "value of real variable becomes equal to 1" with the variable
// udg_PDD_damageEventTrigger. Compare the OnDamage trigger for an example usage.
// 3. Copy the two custom abilities to your map and make sure they have the
// correct ID. You can specify the ID in the function InitGlobalVariables
// above.
// 4. Go to the locust swarm ability and invert its damage return portion
// from (default) 0.75 to -0.75.
// 5. Remove the spell damage reduction ability from the spell damage reduction
// items you use (runed bracers). You can configure the resistance of this
// item in the InitGlobalVariables function, modifying the global variable
// udg_PDD_BRACERS_SPELL_DMG_RED.
//
////////////////////////////////////////////////////////////////////////////////////
//
// Important Notes
// ---------------
// 1. Life Drain does not work with this system, so you should use a triggered
// version of this spell if you want to use it.
// 2. Same for Finger of Death, if you want to use this spell bug free with this
// system, you should use a triggered version of it.
// 3. If you use damage modifiers by setting the damage amount variable, you have
// to use GetUnitLife, GetUnitMaxLife and SetUnitLife instead of GetWidgetLife,
// GetUnitState for UNIT_STATE_MAX_LIFE and SetWidgetLife in your whole map to
// ensure there occure no bugs.
// 4. The boolean udg_PDD_SPELL_RESIST_AUTO_DETECT is only neccessary set to true
// if you want to use a customized damage table with spell damage resistance
// above 100%. If this is not the case, it should be set to false, as the
// system is faster then and still works correct.
// 5. As already mentioned you can't use the spell reduction ability when using this
// system (runed bracers and elunes grace). If you want to use them, you can
// trigger them by using the damage modifiers. Runed bracers is already considered
// in this system, so you don't have to code it.
//
////////////////////////////////////////////////////////////////////////////////////
//
// System API
// ----------
// real damageEventTrigger
// - Use the event "value of real variable becomes equal to 1" to detect a damage
// event. In this event you can use the following API. Compare the OnDamage
// trigger for an example usage.
//
// unit target
// - In this global unit variable, the damaged unit is saved. Don't write any-
// thing into this variable, use it as readonly.
//
// unit source
// - In this global unit variable, the damage source is saved. Don't write any-
// thing into this variable, use it as readonly.
//
// real amount
// - In this global real variable, the amount of damage is saved. This amount
// can be modified by simply setting it to the desired amount that should be
// applied to the target. Set the amount to 0.0 to block the damage completly.
// Negative values will result in heal.
//
// integer damageType
// - In this global integer variable, the damage type of the current damage is
// saved. Use it to differentiate between physical, spell and code damage. The
// variable can takes the values PHYSICAL == 0, SPELL == 1 and CODE == 2. Don't
// write anything into this variable, use it as readonly.
//
// function GetUnitLife takes unit u returns real
// - Use this function instead of the GetWidgetLife native. It ensures that you
// get the correct health value, even when damage modifiers are applied. If
// you don't use damage modifiers at all, you don't need this function.
//
// function GetUnitMaxLife takes unit u returns real
// - Use this function instead of the GetUnitState(u, UNIT_STATE_MAX_LIFE) native.
// It will return the correct value, even when damage modifiers are applied. If
// you don't use damage modifiers at all, you don't need this function.
//
// function SetUnitLife takes unit u, real newLife returns nothing
// - Use this function instead of the SetWidgetLife(u, newLife) native if you use
// damage modifiers in your map. Same rules as for the GetUnitMaxLife and the
// GetUnitMaxLife functions.
//
// function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount, boolean attack, boolean ranged, attacktype localAttackType, damagetype localDamageType, weapontype localWeaponType returns boolean
// - Use this function to deal damage from a running damage event. It works exactly
// the same as the native UnitDamageTarget but is recursion safe so that you can
// realize damage reflection for example with this. Don't ever use this function
// outside of an onDamage event.
//
// function AddDamageHandler takes code damageHandler returns nothing
// - Allows you to add a damage handler function to the system. This is not
// required for GUI users, only for vanilla JASS users. GUI users should
// use the real variable damageEventTrigger to add damage handlers to this
// system as explained above.
//
// function RemoveDamageHandler takes code damageHandler returns nothing
// - Allows you to remove a damage handler function from the system dynamic-
// ally. If you added the same handler function multiple times to the sys-
// tem, this function will remove all of these equal functions. This is not
// required for GUI users, only for vanilla JASS users.
//
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// Configurable globals
//////////////////////////////////////////////////////////////////////////////////
function InitGlobalVariables takes nothing returns nothing
// Put here the correct ability IDs
set udg_PDD_DAMAGE_TYPE_DETECTOR = 'A000'
set udg_PDD_SET_MAX_LIFE = 'A001'
// Here you can configure some stuff, read the documentation for further info
set udg_PDD_SPELL_DMG_REDUCTION_ITEM = 'brac'
set udg_PDD_SPELL_RESIST_AUTO_DETECT = false
set udg_PDD_ETHEREAL_DAMAGE_FACTOR = 1.66
set udg_PDD_BRACERS_SPELL_DMG_RED = 0.33
set udg_PDD_TRIGGER_CLEANUP_PERIOD = 60.0
endfunction
//////////////////////////////////////////////////////////////////////////////////
// End of configurable globals
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// User functions
//////////////////////////////////////////////////////////////////////////////////
function AddDamageHandler takes code func returns nothing
local integer id = GetHandleId(Condition(func))
local integer index = 0
// Loop to manage equal damage handlers
loop
exitwhen ( LoadTriggerConditionHandle(udg_PDD_h, id, index) == null )
set index = index + 1
endloop
// Store the desired damage handler function
call SaveTriggerConditionHandle(udg_PDD_h, id, index, TriggerAddCondition(udg_PDD_damageHandler, Filter(func)))
endfunction
function RemoveDamageHandler takes code func returns nothing
local integer id = GetHandleId(Condition(func))
local integer index = 0
// Loop through all equal damage handlers
loop
exitwhen ( LoadTriggerConditionHandle(udg_PDD_h, id, index) == null )
call TriggerRemoveCondition(udg_PDD_damageHandler, LoadTriggerConditionHandle(udg_PDD_h, id, index))
set index = index + 1
endloop
// Clean things up
call FlushChildHashtable(udg_PDD_h, id)
endfunction
function GetUnitLife takes unit u returns real
local boolean duringModification
local integer uId = GetHandleId(u)
local real health
local real storedHealth = LoadReal(udg_PDD_h, uId, 2)
local real storedDamage = LoadReal(udg_PDD_h, uId, 1)
// Check if the unit is being rescued from damage
set duringModification = GetUnitAbilityLevel(u, udg_PDD_SET_MAX_LIFE) > 0
if duringModification then
call UnitRemoveAbility(u, udg_PDD_SET_MAX_LIFE)
endif
// Get the correct health value of the unit
if storedHealth != 0.0 then
set health = storedHealth - storedDamage
else
set health = GetWidgetLife(u) - storedDamage
endif
// Restore the rescue ability and return
if duringModification then
call UnitAddAbility(u, udg_PDD_SET_MAX_LIFE)
endif
return health
endfunction
function GetUnitMaxLife takes unit u returns real
local real maxHealth
// Check if the unit is being rescued from damage
if GetUnitAbilityLevel(u, udg_PDD_SET_MAX_LIFE) > 0 then
call UnitRemoveAbility(u, udg_PDD_SET_MAX_LIFE)
set maxHealth = GetUnitState(u, UNIT_STATE_MAX_LIFE)
call UnitAddAbility(u, udg_PDD_SET_MAX_LIFE)
return maxHealth
endif
// If the unit isn't being rescued, use the standard native
return GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction
function SetUnitLife takes unit u, real newLife returns nothing
local integer targetId
local integer oldTimerId
local real oldHealth
// Check if the unit is being rescued from damage
if GetUnitAbilityLevel(u, udg_PDD_SET_MAX_LIFE) > 0 then
call UnitRemoveAbility(u, udg_PDD_SET_MAX_LIFE)
call SetWidgetLife(u, newLife)
call UnitAddAbility(u, udg_PDD_SET_MAX_LIFE)
// Get the unit specific timer information
set targetId = GetHandleId(u)
set oldHealth = LoadReal(udg_PDD_h, targetId, 0)
// Update the unit specific timer information
if oldHealth != 0.0 then
set oldTimerId = LoadInteger(udg_PDD_h, targetId, 3)
call SaveReal(udg_PDD_h, targetId, 2, newLife)
call SaveReal(udg_PDD_h, targetId, 0, newLife)
call SaveReal(udg_PDD_h, oldTimerId, 4, newLife)
endif
return
endif
// If the unit isn't being rescued, use the standard native
call SetWidgetLife(u, newLife)
endfunction
function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount, boolean attack, boolean ranged, attacktype localAttackType, damagetype localDamageType, weapontype localWeaponType returns boolean
// Avoid infinite loop due to recursion
if udg_PDD_damageType == udg_PDD_CODE then
return false
endif
// Avoid allocating attacks on units that are about to be killed
if ( localTarget == udg_PDD_target and GetUnitLife(localTarget) - udg_PDD_amount < udg_PDD_UNIT_MIN_LIFE ) then
return false
endif
// Store all damage parameters determined by the user
set udg_PDD_allocatedAttacks = udg_PDD_allocatedAttacks + 1
call SaveUnitHandle(udg_PDD_h, udg_PDD_allocatedAttacks, 0, localSource)
call SaveUnitHandle(udg_PDD_h, udg_PDD_allocatedAttacks, 1, localTarget)
call SaveReal(udg_PDD_h, udg_PDD_allocatedAttacks, 2, localAmount)
call SaveBoolean(udg_PDD_h, udg_PDD_allocatedAttacks, 3, attack)
call SaveBoolean(udg_PDD_h, udg_PDD_allocatedAttacks, 4, ranged)
call SaveInteger(udg_PDD_h, udg_PDD_allocatedAttacks, 5, GetHandleId(localAttackType))
call SaveInteger(udg_PDD_h, udg_PDD_allocatedAttacks, 6, GetHandleId(localDamageType))
call SaveInteger(udg_PDD_h, udg_PDD_allocatedAttacks, 7, GetHandleId(localWeaponType))
// Return true if the damage was allocated
return true
endfunction
////////////////////////////////////////////////////////////////////////////////////
// Sub functions
////////////////////////////////////////////////////////////////////////////////////
function DealFixDamage takes unit source, unit target, real pureAmount returns nothing
local real MAX_DAMAGE = 1000000.0
local real beforeHitpoints
local real newHitpoints
// Ensure the amount is positive
if pureAmount < 0 then
set pureAmount = -pureAmount
endif
// Save the targets hitpoints
set beforeHitpoints = GetWidgetLife(target)
set newHitpoints = beforeHitpoints - pureAmount
// Apply the desired, fixed amount
if newHitpoints >= udg_PDD_UNIT_MIN_LIFE then
call SetUnitState(target, UNIT_STATE_LIFE, newHitpoints)
else
if ( IsUnitType(target, UNIT_TYPE_ETHEREAL) == false ) then
call SetWidgetLife(target, 1.0)
call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, udg_PDD_ATTACK_TYPE_UNIVERSAL, DAMAGE_TYPE_UNIVERSAL, null)
call SetWidgetLife(target, 0.0)
else
call UnitRemoveAbility(target, udg_PDD_DAMAGE_TYPE_DETECTOR)
call SetWidgetLife(target, 1.0)
call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
call SetWidgetLife(target, 0.0)
endif
endif
endfunction
function GetUnitSpellResistance takes unit u returns real
local real originalHP
local real beforeHP
local real afterHP
local real resistance
local real DUMMY_DAMAGE = 100
local real DUMMY_FACTOR = 0.01
// Deal spell damage in order to get the units resistance
call UnitRemoveAbility(udg_PDD_target, udg_PDD_DAMAGE_TYPE_DETECTOR)
set originalHP = GetWidgetLife(udg_PDD_target)
call UnitAddAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
call SetWidgetLife(udg_PDD_target, 20000.0)
set beforeHP = GetWidgetLife(udg_PDD_target)
call DisableTrigger(udg_PDD_damageEvent)
call UnitDamageTarget(udg_PDD_source, udg_PDD_target, DUMMY_DAMAGE, true, false, null, DAMAGE_TYPE_UNIVERSAL, null)
call EnableTrigger(udg_PDD_damageEvent)
set afterHP = GetWidgetLife(udg_PDD_target)
call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
call SetWidgetLife(udg_PDD_target, originalHP)
call UnitAddAbility(udg_PDD_target, udg_PDD_DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(udg_PDD_target, true, udg_PDD_DAMAGE_TYPE_DETECTOR)
// Calculate this resistance
set resistance = DUMMY_FACTOR*(beforeHP - afterHP)
// If the resistance was greater than 100%, return it
if resistance > 1.0 then
return resistance
else
return 1.0
endif
endfunction
function UnitHasItemOfType takes unit u, integer itemId returns integer
local integer index = 0
local item indexItem
// Check if the target has a spell damage reducing item
loop
set indexItem = UnitItemInSlot(u, index)
if ( indexItem != null ) and ( GetItemTypeId(indexItem) == itemId ) then
set indexItem = null
return index + 1
endif
set index = index + 1
exitwhen index >= bj_MAX_INVENTORY
endloop
set indexItem = null
return 0
endfunction
////////////////////////////////////////////////////////////////////////////////////
// Damage Engine
////////////////////////////////////////////////////////////////////////////////////
function AfterDamage takes nothing returns nothing
local timer time = GetExpiredTimer()
local integer id = GetHandleId(time)
local unit localSource = LoadUnitHandle(udg_PDD_h, id, 0)
local unit localTarget = LoadUnitHandle(udg_PDD_h, id, 1)
local real pureAmount = LoadReal(udg_PDD_h, id, 2)
local real amount = LoadReal(udg_PDD_h, id, 3)
local real originalHealth = LoadReal(udg_PDD_h, id, 4)
// If the damage was modified, restore units health
if originalHealth != 0.0 then
call UnitRemoveAbility(localTarget, udg_PDD_SET_MAX_LIFE)
call SetWidgetLife(localTarget, originalHealth)
endif
// Apply the desired amount of damage
if amount > 0.0 then
call DisableTrigger(udg_PDD_damageEvent)
call DealFixDamage(localSource, localTarget, amount)
call EnableTrigger(udg_PDD_damageEvent)
else
call SetWidgetLife(localTarget, originalHealth - amount)
endif
// Clean things up
call FlushChildHashtable(udg_PDD_h, id)
call FlushChildHashtable(udg_PDD_h, GetHandleId(localTarget))
call DestroyTimer(time)
set time = null
set localSource = null
set localTarget = null
endfunction
function DamageEngine takes nothing returns nothing
local timer time
local integer id
local real health
local real rawAmount
local real originalHealth
local integer targetId
local integer oldTimerId
local real oldHealth
local real oldOriginalHealth
local real oldAmount
set rawAmount = GetEventDamage()
if rawAmount == 0.0 then
return
endif
set udg_PDD_source = GetEventDamageSource()
set udg_PDD_target = GetTriggerUnit()
// Determine the damage type
if rawAmount > 0.0 then
if udg_PDD_damageType != udg_PDD_CODE then
set udg_PDD_damageType = udg_PDD_PHYSICAL
endif
set udg_PDD_amount = rawAmount
else
if udg_PDD_damageType != udg_PDD_CODE then
set udg_PDD_damageType = udg_PDD_SPELL
endif
set udg_PDD_amount = -rawAmount
endif
// Register spell reduction above 100%
if udg_PDD_SPELL_RESIST_AUTO_DETECT then
set udg_PDD_amount = GetUnitSpellResistance(udg_PDD_target)*udg_PDD_amount
else
if ( IsUnitType(udg_PDD_target, UNIT_TYPE_ETHEREAL) and IsUnitEnemy(udg_PDD_target, GetOwningPlayer(udg_PDD_source)) and rawAmount < 0.0 ) then
set udg_PDD_amount = udg_PDD_ETHEREAL_DAMAGE_FACTOR*udg_PDD_amount
endif
endif
// Register spell damage reducing items like runed bracers
if ( IsUnitType(udg_PDD_target, UNIT_TYPE_HERO) and UnitHasItemOfType(udg_PDD_target, udg_PDD_SPELL_DMG_REDUCTION_ITEM) > 0 ) and rawAmount < 0.0 then
set udg_PDD_amount = (1 - udg_PDD_BRACERS_SPELL_DMG_RED)*udg_PDD_amount
endif
// Save health and damage variables
if udg_PDD_damageType != udg_PDD_CODE then
call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
endif
set udg_PDD_pureAmount = udg_PDD_amount
set originalHealth = GetWidgetLife(udg_PDD_target)
set oldTimerId = 0
// Call damage handlers
set udg_PDD_damageEventTrigger = 0.0
set udg_PDD_damageEventTrigger = 1.0
set udg_PDD_damageEventTrigger = 0.0
// If the damage was modified, apply the rescue ability
if udg_PDD_amount != udg_PDD_pureAmount then
call UnitAddAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
call SetWidgetLife(udg_PDD_target, GetWidgetLife(udg_PDD_target) + udg_PDD_pureAmount)
endif
// Check if a previous timer didn't yet expire
set targetId = GetHandleId(udg_PDD_target)
set oldHealth = LoadReal(udg_PDD_h, targetId, 0)
// If this is the case, update the timer information
if oldHealth != 0.0 then
set oldTimerId = LoadInteger(udg_PDD_h, targetId, 3)
set oldOriginalHealth = LoadReal(udg_PDD_h, targetId, 2)
set oldAmount = LoadReal(udg_PDD_h, targetId, 1)
set originalHealth = oldOriginalHealth - oldAmount
call SaveReal(udg_PDD_h, oldTimerId, 4, oldOriginalHealth)
endif
// Call after damage event if damage was spell, modified, code or parallel
if ( rawAmount < 0.0 or udg_PDD_pureAmount != udg_PDD_amount or oldTimerId != 0 or udg_PDD_allocatedAttacks > 0 ) then
set time = CreateTimer()
set id = GetHandleId(time)
// Save handles for after damage event
call SaveUnitHandle(udg_PDD_h, id, 0, udg_PDD_source)
call SaveUnitHandle(udg_PDD_h, id, 1, udg_PDD_target)
call SaveReal(udg_PDD_h, id, 2, udg_PDD_pureAmount)
call SaveReal(udg_PDD_h, id, 3, udg_PDD_amount)
call SaveReal(udg_PDD_h, id, 4, originalHealth)
// Save this extra to manage parallel damage instances
call SaveReal(udg_PDD_h, targetId, 0, GetWidgetLife(udg_PDD_target))
call SaveReal(udg_PDD_h, targetId, 1, udg_PDD_amount)
call SaveReal(udg_PDD_h, targetId, 2, originalHealth)
call SaveInteger(udg_PDD_h, targetId, 3, id)
// Avoid healing of negative spell damage
if rawAmount < 0.0 then
call DisableTrigger(udg_PDD_damageEvent)
call DealFixDamage(udg_PDD_source, udg_PDD_target, -rawAmount)
if ( originalHealth - udg_PDD_amount < udg_PDD_UNIT_MIN_LIFE ) then
call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
call DealFixDamage(udg_PDD_source, udg_PDD_target, udg_PDD_amount)
endif
call EnableTrigger(udg_PDD_damageEvent)
endif
// Guarantee unit exploding
if originalHealth - udg_PDD_amount < udg_PDD_UNIT_MIN_LIFE then
if rawAmount > 0.0 then
call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
call SetWidgetLife(udg_PDD_target, udg_PDD_UNIT_MIN_LIFE)
endif
endif
// Start the after damage event
call TimerStart(time, 0.0, false, function AfterDamage)
endif
// Handle allocated attacks from UnitDamageTargetEx
if udg_PDD_totalAllocs == 0 then
set udg_PDD_totalAllocs = udg_PDD_allocatedAttacks
endif
if udg_PDD_allocatedAttacks > 0 then
set udg_PDD_allocatedAttacks = udg_PDD_allocatedAttacks - 1
set udg_PDD_allocCounter = udg_PDD_allocCounter + 1
call TriggerEvaluate(udg_PDD_runAllocatedAttacks)
endif
// Reset all required variables
set udg_PDD_damageType = -1
set udg_PDD_totalAllocs = 0
set udg_PDD_allocCounter = -1
endfunction
////////////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////////////
function RestoreTriggers takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
// Re-register units that are alive
if GetUnitTypeId(enumUnit) != 0 then
call TriggerRegisterUnitEvent(udg_PDD_damageEvent, enumUnit, EVENT_UNIT_DAMAGED)
endif
set enumUnit = null
endfunction
function ClearMemory_Actions takes nothing returns nothing
local group g = CreateGroup()
local code c = function DamageEngine
// Reset the damage event
call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
call ResetTrigger(udg_PDD_damageEvent)
call DestroyTrigger(udg_PDD_damageEvent)
set udg_PDD_damageEvent = null
// Rebuild it then
set udg_PDD_damageEvent = CreateTrigger()
call TriggerAddCondition(udg_PDD_damageEvent, Filter(c))
call ForGroup(g, function RestoreTriggers)
// Clean things up
call DestroyGroup(g)
set g = null
set c = null
endfunction
function MapInit takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
// Register units on map initialization
call UnitAddAbility(enumUnit, udg_PDD_DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(enumUnit, true, udg_PDD_DAMAGE_TYPE_DETECTOR)
call TriggerRegisterUnitEvent(udg_PDD_damageEvent, enumUnit, EVENT_UNIT_DAMAGED)
set enumUnit = null
endfunction
function UnitEntersMap takes nothing returns nothing
local unit triggerUnit = GetTriggerUnit()
// Register units that enter the map
if ( GetUnitAbilityLevel(triggerUnit, udg_PDD_DAMAGE_TYPE_DETECTOR) < 1 ) then
call UnitAddAbility(triggerUnit, udg_PDD_DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(triggerUnit, true, udg_PDD_DAMAGE_TYPE_DETECTOR)
call TriggerRegisterUnitEvent(udg_PDD_damageEvent, triggerUnit, EVENT_UNIT_DAMAGED)
endif
set triggerUnit = null
endfunction
function RunAllocatedAttacks takes nothing returns nothing
local integer localAllocAttacks = udg_PDD_allocatedAttacks + 1
// Calculate the correct sequence of allocated attacks
set localAllocAttacks = localAllocAttacks - udg_PDD_totalAllocs + 1 + 2*udg_PDD_allocCounter
// Deal code damage if the unit isn't exploding
set udg_PDD_damageType = udg_PDD_CODE
if GetUnitLife(LoadUnitHandle(udg_PDD_h, localAllocAttacks, 1)) >= udg_PDD_UNIT_MIN_LIFE then
call UnitDamageTarget(LoadUnitHandle(udg_PDD_h, localAllocAttacks, 0), LoadUnitHandle(udg_PDD_h, localAllocAttacks, 1), LoadReal(udg_PDD_h, localAllocAttacks, 2), LoadBoolean(udg_PDD_h, localAllocAttacks, 3), LoadBoolean(udg_PDD_h, localAllocAttacks, 4), ConvertAttackType(LoadInteger(udg_PDD_h, localAllocAttacks, 5)), ConvertDamageType(LoadInteger(udg_PDD_h, localAllocAttacks, 6)), ConvertWeaponType(LoadInteger(udg_PDD_h, localAllocAttacks, 7)))
else
call FlushChildHashtable(udg_PDD_h, localAllocAttacks - 1)
endif
// Clean things up
call FlushChildHashtable(udg_PDD_h, localAllocAttacks)
endfunction
function InitTrig_DamageEvent takes nothing returns nothing
local group g = CreateGroup()
local region r = CreateRegion()
local trigger UnitEnters = CreateTrigger()
local trigger ClearMemory = CreateTrigger()
local code cDamageEngine = function DamageEngine
local code cUnitEnters = function UnitEntersMap
local code cClearMemory = function ClearMemory_Actions
local code cRunAllocatedAttacks = function RunAllocatedAttacks
// Initialize global variables
set udg_PDD_h = InitHashtable()
set udg_PDD_damageEvent = CreateTrigger()
set udg_PDD_damageHandler = CreateTrigger()
set udg_PDD_damageType = -1
set udg_PDD_allocatedAttacks = 0
set udg_PDD_runAllocatedAttacks = CreateTrigger()
// Initialize global configurable constants
call InitGlobalVariables()
// Initialize global fixed constants
set udg_PDD_PHYSICAL = 0
set udg_PDD_SPELL = 1
set udg_PDD_CODE = 2
set udg_PDD_UNIT_MIN_LIFE = 0.406
set udg_PDD_ATTACK_TYPE_UNIVERSAL = ConvertAttackType(7)
set udg_PDD_totalAllocs = 0
set udg_PDD_allocCounter = -1
set udg_PDD_damageEventTrigger = 0.0
// Register units on map initialization
call TriggerRegisterVariableEvent(udg_PDD_damageHandler, "udg_PDD_damageEventTrigger", EQUAL, 1.0)
call TriggerAddCondition(udg_PDD_damageEvent, Filter(cDamageEngine))
call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
call ForGroup(g, function MapInit)
// Register units that enter the map
call RegionAddRect(r, GetWorldBounds())
call TriggerRegisterEnterRegion(UnitEnters, r, null)
call TriggerAddCondition(UnitEnters, Filter(cUnitEnters))
// Register trigger for allocated attacks
call TriggerAddCondition(udg_PDD_runAllocatedAttacks, Filter(cRunAllocatedAttacks))
// Clear memory leaks
call TriggerRegisterTimerEvent(ClearMemory, udg_PDD_TRIGGER_CLEANUP_PERIOD, true)
call TriggerAddCondition(ClearMemory, Filter(cClearMemory))
// Clean things up
call DestroyGroup(g)
set UnitEnters = null
set ClearMemory = null
set cDamageEngine = null
set cUnitEnters = null
set cClearMemory = null
set cRunAllocatedAttacks = null
set g = null
set r = null
endfunction
//TESH.scrollpos=857
//TESH.alwaysfold=0
//==========================================================================================
// HeroAI v4.3.3
// by watermelon_1234
//==========================================================================================
// This library provides a simple computer AI for arena-type maps.
//
// Basic hero actions taken care of:
// * moving around the map
// * attacking an enemy unit
// * spending gold on items and picking up nearby items
// * running to a "safe unit"
//
// Additionally, the system provides a module, HeroAIStruct, that can be implemented into a
// struct to allow the user to further customize the AI of a specific hero type, such coding
// conditions for it to cast certain spells.
//
// When making a custom AI, it's advised to make the struct private along with naming it AI.
// This way, you can use the textmacro HeroAI_Register to simplify registering the AI.
// Implement the HeroAIStruct module after coding all your interface methods.
// Main control of the AI is done through periodic event.
//
// It is recommended to look at the AIs in the test map to see how a custom AI can be coded.
//##########################################################################################
// HeroAI struct:
//
// Members:
//
// * unit hero -> The hero the AI is controlling
// * player owner -> The owner of hero. It assumed that the owner will be constant.
// * integer hid -> The handle id of the hero
// * Itemset itemBuild -> The item build the hero will try to buy. Defaults to DefaultItemBuild
// If you want to define a custom item build for a specific AI, it is
// recommended to change this in the onCreate method interface for your AI.
//
// Values that are updated:
//
// * integer itemCount -> The number of items the hero has
// * integer gold -> The amount of gold the owner has
// * integer lumber -> The amount of lumber the owner has
// * real life -> Life of hero
// * real maxLife -> Max life of hero
// * real mana -> Mana of hero
// * hx -> x-coordinate of the hero
// * hy -> y-coordinate of the hero
// * group units -> All alive units around the hero, excluding the hero and neutral units
// * group allies -> Derived from group units, only has allied units
// * group enemies -> Derived from group units, only has visible enemy units
// * integer allyNum -> Number of nearby allied units
// * integer enemyNum -> Number of nearby enemy units
// * unit shop -> Set this to the shop that you want the AI to shop at. You only need to use this
// if you define a custom loopActions and don't call update.
//
// * static thistype temp -> Allow easier group enumerations
//
//
// * static method getAIIndexFromHero takes unit hero returns integer
// Returns the index of the AI struct created for a hero. Used primarily in event responses.
//
// * method update takes nothing returns nothing *
// Updates information for the AI. Called periodically with timer. Should be called at least
// once if the AI is dealt with outside of the periodic event.
//
// * method changeDefaultPeriod takes real period returns nothing *
// Changes the timer period of the AI for calling default loop actions.
//
// Item Methods: (Note: uses the custom defined structure in HeroAIItem, not the blizzard defined type)
//
// * method curItem takes nothing returns Item *
// Returns the Item that the hero is trying to buy. Returns 0 if the hero should stop buying
// items.
//
// * method canBuyItem takes Item it returns boolean *
// Checks to see if the hero can buy an Item. An Item can be referred to as
// Item[ITEM-TYPE ID] or .curItem
//
// * method buyItem takes Item it returns nothing *
// Simulates the purchase of an item by deducting the costs and adding the item to the hero's inventory.
// Use canBuyItem first to check if the AI has enough resources to purchase the item.
//
// Utility Methods:
//
// * method operator timer takes nothing returns nothing *
// Gives access to the timer that controls the AI's periodic actions.
//
// * method operator state takes nothing returns nothing *
// Gives access to the AI state.
//
// * method operator percentLife takes nothing returns real *
// Returns the percentage life of the hero
//
// * method operator badCondition takes nothing returns boolean *
// Condition used to tell when the hero should run to the safe spot
//
// * method operator goodCondition takes nothing returns boolean *
// Condition used to tell when the hero should leave the safe spot
//
// * method operator isChanneling takes nothing returns boolean *
// Returns true if the hero is channeling spell
//
// * method recountAllies takes nothing returns nothing *
// Recounts allyNum, in case allies was changed
//
// * method recountEnemies takes nothing returns nothing *
// Recounts enemyNum, in case enemies was changed
//
// * method setRunSpot takes nothing returns nothing *
// Finds the closest safe spot for the hero to run to. This only needs to
// be called once for every time you want the hero to run away.
//
// Action Methods:
//
// * method canShop takes nothing returns nothing *
// Checks if the hero can go shopping and does it if it can. Called by update.
// Note that attacking enemies will take precendence over shopping unless the shop is
// within IGNORE_ENEMY_SHOP_RANGE (in HeroAIItem).
//
// * method move takes nothing returns boolean *
// Makes the hero move around randomly. Returns true if it was able to.
//
// * method run takes nothing returns boolean *
// Makes the hero run to its safe spot. Should be used after setRunSpot has been called at
// least once. Returns true if it was successful.
//
// * method getItems takes nothing returns boolean *
// Makes the hero get items in range of SIGHT_RANGE. Returns true if it was able to.
//
// Default Methods:
//
// * method defaultAssaultEnemy takes nothing returns nothing
// Causes the hero to attack a nearby enemy unit. Will factor in priority/life
// if the required libraries are implemented.
//
// * method defaultLoopActions takes nothing returns nothing
// Makes the hero act based on the default periodic actions.
//
// Interface for structs that implement the HeroAIStruct module:
//
// * method onCreate takes nothing returns nothing *
// Runs when the AI struct is created for a hero.
//
// * method loopActions takes nothing returns nothing *
// Allows complete control over how the AI acts periodically. Overrides the default
// loop actions if defined.
//
// The following methods would only be called if the AI uses the default loop actions.
//
// * method assaultEnemy takes nothing returns nothing *
// Define actions for the AI to take while fighting against an enemy.
//
// * method assistAlly takes nothing returns boolean *
// Define actions for the AI to take while an allied unit is near the AI. Needs
// to return true if some action was taken, false otherwise.
// assistAlly takes precedence over assaultEnemy.
//
// * method runActions takes nothing returns boolean *
// The actions to take when the AI is running to the safe spot. Needs to return true if
// some action was taken, false otherwise.
//
// * method safeActions takes nothing returns nothing *
// Define actions for the AI to take while within the range of a safe spot. Defaults to
// assistAlly and assaultEnemy if this method doesn't exist.
//------------------------------------------------------------------------------------------
// Function API:
//
// * RunHeroAI takes unit hero returns nothing *
// Starts the AI for a unit. Runs the default AI if no custom AI is defined.
//
// * RegisterHeroAI takes integer unitTypeId, code register returns nothing *
// Registers an AI creation function for a unit-type id.
// register should be a function that creates the AI struct for that hero. You must
// refer the unit getting their AI registered as HeroAI_RegisterUnit.
//
// * textmacro HeroAI_Register takes HERO_UNIT_TYPEID
// For the purposes of simplifying hero AI registering. Requires that the custom AI
// struct be named "AI" and "ModuleHack" and "StructHack" to not be used as identifiers.
//
// * DoesHeroHaveAI takes unit hero returns boolean *
// Returns true if a hero has an AI by this system.
//##########################################################################################
// Required Libraries:
// * GetClosestWidget http://www.hiveworkshop.com/forums/jass-resources-412/snippet-getclosestwidget-204217/
// * Table (New Table) http://www.hiveworkshop.com/forums/jass-functions-413/snippet-new-table-188084/
// * RegisterPlayerUnitEvent http://www.hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
// * TimerUtils http://www.wc3c.net/showthread.php?t=101322
//
// Optional:
//
// * GroupUtils http://www.wc3c.net/showthread.php?t=104464
// The system will use GroupEnumUnitsInArea for more accurate group enumeration and ENUM_GROUP if included.
// Note the test map uses this library for the AI provided
//
// * HeroAIPriority OR PruneGroup & FitnessFunc [http://www.wc3c.net/showthread.php?t=106467]
// These libraries control how the AI chooses to attack a target by default. HeroAIPriority allows
// the AI to attack enemies based on life and distance while PruneGroup makes the AI
// focus only on the lowest life enemy. HeroAIPriority takes precedence over PruneGroup, meaning
// that if both are in the map, HeroAIPriority's method will be used.
// If neither are present in the map, the AI will select a random enemy unit to attack by default.
//
// * HeroAIThreat
// Gives the AI the ability to perceive threat. This will be considered in running away and can
// assist spellcast targeting.
//
// * HeroAIEventResponse
// Allows the AI to interact with outside event responses. Requires more vJass code to be written.
//
// * IsUnitChanneling http://www.hiveworkshop.com/forums/submissions-414/snippet-isunitchanneling-211254/
// A recommended way of detecting if a unit is channeling rather than checking the unit's
// current order.
//
// NOTE: Although I normally suggest using updated libraries, keep in mind that the updated IsUnitChanneling
// has no longer made its requirements optional, meaning that UnitIndexer would also have to be implemented
// For the purpose of remaining lightweight, the test-map will feature the older version,
// but feel free to update IsUnitChanneling.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Basic Importing:
// 1. Implement the required libraries.
// 2. Copy this trigger and its two parts, HeroAILearnset and HeroAIItem, into your map.
// 3. Configure the library.
//==========================================================================================
library HeroAI requires GetClosestWidget, RegisterPlayerUnitEvent, Table, TimerUtils, /*
*/ optional FitnessFunc, optional GroupUtils, optional HeroAIPriority, /*
*/ optional HeroAIThreat, optional HeroAIEventResponse, optional IsUnitChanneling
globals
public constant real DEFAULT_PERIOD = 1.6 // The period in which the hero AI will do actions. A very low period can cause strain.
public constant real SIGHT_RANGE = 20000. // Determines how the hero looks for items and units.
public constant real MOVE_DIST = 6000. // The random amount of distance the hero will move
public constant real SAFETY_RANGE = 500. // The range the hero should be within the safe spot.
private constant player NEUTRAL_PLAYER = Player(15) // The player considered neutral in your map
endglobals
// The function that determines what is a safe unit, like a fountain, for the hero to run to.
// Typically, you should check if the unit, u, matches the safe unit-type id in your map.
// The test-map makes a simple unit-type check since it assumes fountain units cannot be killed and will heal any hero
private function IsSafeUnit takes unit u, player heroOwner returns boolean
return GetUnitTypeId(u) == 'gEMY'
endfunction
// General conditions for units that should be considered shops in your map.
// * u is the unit that should be tested a shop
// * heroOwner is the owner of the hero in case you want the hero to only be able to buy
// from allied shops
// The test-map has no special conditions for shops by assuming that shops cannot be
// killed and that anyone can buy items from a shop
private function ShopConditions takes unit u, player heroOwner returns boolean
return true
endfunction
// The following textmacros help configure internal conditions for the AI
// Note that you can access the struct members and methods in here:
// The condition in which the hero will try to run away to a safe spot. Optionally complemented by the threat library
//! textmacro HeroAI_Default_badCondition
return .percentLife <= .30 or (.percentLife <= .30 and .mana / GetUnitState(.hero, UNIT_STATE_MAX_MANA) <= .3) or (.maxLife < 700 and .life <= 250.)
//! endtextmacro
// The condition in which the hero will return to its normal activities
//! textmacro HeroAI_Default_goodCondition
return .percentLife >= .85 and .mana / GetUnitState(.hero, UNIT_STATE_MAX_MANA) >= .65
//! endtextmacro
// Determines which orders are considered channeling order so that the AI won't interrupt those spells
// o is the current order of the hero.
// Disregard this completely if you include IsUnitChanneling in your map
//! textmacro HeroAI_isChanneling
return o == 852652 or /* Cluster Rockets
*/ o == 852488 or /* Flamestrike
*/ o == 852089 or /* Blizzard
*/ o == 852238 or /* Rain of Fire
*/ o == 852664 or /* Healing spray
*/ o == 852183 or /* Starfall
*/ o == 852593 // Stampeded
//! endtextmacro
//==========================================================================================
// END OF USER CONFIGURATION
//==========================================================================================
globals
public constant integer STATE_IDLE = 0 // The state in which the hero is doing nothing in particular
public constant integer STATE_ENGAGED = 1 // The state in which the hero is fighting an enemy
public constant integer STATE_GO_SHOP = 2 // The state in which the hero is running to a shop in order to buy an item
public constant integer STATE_RUN_AWAY = 3 // The state in which the hero is trying to run away
public unit RegisterUnit // For registering an AI for a unit
private Table infoAI // Tracks custom AI structs defined for specific unit-type ids
private Table heroesAI // Tracks the AI struct a hero has
private integer ShopTypeId // Used to pass the shop type id of an item
private player TempHeroOwner // Used to refer to the AI hero for finding the closest safe unit
endglobals
//! runtextmacro HeroAILearnset()
//! runtextmacro HeroAIItem()
private function SafeUnitFilter takes nothing returns boolean
return IsSafeUnit(GetFilterUnit(), TempHeroOwner)
endfunction
private function ShopTypeIdCheck takes nothing returns boolean
return GetUnitTypeId(GetFilterUnit()) == ShopTypeId and ShopConditions(GetFilterUnit(), TempHeroOwner)
endfunction
module HeroAIStruct
unit hero
player owner
integer hid
Itemset itemBuild
integer itemCount
real life
real maxLife
real mana
real hx
real hy
group units
group allies
group enemies
integer allyNum
integer enemyNum
private timer tim
private integer st // Holds the state of the AI
private real runX
private real runY
private integer itemsetIndex
private unit shUnit // Shop unit used internally
static thistype temp // Set whenever update is called
private static integer stack = 1 // Used for creating
implement optional HeroAIPriority
implement optional HeroAIThreat
implement optional HeroAIEventResponse
private static method filtUnits takes nothing returns boolean
local unit u = GetFilterUnit()
// Filter out dead units, the hero itself, and neutral units
if not IsUnitType(u, UNIT_TYPE_DEAD) and u != temp.hero and GetOwningPlayer(u) != NEUTRAL_PLAYER then
// Filter unit is an ally
if IsUnitAlly(u, temp.owner) then
call GroupAddUnit(temp.allies, u)
set temp.allyNum = temp.allyNum + 1
// Filter unit is an enemy, only enum it if it's visible
elseif IsUnitVisible(u, temp.owner) then
call GroupAddUnit(temp.enemies, u)
set temp.enemyNum = temp.enemyNum + 1
endif
set u = null
return true
endif
set u = null
return false
endmethod
// For recounting enemies
private static method enumCountEnemies takes nothing returns nothing
set temp.enemyNum = temp.enemyNum + 1
endmethod
// For recounting allies
private static method enumCountAllies takes nothing returns nothing
set temp.allyNum = temp.allyNum + 1
endmethod
// Helper methods
method operator timer takes nothing returns timer
return .tim
endmethod
method operator state takes nothing returns integer
return .st
endmethod
method operator shop takes nothing returns unit
return .shUnit
endmethod
method operator shop= takes unit u returns nothing
set .st = STATE_GO_SHOP
set .shUnit = u
endmethod
method operator gold takes nothing returns integer
return GetPlayerState(.owner, PLAYER_STATE_RESOURCE_GOLD)
endmethod
method operator gold= takes integer g returns nothing
call SetPlayerState(.owner, PLAYER_STATE_RESOURCE_GOLD, g)
endmethod
method operator lumber takes nothing returns integer
return GetPlayerState(.owner, PLAYER_STATE_RESOURCE_LUMBER)
endmethod
method operator lumber= takes integer l returns nothing
call SetPlayerState(.owner, PLAYER_STATE_RESOURCE_LUMBER, l)
endmethod
method operator percentLife takes nothing returns real
return .life / .maxLife
endmethod
method operator badCondition takes nothing returns boolean
//! runtextmacro HeroAI_Default_badCondition()
endmethod
method operator goodCondition takes nothing returns boolean
//! runtextmacro HeroAI_Default_goodCondition()
endmethod
method operator isChanneling takes nothing returns boolean
static if LIBRARY_IsUnitChanneling then
return IsUnitChanneling(.hero)
else
local integer o = GetUnitCurrentOrder(.hero)
//! runtextmacro HeroAI_isChanneling()
endif
endmethod
method recountEnemies takes nothing returns nothing
set .enemyNum = 0
call ForGroup(.enemies, function thistype.enumCountEnemies)
endmethod
method recountAllies takes nothing returns nothing
set .allyNum = 0
call ForGroup(.allies, function thistype.enumCountAllies)
endmethod
method setRunSpot takes nothing returns nothing
local unit u
set TempHeroOwner = .owner
set u = GetClosestUnit(.hx, .hy, Filter(function SafeUnitFilter))
static if DEBUG_MODE then
if u == null then
call BJDebugMsg("[HeroAI] Error: Couldn't find a safe unit for " + GetUnitName(.hero) + ", will run to (0, 0)")
endif
endif
set .runX = GetUnitX(u)
set .runY = GetUnitY(u)
set u = null
endmethod
// Item-related methods
method operator curItem takes nothing returns Item
return .itemBuild.item(.itemsetIndex)
endmethod
method canBuyItem takes Item it returns boolean
static if CHECK_REFUND_ITEM_COST then
local Item check
if .itemCount == 7 then
set check = Item[GetItemTypeId(UnitItemInSlot(.hero, ModuloInteger(.itemsetIndex, MAX_INVENTORY_SIZE)))]
return it.goldCost <= .gold + check.goldCost * SELL_ITEM_REFUND_RATE and it.lumberCost <= .lumber + check.lumberCost * SELL_ITEM_REFUND_RATE
endif
endif
return it.goldCost <= .gold and it.lumberCost <= .lumber
endmethod
private method refundItem takes Item it returns nothing
if it.goldCost > 0 then
set .gold = R2I(.gold + it.goldCost * SELL_ITEM_REFUND_RATE)
endif
if it.lumberCost > 0 then
set .lumber = R2I(.lumber + it.lumberCost * SELL_ITEM_REFUND_RATE)
endif
endmethod
method buyItem takes Item it returns nothing
local item i
if .itemCount == MAX_INVENTORY_SIZE then
set i = UnitItemInSlot(.hero, ModuloInteger(.itemsetIndex, MAX_INVENTORY_SIZE) )
if i != null then
call .refundItem(Item[GetItemTypeId(i)])
call RemoveItem(i)
set i = null
endif
endif
set .itemsetIndex = .itemsetIndex + 1
// Set back to state idle now that the hero is done shopping.
if .st == STATE_GO_SHOP then
set .st = STATE_IDLE
endif
if it.goldCost > 0 then
set .gold = .gold - it.goldCost
endif
if it.lumberCost > 0 then
set .lumber = .lumber - it.lumberCost
endif
call UnitAddItemById(.hero, it.typeId)
endmethod
// Action methods
method move takes nothing returns nothing
call IssuePointOrder(.hero, "attack", .hx + GetRandomReal(-MOVE_DIST, MOVE_DIST), .hy + GetRandomReal(-MOVE_DIST, MOVE_DIST))
endmethod
method run takes nothing returns boolean
return IssuePointOrder(.hero, "move", .runX + GetRandomReal(-SAFETY_RANGE/2, SAFETY_RANGE/2), .runY + GetRandomReal(-SAFETY_RANGE/2, SAFETY_RANGE/2) )
endmethod
method getItems takes nothing returns boolean
set OnlyPowerUp = .itemCount == MAX_INVENTORY_SIZE
return IssueTargetOrder(.hero, "smart", GetClosestItemInRange(.hx, .hy, SIGHT_RANGE, Filter(function AIItemFilter)))
endmethod
method defaultAssaultEnemy takes nothing returns nothing
static if thistype.setPriorityEnemy.exists then
call .setPriorityEnemy(.enemies)
call IssueTargetOrder(.hero, "attack", .priorityEnemy)
else
static if LIBRARY_FitnessFunc then
static if LIBRARY_GroupUtils then
call GroupClear(ENUM_GROUP)
call GroupAddGroup(.enemies, ENUM_GROUP)
call PruneGroup(ENUM_GROUP, FitnessFunc_LowLife, 1, NO_FITNESS_LIMIT)
call IssueTargetOrder(.hero, "attack", FirstOfGroup(ENUM_GROUP))
else
call GroupClear(bj_lastCreatedGroup)
call GroupAddGroup(.enemies, bj_lastCreatedGroup)
call PruneGroup(bj_lastCreatedGroup, FitnessFunc_LowLife, 1, NO_FITNESS_LIMIT)
call IssueTargetOrder(.hero, "attack", FirstOfGroup(bj_lastCreatedGroup))
endif
else // A lazy way to make the hero attack a random unit if none of the special targetting libraries are there
call IssueTargetOrder(.hero, "attack", GroupPickRandomUnit(.enemies))
endif
endif
endmethod
method defaultLoopActions takes nothing returns nothing
if .st == STATE_RUN_AWAY then
// Make the hero keep running if it's not within range
if not IsUnitInRangeXY(.hero, .runX, .runY, SAFETY_RANGE) then
static if thistype.runActions.exists then
// Only run if no actions were taken in runActions.
if not .runActions() then
call .run()
endif
else
call .run()
endif
else
static if thistype.safeActions.exists then
call .safeActions()
else
// Default looping actions for fighting so that the AI will try to do something at the safe spot.
if not .isChanneling then
static if thistype.assistAlly.exists then
if .allyNum > 0 then
if .assistAlly() then
return
endif
endif
endif
if .enemyNum > 0 then
static if thistype.assaultEnemy.exists then
call .assaultEnemy()
else
call .defaultAssaultEnemy()
endif
endif
endif
endif
endif
else
if not .isChanneling then
static if thistype.assistAlly.exists then
if .allyNum > 0 then
if .assistAlly() then
return // Assisting an ally has precedence over anything else
endif
endif
endif
// Fight enemies if the hero is engaged
if .st == STATE_ENGAGED then
static if thistype.assaultEnemy.exists then
call .assaultEnemy()
else
call .defaultAssaultEnemy()
endif
else
// Makes the hero try to get any nearby item before attempting to shop
if not .getItems() then
if .st == STATE_GO_SHOP then
// If the hero isn't in range of the shop, make it move there.
if not IsUnitInRange(.hero, .shUnit, SELL_ITEM_RANGE) then
call IssuePointOrder(.hero, "move", GetUnitX(.shUnit) + GetRandomReal(-SELL_ITEM_RANGE/2, SELL_ITEM_RANGE/2), GetUnitY(.shUnit) + GetRandomReal(-SELL_ITEM_RANGE/2, SELL_ITEM_RANGE/2))
else
// Buys the item only if it was able to.
if .canBuyItem(.curItem) then
call .buyItem(.curItem)
else
set .st = STATE_IDLE
endif
endif
else
// STATE_IDLE, make the hero move around randomly
call .move()
endif
endif
endif
endif
endif
endmethod
// This method will be called by update periodically to check if the hero can do any shopping
method canShop takes nothing returns nothing
local Item it
loop
set it = .curItem
exitwhen not .canBuyItem(it) or .itemsetIndex == .itemBuild.size
if it.shopTypeId == 0 then
call .buyItem(it)
else
set ShopTypeId = it.shopTypeId
set TempHeroOwner = .owner
set .shUnit = GetClosestUnit(.hx, .hy, Filter(function ShopTypeIdCheck))
debug if .shUnit == null then
debug call BJDebugMsg("[Hero AI] Error: Null shop found for " + GetUnitName(.hero))
debug endif
if IsUnitInRange(.hero, .shUnit, SELL_ITEM_RANGE) then
call .buyItem(it)
else
set .st = STATE_GO_SHOP
exitwhen true
endif
endif
endloop
endmethod
// Updates information about the hero and its surroundings
method update takes nothing returns nothing
// Information about self
set .hx = GetUnitX(.hero)
set .hy = GetUnitY(.hero)
set .life = GetWidgetLife(.hero)
set .mana = GetUnitState(.hero, UNIT_STATE_MANA)
set .maxLife = GetUnitState(.hero, UNIT_STATE_MAX_LIFE)
set .itemCount = UnitInventoryCount(.hero)
// Group enumeration
set temp = this
call GroupClear(.enemies)
call GroupClear(.allies)
set .enemyNum = 0
set .allyNum = 0
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(.units, .hx, .hy, SIGHT_RANGE, Filter(function thistype.filtUnits))
else
call GroupEnumUnitsInRange(.units, .hx, .hy, SIGHT_RANGE, Filter(function thistype.filtUnits))
endif
// Checking to see if the AI should be thinking of runnning away
static if LIBRARY_HeroAIThreat then
if .st != STATE_RUN_AWAY then
if .enemyNum > 0 then
call .calcThreat()
if .threat > .threatThreshold * .thresholdRunFactor then
set .st = STATE_RUN_AWAY
call .setRunSpot()
endif
elseif .badCondition then
set .st = STATE_RUN_AWAY
call .setRunSpot()
endif
elseif .goodCondition then
set .st = STATE_IDLE
endif
else
if .st != STATE_RUN_AWAY and .badCondition then
set .st = STATE_RUN_AWAY
call .setRunSpot()
elseif .st == STATE_RUN_AWAY and .goodCondition then
set .st = STATE_IDLE
endif
endif
// Only check to do shopping if in the AI hasn't completed its itemset and it's in STATE_IDLE
if .itemsetIndex < .itemBuild.size and .st == STATE_IDLE then
call .canShop()
endif
// Don't want to override STATE_RUN_AWAY since it's more important
if .st != STATE_RUN_AWAY then
if .st == STATE_ENGAGED then
if .enemyNum < 1 then
set .st = STATE_IDLE
endif
else
// STATE_ENGAGED will only take precedence over STATE_GO_SHOP if the hero is not within IGNORE_ENEMY_SHOP_RANGE of the shop
if .enemyNum > 0 and .st == STATE_IDLE or (.st == STATE_GO_SHOP and not IsUnitInRange(.hero, .shUnit, IGNORE_ENEMY_SHOP_RANGE)) then
set .st = STATE_ENGAGED
endif
endif
endif
endmethod
private static method defaultLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if not IsUnitType(.hero, UNIT_TYPE_DEAD) then
call .update()
static if thistype.loopActions.exists then
call .loopActions()
else
call .defaultLoopActions()
endif
endif
endmethod
method changeDefaultPeriod takes real period returns nothing
call TimerStart(.tim, period, true, function thistype.defaultLoop)
endmethod
static method getAIIndexFromHero takes unit hero returns thistype
return heroesAI[GetHandleId(hero)]
endmethod
static method create takes unit h returns thistype
local thistype this = stack
local integer lvl = 1 // For levelling up skills
local integer typeId = GetUnitTypeId(h)
static if MULTIPLE_SKILLS_PER_LVL then
local integer i
endif
set .hero = h
set .owner = GetOwningPlayer(.hero)
set .hid = GetHandleId(h)
set .units = CreateGroup()
set .enemies = CreateGroup()
set .allies = CreateGroup()
set .itemBuild = DefaultItemBuild
set .itemsetIndex = 6
set .st = STATE_IDLE
set .tim = NewTimerEx(this)
call TimerStart(.tim, DEFAULT_PERIOD, true, function thistype.defaultLoop)
if GetHeroSkillPoints(.hero) > 0 then
loop
// Need to loop a bit differently if multiple skills are supported at each level
static if MULTIPLE_SKILLS_PER_LVL then
set i = 0
loop
exitwhen i == LearnsetInfo[lvl].skillNum(typeId)
call SelectHeroSkill(h, LearnsetInfo[lvl].getSkill(typeId, i))
set i = i + 1
endloop
else
call SelectHeroSkill(h, LearnsetInfo[lvl][typeId])
endif
set lvl = lvl + 1
exitwhen lvl > GetUnitLevel(h)
endloop
endif
static if LIBRARY_HeroAIEventResponse then
call .registerEventResponses()
endif
static if thistype.onCreate.exists then
call .onCreate()
endif
set stack = stack + 1
set heroesAI[.hid] = this
return this
endmethod
endmodule
private struct DefaultHeroAI extends array
implement HeroAIStruct
endstruct
globals
private trigger fireTrigger = CreateTrigger()
endglobals
// credits to Magtheridon96 for this function
private function FireCondition takes boolexpr b returns nothing
call TriggerClearConditions(fireTrigger)
call TriggerAddCondition(fireTrigger, b)
call TriggerEvaluate(fireTrigger)
endfunction
function RunHeroAI takes unit hero returns nothing
if heroesAI.has(GetHandleId(hero)) then
debug call BJDebugMsg("[Hero AI] Error: Attempt to run an AI for a unit that already has one, aborted.")
return
endif
if infoAI.boolexpr[GetUnitTypeId(hero)] != null then
set RegisterUnit = hero
call FireCondition(infoAI.boolexpr[GetUnitTypeId(hero)])
else
call DefaultHeroAI.create(hero)
endif
endfunction
function RegisterHeroAI takes integer unitTypeId, code register returns nothing
if infoAI.boolexpr[unitTypeId] != null then
debug call BJDebugMsg("[Hero AI] Error: Attempt to register an AI struct for a unit-type id again, aborted")
return
endif
set infoAI.boolexpr[unitTypeId] = Filter(register)
endfunction
function DoesHeroHaveAI takes unit hero returns boolean
return heroesAI.has(GetHandleId(hero))
endfunction
//! textmacro HeroAI_Register takes HERO_UNIT_TYPEID
private function RegisterAI takes nothing returns nothing
call AI.create(HeroAI_RegisterUnit)
endfunction
private module ModuleHack
static method onInit takes nothing returns nothing
call RegisterHeroAI($HERO_UNIT_TYPEID$, function RegisterAI)
endmethod
endmodule
private struct StructHack extends array
implement ModuleHack
endstruct
//! endtextmacro
private module I
static method onInit takes nothing returns nothing
set infoAI = Table.create()
set heroesAI = Table.create()
endmethod
endmodule
private struct A extends array
implement I
endstruct
endlibrary
//TESH.scrollpos=35
//TESH.alwaysfold=0
//==========================================================================================
// This is separated from the main system of HeroAI to provide better organization.
// It covers how the AI will learn skills.
//
// When MULTIPLE_SKILLS_PER_LVL is true, you can register as many as MAX_SKILL_PER_LVL spells
// to one level for a hero in case you want your hero to learn multiple skills at one level.
//##########################################################################################
// Function API:
//
// * RegisterHeroAISkill takes integer unitTypeId, integer level, integer spellId *
// Registers the spell that a hero of unitTypeId should learn at level.
// This is provided as an alternative way of setting up skills in a custom AI and is
// recommended to do so then registering skills here.
//==========================================================================================
// Please don't call this textmacro.
//! textmacro HeroAILearnset
globals
private constant integer MAX_SKILL_LVL = 10 // The level at which the hero learns all of its skills
private constant boolean HAVE_MORPH_HEROES = true // Set to true if you have heroes that can morph
private constant boolean MULTIPLE_SKILLS_PER_LVL = true // Set to true if you want learnsets to register more than one skill per level
private constant integer MAX_SKILL_PER_LVL = 2 // The max number of skills a hero can learn every level. Only configure this if you have MULTIPLE_SKILLS_PER_LVL set to true.
endglobals
// The following allow better ease of configuration
private keyword Learnset
static if HAVE_MORPH_HEROES then
private keyword MorphHeroType
endif
globals
private Learnset LearnsetInfo
endglobals
// Configures how the heroes learn skills by unit-type id.
// You need to override this function with your own heroes and their skills.
// You should also set up any morphing heroes to their normal unit-type id so
// that you won't need to copy a learnset again.
private function SetupLearnset takes nothing returns nothing
// Learnset Syntax:
// set LearnsetInfo[LEVEL OF HERO][HERO UNIT-TYPE ID] = SKILL ID
/*
Just an example of how to use MULTIPLE_SKILLS_PER_LVL.
The syntax remains the same, but the system won't override the value.
*/
static if MULTIPLE_SKILLS_PER_LVL then
set LearnsetInfo[1]['Hblm'] = 'AHbn'
endif
// Tinker
/* Engineering Upgrade makes this a bit messy.
You will have to make sure that the other 3 abilities he learn
are correctly set to the upgraded ones from Engineering Upgrade.
*/
// Cluster Rockets
set LearnsetInfo[1]['Ntin'] = 'ANcs'
set LearnsetInfo[4]['Ntin'] = 'ANc1' // EU at level 1
set LearnsetInfo[8]['Ntin'] = 'ANc2' // EU at level 2
// Pocket Factory
set LearnsetInfo[2]['Ntin'] = 'ANsy'
set LearnsetInfo[5]['Ntin'] = 'ANs1' // EU at level 1
set LearnsetInfo[10]['Ntin'] = 'ANs3' // EU at level 3
// Engineering Upgrade
set LearnsetInfo[3]['Ntin'] = 'ANeg'
set LearnsetInfo[7]['Ntin'] = 'ANeg'
set LearnsetInfo[9]['Ntin'] = 'ANeg'
// Robo-Goblin
set LearnsetInfo[6]['Ntin'] = 'ANg1' // EU at level 1
// Assassin's learn set is defined in her AI using the function API.
// Morphing Hero Syntax:
// set MorphHeroType[MORPHED HERO UNIT-TYPE ID] = NORMAL HERO UNIT-TYPE ID
// If you don't have any morphing heroes, you should have HAVE_MORPH_HEROES set to false
// and remove the configuration.
set MorphHeroType['Nrob'] = 'Ntin'
endfunction
//==========================================================================================
// END OF USER CONFIGURATION
//==========================================================================================
private function MakeAILearnSkills takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer typeId = GetUnitTypeId(u)
static if MULTIPLE_SKILLS_PER_LVL then
local integer i = 0
endif
if heroesAI.has(GetHandleId(u)) and GetHeroSkillPoints(u) > 0 then
static if HAVE_MORPH_HEROES then
if MorphHeroType.has(typeId) then
set typeId = MorphHeroType[typeId]
endif
endif
static if MULTIPLE_SKILLS_PER_LVL then
loop
exitwhen i == LearnsetInfo[GetUnitLevel(u)].skillNum(typeId)
call SelectHeroSkill(u, LearnsetInfo[GetUnitLevel(u)].getSkill(typeId, i))
set i = i + 1
endloop
else
call SelectHeroSkill(u, LearnsetInfo[GetUnitLevel(u)][typeId])
endif
endif
set u = null
endfunction
static if HAVE_MORPH_HEROES then
private module MorphSuperHack
static method onInit takes nothing returns nothing
set thistype.info = Table.create()
endmethod
endmodule
struct MorphHeroType extends array
private static Table info
static method operator [] takes integer morphedTypeId returns integer
return info[morphedTypeId]
endmethod
static method operator []= takes integer morphedTypeId, integer originalTypeId returns nothing
set info[morphedTypeId] = originalTypeId
endmethod
static method has takes integer morphedTypeId returns boolean
return info.has(morphedTypeId)
endmethod
implement MorphSuperHack
endstruct
endif
static if MULTIPLE_SKILLS_PER_LVL then
private module RawIdIndexerInit
static method onInit takes nothing returns nothing
set thistype.info = Table.create()
endmethod
endmodule
private struct RawIdIndexer
static Table info
static integer count = -1 // Zero-based indexing due to array storage
implement RawIdIndexerInit
endstruct
private function RawId2Index takes integer rawId returns integer
if RawIdIndexer.info.has(rawId) then
return RawIdIndexer.info[rawId]
endif
set RawIdIndexer.count = RawIdIndexer.count + 1
set RawIdIndexer.info[rawId] = RawIdIndexer.count
return RawIdIndexer.count
endfunction
struct SkillArray extends array
private static integer array skills // Stores the skills based on hero raw id (indexed) and level it should be learned
private static integer array skillNums // Number of skills for a particular level
// "this" refers to the level (zero-based) that the skill will be learned at
method operator []= takes integer r, integer s returns nothing
local integer index
set r = RawId2Index(r)
set index = this + r * MAX_SKILL_LVL
static if DEBUG_MODE then
if index + skillNums[index] > 8190 then
call BJDebugMsg("[Hero AI Learnset] Error: Index is too high, try lowering MAX_SKILL_PER_LVL?")
return
endif
endif
if skillNums[index] < MAX_SKILL_PER_LVL then
set skills[index + skillNums[index]] = s
set skillNums[index] = skillNums[index] + 1
debug else
debug call BJDebugMsg("[Hero AI Learnset] Error: Exceeding the registration of skills to level " + I2S(this + 1))
endif
endmethod
method getSkill takes integer r, integer whichOne returns integer
return skills[(this + RawId2Index(r) * MAX_SKILL_LVL) + whichOne]
endmethod
method skillNum takes integer r returns integer
return skillNums[this + RawId2Index(r) * MAX_SKILL_LVL]
endmethod
endstruct
endif
private module M
static method onInit takes nothing returns nothing
set LearnsetInfo = thistype.create()
call RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_LEVEL, function MakeAILearnSkills)
call SetupLearnset()
endmethod
endmodule
// Wrapper to make things look better and cheat the zero-indexed Table.
// Only one instance will be created and used.
struct Learnset extends array
static if MULTIPLE_SKILLS_PER_LVL then
method operator [] takes integer lvl returns SkillArray
static if DEBUG_MODE then
if lvl > MAX_SKILL_LVL then
call BJDebugMsg("[Hero AI Learnset] Error: Level of " + I2S(lvl) + " is higher than MAX_SKILL_LVL")
return 0
endif
endif
return lvl - 1
endmethod
else
private static TableArray info
method operator [] takes integer lvl returns Table
static if DEBUG_MODE then
if lvl > MAX_SKILL_LVL then
call BJDebugMsg("[Hero AI Learnset] Error: Level of " + I2S(lvl) + " is higher than MAX_SKILL_LVL")
return 0
endif
endif
return info[lvl - 1]
endmethod
endif
static method create takes nothing returns thistype
static if not MULTIPLE_SKILLS_PER_LVL then
set info = TableArray[MAX_SKILL_LVL]
endif
return 1
endmethod
implement M
endstruct
function RegisterHeroAISkill takes integer unitTypeId, integer level, integer spellId returns nothing
set LearnsetInfo[level][unitTypeId] = spellId
endfunction
//! endtextmacro
//TESH.scrollpos=140
//TESH.alwaysfold=0
//==========================================================================================
// This is separated from the main system of HeroAI to provide better organization.
//
// It covers what items the AI will buy and how the data structures are set up.
// Configuration of what kinds of items shops sell is also done here.
//##########################################################################################
// Itemset API:
//
// * struct HeroAI_Itemset
// The data structure for storing the item ids and costs. Only holds up to MAX_INVENTORY_SIZE
// items. You should treat instances of these as being static since they shouldn't be destroyed.
//
// * method addItemTypeId takes integer itemTypeId returns nothing *
// Adds an item type to the itemset instance.
//==========================================================================================
// Please don't call this textmacro.
//! textmacro HeroAIItem
globals
private constant integer MAX_INVENTORY_SIZE = 0 // The max amount of items a hero can hold
public constant real SELL_ITEM_RANGE = 300. // The range at which shops sell items
private constant real SELL_ITEM_REFUND_RATE = 0.5 // The rate at which items' cost are refunded
private constant real IGNORE_ENEMY_SHOP_RANGE = 600. // The range at which the hero is from a shop and will ignore enemies in favor of buying items. Only considered if the hero is not already in STATE_ENGAGED
private constant integer MAX_ITEMSET_SIZE = 20 // The max amount of items that can be in one itemset
private constant boolean CHECK_REFUND_ITEM_COST = false // Decides if the AI should consider an item's refund price when deciding to buy items
endglobals
// Don't touch the following
public keyword Item
public keyword Itemset
globals
public Itemset DefaultItemBuild
endglobals
// You must set up all the items the AI can buy here. Each item should only be set up once.
// Note that a shop type id of 0 will cause the AI to buy the item without bothering to
// go to an actual shop.
// It is also advised to set up all items the hero can pick up and sell so that
// they will be refunded properly.
//
// Currently, multiple shops selling the same item is not supported.
private function SetupItems takes nothing returns nothing
// Syntax:
// call Item.setup(ITEM-TYPE ID, SHOP-TYPE ID, GOLD COST, LUMBER COST)
endfunction
// Sets up the default item build for AI heroes to get if itemBuild is not overridden.
// You should configure this for your own map.
private function SetupDefaultItemBuild takes nothing returns nothing
// Syntax:
// call DefaultItemBuild.addItem(ITEM-TYPE ID)
endfunction
//==========================================================================================
// END OF USER CONFIGURATION
//==========================================================================================
// serves more of an information wrapper for item cost and shop-type id
struct Item extends array
private static integer count = 0
private static Table info
private static integer array typeIds
private static integer array shopIds
private static integer array goldCosts
private static integer array lumberCosts
static method operator [] takes integer itemTypeId returns thistype
debug if not info.has(itemTypeId) then
debug call BJDebugMsg("[HeroAIItem] Error: Item not registered with system")
return 0
debug endif
return info[itemTypeId]
endmethod
method operator typeId takes nothing returns integer
return typeIds[this]
endmethod
method operator shopTypeId takes nothing returns integer
return shopIds[this]
endmethod
method operator goldCost takes nothing returns integer
return goldCosts[this]
endmethod
method operator lumberCost takes nothing returns integer
return lumberCosts[this]
endmethod
static method setup takes integer itemTypeId, integer shopTypeId, integer goldCost, integer lumberCost returns nothing
if count < 8190 then
set count = count + 1
set info[itemTypeId] = count
set typeIds[count] = itemTypeId
set shopIds[count] = shopTypeId
set goldCosts[count] = goldCost
set lumberCosts[count] = lumberCost
debug else
debug call BJDebugMsg("[HeroAIItem] Error: Max number of items registered")
endif
endmethod
static method onInit takes nothing returns nothing
set thistype.info = Table.create()
call SetupItems()
endmethod
endstruct
struct Itemset extends array
private static integer stack = 0
private static Item array items
private static integer array count
method item takes integer index returns Item
return items[this * MAX_ITEMSET_SIZE + index]
endmethod
method operator size takes nothing returns integer
return count[this]
endmethod
method addItemTypeId takes integer itemTypeId returns nothing
if count[this] < MAX_ITEMSET_SIZE then
set items[this * MAX_ITEMSET_SIZE + count[this]] = Item[itemTypeId]
set count[this] = count[this] + 1
debug else
debug call BJDebugMsg("[HeroAIItemset] Error: Itemset already has max item ids, aborted")
endif
endmethod
static method create takes nothing returns thistype
set stack = stack + 1
set count[stack] = 0
return stack
endmethod
static method onInit takes nothing returns nothing
set DefaultItemBuild = Itemset.create()
call SetupDefaultItemBuild()
endmethod
endstruct
globals
private boolean OnlyPowerUp // Used to determine if the AI could only get power ups
endglobals
private function AIItemFilter takes nothing returns boolean
return GetWidgetLife(GetFilterItem()) > 0.405 and IsItemVisible(GetFilterItem()) and (IsItemPowerup(GetFilterItem()) or not OnlyPowerUp)
endfunction
//! endtextmacro
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*****************************************************************************
*
* Get Closest Widget
* by Spinnaker v2.0.0.0
*
* Special thanks to Troll-Brain
*
******************************************************************************
*
* This snippet contains several functions which returns closest
* widget to given coordinates and passed filter.
*
******************************************************************************
*
* Requirements:
* Snippet optionaly requires IsDestructableTree,
* enabling user to choose if enumerated should be all destructables
* or only tree-type ones, therefore gives option to get closest tree.
*
******************************************************************************
*
* Important:
* Performance drop depends on amount of objects currently on the map
* Snippet functions are designed to reduce the search time as much as
* posible, although it's recommended to use non specific functions
* wisely, especialy if your map contains large numbers of widgets.
*
******************************************************************************
*
* Functions:
* function GetClosestItem takes real x, real y, boolexpr filter returns item
* - Gets single item, nearest passed coordinates
* function GetClosestItemInRange takes real x, real y, real radius, boolexpr filter returns item
* - Returns single item, closest to coordinates in given range
*
* function GetClosestDestructable takes real x, real y, boolean treeOnly, boolexpr filter returns destructable
* - Gets single destructable, nearest passed coordinates
* function GetClosestDestructableInRange takes real x, real y, real radius, boolean treeOnly, boolexpr filter returns destructable
* - Retrieves single destructable, closest to coordinates in given range
*
* function GetClosestUnit takes real x, real y, boolexpr filter returns unit
* - Returns closest unit matching specific filter
* function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
* - Gets nearest unit in range matching specific filter
* function GetClosestUnitInGroup takes real x, real y, group g returns unit
* - Retrieves closest unit in given group
*
* function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group g, boolexpr filter returns nothing
* - Returns up to N nearest units in range of passed coordinates
* function GetClosestNUnitsInGroup takes real x, real y, integer n, group sourceGroup, group destGroup returns nothing
* - Retrieves up to N closest units in passed group
*
*****************************************************************************/
library GetClosestWidget uses optional IsDestructableTree
private keyword Init
globals
private unit array Q
private real array V
private integer C=0
endglobals
struct ClosestWidget extends array
readonly static destructable cDest=null
readonly static item cItem=null
readonly static real distance=0
readonly static real cX=0
readonly static real cY=0
readonly static unit cUnit=null
static boolean cTree=false
static rect initRect=null
static method resetData takes real x, real y returns nothing
set cDest=null
set distance=100000
set cItem=null
set cUnit=null
set cX=x
set cY=y
endmethod
static method enumItems takes nothing returns nothing
local item i=GetEnumItem()
local real dx=GetWidgetX(i)-cX
local real dy=GetWidgetY(i)-cY
set dx = (dx*dx+dy*dy)/10000.
if dx<distance then
set cItem=i
set distance=dx
endif
set i =null
endmethod
static method enumDestructables takes nothing returns nothing
local destructable d=GetEnumDestructable()
local real dx
local real dy
static if LIBRARY_IsDestructableTree then
if cTree and not IsDestructableTree(d) then
return
endif
endif
set dx=GetWidgetX(d)-cX
set dy=GetWidgetY(d)-cY
set dx=(dx*dx+dy*dy)/10000.
if dx<distance then
set cDest=d
set distance=dx
endif
set d=null
endmethod
static method enumUnits takes nothing returns nothing
local unit u=GetEnumUnit()
local real dx=GetUnitX(u)-cX
local real dy=GetUnitY(u)-cY
set dx=(dx*dx+dy*dy)/10000.
if dx<distance then
set cUnit=u
set distance=dx
endif
set u=null
endmethod
static method sortUnits takes integer l, integer r returns nothing
local integer i=l
local integer j=r
local real v=V[(l+r)/2]
loop
loop
exitwhen V[i]>=v
set i=i+1
endloop
loop
exitwhen V[j]<=v
set j=j-1
endloop
if i<=j then
set V[0]=V[i]
set V[i]=V[j]
set V[j]=V[0]
set Q[0]=Q[i]
set Q[i]=Q[j]
set Q[j]=Q[0]
set i=i+1
set j=j-1
endif
exitwhen i>j
endloop
if l<j then
call sortUnits(l,j)
endif
if r>i then
call sortUnits(i,r)
endif
endmethod
static method saveGroup takes nothing returns nothing
local real dx
local real dy
set C=C+1
set Q[C]=GetEnumUnit()
set dx=GetUnitX(Q[C])-cX
set dy=GetUnitY(Q[C])-cY
set V[C]=(dx*dx+dy*dy)/10000.
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
set thistype.initRect=Rect(0,0,0,0)
endmethod
endmodule
function GetClosestItem takes real x, real y, boolexpr filter returns item
local real r=800.
call ClosestWidget.resetData(x,y)
loop
if r>3200. then
call EnumItemsInRect(bj_mapInitialPlayableArea, filter, function ClosestWidget.enumItems)
exitwhen true
else
call SetRect(ClosestWidget.initRect,x-r,y-r,x+r,y+r)
call EnumItemsInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumItems)
exitwhen ClosestWidget.cItem!=null
endif
set r=2*r
endloop
return ClosestWidget.cItem
endfunction
function GetClosestItemInRange takes real x, real y, real radius, boolexpr filter returns item
call ClosestWidget.resetData(x,y)
if radius>=0 then
call SetRect(ClosestWidget.initRect,x-radius,y-radius,x+radius,y+radius)
call EnumItemsInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumItems)
endif
return ClosestWidget.cItem
endfunction
function GetClosestDestructable takes real x, real y, boolean treeOnly, boolexpr filter returns destructable
local real r=800.
call ClosestWidget.resetData(x,y)
set ClosestWidget.cTree=treeOnly
loop
if r>3200. then
call EnumDestructablesInRect(bj_mapInitialPlayableArea, filter, function ClosestWidget.enumDestructables)
exitwhen true
else
call SetRect(ClosestWidget.initRect,x-r,y-r,x+r,y+r)
call EnumDestructablesInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumDestructables)
exitwhen ClosestWidget.cDest!=null
endif
set r=2*r
endloop
return ClosestWidget.cDest
endfunction
function GetClosestDestructableInRange takes real x, real y, real radius, boolean treeOnly, boolexpr filter returns destructable
call ClosestWidget.resetData(x,y)
if radius>=0 then
set ClosestWidget.cTree=treeOnly
call SetRect(ClosestWidget.initRect,x-radius,y-radius,x+radius,y+radius)
call EnumDestructablesInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumDestructables)
endif
return ClosestWidget.cDest
endfunction
function GetClosestUnit takes real x, real y, boolexpr filter returns unit
local real r=800.
call ClosestWidget.resetData(x,y)
loop
if r>3200. then
call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, filter)
exitwhen true
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, r, filter)
exitwhen FirstOfGroup(bj_lastCreatedGroup)!=null
endif
set r=2*r
endloop
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.enumUnits)
return ClosestWidget.cUnit
endfunction
function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
call ClosestWidget.resetData(x,y)
if radius>=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, filter)
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.enumUnits)
endif
return ClosestWidget.cUnit
endfunction
function GetClosestUnitInGroup takes real x, real y, group g returns unit
call ClosestWidget.resetData(x,y)
call ForGroup(g, function ClosestWidget.enumUnits)
return ClosestWidget.cUnit
endfunction
function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group g, boolexpr filter returns nothing
local integer q=n+1
call ClosestWidget.resetData(x,y)
if radius>=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, filter)
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.saveGroup)
call ClosestWidget.sortUnits(1,C)
loop
exitwhen n==0 or Q[-n+q]==null
call GroupAddUnit(g, Q[-n+q])
set n =n-1
endloop
endif
set C=0
endfunction
function GetClosestNUnitsInGroup takes real x, real y, integer n, group sourceGroup, group destGroup returns nothing
local integer q=n+1
call ClosestWidget.resetData(x,y)
call ForGroup(sourceGroup, function ClosestWidget.saveGroup)
call ClosestWidget.sortUnits(1,C)
loop
exitwhen n==0 or Q[-n+q]==null
call GroupAddUnit(destGroup, Q[-n+q])
set n=n-1
endloop
set C=0
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table // made by Bribe, special thanks to Nestharus, version 3.0.0.0
/*
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private hashtable ht = InitHashtable() //The last hashtable you need
private integer more = 2 //Index generation for Tables (above 2)
private integer less = 0 //Index generation for TableArrays (below 0)
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return 1
endmethod
static method operator list takes nothing returns Table
return 2
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive type-syntax
implement realm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
// set this = a[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key)
endmethod
// set a[389034] = 8192
method operator []= takes integer key, Table a returns nothing
call SaveInteger(ht, this, key, a)
endmethod
// set b = a.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key)
endmethod
// call a.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key)
endmethod
// Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
// local Table a = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set more = more + 1
set this = more
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this)
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call a.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
endstruct
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table a = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = a[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set less = less - array_size
set this = less
else
set a[0] = a[this] //Set the last destroyed to the last-last destroyed
call a.remove(this) //Clear hash memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//da[integer a].unit[integer b] = unit u
//da[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; assumed you'd call .flush()
//if you want it flushed too. This is public so that if you are flushing
//instances the whole time you don't waste efficiency when disposing the
//TableArray.
//
method destroy takes nothing returns nothing
local Table a = dex.size[this.size]
debug if this.size <= 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if a == 0 then
//Create an array to index recycled instances with their array size
set a = Table.create()
set dex.size[this.size] = a
endif
call dex.size.remove(this) //Clear the array size from hash memory
set a[this] = a[0]
set a[0] = this
endmethod
//All you need to know about this one is that it won't hit the op limit.
private static method clean takes Table a, integer end returns nothing
local integer i = a + 5000
if i < end then
call clean.evaluate(i, end)
set end = i
endif
loop
call a.flush()
set a = a + 1
exitwhen a == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
local integer end = this.size + this
debug if this == end then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
call clean.evaluate(this, end)
call this.destroy()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v4.2.0.0
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must
* return false. They can return nothing as well.
*
* Disclaimer:
* -----------
*
* - Don't use TriggerSleepAction inside registered code.
*
* API:
* ----
*
* function RegisterPlayerUnitEvent
* takes
* playerunitevent whichEvent : The event you would like to register
* code whichFunction : The code you would like to register
* returns
* nothing
*
* - Registers code that will execute when an event fires.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
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 = false
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
//TESH.scrollpos=0
//TESH.alwaysfold=0
//==========================================================================================
// HeroAIPriority
// by watermelon_1234
//==========================================================================================
// An optional add-on to HeroAI. It causes the hero AI to prioritize enemy units based on
// their life, relative life, and distance. It also allows two methods to be defined to
// allow custom AI to perceive a more specific priority for a unit.
//
// The inclusion of this library automatically makes it available to HeroAI.
//##########################################################################################
// Additional Members:
// * unit priorityEnemy -> The enemy with the most priority
//
// API:
// * method weightPriority takes unit u returns real *
// Returns the priority the hero would view from a unit
//
// * method setPriorityEnemy takes group g returns nothing *
// Sets the priorityEnemy member to the unit with the most priority from the group
//
// Interface:
// * static method addPriority takes unit u returns real *
// This method allows a custom AI to add priority for the unit u.
// Return the additional priority as a real. Note that this priority will be added
// before being modified by any factor.
//
// * static method modPriority takes unit u returns real *
// This method allows a customAI to modify priority for the unit u.
// Return the factor the unit's priority will be multiplied by.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Installing:
// 1. Copy this trigger into your map
// 2. Configure the numbers used for weighting priorities
//==========================================================================================
library HeroAIPriority
globals
private constant real LIFE_PERCENT_WEIGHT = 3.25 // Priority weight given to percent life of the perceived unit
private constant real LIFE_PERCENT_POWER = 3.5 // A higher value causes priority perceived from lower percent life to increase
private constant real RELATIVE_LIFE_WEIGHT = 1.5 // Priority weight given to relative life between the viewer and the perceived unit
private constant real DIST_WEIGHT = 2.85 // Priority weight given to distance between the viewer and perceived unit
private constant real DIST_POWER = 3.15 // A higher value causes priority to increase more if the unit is closer
private constant real HERO_FACTOR = 3.25 // Multplies the overall priority if the unit is a hero
endglobals
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
// To avoid needless duplication.
private function BaseViewedPriority takes unit hero, unit u returns real
local real life = GetWidgetLife(u)
local real maxLife = GetUnitState(u, UNIT_STATE_MAX_LIFE)
local real lifeWeight = LIFE_PERCENT_WEIGHT * Pow( (maxLife - life)/maxLife, LIFE_PERCENT_POWER ) + RELATIVE_LIFE_WEIGHT * GetWidgetLife(hero)/life
// More priority is given if the unit is closer to the hero
local real dx = GetUnitX(u) - GetUnitX(hero)
local real dy = GetUnitY(u) - GetUnitY(hero)
local real distWeight = DIST_WEIGHT * Pow( (HeroAI_SIGHT_RANGE - SquareRoot(dx*dx + dy*dy)) / HeroAI_SIGHT_RANGE, DIST_POWER)
return lifeWeight + distWeight
endfunction
private function BasePriorityFactor takes unit u returns real
local real factor = 1
if IsUnitType(u, UNIT_TYPE_HERO) then
set factor = factor * HERO_FACTOR
endif
return factor
endfunction
module HeroAIPriority
unit priorityEnemy
method weightPriority takes unit u returns real
static if not thistype.addPriority.exists and not thistype.modPriority.exists then
return BaseViewedPriority(.hero, u) * BasePriorityFactor(u)
else
local real priority = BaseViewedPriority(.hero, u)
local real factor = BasePriorityFactor(u)
static if thistype.addPriority.exists then
set priority = priority + .addPriority(u)
endif
static if thistype.modPriority.exists then
set factor = factor * .modPriority(u)
endif
return priority*factor
endif
endmethod
method setPriorityEnemy takes group g returns nothing
local unit u
local real highest = 0
local real priority
set .priorityEnemy = null
// Preserves group g by using another group variable
call GroupClear(bj_lastCreatedGroup)
call GroupAddGroup(g, bj_lastCreatedGroup)
loop
set u = FirstOfGroup(bj_lastCreatedGroup)
exitwhen u == null
call GroupRemoveUnit(bj_lastCreatedGroup, u)
set priority = .weightPriority(u)
if priority > highest then
set highest = priority
set .priorityEnemy = u
endif
endloop
endmethod
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//==========================================================================================
// HeroAIThreat
// by watermelon_1234
//==========================================================================================
// An optional add-on to HeroAI. It causes the hero AI to perceive threat from enemy units
// based on their life, relative life, and level. It also allows two methods to be defined
// to allow custom AI to perceive more specific threat from a unit. An additional method is
// provided to change the AI's threat threshold for running away.
//
// The inclusion of this library automatically makes it available to HeroAI.
//##########################################################################################
// Additional Members:
// * real threat -> The total threat the hero faces
// * real threatThreshold -> The threshold from which the hero would ignore threat
// * unit threatEnemy -> The enemy with the most threat
//
// API:
// * method weightThreat takes unit u returns real *
// Returns the threat the hero would perceive from the unit.
//
// * method calcThreat takes nothing returns nothing *
// Calculates threat and threatThreshold, along with setting threatEnemy. Will be called
// in method update if there is at least one enemy within SIGHT_RANGE of the hero.
//
// * method retreat takes nothing returns boolean *
// Makes the hero retreat from threatEnemy if threat is larger than threatThreshold.
// Returns true if the hero retreated.
//
// Interface:
// * static method addThreat takes unit u returns real *
// This method allows a custom AI to add threat from a unit u.
// Return the additional threat as a real. Note that this threat will be added
// before being modified by any factor.
//
// * static method modThreat takes unit u returns real *
// This method allows a customAI to modify threat for the unit u.
// Return the factor the unit's threat will be multiplied by.
//
// * method selfRunFactor takes nothing returns real *
// This method should return the factor for the threat threshold when the AI is choosing
// to run away.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Installing:
// 1. Copy this trigger into your map
// 2. Configure the constants to control how threat is weighted
//==========================================================================================
library HeroAIThreat
globals
public constant real THRESHOLD_RUN_FACTOR = 2.5 // Factor for threat threshold when deciding to run away based on hero's percent life.
// Used only if selfRunFactor is undefined
private constant real RETREAT_DIST = 225. // Distance the hero attempts to move away from threatEnemy
private constant real LIFE_PERCENT_WEIGHT = 2.15 // Threat weight given to percent life of the unit
private constant real LIFE_PERCENT_POWER = 2.5 // A higher value causes threat perceived from lower percent life to diminish
private constant real RELATIVE_LIFE_WEIGHT = 0.75 // Threat weight given to the relative life of enemy units compared to self
private constant real ALLY_LVL_WEIGHT = .45 // Determines weight for allied unit levels
private constant real ENEMY_LVL_DIFF_WEIGHT = .58 // Determines weight for enemy level difference
private constant real HERO_FACTOR = 2.5 // Multiplies the overall threat if the unit is a hero
endglobals
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
private function BaseViewedThreat takes unit hero, unit u, boolean allied returns real
local real life = GetWidgetLife(u)
local real threat = LIFE_PERCENT_WEIGHT * Pow( life/GetUnitState(u, UNIT_STATE_MAX_LIFE), LIFE_PERCENT_POWER)
if allied then
set threat = threat + ALLY_LVL_WEIGHT * GetUnitLevel(u)
else
set threat = threat + ENEMY_LVL_DIFF_WEIGHT * (GetUnitLevel(u) - GetUnitLevel(hero)) + RELATIVE_LIFE_WEIGHT*life/GetWidgetLife(hero)
endif
return threat
endfunction
private function BaseThreatFactor takes unit u returns real
local real factor = 1
if IsUnitType(u, UNIT_TYPE_HERO) then
set factor = factor * HERO_FACTOR
endif
return factor
endfunction
module HeroAIThreat
real threat
real threatThreshold
unit threatEnemy
method weightThreat takes unit u returns real
local boolean allied = IsUnitAlly(u, .owner)
local real threat = BaseViewedThreat(.hero, u, allied)
local real factor = BaseThreatFactor(u)
static if thistype.addThreat.exists then
set threat = threat + .addThreat(u)
endif
if threat > 0 then
static if thistype.modThreat.exists then
set factor = factor * .modThreat(u)
endif
return threat * factor
endif
// Don't consider negative threat
return 0.
endmethod
method calcThreat takes nothing returns nothing
local unit u
local real highest = 0
local real t
set .threat = 0
set .threatThreshold = LIFE_PERCENT_WEIGHT * Pow(.percentLife, LIFE_PERCENT_POWER) + ALLY_LVL_WEIGHT * GetUnitLevel(.hero) // Threshold calculated for hero
set .threatEnemy = null
// Preserves group g by using another group variable
call GroupClear(bj_lastCreatedGroup)
call GroupAddGroup(.units, bj_lastCreatedGroup)
loop
set u = FirstOfGroup(bj_lastCreatedGroup)
exitwhen u == null
call GroupRemoveUnit(bj_lastCreatedGroup, u)
set t = weightThreat(u)
if IsUnitAlly(u, .owner) then
set .threatThreshold = .threatThreshold + t
else
set .threat = .threat + t
if highest < t then
set highest = t
set .threatEnemy = u
endif
endif
endloop
endmethod
method retreat takes nothing returns boolean
local real ang
if .threat > .threatThreshold then
if .state == HeroAI_STATE_RUN_AWAY then
return .run()
endif
set ang = Atan2(.hy - GetUnitY(.threatEnemy), .hx - GetUnitX(.threatEnemy))
return IssuePointOrder(.hero, "move", .hx + RETREAT_DIST*Cos(ang), .hy + RETREAT_DIST*Sin(ang))
endif
return false
endmethod
method operator thresholdRunFactor takes nothing returns real
static if thistype.selfRunFactor.exists then
return .selfRunFactor()
else
return THRESHOLD_RUN_FACTOR * .percentLife
endif
endmethod
endmodule
endlibrary
//TESH.scrollpos=23
//TESH.alwaysfold=0
//==========================================================================================
// HeroAIEventResponse
// by watermelon_1234
//==========================================================================================
// An optional add-on to HeroAI, intended to allow outside event responses to interact with
// the AI. The library allows the AI to act immediately after casting a spell or picking up
// an item and allowing custom AI to define a method that fires whenever the hero gets attacked
//
// NOTE: It is unadvised to use this library unless you're comfortable with writing vJass code.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Installing:
// 1. Copy this trigger into your map.
// 2. Configure the constants to determine which event responses will be used
//==========================================================================================
library HeroAIEventResponse
globals
private constant boolean ACT_AFTER_ACTION = true
// If set to true, causes the default AI to do looping actions
// immediately after finishing spell casts or picking up items.
private constant boolean USE_ON_ATTACKED = false
// If set to true, allows custom AI structs to define an onAttacked method
// that fires whenever the hero gets attacked.
//
// The onAttacked method should be defined as the following:
// * method onAttacked takes unit attacker returns nothing *
// attacker refers to the unit that attacked the hero
private constant boolean USE_ON_ACQUIRE = false
// If set to true, allows custom AI structs to define an onAcquire method that
// fires whenever the hero acquires a target.
//
// The onAcquire method should be defined as the following:
// * method onAcquire takes unit target returns nothing *
// target refers to the unit that the hero targetted
endglobals
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
module HeroAIEventResponse
static if ACT_AFTER_ACTION then
private static method actAfterwards takes nothing returns nothing
local thistype this = thistype.getAIIndexFromHero(GetTriggerUnit())
call .update()
static if thistype.loopActions.exists then
call .loopActions()
else
call .defaultLoopActions()
endif
endmethod
endif
static if USE_ON_ATTACKED and thistype.onAttacked.exists then
private static method fireAttackedEvent takes nothing returns nothing
local thistype this = thistype.getAIIndexFromHero(GetTriggerUnit())
call .onAttacked(GetAttacker())
endmethod
endif
static if USE_ON_ACQUIRE and thistype.onAcquire.exists then
private static method fireAcquireEvent takes nothing returns nothing
local thistype this = thistype.getAIIndexFromHero(GetTriggerUnit())
call .onAcquire(GetEventTargetUnit())
endmethod
endif
// Registers event responses only when the AI is used for a hero.
// In an ideal world, this would be private, but it needs to be
// called by the create method in the HeroAIStruct module. So please
// don't call this method because it's unnecessary for you to do so.
method registerEventResponses takes nothing returns nothing
local trigger trig
static if ACT_AFTER_ACTION then
set trig = CreateTrigger()
call TriggerRegisterUnitEvent(trig, .hero, EVENT_UNIT_SPELL_ENDCAST)
call TriggerRegisterUnitEvent(trig, .hero, EVENT_UNIT_PICKUP_ITEM)
call TriggerAddAction(trig, function thistype.actAfterwards)
endif
static if USE_ON_ATTACKED and thistype.onAttacked.exists then
set trig = CreateTrigger()
call TriggerRegisterUnitEvent(trig, .hero, EVENT_UNIT_ATTACKED)
call TriggerAddAction(trig, function thistype.fireAttackedEvent)
endif
static if USE_ON_ACQUIRE and thistype.onAcquire.exists then
set trig = CreateTrigger()
call TriggerRegisterUnitEvent(trig, .hero, EVENT_UNIT_ACQUIRED_TARGET)
call TriggerAddAction(trig, function thistype.fireAcquireEvent)
endif
set trig = null
endmethod
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//==========================================================================================
// AIPlayerDifficultySettings
// by watermelon_1234
//==========================================================================================
// Allows the AI to have handicaps or advantages based on difficulty settings. Factors that are
// available to change are experience rate, bonus gold per second, bonus lumber per second.
//
// Note that experience rate will be set for that player while bonus gold and lumber are
// given per hero.
//
// This library is independent of HeroAI and only works for slots set to being a computer.
//##########################################################################################
// API:
//
// * StartAIDifficultyBonuses takes nothing returns nothing *
// Periodic bonuses from difficulty aren't given until this function is called.
//
// * StopAIDifficultyBonuses takes nothing returns nothing *
// Stops giving the periodic bonuses
//==========================================================================================
library AIPlayerDifficultySettings
globals
private constant real BONUS_TIMER_LOOP = 5. // The interval for giving bonuses to the AI
// Experience rate
private constant real EASY_XP_RATE = 2.
private constant real NORM_XP_RATE = 1.35
private constant real HARD_XP_RATE = 2.
// Gold gained per second
private constant integer EASY_BONUS_GOLD = 0
private constant integer NORM_BONUS_GOLD = 1
private constant integer HARD_BONUS_GOLD = 3
// Lumber gained per second
private constant integer EASY_BONUS_LUMBER = 0
private constant integer NORM_BONUS_LUMBER = 0
private constant integer HARD_BONUS_LUMBER = 0
endglobals
private struct dat extends array
static timer tim = null
integer bonusGold
integer bonusLumber
static method onInit takes nothing returns nothing
local aidifficulty diff
local integer i = 0
local player p
loop
exitwhen i == 12
set p = Player(i)
if GetPlayerController(p) == MAP_CONTROL_COMPUTER then
set diff = GetAIDifficulty(p)
// Easy
if diff == AI_DIFFICULTY_NEWBIE then
call SetPlayerHandicapXP(p, EASY_XP_RATE)
set thistype[i].bonusGold = EASY_BONUS_GOLD
set thistype[i].bonusLumber = EASY_BONUS_LUMBER
// Normal
elseif diff == AI_DIFFICULTY_NORMAL then
call SetPlayerHandicapXP(p, NORM_XP_RATE)
set thistype[i].bonusGold = NORM_BONUS_GOLD
set thistype[i].bonusLumber = NORM_BONUS_LUMBER
// Hard
else
call SetPlayerHandicapXP(p, HARD_XP_RATE)
set thistype[i].bonusGold = HARD_BONUS_GOLD
set thistype[i].bonusLumber = HARD_BONUS_LUMBER
endif
endif
set i = i + 1
endloop
endmethod
endstruct
private function AIGiveBonuses takes nothing returns nothing
local integer i = 0
loop
exitwhen i == 12
if dat[i].bonusGold > 0 then
call SetPlayerState(Player(i), PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(Player(i), PLAYER_STATE_RESOURCE_GOLD) + R2I(dat[i].bonusGold * BONUS_TIMER_LOOP))
endif
if dat[i].bonusLumber > 0 then
call SetPlayerState(Player(i), PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(Player(i), PLAYER_STATE_RESOURCE_LUMBER) + R2I(dat[i].bonusLumber * BONUS_TIMER_LOOP))
endif
set i = i + 1
endloop
endfunction
function StartAIDifficultyBonuses takes nothing returns nothing
if dat.tim == null then
set dat.tim = CreateTimer()
endif
call TimerStart(dat.tim, BONUS_TIMER_LOOP, true, function AIGiveBonuses)
endfunction
function StopAIDifficultyBonuses takes nothing returns nothing
call PauseTimer(dat.tim)
endfunction
endlibrary
//TESH.scrollpos=210
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
/**************************************
*
* IsUnitChanneling
* v1.2.0.1
* By Magtheridon96
*
* - Tells whether a unit is channeling or not.
*
* Optional Requirements:
* ----------------------
*
* - RegisterPlayerUnitEvent by Magtheridon96
* - hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
* - UnitIndexer by Nestharus
* - hiveworkshop.com/forums/jass-resources-412/system-unit-indexer-172090/
* - Table by Bribe
* - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
* Note:
* -----
*
* - All the requirements are completely optional.
* You can have none if you want. If all are present, the system
* will utilize UnitIndexer and RegisterPlayerUnitEvent.
* - This will not work with Table versions older than 3.1
*
* API:
* ----
*
* - function IsUnitChanneling takes unit whichUnit returns boolean
* - Tells whether a unit is channeling or not.
*
**************************************/
library IsUnitChanneling requires optional UnitIndexer, optional Table, optional RegisterPlayerUnitEvent
private struct OnChannel extends array
static if LIBRARY_UnitIndexer then
static boolean array channeling
elseif LIBRARY_Table then
private static key tb
static Table channeling = tb
else
static hashtable channeling = InitHashtable()
endif
private static method onStart takes nothing returns nothing
static if LIBRARY_UnitIndexer then
set channeling[GetUnitUserData(GetTriggerUnit())] = true
elseif LIBRARY_Table then
set channeling.boolean[GetHandleId(GetTriggerUnit())] = true
else
call SaveBoolean(channeling, GetHandleId(GetTriggerUnit()), 0, true)
endif
endmethod
private static method onEnd takes nothing returns nothing
static if LIBRARY_UnitIndexer then
set channeling[GetUnitUserData(GetTriggerUnit())] = false
elseif LIBRARY_Table then
call channeling.boolean.remove(GetHandleId(GetTriggerUnit()))
else
call RemoveSavedBoolean(channeling, GetHandleId(GetTriggerUnit()), 0)
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onStart)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onEnd)
else
local trigger t = CreateTrigger()
local code c = function thistype.onStart
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CHANNEL)
call TriggerAddCondition(t, Filter(c))
set t = CreateTrigger()
set c = function thistype.onEnd
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t, Filter(c))
set t = null
endif
endmethod
endstruct
function IsUnitChanneling takes unit u returns boolean
static if LIBRARY_UnitIndexer then
return OnChannel.channeling[GetUnitUserData(u)]
elseif LIBRARY_Table then
return OnChannel.channeling.boolean[GetHandleId(u)]
else
return LoadBoolean(OnChannel.channeling, GetHandleId(u), 0)
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library PruneGroup
//*****************************************************************
//* PruneGroup
//* written by: Anitarf
//*
//* PruneGroup is a function that removes units from a group based
//* on a user-specified fitness function.
//*
//* function PruneGroup takes group g, Fitness Function, integer maxUnits, real minFitness returns nothing
//*
//* The fitness function must follow the Fitness function
//* interface that is defined at the start of this library. The
//* value it returns specifies a unit's fitness, the units with a
//* higher value will remain in the group while the units with a
//* lower fitness will be removed from it.
//* The maxUnits argument specifies at most how many units may
//* remain in the group, but the actual number may be lower if the
//* group didn't have that many units in it to begin with or if
//* too few units had a fitness higher than minFitness, the last
//* argument of the PruneGroup function.
//* The fitness limit can be disabled with the NO_FITNESS_LIMIT
//* constant value defined in the calibration section, this way
//* you can prune a group based only on the unit limit.
//*****************************************************************
// This is the function interface for the fitness functions.
public function interface Fitness takes unit u returns real
globals
// If this constant is passed to the PruneGroup function it will ignore the fitness limit.
// This is a random value that is unlikely to be ever used, you don't really need to change it.
constant real NO_FITNESS_LIMIT = -112358.13
endglobals
// END OF CALIBRATION SECTION
// ================================================================
globals
private Fitness func=0
private integer maxcount=0
private real minfit=0.0
private boolean ignoreminfit=false
private unit array fittest
private real array fitness
private integer array next
private integer last=0
private integer count=0
private integer N=0
endglobals
private function Enum takes nothing returns nothing
local unit u=GetEnumUnit()
local real fit=func.evaluate(u)
local integer i
local integer j
// Check if we should bother adding the unit to the list.
if (ignoreminfit or fit>minfit) and (count<maxcount or fit>fitness[last]) then
// Get a new index and store the unit.
set N=N+1
set count=count+1
set fittest[N]=u
set fitness[N]=fit
// Add the index to the sorted list.
if last==0 or fit<fitness[last] then
set next[N]=last
set last=N
else
set i=last
loop
set j=next[i]
exitwhen j==0 or fitness[j]>=fit
set i=j
endloop
set next[N]=next[i]
set next[i]=N
endif
// Remove the last unit from the list if needed.
if count>maxcount then
set last=next[last]
set count=count-1
endif
endif
set u=null
endfunction
// ================================================================
function PruneGroup takes group g, Fitness Function, integer maxUnits, real minFitness returns nothing
// Remember the previous values in case this is interrupting another PruneGroup call.
local Fitness f=func
local integer mc=maxcount
local real mf=minfit
local integer l=last
local integer c=count
local integer n=N
// Take care of faulty inputs.
if maxUnits<=0 then
call GroupClear(g)
return
endif
// Populate the sorted list.
set count=0
set last=0
set minfit=minFitness
set maxcount=maxUnits
set ignoreminfit=minfit==NO_FITNESS_LIMIT
set func=Function
call ForGroup(g, function Enum)
// Repopulate the group from the list.
call GroupClear(g)
loop
exitwhen last==0
call GroupAddUnit(g, fittest[last])
set last=next[last]
endloop
// Cleanup handle references.
loop
exitwhen N==n
set fittest[N]=null
set N=N-1
endloop
// Return the temporary globals to their previous values.
set func=f
set minfit=mf
set maxcount=mc
set ignoreminfit=minfit==NO_FITNESS_LIMIT
set count=c
set last=l
endfunction
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
library FitnessFunc requires PruneGroup
//*****************************************************************
//* FITNESS FUNCTIONS
//* written by: Anitarf
//* requires: -PruneGroup
//*
//* This is a set of functions intended to be used as fitness
//* functions for PruneGroup calls, so they all take a unit
//* parameter and return a real in accordance with the Fitness
//* function interface. The functions provided cover most basic
//* criteria according to which users might want to sort units.
//* The functions are:
//*
//* FitnessFunc_LowLife - favours units with low life
//* FitnessFunc_HighLife - favours units with high life
//* FitnessFunc_LowMaxLife - favours units with low max life
//* FitnessFunc_HighMaxLife - favours units with high max life
//* FitnessFunc_LowMana - favours units with low mana
//* FitnessFunc_HighMana - favours units with high mana
//* FitnessFunc_LowMaxMana - favours units with low max mana
//* FitnessFunc_HighMaxMana - favours units with high max mana
//* FitnessFunc_LowDistance - favours units closer to a point
//* FitnessFunc_HighDistance - favours units further away from a point
//*
//* The last two functions need to have a point defined before
//* they can be used as a fitness function for PruneGroup, to
//* define a point use the following function:
//*
//* function SetFitnessPosition takes real x, real y returns nothing
//*****************************************************************
//! textmacro PruneFitness_UnitState takes name, state
public function Low$name$ takes unit u returns real
return -GetUnitState(u, $state$)
endfunction
public function High$name$ takes unit u returns real
return GetUnitState(u, $state$)
endfunction
//! endtextmacro
//! runtextmacro PruneFitness_UnitState("Life", "UNIT_STATE_LIFE")
//! runtextmacro PruneFitness_UnitState("Mana", "UNIT_STATE_MANA")
//! runtextmacro PruneFitness_UnitState("MaxLife", "UNIT_STATE_MAX_LIFE")
//! runtextmacro PruneFitness_UnitState("MaxMana", "UNIT_STATE_MAX_MANA")
// ================================================================
globals
private real X=0.0
private real Y=0.0
endglobals
function SetFitnessPosition takes real x, real y returns nothing
set X=x
set Y=y
endfunction
public function LowDistance takes unit u returns real
local real x = GetUnitX(u)-X
local real y = GetUnitY(u)-Y
return -x*x-y*y
endfunction
public function HighDistance takes unit u returns real
local real x = GetUnitX(u)-X
local real y = GetUnitY(u)-Y
return x*x+y*y
endfunction
endlibrary