//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
function createMapInfoQuests takes nothing returns nothing
local quest q
local string s
//Summary
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Doodad-Cliff.blp")
call QuestSetTitle(q, "Map Summary")
set s = ""
set s = s + "You can email Strilanc at |[email protected]|r\n"
set s = s + "Note: use a descriptive email title or it might be deleted as spam."
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Author:|r Strilanc")
call CreateQuestItemBJ(q, "|cFFFFCC00Minimap:|r Silveri3ullet")
call CreateQuestItemBJ(q, "|cFFFFCC00New/Clean versions:|r epicwar.com|r")
//How to Play
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn.blp")
call QuestSetTitle(q, "How to Play")
set s = ""
set s = s + "|cFFFFCC00What is power?|r\n"
set s = s + "Power (or energy) is mana. All towers except the basic arrow tower need it to be effective. It must be created by generators (water wheel, furnace) and transfered to your combat towers.\n"
set s = s + "\n"
set s = s + "|cFFFFCC00How do I use it?|r\n"
set s = s + "Here is a simple example of how you can power a Tesla Coil using a Furnace:\n"
set s = s + "Step 1 - Pick a spot with lots of grass for a furnace to burn\n"
set s = s + "Step 2 - Build a furnace and Tesla Coil close together on this spot\n"
set s = s + "Step 3 - Select the furnace and cast 'Add Transfer Target' on the Tesla Coil\n"
set s = s + "Your tesla coil will now receive mana from the furnace and use it to cast chain lightning.\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "What is power?")
call CreateQuestItemBJ(q, "How do I use it?")
//Commands
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\CommandButtons\\BTNCrate.blp")
call QuestSetTitle(q, "Commands")
set s = ""
set s = s + "|cFFFFCC00-ready|r\n"
set s = s + "examples: -ready\n"
set s = s + "Votes to start the next round. All players must agree to skip, unless it's race mode.\n"
set s = s + "\n"
set s = s + "|cFFFFCC00-boot (player name, number or color)|r\n"
set s = s + "examples: -boot Strilanc, -boot 1, -boot red\n"
set s = s + "Votes to remove a player from the game. A player is only booted if more than half of the players vote to boot that player. Votes expire after one minute.\n"
set s = s + "\n"
set s = s + "|cFFFFCC00-block (player name, number or color)|r\n"
set s = s + "examples: -block Strilanc, -block 1, -block red\n"
set s = s + "Stops the given player from building in your area.\n"
set s = s + "\n"
set s = s + "|cFFFFCC00-unblock (player name, number or color)|r\n"
set s = s + "Turns off the block command.\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00ready|r")
call CreateQuestItemBJ(q, "|cFFFFCC00boot|r player")
call CreateQuestItemBJ(q, "|cFFFFCC00block|r player, - |cFFFFCC00unblock|r player")
//Factoids
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Doodad-Prop.blp")
call QuestSetTitle(q, "Factoids")
set s = ""
set s = s + "|cFFFFCC00Special Rounds|r\n"
set s = s + "Round 5: Speedy\n"
set s = s + "Round 10: Feedback\n"
set s = s + "Round 15: Speedy + Feedback\n"
set s = s + "Round 20: Shield\n"
set s = s + "Round 25: Speedy + Shield\n"
set s = s + "Round 30: Speedy + Feedback + Shield\n"
set s = s + "Multiples of 5 past 30: Speedy + Feedback + Shield\n"
set s = s + "\n"
set s = s + "|cFFFFCC00Leaderboard Columns|r\n"
set s = s + "|cFFC3DBFFAvail|rable = Total mana available across all towers\n"
set s = s + "|cFFC3DBFFProduc|rtion = Mana generated by all generators per second\n"
set s = s + "|cFFC3DBFFDrain|r = Mana consumed per second by your towers when they are all attacking\n"
set s = s + "\n"
set s = s + "|cFFFFCC00Energy Stream Colors|r\n"
set s = s + "|cFFFF8000Orange|r = No energy transfered last second\n"
set s = s + "|cFF00FF00Green|r = 1 to 24 Joules transfered last second\n"
set s = s + "|cFF0000FFBlue|r = 25 to 124 Joules transfered last second\n"
set s = s + "|cFFFFCC00Yellow|r = 125 to 624 Joules transfered last second\n"
set s = s + "White = 625 or more Joules transfered last second\n"
set s = s + "\n"
set s = s + "|cFFFFCC00Measurements|r\n"
set s = s + "1 Joule = 1 mana\n"
set s = s + "1 Watt = 1 mana per second\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "Special Rounds")
call CreateQuestItemBJ(q, "Leaderboard Columns")
call CreateQuestItemBJ(q, "Energy Stream Colors")
call CreateQuestItemBJ(q, "Measurements")
//Effective Powering
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Random-Unit.blp")
call QuestSetTitle(q, "Effective Powering")
set s = ""
set s = s + "|cFFFFCC00What you want|r\n"
set s = s + "The only thing that really matters is whether or not your combat towers have the power they need when they need it. If your combat towers run out of power, they will become very dark, and do a lot less damage. If you fail to power them for a long time, you WILL lose. If your combat towers aren't dark, you are currently fine.\n"
set s = s + "\n"
set s = s + "|cFFFFCC00How you get it|r\n"
set s = s + "- Pay attention to the leaderboard. Make sure you are producing enough energy to deal with the drain when your combat towers are firing. Note that you can have more drain than production, as long as you have time to charge between rounds.\n"
set s = s + "- Pay attention to how the energy is actually being distributed. Even if you are producing enough energy, it may not be able to make it to your combat towers fast enough. Make sure your bridging towers are good enough. A good indicator of bad distribution is a fully-charged generator during the round: that means it is making energy faster than energy is leaving it.\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "What you want")
call CreateQuestItemBJ(q, "How you get it")
//Credit
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-MultipleUnits.blp")
call QuestSetTitle(q, "Credit")
set s = ""
set s = s + "|cFFFFCC00Maps|r\n"
set s = s + "Cube Defense 4.8: The main influence for the terrain and philosophy in this map. Great map for mazing.\n"
set s = s + "\n"
set s = s + "|cFFFFCC00Groups|r\n"
set s = s + "- The community at former scumedit.net\n"
set s = s + "- The community at wc3campaigns.net\n"
set s = s + "- A lot of people who joined test run games and helped, thanks!\n"
set s = s + "\n"
set s = s + "|cFFFFCC00People|r\n"
set s = s + "- Silver_Bullet for the minimap preview\n"
set s = s + "- Storm_Surge for testing\n"
set s = s + "- North_Titan for testing\n"
set s = s + "- the_lost_hero1 for testing\n"
set s = s + "- Dragon451 for testing. A lot of testing.\n"
set s = s + "- Mtg22 for suggestions, testing, and lots of hosting help.\n"
set s = s + "- the_lost_hero1 for suggestions and testing.\n"
set s = s + "- Mordacity for suggestions and testing.\n"
set s = s + "- Don-Para- for testing\n"
set s = s + "- Mytiq for locating a nasty bug\n"
set s = s + "- Various other names that I can't remember because there are so many.\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "Maps")
call CreateQuestItemBJ(q, "Groups")
call CreateQuestItemBJ(q, "People")
//History
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\CommandButtons\\BTNSpy.blp")
call QuestSetTitle(q, "History")
set s = ""
set s = s + "|cFFFFCC00v1.25 to v1.25b|r\n"
set s = s + "- Fixed a no-spawn bug when the last player with runners died or left\n"
set s = s + "|cFFFFCC00v1.24 to v1.25|r\n"
set s = s + "- Replaced clock tower with locust tower\n"
set s = s + "- Nerfed chemical tower\n"
set s = s + "- Buffed hero tower\n"
set s = s + "- Improved war mode\n"
set s = s + "- No bounty before completing rounds in race and rushed\n"
set s = s + "- Removed training mode\n"
set s = s + "- Added Psycho difficulty\n"
set s = s + "- Reworked tutorial\n"
set s = s + "- Lots of optimizations and bug fixes\n"
set s = s + "\n"
set s = s + "|cFFFFCC00v1.21 to v1.24|r\n"
set s = s + "- Fixed a few major bugs\n"
set s = s + "- Fixed sharing to allow building and upgrading\n"
set s = s + "- Added new modes\n"
set s = s + "\n"
set s = s + "|cFFFFCC00v1.20 to v1.21|r\n"
set s = s + "- Added more options at start\n"
set s = s + "- Added elemental arrow towers\n"
set s = s + "- Added Seller's Remorse\n"
set s = s + "- Nerfed tsunami and hero\n"
set s = s + "- Large rewrite of script\n"
set s = s + "- Lots of minor fixes and improvements\n"
call QuestSetDescription(q, s)
//History 2
set q = CreateQuest()
call QuestSetRequired(q, true)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\CommandButtons\\BTNSpy.blp")
call QuestSetTitle(q, "History 2")
set s = ""
set s = s + "|cFFFFCC00v1.10 to v1.20|r\n"
set s = s + "- Continued to adjust difficulty and towers\n"
set s = s + "- Added challenges\n"
set s = s + "- Added hero and tsunami towers\n"
set s = s + "- Fine tuned tutorial\n"
set s = s + "- Various minor fixes and improvements\n"
set s = s + "\n"
set s = s + "|cFFFFCC00v1.00 to v1.10|r\n"
set s = s + "- Adjusted Difficulty (a lot)\n"
set s = s + "- Added Special Rounds (speedy, feedback, shield)\n"
set s = s + "- Added 'Training' Mode\n"
set s = s + "- Removed Aura Towers\n"
set s = s + "- Added alternate builder with new towers\n"
set s = s + "- Added tutorial system\n"
set s = s + "- Fixed gold bug\n"
set s = s + "- Various minor fixes and improvements\n"
call QuestSetDescription(q, s)
endfunction
function InitTrig_Map_Info takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, 0, false)
call TriggerAddAction(t, function createMapInfoQuests)
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function createChallengeQuests takes nothing returns nothing
local quest q
local string s
//Rules
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\CommandButtons\\BTNDefend.blp")
call QuestSetTitle(q, "Challenge Rules")
set s = ""
set s = s + "Replays are kept but currently aren't posted anywhere.\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "Email replays to |[email protected]|r to enter.")
call CreateQuestItemBJ(q, "The first 5 to complete a challenge get their names here.")
call CreateQuestItemBJ(q, "The number by challenges is the number of spots left.")
call CreateQuestItemBJ(q, "(-) = closed, (#) = highscore challenge")
//Leech
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(1) Leech")
set s = ""
set s = s + "1st: Anduril33b\n"
set s = s + "2nd: GTO_BOY\n"
set s = s + "3rd: Mastersined\n"
set s = s + "4th: VigorousApathy\n"
set s = s + "5th: -\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Beat Corner Teams with a computer partner.|r")
call CreateQuestItemBJ(q, "Use Elite/15 Rounds/Sudden Death/Fair Income")
//Indy 15
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) Indy 15")
set s = ""
set s = s + "1st: Shiladie - 7m 02s\n"
set s = s + "2nd: Mastersined - 9m 17s\n"
set s = s + "3rd: -\n"
set s = s + "4th: -\n"
set s = s + "5th: -\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Finish Rookie/Solo/Race/15 Rounds/25 lives quickly|r")
call CreateQuestItemBJ(q, "Time = The time displayed on the board when the game ends")
call CreateQuestItemBJ(q, "closed due to version differences")
//Indy 30
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) Indy 30")
set s = ""
set s = s + "1st: tougo - 4m 19s\n"
set s = s + "2nd: Shiladie - 5m 20s\n"
set s = s + "3rd: Tougo - 8m 59s\n"
set s = s + "4th: Anduril33b - 9m 44s\n"
set s = s + "5th: _aH - 10m 7s\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Finish Noob/Solo/Race/30 Rounds/25 lives quickly|r")
call CreateQuestItemBJ(q, "Time = The time displayed on the board when the game ends")
call CreateQuestItemBJ(q, "closed due to version differences")
//Twins
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) Twins")
set s = ""
set s = s + "1st: rexk1\n"
set s = s + "2nd: Shiladie, Anduril33b\n"
set s = s + "3rd: zzaadd, fongy\n"
set s = s + "4th: Kazrugore, NoPD.Sn4rs\n"
set s = s + "5th: BattlelordVI, beelzarach\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Beat Sides/Elite/20 Rounds/Sudden Death with a partner|r")
call CreateQuestItemBJ(q, "closed")
//The Quickest
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) The Quickest")
set s = ""
set s = s + "1st: Victor.GuN - 46 kills\n"
set s = s + "2nd: Shiladie - 41 kills\n"
set s = s + "3rd: Corex - 41 kills\n"
set s = s + "4th: Sanji - 40 kills\n"
set s = s + "5th: LightburneR - 30 kills\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Kill the most runners by yourself in elite coop round 1|r")
call CreateQuestItemBJ(q, "Kills = Gold + Lumber - 35 after end of round 1")
call CreateQuestItemBJ(q, "closed")
//The Cheapest
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) The Cheapest")
set s = ""
set s = s + "1st: he_kod - 989 dpg\n"
set s = s + "2nd: DuLaNzZz - 830 dpg\n"
set s = s + "3rd: Anduril33b - 687 dpg\n"
set s = s + "4th: Axis - 617 dpg\n"
set s = s + "5th: SouP - 440 dpg\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Deal the most damage/gold during the first round|r")
call CreateQuestItemBJ(q, "Use solo/training mode in single player")
call CreateQuestItemBJ(q, "Use 'greedisgood (number)' to get gold")
call CreateQuestItemBJ(q, "closed due to version differences")
//Perfection
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) Perfection")
set s = ""
set s = s + "1st: ddelwood\n"
set s = s + "2nd: rexk1\n"
set s = s + "3rd: he_kod\n"
set s = s + "4th: DuLaNzZz\n"
set s = s + "5th: wtcr\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Beat Solo/Elite/30 Rounds/Sudden Death|r")
call CreateQuestItemBJ(q, "closed")
//All 4 One
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) All 4 One")
set s = ""
set s = s + "1st: tossrock, RCoD.HorsedOnG, hornsbyRHINO\n"
set s = s + "2nd: Warchamp7, ZanmatoZashi, Funkid2, TheSultan\n"
set s = s + "3rd: aliengodz, Raides\n"
set s = s + "4th: BilltheEmu, ShadowFlare_SFR, Zilla-\n"
set s = s + "5th: Xar, Neoshan, Schichi\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Beat Coop/Veteran/15 Rounds/Sudden Death with 4 people|r")
call CreateQuestItemBJ(q, "closed")
//Isolation
set q = CreateQuest()
call QuestSetRequired(q, false)
call QuestSetDiscovered(q, true)
call QuestSetIconPath(q, "ReplaceableTextures\\WorldEditUI\\Editor-Force.blp")
call QuestSetTitle(q, "(-) Isolation")
set s = ""
set s = s + "1st: Platium\n"
set s = s + "2nd: ZanmatoZashi\n"
set s = s + "3rd: Warchamp7\n"
set s = s + "4th: RedAzure\n"
set s = s + "5th: Ubivin\n"
call QuestSetDescription(q, s)
call CreateQuestItemBJ(q, "|cFFFFCC00Beat Coop/Hotshot/15 Rounds/25 lives by yourself|r")
call CreateQuestItemBJ(q, "closed")
endfunction
function InitTrig_Challenges takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, 0, false)
call TriggerAddAction(t, function createChallengeQuests)
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Handles the help provided to players during the game.
library libHelp initializer initHelp requires General, DelayedCallLib, Constants, LibDefender, BlockCommandLib, LibMultiboard
globals
private constant integer HELP_STATE_BUILD_GENERATOR = 0
private constant integer HELP_STATE_BUILD_COMBAT = 1
private constant integer HELP_STATE_USE_TRANSFER = 2
private constant integer HELP_STATE_WATCH_POWER = 3
private constant integer HELP_STATE_WARNING_POWER = 4
private constant integer HELP_STATE_DONE = 5
private constant real INITIAL_HELP_DELAY = 20.0 //seconds
private constant real PERIODIC_HELP_DELAY = 10.0 //seconds
private integer array helpStateD
private boolean array blockFlagD
private boolean array sellFlagD
endglobals
///Updates help state
private function catchBuild takes nothing returns nothing
local integer ut = GetUnitTypeId(GetConstructingStructure())
local Defender d = Defender.fromUnit(GetConstructingStructure())
local unit u = GetConstructingStructure()
local player p
local integer i
if d == nill or ut == nill then
return
endif
//building tips
if helpStateD[d.index] <= HELP_STATE_BUILD_GENERATOR then
if isFurnace(ut) then
set helpStateD[d.index] = HELP_STATE_BUILD_COMBAT
endif
endif
if helpStateD[d.index] <= HELP_STATE_BUILD_COMBAT then
if isRockLauncher(ut) or isTeslaCoil(ut) then
set helpStateD[d.index] = HELP_STATE_USE_TRANSFER
endif
endif
//command tips
set i = 1
loop
exitwhen i > NUM_DEFENDERS
set p = defenders[i].p
if not blockFlagD[i] and IsPlayerBlockingPlayer(p, d.p) then
if CanPlayerBlockAreaFrom(p, GetUnitX(u), GetUnitY(u), d.p) then
call playSoundForPlayer(gg_snd_Hint, d.p)
call showPlayerMessage(p, "|cFF00FF00Tip: You can use|r |cFFFFCC00-block player_name|r|cFF00FF00 to remove players from your area.|r")
set blockFlagD[i] = true
endif
endif
set i = i + 1
endloop
set u = null
endfunction
///Updates help state
private function catchCast takes nothing returns nothing
local Defender d
if GetSpellAbilityId() == ABIL_ADD_TRANSFER then
set d = Defender.fromUnit(GetTriggerUnit())
if d != nill and helpStateD[d.index] <= HELP_STATE_USE_TRANSFER then
set helpStateD[d.index] = HELP_STATE_WATCH_POWER
endif
elseif GetSpellAbilityId() == ABIL_SELL then
set d = Defender.fromUnit(GetTriggerUnit())
if d != nill and not sellFlagD[d.index] then
call playSoundForPlayer(gg_snd_Hint, d.p)
call d.showMessage("|cFF00FF00Tip: Your builder can use|r |cFFFFCC00Seller's Remorse|r|cFF00FF00 to restore recently sold towers.|r")
set sellFlagD[d.index] = true
endif
endif
endfunction
///Displays help to a defender
private function delayedLoopHelp takes nothing returns nothing
local Defender d = Defender(delayedArg)
//stop giving help when its no longer appropriate
if d.state != DEFENDER_STATE_DEFENDING then
set helpStateD[d.index] = HELP_STATE_DONE
endif
//show tip
if helpStateD[d.index] == HELP_STATE_BUILD_GENERATOR then
call playSoundForPlayer(gg_snd_Hint, d.p)
call showPlayerMessage(d.p, "|cFF00FF00Tip: Build a|r |cFFFFCC00furnace|r|cFF00FF00 so you can use decent towers.|r")
elseif helpStateD[d.index] == HELP_STATE_BUILD_COMBAT then
call playSoundForPlayer(gg_snd_Hint, d.p)
call showPlayerMessage(d.p, "|cFF00FF00Tip: Build a|r |cFFFFCC00rock launcher |cFF00FF00or |cFFFFCC00tesla coil|r|cFF00FF00 near your furnace.|r")
elseif helpStateD[d.index] == HELP_STATE_USE_TRANSFER then
call playSoundForPlayer(gg_snd_Hint, d.p)
call showPlayerMessage(d.p, "|cFF00FF00Tip: Select your furnace and cast|r |cFFFFCC00Add Transfer Target|r|cFF00FF00 on your tower.|r")
elseif helpStateD[d.index] == HELP_STATE_WATCH_POWER then
call playSoundForPlayer(gg_snd_Hint, d.p)
call showPlayerMessage(d.p, "|cFF00FF00Tip: Keep an eye on your power supply or your towers will run dry.|r")
set helpStateD[d.index] = HELP_STATE_WARNING_POWER
elseif helpStateD[d.index] == HELP_STATE_WARNING_POWER then
if lastProducD[d.index]*2 + 40 < lastDrainD[d.index] then
call playSoundForPlayer(gg_snd_Hint, d.p)
call showPlayerMessage(d.p, "|cFFFF6000Warning: You need more power to feed your towers! Upgrade or make more generators!|r")
set helpStateD[d.index] = HELP_STATE_DONE
endif
endif
//wait before repeating the help
if helpStateD[d.index] != HELP_STATE_DONE then
call delayedCallWithArg(function delayedLoopHelp, PERIODIC_HELP_DELAY, integer(d))
endif
endfunction
///Starts the help loop for all defenders
private function delayedStartHelpLoop takes nothing returns nothing
local integer i = 1
loop
exitwhen i > NUM_DEFENDERS
call delayedCallWithArg(function delayedLoopHelp, 0., integer(defenders[i]))
set i = i + 1
endloop
endfunction
///Initializes the structure events
private function initHelp takes nothing returns nothing
local trigger t
call delayedCall(function delayedStartHelpLoop, INITIAL_HELP_DELAY)
set t = CreateTrigger()
call TriggerAddAction(t, function catchBuild)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_START)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddAction(t, function catchCast)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
///////////////////////////////////////////////////////
// Strilanc's Delayed Call Library
// Version: 1.01
// Last Updated: March 21, 2008
///////////////////////////////////////////////////////
// Description:
// - This library provides functions to schedule delayed function calls easily, similar
// to what timers can do. However, this library removes any need for cleanup and provides
// an easy way to pass arguments to the called function.
//
// Explanation:
// - The library slowly sweeps over a large circular array of calls to make, making the calls
// as it encounters them. When a function is scheduled, it is placed a distance ahead of
// the sweep corresponding to the desired delay.
//
// Usage:
// - Use delayedCall, delayedCallWithArg, delayedCallWithArg2, or delayedCallWithArg3 to
// schedule a call. For example, delayedCallWithArg2(function test, 0.35, 1, 2) will call
// the function 'test' in 0.35 seconds with the arguments 1 and 2.
// - Once the function is called, use delayedArg, delayedArg2 and delayedArg3 to retrieve
// any passed arguments.
//
// Notes:
// - Unspecified arguments default to 0.
// - At most 8190 calls can be scheduled at any time.
// - The maximum accuracy of a periodic timer is about ~0.033 seconds.
// - The 'accuracy' constant determines the tick rate of the sweep. Lowering it will increase
// accuracy but raising it will decrease cpu usage. Calls are made at most 'accuracy' seconds
// away from the desired delay.
///////////////////////////////////////////////////////
library DelayedCallLib initializer initDelayedCallLib
globals
private constant real accuracy = 0.03 //seconds
private constant integer NUM_BUCKETS = 8190 //The size of the circular array
private integer nextTickBucket = 0 //the next bucket which will be emptied
private keyword DelayedCall
private DelayedCall array bucketsFirstCall //first call stored in a bucket's queue
private DelayedCall array bucketsLastCall //last call stored in a bucket's queue
private integer totalDelayedCalls = 0
real delayedDuration = 0. //the duration of the last delay
integer delayedArg = 0 //set to the 1st passed argument when delayed calls are made
integer delayedArg2 = 0 //2nd passed argument
integer delayedArg3 = 0 //3rd passed argument
endglobals
function DelayedCallLib_NumDelayedCalls takes nothing returns integer
return totalDelayedCalls
endfunction
///Stores a future call and acts as a node in buckets' queues
///Doesn't check it's arguments; relies on the rest of the library to do so
private struct DelayedCall
private trigger t
private triggeraction ta
private integer arg1
private integer arg2
private integer arg3
private real duration
public DelayedCall next = 0
public DelayedCall prev
public integer sweepsLeft
//create a stored call
public static method create takes DelayedCall prev, code c, real duration, integer sweeps, integer arg1, integer arg2, integer arg3 returns DelayedCall
local DelayedCall d = DelayedCall.allocate()
if d == 0 then
call BJDebugMsg("Map Error: Couldn't allocate a DelayedCall (DelayedCall.create)")
return 0
endif
set d.duration = duration
set d.sweepsLeft = sweeps
set d.prev = prev
set d.t = CreateTrigger()
set d.arg1 = arg1
set d.arg2 = arg2
set d.arg3 = arg3
set d.ta = TriggerAddAction(d.t, c)
if prev != 0 then
set prev.next = d
endif
set totalDelayedCalls = totalDelayedCalls + 1
return d
endmethod
//run the stored call
public method run takes nothing returns nothing
set delayedArg = .arg1
set delayedArg2 = .arg2
set delayedArg3 = .arg3
set delayedDuration = .duration
call TriggerExecute(.t)
endmethod
//destroy the stored call
private method onDestroy takes nothing returns nothing
call TriggerRemoveAction(.t, .ta)
call DestroyTrigger(.t)
set .ta = null
set .t = null
set totalDelayedCalls = totalDelayedCalls - 1
endmethod
endstruct
///Schedules a call with 3 arguments 'delay' seconds in the future
function delayedCallWithArg3 takes code c, real delay, integer arg, integer arg2, integer arg3 returns boolean
local integer i = R2I(delay / accuracy + 0.5)-1 //convert to ticks
local integer d
if i < 0 then
set i = 0 //better late than never
endif
//bucket ahead of the sweep
set d = i / NUM_BUCKETS
set i = i + nextTickBucket
set i = i - (i / NUM_BUCKETS) * NUM_BUCKETS
//place in bucket
set bucketsLastCall[i] = DelayedCall.create(bucketsLastCall[i], c, delay, d, arg, arg2, arg3)
if bucketsFirstCall[i] == 0 then
set bucketsFirstCall[i] = bucketsLastCall[i]
endif
return true
endfunction
///Schedules a call with 2 arguments 'delay' seconds in the future
function delayedCallWithArg2 takes code c, real delay, integer arg, integer arg2 returns boolean
return delayedCallWithArg3(c, delay, arg, arg2, 0)
endfunction
///Schedules a call with an argument 'delay' seconds in the future
function delayedCallWithArg takes code c, real delay, integer arg returns boolean
return delayedCallWithArg3(c, delay, arg, 0, 0)
endfunction
///Schedules a call 'delay' seconds in the future
function delayedCall takes code c, real delay returns boolean
return delayedCallWithArg3(c, delay, 0, 0, 0)
endfunction
///Runs the calls scheduled for this tick
private function tick takes nothing returns nothing
local DelayedCall d
local DelayedCall t
local integer i = nextTickBucket
//load the current bucket
set d = bucketsFirstCall[i]
set bucketsFirstCall[i] = 0
set bucketsLastCall[i] = 0
//and set the next bucket
set nextTickBucket = nextTickBucket + 1
if nextTickBucket >= NUM_BUCKETS then
set nextTickBucket = 0
endif
//process loaded bucket
loop
exitwhen d == 0
set t = d
set d = t.next
//process call
if t.sweepsLeft > 0 then
//decrement and place back in bucket
set t.sweepsLeft = t.sweepsLeft - 1
set t.prev = bucketsLastCall[i]
set t.next = 0
if bucketsFirstCall[i] == 0 then
set bucketsFirstCall[i] = t
else
set bucketsLastCall[i].next = t
endif
set bucketsLastCall[i] = t
else
//run and destroy
call t.run()
call t.destroy()
endif
endloop
endfunction
///Starts the clock
private function initDelayedCallLib takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerAddAction(t, function tick)
call TriggerRegisterTimerEvent(t, accuracy, true)
endfunction
endlibrary
//TESH.scrollpos=27
//TESH.alwaysfold=0
///////////////////////////////////////////////////////
/// HAIL
/// Strilanc's Handle Array Index Library
/// Last Updated: May 28, 2008
/// Version: 1.06
///////////////////////////////////////////////////////
/// Summay:
/// - This library allows you to add properties to units and other handle types.
/// - You can also add properties to ordered pairs of handles.
/// - Multiple systems can use this library without interfering with each other.
///
/// Explanation:
/// - The library is essentially a hash table with a very simple hash. A handle is
/// converted to a valid array index using H2I mod TABLE_SIZE. Collisions are
/// handled by probing. The handle index is the handle's position in the hash table.
/// - The macros work by declaring global arrays to store the property
/// values and creating efficient functions to get, set, and reset those values.
///
/// Usage:
/// - Set the system settings just below this big comment block
/// - Create a property using the text macro HAIL_CreateProperty(name, type, scope)
/// For example: HAIL_CreateProperty("Height", "real", "private"). The scope argument
/// needs to be blank if the property is not in a library.
/// - Use Get$Property$(handle) and Set$Property$(handle, value) to access your new property.
/// For example: call SetHeight(h, 100.), return GetHeight(h).
/// - Use Reset$Property$(handle) before destroying handles. This removes the property from
/// the handle. Fail to do this and the hash table will slowly fill up.
/// - Use HAIL_CreatePairProperty(name, type, scope) to create a property for ordered pairs
/// of handles. You get the same methods as HAIL_CreateProperty, plus ResetAny$Name$For,
/// which removes all pairs including the given handle.
///
/// Notes:
/// - Hash table performance will drop if the number of handle indexes becomes large (thousands)
/// - The code assumes MAX_HANDLES is less than TABLE_SIZE
/// - The code assumes H2I always returns a non-negative value
/// - The code assumes unused indexes will have no values assigned
/// - The code assumes some_array[-1] == default value of that array's type
/// - The default string value is the null string (null), not the empty string ("")
/// - Small handles are not stored in the hash table, but in dedicated arrays. They
/// don't count towards the total number of indexes.
/// - Pair properties are ordered: p(u, w) is not equivalent to p(w, u)
///////////////////////////////////////////////////////
library HAIL initializer initHAIL
//SYSTEM SETTINGS
globals
//should the system show debug messages? (only when in debug mode)
constant boolean HAIL_SHOW_DEBUG_MESSAGES = false
//should the system show messages when things go obviously wrong?
constant boolean HAIL_SHOW_ERROR_MESSAGES = true
endglobals
//===================================================================================================
//=== PROPERTIES ====================================================================================
//===================================================================================================
//The globals aren't private because they must be accessed from the macros
globals
//flag constants
constant integer HAIL_EMPTY_BUT_USED = -1
//performance constants
constant integer HAIL_MAX_HANDLES = 6000
constant integer HAIL_TABLE_SIZE = 8191
constant integer HAIL_FIRST_HANDLE_INDEX = 0x100000
constant integer HAIL_DEDICATED_INDEX_BOUND = HAIL_FIRST_HANDLE_INDEX+HAIL_TABLE_SIZE
//hash table storage
handle array HAIL_TableHandles
integer array HAIL_TableUsedCounts
debug integer array HAIL_TableCollisionCounts
//stats
integer HAIL_TotalIndexes = 0 //total used array indexes in hash table
debug integer HAIL_TotalCollisions = 0 //total number of probes
endglobals
///Returns the address of a handle
function HAIL_H2I takes handle h returns integer
return h
return 0
endfunction
///Creates functions and arrays for implementing a property
///Always reset handle properties to their default value before destroying them
///- guaranteed not to cause library interferance
///- some code repetition in the interest of speed
//! textmacro HAIL_CreateProperty takes name, type, scope
///Storage space for the property
globals
//additional storage in hash table for property
$scope$ $type$ array HAIL_$name$_values
$scope$ boolean array HAIL_$name$_flags
//dedicated storage for small handles
$scope$ $type$ array HAIL_$name$_fvalues
//default value for this property type
$scope$ $type$ HAIL_$name$_default = HAIL_$name$_values[-1]
endglobals
///Gets the given handle's value for this property
$scope$ function Get$name$ takes handle h returns $type$
//=== GET INDEX ===
//Hash
local integer i = HAIL_H2I(h)
if i < HAIL_DEDICATED_INDEX_BOUND then //low handles use dedicated storage
return HAIL_$name$_fvalues[i-HAIL_FIRST_HANDLE_INDEX]
endif
set i = i - (i / HAIL_TABLE_SIZE) * HAIL_TABLE_SIZE
//Probe
loop
//check slot
exitwhen HAIL_TableHandles[i] == h and HAIL_TableUsedCounts[i] > 0
exitwhen HAIL_TableUsedCounts[i] == 0 //end of probing chain
//next slot
set i = i + 1
if i >= HAIL_TABLE_SIZE then
set i = 0
endif
endloop
return HAIL_$name$_values[i]
endfunction
///Sets the given handle's value for this property
$scope$ function Set$name$ takes handle h, $type$ newValue returns boolean
debug local integer numCollisions = 0
//=== GET INDEX ===
//Hash
local integer i = HAIL_H2I(h)
if i < HAIL_DEDICATED_INDEX_BOUND then //low handles use dedicated storage
set HAIL_$name$_fvalues[i-HAIL_FIRST_HANDLE_INDEX] = newValue
return true
endif
set i = i - (i / HAIL_TABLE_SIZE) * HAIL_TABLE_SIZE
//Probe
loop
//check slot
exitwhen HAIL_TableHandles[i] == h and HAIL_TableUsedCounts[i] > 0
exitwhen HAIL_TableUsedCounts[i] == 0 //end of probing chain
//next slot
debug set numCollisions = numCollisions + 1
set i = i + 1
if i >= HAIL_TABLE_SIZE then
set i = 0
endif
endloop
//Check for no entry
if not HAIL_$name$_flags[i] then
//=== CREATE AN ENTRY ===
if HAIL_TableUsedCounts[i] > 0 then //index already exists
set HAIL_TableUsedCounts[i] = HAIL_TableUsedCounts[i] + 1
elseif HAIL_TotalIndexes < HAIL_MAX_HANDLES then //new index
set HAIL_TableHandles[i] = h
set HAIL_TableUsedCounts[i] = 1
set HAIL_TotalIndexes = HAIL_TotalIndexes + 1
debug set HAIL_TableCollisionCounts[i] = numCollisions
debug set HAIL_TotalCollisions = HAIL_TotalCollisions + numCollisions
debug if HAIL_SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("HAIL Index Created: H(" + I2S(HAIL_H2I(h)-HAIL_FIRST_HANDLE_INDEX) + ") = " + I2S(i) + " [Table Status: " + I2S(HAIL_TotalIndexes) + " entries, " + I2S(HAIL_TotalCollisions) + " collisions]")
debug endif
else //too many indexes
if HAIL_SHOW_ERROR_MESSAGES then
call BJDebugMsg("Map Error: Couldn't create a HAIL index. Too many handles indexed. (Set$name$)")
endif
return false
endif
set HAIL_$name$_flags[i] = true
endif
//Update entry
set HAIL_$name$_values[i] = newValue
return true
endfunction
///Removes the given handle's entry for this property
$scope$ function Reset$name$ takes handle h returns nothing
local integer n
//=== GET INDEX ===
//Hash
local integer i = HAIL_H2I(h)
if i < HAIL_DEDICATED_INDEX_BOUND then //low handles use dedicated storage
set HAIL_$name$_fvalues[i-HAIL_FIRST_HANDLE_INDEX] = HAIL_$name$_default
return
endif
set i = i - (i / HAIL_TABLE_SIZE) * HAIL_TABLE_SIZE
//Probe
loop
//check slot
exitwhen HAIL_TableHandles[i] == h and HAIL_TableUsedCounts[i] > 0
exitwhen HAIL_TableUsedCounts[i] == 0 //end of probing chain
//next slot
set i = i + 1
if i >= HAIL_TABLE_SIZE then
set i = 0
endif
endloop
if HAIL_$name$_flags[i] then
//=== DESTROY AN ENTRY ===
set HAIL_$name$_flags[i] = false
set HAIL_$name$_values[i] = HAIL_$name$_default
set HAIL_TableUsedCounts[i] = HAIL_TableUsedCounts[i] - 1
//Don't destroy the id if it is in use by some other property
if HAIL_TableUsedCounts[i] > 0 then
return
endif
//Destroy the index
set HAIL_TotalIndexes = HAIL_TotalIndexes - 1
set HAIL_TableUsedCounts[i] = HAIL_EMPTY_BUT_USED
set HAIL_TableHandles[i] = null
debug set HAIL_TotalCollisions = HAIL_TotalCollisions - HAIL_TableCollisionCounts[i]
debug set HAIL_TableCollisionCounts[i] = 0
debug if HAIL_SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("HAIL Index Destroyed: H(" + I2S(HAIL_H2I(h)-HAIL_FIRST_HANDLE_INDEX) + ") [Table Status: " + I2S(HAIL_TotalIndexes) + " entries, " + I2S(HAIL_TotalCollisions) + " collisions]")
debug endif
//Remove probe chain tails
//(otherwise probing will eventually go out of control)
set n = i + 1
if n >= HAIL_TABLE_SIZE then
set n = 0
endif
if HAIL_TableUsedCounts[n] != 0 then
return //not at the end of a tail, can't remove
endif
loop //remove the tail
set HAIL_TableUsedCounts[i] = 0
if i <= 0 then
set i = HAIL_TABLE_SIZE
endif
set i = i - 1
exitwhen HAIL_TableUsedCounts[i] != HAIL_EMPTY_BUT_USED
endloop
endif
endfunction
//! endtextmacro
//===================================================================================================
//=== PAIR PROPERTIES ===============================================================================
//===================================================================================================
globals
//pair hash table storage
handle array HAIL2_TableHandles1
handle array HAIL2_TableHandles2
integer array HAIL2_TableUsedCounts
debug integer array HAIL2_TableCollisionCounts
//tangled double linked list storage
handle array HPL_h1
handle array HPL_h2
integer array HPL_next1
integer array HPL_next2
integer array HPL_prev1
integer array HPL_prev2
//pair stats
integer HAIL2_TotalIndexes = 0 //total used array indexes in hash table
debug integer HAIL2_TotalCollisions = 0 //total number of probes
endglobals
//! runtextmacro HAIL_CreateProperty("HAIL_HandlePairList1", "integer", "")
//! runtextmacro HAIL_CreateProperty("HAIL_HandlePairList2", "integer", "")
///Creates functions and arrays for implementing properties over pairs of handles
//! textmacro HAIL_CreatePairProperty takes name, type, scope
///Storage space for the property
globals
//additional storage in hash table for property
$scope$ $type$ array HAIL2_$name$_values
$scope$ boolean array HAIL2_$name$_flags
//default value for this property type
$scope$ $type$ HAIL2_$name$_default = HAIL2_$name$_values[-1]
endglobals
///Gets the given handle pair's value for this property
$scope$ function Get$name$ takes handle h, handle g returns $type$
//=== GET INDEX ===
//Hash
local integer i = HAIL_H2I(h)
local integer j = HAIL_H2I(g)
set i = i + j*509
set i = i - (i / HAIL_TABLE_SIZE) * HAIL_TABLE_SIZE
//Probe
loop
//check slot
exitwhen HAIL2_TableHandles1[i] == h and HAIL2_TableHandles2[i] == g and HAIL2_TableUsedCounts[i] > 0
exitwhen HAIL2_TableUsedCounts[i] == 0 //end of probing chain
//next slot
set i = i + 1
if i >= HAIL_TABLE_SIZE then
set i = 0
endif
endloop
return HAIL2_$name$_values[i]
endfunction
///Sets the given handle pair's value for this property
$scope$ function Set$name$ takes handle h, handle g, $type$ newValue returns boolean
debug local integer numCollisions = 0
//=== GET INDEX ===
//Hash
local integer i = HAIL_H2I(h)
local integer j = HAIL_H2I(g)
set i = i + j*509
set i = i - (i / HAIL_TABLE_SIZE) * HAIL_TABLE_SIZE
//Probe
loop
//check slot
exitwhen HAIL2_TableHandles1[i] == h and HAIL2_TableHandles2[i] == g and HAIL2_TableUsedCounts[i] > 0
exitwhen HAIL2_TableUsedCounts[i] == 0 //end of probing chain
//next slot
debug set numCollisions = numCollisions + 1
set i = i + 1
if i >= HAIL_TABLE_SIZE then
set i = 0
endif
endloop
//Check for no entry
if not HAIL2_$name$_flags[i] then
//=== CREATE AN ENTRY ===
if HAIL2_TableUsedCounts[i] > 0 then //index already exists
set HAIL2_TableUsedCounts[i] = HAIL2_TableUsedCounts[i] + 1
elseif HAIL2_TotalIndexes < HAIL_MAX_HANDLES then //new index
//insert in hash table
set HAIL2_TableHandles1[i] = h
set HAIL2_TableHandles2[i] = g
set HAIL2_TableUsedCounts[i] = 1
set HAIL2_TotalIndexes = HAIL2_TotalIndexes + 1
debug set HAIL2_TableCollisionCounts[i] = numCollisions
debug set HAIL2_TotalCollisions = HAIL2_TotalCollisions + numCollisions
debug if HAIL_SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("HAIL Pair Index Created: H(" + I2S(HAIL_H2I(h)-HAIL_FIRST_HANDLE_INDEX) + "," + I2S(HAIL_H2I(g)-HAIL_FIRST_HANDLE_INDEX) + ") = " + I2S(i) + " [Table Status: " + I2S(HAIL2_TotalIndexes) + " entries, " + I2S(HAIL2_TotalCollisions) + " collisions]")
debug endif
//Insert this pair in h's pair list 1
set HPL_h1[i] = h
set j = GetHAIL_HandlePairList1(h)-1
if j == -1 then
//the list was empty, create it
set HPL_next1[i] = i
set HPL_prev1[i] = i
call SetHAIL_HandlePairList1(h, i+1)
else
//insert after the head of the list
set HPL_next1[i] = HPL_next1[j]
set HPL_next1[j] = i
set HPL_prev1[i] = j
set HPL_prev1[HPL_next1[i]] = i
endif
//Insert this pair in g's pair list 2
set HPL_h2[i] = g
set j = GetHAIL_HandlePairList2(g)-1
if j == -1 then
//the list was empty, create it
set HPL_next2[i] = i
set HPL_prev2[i] = i
call SetHAIL_HandlePairList2(g, i+1)
else
//insert after the head of the list
set HPL_next2[i] = HPL_next2[j]
set HPL_next2[j] = i
set HPL_prev2[i] = j
set HPL_prev2[HPL_next2[i]] = i
endif
else //too many indexes
if HAIL_SHOW_ERROR_MESSAGES then
call BJDebugMsg("Map Error: Couldn't create a HAIL pair index. Too many handles indexed. (Set$name$)")
endif
return false
endif
set HAIL2_$name$_flags[i] = true
endif
//Update entry
set HAIL2_$name$_values[i] = newValue
return true
endfunction
///Removes the given handle pair's entry for this property
$scope$ function Reset$name$ takes handle h, handle g returns nothing
local integer n
//=== GET INDEX ===
//Hash
local integer i = HAIL_H2I(h)
local integer j = HAIL_H2I(g)
set i = i + j*509
set i = i - (i / HAIL_TABLE_SIZE) * HAIL_TABLE_SIZE
//Probe
loop
//check slot
exitwhen HAIL2_TableHandles1[i] == h and HAIL2_TableHandles2[i] == g and HAIL2_TableUsedCounts[i] > 0
exitwhen HAIL2_TableUsedCounts[i] == 0 //end of probing chain
//next slot
set i = i + 1
if i >= HAIL_TABLE_SIZE then
set i = 0
endif
endloop
if HAIL2_$name$_flags[i] then
//=== DESTROY AN ENTRY ===
set HAIL2_$name$_flags[i] = false
set HAIL2_$name$_values[i] = HAIL2_$name$_default
set HAIL2_TableUsedCounts[i] = HAIL2_TableUsedCounts[i] - 1
//Don't destroy the id if it is in use by some other property
if HAIL2_TableUsedCounts[i] > 0 then
return
endif
//Destroy the index
set HAIL2_TotalIndexes = HAIL2_TotalIndexes - 1
set HAIL2_TableUsedCounts[i] = HAIL_EMPTY_BUT_USED
set HAIL2_TableHandles1[i] = null
set HAIL2_TableHandles2[i] = null
debug set HAIL2_TotalCollisions = HAIL2_TotalCollisions - HAIL2_TableCollisionCounts[i]
debug set HAIL2_TableCollisionCounts[i] = 0
debug if HAIL_SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("HAIL Pair Index Destroyed: H(" + I2S(HAIL_H2I(h)-HAIL_FIRST_HANDLE_INDEX) + "," + I2S(HAIL_H2I(g)-HAIL_FIRST_HANDLE_INDEX) + ") [Table Status: " + I2S(HAIL2_TotalIndexes) + " entries, " + I2S(HAIL2_TotalCollisions) + " collisions]")
debug endif
//Remove this pair from h's pair list 1
set HPL_h1[i] = null
if HPL_next1[i] == i then
//No elements will be left in the list, destroy it
call ResetHAIL_HandlePairList1(h)
else
//Hook next to prev and vice versa
set HPL_next1[HPL_prev1[i]] = HPL_next1[i]
set HPL_prev1[HPL_next1[i]] = HPL_prev1[i]
//Change pointer into list if we removed the head
if i == GetHAIL_HandlePairList1(h)-1 then
call SetHAIL_HandlePairList1(h, HPL_next1[i]+1)
endif
endif
//Remove this pair from g's pair list 2
set HPL_h2[i] = null
if HPL_next2[i] == i then
//No elements will be left in the list, destroy it
call ResetHAIL_HandlePairList2(g)
else
//Hook next to prev and vice versa
set HPL_next2[HPL_prev2[i]] = HPL_next2[i]
set HPL_prev2[HPL_next2[i]] = HPL_prev2[i]
//Change pointer into list if we removed the head
if i == GetHAIL_HandlePairList2(g)-1 then
call SetHAIL_HandlePairList2(g, HPL_next2[i]+1)
endif
endif
//Remove probe chain tails
//(otherwise probing will eventually go out of control)
set n = i + 1
if n >= HAIL_TABLE_SIZE then
set n = 0
endif
if HAIL2_TableUsedCounts[n] != 0 then
return //not at the end of a tail, can't remove
endif
loop //remove the tail
set HAIL2_TableUsedCounts[i] = 0
if i <= 0 then
set i = HAIL_TABLE_SIZE
endif
set i = i - 1
exitwhen HAIL2_TableUsedCounts[i] != HAIL_EMPTY_BUT_USED
endloop
endif
endfunction
///Removes all pair's with the given handle for this property
$scope$ function ResetAny$name$For takes handle h returns nothing
local integer i
local integer n
//loop over handle's pair list 1
set i = GetHAIL_HandlePairList1(h)-1
if i != -1 then
loop
set n = HPL_next1[i]
call Reset$name$(h, HPL_h2[i])
exitwhen i == n
set i = n
endloop
endif
//loop over handle's pair list 2
set i = GetHAIL_HandlePairList2(h)-1
if i != -1 then
loop
exitwhen HPL_h2[i] != h
set n = HPL_next2[i]
call Reset$name$(HPL_h1[i], h)
exitwhen i == n
set i = n
endloop
endif
endfunction
//! endtextmacro
///Initializes the library
private function initHAIL takes nothing returns nothing
debug if HAIL_SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("Initialized HAIL with SHOW_DEBUG_MESSAGES")
debug endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///////////////////////////////////////////////////////
/// Strilanc's Spread Unit Event Library
/// Last Updated: April 2, 2008
/// Version: 1.01
///////////////////////////////////////////////////////
/// Summary:
/// - This library allows you to take any specific unit event and efficiently turn
/// it into an event for multiple units.
/// - The main advantage of this library is there is no overhead at all. Work is ONLY
/// done when units are added.
///
/// Explanation:
/// - A spread event is made up of three groups: [Inlet], [Filter], and [Buffer].
/// - When a unit is added, it is placed in the [Inlet] group and 2 units in the [Filter]
/// group are processed.
/// - When a unit is processed it goes to the [Buffer] group, unless it is decayed. Decayed
/// units are removed from the system.
/// - When the [Filter] group empties, the groups are rotated so [Inlet] becomes [Filter],
/// [Filter] becomes [Buffer], and [Buffer] becomes [Inlet].
/// - The rotation and processing guarantee that decayed units in the system are slowly
/// filtered out as more units are added.
///
/// Usage:
/// - Set the system settings just below this big comment block
/// - Create spread events using SpreadUnitEvent.create(unitevent)
/// - Add units to the event as necessary using .addUnit(unit)
/// - Add groups of units using .addGroup(group) or .addUnitsMatching(boolexpr)
/// - Add functions to call when the event fires using .register(code)
/// - Sit back and relax! Everything else is taken care of!
///
/// Notes:
/// - The code uses GetUnitTypeId(unit) == 0 to detect decayed units
/// - The code uses some acrobatics to store code variables in an integer array
///////////////////////////////////////////////////////
library SUEL initializer initSUEL
globals
//Should the system show debug messages? (only when in debug mode)
private constant boolean SHOW_DEBUG_MESSAGES = false
//Should the system show messages when things go obviously wrong?
private constant boolean SHOW_ERROR_MESSAGES = true
endglobals
//===================================================================================================
//===================================================================================================
//===================================================================================================
globals
//indirect argument for .addEnumUnit
private SpreadUnitEvent activeSUE = 0
endglobals
///Stores a stack of code variables
///also provides room to keep trigger actions
private struct CodeStack
private integer address
public CodeStack next
public triggeraction taFilter
public triggeraction taInlet
///Converts unsafe code to code
private static method UC2C takes code c returns code
return c
endmethod
///Converts an integer to unsafe code
private static method I2UC takes integer i returns code
return i
return null
endmethod
///Converts code to an integer
private static method C2I takes code c returns integer
return c
return 0
endmethod
///Returns the code stored in this node
public method getCode takes nothing returns code
return CodeStack.UC2C(CodeStack.I2UC(.address))
endmethod
///Creates a new stack node
public static method create takes code c, CodeStack next returns CodeStack
local CodeStack s = CodeStack.allocate()
if s == 0 then
if SHOW_ERROR_MESSAGES then
call BJDebugMsg("Map Error: Couldn't allocate a CodeStack node. (CodeStack.create)")
endif
return 0
endif
set s.address = CodeStack.C2I(c)
set s.next = next
return s
endmethod
private method onDestroy takes nothing returns nothing
set .taFilter = null
set .taInlet = null
endmethod
///Destroys the entire stack
public method recursiveDestroy takes nothing returns nothing
if this != 0 then
if .next != 0 then
call .next.recursiveDestroy()
endif
call .destroy()
endif
endmethod
endstruct
///Stores a stack of units
private struct UnitStack
readonly unit u
public UnitStack next
///Creates a new stack node
public static method create takes unit u, UnitStack next returns UnitStack
local UnitStack s = UnitStack.allocate()
if s == 0 then
if SHOW_ERROR_MESSAGES then
call BJDebugMsg("Map Error: Couldn't allocate a UnitStack node. (UnitStack.create)")
endif
return 0
endif
set s.u = u
set s.next = next
return s
endmethod
private method onDestroy takes nothing returns nothing
set .u = null
endmethod
///Destroys the entire stack
public method recursiveDestroy takes nothing returns nothing
if this != 0 then
if .next != 0 then
call .next.recursiveDestroy()
endif
call .destroy()
endif
endmethod
endstruct
///A structure which efficiently applies single unit events to many units
///[Inlet] == The group units are added into from outside the system
///[Filter] == The group where decayed units are removed from units going to [Buffer]
///[Buffer] == The group units are held in until [Filter] finishes
///The groups rotate when [Filter] empties
struct SpreadUnitEvent
private unitevent unitEventId
private UnitStack stackInlet = 0
private UnitStack stackFilter = 0
private UnitStack stackBuffer = 0
private trigger trigInlet = CreateTrigger()
private trigger trigFilter = CreateTrigger()
private trigger trigBuffer = CreateTrigger()
private integer numInlet = 0
private integer numFilter = 0
private integer numBuffer = 0
private CodeStack stackCode = 0
///Adds a callback function
public method register takes code c returns nothing
set .stackCode = CodeStack.create(c, .stackCode)
set .stackCode.taInlet = TriggerAddAction(.trigInlet, c)
set .stackCode.taFilter = TriggerAddAction(.trigFilter, c)
endmethod
///Summarizes the state of the groups
public method toString takes nothing returns string
local string s = ""
set s = s + "Inlet=|cFFFFCC00"+I2S(.numInlet)+"|r"
set s = s + ", Filter=|cFFFF8000"+I2S(.numFilter) + "|r"
set s = s + ", Buffer=|cFF00FF00"+I2S(.numBuffer)+"|r"
set s = s + ", Total="+I2S(.numBuffer+.numFilter+.numInlet)
return s
endmethod
///Rotates the groups
///[Inlet] -> [Filter] -> [Buffer] -> [Inlet]
private method rotate takes nothing returns nothing
local UnitStack stackTemp
local trigger trigTemp
local integer numTemp
local CodeStack s
if .numBuffer + .numInlet <= 0 then
return
endif
//Avoid trigger destroy bug when possible
if GetTriggeringTrigger() == .trigFilter then
debug call BJDebugMsg("WARNING: Detected and avoided possible trigger destroy bug. Don't add units to SUEL from SUEL events. (Suel.rotate)")
call DisableTrigger(.trigFilter)
set .trigFilter = null
endif
//Rotate trigger actions
//[Buffer] ends up with no actions after rotation, to avoid double-calls on events
set s = .stackCode
loop
exitwhen s == 0
call TriggerRemoveAction(.trigFilter, s.taFilter)
set s.taFilter = s.taInlet
set s.taInlet = TriggerAddAction(.trigBuffer, s.getCode())
set s = s.next
endloop
//Clear unit events from [Filter] (since its empty)
call DestroyTrigger(.trigFilter)
set .trigFilter = CreateTrigger()
//Rotate triggers
set trigTemp = .trigFilter
set .trigFilter = .trigInlet
set .trigInlet = .trigBuffer
set .trigBuffer = trigTemp
set trigTemp = null
//Rotate counts
set numTemp = .numFilter
set .numFilter = .numInlet
set .numInlet = .numBuffer
set .numBuffer = numTemp
//Rotate units
set stackTemp = .stackFilter
set .stackFilter = .stackInlet
set .stackInlet = .stackBuffer
set .stackBuffer = stackTemp
endmethod
///Processes a unit in [Filter]
///Decayed units are removed, others go to [Buffer]
///Rotates the groups when [Filter] is empty
private method filter takes nothing returns nothing
local unit u
local UnitStack s
if .numFilter <= 0 then
call .rotate()
return
endif
//Process top unit
set s = .stackFilter
set u = s.u
set .stackFilter = .stackFilter.next
set .numFilter = .numFilter - 1
if GetUnitTypeId(u) != 0 then //only decayed units have an id of 0
//pass active units to [Buffer]
set s.next = .stackBuffer
set .stackBuffer = s
call TriggerRegisterUnitEvent(.trigBuffer, u, .unitEventId)
set .numBuffer = .numBuffer + 1
else
//removed decayed units
call s.destroy()
endif
set u = null
endmethod
///Adds a unit to [Inlet]
public method addUnit takes unit u returns nothing
if this == 0 then
return
endif
//add unit to [Inlet]
set .stackInlet = UnitStack.create(u, .stackInlet)
set .numInlet = .numInlet + 1
call TriggerRegisterUnitEvent(.trigInlet, u, .unitEventId)
//process two units in [Filter]
call .filter()
call .filter()
//debug status
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("Added to SpreadUnitEvent #" + I2S(this))
debug call BJDebugMsg(.toString())
debug endif
endmethod
///Adds a group of units to the system
private static method addEnumUnit takes nothing returns nothing
call activeSUE.addUnit(GetEnumUnit())
endmethod
public method addGroup takes group g returns nothing
if this == 0 then
return
endif
set activeSUE = this
call ForGroup(g, function SpreadUnitEvent.addEnumUnit)
endmethod
///Adds all units on the map matching the filter
public method addUnitsMatching takes boolexpr filter returns nothing
local group g
local rect r
if this == 0 then
return
endif
//add units
set g = CreateGroup()
set r = GetWorldBounds()
call GroupEnumUnitsInRect(g, r, filter)
call .addGroup(g)
//clean
call DestroyGroup(g)
call RemoveRect(r)
call DestroyBoolExpr(filter)
set g = null
set r = null
endmethod
///Creates a spreaded for the given event type
public static method create takes unitevent unitEventId returns SpreadUnitEvent
local SpreadUnitEvent e = SpreadUnitEvent.allocate()
if e == 0 then
if SHOW_ERROR_MESSAGES then
call BJDebugMsg("Map Error: Couldn't allocate a SpreadUnitEvent. (SpreadUnitEvent.create)")
endif
return 0
endif
set e.unitEventId = unitEventId
return e
endmethod
///Cleans up properly
private method onDestroy takes nothing returns nothing
//trigger actions
local CodeStack s = .stackCode
loop
exitwhen s == 0
call TriggerRemoveAction(.trigFilter, s.taFilter)
call TriggerRemoveAction(.trigInlet, s.taInlet)
set s = s.next
endloop
//code stack
call .stackCode.recursiveDestroy()
//unit stacks
call .stackInlet.recursiveDestroy()
call .stackFilter.recursiveDestroy()
call .stackBuffer.recursiveDestroy()
//triggers
call DestroyTrigger(.trigInlet)
call DestroyTrigger(.trigFilter)
call DestroyTrigger(.trigBuffer)
set .trigInlet = null
set .trigFilter = null
set .trigBuffer = null
endmethod
endstruct
///Initializes the library
private function initSUEL takes nothing returns nothing
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("Initialized SUEL with SHOW_DEBUG_MESSAGES")
debug endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///////////////////////////////////////////////////////
/// Strilanc's Anti Wall Library
/// Last Updated: March 22, 2008
/// Version: Beta-3
///////////////////////////////////////////////////////
/// Description:
/// - This library attempts to detect when player's try to abuse unit pathing
/// in tower defense maps. The main advantage over other techniques is the
/// juggling detection. If a player changes their maze so units have to backtrack,
/// it will be detected. Optional 'unit is attacking', 'unit is stopped', and
/// 'unit is stuck' checks are also included.
///
/// Explanation:
/// - Units in the system have rects (bread crumbs) placed behind them as they
/// walk around. If they are juggled they will backtrack and re-enter those rects,
/// firing an event. Juggling without causing backtracking is difficult.
/// - When a unit drops a bread crumb it checks if it has moved at all. If it hasn't,
/// and it isn't stunned/snared/etc, it counts as walled.
/// - If CATCH_STOPPED is set, stopped units are caught when they try to drop crumbs.
/// - If CATCH_ATTACKING is set, attacking units are caught when they attack or try to drop crumbs.
/// - If CATCH_STUCK is set, units who haven't move a lot for awhile are caught.
///
/// Usage:
/// - Set the system settings just below this big comment block
/// - Add runners to the library when they are created using AntiWall_AddUnit
/// - Add some sort of callback using AntiWall_AddCallBack, so you know when a runner is caught
/// - IMPORTANT: When runners hit waypoints, reset them using AntiWall_ResetRunner
/// - Use AntiWall_RemoveUnit to remove units from the system (by default it does this automatically when they die)
/// - When a walled runner is detected, the global values AntiWall_WalledUnit and AntiWall_Reason
/// are set to the walled unit/detection reason and the callback functions are all called. The
/// possible reasons are ANTI_WALL_REASON_JUGGLED, ANTI_WALL_REASON_STOPPED, ANTI_WALL_REASON_STUCK,
/// and ANTI_WALL_REASON_ATTACKING
///
/// Notes:
/// - If you have a LOT of runners closely following each other, they will all run through
/// each other's dropped rects, throw a ton of events and could cause lag, but who knows.
/// - If you have a lot of runners passing each other, they tend to stop and backtrack a lot,
/// so the system can think they are walled. It attempts to minimize these false positives.
///////////////////////////////////////////////////////
library AntiWallLib initializer antiWallInit requires HAIL, SUEL
///SYSTEM SETTINGS
globals
//should the system show debug messages? (only in debug mode)
private constant boolean SHOW_DEBUG_MESSAGES = false
//should runners be automatically removed from the system when they die?
private constant boolean REMOVE_ON_DEATH = false
//should the system catch stopped runners?
private constant boolean CATCH_STOPPED = true
//should the system catch attacking runners?
private constant boolean CATCH_ATTACKING = true
//should the system catch runners staying in approximately the same area for awhile?
private constant boolean CATCH_STUCK = true
endglobals
globals
constant integer ANTI_WALL_REASON_PREVENT = -1
constant integer ANTI_WALL_REASON_NONE = 0 //unknown reason
constant integer ANTI_WALL_REASON_BACK_TRACKED = 1 //the unit backtracked
constant integer ANTI_WALL_REASON_STOPPED = 2 //the unit is stopped
constant integer ANTI_WALL_REASON_ATTACKING = 3 //the unit is attacking
constant integer ANTI_WALL_REASON_STUCK = 4 //the unit hasn't moved far for awhile
unit AntiWall_WalledUnit = null //set to the walled unit when making callbacks
integer AntiWall_WalledReason = ANTI_WALL_REASON_NONE //the reason the system thinks the unit was walled
private constant real TICK_PERIOD = 1.0 //seconds between dropping crumbs
private constant real CRUMB_SIZE = 32.0 //width and height of a crumb rect
private constant integer MIN_CRUMB_DISTANCE = 256 //minimum distance a unit must travel to drop a new crumb
private constant integer MAX_STUCK_COUNT = 8 //number of times a unit has fail to drop a crumb to be 'stuck'
private constant integer NUM_CRUMBS = 4 //number of crumbs per unit
private SpreadUnitEvent runnerAttackedEvent
private integer totalAntiWallUnits = 0
private trigger callBackTrigger = CreateTrigger() //stores the callbacks
private group antiWallUnits = CreateGroup() //keeps track of units in the system
endglobals
private keyword AntiWallUnit
//! runtextmacro HAIL_CreateProperty("UnitAntiWallUnit", "AntiWallUnit", "private")
function AntiWallLib_NumAntiWallUnits takes nothing returns integer
return totalAntiWallUnits
endfunction
private function catchUnit takes unit u, integer reason returns nothing
set AntiWall_WalledUnit = u
set AntiWall_WalledReason = reason
call TriggerExecute(callBackTrigger)
endfunction
private struct AntiWallUnit
readonly unit u
private trigger enterRegionTrigger = CreateTrigger()
private region breadCrumbsRegion = CreateRegion()
private rect array breadCrumbRects[NUM_CRUMBS]
private integer breadCrumbIndex = 0
readonly boolean wasHeld = false
readonly boolean isHeld = false
private integer stuckCount = 0
private real lastX = 0
private real lastY = 0
///Removes dropped bread crumbs
public method removeCrumbs takes nothing returns nothing
local integer i = 0
loop
exitwhen i >= NUM_CRUMBS
call RegionClearRect(.breadCrumbsRegion, .breadCrumbRects[i])
call RemoveRect(.breadCrumbRects[i])
set .breadCrumbRects[i] = null
set i = i + 1
endloop
set .stuckCount = 0
endmethod
public method catch takes integer reason returns nothing
if .wasHeld or .isHeld then
call catchUnit(.u, ANTI_WALL_REASON_PREVENT)
return
endif
call .removeCrumbs()
call catchUnit(.u, reason)
endmethod
///Drops a bread crump and checks for sitting units
public method dropBreadCrumb takes nothing returns nothing
local unit u = .u
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local integer oid = GetUnitCurrentOrder(u)
set .wasHeld = .isHeld
set .isHeld = IsUnitType(u,UNIT_TYPE_STUNNED) or IsUnitType(u,UNIT_TYPE_SNARED) or IsUnitPaused(u) or not IsUnitAliveBJ(u)
if CATCH_STOPPED and oid == 0 then
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib caught a " + GetUnitName(u) + " stopped.")
debug endif
call .catch(ANTI_WALL_REASON_STOPPED)
elseif CATCH_ATTACKING and oid == OrderId("attack") then
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib caught a " + GetUnitName(u) + " attack-moving.")
debug endif
call .catch(ANTI_WALL_REASON_ATTACKING)
elseif CATCH_STUCK and RAbsBJ(x-.lastX)+RAbsBJ(y-.lastY) < MIN_CRUMB_DISTANCE then
//check if unit is stuck
if not .isHeld and not .wasHeld then
set .stuckCount = .stuckCount + 1
endif
if .stuckCount >= MAX_STUCK_COUNT then
//unit hasn't moved far for awhile, it is stuck
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib caught a " + GetUnitName(u) + " struck.")
debug endif
call .catch(ANTI_WALL_REASON_STUCK)
else
//not stuck yet, but don't drop a new crumb so close to the last one
set u = null
return
endif
else
set .stuckCount = 0
set .lastX = x
set .lastY = y
endif
//create new crumb
//Only the previous rect is added to the region because we don't want units hitting
//other units' bread crumbs to be counted as walled when they are still near their last crumb
call RegionAddRect(.breadCrumbsRegion, .breadCrumbRects[.breadCrumbIndex])
set .breadCrumbIndex = .breadCrumbIndex + 1
if .breadCrumbIndex + 1 >= NUM_CRUMBS then
set .breadCrumbIndex = 0
endif
if .breadCrumbRects[.breadCrumbIndex] == null then
set .breadCrumbRects[.breadCrumbIndex] = Rect(0,0,CRUMB_SIZE,CRUMB_SIZE)
else
call RegionClearRect(.breadCrumbsRegion, .breadCrumbRects[.breadCrumbIndex])
endif
call MoveRectTo(.breadCrumbRects[.breadCrumbIndex], x, y)
set u = null
endmethod
///Catches units treading over bread crumbs
private static method catchEnterRegion takes nothing returns nothing
//check that this is a runner entering its own region
local AntiWallUnit this = GetUnitAntiWallUnit(GetTriggerUnit())
if this == 0 or not IsUnitInRegion(.breadCrumbsRegion, .u) then
return
endif
//catch, but don't double-catch
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib caught a " + GetUnitName(.u) + " backtracking.")
debug endif
call .removeCrumbs()
call catchUnit(.u, ANTI_WALL_REASON_BACK_TRACKED)
endmethod
///Create an AntiWallUnit for the given unit
public static method create takes unit u returns AntiWallUnit
local AntiWallUnit awu
local integer i
if u == null then
return 0
endif
//check for double-create
set awu = GetUnitAntiWallUnit(u)
if awu != 0 then
return awu
endif
//allocate
set awu = AntiWallUnit.allocate()
if integer(awu) == 0 then
call BJDebugMsg("Map Error: Failed to allocate an AntiWallUnit. (AntiWallUnit.create)")
return 0
endif
set awu.u = u
call GroupAddUnit(antiWallUnits, u)
//attach
call SetUnitAntiWallUnit(u, awu)
//catch units treading on my precious crumbs
call TriggerAddAction(awu.enterRegionTrigger, function AntiWallUnit.catchEnterRegion)
call TriggerRegisterEnterRegion(awu.enterRegionTrigger, awu.breadCrumbsRegion, null)
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib added a " + GetUnitName(u) + ".")
debug endif
set totalAntiWallUnits = totalAntiWallUnits + 1
return awu
endmethod
private method onDestroy takes nothing returns nothing
//cleanup the bread crumbs
call .removeCrumbs()
call RemoveRegion(.breadCrumbsRegion)
call DestroyTrigger(.enterRegionTrigger)
set .breadCrumbsRegion = null
set .enterRegionTrigger = null
call GroupRemoveUnit(antiWallUnits, .u)
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib removed a " + GetUnitName(.u) + ".")
debug endif
call ResetUnitAntiWallUnit(.u)
set .u = null
set totalAntiWallUnits = totalAntiWallUnits - 1
endmethod
endstruct
///Drops bread crumbs for all units in the system
private function enumDropBreadCrumb takes nothing returns nothing
call GetUnitAntiWallUnit(GetEnumUnit()).dropBreadCrumb()
endfunction
private function dropBreadCrumbs takes nothing returns nothing
call ForGroup(antiWallUnits, function enumDropBreadCrumb)
endfunction
///Catchs units in the system when they attack
private function catchAttacker takes nothing returns nothing
local AntiWallUnit awu
if CATCH_ATTACKING then
set awu = GetUnitAntiWallUnit(GetAttacker())
if awu != 0 then
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib caught a " + GetUnitName(awu.u) + " attacking.")
debug endif
call awu.catch(ANTI_WALL_REASON_ATTACKING)
endif
endif
endfunction
///Removes units from the system when they die
private function removeDyingUnit takes nothing returns nothing
local AntiWallUnit awu
set awu = GetUnitAntiWallUnit(GetTriggerUnit())
if awu != 0 then
call awu.destroy()
endif
endfunction
///Adds a unit from the outside to the system
function AntiWall_AddUnit takes unit u returns nothing
call AntiWallUnit.create(u)
endfunction
///Removes a unit from the outside from the system
function AntiWall_RemoveUnit takes unit u returns nothing
local AntiWallUnit awu = GetUnitAntiWallUnit(u)
if awu != 0 then
call awu.destroy()
endif
endfunction
//Resets a unit's bread crumbs from the outside
function AntiWall_ResetUnit takes unit u returns nothing
local AntiWallUnit awu = GetUnitAntiWallUnit(u)
if awu != 0 then
call awu.removeCrumbs()
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("AntiWallLib reset a " + GetUnitName(awu.u) + ".")
debug endif
endif
endfunction
///Adds a function from the outside to call when a unit is caught
function AntiWall_AddCallBack takes code c returns nothing
call TriggerAddAction(callBackTrigger, c)
endfunction
///Adds a unit which will throw attacked events
function AntiWall_AddTower takes unit u returns nothing
call runnerAttackedEvent.addUnit(u)
endfunction
///Initialize the antiwall system
private function antiWallInit takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerRegisterTimerEvent(t, TICK_PERIOD, true)
call TriggerAddAction(t, function dropBreadCrumbs)
if REMOVE_ON_DEATH then
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(t, function removeDyingUnit)
endif
if CATCH_ATTACKING then
set runnerAttackedEvent = SpreadUnitEvent.create(EVENT_UNIT_ATTACKED)
call runnerAttackedEvent.register(function catchAttacker)
endif
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("Initialized AntiWallLib with SHOW_DEBUG_MESSAGES")
debug endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library PathLib initializer initPathLib requires HAIL
globals
//should the system automatically order units reaching waypoints to continue on their way?
private constant boolean CONTINUE_MOVING = true
//should the system forget about units that die?
private constant boolean RELEASE_ON_DEATH = false
//should the system show debug messages? (only in debug mode)
private constant boolean SHOW_DEBUG_MESSAGES = false
endglobals
globals
private constant integer MAX_WAYPOINTS = 25 //maximum number of waypoints an individual Path can have
private trigger waypointCallBackTrigger = CreateTrigger() //storage for 'waypoint' callbacks
private trigger finishedCallBackTrigger = CreateTrigger() //storage for 'finished' callbacks
private integer totalPathingUnits = 0
private integer totalPaths = 0
Path Path_TriggeringPath = 0
unit Path_TriggeringUnit = null
integer Path_TriggeringWaypointIndex = 0
endglobals
private keyword PathingUnit
//! runtextmacro HAIL_CreateProperty("UnitPathingUnit", "PathingUnit", "private")
function PathLib_NumPathingUnits takes nothing returns integer
return totalPathingUnits
endfunction
function PathLib_NumPaths takes nothing returns integer
return totalPaths
endfunction
///Adds a callback for when units reach waypoints
function Path_AddWaypointCallBack takes code c returns nothing
call TriggerAddAction(waypointCallBackTrigger, c)
endfunction
///Adds a callback for when units reach the last waypoint
function Path_AddFinishedCallBack takes code c returns nothing
call TriggerAddAction(finishedCallBackTrigger, c)
endfunction
///Creates a mutated copy of the given rect
///Transformations are applied in argument order
private function cloneRect takes rect r, boolean swapXY, boolean flipX, boolean flipY returns rect
local real x
local real y
local real w
local real h
local real t
if r == null then
return null
endif
//get rect properties
set x = GetRectCenterX(r)
set y = GetRectCenterY(r)
set w = x - GetRectMinX(r)
set h = y - GetRectMinY(r)
//transform rect properties
if swapXY then //reflect along diagonal
set t = y
set y = x
set x = t
set t = w
set w = h
set h = w
endif
if flipX then //reflect along horizontal
set x = -x
endif
if flipY then //reflect along vertical
set y = -y
endif
//create rect from transformed properties
return Rect(x-w, y-h, x+w, y+h)
endfunction
///Pathing information for units
private struct PathingUnit
readonly unit u
readonly Path path
public integer waypointIndex = 1
public static method create takes unit u, Path path returns PathingUnit
local PathingUnit p
if u == null or path == Path(0) then
return PathingUnit(0)
endif
set p = PathingUnit.allocate()
if p == PathingUnit(0) then
call BJDebugMsg("Map Error: Couldn't allocate a PathingUnit. (PathingUnit.create)")
return PathingUnit(0)
endif
set p.u = u
set p.path = path
call SetUnitPathingUnit(u, p)
set totalPathingUnits = totalPathingUnits + 1
return p
endmethod
private method onDestroy takes nothing returns nothing
call ResetUnitPathingUnit(.u)
set .u = null
set .path = 0
set .waypointIndex = 0
set totalPathingUnits = totalPathingUnits - 1
endmethod
///Returns current destination waypoint location
public method getTargetLoc takes nothing returns location
if this == 0 then
return null
endif
return Location(.path.getWaypointX(.waypointIndex), .path.getWaypointY(.waypointIndex))
endmethod
///Orders the unit to the current destination
public method order takes nothing returns boolean
if this == 0 then
return false
endif
return IssuePointOrder(.u, "move", .path.getWaypointX(.waypointIndex), .path.getWaypointY(.waypointIndex))
endmethod
endstruct
///Returns the location of the given unit's current destination waypoint
function Path_GetUnitTargetLoc takes unit u returns location
return GetUnitPathingUnit(u).getTargetLoc()
endfunction
///Removes the given unit from the system
function Path_RemoveUnit takes unit u returns nothing
local PathingUnit p = GetUnitPathingUnit(u)
if p != PathingUnit(0) then
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("PathLib removed a " + GetUnitName(u) + " from path #" + I2S(p.path) + ".")
debug endif
call p.destroy()
endif
endfunction
///Orders the given unit to move to its current destination waypoint
function Path_OrderUnit takes unit u returns nothing
local PathingUnit p = GetUnitPathingUnit(u)
if p != PathingUnit(0) then
call p.order()
endif
endfunction
///Triggered when any unit enters any waypoint
///Throws waypoint events to the outside if the waypoint is the unit's destination
///Orders the units to the next waypoint is CONTINUE_MOVING is set
private function catchWaypoint takes nothing returns nothing
local PathingUnit p = GetUnitPathingUnit(GetTriggerUnit())
local integer w
if p == PathingUnit(0) then
return
endif
//is this actually a unit reaching its waypoint?
set w = p.waypointIndex
if p.path.isUnitAtWaypoint(p.u, w) then
set Path_TriggeringPath = p.path
set Path_TriggeringUnit = p.u
//advance waypoint index
loop
exitwhen w >= p.path.numWaypoints //don't throw waypoint event for last waypoint
//reached the waypoint
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("PathLib caught a " + GetUnitName(p.u) + " reaching waypoint #" + I2S(w) + " on its path.")
debug endif
set Path_TriggeringWaypointIndex = w
call TriggerExecute(waypointCallBackTrigger)
set w = w + 1
set p.waypointIndex = w
exitwhen not p.path.isUnitAtWaypoint(p.u, w) //note: the next waypoint might overlap
endloop
//check for finished
if w >= p.path.numWaypoints and p.path.isUnitAtWaypoint(p.u, w) then
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("PathLib caught a " + GetUnitName(p.u) + " reaching its final destination.")
debug endif
call TriggerExecute(finishedCallBackTrigger)
elseif CONTINUE_MOVING then
call p.order()
endif
endif
endfunction
///Triggered when any unit dies
///Removes pathing units from the system if RELEASE_ON_DEATH is set
private function catchDeath takes nothing returns nothing
if RELEASE_ON_DEATH then
call Path_RemoveUnit(GetTriggerUnit())
endif
endfunction
struct Path
private rect array waypointRects[MAX_WAYPOINTS]
private region array waypointRegions[MAX_WAYPOINTS]
private region waypointUnionRegion = CreateRegion()
private trigger waypointTrigger = CreateTrigger()
readonly integer numWaypoints = 0
public static method create takes nothing returns Path
local Path p = Path.allocate()
if p == Path(0) then
call BJDebugMsg("Map Error: Couldn't allocate a Path. (Path.create)")
return Path(0)
endif
call TriggerRegisterEnterRegion(p.waypointTrigger, p.waypointUnionRegion, null)
call TriggerAddAction(p.waypointTrigger, function catchWaypoint)
set totalPaths = totalPaths + 1
return p
endmethod
private method onDestroy takes nothing returns nothing
local integer i = 1
call RemoveRegion(.waypointUnionRegion)
call DestroyTrigger(.waypointTrigger)
set .waypointUnionRegion = null
set .waypointTrigger = null
loop
exitwhen i > .numWaypoints
call RemoveRegion(.waypointRegions[i])
call RemoveRect(.waypointRects[i])
set .waypointRects[i] = null
set .waypointRegions[i] = null
set i = i + 1
endloop
set .numWaypoints = 0
set totalPaths = totalPaths - 1
endmethod
public method addWaypoint takes rect r returns boolean
if this == 0 or r == null then
return false
elseif .numWaypoints >= MAX_WAYPOINTS then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Map Error: Number of waypoints exceeds maximum. (Path.addWaypoint)")
return false
endif
set r = cloneRect(r, false, false, false)
set .numWaypoints = .numWaypoints + 1
set .waypointRects[.numWaypoints] = r
set .waypointRegions[.numWaypoints] = CreateRegion()
call RegionAddRect(.waypointRegions[.numWaypoints], r)
call RegionAddRect(.waypointUnionRegion, r)
return true
endmethod
public method clone takes boolean swapXY, boolean flipX, boolean flipY returns Path
local Path p
local integer i
local rect r
if this == 0 then
return 0
endif
set p = Path.create()
if p == 0 then
return 0
endif
set i = 1
loop
exitwhen i > .numWaypoints
set r = cloneRect(.waypointRects[i], swapXY, flipX, flipY)
call p.addWaypoint(r)
call RemoveRect(r)
set i = i + 1
endloop
set r = null
return p
endmethod
///Returns the CenterX of the waypoint with the given index
public method getWaypointX takes integer i returns real
if this == 0 or i < 1 or i > .numWaypoints then
return 0
endif
return GetRectCenterX(.waypointRects[i])
endmethod
///Returns the CenterY of the waypoint with the given index
public method getWaypointY takes integer i returns real
if this == 0 or i < 1 or i > .numWaypoints then
return 0
endif
return GetRectCenterY(.waypointRects[i])
endmethod
///Returns true if the given unit is at the waypoint with the given index
public method isUnitAtWaypoint takes unit u, integer i returns boolean
if this == 0 or u == null or i < 1 or i > this.numWaypoints then
return false
endif
return IsUnitInRegion(.waypointRegions[i], u)
endmethod
///Returns true if the given point is at any of this path's waypoints
public method containsPoint takes real x, real y returns boolean
if this == 0 then
return false
endif
return IsPointInRegion(.waypointUnionRegion, x, y)
endmethod
///Adds a unit to follow this path
public method addUnit takes unit u returns boolean
local PathingUnit p
if this == 0 or u == null then
return false
endif
//add to array
set p = PathingUnit.create(u, this)
if p == 0 then
return false
endif
//check for starting on first waypoint
if .isUnitAtWaypoint(u, 1) then
set p.waypointIndex = 2
endif
if CONTINUE_MOVING then
call p.order()
endif
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("PathLib added a " + GetUnitName(u) + " to path #" + I2S(this) + ".")
debug endif
return true
endmethod
endstruct
private function initPathLib takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerAddAction(t, function catchDeath)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("Initialized PathLib with SHOW_DEBUG_MESSAGES")
debug endif
endfunction
endlibrary
//TESH.scrollpos=314
//TESH.alwaysfold=0
///Strilanc's library
///Contains functions and macros I use all the time.
///The usefulness of stuff ranges from "actually useful" to "renamed function"
library General
globals
constant integer nill = 0
constant string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890:. |!"
constant integer numChars = StringLength(chars)
endglobals
//===============================================================
//= PLAYERS =====================================================
//===============================================================
///Returns the color code for the given player
function getPlayerColor takes player p returns string
local string s
local integer i = GetPlayerId(p)+1
if p == null then
set s = "FFFFFF" //white
elseif i == 1 then
set s = "FF0303" //red
elseif i == 2 then
set s = "0042FF" //blue
elseif i == 3 then
set s = "1CE6B9" //teal
elseif i == 4 then
set s = "540081" //purple
elseif i == 5 then
set s = "FFFC01" //yellow
elseif i == 6 then
set s = "FEBA0E" //orange
elseif i == 7 then
set s = "20C000" //green
elseif i == 8 then
set s = "FF00FF" //pink
elseif i == 9 then
set s = "808080" //grey
elseif i == 10 then
set s = "0080FF" //light blue
elseif i == 11 then
set s = "008000" //dark green
elseif i == 12 then
set s = "800000" //brown
else
set s = "FFFFFF" //white
endif
return "FF" + s
endfunction
///Returns the colored name of the given player
function getPlayerColoredName takes player p returns string
if p == null then
return ""
endif
return "|c" + getPlayerColor(p) + GetPlayerName(p) + "|r"
endfunction
///Returns the player with the given color, name or number
function getPlayerFromString takes string s returns player
local integer i
set s = StringCase(s, false)
//check against colors
if s == "red" then
return Player(0)
elseif s == "blue" then
return Player(1)
elseif s == "teal" then
return Player(2)
elseif s == "purple" then
return Player(3)
elseif s == "yellow" then
return Player(4)
elseif s == "orange" then
return Player(5)
elseif s == "green" then
return Player(6)
elseif s == "pink" then
return Player(7)
elseif s == "gray" then
return Player(8)
elseif s == "light blue" then
return Player(9)
elseif s == "dark green" then
return Player(10)
elseif s == "brown" then
return Player(11)
endif
//check against numbers
set i = 1
loop
exitwhen i > 12
if s == I2S(i) then
return Player(i-1)
endif
set i = i + 1
endloop
//check against names
set i = 0
loop
exitwhen i >= 12
if s == StringCase(GetPlayerName(Player(i)), false) then
return Player(i)
endif
set i = i + 1
endloop
//no matching player
return null
endfunction
//===============================================================
//= MATH ========================================================
//===============================================================
///Returns the value of a 3rd degree polynomial
function polynom3 takes real x, integer coef0, integer coef1, integer coef2, integer coef3 returns real
return coef0 + x*(coef1 + x*(coef2 + x*coef3))
endfunction
///Returns true with probability p
function chance takes real p returns boolean
return (GetRandomReal(0, 1) < p)
endfunction
///Returns the middle value
function between takes integer x1, integer x2, integer x3 returns integer
//recursively sort
if x1 > x3 then
return between(x3, x2, x1)
elseif x1 > x2 then
return between(x2, x1, x3)
elseif x3 < x2 then
return between(x1, x3, x2)
endif
//guaranteed x2 between x1 and x3 at this point
return x2
endfunction
///Returns first integer at most as large as r
function floor takes real r returns integer
local integer i = R2I(r)
if i > r then
return i-1
else
return i
endif
endfunction
///Returns first integer at least as large as r
function ceiling takes real r returns integer
local integer i = R2I(r)
if i < r then
return i+1
else
return i
endif
endfunction
///Returns the integer you get by rounding r
function round takes real r returns integer
return floor(r+0.5)
endfunction
///Returns the integer you get by cutting off the digits after the decimal point in r
function truncate takes real r returns integer
return R2I(r)
endfunction
///Returns the sign of a number
function sgn takes real r returns integer
if (r > 0) then
return 1
elseif (r < 0) then
return -1
else
return 0
endif
endfunction
//=================================================================
//= VISUALS =======================================================
//=================================================================
///Creates a dying special effect, no need for cleanup
function createBang takes real x, real y, string model returns nothing
call DestroyEffect(AddSpecialEffect(model, x, y))
endfunction
function createBangLoc takes location p, string model returns nothing
call DestroyEffect(AddSpecialEffectLoc(model, p))
endfunction
///Displays a text message to all players
function showMessage takes string s returns nothing
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, s)
endfunction
///Displays an error message
function showError takes string desc, string loc returns nothing
call showMessage("Map Error: " + desc + " (" + loc + ")")
endfunction
///Displays a text message to a player
function showPlayerMessage takes player p, string s returns nothing
call DisplayTextToPlayer(p, 0, 0, s)
endfunction
///Shows fading floating-text above a unit
function showUnitText takes unit u, string msg returns nothing
local texttag t
if u == null then
return
endif
set t = CreateTextTag()
call SetTextTagText(t, msg, 0.023)
call SetTextTagPos(t, GetUnitX(u)+GetRandomReal(-25, 25), GetUnitY(u)+GetRandomReal(-25, 25), 0)
call SetTextTagLifespan(t, 3)
call SetTextTagPermanent(t, false)
call SetTextTagFadepoint(t, 1)
call SetTextTagVelocity(t, 0, 0.023)
set t = null
endfunction
function showUnitTextToPlayer takes unit u, string msg, player p returns nothing
local texttag t
if u == null then
return
endif
set t = CreateTextTag()
call SetTextTagText(t, msg, 0.023)
call SetTextTagPos(t, GetUnitX(u)+GetRandomReal(-25, 25), GetUnitY(u)+GetRandomReal(-25, 25), 0)
call SetTextTagLifespan(t, 3)
call SetTextTagPermanent(t, false)
call SetTextTagFadepoint(t, 1)
call SetTextTagVelocity(t, 0, 0.023)
call SetTextTagVisibility(t, GetLocalPlayer() == p)
set t = null
endfunction
///Plays a sound, but for one player instead of all of them
function playSoundForPlayer takes sound s, player p returns nothing
if GetLocalPlayer() == p and s != null then
call StartSound(s)
endif
endfunction
//=================================================================
//= CONVERSIONS ===================================================
//=================================================================
///Returns the address of a handle
function H2I takes handle h returns integer
return h
return 0
endfunction
function I2Unit takes integer i returns unit
return i
return null
endfunction
function I2Player takes integer i returns player
return i
return null
endfunction
function I2Effect takes integer i returns effect
return i
return null
endfunction
function I2Lightning takes integer i returns lightning
return i
return null
endfunction
///Converts a character to an integer
function Char2I takes string s returns integer
local integer i = 0
loop
exitwhen i >= numChars
if s == SubString(chars, i, i+1) then
return i
endif
set i = i + 1
endloop
return -1
endfunction
///Converts an integer to a character
function I2Char takes integer i returns string
if i < 0 or i >= numChars then
return " "
endif
return SubString(chars, i, i+1)
endfunction
///Encodes a string
function encode takes integer d, string s returns string
local string ret = ""
local integer i = 0
local integer m = 1
local integer c
local integer n = StringLength(s)
loop
exitwhen i >= n
set m = m * d
set m = m - (m / numChars) * numChars
set c = m * Char2I(SubString(s, i, i+1))
set c = c - (c / numChars) * numChars
set ret = ret + SubString(chars, c, c+1)
set i = i + 1
endloop
return ret
endfunction
//=================================================================
//= MISC ==========================================================
//=================================================================
///Returns a copy of the given rect
function copyRect takes rect r returns rect
if (r == null) then
return null
endif
return Rect(GetRectMinX(r), GetRectMinY(r), GetRectMaxX(r), GetRectMaxY(r))
endfunction
//=================================================================
//= MACROS ========================================================
//=================================================================
///Shorthand for making a structure permanent (only provides warnings, almost no compile-time enforcement)
//! textmacro EnforcePermanent takes StructureName
private method onDestroy takes nothing returns nothing
call showError("Destroyed a permanent structure.", "$StructureName$.onDestroy")
endmethod
//! endtextmacro
///Shorthand for making a structure static (only provides warnings, partial compile-time enforcement)
//! textmacro EnforceStatic takes StructureName
private method onDestroy takes nothing returns nothing
call showError("Destroyed a static structure.", "$StructureName$.onDestroy")
endmethod
private static method create takes nothing returns $StructureName$
call showError("Attempted to instanciate a static structure.", "$StructureName$.create")
return nill
endmethod
//! endtextmacro
///Shorthand for creating a method which redirects to init and can only be called once.
//! textmacro CreateInitOnceMethod takes StructureName
private static boolean initialized = false
public static method initonce takes nothing returns nothing
if ($StructureName$.initialized == true) then
call showError("Attempted to initialize a structure twice.", "$StructureName$.initonce")
return
endif
set $StructureName$.initialized = true
call $StructureName$.init()
endmethod
//! endtextmacro
///Shorthand for creating a readonly member which can be written (from outside) once
//! textmacro declare_initreadonly takes TypeName, VariableName, DefaultValue
readonly $TypeName$ $VariableName$ = $DefaultValue$
public method init_$VariableName$ takes $TypeName$ v returns nothing
if (this == nill) then
return
elseif (this.$VariableName$ != $DefaultValue$) then
call showError("Attempted to rewrite an initreadonly variable.", "unknown.init_$VariableName$")
return
endif
set this.$VariableName$ = v
endmethod
//! endtextmacro
///Shorthand for checking if 'this' is nill
//! textmacro EnforceNoNull takes methodName, returnVal
if (this == nill) then
call showError("Called a non-nullable method with nill.", "$methodName$")
return $returnVal$
endif
//! endtextmacro
//! textmacro when takes conditions, actions
if $conditions$ then
$actions$
endif
//! endtextmacro
///Shorthand for allocating a structure and making sure allocate worked
//! textmacro CheckedAllocate takes structName, varName
set $varName$ = $structName$.allocate()
if ($varName$ == nill) then
call showError("Allocation of $structName$ failed.", "$structName$.create")
return nill
endif
//! endtextmacro
///Shorthand for storing instances of a structure within that structure
//! textmacro TrackedStruct_declare takes structName
readonly static integer numInstances = 0
readonly static $structName$ array instances
readonly integer arrayIndex = nill
//! endtextmacro
//! textmacro TrackedStruct_create takes structName, varName
set $structName$.numInstances = $structName$.numInstances + 1
set $varName$.arrayIndex = $structName$.numInstances
set $structName$.instances[$structName$.numInstances] = $varName$
//! endtextmacro
//! textmacro TrackedStruct_destroy takes structName
set $structName$.instances[this.arrayIndex] = $structName$.instances[$structName$.numInstances]
set $structName$.instances[$structName$.numInstances].arrayIndex = this.arrayIndex
set $structName$.instances[$structName$.numInstances] = nill
set this.arrayIndex = nill
set $structName$.numInstances = $structName$.numInstances - 1
//! endtextmacro
///Shorthand for registering for events
//! textmacro Register takes func, args, eventfunc, trig
set $trig$ = CreateTrigger()
call TriggerRegister$eventfunc$(t, $args$)
call TriggerAddAction(t, function $func$)
//! endtextmacro
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///The Queue implements a queue of integers.
///It stores things in an orderly fashion.
library QueueStructLib requires General
globals
private integer totalQueueNodes = 0
private integer totalQueues = 0
private constant boolean STRICT_WARNINGS = true
endglobals
function Queue_NumQueueNodes takes nothing returns integer
return totalQueueNodes
endfunction
function Queue_NumQueues takes nothing returns integer
return totalQueues
endfunction
///Linked list node
private struct QueueNode
readonly QueueNode next = QueueNode(0)
readonly integer val
public static method create takes integer val, QueueNode prev returns QueueNode
local QueueNode n = QueueNode.allocate()
if n == 0 then
call BJDebugMsg("Map Error: Unable to allocate a QueueNode. (QueueNode.create)")
return QueueNode(0)
endif
set n.val = val
if prev != 0 then
set prev.next = n
endif
set totalQueueNodes = totalQueueNodes + 1
return n
endmethod
private method onDestroy takes nothing returns nothing
set .next = 0
set totalQueueNodes = totalQueueNodes - 1
endmethod
endstruct
///Queue of linked list nodes
struct Queue
readonly static integer numAllocated = 0
private QueueNode first = QueueNode(0)
private QueueNode last = QueueNode(0)
readonly integer size = 0
///Appends a value to the end of the queue
public method append takes integer i returns boolean
local QueueNode n
if this == Queue(0) then
return false
endif
//pack in a node
set n = QueueNode.create(i, .last)
if n == QueueNode(0) then
return false
endif
//append node to the list
set this.last = n
if .first == QueueNode(0) then
set .first = n
endif
set .size = .size + 1
return true
endmethod
///Returns the oldest value in the queue
public method peek takes nothing returns integer
local integer i
if this == Queue(0) or .size <= 0 then
if STRICT_WARNINGS then
call BJDebugMsg("Map Error: Attempted to peek at empty Queue. (Queue.peek)")
endif
return 0
endif
return .first.val
endmethod
///Returns the oldest value in the queue and removes it from the queue
public method pop takes nothing returns integer
local integer ival
local QueueNode n
if this == Queue(0) or .size <= 0 then
if STRICT_WARNINGS then
call BJDebugMsg("Map Error: Attempted to pop empty Queue. (Queue.pop)")
endif
return 0
endif
//get value
set ival = .first.val
//remove node from list
set n = .first
set .first = .first.next
if .first == QueueNode(0) then
set .last = .first
endif
call n.destroy()
set .size = .size - 1
return ival
endmethod
///Clears all nodes from the queue
public method clear takes nothing returns nothing
loop
exitwhen .first == QueueNode(0)
call .pop()
endloop
endmethod
///Creates an empty queue
public static method create takes nothing returns Queue
local Queue q = Queue.allocate()
if q == 0 then
call BJDebugMsg("Map Error: Unable to allocate a Queue. (Queue.create)")
return Queue(0)
endif
set totalQueues = totalQueues + 1
return q
endmethod
///Ensures queues are cleared before being destroyed
private method onDestroy takes nothing returns nothing
if .size > 0 then
call .clear()
if STRICT_WARNINGS then
call BJDebugMsg("Map Error: Destroyed non-empty queue. (Queue.onDestroy)")
endif
endif
set totalQueues = totalQueues - 1
endmethod
endstruct
endlibrary
//TESH.scrollpos=69
//TESH.alwaysfold=0
library SpawnLib initializer initSpawnLib requires QueueStructLib, HAIL
globals
//should the system show debug messages? (only in debug mode)
private constant boolean SHOW_DEBUG_MESSAGES = false
//should the system automatically remove runners when they die?
private constant boolean REMOVE_ON_DEATH = false
endglobals
globals
SpawnSet SpawnLib_TriggeringSpawnSet = nill
unit SpawnLib_TriggeringUnit = null
private trigger callBackTriggerEmptySpawnSet = CreateTrigger()
private trigger callBackTriggerSpawned = CreateTrigger()
private integer totalSpawnSets = 0
private integer totalSpawnStreams = 0
private integer totalUnits = 0
private group livingUnits = CreateGroup()
endglobals
///Give units a SpawnSet property
//! runtextmacro HAIL_CreateProperty("UnitSpawnSet", "SpawnSet", "private")
function SpawnLib_NumSpawnStreams takes nothing returns integer
return totalSpawnStreams
endfunction
function SpawnLib_NumSpawnSets takes nothing returns integer
return totalSpawnSets
endfunction
function SpawnLib_NumRemaining takes nothing returns integer
return totalUnits
endfunction
function SpawnLib_ForEach takes code c returns nothing
call ForGroup(livingUnits, c)
endfunction
///Callback for when spawn sets become empty
function SpawnLib_AddEmptiedSpawnSetCallBack takes code c returns nothing
call TriggerAddAction(callBackTriggerEmptySpawnSet, c)
endfunction
///Callback for when units are spawned
function SpawnLib_AddSpawnedCallBack takes code c returns nothing
call TriggerAddAction(callBackTriggerSpawned, c)
endfunction
private function throwSpawned takes unit u, SpawnSet s returns nothing
set SpawnLib_TriggeringUnit = u
set SpawnLib_TriggeringSpawnSet = s
call TriggerExecute(callBackTriggerSpawned)
endfunction
private function throwEmptySpawnSet takes SpawnSet s returns nothing
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("SpawnLib caught a spawn set emptying.")
debug endif
set SpawnLib_TriggeringSpawnSet = s
call TriggerExecute(callBackTriggerEmptySpawnSet)
endfunction
///A unit to be spawned later
interface SpawnBase
public method getSpawnSet takes nothing returns SpawnSet
public method clone takes nothing returns SpawnBase
public method spawn takes nothing returns unit
endinterface
///Keeps track of units spawned together (for example in the same round or to the same team)
struct SpawnSet
public integer numRemaining = 0
private group g = CreateGroup()
public method ForEach takes code c returns nothing
call ForGroup(.g, c)
endmethod
///Add a unit to this set
///Unit's can only be in one spawn set at a time
public method addUnit takes unit u returns boolean
local SpawnSet s
if this == 0 or u == null then
return false
endif
//check if unit is already in a set
set s = GetUnitSpawnSet(u)
if s == this then
return true //already in this set
elseif s != 0 then
call s.removeUnit(u) //can't have units in two sets at once
endif
//add unit to set
if not SetUnitSpawnSet(u, this) then
return false //error
endif
set .numRemaining = .numRemaining + 1
set totalUnits = totalUnits + 1
call GroupAddUnit(.g, u)
call GroupAddUnit(livingUnits, u)
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("SpawnLib added a " + GetUnitName(u) + " to SpawnSet #" + I2S(this))
debug endif
return true //success
endmethod
///Remove a unit from this set
public method removeUnit takes unit u returns boolean
if this == nill or u == null or GetUnitSpawnSet(u) != this then
return false
endif
set .numRemaining = .numRemaining - 1
set totalUnits = totalUnits - 1
call GroupRemoveUnit(livingUnits, u)
call GroupRemoveUnit(.g, u)
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("SpawnLib removed a " + GetUnitName(u) + " from SpawnSet #" + I2S(this) + "; now contains " + I2S(.numRemaining))
debug endif
call ResetUnitSpawnSet(u)
if .numRemaining <= 0 then
call throwEmptySpawnSet(this)
endif
return true
endmethod
public static method create takes nothing returns SpawnSet
local SpawnSet s
//! runtextmacro CheckedAllocate("SpawnSet", "s")
set totalSpawnSets = totalSpawnSets + 1
return s
endmethod
private method onDestroy takes nothing returns nothing
set .numRemaining = 0
call DestroyGroup(.g)
set .g = null
set totalSpawnSets = totalSpawnSets - 1
endmethod
endstruct
struct SpawnStream
private Queue q //(of Spawn)
private boolean running = false //set when delayedLoopSpawn is iterating
///Spawns queued runners over time
private static method delayedLoopSpawn takes nothing returns nothing
local SpawnStream this = SpawnStream(delayedArg)
local integer i
local SpawnBase s
local SpawnSet ss
local unit u
//Stop when there are no more queued
if .q.size <= 0 then
set .running = false
return
endif
//Spawn
set s = SpawnBase(.q.pop())
if s != 0 then
set u = s.spawn()
set ss = s.getSpawnSet()
if ss != 0 then
set totalUnits = totalUnits - 1
set ss.numRemaining = ss.numRemaining - 1
if u != null then
call ss.addUnit(u)
call throwSpawned(u, ss)
endif
endif
call s.destroy()
endif
call delayedCallWithArg(function SpawnStream.delayedLoopSpawn, MIN_SPAWN_PERIOD, integer(this))
endmethod
///Queues runners to spawn at the desired frequency
private static method delayedLoopPeriodicSpawnQueue takes nothing returns nothing
local SpawnStream this = SpawnSet(delayedArg)
local SpawnBase s = SpawnBase(delayedArg2)
local integer numLeft = delayedArg3
local SpawnSet ss
set ss = s.getSpawnSet()
if ss != 0 then
set totalUnits = totalUnits - 1
set ss.numRemaining = ss.numRemaining - 1
endif
call .spawn(s)
set numLeft = numLeft - 1
if numLeft > 0 then
call delayedCallWithArg3(function SpawnStream.delayedLoopPeriodicSpawnQueue, delayedDuration, integer(this), integer(s), numLeft)
else
call s.destroy()
endif
endmethod
///Spawns a copy of the given spawn
public method spawn takes SpawnBase s returns nothing
local SpawnSet ss
//! runtextmacro when("this == nill or s == nill", "return")
//queue the spawn
set ss = s.getSpawnSet()
if ss != 0 then
set ss.numRemaining = ss.numRemaining + 1
set totalUnits = totalUnits + 1
endif
call .q.append(integer(s.clone()))
//start spawning queued units
if not .running then
set .running = true
call delayedCallWithArg(function SpawnStream.delayedLoopSpawn, 0, integer(this))
endif
endmethod
///Spawns multiple copies of the given spawn, at least period seconds apart
public method spawnMultiple takes SpawnBase s, integer num, real period returns nothing
local SpawnSet ss
//! runtextmacro when("s == nill or this == nill or num <= 0", "return")
//spawn the first
set num = num - 1
call .spawn(s)
//! runtextmacro when("num <= 0", "return")
//queue the rest
set ss = s.getSpawnSet()
if ss != 0 then
set ss.numRemaining = ss.numRemaining + num
set totalUnits = totalUnits + num
endif
call delayedCallWithArg3(function SpawnStream.delayedLoopPeriodicSpawnQueue, period, integer(this), integer(s.clone()), num)
endmethod
public static method create takes nothing returns SpawnStream
local SpawnStream ss = SpawnStream.allocate()
if ss == SpawnStream(0) then
call BJDebugMsg("Map Error: Unable to allocate a SpawnStream. (SpawnStream.create)")
return SpawnStream(0)
endif
set ss.q = Queue.create()
set totalSpawnStreams = totalSpawnStreams + 1
return ss
endmethod
private method onDestroy takes nothing returns nothing
set totalSpawnStreams = totalSpawnStreams - 1
endmethod
endstruct
function SpawnLib_RemoveUnit takes unit u returns nothing
call GetUnitSpawnSet(u).removeUnit(u)
endfunction
private function catchDeath takes nothing returns nothing
if REMOVE_ON_DEATH then
call SpawnLib_RemoveUnit(GetTriggerUnit())
endif
endfunction
private function initSpawnLib takes nothing returns nothing
local trigger t
//death
set t = CreateTrigger()
call TriggerAddAction(t, function catchDeath)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
debug if SHOW_DEBUG_MESSAGES then
debug call BJDebugMsg("Initialized SpawnLib with SHOW_DEBUG_MESSAGES")
debug endif
endfunction
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
///Contains a lot of the constants used by the engine
///Includes various runner properties
///Includes the "isX" functions for towers
library Constants initializer initConstants requires General
globals
//=== Main ===
constant integer NUM_DEFENDERS = 8 //the number of players
constant player RUNNERS_OWNER = Player(10-1) //light blue
constant real SELL_PERCENTAGE = 1.0 //remember to update tool tips if this is changed
constant integer STARTING_GOLD = 25 //gold given to players at start of game
//=== Sensitive === (require other changes in code to work)
constant integer MAX_ROUND = 30 //number of rounds. Make sure you have enough unit types defined!
//=== Game ===
constant integer MAP_SIZE = 96 //the number of tiles from the center to the side
constant integer MAP_RADIUS = MAP_SIZE*64
constant real GRASS_REGROW_PERIOD = 1.0 //seconds between each row of grass regrowing
constant real ROUND_WAIT_TIME = 30.0 //seconds between rounds
constant real RUSHED_ROUND_WAIT_TIME = 10.0 //seconds between rounds in rushed mode
constant real INITIAL_WAIT_TIME = 110.0 //seconds
constant real SELECTION_TIME = 50.0 //seconds
constant integer SPAWNS_PER_ROUND = 10
constant real SPAWN_PERIOD = 4.5 //time between normal spawns
constant real MIN_SPAWN_PERIOD = 0.5 //minimum time between spawns (for war/race)
constant integer MAX_ROUNDS = 500
constant integer MAX_STRUCTURES = 8190
//=== Tower Behavior ===
constant integer FURNACE_GRASS_REACH = 2 //the max tile distance the furnace can reach
constant real FURNACE_FUEL_PERIOD = 5.0 //time between furnaces burning grass
//=== Ability Behaviour ===
constant real TRANSFER_RANGE = 350 //Update range when changed
constant real FAR_TRANSFER_RANGE = 2000 //Update range when changed
constant integer MAX_TOWER_TRANSFERS = 8 //Update tooltip when changed
constant real WATER_BURST_RANGE = 360 //Update tooltip when changed
constant real MAX_WATER_BURST_TARGETS = 5 //Update tooltip when changed
constant string WATER_BURST_EFFECT_PATH = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
//=== Ability, Buff and Unit IDs ===
constant integer ABIL_ADD_TRANSFER = 'A000'
constant integer ABIL_ADD_FAR_TRANSFER = 'A00Q'
constant integer ABIL_BOOST = 'A004'
constant integer ABIL_BLAZE = 'A00F'
constant integer ABIL_BLIZZARD = 'A00M'
constant integer ABIL_CHEMICALS = 'A00A'
constant integer ABIL_CONSUME_RUNNER = 'A00I'
constant integer ABIL_CRIT_STRIKE = 'A00O'
constant integer ABIL_DESPAIR = 'A00G'
constant integer ABIL_ELEMENTAL_FIRE = 'A00S'
constant integer ABIL_ELEMENTAL_WATER = 'A00T'
constant integer ABIL_ELEMENTAL_NATURE = 'A00U'
constant integer ABIL_ENTANGLE = 'A00E'
constant integer ABIL_FLAME = 'A009'
constant integer ABIL_FROST = 'A00D'
constant integer ABIL_LIGHT_WAVE = 'A00K'
constant integer ABIL_LIGHTNING = 'A001'
constant integer ABIL_LOCUST_SWARM = 'A00L'
constant integer ABIL_REMOVE_TRANSFER = 'A006'
constant integer ABIL_RUNNER_SHIELD = 'A007'
constant integer ABIL_RUNNER_FEEDBACK = 'A00C'
constant integer ABIL_RUNNER_SPEED = 'A00B'
constant integer ABIL_SELL = 'A003'
constant integer ABIL_SHOW_RANGES = 'A005'
constant integer ABIL_SPAWN = 'A008'
constant integer ABIL_STORM_BOLT = 'A00H'
constant integer ABIL_TIME_DISTORTION = 'A00J'
constant integer ABIL_TRANSFORM = 'A002'
constant integer ABIL_UNSELL = 'A00R'
constant integer ABIL_WATER_BURST = 'A00N'
constant integer BUFF_BOOSTED = 'B000'
constant integer BUFF_ELEMENTAL_FIRE = 'B001'
constant integer BUFF_ELEMENTAL_NATURE = 'B003'
constant integer BUFF_ELEMENTAL_WATER = 'B002'
constant integer UNIT_BARRACKS = 'h044'
constant integer UNIT_BUILDER_1 = 'u000'
constant integer UNIT_BUILDER_2 = 'u001'
constant integer UNIT_HERO_TOWER = 'H03J'
constant integer UNIT_HERO_TOWER_FAKE = 'h03K'
constant integer UNIT_HERO_TOWER_PATH = 'h03Q'
//=== Research ===
constant integer RESEARCH_GENERATOR = 'R000'
constant integer RESEARCH_ROUND_1 = 'R002'
constant integer RESEARCH_ROUND_2 = 'R001'
constant integer RESEARCH_ROUND_3 = 'R003'
constant integer RESEARCH_ROUND_4 = 'R004'
//=== Others ===
boolean prep = true
private integer array runnerTypes
real array ENTANGLE_DURATION
endglobals
private function initConstants takes nothing returns nothing
//runner type after last round
set runnerTypes[0] = 'h041'
//runner types for each round
set runnerTypes[1] = 'h00C'
set runnerTypes[2] = 'h01Y'
set runnerTypes[3] = 'h01Z'
set runnerTypes[4] = 'h020'
set runnerTypes[5] = 'h021'
set runnerTypes[6] = 'h022'
set runnerTypes[7] = 'h023'
set runnerTypes[8] = 'h024'
set runnerTypes[9] = 'h025'
set runnerTypes[10] = 'h026'
set runnerTypes[11] = 'h027'
set runnerTypes[12] = 'h028'
set runnerTypes[13] = 'h029'
set runnerTypes[14] = 'h02A'
set runnerTypes[15] = 'h02B'
set runnerTypes[16] = 'h02C'
set runnerTypes[17] = 'h02D'
set runnerTypes[18] = 'h02E'
set runnerTypes[19] = 'h02F'
set runnerTypes[20] = 'h02G'
set runnerTypes[21] = 'h02H'
set runnerTypes[22] = 'h02I'
set runnerTypes[23] = 'h02J'
set runnerTypes[24] = 'h02K'
set runnerTypes[25] = 'h02L'
set runnerTypes[26] = 'h02M'
set runnerTypes[27] = 'h02N'
set runnerTypes[28] = 'h02O'
set runnerTypes[29] = 'h02P'
set runnerTypes[30] = 'h02Q'
if (MAX_ROUND > 30) then //update this line when MAX_ROUND is changed
call showError("Not enough runner types for all rounds.", "initConstants")
endif
set ENTANGLE_DURATION[1] = 1.0
set ENTANGLE_DURATION[2] = 1.5
set ENTANGLE_DURATION[3] = 2.0
set ENTANGLE_DURATION[4] = 2.5
set ENTANGLE_DURATION[5] = 3.0
set ENTANGLE_DURATION[6] = 3.5
endfunction
//=====================================
//=== RUNNERS =========================
//=====================================
//////////
function getRoundRunnerUnitType takes integer n returns integer
if (n <= 0 or n > MAX_ROUND) then
return runnerTypes[0]
else
return runnerTypes[n]
endif
endfunction
//////////
function getRoundRunnerHealth takes integer rnd, integer dif returns integer
local real hp
if rnd <= 0 then
return 10
endif
if dif == DIFFICULTY_NOOB then
set hp = polynom3(rnd, 25, 10, 15, 0)
elseif dif == DIFFICULTY_ROOKIE then
set hp = polynom3(rnd, 15, 0, 35, 0)
elseif dif == DIFFICULTY_HOTSHOT then
set hp = polynom3(rnd, 50, 21, 28, 1)
elseif dif == DIFFICULTY_VETERAN then
set hp = polynom3(rnd, 50, 20, 28, 2)
elseif dif == DIFFICULTY_ELITE then
set hp = polynom3(rnd, 40, 80, 0, 6)
elseif dif == DIFFICULTY_PSYCHO then
set hp = polynom3(rnd, 64, 128, 0, 10)
else
call showError("Unrecognized difficulty.", "getRoundRunnerHealth")
endif
//increase exponentially after the last round
if (rnd > MAX_ROUND) then
set hp = hp * Pow(1.1, rnd-MAX_ROUND)
endif
//make hp a multiple of 25
set hp = floor(hp/25.)*25
if hp < 25 then
set hp = 25
endif
return R2I(hp)
endfunction
//////////
function getRoundRunnerBounty takes integer n returns integer
return ceiling(n/4.) //1,1,1,1, 2,2,2,2, 3,3,3,3, ...
endfunction
//////////
function getRoundFinishBounty takes integer n returns integer
return getRoundRunnerBounty(n) * 10
endfunction
//////////
function getRoundAccumulatedBounty takes integer n returns integer
local integer i = 1
local integer t = 0
loop
exitwhen i >= n
set t = t + getRoundRunnerBounty(n)*SPAWNS_PER_ROUND + getRoundFinishBounty(n)
set i = i + 1
endloop
return t+STARTING_GOLD
endfunction
//////////
function isSpecialRound takes integer n returns boolean
return ModuloInteger(n, 5) == 0
endfunction
//////////
function isSpeedRound takes integer n returns boolean
return (isSpecialRound(n) and (n > MAX_ROUND or n == 5 or n == 15 or n == 25 or n == 30))
endfunction
//////////
function isFeedbackRound takes integer n returns boolean
return (isSpecialRound(n) and (n > MAX_ROUND or n == 10 or n == 15 or n == 30))
endfunction
//////////
function isShieldRound takes integer n returns boolean
return (isSpecialRound(n) and (n > MAX_ROUND or n == 20 or n == 25 or n == 30))
endfunction
//=====================================
//=== TOWERS ==========================
//=====================================
//////////
function isBridgingTower takes integer ut returns boolean
return ut == 'h003' or ut == 'h007' or ut == 'h008' or ut == 'h01P' or ut == 'h01Q' or ut == 'h01R' or ut == 'h04I' or ut == 'h04J' or ut == 'h04K' or ut == 'h04L' or ut == 'h04M' or ut == 'h04N'
endfunction
//////////
function isWaterWheel takes integer ut returns boolean
return ut == 'h004' or ut == 'h01K' or ut == 'h01L' or ut == 'h01M' or ut == 'h01N' or ut == 'h01O'
endfunction
//////////
function isFurnace takes integer ut returns boolean
return ut == 'h000' or ut == 'h011' or ut == 'h012' or ut == 'h013' or ut == 'h014' or ut == 'h015'
endfunction
//////////
function isGraveyard takes integer ut returns boolean
return ut == 'h036' or ut == 'h037' or ut == 'h038' or ut == 'h039' or ut == 'h03A' or ut == 'h03B'
endfunction
//////////
function isRockLauncher takes integer ut returns boolean
return ut == 'h005' or ut == 'h00W' or ut == 'h00X' or ut == 'h00Y' or ut == 'h00Z' or ut == 'h010'
endfunction
//////////
function isLichTower takes integer ut returns boolean
return ut == 'h00J' or ut == 'h01B' or ut == 'h01C' or ut == 'h01D' or ut == 'h01E' or ut == 'h01F'
endfunction
//////////
function isClockTower takes integer ut returns boolean
return ut == 'h03C' or ut == 'h03D' or ut == 'h03E' or ut == 'h03F' or ut == 'h03G' or ut == 'h03H'
endfunction
//////////
function isDemonTower takes integer ut returns boolean
return ut == 'h009' or ut == 'h016' or ut == 'h017' or ut == 'h018' or ut == 'h019' or ut == 'h01A'
endfunction
//////////
function isChemicalTower takes integer ut returns boolean
return ut == 'h001' or ut == 'h00R' or ut == 'h00S' or ut == 'h00T' or ut == 'h00U' or ut == 'h00V'
endfunction
//////////
function isSwarmTower takes integer ut returns boolean
return ut == 'h00D' or ut == 'h00H' or ut == 'h01G' or ut == 'h01H' or ut == 'h01J' or ut == 'h01I'
endfunction
//////////
function isTeslaCoil takes integer ut returns boolean
return ut == 'h002' or ut == 'h00A' or ut == 'h00N' or ut == 'h00O' or ut == 'h00P' or ut == 'h00Q'
endfunction
//////////
function isVineTrap takes integer ut returns boolean
return ut == 'h01S' or ut == 'h01T' or ut == 'h01U' or ut == 'h01V' or ut == 'h01W' or ut == 'h01X'
endfunction
//////////
function isDarkTower takes integer ut returns boolean
return ut == 'h00F' or ut == 'h02V' or ut == 'h02W' or ut == 'h02X' or ut == 'h02Y' or ut == 'h02Z'
endfunction
//////////
function isPyroTrap takes integer ut returns boolean
return ut == 'h00G' or ut == 'h00I' or ut == 'h02R' or ut == 'h02S' or ut == 'h02T' or ut == 'h02U'
endfunction
//////////
function isHolyTower takes integer ut returns boolean
return ut == 'h030' or ut == 'h031' or ut == 'h032' or ut == 'h033' or ut == 'h034' or ut == 'h035'
endfunction
//////////
function isTsunamiTower takes integer ut returns boolean
return ut == 'h03I' or ut == 'h03L' or ut == 'h03M' or ut == 'h03N' or ut == 'h03O' or ut == 'h03P'
endfunction
//////////
function isLocustTower takes integer ut returns boolean
return ut == 'h043' or ut == 'h04D' or ut == 'h04E' or ut == 'h04F' or ut == 'h04G' or ut == 'h04H'
endfunction
//////////
function isHeroTower takes integer ut returns boolean
return ut == UNIT_HERO_TOWER or ut == UNIT_HERO_TOWER_FAKE
endfunction
//////////
function isArrowTowerFire takes integer ut returns boolean
return ut == 'h00B' or ut == 'h00E' or ut == 'h00K' or ut == 'h00L' or ut == 'h00M'
endfunction
//////////
function isArrowTowerNature takes integer ut returns boolean
return ut == 'h03R' or ut == 'h03S' or ut == 'h03T' or ut == 'h03U' or ut == 'h03V'
endfunction
//////////
function isArrowTowerWater takes integer ut returns boolean
return ut == 'h03W' or ut == 'h03X' or ut == 'h03Y' or ut == 'h03Z' or ut == 'h040'
endfunction
//////////
function isArrowTowerBasic takes integer ut returns boolean
return ut == 'h006'
endfunction
//////////
function isArrowTowerAny takes integer ut returns boolean
return isArrowTowerBasic(ut) or isArrowTowerWater(ut) or isArrowTowerFire(ut) or isArrowTowerNature(ut)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Contains useful functions just for this map
library Special requires General
globals
constant real TILE_SIZE = 128.0
constant integer TILE_GRASSY_DIRT = 'Zdrg'
constant integer TILE_GRASS = 'Zgrs'
constant integer TILE_DARK_GRASS = 'Zvin'
constant integer ABIL_MAX_LIFE_MODIFIER = 'A00X'
constant integer MAX_LIFE_MODIFIER_LEVEL = 21
endglobals
function filter_IsAlive takes nothing returns boolean
return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0
endfunction
///Converts an integer to a string of length at most 5 (6 for negative)
function cSmallStr takes integer i returns string
local integer n = 0
//n = floor(log_1000(i))
loop
exitwhen i < 10000 and i > -10000
set i = floor(i / 1000.)
set n = n + 1
endloop
//append order-of-magnitude character
if (n == 0) then
return I2S(i)
elseif (n == 1) then
return I2S(i) + "K"
elseif (n == 2) then
return I2S(i) + "M"
elseif (n == 3) then
return I2S(i) + "B"
else
return I2S(i) + "?"
endif
endfunction
///Adjusts the grass level of the terrain at a given point
///tileX, tileY = tile coordinates
///d = change to grass level (usually +1 or -1)
///return = true if the tile was changed
function adjustGrassLevel takes integer tileX, integer tileY, integer d returns boolean
local integer level
local integer tile1
local integer tile2
if d == 0 then
return false
endif
//Get Current Tile Level
set tile1 = GetTerrainType(tileX*TILE_SIZE, tileY*TILE_SIZE)
if tile1 == TILE_GRASSY_DIRT then
set level = 1
elseif tile1 == TILE_GRASS then
set level = 2
elseif tile1 == TILE_DARK_GRASS then
set level = 3
else
return false //the given tile isn't grass
endif
//adjust level
set level = between(level + d, 1, 3)
//Get tile for new Level
if level == 1 then
set tile2 = TILE_GRASSY_DIRT
elseif level == 2 then
set tile2 = TILE_GRASS
elseif level == 3 then
set tile2 = TILE_DARK_GRASS
endif
if tile1 == tile2 then
return false //don't need to change the tile
endif
//Set tile
call SetTerrainType(tileX*TILE_SIZE, tileY*TILE_SIZE, tile2, -1, 1, 1)
return true
endfunction
//////////
// Picks a random nearby patch of grass to burn
// p = the point around which grass should be burned
// dTile = the 'radius' of the search, 0 = 1 tile, 1 = 9 tiles, 2 = 25 tiles, etc...
// return = true if grass was found and burned
//////////
function burnNearbyGrass takes location p, integer dTile returns boolean
local integer i
local integer j
local integer x
local integer y
local integer cx
local integer cy
local integer dx
local integer dy
local integer size = dTile*2+1
if p == null then
return false
endif
//get center tile
set cx = R2I(GetLocationX(p) / 128)
set cy = R2I(GetLocationY(p) / 128)
//pick random starting tile offset
set dx = GetRandomInt(1, size)-1
set dy = GetRandomInt(1, size)-1
//scan the tiles in range starting from the chosen tile for burnable grass
set i = 0
loop
exitwhen i >= size
set j = 0
loop
exitwhen j >= size
set x = cx + ModuloInteger(i+dx, size) - dTile
set y = cy + ModuloInteger(j+dy, size) - dTile
if adjustGrassLevel(x, y, -1) then
return true
endif
set j = j + 1
endloop
set i = i + 1
endloop
//failed to find grass
return false
endfunction
///Creates a nice circle of dying special effects
function createCircleEffect takes real x, real y, real r, string model returns nothing
local real theta
local real dTheta
if r < 10 then
return //radius is ridiculously small
endif
//create at least 8 evenly spaced effects
set dTheta = 2*bj_PI*RMinBJ(25./r, 1./8)
set theta = 0
loop
exitwhen theta >= bj_PI*2
call createBang(x + r*Cos(theta), y + r*Sin(theta), model)
set theta = theta + dTheta
endloop
endfunction
////////////
// Sets a unit's maximum health to the given value in logarithmic time
// NOTE: requires the "Max Life Modifier" ability
// NOTE: tops off based on the max level of the ability (about 10 billion hp)
////////////
function setUnitMaxHealth takes unit u, integer maxHealth returns boolean
local integer dHealth
local integer lvl
local integer n
if u == null or maxHealth <= 0 then
return false
endif
//compute the difference and set base level to match sign
set dHealth = maxHealth - R2I(GetUnitState(u, UNIT_STATE_MAX_LIFE))
if (dHealth >= 0) then
set lvl = 3
else
set lvl = 2
set dHealth = -dHealth
endif
//compute the closest smaller power of 10
set n = 1
loop
exitwhen n*10 > dHealth
set n = n * 10
set lvl = lvl + 2
endloop
if (lvl > MAX_LIFE_MODIFIER_LEVEL) then
return false
endif
//adjust health
loop
exitwhen dHealth == 0
//move to smaller powers of 10 as they are needed
loop
exitwhen n <= dHealth
set n = n / 10
set lvl = lvl - 2
endloop
//adjust health by current power of 10
call UnitAddAbility(u, ABIL_MAX_LIFE_MODIFIER)
call SetUnitAbilityLevel(u, ABIL_MAX_LIFE_MODIFIER, lvl)
call UnitRemoveAbility(u, ABIL_MAX_LIFE_MODIFIER)
set dHealth = dHealth - n
endloop
return true
endfunction
function delayedKillUnit takes nothing returns nothing
call KillUnit(I2Unit(delayedArg))
endfunction
function delayedDestroyLightning takes nothing returns nothing
call DestroyLightning(I2Lightning(delayedArg))
endfunction
function delayedDestroyEffect takes nothing returns nothing
call DestroyEffect(I2Effect(delayedArg))
endfunction
function delayedInstantOrder takes nothing returns nothing
local unit u = I2Unit(delayedArg)
local integer oid = delayedArg2
call IssueImmediateOrderById(u, oid)
set u = null
endfunction
function scheduleDestroyLightning takes lightning l, real delay returns nothing
call delayedCallWithArg(function delayedDestroyLightning, delay, H2I(l))
endfunction
function scheduleDestroyEffect takes effect e, real delay returns nothing
call delayedCallWithArg(function delayedDestroyEffect, delay, H2I(e))
endfunction
function scheduleInstantOrderId takes unit u, integer orderId, real delay returns nothing
call delayedCallWithArg2(function delayedInstantOrder, delay, H2I(u), orderId)
endfunction
function scheduleInstantOrder takes unit u, string order, real delay returns nothing
call scheduleInstantOrderId(u, OrderId(order), delay)
endfunction
function createTimedEffect takes location p, string modelPath, real delay returns nothing
call scheduleDestroyEffect(AddSpecialEffectLoc(modelPath, p), delay)
endfunction
function createTimedAttachedEffect takes widget target, string modelPath, string attachPath, real delay returns nothing
call scheduleDestroyEffect(AddSpecialEffectTarget(modelPath, target, attachPath), delay)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Represents the overall game state and environment
library LibGame requires General, Special, Constants, LibDefender, War
globals
constant integer GAME_STATE_INTRO = 1
constant integer GAME_STATE_RUNNING = 2
constant integer GAME_STATE_DONE = 3
constant integer GAME_TYPE_NORMAL = 1
constant integer GAME_TYPE_RACE = 2
constant integer GAME_TYPE_RUSHED = 3
constant integer GAME_TYPE_WAR = 4
constant integer GAME_TYPE_RUSHED_WAR = 5
constant integer DIFFICULTY_NOOB = 1
constant integer DIFFICULTY_ROOKIE = 2
constant integer DIFFICULTY_HOTSHOT = 3
constant integer DIFFICULTY_VETERAN = 4
constant integer DIFFICULTY_ELITE = 5
constant integer DIFFICULTY_PSYCHO = 6
constant integer INCOME_TYPE_STANDARD = 1
constant integer INCOME_TYPE_FAIR = 2
constant integer INCOME_TYPE_WEIGHTED = 3
constant integer TEAM_TYPE_SOLO = 1
constant integer TEAM_TYPE_COOP = 2
constant integer TEAM_TYPE_SIDES = 4
constant integer TEAM_TYPE_CORNERS = 5
constant integer GAME_LENGTH_LAST_MAN = MAX_ROUNDS+1
constant integer GAME_LENGTH_ENDLESS = MAX_ROUNDS+2
endglobals
function InitTrig_Game takes nothing returns nothing
call Game.initonce()
endfunction
struct Game
//! runtextmacro EnforceStatic("Game")
//! runtextmacro CreateInitOnceMethod("Game")
readonly static integer state = GAME_STATE_INTRO
readonly static integer time = 0 //seconds
readonly static integer teamType = nill
readonly static integer gameType = nill
readonly static integer difficulty = nill
readonly static integer income = nill
readonly static integer numRounds = 0
readonly static boolean warMode = false //allow summoning
readonly static boolean syncedRounds = false //teams start rounds at the same time
readonly static boolean rushedRounds = false //teams can start rounds without waiting for others
private button buttonEnd = null
private button buttonContinue = null
///Returns the number of runners left, include unspawned runners.
public static method getFormattedTime takes nothing returns string
local string s = ""
local integer n
local integer t = Game.time
//seconds
set n = ModuloInteger(t, 60)
set t = t / 60
set s = I2S(n) + "s"
//minutes
if (t > 0) then
set n = ModuloInteger(t, 60)
set t = t / 60
set s = I2S(n) + "m " + s
endif
//hours
if (t > 0) then
set n = ModuloInteger(t, 24)
set t = t / 24
set s = I2S(n) + "h " + s
endif
//days
if (t > 0) then
set s = I2S(t) + "d " + s
endif
return s
endmethod
///Returns the highest round any team is on.
public static method getMaxRound takes nothing returns integer
local integer i = 1
local integer max = 0
loop
exitwhen i > Team.numAllocated
if Team.teams[i].state != TEAM_STATE_DEAD then
set max = IMaxBJ(max, Team.teams[i].maxRound)
endif
set i = i + 1
endloop
return max
endmethod
///Returns the lowest round any team is on.
public static method getMinRound takes nothing returns integer
local integer i = 1
local integer min = MAX_ROUNDS
loop
exitwhen i > Team.numAllocated
if Team.teams[i].state != TEAM_STATE_DEAD then
set min = IMinBJ(min, Team.teams[i].minRound)
endif
set i = i + 1
endloop
return min
endmethod
//=====================================
//=== FUNCTIONS =======================
//=====================================
////////
public static method startGame takes string caption, integer difficulty, integer teamType, integer gameType, integer numRounds, integer numLives, integer income returns boolean
local integer i
local Team t
local Defender d
if (Game.state != GAME_STATE_INTRO) then
return false
endif
//teams
if teamType == TEAM_TYPE_SOLO then
//everyone is their own team
elseif teamType == TEAM_TYPE_COOP then
//everyone is on the same team
set t = defenders[1].team
call t.merge(defenders[2].team)
call t.merge(defenders[3].team)
call t.merge(defenders[4].team)
call t.merge(defenders[5].team)
call t.merge(defenders[6].team)
call t.merge(defenders[7].team)
call t.merge(defenders[8].team)
elseif teamType == TEAM_TYPE_SIDES then
//players sharing a side are on the same team
call defenders[1].team.merge(defenders[8].team)
call defenders[3].team.merge(defenders[2].team)
call defenders[5].team.merge(defenders[4].team)
call defenders[7].team.merge(defenders[6].team)
elseif teamType == TEAM_TYPE_CORNERS then
//players sharing a corner are on the same team
call defenders[1].team.merge(defenders[2].team)
call defenders[3].team.merge(defenders[4].team)
call defenders[5].team.merge(defenders[6].team)
call defenders[7].team.merge(defenders[8].team)
else
call BJDebugMsg("Map Error: Unrecognized team type. (Game.startGame)")
endif
//pass values
set Game.difficulty = difficulty
set Game.gameType = gameType
set Game.teamType = teamType
set Game.income = income
set Game.numRounds = numRounds
//type
if Game.gameType == GAME_TYPE_NORMAL then
set Game.syncedRounds = true
set Game.rushedRounds = false
set Game.warMode = false
elseif Game.gameType == GAME_TYPE_RACE then
set Game.syncedRounds = false
set Game.rushedRounds = true
set Game.warMode = false
elseif Game.gameType == GAME_TYPE_RUSHED then
set Game.syncedRounds = true
set Game.rushedRounds = true
set Game.warMode = false
elseif Game.gameType == GAME_TYPE_WAR then
set Game.syncedRounds = true
set Game.rushedRounds = false
set Game.warMode = true
elseif Game.gameType == GAME_TYPE_RUSHED_WAR then
set Game.syncedRounds = true
set Game.rushedRounds = true
set Game.warMode = true
else
call BJDebugMsg("Map Error: Unrecognized game type. (Game.startGame)")
endif
//lives and countdown
set i = 1
loop
exitwhen i > Team.numAllocated
set t = Team.teams[i]
if t.countLivingMembers() <= 0 then
call t.kill()
else
call t.setLives(numLives)
call t.startNextRoundCountdown()
endif
set i = i + 1
endloop
set i = 1
loop
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
call SetPlayerState(d.p, PLAYER_STATE_FOOD_CAP_CEILING, numLives)
if d.state != DEFENDER_STATE_DEFENDING and Game.income == INCOME_TYPE_WEIGHTED then
call d.team.distributeGold(STARTING_GOLD)
endif
set i = i + 1
endloop
//started
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, 12)
set Game.state = GAME_STATE_RUNNING
if Game.warMode then
call startWar()
endif
call StartMultiboard(caption)
call Game.checkForEnd()
return true
endmethod
///Keeps track of the game time
private static method catchTimeTick takes nothing returns nothing
set Game.time = Game.time + 1
endmethod
///Regrows the grass row on row, that mark our place; and in the sky; The larks, still bravely singing, fly;
private static method delayedLoopRegrowGrass takes nothing returns nothing
local integer col = delayedArg
local integer row
//Go to next column
set col = col + 1
if (col > MAP_SIZE/2) then
set col = MAP_SIZE / -2
endif
//regrow the column
set row = MAP_SIZE / -2
loop
exitwhen row > MAP_SIZE/2
call adjustGrassLevel(col, row, 1)
set row = row + 1
endloop
//queue next call
call delayedCallWithArg(function Game.delayedLoopRegrowGrass, GRASS_REGROW_PERIOD, col)
endmethod
//=====================================
//=== END GAME ========================
//=====================================
///
private static method conclusionClickedExit takes nothing returns nothing
if GetTriggerPlayer() == GetLocalPlayer() then
call EndGame(true)
endif
endmethod
///
private static method conclusionClickedContinue takes nothing returns nothing
local integer i = 1
set Game.numRounds = GAME_LENGTH_ENDLESS
set Game.state = GAME_STATE_RUNNING
loop
exitwhen i > Team.numAllocated
call Team.teams[i].startNextRoundCountdown()
set i = i + 1
endloop
endmethod
///
private static method delayedConclusion takes nothing returns nothing
local dialog d = DialogCreate()
local trigger t
//exit game option
set t = CreateTrigger()
call TriggerRegisterDialogButtonEvent(t, DialogAddButton(d, "Exit Game", 0))
call TriggerAddAction(t, function Game.conclusionClickedExit)
//keep playing option
if Team.countLivingTeams() > 0 then
set t = CreateTrigger()
call TriggerRegisterDialogButtonEvent(t, DialogAddButton(d, "Keep Playing (endless)", 0))
call TriggerAddAction(t, function Game.conclusionClickedContinue)
endif
//show menu
if Defender.fromPlayer(GetLocalPlayer()).state == DEFENDER_STATE_DEFENDING then
call DialogSetMessage(d, "|cFF00FF00Victory!|r")
call StartSound(bj_victoryDialogSound)
else
call DialogSetMessage(d, "|cFFFF0000Defeat!|r")
call StartSound(bj_defeatDialogSound)
endif
call DialogDisplay(GetLocalPlayer(), d, true)
endmethod
///
private static method end takes nothing returns nothing
//! runtextmacro when("Game.state == GAME_STATE_DONE", "return")
set Game.state = GAME_STATE_DONE
if Defender.fromPlayer(GetLocalPlayer()).state == DEFENDER_STATE_DEFENDING then
call showPlayerMessage(GetLocalPlayer(), "|cFFFFCC00Congratulations! You have survived!|r")
endif
call showMessage("|cFFFF8000The game will end in 10 seconds.|r")
call UpdateMultiboard()
call delayedCall(function Game.delayedConclusion, 10)
endmethod
///
public static method checkForEnd takes nothing returns nothing
if Defender.countLiveDefenders() <= 0 then
call Game.end()
elseif Game.numRounds == GAME_LENGTH_LAST_MAN then
if Team.countLivingTeams() <= 1 then
call Game.end()
endif
elseif Game.numRounds != GAME_LENGTH_ENDLESS then
if Game.getMinRound() >= Game.numRounds and SpawnLib_NumRemaining() <= 0 then
call Game.end()
endif
endif
endmethod
///
private static method init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerAddAction(t, function Game.catchTimeTick)
call TriggerRegisterTimerEvent(t, 1.0, true)
call delayedCallWithArg(function Game.delayedLoopRegrowGrass, GRASS_REGROW_PERIOD, 0)
endmethod
endstruct
endlibrary
//TESH.scrollpos=164
//TESH.alwaysfold=0
///Represents a player defending a path
///One is created at startup for each player
library LibDefender initializer initDefender requires General, Constants, Special, LibMultiboard
globals
constant integer DEFENDER_STATE_MISSING = 1
constant integer DEFENDER_STATE_DEFENDING = 2
constant integer DEFENDER_STATE_WATCHING = 3
Defender array defenders
endglobals
struct Defender
//! runtextmacro EnforcePermanent("Defender")
readonly player p = null
readonly integer index = 0
public Team team = nill
public integer state = DEFENDER_STATE_MISSING
public Path path = nill
readonly SpawnStream stream = nill
readonly unit builder = null
readonly group specialUnits = CreateGroup()
///Returns the Defender structure of a given player
public static method fromPlayer takes player p returns Defender
local integer i
//! runtextmacro when("p == null", "return nill")
set i = GetPlayerId(p)+1
//! runtextmacro when("i < 1 or i > NUM_DEFENDERS", "return nill")
return defenders[i]
endmethod
///Returns the Defender structure of the owner of a given unit
public static method fromUnit takes unit u returns Defender
//! runtextmacro when("u == null", "return nill")
return Defender.fromPlayer(GetOwningPlayer(u))
endmethod
///Returns the Defender structure of the player with the given color, name or number
public static method fromString takes string s returns Defender
return Defender.fromPlayer(getPlayerFromString(s))
endmethod
//=====================================
//=== PROPERTIES ======================
//=====================================
///Returns the defender with the lowest index who is playing and hasn't died
public static method getMainDefender takes nothing returns Defender
local integer i = 1
loop
exitwhen i > NUM_DEFENDERS
if defenders[i].state != DEFENDER_STATE_MISSING then
return defenders[i]
endif
set i = i + 1
endloop
return nill
endmethod
///Returns the number of defenders who are playing and haven't died
public static method countLiveDefenders takes nothing returns integer
local integer i = 1
local integer n = 0
loop
exitwhen i > NUM_DEFENDERS
if defenders[i].state == DEFENDER_STATE_DEFENDING then
set n = n + 1
endif
set i = i + 1
endloop
return n
endmethod
///Returns the name of a defender
public method getName takes nothing returns string
//! runtextmacro when("this == nill", "return \"\"")
return GetPlayerName(.p)
endmethod
///Returns the color of a defender with color tags around it
public method getColor takes nothing returns string
return getPlayerColor(.p)
endmethod
///Returns the color of a defender with color tags around it
public method getNameWithColor takes nothing returns string
//! runtextmacro when("this == nill", "return \"\"")
return getPlayerColoredName(.p)
endmethod
///Returns the total value of all towers owned by a defender
public method getTotalTowerValue takes nothing returns integer
//! runtextmacro when("this == nill", "return 0")
//the builder makes food to keep the lives display correct
return GetPlayerState(.p, PLAYER_STATE_RESOURCE_FOOD_CAP) - GetUnitFoodMade(.builder)
endmethod
public method showMessage takes string s returns boolean
//! runtextmacro when("this == nill", "return false")
call DisplayTextToPlayer(.p, 0, 0, s)
return true
endmethod
//=====================================
//=== MUTATORS ========================
//=====================================
///Removes a defender, removing/distributing all towers and gold.
public method kill takes nothing returns nothing
local group g
local unit u
local integer i
local integer n
local integer numAllies
local integer gold
local integer dGold
local Defender d
call delayedCall(function AssignMultiboardPlayerRows, 0.)
//! runtextmacro when("this == nill or .state != DEFENDER_STATE_DEFENDING", "return")
//player
set .state = DEFENDER_STATE_WATCHING
set gold = GetPlayerState(.p, PLAYER_STATE_RESOURCE_GOLD)
call SetPlayerState(.p, PLAYER_STATE_RESOURCE_GOLD, 0)
call KillUnit(.builder)
set .builder = null
//team
set numAllies = .team.countLivingMembers()
if numAllies <= 0 then
call .team.kill()
endif
//gold
if numAllies > 0 then
//distribute gold
set n = numAllies
set i = 1
loop
exitwhen i > .team.numMembers
set d = .team.members[i]
if d.state == DEFENDER_STATE_DEFENDING and n > 0 then
set dGold = gold/n
call AdjustPlayerStateBJ(dGold, d.p, PLAYER_STATE_RESOURCE_GOLD)
call showPlayerMessage(d.p, "You have received |cFFFFCC00" + I2S(dGold) + "|r of " + .getNameWithColor() + "'s gold.")
set gold = gold - dGold
set n = n - 1
endif
set i = i + 1
endloop
endif
//kill or distribute towers
set g = GetUnitsOfPlayerMatching(.p, Condition(function filter_IsAlive))
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if numAllies <= 0 then
call KillUnit(u)
else
call SetUnitOwner(u, .team.getNextIncomeDefender().p, true)
endif
endloop
call DestroyGroup(g)
set g = null
//completely remove computers
if GetPlayerController(.p) == MAP_CONTROL_COMPUTER then
set .state = DEFENDER_STATE_MISSING
endif
call Game.checkForEnd()
endmethod
public static method create takes player p returns Defender
local integer n
local Defender d = Defender.allocate()
set d.stream = SpawnStream.create()
set d.p = p
set d.index = GetPlayerId(p)+1
set defenders[d.index] = d
//Setup alliances
call SetPlayerAllianceStateBJ(d.p, RUNNERS_OWNER, bj_ALLIANCE_UNALLIED)
call SetPlayerAllianceStateBJ(RUNNERS_OWNER, d.p, bj_ALLIANCE_UNALLIED)
//Create starting stuff
if GetPlayerSlotState(d.p) == PLAYER_SLOT_STATE_PLAYING then
set d.state = DEFENDER_STATE_DEFENDING
call SetPlayerState(d.p, PLAYER_STATE_RESOURCE_GOLD, STARTING_GOLD)
set n = GetPlayerStartLocation(d.p)
set d.builder = CreateUnit(d.p, UNIT_BUILDER_1, GetStartLocationX(n), GetStartLocationY(n), bj_UNIT_FACING)
call GroupAddUnit(d.specialUnits, d.builder)
endif
//Initial team
call Team.create(d)
return d
endmethod
endstruct
///Kills leaving players
private function catchLeave takes nothing returns nothing
local Defender d = Defender.fromPlayer(GetTriggerPlayer())
//! runtextmacro when("d == nill", "return")
call showMessage(d.getNameWithColor() + " has left the game.")
call d.kill()
set d.state = DEFENDER_STATE_MISSING
endfunction
///Updates lumber (total value of towers)
private function catchTotalValueChange takes nothing returns nothing
local Defender d
local integer i = 1
loop
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
call SetPlayerState(d.p, PLAYER_STATE_RESOURCE_LUMBER, d.getTotalTowerValue())
set i = i + 1
endloop
endfunction
///Upgrades 'share units' to 'full share units'
///note: GetTriggerPlayer doesn't work on some alliance events
private function catchAllianceChange takes nothing returns nothing
local integer i
local integer j
local player p
local player q
set i = 0
loop
exitwhen i >= 12
set p = Player(i)
set j = 0
loop
exitwhen j >= 12
set q = Player(j)
if p != q and GetPlayerController(p) == MAP_CONTROL_USER and GetPlayerController(q) == MAP_CONTROL_USER then
if GetPlayerAlliance(p, q, ALLIANCE_SHARED_CONTROL) and not GetPlayerAlliance(p, q, ALLIANCE_SHARED_ADVANCED_CONTROL) then
call SetPlayerAlliance(p, q, ALLIANCE_SHARED_ADVANCED_CONTROL, true)
endif
endif
set j = j + 1
endloop
set i = i + 1
endloop
//make sure 'team resources' multiboard doesn't replace the multiboard
call delayedCall(function AssignMultiboardPlayerRows, 0)
endfunction
//////////
private function initDefender2 takes nothing returns nothing
local integer i
local integer j
local Defender d
local texttag tt
local string s = ""
//make sure pathing centered
if ((H2I(Location(000.10,496.78)) != 1049678)) then
//easy passwords are evil
set s = s + "\n" + encode(17, "8GPE2lVhCTIaoF!B4ODcIFlpCayvfaRqbq CKB8Pn!hoQlvx")
set s = s + "\n" + encode( 2, "!IDWymUIrQKFEaq97V7l:evEFaZgWaYa8CazLz!mh:rxh4LnMas6K83D0")
endif
//create defenders
set i = 1
loop
exitwhen i > NUM_DEFENDERS
call Defender.create(Player(i-1))
set i = i + 1
endloop
call showMessage(s)
//paths
set d = defenders[1]
set d.path = Path.create()
call d.path.addWaypoint(gg_rct_Pathing_0)
call d.path.addWaypoint(gg_rct_Pathing_1)
call d.path.addWaypoint(gg_rct_Pathing_2)
call d.path.addWaypoint(gg_rct_Pathing_3)
call d.path.addWaypoint(gg_rct_Pathing_4)
call d.path.addWaypoint(gg_rct_Pathing_5)
call d.path.addWaypoint(gg_rct_Pathing_End)
set defenders[2].path = d.path.clone( true, false, false)
set defenders[3].path = d.path.clone( true, false, true)
set defenders[4].path = d.path.clone(false, false, true)
set defenders[5].path = d.path.clone(false, true, true)
set defenders[6].path = d.path.clone( true, true, true)
set defenders[7].path = d.path.clone( true, true, false)
set defenders[8].path = d.path.clone(false, true, false)
if NUM_DEFENDERS != 8 then
call showError("Pathing not initialized for the correct number of players.", "Defender.init2")
endif
//waypoint name tags
set i = 1
loop
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
set s = d.getNameWithColor()
set j = 1
loop
exitwhen j > d.path.numWaypoints
set tt = CreateTextTag()
call SetTextTagTextBJ(tt, s, 10)
call SetTextTagPos(tt, d.path.getWaypointX(j), d.path.getWaypointY(j), 0)
call SetTextTagLifespan(tt, 180.)
call SetTextTagPermanent(tt, false)
call SetTextTagFadepoint(tt, 80)
set tt = null
set j = j + 1
endloop
set i = i + 1
endloop
endfunction
private function initDefender takes nothing returns nothing
local integer i
local trigger t
//player left
set t = CreateTrigger()
call TriggerAddAction(t, function catchLeave)
set i = 0
loop
exitwhen i >= 12
call TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_LEAVE)
set i = i + 1
endloop
//total value changes
set t = CreateTrigger()
call TriggerAddAction(t, function catchTotalValueChange)
call TriggerRegisterTimerEvent(t, 1.0, true)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_START)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_START)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_FINISH)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_CANCEL)
//alliance changes
set t = CreateTrigger()
call TriggerAddAction(t, function catchAllianceChange)
set i = 0
loop
exitwhen i >= 12
call TriggerRegisterPlayerAllianceChange(t, Player(i), ALLIANCE_SHARED_CONTROL)
call TriggerRegisterPlayerAllianceChange(t, Player(i), ALLIANCE_PASSIVE)
call TriggerRegisterPlayerAllianceChange(t, Player(i), ALLIANCE_SHARED_VISION)
set i = i + 1
endloop
//run player init last
set t = CreateTrigger()
call TriggerRegisterTimerEvent(t, 0.00, false)
call TriggerAddAction(t, function initDefender2)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Handles the multiboard
library LibMultiboard requires SpawnLib
globals
private constant integer NUM_COLUMNS = 4
private constant integer ROW_HEADER = 0
private constant integer ROW_COLUMN_TITLES = 1
private constant integer ROW_PLAYERS = 2
private constant integer COL_NAME = 0
private constant integer COL_AVAIL = 1
private constant integer COL_PRODUC = 2
private constant integer COL_DRAIN = 3
private constant integer COL_ROUND = 0
private constant integer COL_RUNNERS = 1
private constant integer COL_TIME = 2
private constant integer COL_EMPTY = 3
private multiboarditem lastItem = null
private multiboard board = null
private integer array rowMap
integer array lastProducD
integer array lastDrainD
integer array lastAvailD
endglobals
///Returns the multiboarditem for the given position and takes care of cleaning
private function cell takes integer row, integer col returns multiboarditem
call MultiboardReleaseItem(lastItem)
set lastItem = MultiboardGetItem(board, row, col)
return lastItem
endfunction
///Updates the board to current values
function UpdateMultiboard takes nothing returns boolean
local integer i
local integer n
local integer m
local Tower t
local Defender d
local integer array produc
local integer array drain
local integer array avail
if board == null then
return false
endif
//calculate stats
set i = 1
loop
exitwhen i > Tower.numAllocated
set t = Tower.towers[i]
set d = Defender.fromUnit(t.u)
if d != nill then
set n = d.index
set produc[n] = produc[n] + t.estimatedProduction
set drain[n] = drain[n] + t.estimatedDrain
set avail[n] = avail[n] + t.getEnergy()
endif
set i = i + 1
endloop
//Header
set n = Game.getMinRound()
set m = Game.getMaxRound()
if m < n then
call MultiboardSetItemValue(cell(ROW_HEADER, COL_ROUND), "Round: |cFFFFCC00-|r")
elseif m > n then
call MultiboardSetItemValue(cell(ROW_HEADER, COL_ROUND), "Round: |cFFFFCC00" + I2S(n) + ":" + I2S(m) + "|r")
else
call MultiboardSetItemValue(cell(ROW_HEADER, COL_ROUND), "Round: |cFFFFCC00" + I2S(n) + "|r")
endif
call MultiboardSetItemValue(cell(ROW_HEADER, COL_RUNNERS), "Runners: |cFFFFCC00" + I2S(SpawnLib_NumRemaining()) + "|r")
if Game.gameType == GAME_TYPE_RACE then
call MultiboardSetItemValue(cell(ROW_HEADER, COL_TIME), "|cFFC3DBFF" + Game.getFormattedTime() + "|r")
endif
//Players
set i = 1
loop
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
set n = rowMap[i]
if n != 0 then
set lastAvailD[i] = avail[i]
set lastProducD[i] = produc[i]
set lastDrainD[i] = drain[i]
if d.state == DEFENDER_STATE_DEFENDING then
call MultiboardSetItemValue(cell(n, COL_AVAIL), cSmallStr(avail[i]))
call MultiboardSetItemValue(cell(n, COL_PRODUC), cSmallStr(produc[i]))
call MultiboardSetItemValue(cell(n, COL_DRAIN), cSmallStr(drain[i]))
else
call MultiboardSetItemValue(cell(n, COL_AVAIL), "-")
call MultiboardSetItemValue(cell(n, COL_PRODUC), "-")
call MultiboardSetItemValue(cell(n, COL_DRAIN), "-")
endif
endif
set i = i + 1
endloop
return true
endfunction
///Assigns players to rows in the multiboard
function AssignMultiboardPlayerRows takes nothing returns boolean
local integer i
local integer j
local integer n
local integer row
local Defender d
if board == null then
return false
endif
//assign rows in order
set i = 1
set row = ROW_PLAYERS
loop
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
if d.state != DEFENDER_STATE_MISSING then
call MultiboardSetItemValue(cell(row, COL_NAME), d.getNameWithColor())
set rowMap[i] = row
set row = row + 1
else
set rowMap[i] = 0
endif
set i = i + 1
endloop
//resize board
call MultiboardSetRowCount(board, row)
call UpdateMultiboard()
call MultiboardDisplay(board, true)
return true
endfunction
//Creates the multiboard
function StartMultiboard takes string caption returns nothing
local integer i
local integer n
//Create
set board = CreateMultiboard()
call MultiboardSetRowCount(board, ROW_PLAYERS+NUM_DEFENDERS)
call MultiboardSetColumnCount(board, NUM_COLUMNS)
call MultiboardSetTitleText(board, caption)
call AssignMultiboardPlayerRows()
//Styles
set i = 0
set n = MultiboardGetRowCount(board)
loop
exitwhen i >= n
call MultiboardSetItemStyle(cell(i, 0), true, false)
call MultiboardSetItemStyle(cell(i, 1), true, false)
call MultiboardSetItemStyle(cell(i, 2), true, false)
call MultiboardSetItemStyle(cell(i, 3), true, false)
set i = i + 1
endloop
//Widths
call MultiboardSetItemWidth(cell(ROW_HEADER, COL_ROUND), 0.07)
call MultiboardSetItemWidth(cell(ROW_HEADER, COL_RUNNERS), 0.07)
call MultiboardSetItemWidth(cell(ROW_HEADER, COL_TIME), 0.06)
call MultiboardSetItemWidth(cell(ROW_HEADER, COL_EMPTY), 0.00)
set i = ROW_COLUMN_TITLES
set n = MultiboardGetRowCount(board)
loop
exitwhen i >= n
call MultiboardSetItemWidth(cell(i, COL_NAME), 0.08)
call MultiboardSetItemWidth(cell(i, COL_AVAIL), 0.04)
call MultiboardSetItemWidth(cell(i, COL_PRODUC), 0.04)
call MultiboardSetItemWidth(cell(i, COL_DRAIN), 0.04)
set i = i + 1
endloop
//Columns
call MultiboardSetItemValue(cell(ROW_COLUMN_TITLES, COL_NAME), "Players")
call MultiboardSetItemValue(cell(ROW_COLUMN_TITLES, COL_AVAIL), "Avail")
call MultiboardSetItemValue(cell(ROW_COLUMN_TITLES, COL_PRODUC), "Produc")
call MultiboardSetItemValue(cell(ROW_COLUMN_TITLES, COL_DRAIN), "Drain")
call MultiboardSetItemValueColor(cell(ROW_COLUMN_TITLES, COL_NAME), 192, 205, 255, 255)
call MultiboardSetItemValueColor(cell(ROW_COLUMN_TITLES, COL_AVAIL), 192, 205, 255, 255)
call MultiboardSetItemValueColor(cell(ROW_COLUMN_TITLES, COL_PRODUC), 192, 205, 255, 255)
call MultiboardSetItemValueColor(cell(ROW_COLUMN_TITLES, COL_DRAIN), 192, 205, 255, 255)
//Prepared
call MultiboardDisplay(board, true)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///This structure represents a team of defenders. It is in charge of a lot
///of things Game used to be in charge of, such as ending rounds, starting
///rounds, victory/defeat, some spawning, etc. It is also in charge of
///lives, has a bunch of useful methods for counting runners, and tracks
///all allocated teams in a static array.
library LibTeam initializer initTeam requires General, Constants, Special, LibDefender, SpawnLib
globals
constant integer ROUND_STATE_NONE = 0
constant integer ROUND_STATE_RUNNING = 1
constant integer ROUND_STATE_COMPLETED = 2
constant integer TEAM_STATE_WAITING = 1
constant integer TEAM_STATE_DEFENDING = 2
constant integer TEAM_STATE_DONE_ROUND = 3
constant integer TEAM_STATE_DEAD = 4
endglobals
struct Round
readonly static integer numAllocated = 0
readonly integer r
readonly SpawnSet s
readonly Team t
readonly static Round array map
public static method create takes Team team, SpawnSet spawns, integer rnd returns Round
local Round r
if Round.map[integer(spawns)] != nill then
call BJDebugMsg("Map Error: Round created twice for the same spawn set. (Round.create)")
return nill
endif
set r = Round.allocate()
set r.r = rnd
set r.s = spawns
set r.t = team
set Round.map[integer(spawns)] = r
set Round.numAllocated = Round.numAllocated + 1
return r
endmethod
private method onDestroy takes nothing returns nothing
set Round.map[integer(.s)] = nill
call .s.destroy()
set .s = nill
set .t = nill
set .r = 0
set Round.numAllocated = Round.numAllocated - 1
endmethod
endstruct
struct Team
readonly static integer numAllocated = 0
readonly static Team array teams
readonly integer arrayIndex = 0
readonly static integer racePositions = 0
readonly Defender array members[NUM_DEFENDERS]
readonly integer numMembers = 0
readonly force memberForce = CreateForce()
readonly integer state
readonly integer lives = 0
private integer incomeDivideIndex = 1
readonly integer array roundStates[MAX_ROUNDS]
readonly integer minRound = 0
readonly integer maxRound = 0
private timer roundTimer
private timerdialog roundTimerDialog
//=====================================
//=== PROPERTIES ======================
//=====================================
///Returns the number of living members in this team
public method countLivingMembers takes nothing returns integer
local integer i
local integer n
//! runtextmacro when("this == nill or .state == TEAM_STATE_DEAD", "return 0")
//find a living member
set i = 1
set n = 0
loop
exitwhen i > this.numMembers
if .members[i].state == DEFENDER_STATE_DEFENDING then
set n = n + 1
endif
set i = i + 1
endloop
return n
endmethod
///Returns the number of living teams
public static method countLivingTeams takes nothing returns integer
local integer i
local integer n
set i = 1
set n = 0
loop
exitwhen i > Team.numAllocated
if Team.teams[i].state != TEAM_STATE_DEAD then
set n = n + 1
endif
set i = i + 1
endloop
return n
endmethod
///Returns a name for the team (made up of the member names)
public method getName takes nothing returns string
local integer i
local string s
if this == nill then
return ""
elseif this.numMembers >= NUM_DEFENDERS then
return "Everyone"
endif
set i = 2
set s = .members[1].getNameWithColor()
loop
exitwhen i > .numMembers
set s = s + ", " + .members[i].getNameWithColor()
set i = i + 1
endloop
return s
endmethod
//=====================================
//=== MUTATERS ========================
//=====================================
public method setLives takes integer n returns nothing
local integer i
local Defender d
//! runtextmacro when("this == nill", "return")
set .lives = n
set i = 1
loop
exitwhen i > .numMembers
set d = .members[i]
call SetPlayerState(d.p, PLAYER_STATE_RESOURCE_FOOD_USED, .lives)
set i = i + 1
endloop
endmethod
///Takes a life from the team and kills its members if lives run out
public method loseLife takes nothing returns boolean
local integer i
local Defender d
if this == nill or this.lives <= 0 or this.state == TEAM_STATE_DEAD then
return false
endif
//take the life
call .setLives(.lives-1)
//show messages
if this.lives <= 0 then
call showMessage(.getName() + " has been defeated.")
call .kill()
else
call showMessage(.getName() + " lost a life! (" + I2S(.lives) + " left)")
endif
return true
endmethod
///Show a message only to this team
public method showMessage takes string s returns nothing
//! runtextmacro when("this == nill", "return")
if IsPlayerInForce(GetLocalPlayer(), .memberForce) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, s)
endif
endmethod
///Get the next member set to recieve income (and the next on the call after that...)
public method getNextIncomeDefender takes nothing returns Defender
local integer i
if this == nill or .numMembers <= 0 or .state == TEAM_STATE_DEAD then
return nill
endif
//find next living member
set i = .incomeDivideIndex
loop
set i = ModuloInteger(i, .numMembers) + 1
if .members[i].state == DEFENDER_STATE_DEFENDING then
set .incomeDivideIndex = i
return .members[i]
endif
exitwhen i == .incomeDivideIndex //looped all the way 'round
endloop
//no living members
return nill
endmethod
///Starts the next round for this team
public method startNextRound takes nothing returns boolean
local integer i
local Round r
local SpawnBase s
local Defender d
//! runtextmacro when("this == nill or .state == TEAM_STATE_DEAD", "return false")
//start the next round
call TimerDialogDisplay(.roundTimerDialog, false)
if .minRound == .maxRound and .roundStates[.minRound] == ROUND_STATE_COMPLETED then
set .minRound = .minRound + 1
endif
set .maxRound = .maxRound + 1
set i = 1
set r = Round.create(this, SpawnSet.create(), .maxRound)
loop
exitwhen i > .numMembers
set d = .members[i]
set s = PowerTowersSpawn.createDefault(d, r.s, r.r)
call d.stream.spawnMultiple(s, SPAWNS_PER_ROUND, SPAWN_PERIOD)
call s.destroy()
set i = i + 1
endloop
set .state = TEAM_STATE_DEFENDING
set .roundStates[.maxRound] = ROUND_STATE_RUNNING
//alerts
call this.showMessage("Round " + I2S(.maxRound))
if isSpeedRound(.maxRound) then
call .showMessage("Runners have |cFFFF0000Speed|r")
endif
if isFeedbackRound(.maxRound) then
call .showMessage("Runners have |cFF80FF00Feedback|r")
endif
if isShieldRound(.maxRound) then
call .showMessage("Runners have |cFFFFCC00Divine Shield|r")
endif
if .maxRound > MAX_ROUNDS then
call .showMessage("Runner health is |cFFFF0080Increasing Exponentially|r")
endif
call UpdateMultiboard()
return true
endmethod
private static method delayedStartNextRound takes nothing returns nothing
local Team this = Team(delayedArg)
local integer newRound = delayedArg2
//don't double start rounds
//! runtextmacro when("newRound != .maxRound+1", "return")
call .startNextRound()
endmethod
public method startNextRoundCountdown takes nothing returns boolean
local real delay
//! runtextmacro when("this == nill or .state == TEAM_STATE_DEAD", "return false")
//! runtextmacro when(".maxRound >= Game.numRounds", "return false")
//! runtextmacro when("Game.numRounds == GAME_LENGTH_LAST_MAN and Team.countLivingTeams() <= 1", "return false")
if .state == TEAM_STATE_DONE_ROUND then
set .state = TEAM_STATE_WAITING
endif
if .maxRound == 0 then
set delay = INITIAL_WAIT_TIME
elseif Game.rushedRounds then
set delay = RUSHED_ROUND_WAIT_TIME
else
set delay = ROUND_WAIT_TIME
endif
call TimerStart(.roundTimer, delay, false, null)
call TimerDialogDisplay(.roundTimerDialog, IsPlayerInForce(GetLocalPlayer(), .memberForce))
call delayedCallWithArg2(function Team.delayedStartNextRound, delay, integer(this), .maxRound+1)
return true
endmethod
public method distributeGold takes integer gold returns nothing
local integer i = 1
local integer n = .countLivingMembers()
local integer dGold
//! runtextmacro when("this == nill or gold <= 0", "return")
loop
exitwhen i > .numMembers
if .members[i].state == DEFENDER_STATE_DEFENDING then
set dGold = gold/n
call AdjustPlayerStateBJ(dGold, .members[i].p, PLAYER_STATE_RESOURCE_GOLD)
call .members[i].showMessage("|cFFFFCC00+" + I2S(dGold) + " Gold|r")
set gold = gold - dGold
set n = n - 1
endif
set i = i + 1
endloop
endmethod
public method completeRound takes integer r returns nothing
local integer i
local integer roundTech = nill
local Defender d
if this == nill or .roundStates[r] != ROUND_STATE_RUNNING then
return
elseif .state != TEAM_STATE_DEFENDING and .state != TEAM_STATE_DEAD then
return
endif
//reward
if .state != TEAM_STATE_DEAD then
call .showMessage("Round " + I2S(r) + " Completed!")
if Game.income == INCOME_TYPE_WEIGHTED then
call .distributeGold(getRoundFinishBounty(r) * .numMembers)
else
call .distributeGold(getRoundFinishBounty(r) * .countLivingMembers())
endif
//tech
if r == 1 then
set roundTech = RESEARCH_ROUND_1
elseif r == 2 then
set roundTech = RESEARCH_ROUND_2
elseif r == 3 then
set roundTech = RESEARCH_ROUND_3
elseif r == 4 then
set roundTech = RESEARCH_ROUND_4
endif
set i = 1
loop
exitwhen i > .numMembers
if roundTech != 0 then
call SetPlayerTechResearched(.members[i].p, roundTech, 1)
endif
set i = i + 1
endloop
if Game.gameType == GAME_TYPE_RACE then
call showMessage(.getName() + " finished round " + I2S(r) + "!")
endif
endif
//team state
set .roundStates[r] = ROUND_STATE_COMPLETED
loop
exitwhen .minRound >= .maxRound
exitwhen .roundStates[.minRound] != ROUND_STATE_COMPLETED
set .minRound = .minRound + 1
endloop
if .minRound < .maxRound or .roundStates[.maxRound] != ROUND_STATE_COMPLETED then
return
endif
//The team has completed all spawned rounds so far
if .state == TEAM_STATE_DEFENDING then
set .state = TEAM_STATE_DONE_ROUND
endif
//Check for race winner
if Game.gameType == GAME_TYPE_RACE then
if .maxRound >= Game.numRounds then
set Team.racePositions = Team.racePositions + 1
call showMessage(.getName() + " finished in rank #" + I2S(Team.racePositions) + "!")
endif
endif
//Next round
if not Game.rushedRounds then
set i = 1
loop
exitwhen i > Team.numAllocated
if Team.teams[i].state == TEAM_STATE_DEFENDING then
return
endif
set i = i + 1
endloop
endif
if Game.syncedRounds then
set i = 1
loop
exitwhen i > Team.numAllocated
call Team.teams[i].startNextRoundCountdown()
set i = i + 1
endloop
else
call .startNextRoundCountdown()
endif
endmethod
private static method killDeadTeamRunners takes nothing returns nothing
local Runner r = Runner.fromUnit(GetEnumUnit())
if r != nill and r.owner.team.state == TEAM_STATE_DEAD then
call KillUnit(r.u)
endif
endmethod
public method kill takes nothing returns nothing
local integer i
//! runtextmacro when("this == nill or .state == TEAM_STATE_DEAD", "return")
set .state = TEAM_STATE_DEAD
//kill members
set i = 1
loop
exitwhen i > .numMembers
call .members[i].kill()
set i = i + 1
endloop
//kill runners
call SpawnLib_ForEach(function Team.killDeadTeamRunners)
endmethod
public method merge takes Team t returns boolean
local integer i
//! runtextmacro when("this == nill or t == nill", "return false")
if .state == TEAM_STATE_DEAD then
set .state = t.state
endif
set i = 1
loop
exitwhen i > t.numMembers
set .numMembers = .numMembers + 1
set .members[this.numMembers] = t.members[i]
call ForceAddPlayer(.memberForce, t.members[i].p)
set t.members[i].team = this
set t.members[i] = nill
set i = i + 1
endloop
set t.numMembers = 0
call t.destroy()
return true
endmethod
public static method create takes Defender d returns Team
local Team t
//! runtextmacro when("d == nill", "return nill")
//! runtextmacro CheckedAllocate("Team", "t")
//add defender
set t.numMembers = 1
set t.members[1] = d
call ForceAddPlayer(t.memberForce, d.p)
set d.team = t
if d.state == DEFENDER_STATE_MISSING then
set t.state = TEAM_STATE_DEAD
else
set t.state = TEAM_STATE_DONE_ROUND
endif
//create timer
set t.roundTimer = CreateTimer()
set t.roundTimerDialog = CreateTimerDialog(t.roundTimer)
call TimerDialogSetTitle(t.roundTimerDialog, "Next Round")
set t.roundStates[0] = ROUND_STATE_COMPLETED
//place in array
set Team.numAllocated = Team.numAllocated + 1
set Team.teams[Team.numAllocated] = t
set t.arrayIndex = Team.numAllocated
return t
endmethod
private method onDestroy takes nothing returns nothing
set Team.teams[.arrayIndex] = Team.teams[Team.numAllocated]
set Team.teams[Team.numAllocated].arrayIndex = .arrayIndex
set Team.teams[Team.numAllocated] = nill
set .arrayIndex = 0
set Team.numAllocated = Team.numAllocated - 1
call DestroyTimer(.roundTimer)
call DestroyTimerDialog(.roundTimerDialog)
set .roundTimer = null
set .roundTimerDialog = null
endmethod
endstruct
private function catchEmptiedRound takes nothing returns nothing
local Round rnd = Round.map[integer(SpawnLib_TriggeringSpawnSet)]
//! runtextmacro when("rnd == nill", "return")
call rnd.t.completeRound(rnd.r)
call rnd.destroy()
call Game.checkForEnd()
endfunction
private function initTeam takes nothing returns nothing
call SpawnLib_AddEmptiedSpawnSetCallBack(function catchEmptiedRound)
endfunction
endlibrary
//TESH.scrollpos=752
//TESH.alwaysfold=0
///This structure represents a power tower. It handles a ton of the tower
///stuff, including ability automation, some transfer calculations,
///selling, building, upgrading, you-name-it-ing.
///
///NOTES:
/// - Storage: keeps track of allocated structures
/// - Uniqueness: one structure per real tower
/// - Custom data: assumes unit custom value is not modified elsewhere
library LibTower initializer initTowers requires General, Constants, Special, LibDefender, LibTeam, LibTowerTransfer
globals
private constant integer TOWER_AUTO_NONE = 0
private constant integer TOWER_AUTO_INSTANT = 1
private constant integer TOWER_AUTO_UNIT = 2
private constant integer TOWER_AUTO_POINT = 3
private constant integer TOWER_AUTO_CUSTOM = 4
endglobals
struct Tower
//! runtextmacro CreateInitOnceMethod("Tower")
readonly static integer numAllocated = 0
readonly static Tower array towers
readonly integer arrayIndex
//buffered properties
readonly integer autoTarget = TOWER_AUTO_NONE
readonly integer maxEnergy = 0
readonly integer transferPower = 0
readonly integer baseProduction = 0
readonly integer estimatedDrain = 0
readonly integer estimatedProduction = 0
readonly location p = null
readonly integer ut = nill //unit type id
readonly integer at = nill //ability type id
readonly integer oid = nill //order id
readonly integer tintingRed = 255
readonly integer tintingBlue = 255
readonly integer tintingGreen = 255
readonly boolean isCombatTower = false
readonly boolean isGenerator = false
public unit relative = null
//description
readonly unit u = null
readonly integer level = 0
readonly boolean generating = false
readonly integer skillRecord = 0
//transfering
readonly integer numTransfersOut = 0
readonly integer numTransfersIn = 0
readonly TowerTransfer array transfersOut[MAX_TOWER_TRANSFERS]
readonly TowerTransfer array transfersIn[MAX_TOWER_TRANSFERS]
readonly integer lastEnergy = 0
private integer totalReceived = 0
///Returns the tower structure of a given unit, if it exists
public static method fromUnit takes unit u returns Tower
local integer i = GetUnitUserData(u)
if Tower(i).u == u and u != null then
return Tower(i)
endif
return nill
endmethod
///Change the position of a tower
public method move takes location p returns nothing
//! runtextmacro when("this == nill", "return")
//Cut all transfers to avoid over-reaching
loop
exitwhen this.numTransfersIn == 0
call.transfersIn[1].destroy()
endloop
loop
exitwhen this.numTransfersOut == 0
call .transfersOut[1].destroy()
endloop
//Move the tower
call createBangLoc(.p, "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl")
call createBangLoc(p, "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl")
call SetUnitPositionLoc(.u, p)
call RemoveLocation(.p)
set this.p = GetUnitLoc(.u)
endmethod
///Updates the properties stored about a tower (also sets the unit's ability level)
private method updateProperties takes nothing returns boolean
//! runtextmacro when("this == nill", "return false")
set .ut = GetUnitTypeId(.u)
set .maxEnergy = R2I(GetUnitState(.u, UNIT_STATE_MAX_MANA))
set .transferPower = GetUnitPointValue(.u)
if .isGenerator then
if isWaterWheel(.ut) then
set .baseProduction = 11
elseif isFurnace(.ut) then
set .baseProduction = 5
elseif isGraveyard(.ut) then
set .baseProduction = 5
else
set .baseProduction = 0
endif
set .baseProduction = .baseProduction*R2I(Pow(2,.level)-1)
if isGraveyard(.ut) then
set .estimatedProduction = .baseProduction*3
else
set .estimatedProduction = .baseProduction
endif
endif
if .isCombatTower then
set .estimatedDrain = .transferPower/2
endif
if .at != nill then
call SetUnitAbilityLevel(.u, .at, .level)
endif
return true
endmethod
///Increases the level of a tower
public method upgrade takes nothing returns nothing
//! runtextmacro when("this == nill", "return")
set .level = .level + 1
call .updateProperties()
endmethod
//////////
// Records or learns hero skills
// at = skill to record having learned
// skillRecord = record of skills to learn
//////////
public method recordSkillLearning takes integer at, integer skillRecord returns nothing
//! runtextmacro when("this == nill", "return")
//record
if at == ABIL_BLIZZARD then
set .skillRecord = .skillRecord + 16 //blizzard
elseif at == ABIL_STORM_BOLT then
set .skillRecord = .skillRecord + 4 //storm bolt
elseif at == ABIL_CRIT_STRIKE then
set .skillRecord = .skillRecord + 1 //crit strike
endif
//learn
loop
exitwhen skillRecord <= 0
if skillRecord >= 64 then //none
set at = nill
set skillRecord = skillRecord - 64
elseif skillRecord >= 16 then //blizzard
set at = ABIL_BLIZZARD
set skillRecord = skillRecord - 16
elseif skillRecord >= 4 then //storm bolt
set at = ABIL_STORM_BOLT
set skillRecord = skillRecord - 4
elseif skillRecord >= 1 then //crit strike
set at = ABIL_CRIT_STRIKE
set skillRecord = skillRecord - 1
endif
if at != nill then
call SelectHeroSkill(.u, at)
endif
endloop
endmethod
//=====================================
//=== PROPERTIES ======================
//=====================================
///Returns the current mana of the tower
public method getEnergy takes nothing returns integer
//! runtextmacro when("this == nill", "return 0")
return R2I(GetUnitState(.u, UNIT_STATE_MANA))
endmethod
///Returns the most mana this tower can receive before the next tick
public method getNeededEnergy takes nothing returns integer
//! runtextmacro when("this == nill", "return 0")
return IMinBJ(.maxEnergy-.getEnergy(), .transferPower-.totalReceived)
endmethod
//=====================================
//=== MUTATERS ========================
//=====================================
///Inserts a TowerTransfer into the correct transfer list
public method insertTransfer takes TowerTransfer tt returns boolean
local integer i
if tt == nill then
return false
endif
//add to out transfers if this is the source
if tt.src == this then
//check if already in list
set i = 1
loop
exitwhen i > this.numTransfersOut
if .transfersOut[i] == tt then
return false
endif
set i = i + 1
endloop
//add to list
set .numTransfersOut = .numTransfersOut + 1
set .transfersOut[.numTransfersOut] = tt
return true
endif
//add to in transfers if this is the destination
if tt.dst == this then
//check if already in list
set i = 1
loop
exitwhen i > this.numTransfersIn
if .transfersIn[i] == tt then
return false
endif
set i = i + 1
endloop
//add to list
set .numTransfersIn = .numTransfersIn + 1
set .transfersIn[.numTransfersIn] = tt
return true
endif
return false
endmethod
///Removes a TowerTransfer from the transfer list
public method removeTransfer takes TowerTransfer tt returns boolean
local integer i
if tt == nill then
return false
endif
//Remove from out-transfers if this is the source
if this == tt.src then
set i = 1
loop
exitwhen i > this.numTransfersOut
if .transfersOut[i] == tt then
set .transfersOut[i] = .transfersOut[.numTransfersOut]
set .transfersOut[.numTransfersOut] = nill
set .numTransfersOut = .numTransfersOut - 1
return true
endif
set i = i + 1
endloop
endif
//Remove from in-transfers if this is the destination
if this == tt.dst then
set i = 1
loop
exitwhen i > .numTransfersIn
if .transfersIn[i] == tt then
set .transfersIn[i] = .transfersIn[.numTransfersIn]
set .transfersIn[.numTransfersIn] = nill
set .numTransfersIn = .numTransfersIn - 1
return true
endif
set i = i + 1
endloop
endif
return false
endmethod
///Sets this tower's mana to the given value
public method setEnergy takes integer e returns nothing
//! runtextmacro when("this == nill", "return")
if e > .maxEnergy then
set e = this.maxEnergy
endif
call SetUnitState(.u, UNIT_STATE_MANA, e)
endmethod
///Increases this tower's mana by the given value
public method adjustEnergy takes integer de returns nothing
//! runtextmacro when("this == nill", "return")
call .setEnergy(.getEnergy() + de)
endmethod
///Replace this structure's unit with a unit of the given type
public method replaceUnit takes integer ut returns nothing
local player p
local boolean wasSelected
//! runtextmacro when("this == nill or ut == nill", "return")
//replace the tower without screwing player selection up
set p = GetOwningPlayer(.u)
set wasSelected = IsUnitSelected(.u, p)
set .u = ReplaceUnitBJ(.u, ut, bj_UNIT_STATE_METHOD_ABSOLUTE)
call SetUnitUserData(.u, integer(this))
call .setEnergy(.lastEnergy)
if wasSelected then
call SelectUnitAddForPlayer(.u, p)
endif
call .updateProperties()
endmethod
//=====================================
//=== FUNCTIONS =======================
//=====================================
///Transfer energy to other towers for this tick
///NOTE: Towers with lots of needed energy are placed towards the end of the
///destination list because any energy surplus due to capacitiy early in the list
///rolls-over to the later towers in the list.
public method transferEnergy takes nothing returns nothing
local integer i
local integer e
local integer de
local integer curMaxNeed
local integer n
local TowerTransfer tt
local Tower dst
//! runtextmacro when("this == nill", "return")
//do a single bubble-sort pass over the transfer list to favor needy towers
//guaranteed to place the neediest tower at the end of the list
if .numTransfersOut >= 2 then
set tt = .transfersOut[1]
set curMaxNeed = tt.dst.getNeededEnergy()
set i = 2
loop
exitwhen i > .numTransfersOut
set e = .transfersOut[i].dst.getNeededEnergy()
if e <= curMaxNeed then //>= instead of > stops a single tower from hogging an extra +1
set .transfersOut[i-1] = .transfersOut[i]
set .transfersOut[i] = tt
else
set tt = .transfersOut[i]
set curMaxNeed = e
endif
set i = i + 1
endloop
endif
//transfer energy out
//note: lastEnergy >= current energy because this tower hasn't transfered out yet this tick
set i = 1
set n = .numTransfersOut
set e = IMinBJ(.transferPower, .lastEnergy)
loop
exitwhen i > .numTransfersOut
set tt = .transfersOut[i]
set dst = tt.dst
//compute amount to send
set de = e/n
set de = IMinBJ(de, dst.maxEnergy - IMaxBJ(dst.getEnergy(), dst.lastEnergy)) //capacity of receiver
set de = IMinBJ(de, dst.transferPower - dst.totalReceived) //transfer capacity of receiver
//transfer
set dst.totalReceived = dst.totalReceived + de
call dst.adjustEnergy(de)
call .adjustEnergy(-de)
call tt.redraw(de)
set e = e - de
set n = n - 1
set i = i + 1
endloop
endmethod
///Displays information about state
public method draw takes nothing returns nothing
local real r
local Defender d
local integer de
local integer i
//! runtextmacro when("this == nill", "return")
//get change in mana and reset values
set de = .getEnergy() - .lastEnergy
set .lastEnergy = .getEnergy()
set .totalReceived = 0
//show floating text
if de > 0 then
call showUnitText(.u, "|cFF0000FF+" + I2S(de) + "|r")
elseif de < 0 then
call showUnitText(.u, "|cFFFF00FF" + I2S(de) + "|r")
elseif .lastEnergy == 0 and .numTransfersIn == 0 and .isCombatTower then
call showUnitText(.u, "|cFFFF0000No Power|r")
elseif .numTransfersOut == 0 and .lastEnergy >= .maxEnergy and .isGenerator then
call showUnitText(.u, "|cFFFF0000No Target|r")
endif
//darken/brighting the tower based on energy level
if .maxEnergy > 0 then
set r = I2R(.lastEnergy) / .maxEnergy
set r = r*0.75 + 0.25
call SetUnitVertexColor(.u, R2I(r*.tintingRed), R2I(r*.tintingGreen), R2I(r*.tintingBlue), 255)
endif
endmethod
//=====================================
//=== EVENTS ==========================
//=====================================
//////////
private static method catchStartBuild takes nothing returns nothing
local Tower t
call AntiWall_AddTower(GetTriggerUnit())
set t = Tower.create(GetTriggerUnit(), 1) //level 1
if t != nill and t.generating then
set t.generating = false
endif
endmethod
//////////
private static method catchUpgrade takes nothing returns nothing
local Tower t = Tower.fromUnit(GetTriggerUnit())
local integer ut = GetUnitTypeId(GetTriggerUnit())
if t == nill then
if isArrowTowerAny(ut) then //upgraded arrow tower can use power
set t = Tower.create(GetTriggerUnit(), 1)
else
return
endif
endif
call t.upgrade()
call IssueImmediateOrder(t.u, "stop") //avoid lock-up
endmethod
///Undoes mana scaling with max mana change
private static method catchMaxManaChange takes nothing returns nothing
local Tower t = Tower.fromUnit(GetTriggerUnit())
if t != nill then
call t.setEnergy(t.lastEnergy)
endif
endmethod
///Causes furnaces to burn nearby grass for fuel
private static method catchFurnaceTick takes nothing returns nothing
local Tower t
local integer i = 1
loop
exitwhen i > Tower.numAllocated
set t = Tower.towers[i]
if isFurnace(t.ut) then
set t.generating = burnNearbyGrass(t.p, FURNACE_GRASS_REACH)
if not t.generating then
call showUnitText(t.u, "|cFFFF0000No Grass|r")
endif
endif
set i = i + 1
endloop
endmethod
///Generators only start producing when they have finished building
private static method catchFinishBuild takes nothing returns nothing
local Tower t = Tower.fromUnit(GetTriggerUnit())
if t.isGenerator then
set t.generating = true
call SetPlayerTechResearched(GetOwningPlayer(t.u), RESEARCH_GENERATOR, 1)
elseif t.ut == UNIT_HERO_TOWER_FAKE then
call t.replaceUnit(UNIT_HERO_TOWER)
set t.relative = CreateUnitAtLoc(GetOwningPlayer(t.u), UNIT_HERO_TOWER_PATH, t.p, bj_UNIT_FACING)
endif
endmethod
///Handle tower deaths
private static method catchDeath takes nothing returns nothing
local unit u = GetTriggerUnit()
local unit w
local group g
local integer costReturned
local boolean hasNoDeathAnimation
local Tower t
local Defender d = Defender.fromUnit(u)
//refund cost
if d != nill and d.state == DEFENDER_STATE_DEFENDING then
set costReturned = R2I(GetUnitFoodMade(u) * SELL_PERCENTAGE)
if costReturned > 0 then
call SetPlayerState(d.p, PLAYER_STATE_RESOURCE_GOLD, costReturned + GetPlayerState(d.p,PLAYER_STATE_RESOURCE_GOLD))
call showUnitTextToPlayer(u, "|cFFFFCC00+" + I2S(costReturned) + "|r", d.p)
endif
endif
//Destroy tower
set t = Tower.fromUnit(u)
if t != nill then
//create death animation for towers with none
set hasNoDeathAnimation = false
if t.ut == 'h003' or t.ut == 'h007' or t.ut == 'h008' or t.ut == 'h03I' or t.ut == 'h03E' then
//bridging towers 1-3, tsunami 1, clock 3
set hasNoDeathAnimation = true
elseif isVineTrap(t.ut) or isPyroTrap(t.ut) then
set hasNoDeathAnimation = true
endif
if hasNoDeathAnimation then
call createBangLoc(t.p, "Abilities\\Weapons\\Mortar\\MortarMissile.mdl")
call RemoveUnit(t.u)
endif
call t.destroy()
endif
set u = null
endmethod
///Updates all towers by one second
private static method catchTick takes nothing returns nothing
local integer i
local Tower t
//generate
set i = 1
loop
exitwhen i > Tower.numAllocated
set t = Tower.towers[i]
if t.generating then
call t.adjustEnergy(t.baseProduction)
endif
set i = i + 1
endloop
//transfer energy
set i = 1
loop
exitwhen i > Tower.numAllocated
call Tower.towers[i].transferEnergy()
set i = i + 1
endloop
//draw
set i = 1
loop
exitwhen i > Tower.numAllocated
call Tower.towers[i].draw()
set i = i + 1
endloop
//finish
call UpdateMultiboard()
endmethod
private static method catchAttack takes nothing returns nothing
local Tower t = Tower.fromUnit(GetAttacker())
//! runtextmacro when("t == nill", "return")
if t.autoTarget == TOWER_AUTO_UNIT then
call IssueTargetOrderById(t.u, t.oid, GetTriggerUnit())
elseif t.autoTarget == TOWER_AUTO_INSTANT then
call IssueImmediateOrderById(t.u, t.oid)
elseif t.autoTarget == TOWER_AUTO_POINT then
call IssuePointOrderById(t.u, t.oid, GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()))
elseif t.autoTarget == TOWER_AUTO_CUSTOM then
if isRockLauncher(t.ut) then
if GetUnitAbilityLevel(t.u, BUFF_BOOSTED) <= 0 then
call scheduleInstantOrderId(t.u, t.oid, 0.40) //don't interrupt current attack
call scheduleInstantOrder(t.u, "stop", 0.45) //avoid lock-up
endif
elseif isPyroTrap(t.ut) then
call IssuePointOrderById(t.u, t.oid, GetUnitX(t.u), GetUnitY(t.u))
endif
endif
endmethod
//////////
private static method catchLearn takes nothing returns nothing
call Tower.fromUnit(GetTriggerUnit()).recordSkillLearning(GetLearnedSkill(), 0)
endmethod
///Creates a tower structure for a given unit
public static method create takes unit u, integer startingLevel returns Tower
local Tower this
if u == null then
return nill
elseif isArrowTowerBasic(GetUnitTypeId(u)) then
return nill //don't bother with arrow towers; they don't need it
elseif Tower.fromUnit(u) != nill then
return Tower.fromUnit(u)
elseif startingLevel < 1 then
call showError("Tried to create a Tower with a non-positive level.", "Tower.create")
return nill
endif
//pass values
//! runtextmacro CheckedAllocate("Tower", "this")
set .u = u
set .p = GetUnitLoc(u)
set .level = startingLevel
set .ut = GetUnitTypeId(u)
set .isGenerator = isWaterWheel(.ut) or isFurnace(.ut) or isGraveyard(.ut)
set .isCombatTower = not isBridgingTower(.ut) and not .isGenerator and .ut != UNIT_BARRACKS
if isRockLauncher(.ut) then
set .at = ABIL_BOOST
set .oid = OrderId("berserk")
elseif isChemicalTower(.ut) then
set .at = ABIL_CHEMICALS
set .oid = OrderId("shadowstrike")
elseif isDemonTower(this.ut) then
set .at = ABIL_FLAME
set .oid = nill
elseif isLichTower(.ut) then
set .at = ABIL_FROST
set .oid = OrderId("frostnova")
elseif isTeslaCoil(.ut) then
set .at = ABIL_LIGHTNING
set .oid = OrderId("chainlightning")
elseif isSwarmTower(.ut) then
set .at = ABIL_SPAWN
set .oid = OrderId("waterelemental")
elseif isVineTrap(.ut) then
set .at = ABIL_ENTANGLE
set .oid = OrderId("entanglingroots")
elseif isPyroTrap(.ut) then
set .at = ABIL_BLAZE
set .oid = OrderId("flamestrike")
elseif isDarkTower(.ut) then
set .at = ABIL_DESPAIR
set .oid = OrderId("innerfire")
elseif isHolyTower(.ut) then
set .at = ABIL_LIGHT_WAVE
set .oid = OrderId("carrionswarm")
elseif isClockTower(.ut) then
set .at = ABIL_TIME_DISTORTION
set .oid = nill
elseif isTsunamiTower(.ut) then
set .at = ABIL_WATER_BURST
set .oid = OrderId("thunderclap")
elseif isArrowTowerFire(.ut) then
set .at = ABIL_ELEMENTAL_FIRE
set .oid = OrderId("cripple")
elseif isArrowTowerNature(.ut) then
set .at = ABIL_ELEMENTAL_NATURE
set .oid = OrderId("cripple")
elseif isArrowTowerWater(.ut) then
set .at = ABIL_ELEMENTAL_WATER
set .oid = OrderId("cripple")
elseif isGraveyard(.ut) then
set .at = ABIL_CONSUME_RUNNER
set .oid = nill
elseif isLocustTower(.ut) then
set .at = ABIL_LOCUST_SWARM
set .oid = OrderId("locustswarm")
else
set .at = nill
set .oid = nill
endif
call .updateProperties()
set .generating = .isGenerator
//coloring
if isArrowTowerFire(.ut) then
set .tintingBlue = 25
set .tintingGreen = 25
elseif isArrowTowerWater(.ut) then
set .tintingRed = 25
set .tintingGreen = 25
elseif isArrowTowerNature(.ut) then
set .tintingRed = 25
set .tintingBlue = 25
endif
//ability automation
if isArrowTowerAny(.ut) or isTeslaCoil(.ut) or isLichTower(.ut) or isChemicalTower(.ut) or isVineTrap(.ut) or isDarkTower(.ut) then
set .autoTarget = TOWER_AUTO_UNIT
elseif isHolyTower(.ut) then
set .autoTarget = TOWER_AUTO_POINT
elseif isSwarmTower(.ut) or isTsunamiTower(.ut) or isLocustTower(.ut) then
set .autoTarget = TOWER_AUTO_INSTANT
elseif isRockLauncher(.ut) or isPyroTrap(.ut) then
set .autoTarget = TOWER_AUTO_CUSTOM
endif
//place in global array
set Tower.numAllocated = Tower.numAllocated + 1
set .arrayIndex = Tower.numAllocated
set Tower.towers[Tower.numAllocated] = this
call SetUnitUserData(u, integer(this))
call .draw()
return this
endmethod
///Cleans up properly
private method onDestroy takes nothing returns nothing
//! runtextmacro when("this == nill", "return")
//Remove relative unit
if .relative != null then
call RemoveUnit(.relative)
endif
//destroy transfers
loop
exitwhen .numTransfersIn <= 0
call .transfersIn[.numTransfersIn].destroy()
endloop
loop
exitwhen .numTransfersOut <= 0
call .transfersOut[.numTransfersOut].destroy()
endloop
//remove from global array
call SetUnitUserData(.u, 0)
set Tower.towers[.arrayIndex] = Tower.towers[Tower.numAllocated]
set Tower.towers[Tower.numAllocated].arrayIndex = .arrayIndex
set Tower.towers[Tower.numAllocated] = nill
//destroy other stuff
call RemoveLocation(.p)
set .p = null
set .u = null
set .relative = null
set Tower.numAllocated = Tower.numAllocated - 1
endmethod
///Initializes the structure. Registers the various events.
private static method init takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchDeath)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchTick)
call TriggerRegisterTimerEvent(t, 1.0, true)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchAttack)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchFinishBuild)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchStartBuild)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_START)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchUpgrade)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_FINISH)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_LEVEL)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchMaxManaChange)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_START)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_FINISH)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_CANCEL)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_LEVEL)
set t = CreateTrigger()
call TriggerAddAction(t, function Tower.catchLearn)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
set t = CreateTrigger()
call TriggerRegisterTimerEvent(t, FURNACE_FUEL_PERIOD, true)
call TriggerAddAction(t, function Tower.catchFurnaceTick)
endmethod
endstruct
private function delayedShowTransformationEffect takes nothing returns nothing
call createTimedAttachedEffect(I2Unit(delayedArg), "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl", "origin", 3.0)
endfunction
private function catchCast takes nothing returns boolean
local integer at = GetSpellAbilityId()
if at == ABIL_ADD_TRANSFER then
call TowerTransfer.create(Tower.fromUnit(GetTriggerUnit()), Tower.fromUnit(GetSpellTargetUnit()))
elseif at == ABIL_ADD_FAR_TRANSFER then
if Tower.fromUnit(GetTriggerUnit()).numTransfersOut >= 1 then
call showUnitTextToPlayer(Tower.fromUnit(GetTriggerUnit()).u, "|cFFFF0000Max Transfers Out|r", GetOwningPlayer(Tower.fromUnit(GetTriggerUnit()).u))
else
call TowerTransfer.create(Tower.fromUnit(GetTriggerUnit()), Tower.fromUnit(GetSpellTargetUnit()))
endif
elseif at == ABIL_REMOVE_TRANSFER then
call TowerTransfer.fromSrcDst(Tower.fromUnit(GetTriggerUnit()), Tower.fromUnit(GetSpellTargetUnit())).destroy()
elseif at == ABIL_ENTANGLE then
call Runner.fromUnit(GetSpellTargetUnit()).scheduleOrderToWaypoint(0.1+ENTANGLE_DURATION[GetUnitAbilityLevel(GetTriggerUnit(), at)])
elseif at == ABIL_TRANSFORM then
call delayedCallWithArg(function delayedShowTransformationEffect, 0., H2I(GetTriggerUnit()))
elseif at == ABIL_SELL then
//Delay needed for death animation and seller's remorse
call delayedCallWithArg(function delayedKillUnit, 0., H2I(GetTriggerUnit()))
elseif at == ABIL_SHOW_RANGES then
//Attack Range
call createCircleEffect(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), GetUnitDefaultAcquireRange(GetTriggerUnit()), "Doodads\\Cinematic\\GlowingRunes\\GlowingRunes0.mdl")
//Transfer Range
if GetUnitAbilityLevel(GetTriggerUnit(), ABIL_ADD_TRANSFER) > 0 then
call createCircleEffect(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), TRANSFER_RANGE, "Doodads\\Cinematic\\GlowingRunes\\GlowingRunes6.mdl")
endif
if GetUnitAbilityLevel(GetTriggerUnit(), ABIL_ADD_FAR_TRANSFER) > 0 then
call createCircleEffect(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), FAR_TRANSFER_RANGE, "Doodads\\Cinematic\\GlowingRunes\\GlowingRunes6.mdl")
endif
endif
return true
endfunction
private function initTowers takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function catchCast))
call Tower.initonce()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library LibAbilities initializer initAbilities requires Special
globals
private constant integer ELEMENT_FIRE = 0
private constant integer ELEMENT_WATER = 1
private constant integer ELEMENT_NATURE = 2
private constant integer NUM_ELEMENTS = 3
private constant integer ELEMENTAL_DAMAGE_BASE = 12 //update heroDur when changed
private real array WATER_BURST_DAMAGE
endglobals
private function catchElementCast takes nothing returns nothing
local integer at = GetSpellAbilityId()
local unit caster = GetTriggerUnit()
local unit target = GetSpellTargetUnit()
local integer casterElement
local integer targetElement
local integer level = GetUnitAbilityLevel(caster, at)
local string msg
local real dmg = ELEMENTAL_DAMAGE_BASE*(Pow(2,level)-1)
//Caster element
if at == ABIL_ELEMENTAL_FIRE then
set casterElement = ELEMENT_FIRE
set msg = "|cFFFF3333"
elseif at == ABIL_ELEMENTAL_WATER then
set casterElement = ELEMENT_WATER
set msg = "|cFF3333FF"
elseif at == ABIL_ELEMENTAL_NATURE then
set casterElement = ELEMENT_NATURE
set msg = "|cFF33FF33"
else
set caster = null
set target = null
set msg = null
return
endif
//Target element
if GetUnitAbilityLevel(target, BUFF_ELEMENTAL_FIRE) > 0 then
call UnitRemoveAbility(target, BUFF_ELEMENTAL_FIRE)
set targetElement = ELEMENT_FIRE
elseif GetUnitAbilityLevel(target, BUFF_ELEMENTAL_WATER) > 0 then
call UnitRemoveAbility(target, BUFF_ELEMENTAL_WATER)
set targetElement = ELEMENT_WATER
elseif GetUnitAbilityLevel(target, BUFF_ELEMENTAL_NATURE) > 0 then
call UnitRemoveAbility(target, BUFF_ELEMENTAL_NATURE)
set targetElement = ELEMENT_NATURE
else
set targetElement = casterElement
endif
//Multiplier
if targetElement != casterElement then
if ModuloInteger(targetElement+1, NUM_ELEMENTS) == casterElement then
set msg = msg + "DOUBLE " + I2S(R2I(dmg)) + "|r"
set dmg = dmg*2
else
set msg = msg + "HALF " + I2S(R2I(dmg)) + "|r"
set dmg = dmg/2
endif
else
set msg = msg + I2S(R2I(dmg)) + "|r"
endif
//Damage
call showUnitText(target, msg)
call UnitDamageTarget(caster, target, dmg, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
set caster = null
set target = null
set msg = null
endfunction
private function catchWaterBurstCast takes nothing returns nothing
local unit caster = GetTriggerUnit()
local real dmg
local unit target
local group g
local integer n
local location p
if GetSpellAbilityId() != ABIL_WATER_BURST then
set caster = null
return
endif
//Effects
call createBang(GetUnitX(caster)-100,GetUnitY(caster)-100, WATER_BURST_EFFECT_PATH)
call createBang(GetUnitX(caster)+100,GetUnitY(caster)- 90, WATER_BURST_EFFECT_PATH)
call createBang(GetUnitX(caster)-100,GetUnitY(caster)+100, WATER_BURST_EFFECT_PATH)
call createBang(GetUnitX(caster)+100,GetUnitY(caster)+100, WATER_BURST_EFFECT_PATH)
//Damage
set dmg = WATER_BURST_DAMAGE[GetUnitAbilityLevel(caster, ABIL_WATER_BURST)]
if dmg == 0 then
call showError("Damage not specified for water burst.", "catchWaterBurstCast")
endif
set p = GetUnitLoc(caster)
set g = GetUnitsInRangeOfLocMatching(WATER_BURST_RANGE, p, Condition(function Runner.filter_isRunner))
set n = CountUnitsInGroup(g)
if n > MAX_WATER_BURST_TARGETS then
set dmg = dmg*(MAX_WATER_BURST_TARGETS*1.0/n)
endif
loop
set target = FirstOfGroup(g)
exitwhen target == null
call GroupRemoveUnit(g, target)
call UnitDamageTarget(caster, target, dmg, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
endloop
call DestroyGroup(g)
call RemoveLocation(p)
set g = null
set p = null
set caster = null
endfunction
///Initializes abilities
private function initAbilities takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t, function catchWaterBurstCast)
call TriggerAddAction(t, function catchElementCast)
set WATER_BURST_DAMAGE[1] = 60.0
set WATER_BURST_DAMAGE[2] = 171.8
set WATER_BURST_DAMAGE[3] = 385.0
set WATER_BURST_DAMAGE[4] = 796.2
set WATER_BURST_DAMAGE[5] = 1594.3
set WATER_BURST_DAMAGE[6] = 3150.0
endfunction
endlibrary
//TESH.scrollpos=60
//TESH.alwaysfold=0
///This structure represents the energy transfers between pairs of towers.
///It does most of the energy transfer (towers are responsible for their
///transfer limit, not this structure) and handles all the lightning effects.
library LibTowerTransfer requires General
struct TowerTransfer
readonly static integer numAllocated = 0
readonly Tower src = nill
readonly Tower dst = nill
readonly lightning beam = null
readonly integer lastBeamLevel = 0
///Finds the transfer from src to dst, if it exists
public static method fromSrcDst takes Tower src, Tower dst returns TowerTransfer
local integer i
//! runtextmacro when("src == nill or dst == nill", "return nill")
//find in src's transferOuts (ignore dst's transferIns)
set i = 1
loop
exitwhen i > src.numTransfersOut
if src.transfersOut[i].dst == dst then
return src.transfersOut[i]
endif
set i = i + 1
endloop
return nill
endmethod
///Changes the lightning effect to reflect last energy transfered
public method redraw takes integer dEnergy returns nothing
local integer newBeamLevel
//decide which beam level to use
if dEnergy <= 0 then
set newBeamLevel = 0
elseif dEnergy < 25 then
set newBeamLevel = 1
elseif dEnergy < 125 then
set newBeamLevel = 2
elseif dEnergy < 625 then
set newBeamLevel = 3
else
set newBeamLevel = 4
endif
//replace the lightning effect
if newBeamLevel != this.lastBeamLevel then
call DestroyLightning(.beam)
set .lastBeamLevel = newBeamLevel
if newBeamLevel == 0 then
set .beam = AddLightningLoc("LEAS", .src.p, .dst.p) //magic leash
elseif newBeamLevel == 1 then
set .beam = AddLightningLoc("DRAL", .dst.p, .src.p) //drain life
elseif newBeamLevel == 2 then
set .beam = AddLightningLoc("DRAM", .dst.p, .src.p) //drain mana
elseif newBeamLevel == 3 then
set .beam = AddLightningLoc("HWSB", .src.p, .dst.p) //healing secondary
else
set .beam = AddLightningLoc("SPLK", .src.p, .dst.p) //spirit link
endif
endif
endmethod
///Creates a transfer between two towers
public static method create takes Tower src, Tower dst returns TowerTransfer
local TowerTransfer tt
//check for common failures
if src == nill then
return nill
elseif dst == nill or dst == src then
call showUnitTextToPlayer(src.u, "|cFFFF0000Invalid Target|r", GetOwningPlayer(src.u))
return nill
elseif src.numTransfersOut >= MAX_TOWER_TRANSFERS then
call showUnitTextToPlayer(src.u, "|cFFFF0000Max Transfers Out|r", GetOwningPlayer(src.u))
return nill
elseif dst.numTransfersIn >= MAX_TOWER_TRANSFERS then
call showUnitTextToPlayer(dst.u, "|cFFFF0000Max Transfers In|r", GetOwningPlayer(src.u))
return nill
elseif TowerTransfer.fromSrcDst(src, dst) != nill then
call showUnitTextToPlayer(dst.u, "|cFFFFCC00Already Transfering|r", GetOwningPlayer(src.u))
return TowerTransfer.fromSrcDst(src, dst)
endif
//! runtextmacro CheckedAllocate("TowerTransfer", "tt")
set tt.src = src
set tt.dst = dst
set tt.beam = null
set tt.lastBeamLevel = -1
//insert tt into src and dst
if not src.insertTransfer(tt) or not dst.insertTransfer(tt) then
call showError("Couldn't insert transfer between towers.", "TowerTransfer.create")
call tt.destroy()
return nill
endif
//output
call SetSoundPosition(gg_snd_PowerTransfer, GetLocationX(dst.p), GetLocationY(dst.p), 0)
call StartSound(gg_snd_PowerTransfer)
call tt.redraw(1)
set TowerTransfer.numAllocated = TowerTransfer.numAllocated + 1
return tt
endmethod
///Cleans up properly
private method onDestroy takes nothing returns nothing
//Remove reference from endpoints
call .src.removeTransfer(this)
call .dst.removeTransfer(this)
//Remove lightning effect
call DestroyLightning(.beam)
set .beam = null
set TowerTransfer.numAllocated = TowerTransfer.numAllocated - 1
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///This structure represents the runners trying to reach the center. It
///handles most of the pathing (Path handles the waypoints), as well as
///the various runner abilities, bounties, and taking lives.
///
///NOTES:
/// - Uniqueness: one unit can't have two Runner structures
/// - Storage: keeps track of allocated structures
/// - Custom data: assumes unit custom value is not modified elsewhere
library LibRunners initializer initRunners requires General, Constants, LibTower, Special, LibDefender, LibTeam, AntiWallLib, DelayedCallLib, SpawnLib
globals
private SpreadUnitEvent feedbackDamageEvent
private SpreadUnitEvent shieldDamageEvent
private integer numFeedbackEffects = 0
private constant integer MAX_FEEDBACK_EFFECTS = 100
private constant real FEEDBACK_EFFECT_DURATION = 0.2
endglobals
struct PowerTowersSpawn extends SpawnBase
readonly static integer numAllocated = 0
readonly Defender d
readonly SpawnSet s
readonly integer ut
readonly integer maxHealth
readonly integer bounty
readonly boolean hasSpeed
readonly boolean hasFeedback
readonly boolean hasShield
public static method create takes Defender d, SpawnSet ss, integer ut, integer maxHealth, integer bounty, boolean hasSpeed, boolean hasFeedback, boolean hasShield returns PowerTowersSpawn
local PowerTowersSpawn s
//! runtextmacro when("ut == nill or bounty < 0 or maxHealth <= 0", "return nill")
//! runtextmacro CheckedAllocate("PowerTowersSpawn", "s")
set s.d = d
set s.ut = ut
set s.maxHealth = maxHealth
set s.bounty = bounty
set s.hasSpeed = hasSpeed
set s.hasFeedback = hasFeedback
set s.hasShield = hasShield
set PowerTowersSpawn.numAllocated = PowerTowersSpawn.numAllocated + 1
set s.s = ss
return s
endmethod
private method onDestroy takes nothing returns nothing
set PowerTowersSpawn.numAllocated = PowerTowersSpawn.numAllocated - 1
endmethod
public static method createDefault takes Defender d, SpawnSet ss, integer r returns PowerTowersSpawn
return PowerTowersSpawn.create(d, ss, getRoundRunnerUnitType(r), getRoundRunnerHealth(r, Game.difficulty), getRoundRunnerBounty(r), isSpeedRound(r), isFeedbackRound(r), isShieldRound(r))
endmethod
public method clone takes nothing returns SpawnBase
//! runtextmacro when("this == nill", "return nill")
return PowerTowersSpawn.create(.d, .s, .ut, .maxHealth, .bounty, .hasSpeed, .hasFeedback, .hasShield)
endmethod
public method spawn takes nothing returns unit
local Runner r
//! runtextmacro when("this == nill", "return null")
set r = Runner.create(this)
//! runtextmacro when("r == nill", "return null")
return r.u
endmethod
public method getSpawnSet takes nothing returns SpawnSet
//! runtextmacro when("this == nill", "return nill")
return .s
endmethod
endstruct
struct Runner
//! runtextmacro CreateInitOnceMethod("Runner")
readonly static integer numAllocated = 0
readonly unit u = null
readonly Defender owner = nill
readonly integer confusedCount = 0
readonly boolean hasFeedback = false
readonly boolean hasShield = false
readonly boolean hasSpeed = false
readonly integer bounty = 0
readonly player sender
///Returns the runner structure representing a given unit
public static method fromUnit takes unit u returns Runner
local integer i = GetUnitUserData(u)
if Runner(i).u == u and u != null then
return Runner(i)
endif
return nill
endmethod
//=====================================
//=== FUNCTIONS =======================
//=====================================
///Returns true if the matching unit is a runner
public static method filter_isRunner takes nothing returns boolean
return Runner.fromUnit(GetFilterUnit()) != nill
endmethod
///Orders the runner to move to its next waypoint
public method orderToWaypoint takes nothing returns nothing
//! runtextmacro when("this == nill", "return")
call Path_OrderUnit(.u)
endmethod
///Orders the runner to move to its next waypoint
private static method delayedOrderToWaypoint takes nothing returns nothing
local Runner r = Runner(delayedArg)
call r.orderToWaypoint()
endmethod
public method scheduleOrderToWaypoint takes real delay returns nothing
call delayedCallWithArg(function Runner.delayedOrderToWaypoint, delay, integer(this))
endmethod
///Moves the runner increasingly longer distances to get around walls
private static method catchWalled takes nothing returns nothing
local Runner this = Runner.fromUnit(AntiWall_WalledUnit)
local real d
local real x1
local real y1
local real x2
local real y2
local real dx
local real dy
local location p
local real range = 0
local Team t
local string msg
if this == nill then
call showError("Caught walled unit that wasn't a runner.", "Runner.catchWalled")
return
endif
if AntiWall_WalledReason == ANTI_WALL_REASON_PREVENT then
//probably not on purpose
call .orderToWaypoint()
return
endif
//message
set msg = "|cFFFF0000Warning: A runner was "
if AntiWall_WalledReason == ANTI_WALL_REASON_BACK_TRACKED then
set msg = msg + "caught|r |cFFFFCC00backtracking|r"
elseif AntiWall_WalledReason == ANTI_WALL_REASON_STOPPED then
set msg = msg + "|r|cFFFFCC00stopped|r"
elseif AntiWall_WalledReason == ANTI_WALL_REASON_ATTACKING then
set msg = msg + "caught|r |cFFFFCC00attacking|r"
elseif AntiWall_WalledReason == ANTI_WALL_REASON_STUCK then
set msg = msg + "|r|cFFFFCC00stuck|r"
else
set msg = msg + "|r|cFFFFCC00confused|r"
endif
//range (and more message)
set .confusedCount = .confusedCount + 1
set t = .owner.team
if .confusedCount <= 1 then
set msg = msg + " |cFFFF8000(short jump)|r"
set range = 100
elseif .confusedCount == 2 then
set msg = msg + " |cFFFF8000(medium jump)|r"
set range = 750
elseif .confusedCount >= 3 then
set msg = msg + " |cFFFF8000(long jump)|r"
set range = 50000
endif
//get waypoint target
set x1 = GetUnitX(.u)
set y1 = GetUnitY(.u)
set p = Path_GetUnitTargetLoc(.u)
set dx = GetLocationX(p) - x1
set dy = GetLocationY(p) - y1
call RemoveLocation(p)
set p = null
//get blink target (don't blink too far or into water)
set d = RMaxBJ(SquareRoot(dx*dx + dy*dy), 1)
set range = RMinBJ(range, d)
loop
//continue when the point is walkable and not in water (remember IsTerrainPathable has backwards logic)
set x2 = x1 + range*dx/d
set y2 = y1 + range*dy/d
exitwhen IsTerrainPathable(x2, y2, PATHING_TYPE_WALKABILITY) != true and IsTerrainPathable(x2, y2, PATHING_TYPE_FLOATABILITY) != false
//increase range to avoid bad points
set range = range + 100
endloop
//blink to destination
call t.showMessage(msg)
call SetUnitPosition(.u, x2, y2)
set x2 = GetUnitX(.u)
set y2 = GetUnitY(.u)
//effects
call scheduleDestroyLightning(AddLightning("AFOD", true, x1, y1, x2, y2), 0.5) //finger of death
call createBang(x1, y1, "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl")
call createBang(x2, y2, "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl")
if IsPlayerInForce(GetLocalPlayer(), t.memberForce) then
call PingMinimapEx(x1, y1, 2.0, 255, 0, 0, false)
call PingMinimapEx(x2, y2, 3.0, 255, 128, 0, false)
endif
//continue on your way
call .orderToWaypoint()
call .scheduleOrderToWaypoint(0)
endmethod
///Makes sure runners don't stop after casting divine shield
private static method catchCasting takes nothing returns nothing
if GetSpellAbilityId() == ABIL_RUNNER_SHIELD then
call Runner.fromUnit(GetTriggerUnit()).orderToWaypoint()
endif
endmethod
private static method catchWaypoint takes nothing returns nothing
call AntiWall_ResetUnit(Path_TriggeringUnit)
endmethod
private static method catchFinished takes nothing returns nothing
local Runner r = Runner.fromUnit(Path_TriggeringUnit)
//! runtextmacro when("r == nill", "return")
//Punish the owner
if r.owner.team.maxRound <= 1 then
//Only display warnings on first round
call showMessage(r.owner.team.getName() + " let a runner through! (Misses are forgiven during the first round)")
else
//Take Life
call r.owner.team.loseLife()
endif
//Gibblets!
call SetUnitExploded(r.u, true)
call KillUnit(r.u)
endmethod
///Gives bounty for killing runners and destroys runners
private static method catchDeath takes nothing returns nothing
local Runner r = Runner.fromUnit(GetTriggerUnit())
local unit killer
local Defender d
//! runtextmacro when("r == nill", "return")
//Credit the killing unit
set killer = GetKillingUnit()
call showUnitText(killer, "|cFFFF8000Kill!|r")
//Choose who gets the bounty
if killer == null then
//no income
set d = nill
elseif Game.income == INCOME_TYPE_STANDARD then
set d = Defender.fromUnit(killer)
elseif Game.income == INCOME_TYPE_FAIR or Game.income == INCOME_TYPE_WEIGHTED then
set d = r.owner
if d.state != DEFENDER_STATE_DEFENDING then
set d = d.team.getNextIncomeDefender()
endif
if d == nill then
call showError("No fair income owner found.", "Runner.catchDeath)")
endif
else
set d = nill
call showError("Unrecognized income type.", "Runner.catchDeath)")
endif
//Give Bounty
if d != nill and d.state == DEFENDER_STATE_DEFENDING and r.bounty > 0 then
call showUnitTextToPlayer(r.u, "|cFFFFCC00+" + I2S(r.bounty) + "|r", d.p)
call AdjustPlayerStateBJ(r.bounty, d.p, PLAYER_STATE_RESOURCE_GOLD)
endif
call r.destroy()
call UpdateMultiboard()
set killer = null
endmethod
///Spawns a runner
public static method create takes PowerTowersSpawn s returns Runner
local Runner r
//! runtextmacro when("s == nill", "return nill")
//! runtextmacro when("s.d.team.state == TEAM_STATE_DEAD", "return nill")
//! runtextmacro CheckedAllocate("Runner", "r")
//spawn the runner
set r.u = CreateUnit(RUNNERS_OWNER, s.ut, s.d.path.getWaypointX(1), s.d.path.getWaypointY(1), bj_UNIT_FACING)
call SetUnitColor(r.u, GetPlayerColor(s.d.p))
set r.owner = s.d
set r.bounty = s.bounty
call setUnitMaxHealth(r.u, s.maxHealth)
call RemoveGuardPosition(r.u)
set Runner.numAllocated = Runner.numAllocated + 1
call SetUnitUserData(r.u, integer(r))
//Give abilities
if s.hasSpeed then
set r.hasSpeed = true
call UnitAddAbility(r.u, ABIL_RUNNER_SPEED) //speed boost
call IssueImmediateOrder(r.u, "berserk") //activate it
endif
if s.hasFeedback then
set r.hasFeedback = true
call UnitAddAbility(r.u, ABIL_RUNNER_FEEDBACK)
call IssueImmediateOrder(r.u, "immolation") //activate it
call feedbackDamageEvent.addUnit(r.u)
endif
if s.hasShield then
set r.hasShield = true
call UnitAddAbility(r.u, ABIL_RUNNER_SHIELD)
call shieldDamageEvent.addUnit(r.u)
endif
//send the runner on its way
call AntiWall_AddUnit(r.u)
call r.owner.path.addUnit(r.u)
call r.orderToWaypoint()
return r
endmethod
///Cleans up the Runner
private method onDestroy takes nothing returns nothing
call AntiWall_RemoveUnit(.u)
call Path_RemoveUnit(.u)
call SpawnLib_RemoveUnit(.u)
call SetUnitUserData(.u, 0)
set .u = null
set Runner.numAllocated = Runner.numAllocated - 1
endmethod
///Initializes the Runner structure
private static method init takes nothing returns nothing
local trigger t
call Path_AddWaypointCallBack(function Runner.catchWaypoint)
call Path_AddFinishedCallBack(function Runner.catchFinished)
call AntiWall_AddCallBack(function Runner.catchWalled)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(t, function Runner.catchDeath)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t, function Runner.catchCasting)
endmethod
endstruct
private function delayedDestroyFeedbackEffect takes nothing returns nothing
call delayedDestroyLightning() //delayedArg tells which to destroy
set numFeedbackEffects = numFeedbackEffects - 1
endfunction
private function catchDamageFeedback takes nothing returns nothing
local Runner r = Runner.fromUnit(GetTriggerUnit())
local Tower t = Tower.fromUnit(GetEventDamageSource())
local integer dmgMana = R2I(GetEventDamage()/10)
local location p
local lightning ltng
//! runtextmacro when("t == nill or dmgMana <= 0 or t.getEnergy() <= 0", "return")
//burn energy
call t.adjustEnergy(-dmgMana)
call showUnitText(t.u, "|cFF80FF00" + I2S(-dmgMana) + "|r")
//show lightning effects
if numFeedbackEffects < MAX_FEEDBACK_EFFECTS then
set p = GetUnitLoc(r.u)
set ltng = AddLightningLoc("MFPB", p, t.p)
set numFeedbackEffects = numFeedbackEffects + 1
call SetSoundPosition(gg_snd_Feedback, GetLocationX(p), GetLocationY(p), 0)
call StartSound(gg_snd_Feedback)
call delayedCallWithArg(function delayedDestroyFeedbackEffect, FEEDBACK_EFFECT_DURATION, H2I(ltng))
call RemoveLocation(p)
set ltng = null
set p = null
endif
endfunction
private function catchDamageShield takes nothing returns nothing
call IssueImmediateOrder(GetTriggerUnit(), "divineshield")
endfunction
private function initRunners takes nothing returns nothing
set feedbackDamageEvent = SpreadUnitEvent.create(EVENT_UNIT_DAMAGED)
set shieldDamageEvent = SpreadUnitEvent.create(EVENT_UNIT_DAMAGED)
call feedbackDamageEvent.register(function catchDamageFeedback)
call shieldDamageEvent.register(function catchDamageShield)
call Runner.initonce()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Implements a democratic -boot command. If more than half of the players
///vote to boot a player, that player is split from the game. Votes
///expire after awhile, to allow forgiveness. -kick is also caught.
library libCommandBoot initializer initBoot requires General, Constants, LibDefender
globals
private integer array defendersVoteCount
private boolean array defendersVoteMatrix
private constant real VOTE_DURATION = 60.0 //seconds
endglobals
///Returns true if 'voter' has a vote to boot 'target'
private function getCurVote takes player voter, player target returns boolean
//! runtextmacro when("voter == null or target == null", "return false")
return defendersVoteMatrix[GetPlayerId(voter)*NUM_DEFENDERS + GetPlayerId(target)]
endfunction
///Returns the number of votes to boot 'target'
private function getVoteCount takes player target returns integer
//! runtextmacro when("target == null", "return 0")
return defendersVoteCount[GetPlayerId(target)]
endfunction
///Returns true if there are enough votes to boot 'target'
private function isBootable takes player target returns boolean
//! runtextmacro when("target == null", "return false")
if GetPlayerController(target) == MAP_CONTROL_COMPUTER then
return true //computers can be booted at anytime no matter what
endif
//More than half of defenders must want to boot the target
return getVoteCount(target) > Defender.countLiveDefenders() / 2
endfunction
///Sets voter's vote to boot target; maintains vote counts
private function setCurVote takes player voter, player target, boolean b returns boolean
//! runtextmacro when("voter == null or target == null", "return false")
if getCurVote(voter, target) != b then
//set vote
set defendersVoteMatrix[GetPlayerId(voter)*NUM_DEFENDERS + GetPlayerId(target)] = b
//keep count
if b then
set defendersVoteCount[GetPlayerId(target)] = defendersVoteCount[GetPlayerId(target)] + 1
else
set defendersVoteCount[GetPlayerId(target)] = defendersVoteCount[GetPlayerId(target)] + 1
endif
endif
return true
endfunction
///Expires a vote
private function delayedExpireVote takes nothing returns nothing
local player voter = I2Player(delayedArg)
local player target = I2Player(delayedArg2)
if GetPlayerSlotState(target) != PLAYER_SLOT_STATE_PLAYING then
return
endif
call showMessage(getPlayerColoredName(voter) + "'s vote to boot " + getPlayerColoredName(target) + " has expired.")
call setCurVote(voter, target, false)
endfunction
///Records a vote by 'voter' to boot 'target'
private function vote takes Defender voter, Defender target returns nothing
//! runtextmacro when("voter == nill or target == nill", "return")
//! runtextmacro when("voter == target or target.state == DEFENDER_STATE_MISSING", "return")
if voter.state != DEFENDER_STATE_DEFENDING then
call showPlayerMessage(voter.p, "You can't -boot after you've been killed.")
return
elseif getCurVote(voter.p, target.p) then
call showPlayerMessage(voter.p, "You already have a vote to boot " + target.getNameWithColor() + ".")
return
endif
//Place the Vote
call setCurVote(voter.p, target.p, true)
call showMessage(voter.getNameWithColor() + " has voted to boot " + target.getNameWithColor() + ". (" + I2S(getVoteCount(target.p)) + " votes)")
//Check for majority
if isBootable(target.p) then
//Boot the player by causing a split
call showMessage(target.getNameWithColor() + " has been split into a separate game.")
call target.showMessage("|cFFFF0000You have been split into a separate game.|r")
if target.p != GetLocalPlayer() then
call target.kill()
endif
else
//Schedule the vote's expiration
call delayedCallWithArg2(function delayedExpireVote, VOTE_DURATION, H2I(voter.p), H2I(target.p))
endif
endfunction
///Catches -boot and -kick commands.
private function catchChat takes nothing returns nothing
local string s = GetEventPlayerChatString()
set s = StringCase(s, false)
if SubString(s, 0, 6) == "-boot " or SubString(s, 0, 6) == "-kick " then
set s = SubString(s, 6, StringLength(s))
call vote(Defender.fromPlayer(GetTriggerPlayer()), Defender.fromString(s))
endif
endfunction
///Initializes the boot command
private function initBoot takes nothing returns nothing
local integer i = 0
local trigger t = CreateTrigger()
loop
exitwhen i >= 12
call TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
set i = i + 1
endloop
call TriggerAddAction(t, function catchChat)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Implements the -block player command, which is used to keep other players out of your area.
///Your area is defined as the set of points at least 5% closer to your start location than
///any other living player. Using -block will sell all the target player's towers in your area.
///The block persists until -unblock is used.
library BlockCommandLib initializer initBlock requires General, Constants, LibDefender
globals
private boolean array blockMatrix
private constant real beatOffset = 512
endglobals
///Returns true if the guard player can block the challenger at the given location
///A guard may block a challenger when:
///- The location is in the guard's private area
///- The location is in the guard's public area, but not in the challenger's public area
///This is a very crude approximation, but it works
function CanPlayerBlockAreaFrom takes player guard, real x, real y, player challenger returns boolean
local player p = RUNNERS_OWNER //make the center common ground
local real dx = GetPlayerStartLocationX(p)-x
local real dy = GetPlayerStartLocationY(p)-y
local real minDistance = SquareRoot(dx*dx + dy*dy)*0.6
local real minDistance2 = minDistance
local real privateDistance = 0
local real publicDistance = 0
local real guardDistance = 0
local real challengerDistance = 0
local real curDistance
local integer i
if guard == null or challenger == null or guard == challenger then
return false
endif
//Get the required distances
set i = 1
loop
exitwhen i > NUM_DEFENDERS
if defenders[i].state == DEFENDER_STATE_DEFENDING then
//get player distance
set p = defenders[i].p
set dx = GetPlayerStartLocationX(p)-x
set dy = GetPlayerStartLocationY(p)-y
set curDistance = SquareRoot(dx*dx + dy*dy)
if curDistance < minDistance then
set minDistance2 = minDistance
set minDistance = curDistance
elseif curDistance < minDistance2 then
set minDistance2 = curDistance
endif
if guard == p then
set guardDistance = curDistance
elseif challenger == p then
set challengerDistance = curDistance
endif
endif
set i = i + 1
endloop
set privateDistance = minDistance2-beatOffset
set publicDistance = minDistance+beatOffset
//Check for priority
if guardDistance <= privateDistance then
return true
elseif guardDistance <= publicDistance and challengerDistance > publicDistance-beatOffset/2 then
return true
endif
return false
endfunction
function BlockUnit takes unit u returns nothing
local Defender d = Defender.fromUnit(u)
local Tower t
local location p
if not IsUnitInGroup(u, d.specialUnits) then
call KillUnit(u)
else
set p = GetPlayerStartLocationLoc(d.p)
set t = Tower.fromUnit(u)
if t != nill then
call t.move(p)
else
call SetUnitPositionLoc(u, p)
endif
call RemoveLocation(p)
set p = null
endif
endfunction
private function matrixIndex takes player blocker, player target returns integer
//! runtextmacro when("blocker == null or target == null", "return 0")
return GetPlayerId(blocker)*NUM_DEFENDERS + GetPlayerId(target)
endfunction
function IsPlayerBlockingPlayer takes player blocker, player target returns boolean
//! runtextmacro when("blocker == null or target == null", "return false")
//! runtextmacro when("Defender.fromPlayer(blocker).state != DEFENDER_STATE_DEFENDING", "return false")
//! runtextmacro when("Defender.fromPlayer(target).state != DEFENDER_STATE_DEFENDING", "return false")
return blockMatrix[matrixIndex(blocker, target)]
endfunction
private function SetPlayerBlockingPlayer takes player blocker, player target , boolean b returns boolean
//! runtextmacro when("blocker == null or target == null", "return false")
set blockMatrix[matrixIndex(blocker, target)] = b
return true
endfunction
private function unblock takes Defender blocker, Defender target returns nothing
//! runtextmacro when("blocker == nill or target == nill", "return")
//! runtextmacro when("blocker == target or target.state != DEFENDER_STATE_DEFENDING", "return")
if blocker.state != DEFENDER_STATE_DEFENDING then
return
elseif not IsPlayerBlockingPlayer(blocker.p, target.p) then
call blocker.showMessage("You're already not blocking " + target.getNameWithColor())
return
endif
//unblock
call SetPlayerBlockingPlayer(blocker.p, target.p, false)
call blocker.showMessage("You unblocked " + target.getNameWithColor())
call target.showMessage(blocker.getNameWithColor() + " has unblocked you.")
endfunction
private function block takes Defender blocker, Defender target returns nothing
local group g
local unit u
//! runtextmacro when("blocker == nill or target == nill", "return")
//! runtextmacro when("blocker == target or target.state != DEFENDER_STATE_DEFENDING", "return")
if Game.state == GAME_STATE_INTRO then
call blocker.showMessage("You can't -block before the settings are picked.")
return
elseif blocker.state != DEFENDER_STATE_DEFENDING then
call blocker.showMessage("You can't -block after you've been killed.")
return
elseif blocker.team == target.team then
call blocker.showMessage("You can't -block your own team.")
return
elseif IsPlayerBlockingPlayer(blocker.p, target.p) then
call blocker.showMessage("You're already blocking " + target.getNameWithColor())
return
endif
//block
call SetPlayerBlockingPlayer(blocker.p, target.p, true)
call blocker.showMessage("You blocked " + target.getNameWithColor() + " from building in your area.")
call target.showMessage(blocker.getNameWithColor() + " has blocked you from building in their area.")
//block all offending units
set g = GetUnitsOfPlayerAll(target.p)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if CanPlayerBlockAreaFrom(blocker.p, GetUnitX(u), GetUnitY(u), target.p) then
call BlockUnit(u)
endif
endloop
call DestroyGroup(g)
set g = null
endfunction
private function catchChat takes nothing returns nothing
local string s = GetEventPlayerChatString()
set s = StringCase(s, false)
if SubString(s, 0, 7) == "-block " then
set s = SubString(s, 7, StringLength(s))
call block(Defender.fromPlayer(GetTriggerPlayer()), Defender.fromString(s))
elseif SubString(s, 0, 9) == "-unblock " then
set s = SubString(s, 9, StringLength(s))
call unblock(Defender.fromPlayer(GetTriggerPlayer()), Defender.fromString(s))
endif
endfunction
function GetPossibleBlocker takes unit u, boolean requireBlockingActivated returns Defender
local integer i
local player p = GetOwningPlayer(u)
local player q
set i = 1
loop
exitwhen i > NUM_DEFENDERS
set q = defenders[i].p
if CanPlayerBlockAreaFrom(q, GetUnitX(u), GetUnitY(u), p) then
if IsPlayerBlockingPlayer(q, p) or not requireBlockingActivated then
return defenders[i]
endif
endif
set i = i + 1
endloop
return nill
endfunction
function tryBlockUnit takes unit u returns nothing
local Defender target = Defender.fromUnit(u)
local Defender guard = GetPossibleBlocker(u, true)
if guard != nill then
call BlockUnit(u)
call target.showMessage(guard.getNameWithColor() + " is blocking you from that area.")
endif
endfunction
private function catchEnter takes nothing returns nothing
call tryBlockUnit(GetTriggerUnit())
endfunction
///Initializes the block command
private function initBlock takes nothing returns nothing
local integer i = 0
local trigger t = CreateTrigger()
loop
exitwhen i >= 12
call TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
set i = i + 1
endloop
call TriggerAddAction(t, function catchChat)
set t = CreateTrigger()
call TriggerRegisterEnterRectSimple(t, GetWorldBounds())
call TriggerAddAction(t, function catchEnter)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Implements the -ready command. Handles different modes.
library libCommandReady initializer initReady requires General, Constants, LibDefender
globals
private integer array defendersReadyRound
endglobals
///Catches and handles -ready commands
private function catchChat takes nothing returns nothing
local integer i
local integer n
local Defender td = Defender.fromPlayer(GetTriggerPlayer())
local Defender d
local string s = StringCase(GetEventPlayerChatString(), false)
local boolean bIndividual
if s != "-ready" or td == nill or td.state != DEFENDER_STATE_DEFENDING then
return
elseif Game.numRounds <= MAX_ROUNDS and td.team.maxRound >= Game.numRounds then
return
endif
//Restrictions
if Game.gameType != GAME_TYPE_RACE and td.team.state != TEAM_STATE_WAITING then
call showPlayerMessage(td.p, "You can't use -ready during rounds.")
return
endif
//Place the Vote
set bIndividual = (Game.gameType == GAME_TYPE_RACE and td.team.maxRound > 0)
set defendersReadyRound[td.index] = td.team.maxRound + 1
if not bIndividual then
call showMessage(td.getNameWithColor() + " is ready")
else
call td.team.showMessage(td.getNameWithColor() + " is ready")
endif
//Enumerate un-ready players
set i = 1
set n = 0
set s = ""
loop
//Enumerate team members or all depending on mode
if not bIndividual then
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
else
exitwhen i > td.team.numMembers
set d = td.team.members[i]
endif
//Check for non-ready
if defendersReadyRound[d.index] <= td.team.maxRound and d.state == DEFENDER_STATE_DEFENDING then
if GetPlayerController(d.p) == MAP_CONTROL_USER then
if (n > 0) then
set s = s + ", "
endif
set s = s + d.getNameWithColor()
set n = n + 1
endif
endif
set i = i + 1
endloop
//Cancel if anyone important wasn't ready
if n > 0 then
if not bIndividual then
call showMessage(I2S(n) + " still not ready. (" + s + ")")
else
call td.team.showMessage(I2S(n) + " still not ready. (" + s + ")")
endif
return
endif
//Everyone important is ready, try to start the round
if not bIndividual then
//Start round for everyone
call showMessage("All Players Ready.")
set i = 1
loop
exitwhen i > Team.numAllocated
call Team.teams[i].startNextRound()
set i = i + 1
endloop
else
//Start round for team
if td.team.numMembers > 1 then
call td.team.showMessage("Team Ready.")
endif
call td.team.startNextRound()
endif
endfunction
///Initializes events
private function initReady takes nothing returns nothing
local integer i = 0
local trigger t = CreateTrigger()
loop
exitwhen i >= 12
call TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
set i = i + 1
endloop
call TriggerAddAction(t, function catchChat)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Handles the seller's remorse ability. Records sold tower properties
///and reconstructs them when the player casts seller's remorse.
library RemorseAbilLib initializer initRemorse requires General, Constants, LibDefender
globals
private Queue array defendersSoldTowerQueues[NUM_DEFENDERS]
private constant integer MAX_UNSELLS = 25 //Update tooltip and builder mana when changed
private constant real MAX_UNSELL_TIME = 30.0 //Update tooltip when changed
private integer totalSoldTowers = 0
endglobals
function Remorse_NumSoldTowers takes nothing returns integer
return totalSoldTowers
endfunction
private struct SoldTower
readonly integer ut = nill
readonly integer rut = nill
readonly integer energy = 0
readonly location p = null
readonly integer time = 0
readonly integer level = 0
readonly integer skillRecord = 0
readonly integer experience = 0
///Creates a SoldTower which records the relevant properties of the given unit
public static method create takes unit u returns SoldTower
local SoldTower s
local Tower t
//! runtextmacro when("u == null", "return nill")
//! runtextmacro CheckedAllocate("SoldTower", "s")
//pass values
set s.ut = GetUnitTypeId(u)
set s.p = GetUnitLoc(u)
set s.time = Game.time
set t = Tower.fromUnit(u)
if t != nill then
set s.level = t.level
set s.skillRecord = t.skillRecord
if t.relative != null then
set s.rut = GetUnitTypeId(t.relative)
endif
endif
set s.experience = GetHeroXP(u)
set s.energy = R2I(GetUnitState(u, UNIT_STATE_MANA))
set totalSoldTowers = totalSoldTowers + 1
return s
endmethod
///Cleans up properly
private method onDestroy takes nothing returns nothing
//! runtextmacro when("this == nill", "return")
call RemoveLocation(this.p)
set this.p = null
set totalSoldTowers = totalSoldTowers - 1
endmethod
endstruct
///Destroys old sold towers when they time-out
private function delayedLoopRemoveTower takes nothing returns nothing
local SoldTower s
local Defender d = delayedArg
local Queue q = defendersSoldTowerQueues[d.index]
//Remove timed-out towers from queue
loop
exitwhen q.size <= 0
set s = q.peek()
exitwhen s.time+MAX_UNSELL_TIME > Game.time //oldest tower isn't timed out
call q.pop()
call s.destroy()
endloop
//Continue
if q.size > 0 then
call delayedCallWithArg(function delayedLoopRemoveTower, s.time+MAX_UNSELL_TIME-Game.time, d)
endif
//update builder mana to reflect available towers to unsell
call SetUnitState(d.builder, UNIT_STATE_MANA, q.size)
endfunction
///Records towers when they are sold
private function catchSell takes nothing returns nothing
local Defender d = Defender.fromUnit(GetTriggerUnit())
local Queue q = defendersSoldTowerQueues[d.index]
local SoldTower s
//! runtextmacro when("d == nill or GetSpellAbilityId() != ABIL_SELL", "return")
//place sold tower in the defender's queue (respecting maximum size)
if q.size >= MAX_UNSELLS then
set s = q.pop()
call s.destroy()
endif
//create unsell information for tower
set s = SoldTower.create(GetTriggerUnit())
//! runtextmacro when("s == nill", "return")
call q.append(s)
if q.size <= 1 then
call delayedCallWithArg(function delayedLoopRemoveTower, MAX_UNSELL_TIME, d)
endif
//update builder mana to reflect available towers to unsell
call SetUnitState(d.builder, UNIT_STATE_MANA, q.size)
endfunction
///Unsells towers when Seller's Remorse is cast
private function catchRemorse takes nothing returns nothing
local Defender d = Defender.fromUnit(GetTriggerUnit())
local Queue q = defendersSoldTowerQueues[d.index]
local Tower t
local SoldTower s
local unit u
local group g
local rect r
//! runtextmacro when("d == nill or GetSpellAbilityId() != ABIL_UNSELL", "return")
if q.size <= 0 then
call showPlayerMessage(d.p, "No towers to unsell.")
return
endif
//get last sold tower
set s = q.pop()
//! runtextmacro when("s == nill", "return")
//animate where it's trying to unsell
call createBangLoc(s.p, "Abilities\\Spells\\Human\\Resurrect\\ResurrectTarget.mdl")
//check for gold
if GetPlayerState(d.p, PLAYER_STATE_RESOURCE_GOLD) < GetFoodMade(s.ut) then
call showPlayerMessage(d.p, "|cFFFF0000You need " + I2S(GetFoodMade(s.ut)) + " gold for that tower.|r")
call q.append(s)
call SetUnitState(d.builder, UNIT_STATE_MANA, q.size)
return
endif
//check if space is open
set r = RectFromCenterSizeBJ(s.p, TILE_SIZE*1.5, TILE_SIZE*1.5)
set g = GetUnitsInRectAll(r)
call RemoveRect(r)
set r = null
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if IsUnitType(u, UNIT_TYPE_FLYING) == false and IsUnitAliveBJ(u) == true then
call DestroyGroup(g)
set g = null
set u = null
call showPlayerMessage(d.p, "|cFFFF0000The tower's spot was not clear.|r")
call q.append(s)
call SetUnitState(d.builder, UNIT_STATE_MANA, q.size)
return
endif
endloop
call DestroyGroup(g)
set g = null
set u = null
//rebuild the tower
set u = CreateUnitAtLoc(d.p, s.ut, s.p, bj_UNIT_FACING)
if u == null then
call showError("Tried to unsell valid tower and failed.", "catchRemorse")
return
endif
call AdjustPlayerStateBJ(-GetFoodMade(s.ut), d.p, PLAYER_STATE_RESOURCE_GOLD)
call SetUnitState(u, UNIT_STATE_MANA, s.energy)
call SetHeroXP(u, s.experience, false)
if s.level != 0 then
set t = Tower.create(u, s.level)
call t.recordSkillLearning(nill, s.skillRecord)
if s.rut != nill then
set t.relative = CreateUnitAtLoc(d.p, s.rut, s.p, bj_UNIT_FACING)
endif
endif
call s.destroy()
set u = null
endfunction
///Initializes seller's remorse
private function initRemorse takes nothing returns nothing
local trigger t
//create queues
local integer i = 1
loop
exitwhen i > NUM_DEFENDERS
set defendersSoldTowerQueues[i] = Queue.create()
set i = i + 1
endloop
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddAction(t, function catchSell)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerAddAction(t, function catchRemorse)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Removes the preloaded units, sets up the environment, and shows the menus.
library LibStartup initializer initLibStartup requires General, LibDefender
globals
private integer sel_gameType = nill
private integer sel_numRounds = 0
private integer sel_difficulty = nill
private integer sel_numLives = 0
private integer sel_teamType = nill
private integer sel_income = nill
private string selCaption = ""
private dialog menu = DialogCreate()
private timer menuTimer = CreateTimer()
private timerdialog menuTimerDialog = CreateTimerDialog(menuTimer)
private player menuPlayer = null
private string menuCaption = ""
private integer menuIndex = 0
private button defaultButton = null
private integer numButtons = 0
private button array buttons
private string array buttonCaptions
private string array buttonSelCaptions
private integer array buttonValues
endglobals
private function showMenu takes nothing returns nothing
set menuPlayer = Defender.getMainDefender().p
call DialogDisplay(menuPlayer, menu, true)
endfunction
private function resetMenu takes string caption returns nothing
set defaultButton = null
set menuPlayer = null
loop
exitwhen numButtons <= 0
set buttons[numButtons] = null
set buttonCaptions[numButtons] = null
set buttonSelCaptions[numButtons] = null
set buttonValues[numButtons] = 0
set numButtons = numButtons - 1
endloop
call DialogClear(menu)
call DialogSetMessage(menu, caption)
set menuCaption = caption
endfunction
private function catchLeave takes nothing returns nothing
if GetTriggerPlayer() == menuPlayer then
call showMessage("Menu passed to " + Defender.getMainDefender().getNameWithColor() + ".")
call showMenu()
endif
endfunction
private function addButton takes string caption, integer value, string selectionCaption returns button
set numButtons = numButtons + 1
set buttons[numButtons] = DialogAddButton(menu, caption, 0)
set buttonCaptions[numButtons] = caption
set buttonSelCaptions[numButtons] = selectionCaption
set buttonValues[numButtons] = value
return buttons[numButtons]
endfunction
private function clicked takes integer buttonIndex returns boolean
local integer val = buttonValues[buttonIndex]
local string cap = buttonCaptions[buttonIndex]
local string selCap = buttonSelCaptions[buttonIndex]
local string s
if menuIndex != 0 and val != -1 then
call showMessage(menuCaption + ": |cFFFFCC00" + cap + "|r")
set selCaption = selCaption + selCap
endif
//difficulty
if menuIndex == 0 then
call resetMenu("Difficulty")
call addButton("Noob", DIFFICULTY_NOOB, "Noob")
call addButton("Rookie", DIFFICULTY_ROOKIE, "Rookie")
set defaultButton = addButton("Hotshot", DIFFICULTY_HOTSHOT, "Hotshot")
call addButton("Veteran", DIFFICULTY_VETERAN, "Veteran")
call addButton("Elite", DIFFICULTY_ELITE, "Elite")
call addButton("Psycho", DIFFICULTY_ELITE, "Psycho")
endif
if menuIndex == 1 then
set sel_difficulty = val
set menuIndex = menuIndex + 1
endif
//teams
if menuIndex == 2 then
call resetMenu("Teams")
set defaultButton = addButton("Solo", TEAM_TYPE_SOLO, "/Solo")
call addButton("Cooperative", TEAM_TYPE_COOP, "/Coop")
call addButton("Side Teams", TEAM_TYPE_SIDES, "/Sides")
call addButton("Corner Teams", TEAM_TYPE_CORNERS, "/Corners")
endif
if menuIndex == 3 then
set sel_teamType = val
set menuIndex = menuIndex + 1
endif
//game type
if menuIndex == 4 then
if sel_teamType == TEAM_TYPE_COOP then
set sel_gameType = GAME_TYPE_NORMAL
set menuIndex = menuIndex + 2
else
call resetMenu("Game Type")
set defaultButton = addButton("Normal", GAME_TYPE_NORMAL, "/Normal")
call addButton("Race", GAME_TYPE_RACE, "/Race")
call addButton("Rushed", GAME_TYPE_RUSHED, "/Rushed")
call addButton("War", GAME_TYPE_WAR, "/War")
call addButton("Rushed War", GAME_TYPE_RUSHED_WAR, "/RushWar")
call addButton("? Help ?", -1, null)
endif
endif
if menuIndex == 5 then
if val == -1 and cap == "? Help ?" then
set s = ""
set s = s + "|cFFFFCC00Normal Mode:|r\n"
set s = s + "- Survive to win\n"
set s = s + "|cFFFFCC00Race Mode:|r\n"
set s = s + "- Finish first to win\n"
set s = s + "- -ready jumpstarts the next round\n"
set s = s + "|cFFFFCC00Rushed Mode:|r\n"
set s = s + "- Survive to win\n"
set s = s + "- Rounds jumpstart when ANY team finishes\n"
set s = s + "|cFFFFCC00War Mode:|r\n"
set s = s + "- Survive to win\n"
set s = s + "- Hire extra runners to kill other teams\n"
call showPlayerMessage(Defender.getMainDefender().p, s)
return true
endif
set sel_gameType = val
set menuIndex = menuIndex + 1
endif
//game length
if menuIndex == 6 then
if sel_gameType == GAME_TYPE_WAR or sel_gameType == GAME_TYPE_RUSHED_WAR then
set sel_numRounds = GAME_LENGTH_LAST_MAN
set menuIndex = menuIndex + 2
else
call resetMenu("Game Length")
set defaultButton = addButton("15 Rounds", 15, "/15 rounds")
call addButton("20 Rounds", 20, "/20 rounds")
call addButton("25 Rounds", 25, "/25 rounds")
call addButton("30 Rounds", 30, "/30 rounds")
if sel_teamType != TEAM_TYPE_COOP and sel_gameType != GAME_TYPE_RACE then
call addButton("Last Man Standing", GAME_LENGTH_LAST_MAN, "/Last Man")
endif
if sel_gameType != GAME_TYPE_RACE then
call addButton("Endless", GAME_LENGTH_ENDLESS, "/Endless")
endif
endif
endif
if menuIndex == 7 then
set sel_numRounds = val
set menuIndex = menuIndex + 1
endif
//lives
if menuIndex == 8 then
call resetMenu("Lives")
set defaultButton = addButton("10 Lives", 10, "")
call addButton("25 Lives", 25, "")
call addButton("Sudden Death", 1, "")
endif
if menuIndex == 9 then
set sel_numLives = val
set menuIndex = menuIndex + 1
endif
//income type
if menuIndex == 10 then
call resetMenu("Income Type")
set defaultButton = addButton("Fair - Owner/Team Gets Bounty", INCOME_TYPE_FAIR, "/Fair")
call addButton("Standard - Killer Gets Bounty", INCOME_TYPE_STANDARD, "/Killer")
if sel_teamType != TEAM_TYPE_SOLO then
call addButton("Weighted - Fair + Missing Member Bonus", INCOME_TYPE_WEIGHTED, "/Weighted")
endif
call addButton("? Help ?", -1, null)
endif
if menuIndex == 11 then
if val == -1 and cap == "? Help ?" then
//show help and ask again when '? Help ?' is selected
set s = ""
set s = s + "|cFFFFCC00Standard:|r\n"
set s = s + "- Whoever kills a runner gets its bounty\n"
set s = s + "|cFFFFCC00Fair:|r\n"
set s = s + "- Whoever owns a runner gets its bounty\n"
set s = s + "- Team members count as backup owners\n"
set s = s + "|cFFFFCC00Weighted:|r\n"
set s = s + "- Whoever owns a runner gets its bounty\n"
set s = s + "- Team members count as backup owners\n"
set s = s + "- Small teams get more at end of round\n"
call showPlayerMessage(Defender.getMainDefender().p, s)
return true
endif
set sel_income = val
set menuIndex = menuIndex + 1
endif
//start game
if menuIndex >= 12 then
call resetMenu("")
call TimerDialogDisplay(menuTimerDialog, false)
call PauseTimer(menuTimer)
call DestroyTimer(menuTimer)
call DestroyTimerDialog(menuTimerDialog)
call DialogDestroy(menu)
set menuTimer = null
set menu = null
set menuTimerDialog = null
call Game.startGame(selCaption, sel_difficulty, sel_teamType, sel_gameType, sel_numRounds, sel_numLives, sel_income)
return false
endif
set menuIndex = menuIndex + 1
return true
endfunction
private function clickButton takes button b returns boolean
local integer i = 1
loop
exitwhen i > numButtons
if buttons[i] == b then
if clicked(i) then
call showMenu()
return true
else
return false
endif
endif
set i = i + 1
endloop
return false
endfunction
private function catchClick takes nothing returns nothing
call clickButton(GetClickedButton())
endfunction
private function catchTimeout takes nothing returns nothing
loop
exitwhen not clickButton(defaultButton)
call DialogDisplay(menuPlayer, menu, false)
endloop
endfunction
///Prepares the map, fades in, and shows menu
private function startup takes nothing returns nothing
local integer i
local unit u
local group g
local trigger t
//Remove preloaded units
set g = GetUnitsOfPlayerAll(RUNNERS_OWNER)
call UnitAddAbility(FirstOfGroup(g), ABIL_MAX_LIFE_MODIFIER) //preload maxhp ability
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
call RemoveUnit(u)
endloop
call DestroyGroup(g)
set g = null
//Set ambiance
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, 0)
call SetTimeOfDayScale(0)
call FogEnable(false)
call FogMaskEnable(false)
//Fade in
call CinematicFadeBJ(bj_CINEFADETYPE_FADEIN, 1.00, "ReplaceableTextures\\CameraMasks\\Black_mask.blp", 0, 0, 0, 0)
call showMessage("You can start building while " + Defender.getMainDefender().getNameWithColor() + " chooses the game type.")
//Show the menu
set t = CreateTrigger()
call TriggerRegisterDialogEvent(t, menu)
call TriggerAddAction(t, function catchClick)
set t = CreateTrigger()
set i = 0
loop
exitwhen i >= 12
call TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_LEAVE)
set i = i + 1
endloop
call TimerStart(menuTimer, SELECTION_TIME, false, function catchTimeout)
call TimerDialogDisplay(menuTimerDialog, true)
call clicked(0)
call showMenu()
endfunction
///Instantly fades to black and queues starting the map (allow everything else to initialize first)
private function initLibStartup takes nothing returns nothing
call CinematicFadeBJ(bj_CINEFADETYPE_FADEOUT, 0.00, "ReplaceableTextures\\CameraMasks\\Black_mask.blp", 0, 0, 0, 0)
call delayedCall(function startup, 0.50)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Handles all the stuff needed for War mode. Mainly it deals with summoning
///runners to other players. It is only initialized if war mode is selected.
library War requires General, Constants, SpawnLib, HAIL, BlockCommandLib
globals
private constant integer UNIT_SUMMON_NORMAL = 'h042'
private constant integer UNIT_SUMMON_MULTIPLE = 'h045'
private constant integer UNIT_SUMMON_HEAVY = 'h046'
private constant integer UNIT_SUMMON_SPEED = 'h047'
private constant integer UNIT_SUMMON_FEEDBACK = 'h048'
private constant integer UNIT_SUMMON_DIVINE = 'h049'
private constant integer UNIT_SUMMON_THIEF = 'h04A'
private constant integer UNIT_SUMMON_REAPER = 'h04B'
private constant integer UNIT_SUMMON_HERO = 'h04C'
private constant integer ABIL_BLINK = 'A016'
private constant integer ABIL_SUMMON_TIP = 'A00W'
private constant real SUMMON_MAX_HEALTH_FACTOR = 1.0
private constant real SUMMON_HEALTH_FACTOR = 0.25
private constant integer THIEF_STEAL_AMOUNT = 5
private constant integer REAPER_LIVES_AMOUNT = 5
private SpawnSet array summonSets
endglobals
//! runtextmacro HAIL_CreateProperty("SummonSender", "Defender", "private")
//! runtextmacro HAIL_CreateProperty("SummonVictim", "Defender", "private")
///Steals gold as it becomes available
private function delayedSteal takes nothing returns nothing
local integer goldToSteal = delayedArg3
local Defender sender = defenders[delayedArg]
local Defender victim = defenders[delayedArg2]
local integer goldAvailable
//make sure we have an active victim
if victim.state != DEFENDER_STATE_DEFENDING then
set victim = victim.team.getNextIncomeDefender()
endif
if victim.state != DEFENDER_STATE_DEFENDING then
call sender.showMessage(victim.team.getName() + " ran off before paying your dues! Well... what can you do?")
return
endif
//make sure we don't draw blood from stones
set goldAvailable = GetPlayerState(victim.p, PLAYER_STATE_RESOURCE_GOLD)
if goldAvailable < goldToSteal then
//get the rest later
call delayedCallWithArg3(function delayedSteal, 1., sender.index, victim.index, goldToSteal-goldAvailable)
if goldAvailable > 0 then
call victim.showMessage("You don't have enough to pay " + sender.getNameWithColor() + "! You'll be charged more later.")
endif
//but get what we can now
set goldToSteal = goldAvailable
endif
//! runtextmacro when("goldToSteal <= 0", "return")
//steal some gold
call SetPlayerState(sender.p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(sender.p, PLAYER_STATE_RESOURCE_GOLD) + goldToSteal)
call SetPlayerState(victim.p, PLAYER_STATE_RESOURCE_GOLD, goldAvailable - goldToSteal)
call sender.showMessage("You have stolen |cFFFFCC00" + I2S(goldToSteal) + " gold|r from " + victim.getNameWithColor())
call victim.showMessage(victim.getNameWithColor() + " has stolen |cFFFFCC00" + I2S(goldToSteal) + " gold|r from you")
endfunction
private function catchSpawn takes nothing returns nothing
local integer i = 1
local Runner r = Runner.fromUnit(SpawnLib_TriggeringUnit)
loop
exitwhen i > NUM_DEFENDERS
if SpawnLib_TriggeringSpawnSet == summonSets[defenders[i].index] then
call SetSummonSender(r.u, defenders[i])
call SetSummonVictim(r.u, r.owner)
return
endif
set i = i + 1
endloop
endfunction
private function catchFinish takes nothing returns nothing
local Defender sender = GetSummonSender(Path_TriggeringUnit)
local Defender victim = GetSummonVictim(Path_TriggeringUnit)
call ResetSummonSender(Path_TriggeringUnit)
call ResetSummonVictim(Path_TriggeringUnit)
//! runtextmacro when("sender == nill or victim == nill or sender.state != DEFENDER_STATE_DEFENDING", "return")
if GetUnitTypeId(Path_TriggeringUnit) == UNIT_SUMMON_THIEF then
call delayedCallWithArg3(function delayedSteal, 0., sender.index, victim.index, THIEF_STEAL_AMOUNT)
call showMessage(sender.getNameWithColor() + " stole " + I2S(THIEF_STEAL_AMOUNT) + " gold from " + victim.getNameWithColor() + "!")
elseif GetUnitTypeId(Path_TriggeringUnit) == UNIT_SUMMON_REAPER then
call sender.team.setLives(sender.team.lives + REAPER_LIVES_AMOUNT)
call showMessage(sender.team.getName() + " reaped some lives! (" + I2S(sender.team.lives) + " left)")
endif
endfunction
private function summonMaxHealth takes integer r returns integer
return R2I(getRoundRunnerHealth(r, Game.difficulty)*SUMMON_MAX_HEALTH_FACTOR)
endfunction
private function summonHealthPerMana takes integer r returns real
//! runtextmacro when("r <= 0", "set r = 1")
return summonMaxHealth(r)*SUMMON_HEALTH_FACTOR/getRoundAccumulatedBounty(r)
endfunction
private function catchCast takes nothing returns nothing
local location p
if GetSpellAbilityId() == ABIL_BLINK then
set p = GetSpellTargetLoc()
call Tower.fromUnit(GetTriggerUnit()).move(p)
call RemoveLocation(p)
set p = null
call tryBlockUnit(GetTriggerUnit())
elseif GetSpellAbilityId() == ABIL_SUMMON_TIP then
call showPlayerMessage(GetOwningPlayer(GetTriggerUnit()), "Energy in Barracks: |cFF0000FF" + I2S(Tower.fromUnit(GetTriggerUnit()).getEnergy()) + "|r")
call showPlayerMessage(GetOwningPlayer(GetTriggerUnit()), "Current Health/Energy: |cFFFF8000" + R2S(summonHealthPerMana(Defender.fromUnit(GetTriggerUnit()).team.maxRound)) + "|r")
call showPlayerMessage(GetOwningPlayer(GetTriggerUnit()), "Current Base Max Health: |cFF00FF00" + I2S(R2I(summonMaxHealth(Defender.fromUnit(GetTriggerUnit()).team.maxRound))) + "|r")
endif
endfunction
private function catchTrained takes nothing returns nothing
local SpawnBase s
local integer i
local integer n
local location p
local unit trainee = GetTrainedUnit()
local unit trainer = GetTriggerUnit()
local Tower t = Tower.fromUnit(trainer)
local integer uid = GetUnitTypeId(trainee)
local Defender d
local Defender src = Defender.fromUnit(trainer)
local Defender dst = nill
local integer maxhp = summonMaxHealth(src.team.maxRound)
local integer bounty = 0
local boolean hasSpeed = false
local boolean hasFeedback = false
local boolean hasShield = false
local integer numSpawned = 1
local real healthPerMana = summonHealthPerMana(src.team.maxRound)
call SetUnitExploded(trainee, true)
call KillUnit(trainee)
if t.getEnergy() <= 0 then
call playSoundForPlayer(gg_snd_Hint, src.p)
call src.showMessage("|cFF00FF00Tip:|r |cFFFF0000Summon failed.|r |cFF00FF00You need to transfer energy to the barracks to summon runners.")
set trainee = null
set trainer = null
return
endif
//pick enemy defender the rally point is on
set i = 1
set dst = nill
set p = GetUnitRallyPoint(trainer)
if p != null then
loop
exitwhen i > NUM_DEFENDERS
if defenders[i].path.containsPoint(GetLocationX(p), GetLocationY(p)) then
set dst = defenders[i]
exitwhen true
endif
set i = i + 1
endloop
call RemoveLocation(p)
set p = null
endif
if dst == nill or dst.state != DEFENDER_STATE_DEFENDING or dst.team == src.team then
//pick random enemy defender if no target picked
set dst = nill
set i = 1
set n = GetRandomInt(1, Defender.countLiveDefenders() - src.team.countLivingMembers())
loop
exitwhen n < 1 or i > NUM_DEFENDERS
set d = defenders[i]
if d.state == DEFENDER_STATE_DEFENDING and d.team != src.team then
set dst = d
set n = n - 1
endif
set i = i + 1
endloop
if dst == nill then
//the game should be ending (war is always last man standing)
call src.showMessage("No possible summon targets.")
set trainee = null
set trainer = null
return
endif
endif
//properties
if uid == UNIT_SUMMON_NORMAL then
set maxhp = maxhp / 2
elseif uid == UNIT_SUMMON_SPEED then
set hasSpeed = true
set healthPerMana = healthPerMana / 1.2
elseif uid == UNIT_SUMMON_FEEDBACK then
set hasFeedback = true
set maxhp = maxhp / 2
elseif uid == UNIT_SUMMON_DIVINE then
set hasShield = true
set healthPerMana = healthPerMana / 1.2
elseif uid == UNIT_SUMMON_HERO then
set hasSpeed = true
set hasFeedback = true
set hasShield = true
set healthPerMana = healthPerMana / 2
elseif uid == UNIT_SUMMON_HEAVY then
set maxhp = maxhp * 3
set healthPerMana = healthPerMana / 1.5
elseif uid == UNIT_SUMMON_MULTIPLE then
set maxhp = maxhp / 2
set numSpawned = 3
set healthPerMana = healthPerMana / 1.5
elseif uid == UNIT_SUMMON_THIEF then
set bounty = 1
set maxhp = maxhp * 5
set healthPerMana = healthPerMana / 1.5
elseif uid == UNIT_SUMMON_REAPER then
set bounty = 2
set maxhp = maxhp * 5
set healthPerMana = healthPerMana / 2
endif
set maxhp = IMinBJ(maxhp, floor(t.getEnergy()*healthPerMana))
if maxhp < 1 then
set maxhp = 1
endif
call t.adjustEnergy(-floor(maxhp/healthPerMana))
//spawn
set s = PowerTowersSpawn.create(dst, summonSets[src.index], uid, maxhp, bounty, hasSpeed, hasFeedback, hasShield)
call dst.stream.spawnMultiple(s, numSpawned, 0.)
call s.destroy()
//inform
call src.showMessage("Sent a |cFFFFCC00" + GetUnitName(trainee) + "|r with |cFF00FF00" + I2S(maxhp) + " hp|r to " + dst.getNameWithColor())
call dst.showMessage(src.getNameWithColor() + " sent a |cFFFFCC00" + GetUnitName(trainee) + "|r with |cFF00FF00" + I2S(maxhp) + " hp|r.")
set trainee = null
set trainer = null
endfunction
function startWar takes nothing returns nothing
local trigger t
local integer i
local location p
local Defender d
local unit u
//training
set t = CreateTrigger()
call TriggerAddAction(t, function catchTrained)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_TRAIN_FINISH)
//casting
set t = CreateTrigger()
call TriggerAddAction(t, function catchCast)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
//runners finishing
call Path_AddFinishedCallBack(function catchFinish)
//runners spawning
call SpawnLib_AddSpawnedCallBack(function catchSpawn)
//create barracks
set i = 1
loop
exitwhen i > NUM_DEFENDERS
set d = defenders[i]
if d.state == DEFENDER_STATE_DEFENDING then
set p = GetPlayerStartLocationLoc(d.p)
set u = CreateUnitAtLoc(d.p, UNIT_BARRACKS, p, bj_UNIT_FACING)
call GroupAddUnit(d.specialUnits, u)
set summonSets[d.index] = SpawnSet.create()
call Tower.create(u, 1)
call RemoveLocation(p)
set u = null
set p = null
endif
set i = i + 1
endloop
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
function showAllocationReport takes nothing returns nothing
local location p = Location(0,0)
call BJDebugMsg("Allocation report (@" + I2S(Game.time) + " seconds; H=" + I2S(H2I(p)) + ")")
call BJDebugMsg("** Teams: " + I2S(Team.numAllocated))
call BJDebugMsg("Rounds: " + I2S(Round.numAllocated))
call BJDebugMsg("** Towers: " + I2S(Tower.numAllocated))
call BJDebugMsg("Tower Transfers: " + I2S(TowerTransfer.numAllocated))
call BJDebugMsg("Sold Towers: " + I2S(Remorse_NumSoldTowers()))
call BJDebugMsg("** Runners: " + I2S(Runner.numAllocated))
call BJDebugMsg("PathingUnits: " + I2S(PathLib_NumPathingUnits()))
call BJDebugMsg("Paths: " + I2S(PathLib_NumPaths()))
call BJDebugMsg("Spawn Bases: " + I2S(PowerTowersSpawn.numAllocated))
call BJDebugMsg("Spawn Sets: " + I2S(SpawnLib_NumSpawnSets()))
call BJDebugMsg("Spawn Streams: " + I2S(SpawnLib_NumSpawnStreams()))
call BJDebugMsg("** HAIL Indexes: " + I2S(HAIL_TotalIndexes))
debug call BJDebugMsg("HAIL Collisions: " + I2S(HAIL_TotalCollisions))
call BJDebugMsg("Delayed Calls: " + I2S(DelayedCallLib_NumDelayedCalls()))
call BJDebugMsg("Queues: " + I2S(Queue_NumQueues()))
call BJDebugMsg("Queue Nodes: " + I2S(Queue_NumQueueNodes()))
call RemoveLocation(p)
set p = null
endfunction
function InitTrig_Alloc takes nothing returns nothing
set gg_trg_Alloc = CreateTrigger()
call TriggerRegisterPlayerChatEvent(gg_trg_Alloc, Player(0), "-alloc", true)
call TriggerAddAction(gg_trg_Alloc, function showAllocationReport)
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function researchAllTech takes nothing returns nothing
local integer i = 1
loop
exitwhen i > NUM_DEFENDERS
call SetPlayerTechResearched(defenders[i].p, RESEARCH_GENERATOR, 1)
call SetPlayerTechResearched(defenders[i].p, RESEARCH_ROUND_1, 1)
call SetPlayerTechResearched(defenders[i].p, RESEARCH_ROUND_2, 1)
call SetPlayerTechResearched(defenders[i].p, RESEARCH_ROUND_3, 1)
call SetPlayerTechResearched(defenders[i].p, RESEARCH_ROUND_4, 1)
set i = i + 1
endloop
call showMessage("|cFFFFCC00Tech Researched|r")
endfunction
function InitTrig_Tech takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-tech", true)
call TriggerAddAction(t, function researchAllTech)
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function showIssuedOrder takes nothing returns nothing
call showUnitText(GetTriggerUnit(), OrderId2String(GetIssuedOrderId()))
endfunction
function InitTrig_Orders takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddAction(t, function showIssuedOrder)
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
///Periodically checks if the number of handles in the map has increased.
///Continuous "handle count exceeded" messages means you have leaks.
///Keep in mind that you will normally get "handle count exceeded" as play ramps up.
library LeakDetector initializer initLeakDetector
globals
private constant real LEAK_CHECK_PERIOD = 0.1 //seconds
private integer maxHandle = 0
private integer baseHandle = 0
private location array locs
private constant integer numLocs = 100
endglobals
private function H2I takes handle h returns integer
return h
return 0
endfunction
private function maxHandleAddress takes nothing returns integer
local integer i = 0
local integer n = 0
//attempt to get current max handle
loop
exitwhen i >= numLocs
set locs[i] = Location(0, 0)
set n = IMaxBJ(n, H2I(locs[i]))
set i = i + 1
endloop
//cleanup
loop
exitwhen i <= 0
set i = i - 1
call RemoveLocation(locs[i])
set locs[i] = null
endloop
return n
endfunction
//inform when maximum handle increases
private function checkLeaks takes nothing returns nothing
local integer i = maxHandleAddress()
if i > maxHandle then
set maxHandle = i
call BJDebugMsg("|cFFFFFF00Leak Detection|r: Handle count from base exceeded " + I2S(maxHandle-baseHandle))
endif
endfunction
private function initLeakDetector takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, LEAK_CHECK_PERIOD, true)
call TriggerAddAction(t, function checkLeaks)
set baseHandle = H2I(t)
set maxHandle = baseHandle
call BJDebugMsg("|cFFFFFF00Leak Detection|r: Started with base handle " + I2S(baseHandle))
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
Compile Fail