InsaneMonster
Hosted Project: W3RR
- Joined
- Jul 20, 2011
- Messages
- 505
JASS CAMPAIGN AI 2.0
Description
Code
Tutorial
Known Issues
We all know JASS Campaign AI is... clunky, at best.
I always loved its ease of use and even tried to imitate it in Starcraft 2 in the past. However, while working on Warcraft 3 Re-Reforged, I soon realized some of the built-in limitations were an hindrance to my game design process.
For example, using the Exodus of the Horde campaign as reference, it was impossible for me to prevent Kul Tiras Human AI, in chapter three Riders on the Storm, from attacking player's unit groups around the map. While it happened only sometimes, actually quite rarely, it was breaking the map flow of defending the Darkspear Trolls against scripted waves of enemies.
Likewise, in chapter five, I had to resort to an insane set of tricks with pathing blockers to make the Underworld Minions AIs attack all the various entrances of the player base, at random.
Finally, there was another known issue, which often prevents the AI players from continuing their attack wave if such wave is actually winning. Basically, when the AI targets a certain place, it clears its proximity and then it returns to base, unless attacked, even if other enemies structure are present nearby (but just a bit further away then what the original designers planned when writing the natives).
This is very bad because the AI is basically not exploiting its own advantage and stop its successful attack when the player cannot defend itself.
So, after dealing for quite some time with these issues using clunky workarounds, I decided that they were tolerable no longer, and started to study the only real insight we have into Warcraft 3 default JASS AI, the file common.ai:
I always loved its ease of use and even tried to imitate it in Starcraft 2 in the past. However, while working on Warcraft 3 Re-Reforged, I soon realized some of the built-in limitations were an hindrance to my game design process.
For example, using the Exodus of the Horde campaign as reference, it was impossible for me to prevent Kul Tiras Human AI, in chapter three Riders on the Storm, from attacking player's unit groups around the map. While it happened only sometimes, actually quite rarely, it was breaking the map flow of defending the Darkspear Trolls against scripted waves of enemies.
Likewise, in chapter five, I had to resort to an insane set of tricks with pathing blockers to make the Underworld Minions AIs attack all the various entrances of the player base, at random.
Finally, there was another known issue, which often prevents the AI players from continuing their attack wave if such wave is actually winning. Basically, when the AI targets a certain place, it clears its proximity and then it returns to base, unless attacked, even if other enemies structure are present nearby (but just a bit further away then what the original designers planned when writing the natives).
This is very bad because the AI is basically not exploiting its own advantage and stop its successful attack when the player cannot defend itself.
So, after dealing for quite some time with these issues using clunky workarounds, I decided that they were tolerable no longer, and started to study the only real insight we have into Warcraft 3 default JASS AI, the file common.ai:
JASS:
//==================================================================================================
// $Id: common.ai,v 1.68 2003/05/12 02:34:18 bfitch Exp $
//==================================================================================================
native DebugS takes string str returns nothing
native DebugFI takes string str, integer val returns nothing
native DebugUnitID takes string str, integer val returns nothing
native DisplayText takes integer p, string str returns nothing
native DisplayTextI takes integer p, string str, integer val returns nothing
native DisplayTextII takes integer p, string str, integer v1, integer v2 returns nothing
native DisplayTextIII takes integer p, string str, integer v1, integer v2, integer v3 returns nothing
native DoAiScriptDebug takes nothing returns boolean
native GetAiPlayer takes nothing returns integer
native GetHeroId takes nothing returns integer
native GetHeroLevelAI takes nothing returns integer
native GetUnitCount takes integer unitid returns integer
native GetPlayerUnitTypeCount takes player p, integer unitid returns integer
native GetUnitCountDone takes integer unitid returns integer
native GetTownUnitCount takes integer id, integer tn, boolean dn returns integer
native GetUnitGoldCost takes integer unitid returns integer
native GetUnitWoodCost takes integer unitid returns integer
native GetUnitBuildTime takes integer unitid returns integer
native GetMinesOwned takes nothing returns integer
native GetGoldOwned takes nothing returns integer
native TownWithMine takes nothing returns integer
native TownHasMine takes integer townid returns boolean
native TownHasHall takes integer townid returns boolean
native GetUpgradeLevel takes integer id returns integer
native GetUpgradeGoldCost takes integer id returns integer
native GetUpgradeWoodCost takes integer id returns integer
native GetNextExpansion takes nothing returns integer
native GetMegaTarget takes nothing returns unit
native GetBuilding takes player p returns unit
native GetEnemyPower takes nothing returns integer
native SetAllianceTarget takes unit id returns nothing
native GetAllianceTarget takes nothing returns unit
native SetProduce takes integer qty, integer id, integer town returns boolean
native Unsummon takes unit unitid returns nothing
native SetExpansion takes unit peon, integer id returns boolean
native SetUpgrade takes integer id returns boolean
native SetHeroLevels takes code func returns nothing
native SetNewHeroes takes boolean state returns nothing
native PurchaseZeppelin takes nothing returns nothing
native MergeUnits takes integer qty, integer a, integer b, integer make returns boolean
native ConvertUnits takes integer qty, integer id returns boolean
native SetCampaignAI takes nothing returns nothing
native SetMeleeAI takes nothing returns nothing
native SetTargetHeroes takes boolean state returns nothing
native SetPeonsRepair takes boolean state returns nothing
native SetRandomPaths takes boolean state returns nothing
native SetDefendPlayer takes boolean state returns nothing
native SetHeroesFlee takes boolean state returns nothing
native SetHeroesBuyItems takes boolean state returns nothing
native SetWatchMegaTargets takes boolean state returns nothing
native SetIgnoreInjured takes boolean state returns nothing
native SetHeroesTakeItems takes boolean state returns nothing
native SetUnitsFlee takes boolean state returns nothing
native SetGroupsFlee takes boolean state returns nothing
native SetSlowChopping takes boolean state returns nothing
native SetCaptainChanges takes boolean allow returns nothing
native SetSmartArtillery takes boolean state returns nothing
native SetReplacementCount takes integer qty returns nothing
native GroupTimedLife takes boolean allow returns nothing
native RemoveInjuries takes nothing returns nothing
native RemoveSiege takes nothing returns nothing
native InitAssault takes nothing returns nothing
native AddAssault takes integer qty, integer id returns boolean
native AddDefenders takes integer qty, integer id returns boolean
native GetCreepCamp takes integer min, integer max, boolean flyers_ok returns unit
native StartGetEnemyBase takes nothing returns nothing
native WaitGetEnemyBase takes nothing returns boolean
native GetEnemyBase takes nothing returns unit
native GetExpansionFoe takes nothing returns unit
native GetEnemyExpansion takes nothing returns unit
native GetExpansionX takes nothing returns integer
native GetExpansionY takes nothing returns integer
native SetStagePoint takes real x, real y returns nothing
native AttackMoveKill takes unit target returns nothing
native AttackMoveXY takes integer x, integer y returns nothing
native LoadZepWave takes integer x, integer y returns nothing
native SuicidePlayer takes player id, boolean check_full returns boolean
native SuicidePlayerUnits takes player id, boolean check_full returns boolean
native CaptainInCombat takes boolean attack_captain returns boolean
native IsTowered takes unit target returns boolean
native ClearHarvestAI takes nothing returns nothing
native HarvestGold takes integer town, integer peons returns nothing
native HarvestWood takes integer town, integer peons returns nothing
native GetExpansionPeon takes nothing returns unit
native StopGathering takes nothing returns nothing
native AddGuardPost takes integer id, real x, real y returns nothing
native FillGuardPosts takes nothing returns nothing
native ReturnGuardPosts takes nothing returns nothing
native CreateCaptains takes nothing returns nothing
native SetCaptainHome takes integer which, real x, real y returns nothing
native ResetCaptainLocs takes nothing returns nothing
native ShiftTownSpot takes real x, real y returns nothing
native TeleportCaptain takes real x, real y returns nothing
native ClearCaptainTargets takes nothing returns nothing
native CaptainAttack takes real x, real y returns nothing
native CaptainVsUnits takes player id returns nothing
native CaptainVsPlayer takes player id returns nothing
native CaptainGoHome takes nothing returns nothing
native CaptainIsHome takes nothing returns boolean
native CaptainIsFull takes nothing returns boolean
native CaptainIsEmpty takes nothing returns boolean
native CaptainGroupSize takes nothing returns integer
native CaptainReadiness takes nothing returns integer
native CaptainRetreating takes nothing returns boolean
native CaptainReadinessHP takes nothing returns integer
native CaptainReadinessMa takes nothing returns integer
native CaptainAtGoal takes nothing returns boolean
native CreepsOnMap takes nothing returns boolean
native SuicideUnit takes integer count, integer unitid returns nothing
native SuicideUnitEx takes integer ct, integer uid, integer pid returns nothing
native StartThread takes code func returns nothing
native Sleep takes real seconds returns nothing
native UnitAlive takes unit id returns boolean
native UnitInvis takes unit id returns boolean
native IgnoredUnits takes integer unitid returns integer
native TownThreatened takes nothing returns boolean
native DisablePathing takes nothing returns nothing
native SetAmphibious takes nothing returns nothing
native CommandsWaiting takes nothing returns integer
native GetLastCommand takes nothing returns integer
native GetLastData takes nothing returns integer
native PopLastCommand takes nothing returns nothing
native MeleeDifficulty takes nothing returns integer
//============================================================================
// Globals for all AI scripts
//============================================================================
globals
//--------------------------------------------------------------------
// HUMANS
//--------------------------------------------------------------------
// human heroes
constant integer ARCHMAGE = 'Hamg'
constant integer PALADIN = 'Hpal'
constant integer MTN_KING = 'Hmkg'
constant integer BLOOD_MAGE = 'Hblm'
// human hero abilities
constant integer AVATAR = 'AHav'
constant integer BASH = 'AHbh'
constant integer THUNDER_BOLT = 'AHtb'
constant integer THUNDER_CLAP = 'AHtc'
constant integer DEVOTION_AURA = 'AHad'
constant integer DIVINE_SHIELD = 'AHds'
constant integer HOLY_BOLT = 'AHhb'
constant integer RESURRECTION = 'AHre'
constant integer BLIZZARD = 'AHbz'
constant integer BRILLIANCE_AURA = 'AHab'
constant integer MASS_TELEPORT = 'AHmt'
constant integer WATER_ELEMENTAL = 'AHwe'
constant integer BANISH = 'AHbn'
constant integer FLAME_STRIKE = 'AHfs'
constant integer SUMMON_PHOENIX = 'AHpx'
constant integer SIPHON_MANA = 'AHdr'
// special human heroes
constant integer JAINA = 'Hjai'
constant integer MURADIN = 'Hmbr'
constant integer GARITHOS = 'Hlgr'
constant integer KAEL = 'Hkal'
// human units
constant integer COPTER = 'hgyr'
constant integer GYRO = COPTER
constant integer ELEMENTAL = 'hwat'
constant integer FOOTMAN = 'hfoo'
constant integer FOOTMEN = FOOTMAN
constant integer GRYPHON = 'hgry'
constant integer KNIGHT = 'hkni'
constant integer MORTAR = 'hmtm'
constant integer PEASANT = 'hpea'
constant integer PRIEST = 'hmpr'
constant integer RIFLEMAN = 'hrif'
constant integer RIFLEMEN = RIFLEMAN
constant integer SORCERESS = 'hsor'
constant integer TANK = 'hmtt'
constant integer STEAM_TANK = TANK
constant integer ROCKET_TANK = 'hrtt'
constant integer MILITIA = 'hmil'
constant integer SPELL_BREAKER = 'hspt'
constant integer HUMAN_DRAGON_HAWK = 'hdhw'
// special human units
constant integer BLOOD_PRIEST = 'hbep'
constant integer BLOOD_SORCERESS = 'hbes'
constant integer BLOOD_PEASANT = 'nhew'
// human buildings
constant integer AVIARY = 'hgra'
constant integer BARRACKS = 'hbar'
constant integer BLACKSMITH = 'hbla'
constant integer CANNON_TOWER = 'hctw'
constant integer CASTLE = 'hcas'
constant integer CHURCH = 'htws'
constant integer MAGE_TOWER = CHURCH
constant integer GUARD_TOWER = 'hgtw'
constant integer HOUSE = 'hhou'
constant integer HUMAN_ALTAR = 'halt'
constant integer KEEP = 'hkee'
constant integer LUMBER_MILL = 'hlum'
constant integer SANCTUM = 'hars'
constant integer ARCANE_SANCTUM = SANCTUM
constant integer TOWN_HALL = 'htow'
constant integer WATCH_TOWER = 'hwtw'
constant integer WORKSHOP = 'harm'
constant integer ARCANE_VAULT = 'hvlt'
constant integer ARCANE_TOWER = 'hatw'
// human upgrades
constant integer UPG_MELEE = 'Rhme'
constant integer UPG_RANGED = 'Rhra'
constant integer UPG_ARTILLERY = 'Rhaa'
constant integer UPG_ARMOR = 'Rhar'
constant integer UPG_GOLD = 'Rhmi'
constant integer UPG_MASONRY = 'Rhac'
constant integer UPG_SIGHT = 'Rhss'
constant integer UPG_DEFEND = 'Rhde'
constant integer UPG_BREEDING = 'Rhan'
constant integer UPG_PRAYING = 'Rhpt'
constant integer UPG_SORCERY = 'Rhst'
constant integer UPG_LEATHER = 'Rhla'
constant integer UPG_GUN_RANGE = 'Rhri'
constant integer UPG_WOOD = 'Rhlh'
constant integer UPG_SENTINEL = 'Rhse'
constant integer UPG_SCATTER = 'Rhsr'
constant integer UPG_BOMBS = 'Rhgb'
constant integer UPG_HAMMERS = 'Rhhb'
constant integer UPG_CONT_MAGIC = 'Rhss'
constant integer UPG_FRAGS = 'Rhfs'
constant integer UPG_TANK = 'Rhrt'
constant integer UPG_FLAK = 'Rhfc'
constant integer UPG_CLOUD = 'Rhcd'
//--------------------------------------------------------------------
// ORCS
//--------------------------------------------------------------------
// orc heroes
constant integer BLADE_MASTER = 'Obla'
constant integer FAR_SEER = 'Ofar'
constant integer TAUREN_CHIEF = 'Otch'
constant integer SHADOW_HUNTER = 'Oshd'
// special orc heroes
constant integer GROM = 'Ogrh'
constant integer THRALL = 'Othr'
// orc hero abilities
constant integer CRITICAL_STRIKE = 'AOcr'
constant integer MIRROR_IMAGE = 'AOmi'
constant integer BLADE_STORM = 'AOww'
constant integer WIND_WALK = 'AOwk'
constant integer CHAIN_LIGHTNING = 'AOcl'
constant integer EARTHQUAKE = 'AOeq'
constant integer FAR_SIGHT = 'AOfs'
constant integer SPIRIT_WOLF = 'AOsf'
constant integer ENDURANE_AURA = 'AOae'
constant integer REINCARNATION = 'AOre'
constant integer SHOCKWAVE = 'AOsh'
constant integer WAR_STOMP = 'AOws'
constant integer HEALING_WAVE = 'AOhw'
constant integer HEX = 'AOhx'
constant integer SERPENT_WARD = 'AOsw'
constant integer VOODOO = 'AOvd'
// orc units
constant integer GUARDIAN = 'oang'
constant integer CATAPULT = 'ocat'
constant integer WITCH_DOCTOR = 'odoc'
constant integer GRUNT = 'ogru'
constant integer HEAD_HUNTER = 'ohun'
constant integer BERSERKER = 'otbk'
constant integer KODO_BEAST = 'okod'
constant integer PEON = 'opeo'
constant integer RAIDER = 'orai'
constant integer SHAMAN = 'oshm'
constant integer TAUREN = 'otau'
constant integer WYVERN = 'owyv'
constant integer BATRIDER = 'otbr'
constant integer SPIRIT_WALKER = 'ospw'
constant integer SPIRIT_WALKER_M = 'ospm'
// orc buildings
constant integer ORC_ALTAR = 'oalt'
constant integer ORC_BARRACKS = 'obar'
constant integer BESTIARY = 'obea'
constant integer FORGE = 'ofor'
constant integer FORTRESS = 'ofrt'
constant integer GREAT_HALL = 'ogre'
constant integer LODGE = 'osld'
constant integer STRONGHOLD = 'ostr'
constant integer BURROW = 'otrb'
constant integer TOTEM = 'otto'
constant integer ORC_WATCH_TOWER = 'owtw'
constant integer VOODOO_LOUNGE = 'ovln'
// orc upgrades
constant integer UPG_ORC_MELEE = 'Rome'
constant integer UPG_ORC_RANGED = 'Rora'
constant integer UPG_ORC_ARTILLERY = 'Roaa'
constant integer UPG_ORC_ARMOR = 'Roar'
constant integer UPG_ORC_WAR_DRUMS = 'Rwdm'
constant integer UPG_ORC_PILLAGE = 'Ropg'
constant integer UPG_ORC_BERSERK = 'Robs'
constant integer UPG_ORC_PULVERIZE = 'Rows'
constant integer UPG_ORC_ENSNARE = 'Roen'
constant integer UPG_ORC_VENOM = 'Rovs'
constant integer UPG_ORC_DOCS = 'Rowd'
constant integer UPG_ORC_SHAMAN = 'Rost'
constant integer UPG_ORC_SPIKES = 'Rosp'
constant integer UPG_ORC_BURROWS = 'Rorb'
constant integer UPG_ORC_REGEN = 'Rotr'
constant integer UPG_ORC_FIRE = 'Rolf'
constant integer UPG_ORC_SWALKER = 'Rowt'
constant integer UPG_ORC_BERSERKER = 'Robk'
constant integer UPG_ORC_NAPTHA = 'Robf'
constant integer UPG_ORC_CHAOS = 'Roch'
// Warcraft 2 orc units
constant integer OGRE_MAGI = 'nomg'
constant integer ORC_DRAGON = 'nrwm'
constant integer SAPPER = 'ngsp'
constant integer ZEPPLIN = 'nzep'
constant integer ZEPPELIN = ZEPPLIN
constant integer W2_WARLOCK = 'nw2w'
constant integer PIG_FARM = 'npgf'
constant integer FOREST_TROLL = 'nftr'
// special orc units
constant integer CHAOS_GRUNT = 'nchg'
constant integer CHAOS_WARLOCK = 'nchw'
constant integer CHAOS_RAIDER = 'nchr'
constant integer CHAOS_PEON = 'ncpn'
constant integer CHAOS_KODO = 'nckb'
constant integer CHAOS_GROM = 'Opgh'
constant integer CHAOS_BLADEMASTER = 'Nbbc'
constant integer CHAOS_BURROW = 'ocbw'
//--------------------------------------------------------------------
// UNDEAD
//--------------------------------------------------------------------
// undead heroes
constant integer DEATH_KNIGHT = 'Udea'
constant integer DREAD_LORD = 'Udre'
constant integer LICH = 'Ulic'
constant integer CRYPT_LORD = 'Ucrl'
// special undead heroes
constant integer MALGANIS = 'Umal'
constant integer TICHONDRIUS = 'Utic'
constant integer PIT_LORD = 'Npld'
constant integer DETHEROC = 'Udth'
// undead hero abilities
constant integer SLEEP = 'AUsl'
constant integer VAMP_AURA = 'AUav'
constant integer CARRION_SWARM = 'AUcs'
constant integer INFERNO = 'AUin'
constant integer DARK_RITUAL = 'AUdr'
constant integer DEATH_DECAY = 'AUdd'
constant integer FROST_ARMOR = 'AUfu'
constant integer FROST_NOVA = 'AUfn'
constant integer ANIM_DEAD = 'AUan'
constant integer DEATH_COIL = 'AUdc'
constant integer DEATH_PACT = 'AUdp'
constant integer UNHOLY_AURA = 'AUau'
constant integer CARRION_SCARAB = 'AUcb'
constant integer IMPALE = 'AUim'
constant integer LOCUST_SWARM = 'AUls'
constant integer THORNY_SHIELD = 'AUts'
// undead units
constant integer ABOMINATION = 'uabo'
constant integer ACOLYTE = 'uaco'
constant integer BANSHEE = 'uban'
constant integer PIT_FIEND = 'ucry'
constant integer CRYPT_FIEND = PIT_FIEND
constant integer FROST_WYRM = 'ufro'
constant integer GARGOYLE = 'ugar'
constant integer GARGOYLE_MORPH = 'ugrm'
constant integer GHOUL = 'ugho'
constant integer MEAT_WAGON = 'umtw'
constant integer NECRO = 'unec'
constant integer SKEL_WARRIOR = 'uske'
constant integer SHADE = 'ushd'
constant integer UNDEAD_BARGE = 'uarb'
constant integer OBSIDIAN_STATUE = 'uobs'
constant integer OBS_STATUE = OBSIDIAN_STATUE
constant integer BLK_SPHINX = 'ubsp'
// undead buildings
constant integer UNDEAD_MINE = 'ugol'
constant integer UNDEAD_ALTAR = 'uaod'
constant integer BONEYARD = 'ubon'
constant integer GARG_SPIRE = 'ugsp'
constant integer NECROPOLIS_1 = 'unpl' // normal
constant integer NECROPOLIS_2 = 'unp1' // upgraded once
constant integer NECROPOLIS_3 = 'unp2' // full upgrade
constant integer SAC_PIT = 'usap'
constant integer CRYPT = 'usep'
constant integer SLAUGHTERHOUSE = 'uslh'
constant integer DAMNED_TEMPLE = 'utod'
constant integer ZIGGURAT_1 = 'uzig' // normal
constant integer ZIGGURAT_2 = 'uzg1' // upgraded
constant integer ZIGGURAT_FROST = 'uzg2' // frost tower
constant integer GRAVEYARD = 'ugrv'
constant integer TOMB_OF_RELICS = 'utom'
// undead upgrades
constant integer UPG_UNHOLY_STR = 'Rume'
constant integer UPG_CR_ATTACK = 'Rura'
constant integer UPG_UNHOLY_ARMOR = 'Ruar'
constant integer UPG_CANNIBALIZE = 'Ruac'
constant integer UPG_GHOUL_FRENZY = 'Rugf'
constant integer UPG_FIEND_WEB = 'Ruwb'
constant integer UPG_ABOM = 'Ruab'
constant integer UPG_STONE_FORM = 'Rusf'
constant integer UPG_NECROS = 'Rune'
constant integer UPG_BANSHEE = 'Ruba'
constant integer UPG_MEAT_WAGON = 'Rump'
constant integer UPG_WYRM_BREATH = 'Rufb'
constant integer UPG_SKEL_LIFE = 'Rusl'
constant integer UPG_SKEL_MASTERY = 'Rusm'
constant integer UPG_EXHUME = 'Ruex'
constant integer UPG_SACRIFICE = 'Rurs'
constant integer UPG_ABOM_EXPL = 'Ruax'
constant integer UPG_CR_ARMOR = 'Rucr'
constant integer UPG_PLAGUE = 'Rupc'
constant integer UPG_BLK_SPHINX = 'Rusp'
constant integer UPG_BURROWING = 'Rubu'
//--------------------------------------------------------------------
// ELVES
//--------------------------------------------------------------------
// elf heroes
constant integer DEMON_HUNTER = 'Edem'
constant integer DEMON_HUNTER_M = 'Edmm'
constant integer KEEPER = 'Ekee'
constant integer MOON_CHICK = 'Emoo'
constant integer MOON_BABE = MOON_CHICK
constant integer MOON_HONEY = MOON_CHICK
constant integer WARDEN = 'Ewar'
// special elf heroes
constant integer SYLVANUS = 'Hvwd'
constant integer CENARIUS = 'Ecen'
constant integer ILLIDAN = 'Eevi'
constant integer ILLIDAN_DEMON = 'Eevm'
constant integer MAIEV = 'Ewrd'
// elf hero abilities
constant integer FORCE_NATURE = 'AEfn'
constant integer ENT_ROOTS = 'AEer'
constant integer THORNS_AURA = 'AEah'
constant integer TRANQUILITY = 'AEtq'
constant integer EVASION = 'AEev'
constant integer IMMOLATION = 'AEim'
constant integer MANA_BURN = 'AEmb'
constant integer METAMORPHOSIS = 'AEme'
constant integer SEARING_ARROWS = 'AHfa'
constant integer SCOUT = 'AEst'
constant integer STARFALL = 'AEsf'
constant integer TRUESHOT = 'AEar'
constant integer BLINK = 'AEbl'
constant integer FAN_KNIVES = 'AEfk'
constant integer SHADOW_TOUCH = 'AEsh'
constant integer VENGEANCE = 'AEsv'
// elf units
constant integer WISP = 'ewsp'
constant integer ARCHER = 'earc'
constant integer DRUID_TALON = 'edot'
constant integer DRUID_TALON_M = 'edtm'
constant integer BALLISTA = 'ebal'
constant integer DRUID_CLAW = 'edoc'
constant integer DRUID_CLAW_M = 'edcm'
constant integer DRYAD = 'edry'
constant integer HIPPO = 'ehip'
constant integer HIPPO_RIDER = 'ehpr'
constant integer HUNTRESS = 'esen'
constant integer CHIMAERA = 'echm'
constant integer ENT = 'efon'
constant integer MOUNTAIN_GIANT = 'emtg'
constant integer FAERIE_DRAGON = 'efdr'
// special elf units
constant integer HIGH_ARCHER = 'nhea'
constant integer HIGH_FOOTMAN = 'hcth'
constant integer HIGH_FOOTMEN = HIGH_FOOTMAN
constant integer HIGH_SWORDMAN = 'hhes'
constant integer DRAGON_HAWK = 'nws1'
constant integer CORRUPT_TREANT = 'nenc'
constant integer POISON_TREANT = 'nenp'
constant integer PLAGUE_TREANT = 'nepl'
constant integer SHANDRIS = 'eshd'
// elf buildings
constant integer ANCIENT_LORE = 'eaoe'
constant integer ANCIENT_WAR = 'eaom'
constant integer ANCIENT_WIND = 'eaow'
constant integer TREE_AGES = 'etoa'
constant integer TREE_ETERNITY = 'etoe'
constant integer TREE_LIFE = 'etol'
constant integer ANCIENT_PROTECT = 'etrp'
constant integer ELF_ALTAR = 'eate'
constant integer BEAR_DEN = 'edol'
constant integer CHIMAERA_ROOST = 'edos'
constant integer HUNTERS_HALL = 'edob'
constant integer MOON_WELL = 'emow'
constant integer ELF_MINE = 'egol'
constant integer DEN_OF_WONDERS = 'eden'
// special elf buildings
constant integer ELF_FARM = 'nefm'
constant integer ELF_GUARD_TOWER = 'negt'
constant integer HIGH_SKY = 'negm'
constant integer HIGH_EARTH = 'negf'
constant integer HIGH_TOWER = 'negt'
constant integer ELF_HIGH_BARRACKS = 'nheb'
constant integer CORRUPT_LIFE = 'nctl'
constant integer CORRUPT_WELL = 'ncmw'
constant integer CORRUPT_PROTECTOR = 'ncap'
constant integer CORRUPT_WAR = 'ncaw'
// elf upgrades
constant integer UPG_STR_MOON = 'Resm'
constant integer UPG_STR_WILD = 'Resw'
constant integer UPG_MOON_ARMOR = 'Rema'
constant integer UPG_HIDES = 'Rerh'
constant integer UPG_ULTRAVISION = 'Reuv'
constant integer UPG_BLESSING = 'Renb'
constant integer UPG_SCOUT = 'Resc'
constant integer UPG_GLAIVE = 'Remg'
constant integer UPG_BOWS = 'Reib'
constant integer UPG_MARKSMAN = 'Remk'
constant integer UPG_DRUID_TALON = 'Redt'
constant integer UPG_DRUID_CLAW = 'Redc'
constant integer UPG_ABOLISH = 'Resi'
constant integer UPG_CHIM_ACID = 'Recb'
constant integer UPG_HIPPO_TAME = 'Reht'
constant integer UPG_BOLT = 'Repd'
constant integer UPG_MARK_CLAW = 'Reeb'
constant integer UPG_MARK_TALON = 'Reec'
constant integer UPG_HARD_SKIN = 'Rehs'
constant integer UPG_RESIST_SKIN = 'Rers'
constant integer UPG_WELL_SPRING = 'Rews'
//--------------------------------------------------------------------
// Neutral
//--------------------------------------------------------------------
constant integer DEMON_GATE = 'ndmg'
constant integer FELLHOUND = 'nfel'
constant integer INFERNAL = 'ninf'
constant integer DOOMGUARD = 'nbal'
constant integer SATYR = 'nsty'
constant integer TRICKSTER = 'nsat'
constant integer SHADOWDANCER = 'nsts'
constant integer SOULSTEALER = 'nstl'
constant integer HELLCALLER = 'nsth'
constant integer SKEL_ARCHER = 'nska'
constant integer SKEL_MARKSMAN = 'nskm'
constant integer SKEL_BURNING = 'nskf'
constant integer SKEL_GIANT = 'nskg'
constant integer FURBOLG = 'nfrl'
constant integer FURBOLG_TRACKER = 'nfrb'
constant integer FURBOLG_SHAMAN = 'nfrs'
constant integer FURBOLG_CHAMP = 'nfrg'
constant integer FURBOLG_ELDER = 'nfre'
//--------------------------------------------------------------------
// NAGA
//--------------------------------------------------------------------
// naga heroes
constant integer NAGA_SORCERESS = 'Nngs'
constant integer NAGA_VASHJ = 'Hvsh'
// naga units
constant integer NAGA_DRAGON = 'nsnp' // old names
constant integer NAGA_WITCH = 'nnsw'
constant integer NAGA_SERPENT = 'nwgs'
constant integer NAGA_HYDRA = 'nhyc'
constant integer NAGA_SLAVE = 'nmpe' // peon
constant integer NAGA_SNAP_DRAGON = NAGA_DRAGON // weak ranged
constant integer NAGA_COUATL = NAGA_SERPENT // weak air
constant integer NAGA_SIREN = NAGA_WITCH // caster
constant integer NAGA_MYRMIDON = 'nmyr' // knight
constant integer NAGA_REAVER = 'nnmg' // footman
constant integer NAGA_TURTLE = NAGA_HYDRA // siege
constant integer NAGA_ROYAL = 'nnrg' // royal guard
// naga buildings
constant integer NAGA_TEMPLE = 'nntt' // town hall
constant integer NAGA_CORAL = 'nnfm' // farm
constant integer NAGA_SHRINE = 'nnsa' // sirens & couatls
constant integer NAGA_SPAWNING = 'nnsg' // myrm, snap dragon, hydra
constant integer NAGA_GUARDIAN = 'nntg' // tower
constant integer NAGA_ALTAR = 'nnad' // altar
// naga upgrades
constant integer UPG_NAGA_ARMOR = 'Rnam'
constant integer UPG_NAGA_ATTACK = 'Rnat'
constant integer UPG_NAGA_ABOLISH = 'Rnsi'
constant integer UPG_SIREN = 'Rnsw'
constant integer UPG_NAGA_ENSNARE = 'Rnen'
//--------------------------------------------------------------------
constant integer M1 = 60
constant integer M2 = 2*60
constant integer M3 = 3*60
constant integer M4 = 4*60
constant integer M5 = 5*60
constant integer M6 = 6*60
constant integer M7 = 7*60
constant integer M8 = 8*60
constant integer M9 = 9*60
constant integer M10 = 10*60
constant integer M11 = 11*60
constant integer M12 = 12*60
constant integer M13 = 13*60
constant integer M14 = 14*60
constant integer M15 = 15*60
constant integer EASY = 1
constant integer NORMAL = 2
constant integer HARD = 3
constant integer INSANE = 4 // not used
constant integer MELEE_NEWBIE = 1
constant integer MELEE_NORMAL = 2
constant integer MELEE_INSANE = 3
constant integer ATTACK_CAPTAIN = 1
constant integer DEFENSE_CAPTAIN = 2
constant integer BOTH_CAPTAINS = 3
constant integer BUILD_UNIT = 1
constant integer BUILD_UPGRADE = 2
constant integer BUILD_EXPAND = 3
constant integer UPKEEP_TIER1 = 50
constant integer UPKEEP_TIER2 = 80
//--------------------------------------------------------------------
player ai_player
integer sleep_seconds
integer total_gold = 0
integer total_wood = 0
integer gold_buffer = 0 // usually for potion money
integer difficulty = NORMAL
integer exp_seen = 0
integer racial_farm = 'hhou'
integer hero_id = 'Hamg'
integer hero_id2 = 'Hmkg'
integer hero_id3 = 'Hpal'
integer array skill
integer array skills1
integer array skills2
integer array skills3
integer max_hero_level = 0
integer array harass_qty
integer array harass_max
integer array harass_units
integer harass_length = 0
integer array defense_qty
integer array defense_units
integer defense_length = 0
integer array build_qty
integer array build_type
integer array build_item
integer array build_town
integer build_length = 0
integer campaign_gold_peons = 5
integer campaign_wood_peons = 3
integer campaign_basics_speed = 5
integer min_creeps = -1
integer max_creeps = -1
boolean harvest_town1 = true
boolean harvest_town2 = true
boolean harvest_town3 = true
boolean do_campaign_farms = true
boolean two_heroes = false
boolean allow_air_creeps = false
boolean take_exp = false
boolean allow_signal_abort = false
boolean ready_for_zeppelin = true
boolean get_zeppelin = false
boolean build_campaign_attackers = true
boolean do_debug_cheats = false
boolean trace_on = true
boolean zep_next_wave = false
boolean form_group_timeouts = true
endglobals
//============================================================================
function PlayerEx takes integer slot returns player
return Player(slot-1)
endfunction
//============================================================================
function Trace takes string message returns nothing
if trace_on then
call DisplayText(GetAiPlayer(),message)
endif
endfunction
//============================================================================
function TraceI takes string message, integer val returns nothing
if trace_on then
call DisplayTextI(GetAiPlayer(),message,val)
endif
endfunction
//============================================================================
function TraceII takes string message, integer v1, integer v2 returns nothing
if trace_on then
call DisplayTextII(GetAiPlayer(),message,v1,v2)
endif
endfunction
//============================================================================
function TraceIII takes string message, integer v1, integer v2, integer v3 returns nothing
if trace_on then
call DisplayTextIII(GetAiPlayer(),message,v1,v2,v3)
endif
endfunction
//============================================================================
function InitAI takes nothing returns nothing
set ai_player = Player(GetAiPlayer())
set sleep_seconds = 0
call StopGathering()
endfunction
//============================================================================
function StandardAI takes code heroes, code peons, code attacks returns nothing
local boolean isNewbie = (MeleeDifficulty() == MELEE_NEWBIE)
call InitAI()
call SetMeleeAI()
call SetDefendPlayer(true)
call SetGroupsFlee(not isNewbie)
call SetHeroesBuyItems(not isNewbie)
call SetHeroesFlee(true)
call SetHeroesTakeItems(true)
call SetIgnoreInjured(true)
call SetPeonsRepair(true)
call SetSmartArtillery(not isNewbie)
call SetTargetHeroes(not isNewbie)
call SetUnitsFlee(not isNewbie)
call SetWatchMegaTargets(true)
call CreateCaptains()
call SetHeroLevels(heroes)
call Sleep(0.1)
call StartThread(peons)
call StartThread(attacks)
endfunction
//============================================================================
// Utility Functions
//============================================================================
function Min takes integer A, integer B returns integer
if A < B then
return A
else
return B
endif
endfunction
function Max takes integer A, integer B returns integer
if A > B then
return A
else
return B
endif
endfunction
function SetZepNextWave takes nothing returns nothing
set zep_next_wave = true
endfunction
function SuicideSleep takes integer seconds returns nothing
set sleep_seconds = sleep_seconds - seconds
loop
exitwhen seconds <= 0
exitwhen allow_signal_abort and CommandsWaiting() != 0
if seconds >= 5 then
call Sleep(5)
set seconds = seconds - 5
else
call Sleep(seconds)
set seconds = 0
endif
endloop
endfunction
//============================================================================
function WaitForSignal takes nothing returns integer
local integer cmd
local boolean display = false //xxx
loop
exitwhen CommandsWaiting() != 0
//xxx
call Trace("waiting for a signal to begin AI script...\n")
set display = true
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
//xxx
endloop
//xxx
if display then
call Trace("signal received, beginning AI script\n")
endif
//xxx
set cmd = GetLastCommand()
call PopLastCommand()
return cmd
endfunction
//============================================================================
function SetWoodPeons takes integer count returns nothing
set campaign_wood_peons = count
endfunction
//============================================================================
function SetGoldPeons takes integer count returns nothing
set campaign_gold_peons = count
endfunction
//============================================================================
function SetHarvestLumber takes boolean harvest returns nothing
if harvest then
set campaign_wood_peons = 3
else
set campaign_wood_peons = 0
endif
endfunction
//============================================================================
function SetFormGroupTimeouts takes boolean state returns nothing
set form_group_timeouts = state
endfunction
//============================================================================
function DoCampaignFarms takes boolean state returns nothing
set do_campaign_farms = state
endfunction
//============================================================================
function GetMinorCreep takes nothing returns unit
return GetCreepCamp(0,9,false)
endfunction
//============================================================================
function GetMajorCreep takes nothing returns unit
return GetCreepCamp(10,100,allow_air_creeps)
endfunction
//============================================================================
function GetGold takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_GOLD)
endfunction
//============================================================================
function GetWood takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_LUMBER)
endfunction
//============================================================================
function InitBuildArray takes nothing returns nothing
set build_length = 0
endfunction
//============================================================================
function InitAssaultGroup takes nothing returns nothing
set harass_length = 0
endfunction
//============================================================================
function InitDefenseGroup takes nothing returns nothing
set defense_length = 0
endfunction
//============================================================================
function InitMeleeGroup takes nothing returns nothing
call InitAssaultGroup()
call RemoveInjuries()
call RemoveSiege()
endfunction
//============================================================================
function PrepFullSuicide takes nothing returns nothing
call InitAssaultGroup()
call InitDefenseGroup()
set campaign_gold_peons = 0
set campaign_wood_peons = 0
endfunction
//============================================================================
function SetReplacements takes integer easy, integer med, integer hard returns nothing
if difficulty == EASY then
call SetReplacementCount(easy)
elseif difficulty == NORMAL then
call SetReplacementCount(med)
else
call SetReplacementCount(hard)
endif
endfunction
//============================================================================
function StartTownBuilder takes code func returns nothing
call StartThread(func)
endfunction
//============================================================================
function SetBuildAll takes integer t, integer qty, integer unitid, integer town returns nothing
if qty > 0 then
set build_qty[build_length] = qty
set build_type[build_length] = t
set build_item[build_length] = unitid
set build_town[build_length] = town
set build_length = build_length + 1
endif
endfunction
//============================================================================
function SetBuildUnit takes integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_UNIT,qty,unitid,-1)
endfunction
//============================================================================
function SetBuildNext takes integer qty, integer unitid returns nothing
local integer has = GetUnitCount(unitid)
if has >= qty then
return
endif
call SetBuildAll(BUILD_UNIT,GetUnitCountDone(unitid)+1,unitid,-1)
endfunction
//============================================================================
function SetBuildUnitEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call SetBuildAll(BUILD_UNIT,easy,unitid,-1)
elseif difficulty == NORMAL then
call SetBuildAll(BUILD_UNIT,med,unitid,-1)
else
call SetBuildAll(BUILD_UNIT,hard,unitid,-1)
endif
endfunction
//============================================================================
function SecondaryTown takes integer town, integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_UNIT,qty,unitid,town)
endfunction
//============================================================================
function SecTown takes integer town, integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_UNIT,qty,unitid,town)
endfunction
//============================================================================
function SetBuildUpgr takes integer qty, integer unitid returns nothing
if MeleeDifficulty() != MELEE_NEWBIE or qty == 1 then
call SetBuildAll(BUILD_UPGRADE,qty,unitid,-1)
endif
endfunction
//============================================================================
function SetBuildUpgrEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call SetBuildAll(BUILD_UPGRADE,easy,unitid,-1)
elseif difficulty == NORMAL then
call SetBuildAll(BUILD_UPGRADE,med,unitid,-1)
else
call SetBuildAll(BUILD_UPGRADE,hard,unitid,-1)
endif
endfunction
//============================================================================
function SetBuildExpa takes integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_EXPAND,qty,unitid,-1)
endfunction
//============================================================================
function StartUpgrade takes integer level, integer upgid returns boolean
local integer gold_cost
local integer wood_cost
if GetUpgradeLevel(upgid) >= level then
return true
endif
set gold_cost = GetUpgradeGoldCost(upgid)
if total_gold < gold_cost then
return false
endif
set wood_cost = GetUpgradeWoodCost(upgid)
if total_wood < wood_cost then
return false
endif
return SetUpgrade(upgid)
endfunction
//============================================================================
function BuildFactory takes integer unitid returns nothing
if GetGold() > 1000 and GetWood() > 500 then
call SetBuildUnit( 2, unitid )
else
call SetBuildUnit( 1, unitid )
endif
endfunction
//============================================================================
function HallsCompleted takes integer unitid returns boolean
return GetUnitCount(unitid) == GetUnitCountDone(unitid)
endfunction
//============================================================================
function GuardSecondary takes integer townid, integer qty, integer unitid returns nothing
if TownHasHall(townid) and TownHasMine(townid) then
call SecondaryTown( townid, qty, unitid )
endif
endfunction
//============================================================================
function GetUnitCountEx takes integer unitid, boolean only_done, integer townid returns integer
if townid == -1 then
if only_done then
return GetUnitCountDone(unitid)
else
return GetUnitCount(unitid)
endif
else
return GetTownUnitCount(unitid,townid,only_done)
endif
endfunction
//============================================================================
function TownCountEx takes integer unitid, boolean only_done, integer townid returns integer
local integer have_qty = GetUnitCountEx(unitid,only_done,townid)
if unitid == TOWN_HALL then
set have_qty = have_qty + GetUnitCountEx(KEEP,false,townid) + GetUnitCountEx(CASTLE,false,townid)
elseif unitid == KEEP then
set have_qty = have_qty + GetUnitCountEx(CASTLE,false,townid)
elseif unitid == WATCH_TOWER then
set have_qty = have_qty + GetUnitCountEx(GUARD_TOWER,false,townid) + GetUnitCountEx(CANNON_TOWER,false,townid) + GetUnitCountEx(ARCANE_TOWER,false,townid)
elseif unitid == PEASANT then
set have_qty = have_qty + GetUnitCountEx(MILITIA,false,townid)
elseif unitid == GREAT_HALL then
set have_qty = have_qty + GetUnitCountEx(STRONGHOLD,false,townid) + GetUnitCountEx(FORTRESS,false,townid)
elseif unitid == STRONGHOLD then
set have_qty = have_qty + GetUnitCountEx(FORTRESS,false,townid)
elseif unitid == HEAD_HUNTER then
set have_qty = have_qty + GetUnitCountEx(BERSERKER,false,townid)
elseif unitid == SPIRIT_WALKER then
set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER_M,false,townid)
elseif unitid == SPIRIT_WALKER_M then
set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER,only_done,townid)
elseif unitid == NECROPOLIS_1 then
set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_2,false,townid) + GetUnitCountEx(NECROPOLIS_3,false,townid)
elseif unitid == NECROPOLIS_2 then
set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_3,false,townid)
elseif unitid == ZIGGURAT_1 then
set have_qty = have_qty + GetUnitCountEx(ZIGGURAT_2,false,townid) + GetUnitCountEx(ZIGGURAT_FROST,false,townid)
elseif unitid == GARGOYLE then
set have_qty = have_qty + GetUnitCountEx(GARGOYLE_MORPH,false,townid)
elseif unitid == TREE_LIFE then
set have_qty = have_qty + GetUnitCountEx(TREE_AGES,false,townid) + GetUnitCountEx(TREE_ETERNITY,false,townid)
elseif unitid == TREE_AGES then
set have_qty = have_qty + GetUnitCountEx(TREE_ETERNITY,false,townid)
elseif unitid == DRUID_TALON then
set have_qty = have_qty + GetUnitCountEx(DRUID_TALON_M,false,townid)
elseif unitid == DRUID_TALON_M then
set have_qty = have_qty + GetUnitCountEx(DRUID_TALON,only_done,townid)
elseif unitid == DRUID_CLAW then
set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW_M,false,townid)
elseif unitid == DRUID_CLAW_M then
set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW,only_done,townid)
elseif unitid == ILLIDAN then
set have_qty = have_qty + GetUnitCountEx(ILLIDAN_DEMON,false,townid)
endif
return have_qty
endfunction
//============================================================================
function TownCountDone takes integer base returns integer
return TownCountEx(base,true,-1)
endfunction
//============================================================================
function TownCount takes integer base returns integer
return TownCountEx(base,false,-1)
endfunction
//============================================================================
function BasicExpansion takes boolean build_it, integer unitid returns nothing
if build_it and HallsCompleted(unitid) then
call SetBuildExpa( TownCount(unitid)+1, unitid )
endif
endfunction
//============================================================================
function UpgradeAll takes integer baseid, integer newid returns nothing
call SetBuildUnit( TownCountDone(baseid), newid )
endfunction
//============================================================================
function TownCountTown takes integer base, integer townid returns integer
return TownCountEx(base,false,townid)
endfunction
//============================================================================
// FoodPool
//============================================================================
function FoodPool takes integer food, boolean weak, integer id1, integer use1, boolean strong, integer id2, integer use2 returns nothing
if strong then
call SetBuildUnit( (food - use1 * TownCount(id1)) / use2, id2 )
elseif weak then
call SetBuildUnit( (food - use2 * TownCount(id2)) / use1, id1 )
endif
endfunction
//============================================================================
// MeleeTownHall
//============================================================================
function MeleeTownHall takes integer townid, integer unitid returns nothing
if TownHasMine(townid) and not TownHasHall(townid) then
call SecondaryTown ( townid, 1, unitid )
endif
endfunction
//============================================================================
function WaitForUnits takes integer unitid, integer qty returns nothing
loop
exitwhen TownCountDone(unitid) == qty
call Sleep(2)
endloop
endfunction
//============================================================================
function StartUnit takes integer ask_qty, integer unitid, integer town returns boolean
local integer have_qty
local integer need_qty
local integer afford_gold
local integer afford_wood
local integer afford_qty
local integer gold_cost
local integer wood_cost
//------------------------------------------------------------------------
// if we have all we're asking for then make nothing
//
if town == -1 then
set have_qty = TownCount(unitid)
else
set have_qty = TownCountTown(unitid,town)
endif
if have_qty >= ask_qty then
return true
endif
set need_qty = ask_qty - have_qty
//------------------------------------------------------------------------
// limit the qty we're requesting to the amount of resources available
//
set gold_cost = GetUnitGoldCost(unitid)
set wood_cost = GetUnitWoodCost(unitid)
if gold_cost == 0 then
set afford_gold = need_qty
else
set afford_gold = total_gold / gold_cost
endif
if afford_gold < need_qty then
set afford_qty = afford_gold
else
set afford_qty = need_qty
endif
if wood_cost == 0 then
set afford_wood = need_qty
else
set afford_wood = total_wood / wood_cost
endif
if afford_wood < afford_qty then
set afford_qty = afford_wood
endif
// if we're waiting on gold/wood; pause build orders
if afford_qty < 1 then
return false
endif
//------------------------------------------------------------------------
// whether we make right now what we're requesting or not, assume we will
// and deduct the cost of the units from our fake gold total right away
//
set total_gold = total_gold - gold_cost * need_qty
set total_wood = total_wood - wood_cost * need_qty
if total_gold < 0 then
set total_gold = 0
endif
if total_wood < 0 then
set total_wood = 0
endif
//------------------------------------------------------------------------
// give the AI a chance to make the units (it may not be able to right now
// but that doesn't stop us from trying other units after this as long
// as we have enough money to make this AND the needed, unbuilt ones)
//
return SetProduce(afford_qty,unitid,town)
endfunction
//============================================================================
function WaitForTown takes integer towns, integer townid returns nothing
local integer i = 0
loop
call Sleep(10)
exitwhen TownCount(townid) >= towns
set i = i + 1
exitwhen i == 12
endloop
endfunction
//============================================================================
function StartExpansion takes integer qty, integer hall returns boolean
local integer count
local integer town
local unit peon
local integer gold_cost
set count = TownCount(hall)
if count >= qty then
return true
endif
set town = GetNextExpansion()
if town == -1 then
return true
endif
set take_exp = true
set gold_cost = GetUnitGoldCost(hall)
if gold_cost > total_gold then
return false
endif
set total_gold = total_gold - gold_cost
if GetExpansionFoe() != null then
return true
endif
set peon = GetExpansionPeon()
if peon != null then
return SetExpansion(peon,hall)
endif
return true
endfunction
//============================================================================
function OneBuildLoop takes nothing returns nothing
local integer index = 0
local integer qty
local integer id
local integer tp
set total_gold = GetGold() - gold_buffer
set total_wood = GetWood()
loop
exitwhen index == build_length
set qty = build_qty [index]
set id = build_item[index]
set tp = build_type[index]
//--------------------------------------------------------------------
if tp == BUILD_UNIT then
if not StartUnit(qty,id,build_town[index]) then
return
endif
//--------------------------------------------------------------------
elseif tp == BUILD_UPGRADE then
call StartUpgrade(qty,id)
//--------------------------------------------------------------------
else // tp == BUILD_EXPAND
if not StartExpansion(qty,id) then
return
endif
endif
set index = index + 1
endloop
endfunction
//============================================================================
function StaggerSleep takes real base, real spread returns nothing
call Sleep(base + spread * I2R(GetAiPlayer()) / I2R(GetPlayers()))
endfunction
//============================================================================
function BuildLoop takes nothing returns nothing
call OneBuildLoop()
call StaggerSleep(1,2)
loop
call OneBuildLoop()
call Sleep(2)
endloop
endfunction
//============================================================================
function StartBuildLoop takes nothing returns nothing
call StartThread(function BuildLoop)
endfunction
//============================================================================
function SetInitialWave takes integer seconds returns nothing
set sleep_seconds = seconds
endfunction
//============================================================================
function AddSleepSeconds takes integer seconds returns nothing
set sleep_seconds = sleep_seconds + seconds
endfunction
//============================================================================
function SleepForever takes nothing returns nothing
call Trace("going to sleep forever\n") //xxx
loop
call Sleep(100)
endloop
endfunction
//============================================================================
function PlayGame takes nothing returns nothing
call StartBuildLoop()
call SleepForever()
endfunction
//============================================================================
function ConvertNeeds takes integer unitid returns nothing
if GetUnitCount(unitid) < 1 then
call StartUnit(1,unitid,-1)
endif
endfunction
//============================================================================
function Conversions takes integer desire, integer unitid returns nothing
if GetUnitCount(unitid) >= desire then
return
endif
if unitid == HIPPO_RIDER then
call ConvertNeeds(ARCHER)
call ConvertNeeds(HIPPO)
call MergeUnits(desire,ARCHER,HIPPO,HIPPO_RIDER)
elseif unitid == BLK_SPHINX then
call ConvertNeeds(OBS_STATUE)
call ConvertUnits(desire,OBS_STATUE)
endif
endfunction
//============================================================================
function SetAssaultGroup takes integer qty, integer max, integer unitid returns nothing
call Conversions(max,unitid)
if qty <= 0 and TownCountDone(unitid) == 0 then
return
endif
set harass_qty[harass_length] = qty
set harass_max[harass_length] = max
set harass_units[harass_length] = unitid
set harass_length = harass_length + 1
endfunction
//============================================================================
function Interleave3 takes integer e1, integer m1, integer h1, integer u1, integer e2, integer m2, integer h2, integer u2, integer e3, integer m3, integer h3, integer u3 returns nothing
local integer i1 = 1
local integer i2 = 1
local integer i3 = 1
local integer q1
local integer q2
local integer q3
if difficulty == EASY then
set q1 = e1
set q2 = e2
set q3 = e3
elseif difficulty == NORMAL then
set q1 = m1
set q2 = m2
set q3 = m3
else // difficulty == HARD
set q1 = h1
set q2 = h2
set q3 = h3
endif
loop
exitwhen q1<=0 and q2<=0 and q3<=0
if q1 > 0 then
call SetAssaultGroup(i1,i1,u1)
set q1 = q1 - 1
set i1 = i1 + 1
endif
if q2 > 0 then
call SetAssaultGroup(i2,i2,u2)
set q2 = q2 - 1
set i2 = i2 + 1
endif
if q3 > 0 then
call SetAssaultGroup(i3,i3,u3)
set q3 = q3 - 1
set i3 = i3 + 1
endif
endloop
endfunction
//============================================================================
function SetMeleeGroup takes integer unitid returns nothing
if unitid == hero_id then
call SetAssaultGroup(1,9,unitid)
else
call SetAssaultGroup((TownCountDone(unitid)*3)/4,20,unitid)
endif
endfunction
//============================================================================
function CampaignDefender takes integer level, integer qty, integer unitid returns nothing
if qty > 0 and difficulty >= level then
set defense_qty[defense_length] = qty
set defense_units[defense_length] = unitid
set defense_length = defense_length + 1
call Conversions(qty,unitid)
call SetBuildUnit(qty,unitid)
endif
endfunction
//============================================================================
function CampaignDefenderEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call CampaignDefender(EASY,easy,unitid)
elseif difficulty == NORMAL then
call CampaignDefender(NORMAL,med,unitid)
else
call CampaignDefender(HARD,hard,unitid)
endif
endfunction
//============================================================================
function CampaignAttacker takes integer level, integer qty, integer unitid returns nothing
if qty > 0 and difficulty >= level then
call SetAssaultGroup(qty,qty,unitid)
endif
endfunction
//============================================================================
function CampaignAttackerEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call CampaignAttacker(EASY,easy,unitid)
elseif difficulty == NORMAL then
call CampaignAttacker(NORMAL,med,unitid)
else
call CampaignAttacker(HARD,hard,unitid)
endif
endfunction
//============================================================================
function FormGroup takes integer seconds, boolean testReady returns nothing
local integer index
local integer count
local integer unitid
local integer desire
local integer readyPercent
// normally test for CaptainReadiness() of 50%
if testReady == true then
set readyPercent = 50
call Trace("forming group, requiring healthy guys\n") //xxx
else
set readyPercent = 0
call Trace("forming group, unit health not important\n") //xxx
endif
call Trace("trying to gather forces\n") //xxx
loop
call SuicideSleep(seconds)
call InitAssault()
set index = 0
loop
exitwhen index == harass_length
set unitid = harass_units[index]
set desire = harass_max[index]
set count = TownCountDone(unitid)
call Conversions(desire,unitid)
if count >= desire then
call AddAssault(desire,unitid)
else
set desire = harass_qty[index]
if count < desire then
call AddAssault(desire,unitid)
else
call AddAssault(count,unitid)
endif
endif
set index = index + 1
endloop
//xxx
if form_group_timeouts and (sleep_seconds < -60) then
call Trace("exit form group -- timeout\n")
elseif CaptainInCombat(true) then
call Trace("exit form group -- can't form while already in combat\n")
elseif CaptainIsFull() and CaptainReadiness() >= readyPercent then
call Trace("exit form group -- ready\n")
endif
//xxx
// time out and send group anyway if time has already expired
exitwhen form_group_timeouts and (sleep_seconds < -60)
exitwhen CaptainInCombat(true)
exitwhen CaptainIsFull() and CaptainReadiness() >= readyPercent
endloop
endfunction
//============================================================================
function WavePrepare takes integer unitid returns integer
return GetUnitBuildTime(unitid)
endfunction
//============================================================================
function PrepTime takes nothing returns integer
local integer unitid
local integer missing
local integer prep
local integer count
local integer largest = 30
local integer index = 0
loop
exitwhen index == harass_length
set unitid = harass_units[index]
set missing = harass_qty[index] + IgnoredUnits(unitid) - TownCount(unitid)
set prep = WavePrepare(unitid) * missing
if prep > largest then
set largest = prep
endif
set index = index + 1
endloop
call TraceI("next wave will require around %d seconds to build and gather\n",largest) //xxx
return largest
endfunction
//============================================================================
function PrepSuicideOnPlayer takes integer seconds returns boolean
local integer wave_prep = PrepTime()
local integer save_length
set save_length = harass_length
set harass_length = 0
call AddSleepSeconds(seconds)
if sleep_seconds-wave_prep > 0 then
call TraceI("going to sleep for %d seconds before gathering next attack wave\n",sleep_seconds-wave_prep) //xxx
call SuicideSleep(sleep_seconds-wave_prep)
endif
call Trace("preparing suicide attack wave\n") //xxx
set harass_length = save_length
if harass_length < 1 then
call Trace("ERROR - no units specificed, exiting early\n") //xxx
return false
endif
return true
endfunction
//============================================================================
function SleepUntilAtGoal takes nothing returns nothing
loop
exitwhen CaptainRetreating()
exitwhen CaptainAtGoal() // reached goal
exitwhen CaptainIsHome() // failed to path and returned home
exitwhen CaptainIsEmpty() // all units died
call SuicideSleep(3)
endloop
endfunction
//============================================================================
function SleepInCombat takes nothing returns nothing
local integer count = 0
debug call Trace("SleepInCombat\n")
loop
loop
exitwhen not CaptainInCombat(true) // goal is cleared
exitwhen CaptainIsEmpty() // duh
call SuicideSleep(1)
endloop
set count = count + 1
exitwhen count >= 8
//xxx this is what it should have been; do this for next patch?
//call SuicideSleep(1)
endloop
debug call Trace("exit SleepInCombat\n")
endfunction
//============================================================================
function AttackMoveXYA takes integer x, integer y returns nothing
if zep_next_wave then
call LoadZepWave(x,y)
set zep_next_wave = false
endif
call AttackMoveXY(x,y)
call SleepUntilAtGoal()
call SleepInCombat()
endfunction
//============================================================================
function SuicideOnPlayerWave takes nothing returns nothing
call Trace("waiting for attack wave to enter combat\n") //xxx
loop
//xxx
if allow_signal_abort and CommandsWaiting() != 0 then
call Trace("ABORT -- attack wave override\n")
endif
if CaptainInCombat(true) then
call Trace("done - captain has entered combat\n")
endif
if CaptainIsEmpty() then
call Trace("done - all units are dead\n")
endif
if sleep_seconds < -300 then
call Trace("done - timeout, took too long to reach engage the enemy\n")
endif
//xxx
exitwhen allow_signal_abort and CommandsWaiting() != 0
exitwhen CaptainInCombat(true)
exitwhen CaptainIsEmpty()
call SuicideSleep(10)
exitwhen sleep_seconds < -300
endloop
call Trace("waiting for attack wave to die\n") //xxx
loop
//xxx
if allow_signal_abort and CommandsWaiting() != 0 then
call Trace("ABORT - attack wave override\n")
endif
if CaptainIsEmpty() then
call Trace("done - all units are dead\n")
endif
if sleep_seconds < -300 then
call Trace("done - timeout, took too long to reach engage the enemy\n")
endif
//xxx
exitwhen allow_signal_abort and CommandsWaiting() != 0
exitwhen CaptainIsEmpty()
call SuicideSleep(10)
exitwhen sleep_seconds < -300
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function CommonSuicideOnPlayer takes boolean standard, boolean bldgs, integer seconds, player p, integer x, integer y returns nothing
local integer save_peons
if not PrepSuicideOnPlayer(seconds) then
return
endif
set save_peons = campaign_wood_peons
set campaign_wood_peons = 0
loop
//xxx
if allow_signal_abort and CommandsWaiting() != 0 then
call Trace("ABORT -- attack wave override\n")
endif
//xxx
exitwhen allow_signal_abort and CommandsWaiting() != 0
loop
exitwhen allow_signal_abort and CommandsWaiting() != 0
call FormGroup(5,true)
exitwhen sleep_seconds <= 0
call TraceI("waiting %d seconds before suicide\n",sleep_seconds) //xxx
endloop
if standard then
if bldgs then
exitwhen SuicidePlayer(p,sleep_seconds >= -60)
else
exitwhen SuicidePlayerUnits(p,sleep_seconds >= -60)
endif
else
call AttackMoveXYA(x,y)
endif
call TraceI("waiting %d seconds before timeout\n",60+sleep_seconds) //xxx
call SuicideSleep(5)
endloop
set campaign_wood_peons = save_peons
set harass_length = 0
call SuicideOnPlayerWave()
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPlayer takes integer seconds, player p returns nothing
call CommonSuicideOnPlayer(true,true,seconds,p,0,0)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnUnits takes integer seconds, player p returns nothing
call CommonSuicideOnPlayer(true,false,seconds,p,0,0)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPoint takes integer seconds, player p, integer x, integer y returns nothing
call CommonSuicideOnPlayer(false,false,seconds,p,x,y)
endfunction
//============================================================================
function SuicideUntilSignal takes integer seconds, player p returns nothing
local integer save
local integer wave_prep = PrepTime()
loop
call AddSleepSeconds(seconds)
if sleep_seconds-wave_prep > 0 then
call SuicideSleep(sleep_seconds-wave_prep)
endif
set save = campaign_wood_peons
set campaign_wood_peons = 0
loop
loop
call FormGroup(5, true)
exitwhen sleep_seconds <= 0
exitwhen CommandsWaiting() != 0
endloop
exitwhen SuicidePlayer(p,sleep_seconds >= -60)
exitwhen CommandsWaiting() != 0
call SuicideSleep(3)
endloop
set campaign_wood_peons = save
loop
exitwhen CaptainIsEmpty()
exitwhen CommandsWaiting() != 0
call SuicideSleep(5)
endloop
exitwhen CommandsWaiting() != 0
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnce takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call SuicideUnit(easy,unitid)
elseif difficulty == NORMAL then
call SuicideUnit(med,unitid)
else
call SuicideUnit(hard,unitid)
endif
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnitA takes integer unitid returns nothing
if unitid != 0 then
call SuicideUnit(1,unitid)
endif
call Sleep(0.1)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnitB takes integer unitid, integer playerid returns nothing
if unitid != 0 then
call SuicideUnitEx(1,unitid,playerid)
endif
call Sleep(0.1)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnits takes integer u1, integer u2, integer u3, integer u4, integer u5, integer u6, integer u7, integer u8, integer u9, integer uA returns nothing
call Trace("MASS SUICIDE - this script is now technically done\n") //xxx
call PrepFullSuicide()
loop
call SuicideUnitA(u1)
call SuicideUnitA(u2)
call SuicideUnitA(u3)
call SuicideUnitA(u4)
call SuicideUnitA(u5)
call SuicideUnitA(u6)
call SuicideUnitA(u7)
call SuicideUnitA(u8)
call SuicideUnitA(u9)
call SuicideUnitA(uA)
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnitsEx takes integer playerid, integer u1, integer u2, integer u3, integer u4, integer u5, integer u6, integer u7, integer u8, integer u9, integer uA returns nothing
call Trace("MASS SUICIDE - this script is now technically done\n") //xxx
call PrepFullSuicide()
loop
call SuicideUnitB(u1,playerid)
call SuicideUnitB(u2,playerid)
call SuicideUnitB(u3,playerid)
call SuicideUnitB(u4,playerid)
call SuicideUnitB(u5,playerid)
call SuicideUnitB(u6,playerid)
call SuicideUnitB(u7,playerid)
call SuicideUnitB(u8,playerid)
call SuicideUnitB(u9,playerid)
call SuicideUnitB(uA,playerid)
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPlayerEx takes integer easy, integer med, integer hard, player p returns nothing
if difficulty == EASY then
call SuicideOnPlayer(easy,p)
elseif difficulty == NORMAL then
call SuicideOnPlayer(med,p)
else
call SuicideOnPlayer(hard,p)
endif
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnUnitsEx takes integer easy, integer med, integer hard, player p returns nothing
if difficulty == EASY then
call SuicideOnUnits(easy,p)
elseif difficulty == NORMAL then
call SuicideOnUnits(med,p)
else
call SuicideOnUnits(hard,p)
endif
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPointEx takes integer easy, integer med, integer hard, player p, integer x, integer y returns nothing
if difficulty == EASY then
call SuicideOnPoint(easy,p,x,y)
elseif difficulty == NORMAL then
call SuicideOnPoint(med,p,x,y)
else
call SuicideOnPoint(hard,p,x,y)
endif
endfunction
//============================================================================
function ForeverSuicideOnPlayer takes integer seconds, player p returns nothing
local integer length = harass_length
loop
exitwhen allow_signal_abort and CommandsWaiting() != 0
call SuicideOnPlayer(seconds,p)
set harass_length = length
endloop
endfunction
//============================================================================
function CommonSleepUntilTargetDead takes unit target, boolean reform returns nothing
loop
exitwhen CaptainRetreating()
exitwhen CaptainReadinessHP() <= 40
exitwhen not UnitAlive(target)
exitwhen UnitInvis(target) and not IsUnitDetected(target,ai_player)
if not TownThreatened() then
call AttackMoveKill(target)
endif
call SuicideSleep(3)
if reform and sleep_seconds < -40 then
if CaptainInCombat(true) then
set sleep_seconds = sleep_seconds + 5
else
set sleep_seconds = 0
call FormGroup(1,false)
endif
endif
endloop
endfunction
//============================================================================
function SleepUntilTargetDead takes unit target returns nothing
call CommonSleepUntilTargetDead(target,false)
endfunction
//============================================================================
function ReformUntilTargetDead takes unit target returns nothing
debug call Trace("ReformUntilTargetDead\n")
call CommonSleepUntilTargetDead(target,true)
endfunction
//============================================================================
function AttackMoveKillA takes unit target returns nothing
if target == null then
call SuicideSleep(3)
return
endif
debug call Trace("AttackMoveKillA\n")
call AttackMoveKill(target)
call ReformUntilTargetDead(target)
call SleepInCombat()
endfunction
//============================================================================
function MinorCreepAttack takes nothing returns nothing
local unit target = GetMinorCreep()
call SetAllianceTarget(target)
call FormGroup(3, true)
call AttackMoveKillA(target)
endfunction
//============================================================================
function MajorCreepAttack takes nothing returns nothing
local unit target = GetMajorCreep()
call SetAllianceTarget(target)
call FormGroup(3,true)
call AttackMoveKillA(target)
endfunction
//============================================================================
function CreepAttackEx takes nothing returns nothing
local unit target = GetCreepCamp(min_creeps,max_creeps,allow_air_creeps)
call SetAllianceTarget(target)
call FormGroup(3,true)
call AttackMoveKillA(target)
endfunction
//============================================================================
function AnyPlayerAttack takes nothing returns nothing
local unit hall
set hall = GetEnemyExpansion()
if hall == null then
call StartGetEnemyBase()
loop
exitwhen not WaitGetEnemyBase()
call SuicideSleep(1)
endloop
set hall = GetEnemyBase()
endif
call SetAllianceTarget(hall)
call FormGroup(3,true)
call AttackMoveKillA(hall)
endfunction
//============================================================================
function ExpansionAttack takes nothing returns nothing
local unit creep = GetExpansionFoe()
local integer x
call FormGroup(3, true)
if creep == null then
set x = GetExpansionX()
if x != -1 then
call AttackMoveXYA(x,GetExpansionY())
endif
else
call AttackMoveKillA(creep)
endif
endfunction
//============================================================================
// AddSiege
//============================================================================
function AddSiege takes nothing returns nothing
call SetAssaultGroup( 0, 9, SHADE )
call SetAssaultGroup( 0, 9, MEAT_WAGON )
call SetAssaultGroup( 0, 9, MORTAR )
call SetAssaultGroup( 0, 9, TANK )
call SetAssaultGroup( 0, 9, BALLISTA )
call SetAssaultGroup( 0, 9, CATAPULT )
endfunction
//===========================================================================
// GetAllyCount
//============================================================================
function GetAllyCount takes player whichPlayer returns integer
local integer playerIndex = 0
local integer count = 0
local player indexPlayer
loop
set indexPlayer = Player(playerIndex)
if whichPlayer != indexPlayer then
if GetPlayerAlliance(whichPlayer,indexPlayer,ALLIANCE_PASSIVE) then
if GetPlayerAlliance(indexPlayer,whichPlayer,ALLIANCE_PASSIVE) then
if GetPlayerStructureCount(indexPlayer,true) > 0 then
set count = count + 1
endif
endif
endif
endif
set playerIndex = playerIndex + 1
exitwhen playerIndex == 12
endloop
return count
endfunction
//============================================================================
// SingleMeleeAttack
//============================================================================
function SingleMeleeAttack takes boolean needs_exp, boolean has_siege, boolean major_ok, boolean air_units returns nothing
local boolean can_siege
local real daytime
local unit hall
local unit mega
local unit creep
local unit common
local integer minimum
local boolean allies
call Trace("===SingleMeleeAttack===\n") //xxx
if TownThreatened() then
call Trace("sleep 2, town threatened\n") //xxx
call Sleep(2)
return
endif
// purchase zeppelins
//
if get_zeppelin and GetGold() > 300 and GetWood() > 100 then
call Trace("purchase zep\n") //xxx
call PurchaseZeppelin()
set get_zeppelin = false
set ready_for_zeppelin = false
return
endif
set ready_for_zeppelin = true
// coordinate with allies
//
set allies = GetAllyCount(ai_player) > 0
if allies and MeleeDifficulty() != MELEE_NEWBIE then
set common = GetAllianceTarget()
if common != null then
call Trace("join ally force\n") //xxx
if GetMegaTarget() != null then
call AddSiege()
endif
call FormGroup(3,true)
call AttackMoveKillA(common)
call SetAllianceTarget(null)
return
endif
endif
// take expansions as needed
//
if needs_exp then
call Trace("needs exp\n") //xxx
set creep = GetExpansionFoe()
if creep != null then
call Trace("attack exp\n") //xxx
call SetAllianceTarget(creep)
call FormGroup(3,true)
call AttackMoveKillA(creep)
call Sleep(20)
set take_exp = false
return
endif
endif
// all-out attack if the player is weak
//
if MeleeDifficulty() != MELEE_NEWBIE then
set mega = GetMegaTarget()
if mega != null then
call Trace("MEGA TARGET!!!\n") //xxx
call AddSiege()
call FormGroup(3,true)
call AttackMoveKillA(mega)
return
endif
endif
// deny player an expansion
//
set hall = GetEnemyExpansion()
set daytime = GetFloatGameState(GAME_STATE_TIME_OF_DAY)
set can_siege = has_siege and (air_units or (daytime>=4 and daytime<=12))
if hall!=null and (can_siege or not IsTowered(hall)) then
call Trace("test player town attack\n") //xxx
if MeleeDifficulty() == MELEE_NEWBIE then
set minimum = 3
elseif allies and MeleeDifficulty() == MELEE_NORMAL then
set minimum = 1
else
set minimum = 0 // HARD, INSANE, and NORMAL with no allies
endif
if exp_seen >= minimum then
call Trace("do player town attack\n") //xxx
set exp_seen = 0
call AddSiege()
call SetAllianceTarget(hall)
call FormGroup(3,true)
call AttackMoveKillA(hall)
return
endif
set exp_seen = exp_seen + 1
endif
// attack player's main base when siege is available
//
if can_siege then
call Trace("attack player's town\n") //xxx
call AddSiege()
call AnyPlayerAttack()
return
endif
// extended, more specific method of determining creep levels
//
if min_creeps != -1 then
call TraceI("custom creep attack %d\n",max_creeps) //xxx
call CreepAttackEx()
return
endif
// nothing better to do, so kill a creep camp
//
if major_ok then
call Trace("major creep attack\n") //xxx
call MajorCreepAttack()
return
endif
call Trace("minor creep attack\n") //xxx
call MinorCreepAttack()
endfunction
//============================================================================
function GetZeppelin takes nothing returns nothing
if ready_for_zeppelin then
set get_zeppelin = true
endif
endfunction
//============================================================================
function FoodUsed takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_FOOD_USED)
endfunction
//============================================================================
function FoodCap takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_FOOD_CAP)
endfunction
//============================================================================
function FoodSpace takes nothing returns integer
return FoodCap() - FoodUsed()
endfunction
//============================================================================
function FoodAvail takes integer base returns integer
return GetFoodMade(racial_farm) * TownCount(racial_farm) + GetFoodMade(base) * TownCount(base)
endfunction
//============================================================================
function BuildAttackers takes nothing returns nothing
local integer index = 0
local integer unitid
local integer desire
local integer count
loop
exitwhen index == harass_length
set unitid = harass_units[index]
set desire = harass_qty[index] + IgnoredUnits(unitid)
set count = TownCount(unitid)
if count != desire then
if not StartUnit(desire,unitid,-1) then
return
endif
endif
set index = index + 1
endloop
endfunction
//============================================================================
function BuildDefenders takes nothing returns nothing
local integer index = 0
local integer unitid
local integer qty
loop
exitwhen index == defense_length
set unitid = defense_units[index]
set qty = defense_qty[index]
call Conversions(qty,unitid)
call AddDefenders(qty,unitid)
set index = index + 1
endloop
endfunction
//============================================================================
function CampaignBasicsA takes nothing returns nothing
local integer food_each = GetFoodMade(racial_farm)
local integer on_wood
call ClearHarvestAI()
if CaptainInCombat(false) then
set on_wood = 0
else
set on_wood = campaign_wood_peons
endif
call HarvestGold(0,campaign_gold_peons)
call HarvestWood(0,on_wood)
if harvest_town1 then
call HarvestGold(1,campaign_gold_peons)
call HarvestWood(1,on_wood)
endif
if harvest_town2 then
call HarvestGold(2,campaign_gold_peons)
call HarvestWood(2,on_wood)
endif
if harvest_town3 then
call HarvestGold(3,campaign_gold_peons)
call HarvestWood(3,on_wood)
endif
if do_campaign_farms and FoodUsed()+food_each-1 > food_each*(TownCount(racial_farm)+1) then
call StartUnit(TownCount(racial_farm)+1,racial_farm,-1)
endif
if build_campaign_attackers then
call BuildAttackers()
endif
if not CaptainInCombat(false) then
call BuildDefenders()
endif
call FillGuardPosts()
call ReturnGuardPosts()
endfunction
//============================================================================
function CampaignBasics takes nothing returns nothing
call Sleep(1)
call CampaignBasicsA()
call StaggerSleep(1,5)
loop
call CampaignBasicsA()
call Sleep(campaign_basics_speed)
endloop
endfunction
//============================================================================
function CampaignAI takes integer farms, code heroes returns nothing
if GetGameDifficulty() == MAP_DIFFICULTY_EASY then
set difficulty = EASY
call SetTargetHeroes(false)
call SetUnitsFlee(false)
elseif GetGameDifficulty() == MAP_DIFFICULTY_NORMAL then
set difficulty = NORMAL
call SetTargetHeroes(false)
call SetUnitsFlee(false)
elseif GetGameDifficulty() == MAP_DIFFICULTY_HARD then
set difficulty = HARD
call SetPeonsRepair(true)
else
set difficulty = INSANE
endif
call InitAI()
call InitBuildArray()
call InitAssaultGroup()
call CreateCaptains()
call SetNewHeroes(false)
if heroes != null then
call SetHeroLevels(heroes)
endif
call SetHeroesFlee(false)
call SetGroupsFlee(false)
call SetSlowChopping(true)
call GroupTimedLife(false)
call SetCampaignAI()
call Sleep(0.1)
set racial_farm = farms
call StartThread(function CampaignBasics)
call StartBuildLoop()
endfunction
//============================================================================
function UnsummonAll takes nothing returns nothing
local unit bldg
loop
set bldg = GetBuilding(ai_player)
exitwhen bldg==null
call Unsummon(bldg)
call Sleep(2)
endloop
endfunction
//============================================================================
// SkillArrays
//============================================================================
function SkillArrays takes nothing returns integer
local integer level = GetHeroLevelAI()
if level > max_hero_level then
set max_hero_level = level
endif
if GetHeroId() == hero_id then
return skills1[level]
elseif GetHeroId() == hero_id2 then
return skills2[level]
else
return skills3[level]
endif
endfunction
//--------------------------------------------------------------------------------------------------
// SetSkillArray
//--------------------------------------------------------------------------------------------------
function SetSkillArray takes integer index, integer id returns nothing
local integer i = 1
if index == 1 then
if hero_id != id then
return
endif
loop
set skills1[i] = skill[i]
exitwhen i == 10
set i = i + 1
endloop
elseif index == 2 then
if hero_id2 != id then
return
endif
loop
set skills2[i] = skill[i]
exitwhen i == 10
set i = i + 1
endloop
else
if hero_id3 != id then
return
endif
loop
set skills3[i] = skill[i]
exitwhen i == 10
set i = i + 1
endloop
endif
endfunction
//============================================================================
// AwaitMeleeHeroes
//============================================================================
function AwaitMeleeHeroes takes nothing returns nothing
if GetUnitCountDone(hero_id2) > 0 then
set two_heroes = true
endif
loop
exitwhen GetUnitCountDone(hero_id)>0 and (take_exp or (not two_heroes or GetUnitCountDone(hero_id2)>0))
call Sleep(1)
endloop
endfunction
//============================================================================
// PickMeleeHero
//============================================================================
function PickMeleeHero takes race raceid returns integer
local integer first
local integer second
local integer third
local integer last
local integer array heroes
//------------------------------------------------------------------------
if raceid == RACE_HUMAN then
//------------------------------------------------------------------------
set heroes[1] = ARCHMAGE
set heroes[2] = MTN_KING
set heroes[3] = PALADIN
set heroes[4] = BLOOD_MAGE
//------------------------------------------------------------------------
elseif raceid == RACE_ORC then
//------------------------------------------------------------------------
set heroes[1] = BLADE_MASTER
set heroes[2] = FAR_SEER
set heroes[3] = TAUREN_CHIEF
set heroes[4] = SHADOW_HUNTER
//------------------------------------------------------------------------
elseif raceid == RACE_NIGHTELF then
//------------------------------------------------------------------------
set heroes[1] = DEMON_HUNTER
set heroes[2] = KEEPER
set heroes[3] = MOON_BABE
set heroes[4] = WARDEN
//------------------------------------------------------------------------
elseif raceid == RACE_UNDEAD then
//------------------------------------------------------------------------
set heroes[1] = DEATH_KNIGHT
set heroes[2] = DREAD_LORD
set heroes[3] = LICH
set heroes[4] = CRYPT_LORD
else
set hero_id = 0
endif
if VersionCompatible(VERSION_FROZEN_THRONE) then
set last = 4
else
set last = 3
endif
set first = GetRandomInt(1,last)
set second = GetRandomInt(1,last-1)
set third = GetRandomInt(1,last-2)
set hero_id = heroes[first]
set heroes[first] = heroes[last]
set hero_id2 = heroes[second]
set heroes[second] = heroes[last-1]
set hero_id3 = heroes[third]
return hero_id
endfunction
This file contains all the main routines, both for melee AIs and campaign AIs. Some are easy to understand, some are not. They all end in the so-called natives, code which we cannot see nor edit (they are just calls to internal engine code). You can find this file in the scripts folder of your Warcraft 3 root directory using Ladik's Casc Viewer.
Since I was always satisfied with default AI's defensive behaviour, I concentrate myself on offensive behaviours, specifically, the attack waves routines.
After around a week of development and another week of testing, I came up with a new set of routines, additional to and fully compatible with the default one, to greatly enhance the customization power of my AIs... and their overall attack intelligence.
They are all collected in what I call common AI 2.0 file, which is what I am sharing with this system. After over 18 years, the common.ai file is finally updated from v1.68 to v2.0, and I belive it will be very useful to the community, especially to single player maps/campaigns makers. All the new functions use the prefix "Adv", standing for Advanced. This is done to easily tell them apart the vanilla routines. You can find this file in the code section of this system.
Consider it a retrocompatible and additive update to default routines. Among the other things, it will allow your AI players to target specific locations or unit types in an intelligent way, while also being able to continue the attack wave towards nearby or further away enemies. All of that, and much more, fully configurable.
Since I was always satisfied with default AI's defensive behaviour, I concentrate myself on offensive behaviours, specifically, the attack waves routines.
After around a week of development and another week of testing, I came up with a new set of routines, additional to and fully compatible with the default one, to greatly enhance the customization power of my AIs... and their overall attack intelligence.
They are all collected in what I call common AI 2.0 file, which is what I am sharing with this system. After over 18 years, the common.ai file is finally updated from v1.68 to v2.0, and I belive it will be very useful to the community, especially to single player maps/campaigns makers. All the new functions use the prefix "Adv", standing for Advanced. This is done to easily tell them apart the vanilla routines. You can find this file in the code section of this system.
Consider it a retrocompatible and additive update to default routines. Among the other things, it will allow your AI players to target specific locations or unit types in an intelligent way, while also being able to continue the attack wave towards nearby or further away enemies. All of that, and much more, fully configurable.
To import the new AI routines into your map is very easy and you just need to do the following:
- Download the common_v2.ai file attached to this system (or just make your own .ai using the script in the code section, it's just a text file!), which contains all the advanced routines
- Import this file into your map using the Asset Manager
- Change the file path to scripts\common.ai
By doing so, each .ai script will have the new routines included, so you can use the new routines in all AI scripts for that specific map.
You can also check out the in-depth tutorial at the third page to learn how the system works and cover how each routine works.
Finally, please note that this system was only tested on 1.32, but it should be compatible with 1.31.x versions of the game!
You can also check out the in-depth tutorial at the third page to learn how the system works and cover how each routine works.
Finally, please note that this system was only tested on 1.32, but it should be compatible with 1.31.x versions of the game!
JASS:
//==================================================================================================
// $Id: common.ai, v 2.0 2021/06/04 12:00:00 - InsaneMonster (Luca Pasqualini) $
//==================================================================================================
native DebugS takes string str returns nothing
native DebugFI takes string str, integer val returns nothing
native DebugUnitID takes string str, integer val returns nothing
native DisplayText takes integer p, string str returns nothing
native DisplayTextI takes integer p, string str, integer val returns nothing
native DisplayTextII takes integer p, string str, integer v1, integer v2 returns nothing
native DisplayTextIII takes integer p, string str, integer v1, integer v2, integer v3 returns nothing
native DoAiScriptDebug takes nothing returns boolean
native GetAiPlayer takes nothing returns integer
native GetHeroId takes nothing returns integer
native GetHeroLevelAI takes nothing returns integer
native GetUnitCount takes integer unitid returns integer
native GetPlayerUnitTypeCount takes player p, integer unitid returns integer
native GetUnitCountDone takes integer unitid returns integer
native GetTownUnitCount takes integer id, integer tn, boolean dn returns integer
native GetUnitGoldCost takes integer unitid returns integer
native GetUnitWoodCost takes integer unitid returns integer
native GetUnitBuildTime takes integer unitid returns integer
native GetMinesOwned takes nothing returns integer
native GetGoldOwned takes nothing returns integer
native TownWithMine takes nothing returns integer
native TownHasMine takes integer townid returns boolean
native TownHasHall takes integer townid returns boolean
native GetUpgradeLevel takes integer id returns integer
native GetUpgradeGoldCost takes integer id returns integer
native GetUpgradeWoodCost takes integer id returns integer
native GetNextExpansion takes nothing returns integer
native GetMegaTarget takes nothing returns unit
native GetBuilding takes player p returns unit
native GetEnemyPower takes nothing returns integer
native SetAllianceTarget takes unit id returns nothing
native GetAllianceTarget takes nothing returns unit
native SetProduce takes integer qty, integer id, integer town returns boolean
native Unsummon takes unit unitid returns nothing
native SetExpansion takes unit peon, integer id returns boolean
native SetUpgrade takes integer id returns boolean
native SetHeroLevels takes code func returns nothing
native SetNewHeroes takes boolean state returns nothing
native PurchaseZeppelin takes nothing returns nothing
native MergeUnits takes integer qty, integer a, integer b, integer make returns boolean
native ConvertUnits takes integer qty, integer id returns boolean
native SetCampaignAI takes nothing returns nothing
native SetMeleeAI takes nothing returns nothing
native SetTargetHeroes takes boolean state returns nothing
native SetPeonsRepair takes boolean state returns nothing
native SetRandomPaths takes boolean state returns nothing
native SetDefendPlayer takes boolean state returns nothing
native SetHeroesFlee takes boolean state returns nothing
native SetHeroesBuyItems takes boolean state returns nothing
native SetWatchMegaTargets takes boolean state returns nothing
native SetIgnoreInjured takes boolean state returns nothing
native SetHeroesTakeItems takes boolean state returns nothing
native SetUnitsFlee takes boolean state returns nothing
native SetGroupsFlee takes boolean state returns nothing
native SetSlowChopping takes boolean state returns nothing
native SetCaptainChanges takes boolean allow returns nothing
native SetSmartArtillery takes boolean state returns nothing
native SetReplacementCount takes integer qty returns nothing
native GroupTimedLife takes boolean allow returns nothing
native RemoveInjuries takes nothing returns nothing
native RemoveSiege takes nothing returns nothing
native InitAssault takes nothing returns nothing
native AddAssault takes integer qty, integer id returns boolean
native AddDefenders takes integer qty, integer id returns boolean
native GetCreepCamp takes integer min, integer max, boolean flyers_ok returns unit
native StartGetEnemyBase takes nothing returns nothing
native WaitGetEnemyBase takes nothing returns boolean
native GetEnemyBase takes nothing returns unit
native GetExpansionFoe takes nothing returns unit
native GetEnemyExpansion takes nothing returns unit
native GetExpansionX takes nothing returns integer
native GetExpansionY takes nothing returns integer
native SetStagePoint takes real x, real y returns nothing
native AttackMoveKill takes unit target returns nothing
native AttackMoveXY takes integer x, integer y returns nothing
native LoadZepWave takes integer x, integer y returns nothing
native SuicidePlayer takes player id, boolean check_full returns boolean
native SuicidePlayerUnits takes player id, boolean check_full returns boolean
native CaptainInCombat takes boolean attack_captain returns boolean
native IsTowered takes unit target returns boolean
native ClearHarvestAI takes nothing returns nothing
native HarvestGold takes integer town, integer peons returns nothing
native HarvestWood takes integer town, integer peons returns nothing
native GetExpansionPeon takes nothing returns unit
native StopGathering takes nothing returns nothing
native AddGuardPost takes integer id, real x, real y returns nothing
native FillGuardPosts takes nothing returns nothing
native ReturnGuardPosts takes nothing returns nothing
native CreateCaptains takes nothing returns nothing
native SetCaptainHome takes integer which, real x, real y returns nothing
native ResetCaptainLocs takes nothing returns nothing
native ShiftTownSpot takes real x, real y returns nothing
native TeleportCaptain takes real x, real y returns nothing
native ClearCaptainTargets takes nothing returns nothing
native CaptainAttack takes real x, real y returns nothing
native CaptainVsUnits takes player id returns nothing
native CaptainVsPlayer takes player id returns nothing
native CaptainGoHome takes nothing returns nothing
native CaptainIsHome takes nothing returns boolean
native CaptainIsFull takes nothing returns boolean
native CaptainIsEmpty takes nothing returns boolean
native CaptainGroupSize takes nothing returns integer
native CaptainReadiness takes nothing returns integer
native CaptainRetreating takes nothing returns boolean
native CaptainReadinessHP takes nothing returns integer
native CaptainReadinessMa takes nothing returns integer
native CaptainAtGoal takes nothing returns boolean
native CreepsOnMap takes nothing returns boolean
native SuicideUnit takes integer count, integer unitid returns nothing
native SuicideUnitEx takes integer ct, integer uid, integer pid returns nothing
native StartThread takes code func returns nothing
native Sleep takes real seconds returns nothing
native UnitAlive takes unit id returns boolean
native UnitInvis takes unit id returns boolean
native IgnoredUnits takes integer unitid returns integer
native TownThreatened takes nothing returns boolean
native DisablePathing takes nothing returns nothing
native SetAmphibious takes nothing returns nothing
native CommandsWaiting takes nothing returns integer
native GetLastCommand takes nothing returns integer
native GetLastData takes nothing returns integer
native PopLastCommand takes nothing returns nothing
native MeleeDifficulty takes nothing returns integer
//============================================================================
// Globals for all AI scripts
//============================================================================
globals
//--------------------------------------------------------------------
// HUMANS
//--------------------------------------------------------------------
// human heroes
constant integer ARCHMAGE = 'Hamg'
constant integer PALADIN = 'Hpal'
constant integer MTN_KING = 'Hmkg'
constant integer BLOOD_MAGE = 'Hblm'
// human hero abilities
constant integer AVATAR = 'AHav'
constant integer BASH = 'AHbh'
constant integer THUNDER_BOLT = 'AHtb'
constant integer THUNDER_CLAP = 'AHtc'
constant integer DEVOTION_AURA = 'AHad'
constant integer DIVINE_SHIELD = 'AHds'
constant integer HOLY_BOLT = 'AHhb'
constant integer RESURRECTION = 'AHre'
constant integer BLIZZARD = 'AHbz'
constant integer BRILLIANCE_AURA = 'AHab'
constant integer MASS_TELEPORT = 'AHmt'
constant integer WATER_ELEMENTAL = 'AHwe'
constant integer BANISH = 'AHbn'
constant integer FLAME_STRIKE = 'AHfs'
constant integer SUMMON_PHOENIX = 'AHpx'
constant integer SIPHON_MANA = 'AHdr'
// special human heroes
constant integer JAINA = 'Hjai'
constant integer MURADIN = 'Hmbr'
constant integer GARITHOS = 'Hlgr'
constant integer KAEL = 'Hkal'
// human units
constant integer COPTER = 'hgyr'
constant integer GYRO = COPTER
constant integer ELEMENTAL = 'hwat'
constant integer FOOTMAN = 'hfoo'
constant integer FOOTMEN = FOOTMAN
constant integer GRYPHON = 'hgry'
constant integer KNIGHT = 'hkni'
constant integer MORTAR = 'hmtm'
constant integer PEASANT = 'hpea'
constant integer PRIEST = 'hmpr'
constant integer RIFLEMAN = 'hrif'
constant integer RIFLEMEN = RIFLEMAN
constant integer SORCERESS = 'hsor'
constant integer TANK = 'hmtt'
constant integer STEAM_TANK = TANK
constant integer ROCKET_TANK = 'hrtt'
constant integer MILITIA = 'hmil'
constant integer SPELL_BREAKER = 'hspt'
constant integer HUMAN_DRAGON_HAWK = 'hdhw'
// special human units
constant integer BLOOD_PRIEST = 'hbep'
constant integer BLOOD_SORCERESS = 'hbes'
constant integer BLOOD_PEASANT = 'nhew'
// human buildings
constant integer AVIARY = 'hgra'
constant integer BARRACKS = 'hbar'
constant integer BLACKSMITH = 'hbla'
constant integer CANNON_TOWER = 'hctw'
constant integer CASTLE = 'hcas'
constant integer CHURCH = 'htws'
constant integer MAGE_TOWER = CHURCH
constant integer GUARD_TOWER = 'hgtw'
constant integer HOUSE = 'hhou'
constant integer HUMAN_ALTAR = 'halt'
constant integer KEEP = 'hkee'
constant integer LUMBER_MILL = 'hlum'
constant integer SANCTUM = 'hars'
constant integer ARCANE_SANCTUM = SANCTUM
constant integer TOWN_HALL = 'htow'
constant integer WATCH_TOWER = 'hwtw'
constant integer WORKSHOP = 'harm'
constant integer ARCANE_VAULT = 'hvlt'
constant integer ARCANE_TOWER = 'hatw'
// human upgrades
constant integer UPG_MELEE = 'Rhme'
constant integer UPG_RANGED = 'Rhra'
constant integer UPG_ARTILLERY = 'Rhaa'
constant integer UPG_ARMOR = 'Rhar'
constant integer UPG_GOLD = 'Rhmi'
constant integer UPG_MASONRY = 'Rhac'
constant integer UPG_SIGHT = 'Rhss'
constant integer UPG_DEFEND = 'Rhde'
constant integer UPG_BREEDING = 'Rhan'
constant integer UPG_PRAYING = 'Rhpt'
constant integer UPG_SORCERY = 'Rhst'
constant integer UPG_LEATHER = 'Rhla'
constant integer UPG_GUN_RANGE = 'Rhri'
constant integer UPG_WOOD = 'Rhlh'
constant integer UPG_SENTINEL = 'Rhse'
constant integer UPG_SCATTER = 'Rhsr'
constant integer UPG_BOMBS = 'Rhgb'
constant integer UPG_HAMMERS = 'Rhhb'
constant integer UPG_CONT_MAGIC = 'Rhss'
constant integer UPG_FRAGS = 'Rhfs'
constant integer UPG_TANK = 'Rhrt'
constant integer UPG_FLAK = 'Rhfc'
constant integer UPG_CLOUD = 'Rhcd'
//--------------------------------------------------------------------
// ORCS
//--------------------------------------------------------------------
// orc heroes
constant integer BLADE_MASTER = 'Obla'
constant integer FAR_SEER = 'Ofar'
constant integer TAUREN_CHIEF = 'Otch'
constant integer SHADOW_HUNTER = 'Oshd'
// special orc heroes
constant integer GROM = 'Ogrh'
constant integer THRALL = 'Othr'
// orc hero abilities
constant integer CRITICAL_STRIKE = 'AOcr'
constant integer MIRROR_IMAGE = 'AOmi'
constant integer BLADE_STORM = 'AOww'
constant integer WIND_WALK = 'AOwk'
constant integer CHAIN_LIGHTNING = 'AOcl'
constant integer EARTHQUAKE = 'AOeq'
constant integer FAR_SIGHT = 'AOfs'
constant integer SPIRIT_WOLF = 'AOsf'
constant integer ENDURANE_AURA = 'AOae'
constant integer REINCARNATION = 'AOre'
constant integer SHOCKWAVE = 'AOsh'
constant integer WAR_STOMP = 'AOws'
constant integer HEALING_WAVE = 'AOhw'
constant integer HEX = 'AOhx'
constant integer SERPENT_WARD = 'AOsw'
constant integer VOODOO = 'AOvd'
// orc units
constant integer GUARDIAN = 'oang'
constant integer CATAPULT = 'ocat'
constant integer WITCH_DOCTOR = 'odoc'
constant integer GRUNT = 'ogru'
constant integer HEAD_HUNTER = 'ohun'
constant integer BERSERKER = 'otbk'
constant integer KODO_BEAST = 'okod'
constant integer PEON = 'opeo'
constant integer RAIDER = 'orai'
constant integer SHAMAN = 'oshm'
constant integer TAUREN = 'otau'
constant integer WYVERN = 'owyv'
constant integer BATRIDER = 'otbr'
constant integer SPIRIT_WALKER = 'ospw'
constant integer SPIRIT_WALKER_M = 'ospm'
// orc buildings
constant integer ORC_ALTAR = 'oalt'
constant integer ORC_BARRACKS = 'obar'
constant integer BESTIARY = 'obea'
constant integer FORGE = 'ofor'
constant integer FORTRESS = 'ofrt'
constant integer GREAT_HALL = 'ogre'
constant integer LODGE = 'osld'
constant integer STRONGHOLD = 'ostr'
constant integer BURROW = 'otrb'
constant integer TOTEM = 'otto'
constant integer ORC_WATCH_TOWER = 'owtw'
constant integer VOODOO_LOUNGE = 'ovln'
// orc upgrades
constant integer UPG_ORC_MELEE = 'Rome'
constant integer UPG_ORC_RANGED = 'Rora'
constant integer UPG_ORC_ARTILLERY = 'Roaa'
constant integer UPG_ORC_ARMOR = 'Roar'
constant integer UPG_ORC_WAR_DRUMS = 'Rwdm'
constant integer UPG_ORC_PILLAGE = 'Ropg'
constant integer UPG_ORC_BERSERK = 'Robs'
constant integer UPG_ORC_PULVERIZE = 'Rows'
constant integer UPG_ORC_ENSNARE = 'Roen'
constant integer UPG_ORC_VENOM = 'Rovs'
constant integer UPG_ORC_DOCS = 'Rowd'
constant integer UPG_ORC_SHAMAN = 'Rost'
constant integer UPG_ORC_SPIKES = 'Rosp'
constant integer UPG_ORC_BURROWS = 'Rorb'
constant integer UPG_ORC_REGEN = 'Rotr'
constant integer UPG_ORC_FIRE = 'Rolf'
constant integer UPG_ORC_SWALKER = 'Rowt'
constant integer UPG_ORC_BERSERKER = 'Robk'
constant integer UPG_ORC_NAPTHA = 'Robf'
constant integer UPG_ORC_CHAOS = 'Roch'
// Warcraft 2 orc units
constant integer OGRE_MAGI = 'nomg'
constant integer ORC_DRAGON = 'nrwm'
constant integer SAPPER = 'ngsp'
constant integer ZEPPLIN = 'nzep'
constant integer ZEPPELIN = ZEPPLIN
constant integer W2_WARLOCK = 'nw2w'
constant integer PIG_FARM = 'npgf'
constant integer FOREST_TROLL = 'nftr'
// special orc units
constant integer CHAOS_GRUNT = 'nchg'
constant integer CHAOS_WARLOCK = 'nchw'
constant integer CHAOS_RAIDER = 'nchr'
constant integer CHAOS_PEON = 'ncpn'
constant integer CHAOS_KODO = 'nckb'
constant integer CHAOS_GROM = 'Opgh'
constant integer CHAOS_BLADEMASTER = 'Nbbc'
constant integer CHAOS_BURROW = 'ocbw'
//--------------------------------------------------------------------
// UNDEAD
//--------------------------------------------------------------------
// undead heroes
constant integer DEATH_KNIGHT = 'Udea'
constant integer DREAD_LORD = 'Udre'
constant integer LICH = 'Ulic'
constant integer CRYPT_LORD = 'Ucrl'
// special undead heroes
constant integer MALGANIS = 'Umal'
constant integer TICHONDRIUS = 'Utic'
constant integer PIT_LORD = 'Npld'
constant integer DETHEROC = 'Udth'
// undead hero abilities
constant integer SLEEP = 'AUsl'
constant integer VAMP_AURA = 'AUav'
constant integer CARRION_SWARM = 'AUcs'
constant integer INFERNO = 'AUin'
constant integer DARK_RITUAL = 'AUdr'
constant integer DEATH_DECAY = 'AUdd'
constant integer FROST_ARMOR = 'AUfu'
constant integer FROST_NOVA = 'AUfn'
constant integer ANIM_DEAD = 'AUan'
constant integer DEATH_COIL = 'AUdc'
constant integer DEATH_PACT = 'AUdp'
constant integer UNHOLY_AURA = 'AUau'
constant integer CARRION_SCARAB = 'AUcb'
constant integer IMPALE = 'AUim'
constant integer LOCUST_SWARM = 'AUls'
constant integer THORNY_SHIELD = 'AUts'
// undead units
constant integer ABOMINATION = 'uabo'
constant integer ACOLYTE = 'uaco'
constant integer BANSHEE = 'uban'
constant integer PIT_FIEND = 'ucry'
constant integer CRYPT_FIEND = PIT_FIEND
constant integer FROST_WYRM = 'ufro'
constant integer GARGOYLE = 'ugar'
constant integer GARGOYLE_MORPH = 'ugrm'
constant integer GHOUL = 'ugho'
constant integer MEAT_WAGON = 'umtw'
constant integer NECRO = 'unec'
constant integer SKEL_WARRIOR = 'uske'
constant integer SHADE = 'ushd'
constant integer UNDEAD_BARGE = 'uarb'
constant integer OBSIDIAN_STATUE = 'uobs'
constant integer OBS_STATUE = OBSIDIAN_STATUE
constant integer BLK_SPHINX = 'ubsp'
// undead buildings
constant integer UNDEAD_MINE = 'ugol'
constant integer UNDEAD_ALTAR = 'uaod'
constant integer BONEYARD = 'ubon'
constant integer GARG_SPIRE = 'ugsp'
constant integer NECROPOLIS_1 = 'unpl' // normal
constant integer NECROPOLIS_2 = 'unp1' // upgraded once
constant integer NECROPOLIS_3 = 'unp2' // full upgrade
constant integer SAC_PIT = 'usap'
constant integer CRYPT = 'usep'
constant integer SLAUGHTERHOUSE = 'uslh'
constant integer DAMNED_TEMPLE = 'utod'
constant integer ZIGGURAT_1 = 'uzig' // normal
constant integer ZIGGURAT_2 = 'uzg1' // upgraded
constant integer ZIGGURAT_FROST = 'uzg2' // frost tower
constant integer GRAVEYARD = 'ugrv'
constant integer TOMB_OF_RELICS = 'utom'
// undead upgrades
constant integer UPG_UNHOLY_STR = 'Rume'
constant integer UPG_CR_ATTACK = 'Rura'
constant integer UPG_UNHOLY_ARMOR = 'Ruar'
constant integer UPG_CANNIBALIZE = 'Ruac'
constant integer UPG_GHOUL_FRENZY = 'Rugf'
constant integer UPG_FIEND_WEB = 'Ruwb'
constant integer UPG_ABOM = 'Ruab'
constant integer UPG_STONE_FORM = 'Rusf'
constant integer UPG_NECROS = 'Rune'
constant integer UPG_BANSHEE = 'Ruba'
constant integer UPG_MEAT_WAGON = 'Rump'
constant integer UPG_WYRM_BREATH = 'Rufb'
constant integer UPG_SKEL_LIFE = 'Rusl'
constant integer UPG_SKEL_MASTERY = 'Rusm'
constant integer UPG_EXHUME = 'Ruex'
constant integer UPG_SACRIFICE = 'Rurs'
constant integer UPG_ABOM_EXPL = 'Ruax'
constant integer UPG_CR_ARMOR = 'Rucr'
constant integer UPG_PLAGUE = 'Rupc'
constant integer UPG_BLK_SPHINX = 'Rusp'
constant integer UPG_BURROWING = 'Rubu'
//--------------------------------------------------------------------
// ELVES
//--------------------------------------------------------------------
// elf heroes
constant integer DEMON_HUNTER = 'Edem'
constant integer DEMON_HUNTER_M = 'Edmm'
constant integer KEEPER = 'Ekee'
constant integer MOON_CHICK = 'Emoo'
constant integer MOON_BABE = MOON_CHICK
constant integer MOON_HONEY = MOON_CHICK
constant integer WARDEN = 'Ewar'
// special elf heroes
constant integer SYLVANUS = 'Hvwd'
constant integer CENARIUS = 'Ecen'
constant integer ILLIDAN = 'Eevi'
constant integer ILLIDAN_DEMON = 'Eevm'
constant integer MAIEV = 'Ewrd'
// elf hero abilities
constant integer FORCE_NATURE = 'AEfn'
constant integer ENT_ROOTS = 'AEer'
constant integer THORNS_AURA = 'AEah'
constant integer TRANQUILITY = 'AEtq'
constant integer EVASION = 'AEev'
constant integer IMMOLATION = 'AEim'
constant integer MANA_BURN = 'AEmb'
constant integer METAMORPHOSIS = 'AEme'
constant integer SEARING_ARROWS = 'AHfa'
constant integer SCOUT = 'AEst'
constant integer STARFALL = 'AEsf'
constant integer TRUESHOT = 'AEar'
constant integer BLINK = 'AEbl'
constant integer FAN_KNIVES = 'AEfk'
constant integer SHADOW_TOUCH = 'AEsh'
constant integer VENGEANCE = 'AEsv'
// elf units
constant integer WISP = 'ewsp'
constant integer ARCHER = 'earc'
constant integer DRUID_TALON = 'edot'
constant integer DRUID_TALON_M = 'edtm'
constant integer BALLISTA = 'ebal'
constant integer DRUID_CLAW = 'edoc'
constant integer DRUID_CLAW_M = 'edcm'
constant integer DRYAD = 'edry'
constant integer HIPPO = 'ehip'
constant integer HIPPO_RIDER = 'ehpr'
constant integer HUNTRESS = 'esen'
constant integer CHIMAERA = 'echm'
constant integer ENT = 'efon'
constant integer MOUNTAIN_GIANT = 'emtg'
constant integer FAERIE_DRAGON = 'efdr'
// special elf units
constant integer HIGH_ARCHER = 'nhea'
constant integer HIGH_FOOTMAN = 'hcth'
constant integer HIGH_FOOTMEN = HIGH_FOOTMAN
constant integer HIGH_SWORDMAN = 'hhes'
constant integer DRAGON_HAWK = 'nws1'
constant integer CORRUPT_TREANT = 'nenc'
constant integer POISON_TREANT = 'nenp'
constant integer PLAGUE_TREANT = 'nepl'
constant integer SHANDRIS = 'eshd'
// elf buildings
constant integer ANCIENT_LORE = 'eaoe'
constant integer ANCIENT_WAR = 'eaom'
constant integer ANCIENT_WIND = 'eaow'
constant integer TREE_AGES = 'etoa'
constant integer TREE_ETERNITY = 'etoe'
constant integer TREE_LIFE = 'etol'
constant integer ANCIENT_PROTECT = 'etrp'
constant integer ELF_ALTAR = 'eate'
constant integer BEAR_DEN = 'edol'
constant integer CHIMAERA_ROOST = 'edos'
constant integer HUNTERS_HALL = 'edob'
constant integer MOON_WELL = 'emow'
constant integer ELF_MINE = 'egol'
constant integer DEN_OF_WONDERS = 'eden'
// special elf buildings
constant integer ELF_FARM = 'nefm'
constant integer ELF_GUARD_TOWER = 'negt'
constant integer HIGH_SKY = 'negm'
constant integer HIGH_EARTH = 'negf'
constant integer HIGH_TOWER = 'negt'
constant integer ELF_HIGH_BARRACKS = 'nheb'
constant integer CORRUPT_LIFE = 'nctl'
constant integer CORRUPT_WELL = 'ncmw'
constant integer CORRUPT_PROTECTOR = 'ncap'
constant integer CORRUPT_WAR = 'ncaw'
// elf upgrades
constant integer UPG_STR_MOON = 'Resm'
constant integer UPG_STR_WILD = 'Resw'
constant integer UPG_MOON_ARMOR = 'Rema'
constant integer UPG_HIDES = 'Rerh'
constant integer UPG_ULTRAVISION = 'Reuv'
constant integer UPG_BLESSING = 'Renb'
constant integer UPG_SCOUT = 'Resc'
constant integer UPG_GLAIVE = 'Remg'
constant integer UPG_BOWS = 'Reib'
constant integer UPG_MARKSMAN = 'Remk'
constant integer UPG_DRUID_TALON = 'Redt'
constant integer UPG_DRUID_CLAW = 'Redc'
constant integer UPG_ABOLISH = 'Resi'
constant integer UPG_CHIM_ACID = 'Recb'
constant integer UPG_HIPPO_TAME = 'Reht'
constant integer UPG_BOLT = 'Repd'
constant integer UPG_MARK_CLAW = 'Reeb'
constant integer UPG_MARK_TALON = 'Reec'
constant integer UPG_HARD_SKIN = 'Rehs'
constant integer UPG_RESIST_SKIN = 'Rers'
constant integer UPG_WELL_SPRING = 'Rews'
//--------------------------------------------------------------------
// Neutral
//--------------------------------------------------------------------
constant integer DEMON_GATE = 'ndmg'
constant integer FELLHOUND = 'nfel'
constant integer INFERNAL = 'ninf'
constant integer DOOMGUARD = 'nbal'
constant integer SATYR = 'nsty'
constant integer TRICKSTER = 'nsat'
constant integer SHADOWDANCER = 'nsts'
constant integer SOULSTEALER = 'nstl'
constant integer HELLCALLER = 'nsth'
constant integer SKEL_ARCHER = 'nska'
constant integer SKEL_MARKSMAN = 'nskm'
constant integer SKEL_BURNING = 'nskf'
constant integer SKEL_GIANT = 'nskg'
constant integer FURBOLG = 'nfrl'
constant integer FURBOLG_TRACKER = 'nfrb'
constant integer FURBOLG_SHAMAN = 'nfrs'
constant integer FURBOLG_CHAMP = 'nfrg'
constant integer FURBOLG_ELDER = 'nfre'
//--------------------------------------------------------------------
// NAGA
//--------------------------------------------------------------------
// naga heroes
constant integer NAGA_SORCERESS = 'Nngs'
constant integer NAGA_VASHJ = 'Hvsh'
// naga units
constant integer NAGA_DRAGON = 'nsnp' // old names
constant integer NAGA_WITCH = 'nnsw'
constant integer NAGA_SERPENT = 'nwgs'
constant integer NAGA_HYDRA = 'nhyc'
constant integer NAGA_SLAVE = 'nmpe' // peon
constant integer NAGA_SNAP_DRAGON = NAGA_DRAGON // weak ranged
constant integer NAGA_COUATL = NAGA_SERPENT // weak air
constant integer NAGA_SIREN = NAGA_WITCH // caster
constant integer NAGA_MYRMIDON = 'nmyr' // knight
constant integer NAGA_REAVER = 'nnmg' // footman
constant integer NAGA_TURTLE = NAGA_HYDRA // siege
constant integer NAGA_ROYAL = 'nnrg' // royal guard
// naga buildings
constant integer NAGA_TEMPLE = 'nntt' // town hall
constant integer NAGA_CORAL = 'nnfm' // farm
constant integer NAGA_SHRINE = 'nnsa' // sirens & couatls
constant integer NAGA_SPAWNING = 'nnsg' // myrm, snap dragon, hydra
constant integer NAGA_GUARDIAN = 'nntg' // tower
constant integer NAGA_ALTAR = 'nnad' // altar
// naga upgrades
constant integer UPG_NAGA_ARMOR = 'Rnam'
constant integer UPG_NAGA_ATTACK = 'Rnat'
constant integer UPG_NAGA_ABOLISH = 'Rnsi'
constant integer UPG_SIREN = 'Rnsw'
constant integer UPG_NAGA_ENSNARE = 'Rnen'
//--------------------------------------------------------------------
constant integer M1 = 60
constant integer M2 = 2*60
constant integer M3 = 3*60
constant integer M4 = 4*60
constant integer M5 = 5*60
constant integer M6 = 6*60
constant integer M7 = 7*60
constant integer M8 = 8*60
constant integer M9 = 9*60
constant integer M10 = 10*60
constant integer M11 = 11*60
constant integer M12 = 12*60
constant integer M13 = 13*60
constant integer M14 = 14*60
constant integer M15 = 15*60
constant integer EASY = 1
constant integer NORMAL = 2
constant integer HARD = 3
constant integer INSANE = 4 // not used
constant integer MELEE_NEWBIE = 1
constant integer MELEE_NORMAL = 2
constant integer MELEE_INSANE = 3
constant integer ATTACK_CAPTAIN = 1
constant integer DEFENSE_CAPTAIN = 2
constant integer BOTH_CAPTAINS = 3
constant integer BUILD_UNIT = 1
constant integer BUILD_UPGRADE = 2
constant integer BUILD_EXPAND = 3
constant integer UPKEEP_TIER1 = 50
constant integer UPKEEP_TIER2 = 80
//--------------------------------------------------------------------
player ai_player
integer sleep_seconds
integer total_gold = 0
integer total_wood = 0
integer gold_buffer = 0 // usually for potion money
integer difficulty = NORMAL
integer exp_seen = 0
integer racial_farm = 'hhou'
integer hero_id = 'Hamg'
integer hero_id2 = 'Hmkg'
integer hero_id3 = 'Hpal'
integer array skill
integer array skills1
integer array skills2
integer array skills3
integer max_hero_level = 0
integer array harass_qty
integer array harass_max
integer array harass_units
integer harass_length = 0
integer array defense_qty
integer array defense_units
integer defense_length = 0
integer array build_qty
integer array build_type
integer array build_item
integer array build_town
integer build_length = 0
integer campaign_gold_peons = 5
integer campaign_wood_peons = 3
integer campaign_basics_speed = 5
integer min_creeps = -1
integer max_creeps = -1
boolean harvest_town1 = true
boolean harvest_town2 = true
boolean harvest_town3 = true
boolean do_campaign_farms = true
boolean two_heroes = false
boolean allow_air_creeps = false
boolean take_exp = false
boolean allow_signal_abort = false
boolean ready_for_zeppelin = true
boolean get_zeppelin = false
boolean build_campaign_attackers = true
boolean do_debug_cheats = false
boolean trace_on = true
boolean zep_next_wave = false
boolean form_group_timeouts = true
//--------------------------------------------------------------------
player debug_player
boolean debug = false
boolean suicide = false
integer wave_reach_goal_timeout_seconds = 300
integer wave_enter_combat_timeout_seconds = 3
integer wave_clear_goal_timeout_seconds = 60
real continue_far_away_radius = 960.0
real preferred_locations_radius = 640.0
integer wave_reach_goal_sleep_seconds
integer wave_enter_combat_sleep_seconds
integer wave_clear_goal_sleep_seconds
real defense_captain_home_x
real defense_captain_home_y
boolean defense_captain_home_set = false
real attack_wave_gather_return_x
real attack_wave_gather_return_y
boolean attack_wave_gather_return_set = false
location array preferred_locations
integer preferred_locations_count = 0
location array preferred_locations_current
integer preferred_locations_count_current = 0
boolean wood_peons_warriors = false
boolean search_preferred_locations = true
boolean search_everywhere = true
boolean can_search_units = true
boolean can_search_invulnerable = false
boolean prioritize_town_halls = true
boolean prioritize_nearest = true
boolean reach_goal_timeout = true
boolean enter_combat_timeout = true
boolean clear_goal_timeout = true
integer continue_attack_percentage = 100
boolean continue_attack_reduce_percentage_if_far_away = true
integer continue_attack_reduce_percentage = 25
unit array targetable_town_halls
integer targetable_town_halls_count = 0
unit array targetable_buildings
integer targetable_buildings_count = 0
unit array targetable_units
integer targetable_units_count = 0
real last_target_x
real last_target_y
integer search_target_max_cycles_default = 100
integer search_target_max_cycles_last_target = 10
endglobals
//============================================================================
function PlayerEx takes integer slot returns player
return Player(slot-1)
endfunction
//============================================================================
function Trace takes string message returns nothing
if trace_on then
call DisplayText(GetAiPlayer(),message)
endif
endfunction
//============================================================================
function TraceI takes string message, integer val returns nothing
if trace_on then
call DisplayTextI(GetAiPlayer(),message,val)
endif
endfunction
//============================================================================
function TraceII takes string message, integer v1, integer v2 returns nothing
if trace_on then
call DisplayTextII(GetAiPlayer(),message,v1,v2)
endif
endfunction
//============================================================================
function TraceIII takes string message, integer v1, integer v2, integer v3 returns nothing
if trace_on then
call DisplayTextIII(GetAiPlayer(),message,v1,v2,v3)
endif
endfunction
//============================================================================
function InitAI takes nothing returns nothing
set ai_player = Player(GetAiPlayer())
set sleep_seconds = 0
call StopGathering()
endfunction
//============================================================================
function StandardAI takes code heroes, code peons, code attacks returns nothing
local boolean isNewbie = (MeleeDifficulty() == MELEE_NEWBIE)
call InitAI()
call SetMeleeAI()
call SetDefendPlayer(true)
call SetGroupsFlee(not isNewbie)
call SetHeroesBuyItems(not isNewbie)
call SetHeroesFlee(true)
call SetHeroesTakeItems(true)
call SetIgnoreInjured(true)
call SetPeonsRepair(true)
call SetSmartArtillery(not isNewbie)
call SetTargetHeroes(not isNewbie)
call SetUnitsFlee(not isNewbie)
call SetWatchMegaTargets(true)
call CreateCaptains()
call SetHeroLevels(heroes)
call Sleep(0.1)
call StartThread(peons)
call StartThread(attacks)
endfunction
//============================================================================
// Utility Functions
//============================================================================
function Min takes integer A, integer B returns integer
if A < B then
return A
else
return B
endif
endfunction
function Max takes integer A, integer B returns integer
if A > B then
return A
else
return B
endif
endfunction
function SetZepNextWave takes nothing returns nothing
set zep_next_wave = true
endfunction
function SuicideSleep takes integer seconds returns nothing
set sleep_seconds = sleep_seconds - seconds
loop
exitwhen seconds <= 0
exitwhen allow_signal_abort and CommandsWaiting() != 0
exitwhen suicide
if seconds >= 5 then
call Sleep(5)
set seconds = seconds - 5
else
call Sleep(seconds)
set seconds = 0
endif
endloop
endfunction
//============================================================================
function WaitForSignal takes nothing returns integer
local integer cmd
local boolean display = false //xxx
loop
exitwhen CommandsWaiting() != 0
//xxx
call Trace("waiting for a signal to begin AI script...\n")
set display = true
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
exitwhen CommandsWaiting() != 0
call Sleep(2)
//xxx
endloop
//xxx
if display then
call Trace("signal received, beginning AI script\n")
endif
//xxx
set cmd = GetLastCommand()
call PopLastCommand()
return cmd
endfunction
//============================================================================
function SetWoodPeons takes integer count returns nothing
set campaign_wood_peons = count
endfunction
//============================================================================
function SetGoldPeons takes integer count returns nothing
set campaign_gold_peons = count
endfunction
//============================================================================
function SetHarvestLumber takes boolean harvest returns nothing
if harvest then
set campaign_wood_peons = 3
else
set campaign_wood_peons = 0
endif
endfunction
//============================================================================
function SetFormGroupTimeouts takes boolean state returns nothing
set form_group_timeouts = state
endfunction
//============================================================================
function DoCampaignFarms takes boolean state returns nothing
set do_campaign_farms = state
endfunction
//============================================================================
function GetMinorCreep takes nothing returns unit
return GetCreepCamp(0,9,false)
endfunction
//============================================================================
function GetMajorCreep takes nothing returns unit
return GetCreepCamp(10,100,allow_air_creeps)
endfunction
//============================================================================
function GetGold takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_GOLD)
endfunction
//============================================================================
function GetWood takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_LUMBER)
endfunction
//============================================================================
function InitBuildArray takes nothing returns nothing
set build_length = 0
endfunction
//============================================================================
function InitAssaultGroup takes nothing returns nothing
set harass_length = 0
endfunction
//============================================================================
function InitDefenseGroup takes nothing returns nothing
set defense_length = 0
endfunction
//============================================================================
function InitMeleeGroup takes nothing returns nothing
call InitAssaultGroup()
call RemoveInjuries()
call RemoveSiege()
endfunction
//============================================================================
function PrepFullSuicide takes nothing returns nothing
call InitAssaultGroup()
call InitDefenseGroup()
set campaign_gold_peons = 0
set campaign_wood_peons = 0
endfunction
//============================================================================
function SetReplacements takes integer easy, integer med, integer hard returns nothing
if difficulty == EASY then
call SetReplacementCount(easy)
elseif difficulty == NORMAL then
call SetReplacementCount(med)
else
call SetReplacementCount(hard)
endif
endfunction
//============================================================================
function StartTownBuilder takes code func returns nothing
call StartThread(func)
endfunction
//============================================================================
function SetBuildAll takes integer t, integer qty, integer unitid, integer town returns nothing
if qty > 0 then
set build_qty[build_length] = qty
set build_type[build_length] = t
set build_item[build_length] = unitid
set build_town[build_length] = town
set build_length = build_length + 1
endif
endfunction
//============================================================================
function SetBuildUnit takes integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_UNIT,qty,unitid,-1)
endfunction
//============================================================================
function SetBuildNext takes integer qty, integer unitid returns nothing
local integer has = GetUnitCount(unitid)
if has >= qty then
return
endif
call SetBuildAll(BUILD_UNIT,GetUnitCountDone(unitid)+1,unitid,-1)
endfunction
//============================================================================
function SetBuildUnitEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call SetBuildAll(BUILD_UNIT,easy,unitid,-1)
elseif difficulty == NORMAL then
call SetBuildAll(BUILD_UNIT,med,unitid,-1)
else
call SetBuildAll(BUILD_UNIT,hard,unitid,-1)
endif
endfunction
//============================================================================
function SecondaryTown takes integer town, integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_UNIT,qty,unitid,town)
endfunction
//============================================================================
function SecTown takes integer town, integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_UNIT,qty,unitid,town)
endfunction
//============================================================================
function SetBuildUpgr takes integer qty, integer unitid returns nothing
if MeleeDifficulty() != MELEE_NEWBIE or qty == 1 then
call SetBuildAll(BUILD_UPGRADE,qty,unitid,-1)
endif
endfunction
//============================================================================
function SetBuildUpgrEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call SetBuildAll(BUILD_UPGRADE,easy,unitid,-1)
elseif difficulty == NORMAL then
call SetBuildAll(BUILD_UPGRADE,med,unitid,-1)
else
call SetBuildAll(BUILD_UPGRADE,hard,unitid,-1)
endif
endfunction
//============================================================================
function SetBuildExpa takes integer qty, integer unitid returns nothing
call SetBuildAll(BUILD_EXPAND,qty,unitid,-1)
endfunction
//============================================================================
function StartUpgrade takes integer level, integer upgid returns boolean
local integer gold_cost
local integer wood_cost
if GetUpgradeLevel(upgid) >= level then
return true
endif
set gold_cost = GetUpgradeGoldCost(upgid)
if total_gold < gold_cost then
return false
endif
set wood_cost = GetUpgradeWoodCost(upgid)
if total_wood < wood_cost then
return false
endif
return SetUpgrade(upgid)
endfunction
//============================================================================
function BuildFactory takes integer unitid returns nothing
if GetGold() > 1000 and GetWood() > 500 then
call SetBuildUnit( 2, unitid )
else
call SetBuildUnit( 1, unitid )
endif
endfunction
//============================================================================
function HallsCompleted takes integer unitid returns boolean
return GetUnitCount(unitid) == GetUnitCountDone(unitid)
endfunction
//============================================================================
function GuardSecondary takes integer townid, integer qty, integer unitid returns nothing
if TownHasHall(townid) and TownHasMine(townid) then
call SecondaryTown( townid, qty, unitid )
endif
endfunction
//============================================================================
function GetUnitCountEx takes integer unitid, boolean only_done, integer townid returns integer
if townid == -1 then
if only_done then
return GetUnitCountDone(unitid)
else
return GetUnitCount(unitid)
endif
else
return GetTownUnitCount(unitid,townid,only_done)
endif
endfunction
//============================================================================
function TownCountEx takes integer unitid, boolean only_done, integer townid returns integer
local integer have_qty = GetUnitCountEx(unitid,only_done,townid)
if unitid == TOWN_HALL then
set have_qty = have_qty + GetUnitCountEx(KEEP,false,townid) + GetUnitCountEx(CASTLE,false,townid)
elseif unitid == KEEP then
set have_qty = have_qty + GetUnitCountEx(CASTLE,false,townid)
elseif unitid == WATCH_TOWER then
set have_qty = have_qty + GetUnitCountEx(GUARD_TOWER,false,townid) + GetUnitCountEx(CANNON_TOWER,false,townid) + GetUnitCountEx(ARCANE_TOWER,false,townid)
elseif unitid == PEASANT then
set have_qty = have_qty + GetUnitCountEx(MILITIA,false,townid)
elseif unitid == GREAT_HALL then
set have_qty = have_qty + GetUnitCountEx(STRONGHOLD,false,townid) + GetUnitCountEx(FORTRESS,false,townid)
elseif unitid == STRONGHOLD then
set have_qty = have_qty + GetUnitCountEx(FORTRESS,false,townid)
elseif unitid == HEAD_HUNTER then
set have_qty = have_qty + GetUnitCountEx(BERSERKER,false,townid)
elseif unitid == SPIRIT_WALKER then
set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER_M,false,townid)
elseif unitid == SPIRIT_WALKER_M then
set have_qty = have_qty + GetUnitCountEx(SPIRIT_WALKER,only_done,townid)
elseif unitid == NECROPOLIS_1 then
set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_2,false,townid) + GetUnitCountEx(NECROPOLIS_3,false,townid)
elseif unitid == NECROPOLIS_2 then
set have_qty = have_qty + GetUnitCountEx(NECROPOLIS_3,false,townid)
elseif unitid == ZIGGURAT_1 then
set have_qty = have_qty + GetUnitCountEx(ZIGGURAT_2,false,townid) + GetUnitCountEx(ZIGGURAT_FROST,false,townid)
elseif unitid == GARGOYLE then
set have_qty = have_qty + GetUnitCountEx(GARGOYLE_MORPH,false,townid)
elseif unitid == TREE_LIFE then
set have_qty = have_qty + GetUnitCountEx(TREE_AGES,false,townid) + GetUnitCountEx(TREE_ETERNITY,false,townid)
elseif unitid == TREE_AGES then
set have_qty = have_qty + GetUnitCountEx(TREE_ETERNITY,false,townid)
elseif unitid == DRUID_TALON then
set have_qty = have_qty + GetUnitCountEx(DRUID_TALON_M,false,townid)
elseif unitid == DRUID_TALON_M then
set have_qty = have_qty + GetUnitCountEx(DRUID_TALON,only_done,townid)
elseif unitid == DRUID_CLAW then
set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW_M,false,townid)
elseif unitid == DRUID_CLAW_M then
set have_qty = have_qty + GetUnitCountEx(DRUID_CLAW,only_done,townid)
elseif unitid == ILLIDAN then
set have_qty = have_qty + GetUnitCountEx(ILLIDAN_DEMON,false,townid)
endif
return have_qty
endfunction
//============================================================================
function TownCountDone takes integer base returns integer
return TownCountEx(base,true,-1)
endfunction
//============================================================================
function TownCount takes integer base returns integer
return TownCountEx(base,false,-1)
endfunction
//============================================================================
function BasicExpansion takes boolean build_it, integer unitid returns nothing
if build_it and HallsCompleted(unitid) then
call SetBuildExpa( TownCount(unitid)+1, unitid )
endif
endfunction
//============================================================================
function UpgradeAll takes integer baseid, integer newid returns nothing
call SetBuildUnit( TownCountDone(baseid), newid )
endfunction
//============================================================================
function TownCountTown takes integer base, integer townid returns integer
return TownCountEx(base,false,townid)
endfunction
//============================================================================
// FoodPool
//============================================================================
function FoodPool takes integer food, boolean weak, integer id1, integer use1, boolean strong, integer id2, integer use2 returns nothing
if strong then
call SetBuildUnit( (food - use1 * TownCount(id1)) / use2, id2 )
elseif weak then
call SetBuildUnit( (food - use2 * TownCount(id2)) / use1, id1 )
endif
endfunction
//============================================================================
// MeleeTownHall
//============================================================================
function MeleeTownHall takes integer townid, integer unitid returns nothing
if TownHasMine(townid) and not TownHasHall(townid) then
call SecondaryTown ( townid, 1, unitid )
endif
endfunction
//============================================================================
function WaitForUnits takes integer unitid, integer qty returns nothing
loop
exitwhen TownCountDone(unitid) == qty
call Sleep(2)
endloop
endfunction
//============================================================================
function StartUnit takes integer ask_qty, integer unitid, integer town returns boolean
local integer have_qty
local integer need_qty
local integer afford_gold
local integer afford_wood
local integer afford_qty
local integer gold_cost
local integer wood_cost
//------------------------------------------------------------------------
// if we have all we're asking for then make nothing
//
if town == -1 then
set have_qty = TownCount(unitid)
else
set have_qty = TownCountTown(unitid,town)
endif
if have_qty >= ask_qty then
return true
endif
set need_qty = ask_qty - have_qty
//------------------------------------------------------------------------
// limit the qty we're requesting to the amount of resources available
//
set gold_cost = GetUnitGoldCost(unitid)
set wood_cost = GetUnitWoodCost(unitid)
if gold_cost == 0 then
set afford_gold = need_qty
else
set afford_gold = total_gold / gold_cost
endif
if afford_gold < need_qty then
set afford_qty = afford_gold
else
set afford_qty = need_qty
endif
if wood_cost == 0 then
set afford_wood = need_qty
else
set afford_wood = total_wood / wood_cost
endif
if afford_wood < afford_qty then
set afford_qty = afford_wood
endif
// if we're waiting on gold/wood; pause build orders
if afford_qty < 1 then
return false
endif
//------------------------------------------------------------------------
// whether we make right now what we're requesting or not, assume we will
// and deduct the cost of the units from our fake gold total right away
//
set total_gold = total_gold - gold_cost * need_qty
set total_wood = total_wood - wood_cost * need_qty
if total_gold < 0 then
set total_gold = 0
endif
if total_wood < 0 then
set total_wood = 0
endif
//------------------------------------------------------------------------
// give the AI a chance to make the units (it may not be able to right now
// but that doesn't stop us from trying other units after this as long
// as we have enough money to make this AND the needed, unbuilt ones)
//
return SetProduce(afford_qty,unitid,town)
endfunction
//============================================================================
function WaitForTown takes integer towns, integer townid returns nothing
local integer i = 0
loop
call Sleep(10)
exitwhen TownCount(townid) >= towns
set i = i + 1
exitwhen i == 12
endloop
endfunction
//============================================================================
function StartExpansion takes integer qty, integer hall returns boolean
local integer count
local integer town
local unit peon
local integer gold_cost
set count = TownCount(hall)
if count >= qty then
return true
endif
set town = GetNextExpansion()
if town == -1 then
return true
endif
set take_exp = true
set gold_cost = GetUnitGoldCost(hall)
if gold_cost > total_gold then
return false
endif
set total_gold = total_gold - gold_cost
if GetExpansionFoe() != null then
return true
endif
set peon = GetExpansionPeon()
if peon != null then
return SetExpansion(peon,hall)
endif
return true
endfunction
//============================================================================
function OneBuildLoop takes nothing returns nothing
local integer index = 0
local integer qty
local integer id
local integer tp
set total_gold = GetGold() - gold_buffer
set total_wood = GetWood()
loop
exitwhen index == build_length
set qty = build_qty [index]
set id = build_item[index]
set tp = build_type[index]
//--------------------------------------------------------------------
if tp == BUILD_UNIT then
if not StartUnit(qty,id,build_town[index]) then
return
endif
//--------------------------------------------------------------------
elseif tp == BUILD_UPGRADE then
call StartUpgrade(qty,id)
//--------------------------------------------------------------------
else // tp == BUILD_EXPAND
if not StartExpansion(qty,id) then
return
endif
endif
set index = index + 1
endloop
endfunction
//============================================================================
function StaggerSleep takes real base, real spread returns nothing
call Sleep(base + spread * I2R(GetAiPlayer()) / I2R(GetPlayers()))
endfunction
//============================================================================
function BuildLoop takes nothing returns nothing
call OneBuildLoop()
call StaggerSleep(1,2)
loop
call OneBuildLoop()
call Sleep(2)
endloop
endfunction
//============================================================================
function StartBuildLoop takes nothing returns nothing
call StartThread(function BuildLoop)
endfunction
//============================================================================
function SetInitialWave takes integer seconds returns nothing
set sleep_seconds = seconds
endfunction
//============================================================================
function AddSleepSeconds takes integer seconds returns nothing
set sleep_seconds = sleep_seconds + seconds
endfunction
//============================================================================
function SleepForever takes nothing returns nothing
call Trace("going to sleep forever\n") //xxx
loop
call Sleep(100)
endloop
endfunction
//============================================================================
function PlayGame takes nothing returns nothing
call StartBuildLoop()
call SleepForever()
endfunction
//============================================================================
function ConvertNeeds takes integer unitid returns nothing
if GetUnitCount(unitid) < 1 then
call StartUnit(1,unitid,-1)
endif
endfunction
//============================================================================
function Conversions takes integer desire, integer unitid returns nothing
if GetUnitCount(unitid) >= desire then
return
endif
if unitid == HIPPO_RIDER then
call ConvertNeeds(ARCHER)
call ConvertNeeds(HIPPO)
call MergeUnits(desire,ARCHER,HIPPO,HIPPO_RIDER)
elseif unitid == BLK_SPHINX then
call ConvertNeeds(OBS_STATUE)
call ConvertUnits(desire,OBS_STATUE)
endif
endfunction
//============================================================================
function SetAssaultGroup takes integer qty, integer max, integer unitid returns nothing
call Conversions(max,unitid)
if qty <= 0 and TownCountDone(unitid) == 0 then
return
endif
set harass_qty[harass_length] = qty
set harass_max[harass_length] = max
set harass_units[harass_length] = unitid
set harass_length = harass_length + 1
endfunction
//============================================================================
function Interleave3 takes integer e1, integer m1, integer h1, integer u1, integer e2, integer m2, integer h2, integer u2, integer e3, integer m3, integer h3, integer u3 returns nothing
local integer i1 = 1
local integer i2 = 1
local integer i3 = 1
local integer q1
local integer q2
local integer q3
if difficulty == EASY then
set q1 = e1
set q2 = e2
set q3 = e3
elseif difficulty == NORMAL then
set q1 = m1
set q2 = m2
set q3 = m3
else // difficulty == HARD
set q1 = h1
set q2 = h2
set q3 = h3
endif
loop
exitwhen q1<=0 and q2<=0 and q3<=0
if q1 > 0 then
call SetAssaultGroup(i1,i1,u1)
set q1 = q1 - 1
set i1 = i1 + 1
endif
if q2 > 0 then
call SetAssaultGroup(i2,i2,u2)
set q2 = q2 - 1
set i2 = i2 + 1
endif
if q3 > 0 then
call SetAssaultGroup(i3,i3,u3)
set q3 = q3 - 1
set i3 = i3 + 1
endif
endloop
endfunction
//============================================================================
function SetMeleeGroup takes integer unitid returns nothing
if unitid == hero_id then
call SetAssaultGroup(1,9,unitid)
else
call SetAssaultGroup((TownCountDone(unitid)*3)/4,20,unitid)
endif
endfunction
//============================================================================
function CampaignDefender takes integer level, integer qty, integer unitid returns nothing
if qty > 0 and difficulty >= level then
set defense_qty[defense_length] = qty
set defense_units[defense_length] = unitid
set defense_length = defense_length + 1
call Conversions(qty,unitid)
call SetBuildUnit(qty,unitid)
endif
endfunction
//============================================================================
function CampaignDefenderEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call CampaignDefender(EASY,easy,unitid)
elseif difficulty == NORMAL then
call CampaignDefender(NORMAL,med,unitid)
else
call CampaignDefender(HARD,hard,unitid)
endif
endfunction
//============================================================================
function CampaignAttacker takes integer level, integer qty, integer unitid returns nothing
if qty > 0 and difficulty >= level then
call SetAssaultGroup(qty,qty,unitid)
endif
endfunction
//============================================================================
function CampaignAttackerEx takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call CampaignAttacker(EASY,easy,unitid)
elseif difficulty == NORMAL then
call CampaignAttacker(NORMAL,med,unitid)
else
call CampaignAttacker(HARD,hard,unitid)
endif
endfunction
//============================================================================
function FormGroup takes integer seconds, boolean testReady returns nothing
local integer index
local integer count
local integer unitid
local integer desire
local integer readyPercent
// normally test for CaptainReadiness() of 50%
if testReady == true then
set readyPercent = 50
call Trace("forming group, requiring healthy guys\n") //xxx
else
set readyPercent = 0
call Trace("forming group, unit health not important\n") //xxx
endif
call Trace("trying to gather forces\n") //xxx
loop
call SuicideSleep(seconds)
call InitAssault()
set index = 0
loop
exitwhen index == harass_length
set unitid = harass_units[index]
set desire = harass_max[index]
set count = TownCountDone(unitid)
call Conversions(desire,unitid)
if count >= desire then
call AddAssault(desire,unitid)
else
set desire = harass_qty[index]
if count < desire then
call AddAssault(desire,unitid)
else
call AddAssault(count,unitid)
endif
endif
set index = index + 1
endloop
//xxx
if form_group_timeouts and (sleep_seconds < -60) then
call Trace("exit form group -- timeout\n")
elseif CaptainInCombat(true) then
call Trace("exit form group -- can't form while already in combat\n")
elseif CaptainIsFull() and CaptainReadiness() >= readyPercent then
call Trace("exit form group -- ready\n")
endif
//xxx
// time out and send group anyway if time has already expired
exitwhen form_group_timeouts and (sleep_seconds < -60)
exitwhen CaptainInCombat(true)
exitwhen CaptainIsFull() and CaptainReadiness() >= readyPercent
endloop
endfunction
//============================================================================
function WavePrepare takes integer unitid returns integer
return GetUnitBuildTime(unitid)
endfunction
//============================================================================
function PrepTime takes nothing returns integer
local integer unitid
local integer missing
local integer prep
local integer count
local integer largest = 30
local integer index = 0
loop
exitwhen index == harass_length
set unitid = harass_units[index]
set missing = harass_qty[index] + IgnoredUnits(unitid) - TownCount(unitid)
set prep = WavePrepare(unitid) * missing
if prep > largest then
set largest = prep
endif
set index = index + 1
endloop
call TraceI("next wave will require around %d seconds to build and gather\n",largest) //xxx
return largest
endfunction
//============================================================================
function PrepSuicideOnPlayer takes integer seconds returns boolean
local integer wave_prep = PrepTime()
local integer save_length
set save_length = harass_length
set harass_length = 0
call AddSleepSeconds(seconds)
if sleep_seconds-wave_prep > 0 then
call TraceI("going to sleep for %d seconds before gathering next attack wave\n",sleep_seconds-wave_prep) //xxx
call SuicideSleep(sleep_seconds-wave_prep)
endif
call Trace("preparing suicide attack wave\n") //xxx
set harass_length = save_length
if harass_length < 1 then
call Trace("ERROR - no units specificed, exiting early\n") //xxx
return false
endif
return true
endfunction
//============================================================================
function SleepUntilAtGoal takes nothing returns nothing
loop
exitwhen CaptainRetreating()
exitwhen CaptainAtGoal() // reached goal
exitwhen CaptainIsHome() // failed to path and returned home
exitwhen CaptainIsEmpty() // all units died
call SuicideSleep(3)
endloop
endfunction
//============================================================================
function SleepInCombat takes nothing returns nothing
local integer count = 0
debug call Trace("SleepInCombat\n")
loop
loop
exitwhen not CaptainInCombat(true) // goal is cleared
exitwhen CaptainIsEmpty() // duh
call SuicideSleep(1)
endloop
set count = count + 1
exitwhen count >= 8
//xxx this is what it should have been; do this for next patch?
//call SuicideSleep(1)
endloop
debug call Trace("exit SleepInCombat\n")
endfunction
//============================================================================
function AttackMoveXYA takes integer x, integer y returns nothing
if zep_next_wave then
call LoadZepWave(x,y)
set zep_next_wave = false
endif
call AttackMoveXY(x,y)
call SleepUntilAtGoal()
call SleepInCombat()
endfunction
//============================================================================
function SuicideOnPlayerWave takes nothing returns nothing
call Trace("waiting for attack wave to enter combat\n") //xxx
loop
//xxx
if allow_signal_abort and CommandsWaiting() != 0 then
call Trace("ABORT -- attack wave override\n")
endif
if CaptainInCombat(true) then
call Trace("done - captain has entered combat\n")
endif
if CaptainIsEmpty() then
call Trace("done - all units are dead\n")
endif
if sleep_seconds < -300 then
call Trace("done - timeout, took too long to reach engage the enemy\n")
endif
//xxx
exitwhen allow_signal_abort and CommandsWaiting() != 0
exitwhen CaptainInCombat(true)
exitwhen CaptainIsEmpty()
call SuicideSleep(10)
exitwhen sleep_seconds < -300
endloop
call Trace("waiting for attack wave to die\n") //xxx
loop
//xxx
if allow_signal_abort and CommandsWaiting() != 0 then
call Trace("ABORT - attack wave override\n")
endif
if CaptainIsEmpty() then
call Trace("done - all units are dead\n")
endif
if sleep_seconds < -300 then
call Trace("done - timeout, took too long to reach engage the enemy\n")
endif
//xxx
exitwhen allow_signal_abort and CommandsWaiting() != 0
exitwhen CaptainIsEmpty()
call SuicideSleep(10)
exitwhen sleep_seconds < -300
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function CommonSuicideOnPlayer takes boolean standard, boolean bldgs, integer seconds, player p, integer x, integer y returns nothing
local integer save_peons
if not PrepSuicideOnPlayer(seconds) then
return
endif
set save_peons = campaign_wood_peons
set campaign_wood_peons = 0
loop
//xxx
if allow_signal_abort and CommandsWaiting() != 0 then
call Trace("ABORT -- attack wave override\n")
endif
//xxx
exitwhen allow_signal_abort and CommandsWaiting() != 0
loop
exitwhen allow_signal_abort and CommandsWaiting() != 0
call FormGroup(5,true)
exitwhen sleep_seconds <= 0
call TraceI("waiting %d seconds before suicide\n",sleep_seconds) //xxx
endloop
if standard then
if bldgs then
exitwhen SuicidePlayer(p,sleep_seconds >= -60)
else
exitwhen SuicidePlayerUnits(p,sleep_seconds >= -60)
endif
else
call AttackMoveXYA(x,y)
endif
call TraceI("waiting %d seconds before timeout\n",60+sleep_seconds) //xxx
call SuicideSleep(5)
endloop
set campaign_wood_peons = save_peons
set harass_length = 0
call SuicideOnPlayerWave()
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPlayer takes integer seconds, player p returns nothing
call CommonSuicideOnPlayer(true,true,seconds,p,0,0)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnUnits takes integer seconds, player p returns nothing
call CommonSuicideOnPlayer(true,false,seconds,p,0,0)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPoint takes integer seconds, player p, integer x, integer y returns nothing
call CommonSuicideOnPlayer(false,false,seconds,p,x,y)
endfunction
//============================================================================
function SuicideUntilSignal takes integer seconds, player p returns nothing
local integer save
local integer wave_prep = PrepTime()
loop
call AddSleepSeconds(seconds)
if sleep_seconds-wave_prep > 0 then
call SuicideSleep(sleep_seconds-wave_prep)
endif
set save = campaign_wood_peons
set campaign_wood_peons = 0
loop
loop
call FormGroup(5, true)
exitwhen sleep_seconds <= 0
exitwhen CommandsWaiting() != 0
endloop
exitwhen SuicidePlayer(p,sleep_seconds >= -60)
exitwhen CommandsWaiting() != 0
call SuicideSleep(3)
endloop
set campaign_wood_peons = save
loop
exitwhen CaptainIsEmpty()
exitwhen CommandsWaiting() != 0
call SuicideSleep(5)
endloop
exitwhen CommandsWaiting() != 0
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnce takes integer easy, integer med, integer hard, integer unitid returns nothing
if difficulty == EASY then
call SuicideUnit(easy,unitid)
elseif difficulty == NORMAL then
call SuicideUnit(med,unitid)
else
call SuicideUnit(hard,unitid)
endif
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnitA takes integer unitid returns nothing
if unitid != 0 then
call SuicideUnit(1,unitid)
endif
call Sleep(0.1)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnitB takes integer unitid, integer playerid returns nothing
if unitid != 0 then
call SuicideUnitEx(1,unitid,playerid)
endif
call Sleep(0.1)
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnits takes integer u1, integer u2, integer u3, integer u4, integer u5, integer u6, integer u7, integer u8, integer u9, integer uA returns nothing
call Trace("MASS SUICIDE - this script is now technically done\n") //xxx
call PrepFullSuicide()
loop
call SuicideUnitA(u1)
call SuicideUnitA(u2)
call SuicideUnitA(u3)
call SuicideUnitA(u4)
call SuicideUnitA(u5)
call SuicideUnitA(u6)
call SuicideUnitA(u7)
call SuicideUnitA(u8)
call SuicideUnitA(u9)
call SuicideUnitA(uA)
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideUnitsEx takes integer playerid, integer u1, integer u2, integer u3, integer u4, integer u5, integer u6, integer u7, integer u8, integer u9, integer uA returns nothing
call Trace("MASS SUICIDE - this script is now technically done\n") //xxx
call PrepFullSuicide()
loop
call SuicideUnitB(u1,playerid)
call SuicideUnitB(u2,playerid)
call SuicideUnitB(u3,playerid)
call SuicideUnitB(u4,playerid)
call SuicideUnitB(u5,playerid)
call SuicideUnitB(u6,playerid)
call SuicideUnitB(u7,playerid)
call SuicideUnitB(u8,playerid)
call SuicideUnitB(u9,playerid)
call SuicideUnitB(uA,playerid)
endloop
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPlayerEx takes integer easy, integer med, integer hard, player p returns nothing
if difficulty == EASY then
call SuicideOnPlayer(easy,p)
elseif difficulty == NORMAL then
call SuicideOnPlayer(med,p)
else
call SuicideOnPlayer(hard,p)
endif
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnUnitsEx takes integer easy, integer med, integer hard, player p returns nothing
if difficulty == EASY then
call SuicideOnUnits(easy,p)
elseif difficulty == NORMAL then
call SuicideOnUnits(med,p)
else
call SuicideOnUnits(hard,p)
endif
endfunction
//--------------------------------------------------------------------------------------------------
function SuicideOnPointEx takes integer easy, integer med, integer hard, player p, integer x, integer y returns nothing
if difficulty == EASY then
call SuicideOnPoint(easy,p,x,y)
elseif difficulty == NORMAL then
call SuicideOnPoint(med,p,x,y)
else
call SuicideOnPoint(hard,p,x,y)
endif
endfunction
//============================================================================
function ForeverSuicideOnPlayer takes integer seconds, player p returns nothing
local integer length = harass_length
loop
exitwhen allow_signal_abort and CommandsWaiting() != 0
call SuicideOnPlayer(seconds,p)
set harass_length = length
endloop
endfunction
//============================================================================
function CommonSleepUntilTargetDead takes unit target, boolean reform returns nothing
loop
exitwhen CaptainRetreating()
exitwhen CaptainReadinessHP() <= 40
exitwhen not UnitAlive(target)
exitwhen UnitInvis(target) and not IsUnitDetected(target,ai_player)
if not TownThreatened() then
call AttackMoveKill(target)
endif
call SuicideSleep(3)
if reform and sleep_seconds < -40 then
if CaptainInCombat(true) then
set sleep_seconds = sleep_seconds + 5
else
set sleep_seconds = 0
call FormGroup(1,false)
endif
endif
endloop
endfunction
//============================================================================
function SleepUntilTargetDead takes unit target returns nothing
call CommonSleepUntilTargetDead(target,false)
endfunction
//============================================================================
function ReformUntilTargetDead takes unit target returns nothing
debug call Trace("ReformUntilTargetDead\n")
call CommonSleepUntilTargetDead(target,true)
endfunction
//============================================================================
function AttackMoveKillA takes unit target returns nothing
if target == null then
call SuicideSleep(3)
return
endif
debug call Trace("AttackMoveKillA\n")
call AttackMoveKill(target)
call ReformUntilTargetDead(target)
call SleepInCombat()
endfunction
//============================================================================
function MinorCreepAttack takes nothing returns nothing
local unit target = GetMinorCreep()
call SetAllianceTarget(target)
call FormGroup(3, true)
call AttackMoveKillA(target)
endfunction
//============================================================================
function MajorCreepAttack takes nothing returns nothing
local unit target = GetMajorCreep()
call SetAllianceTarget(target)
call FormGroup(3,true)
call AttackMoveKillA(target)
endfunction
//============================================================================
function CreepAttackEx takes nothing returns nothing
local unit target = GetCreepCamp(min_creeps,max_creeps,allow_air_creeps)
call SetAllianceTarget(target)
call FormGroup(3,true)
call AttackMoveKillA(target)
endfunction
//============================================================================
function AnyPlayerAttack takes nothing returns nothing
local unit hall
set hall = GetEnemyExpansion()
if hall == null then
call StartGetEnemyBase()
loop
exitwhen not WaitGetEnemyBase()
call SuicideSleep(1)
endloop
set hall = GetEnemyBase()
endif
call SetAllianceTarget(hall)
call FormGroup(3,true)
call AttackMoveKillA(hall)
endfunction
//============================================================================
function ExpansionAttack takes nothing returns nothing
local unit creep = GetExpansionFoe()
local integer x
call FormGroup(3, true)
if creep == null then
set x = GetExpansionX()
if x != -1 then
call AttackMoveXYA(x,GetExpansionY())
endif
else
call AttackMoveKillA(creep)
endif
endfunction
//============================================================================
// AddSiege
//============================================================================
function AddSiege takes nothing returns nothing
call SetAssaultGroup( 0, 9, SHADE )
call SetAssaultGroup( 0, 9, MEAT_WAGON )
call SetAssaultGroup( 0, 9, MORTAR )
call SetAssaultGroup( 0, 9, TANK )
call SetAssaultGroup( 0, 9, BALLISTA )
call SetAssaultGroup( 0, 9, CATAPULT )
endfunction
//===========================================================================
// GetAllyCount
//============================================================================
function GetAllyCount takes player whichPlayer returns integer
local integer playerIndex = 0
local integer count = 0
local player indexPlayer
loop
set indexPlayer = Player(playerIndex)
if whichPlayer != indexPlayer then
if GetPlayerAlliance(whichPlayer,indexPlayer,ALLIANCE_PASSIVE) then
if GetPlayerAlliance(indexPlayer,whichPlayer,ALLIANCE_PASSIVE) then
if GetPlayerStructureCount(indexPlayer,true) > 0 then
set count = count + 1
endif
endif
endif
endif
set playerIndex = playerIndex + 1
exitwhen playerIndex == 12
endloop
return count
endfunction
//============================================================================
// SingleMeleeAttack
//============================================================================
function SingleMeleeAttack takes boolean needs_exp, boolean has_siege, boolean major_ok, boolean air_units returns nothing
local boolean can_siege
local real daytime
local unit hall
local unit mega
local unit creep
local unit common
local integer minimum
local boolean allies
call Trace("===SingleMeleeAttack===\n") //xxx
if TownThreatened() then
call Trace("sleep 2, town threatened\n") //xxx
call Sleep(2)
return
endif
// purchase zeppelins
//
if get_zeppelin and GetGold() > 300 and GetWood() > 100 then
call Trace("purchase zep\n") //xxx
call PurchaseZeppelin()
set get_zeppelin = false
set ready_for_zeppelin = false
return
endif
set ready_for_zeppelin = true
// coordinate with allies
//
set allies = GetAllyCount(ai_player) > 0
if allies and MeleeDifficulty() != MELEE_NEWBIE then
set common = GetAllianceTarget()
if common != null then
call Trace("join ally force\n") //xxx
if GetMegaTarget() != null then
call AddSiege()
endif
call FormGroup(3,true)
call AttackMoveKillA(common)
call SetAllianceTarget(null)
return
endif
endif
// take expansions as needed
//
if needs_exp then
call Trace("needs exp\n") //xxx
set creep = GetExpansionFoe()
if creep != null then
call Trace("attack exp\n") //xxx
call SetAllianceTarget(creep)
call FormGroup(3,true)
call AttackMoveKillA(creep)
call Sleep(20)
set take_exp = false
return
endif
endif
// all-out attack if the player is weak
//
if MeleeDifficulty() != MELEE_NEWBIE then
set mega = GetMegaTarget()
if mega != null then
call Trace("MEGA TARGET!!!\n") //xxx
call AddSiege()
call FormGroup(3,true)
call AttackMoveKillA(mega)
return
endif
endif
// deny player an expansion
//
set hall = GetEnemyExpansion()
set daytime = GetFloatGameState(GAME_STATE_TIME_OF_DAY)
set can_siege = has_siege and (air_units or (daytime>=4 and daytime<=12))
if hall!=null and (can_siege or not IsTowered(hall)) then
call Trace("test player town attack\n") //xxx
if MeleeDifficulty() == MELEE_NEWBIE then
set minimum = 3
elseif allies and MeleeDifficulty() == MELEE_NORMAL then
set minimum = 1
else
set minimum = 0 // HARD, INSANE, and NORMAL with no allies
endif
if exp_seen >= minimum then
call Trace("do player town attack\n") //xxx
set exp_seen = 0
call AddSiege()
call SetAllianceTarget(hall)
call FormGroup(3,true)
call AttackMoveKillA(hall)
return
endif
set exp_seen = exp_seen + 1
endif
// attack player's main base when siege is available
//
if can_siege then
call Trace("attack player's town\n") //xxx
call AddSiege()
call AnyPlayerAttack()
return
endif
// extended, more specific method of determining creep levels
//
if min_creeps != -1 then
call TraceI("custom creep attack %d\n",max_creeps) //xxx
call CreepAttackEx()
return
endif
// nothing better to do, so kill a creep camp
//
if major_ok then
call Trace("major creep attack\n") //xxx
call MajorCreepAttack()
return
endif
call Trace("minor creep attack\n") //xxx
call MinorCreepAttack()
endfunction
//============================================================================
function GetZeppelin takes nothing returns nothing
if ready_for_zeppelin then
set get_zeppelin = true
endif
endfunction
//============================================================================
function FoodUsed takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_FOOD_USED)
endfunction
//============================================================================
function FoodCap takes nothing returns integer
return GetPlayerState(ai_player,PLAYER_STATE_RESOURCE_FOOD_CAP)
endfunction
//============================================================================
function FoodSpace takes nothing returns integer
return FoodCap() - FoodUsed()
endfunction
//============================================================================
function FoodAvail takes integer base returns integer
return GetFoodMade(racial_farm) * TownCount(racial_farm) + GetFoodMade(base) * TownCount(base)
endfunction
//============================================================================
function BuildAttackers takes nothing returns nothing
local integer index = 0
local integer unitid
local integer desire
local integer count
loop
exitwhen index == harass_length
set unitid = harass_units[index]
set desire = harass_qty[index] + IgnoredUnits(unitid)
set count = TownCount(unitid)
if count != desire then
if not StartUnit(desire,unitid,-1) then
return
endif
endif
set index = index + 1
endloop
endfunction
//============================================================================
function BuildDefenders takes nothing returns nothing
local integer index = 0
local integer unitid
local integer qty
loop
exitwhen index == defense_length
set unitid = defense_units[index]
set qty = defense_qty[index]
call Conversions(qty,unitid)
call AddDefenders(qty,unitid)
set index = index + 1
endloop
endfunction
//============================================================================
function CampaignBasicsA takes nothing returns nothing
local integer food_each = GetFoodMade(racial_farm)
local integer on_wood
call ClearHarvestAI()
if CaptainInCombat(false) then
set on_wood = 0
else
set on_wood = campaign_wood_peons
endif
call HarvestGold(0,campaign_gold_peons)
call HarvestWood(0,on_wood)
if harvest_town1 then
call HarvestGold(1,campaign_gold_peons)
call HarvestWood(1,on_wood)
endif
if harvest_town2 then
call HarvestGold(2,campaign_gold_peons)
call HarvestWood(2,on_wood)
endif
if harvest_town3 then
call HarvestGold(3,campaign_gold_peons)
call HarvestWood(3,on_wood)
endif
if do_campaign_farms and FoodUsed()+food_each-1 > food_each*(TownCount(racial_farm)+1) then
call StartUnit(TownCount(racial_farm)+1,racial_farm,-1)
endif
if build_campaign_attackers then
call BuildAttackers()
endif
if not CaptainInCombat(false) then
call BuildDefenders()
endif
call FillGuardPosts()
call ReturnGuardPosts()
endfunction
//============================================================================
function CampaignBasics takes nothing returns nothing
call Sleep(1)
call CampaignBasicsA()
call StaggerSleep(1,5)
loop
call CampaignBasicsA()
call Sleep(campaign_basics_speed)
endloop
endfunction
//============================================================================
function CampaignAI takes integer farms, code heroes returns nothing
if GetGameDifficulty() == MAP_DIFFICULTY_EASY then
set difficulty = EASY
call SetTargetHeroes(false)
call SetUnitsFlee(false)
elseif GetGameDifficulty() == MAP_DIFFICULTY_NORMAL then
set difficulty = NORMAL
call SetTargetHeroes(false)
call SetUnitsFlee(false)
elseif GetGameDifficulty() == MAP_DIFFICULTY_HARD then
set difficulty = HARD
call SetPeonsRepair(true)
else
set difficulty = INSANE
endif
call InitAI()
call InitBuildArray()
call InitAssaultGroup()
call CreateCaptains()
call SetNewHeroes(false)
if heroes != null then
call SetHeroLevels(heroes)
endif
call SetHeroesFlee(false)
call SetGroupsFlee(false)
call SetSlowChopping(true)
call GroupTimedLife(false)
call SetCampaignAI()
call Sleep(0.1)
set racial_farm = farms
call StartThread(function CampaignBasics)
call StartBuildLoop()
endfunction
//============================================================================
function UnsummonAll takes nothing returns nothing
local unit bldg
loop
set bldg = GetBuilding(ai_player)
exitwhen bldg==null
call Unsummon(bldg)
call Sleep(2)
endloop
endfunction
//============================================================================
// SkillArrays
//============================================================================
function SkillArrays takes nothing returns integer
local integer level = GetHeroLevelAI()
if level > max_hero_level then
set max_hero_level = level
endif
if GetHeroId() == hero_id then
return skills1[level]
elseif GetHeroId() == hero_id2 then
return skills2[level]
else
return skills3[level]
endif
endfunction
//--------------------------------------------------------------------------------------------------
// SetSkillArray
//--------------------------------------------------------------------------------------------------
function SetSkillArray takes integer index, integer id returns nothing
local integer i = 1
if index == 1 then
if hero_id != id then
return
endif
loop
set skills1[i] = skill[i]
exitwhen i == 10
set i = i + 1
endloop
elseif index == 2 then
if hero_id2 != id then
return
endif
loop
set skills2[i] = skill[i]
exitwhen i == 10
set i = i + 1
endloop
else
if hero_id3 != id then
return
endif
loop
set skills3[i] = skill[i]
exitwhen i == 10
set i = i + 1
endloop
endif
endfunction
//============================================================================
// AwaitMeleeHeroes
//============================================================================
function AwaitMeleeHeroes takes nothing returns nothing
if GetUnitCountDone(hero_id2) > 0 then
set two_heroes = true
endif
loop
exitwhen GetUnitCountDone(hero_id)>0 and (take_exp or (not two_heroes or GetUnitCountDone(hero_id2)>0))
call Sleep(1)
endloop
endfunction
//============================================================================
// PickMeleeHero
//============================================================================
function PickMeleeHero takes race raceid returns integer
local integer first
local integer second
local integer third
local integer last
local integer array heroes
//------------------------------------------------------------------------
if raceid == RACE_HUMAN then
//------------------------------------------------------------------------
set heroes[1] = ARCHMAGE
set heroes[2] = MTN_KING
set heroes[3] = PALADIN
set heroes[4] = BLOOD_MAGE
//------------------------------------------------------------------------
elseif raceid == RACE_ORC then
//------------------------------------------------------------------------
set heroes[1] = BLADE_MASTER
set heroes[2] = FAR_SEER
set heroes[3] = TAUREN_CHIEF
set heroes[4] = SHADOW_HUNTER
//------------------------------------------------------------------------
elseif raceid == RACE_NIGHTELF then
//------------------------------------------------------------------------
set heroes[1] = DEMON_HUNTER
set heroes[2] = KEEPER
set heroes[3] = MOON_BABE
set heroes[4] = WARDEN
//------------------------------------------------------------------------
elseif raceid == RACE_UNDEAD then
//------------------------------------------------------------------------
set heroes[1] = DEATH_KNIGHT
set heroes[2] = DREAD_LORD
set heroes[3] = LICH
set heroes[4] = CRYPT_LORD
else
set hero_id = 0
endif
if VersionCompatible(VERSION_FROZEN_THRONE) then
set last = 4
else
set last = 3
endif
set first = GetRandomInt(1,last)
set second = GetRandomInt(1,last-1)
set third = GetRandomInt(1,last-2)
set hero_id = heroes[first]
set heroes[first] = heroes[last]
set hero_id2 = heroes[second]
set heroes[second] = heroes[last-1]
set hero_id3 = heroes[third]
return hero_id
endfunction
//============================================================================
// ADVANCED UTILITY
//============================================================================
function AdvDistance takes real x1, real y1, real x2, real y2 returns real
return SquareRoot(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
endfunction
function AdvIsUnitAlive takes unit u returns boolean
return GetUnitState(u, UNIT_STATE_LIFE) > 0.0
endfunction
function AdvDebugDisplayToPlayer takes string message returns nothing
if debug then
call DisplayTextToPlayer(debug_player, 0, 0, message)
endif
call Trace(message)
endfunction
//============================================================================
// ADVANCED SETTINGS
//============================================================================
function AdvSetDebug takes player debugPlayer, boolean flag returns nothing
set debug_player = debugPlayer
set debug = flag
endfunction
//============================================================================
function AdvSetWaveReachGoalTimeoutSeconds takes integer seconds returns nothing
set wave_reach_goal_timeout_seconds = seconds
endfunction
function AdvSetWaveEnterCombatTimeoutSeconds takes integer seconds returns nothing
set wave_enter_combat_timeout_seconds = seconds
endfunction
function AdvSetWaveClearGoalTimeoutSeconds takes integer seconds returns nothing
set wave_clear_goal_timeout_seconds = seconds
endfunction
//============================================================================
function AdvSetContinueFarAwayRadius takes real radius returns nothing
set continue_far_away_radius = radius
endfunction
function AdvSetPreferredLocationsRadius takes real radius returns nothing
set preferred_locations_radius = radius
endfunction
//============================================================================
function AdvSetWoodPeonsWarriors takes boolean flag returns nothing
set wood_peons_warriors = flag
endfunction
//============================================================================
function AdvSetSearchPreferredLocations takes boolean flag returns nothing
set search_preferred_locations = flag
endfunction
function AdvSetSearchEverywhere takes boolean flag returns nothing
set search_everywhere = flag
endfunction
function AdvSetCanSearchUnit takes boolean flag returns nothing
set can_search_units = flag
endfunction
function AdvSetCanSearchInvulnerable takes boolean flag returns nothing
set can_search_invulnerable = flag
endfunction
//============================================================================
function AdvSetPrioritizeTownHalls takes boolean flag returns nothing
set prioritize_town_halls = flag
endfunction
function AdvSetPrioritizeNearest takes boolean flag returns nothing
set prioritize_nearest = flag
endfunction
function AdvSetSearchTargetMaxCyclesDefault takes integer value returns nothing
set search_target_max_cycles_default = value
endfunction
function AdvSetSearchTargetMaxCyclesLastTarget takes integer value returns nothing
set search_target_max_cycles_last_target = value
endfunction
//============================================================================
function AdvSetUseReachGoalTimeout takes boolean flag returns nothing
set reach_goal_timeout = flag
endfunction
function AdvSetUseEnterCombatTimeout takes boolean flag returns nothing
set reach_goal_timeout = flag
endfunction
function AdvSetUseClearGoalTimeout takes boolean flag returns nothing
set enter_combat_timeout = flag
endfunction
//============================================================================
function AdvSetContinueAttackPercentage takes integer percentage returns nothing
set continue_attack_percentage = percentage
endfunction
function AdvSetContinueAttackReducePercentageIfFarAway takes boolean flag returns nothing
set continue_attack_reduce_percentage_if_far_away = flag
endfunction
function AdvSetContinueAttackReducePercentage takes integer percentage returns nothing
set continue_attack_reduce_percentage = percentage
endfunction
//============================================================================
function AdvSetAttackWaveGatherReturnXY takes real x, real y returns nothing
set attack_wave_gather_return_x = x
set attack_wave_gather_return_y = y
set attack_wave_gather_return_set = true
endfunction
function AdvSetDefenseCaptainHomeXY takes real x, real y returns nothing
set defense_captain_home_x = x
set defense_captain_home_y = y
set defense_captain_home_set = true
endfunction
// Note: this should never be called in the script with argument different from DEFENSE_CAPTAIN
function AdvSetCaptainHome takes integer whichCaptain returns nothing
if whichCaptain == BOTH_CAPTAINS then
if defense_captain_home_set then
call SetCaptainHome(DEFENSE_CAPTAIN, defense_captain_home_x, defense_captain_home_y)
endif
if attack_wave_gather_return_set then
call SetCaptainHome(ATTACK_CAPTAIN, attack_wave_gather_return_x, attack_wave_gather_return_y)
endif
return
endif
if defense_captain_home_set and whichCaptain == DEFENSE_CAPTAIN then
call SetCaptainHome(DEFENSE_CAPTAIN, defense_captain_home_x, defense_captain_home_y)
return
endif
if attack_wave_gather_return_set and whichCaptain == ATTACK_CAPTAIN then
call SetCaptainHome(ATTACK_CAPTAIN, attack_wave_gather_return_x, attack_wave_gather_return_y)
return
endif
endfunction
//============================================================================
function AdvAddPreferredLocation takes real x, real y returns integer
set preferred_locations[preferred_locations_count] = Location(x, y)
set preferred_locations_count = preferred_locations_count + 1
return preferred_locations_count
endfunction
function AdvSetPreferredLocationXY takes integer index, real x, real y returns nothing
if index < preferred_locations_count then
set preferred_locations[index] = Location(x, y)
endif
endfunction
//============================================================================
function AdvSetSuicide takes boolean flag returns nothing
set suicide = flag
endfunction
//============================================================================
// ADVANCED SUICIDE WAVE LAUNCH
//============================================================================
function AdvGetRandomTargetableTownHall takes nothing returns unit
local integer randomIndex = GetRandomInt(0, targetable_town_halls_count - 1)
if targetable_town_halls_count <= 0 then
return null
endif
return targetable_town_halls[randomIndex]
endfunction
function AdvGetNearestTargetableTownHall takes real x, real y returns unit
local integer index = 0
local integer minDistanceIndex = 0
local real minDistance
if targetable_town_halls_count <= 0 then
return null
endif
set minDistance = AdvDistance(GetUnitX(targetable_town_halls[minDistanceIndex]), GetUnitY(targetable_town_halls[minDistanceIndex]), x, y)
loop
set index = index + 1
exitwhen index >= targetable_town_halls_count
if AdvDistance(GetUnitX(targetable_town_halls[index]), GetUnitY(targetable_town_halls[index]), x, y) < minDistance then
set minDistanceIndex = index
set minDistance = AdvDistance(GetUnitX(targetable_town_halls[minDistanceIndex]), GetUnitY(targetable_town_halls[minDistanceIndex]), x, y)
endif
endloop
return targetable_town_halls[minDistanceIndex]
endfunction
//============================================================================
function AdvGetRandomTargetableBuilding takes nothing returns unit
local integer randomIndex = GetRandomInt(0, targetable_buildings_count - 1)
if targetable_buildings_count <= 0 then
return null
endif
return targetable_buildings[randomIndex]
endfunction
function AdvGetNearestTargetableBuilding takes real x, real y returns unit
local integer index = 0
local integer minDistanceIndex = 0
local real minDistance
if targetable_buildings_count <= 0 then
return null
endif
set minDistance = AdvDistance(GetUnitX(targetable_buildings[minDistanceIndex]), GetUnitY(targetable_buildings[minDistanceIndex]), x, y)
loop
set index = index + 1
exitwhen index >= targetable_buildings_count
if AdvDistance(GetUnitX(targetable_buildings[index]), GetUnitY(targetable_buildings[index]), x, y) < minDistance then
set minDistanceIndex = index
set minDistance = AdvDistance(GetUnitX(targetable_buildings[minDistanceIndex]), GetUnitY(targetable_buildings[minDistanceIndex]), x, y)
endif
endloop
return targetable_buildings[minDistanceIndex]
endfunction
//============================================================================
function AdvGetRandomTargetableUnit takes nothing returns unit
local integer randomIndex = GetRandomInt(0, targetable_units_count - 1)
if targetable_units_count <= 0 then
return null
endif
return targetable_units[randomIndex]
endfunction
function AdvGetNearestTargetableUnit takes real x, real y returns unit
local integer index = 0
local integer minDistanceIndex = 0
local real minDistance
if targetable_units_count <= 0 then
return null
endif
set minDistance = AdvDistance(GetUnitX(targetable_units[minDistanceIndex]), GetUnitY(targetable_units[minDistanceIndex]), x, y)
loop
set index = index + 1
exitwhen index >= targetable_units_count
if AdvDistance(GetUnitX(targetable_units[index]), GetUnitY(targetable_units[index]), x, y) < minDistance then
set minDistanceIndex = index
set minDistance = AdvDistance(GetUnitX(targetable_units[minDistanceIndex]), GetUnitY(targetable_units[minDistanceIndex]), x, y)
endif
endloop
return targetable_units[minDistanceIndex]
endfunction
//============================================================================
function AdvAssignTargetableTownHalls takes player targetPlayer, boolean preferredLocations returns boolean
local unit currentUnit = null
local group targetsGroup = CreateGroup()
local group tempGroup = CreateGroup()
local integer index = 0
set targetable_town_halls_count = 0
if preferredLocations then
loop
call GroupEnumUnitsInRangeOfLoc(tempGroup, preferred_locations_current[index], preferred_locations_radius, null)
call BlzGroupAddGroupFast(tempGroup, targetsGroup)
set index = index + 1
call GroupClear(tempGroup)
exitwhen index >= preferred_locations_count_current
endloop
else
call GroupEnumUnitsOfPlayer(targetsGroup, targetPlayer, null)
endif
if BlzGroupGetSize(targetsGroup) <= 0 then
return false
endif
loop
set currentUnit = FirstOfGroup(targetsGroup)
exitwhen currentUnit == null
if GetOwningPlayer(currentUnit) == targetPlayer and IsUnitType(currentUnit, UNIT_TYPE_TOWNHALL) and IsUnitType(currentUnit, UNIT_TYPE_STRUCTURE) and AdvIsUnitAlive(currentUnit) then
if not can_search_invulnerable then
if not BlzIsUnitInvulnerable(currentUnit) then
set targetable_town_halls[targetable_town_halls_count] = currentUnit
set targetable_town_halls_count = targetable_town_halls_count + 1
endif
else
set targetable_town_halls[targetable_town_halls_count] = currentUnit
set targetable_town_halls_count = targetable_town_halls_count + 1
endif
endif
call GroupRemoveUnit(targetsGroup, currentUnit)
endloop
if targetable_town_halls_count > 0 then
return true
endif
return false
endfunction
//============================================================================
function AdvAssignTargetableBuildings takes player targetPlayer, boolean preferredLocations returns boolean
local unit currentUnit = null
local group targetsGroup = CreateGroup()
local group tempGroup = CreateGroup()
local integer index = 0
set targetable_buildings_count = 0
if preferredLocations then
loop
call GroupEnumUnitsInRangeOfLoc(tempGroup, preferred_locations_current[index], preferred_locations_radius, null)
call BlzGroupAddGroupFast(tempGroup, targetsGroup)
set index = index + 1
call GroupClear(tempGroup)
exitwhen index >= preferred_locations_count_current
endloop
else
call GroupEnumUnitsOfPlayer(targetsGroup, targetPlayer, null)
endif
if BlzGroupGetSize(targetsGroup) <= 0 then
return false
endif
loop
set currentUnit = FirstOfGroup(targetsGroup)
exitwhen currentUnit == null
if GetOwningPlayer(currentUnit) == targetPlayer and IsUnitType(currentUnit, UNIT_TYPE_STRUCTURE) and AdvIsUnitAlive(currentUnit) then
if not can_search_invulnerable then
if not BlzIsUnitInvulnerable(currentUnit) then
set targetable_buildings[targetable_buildings_count] = currentUnit
set targetable_buildings_count = targetable_buildings_count + 1
endif
else
set targetable_buildings[targetable_buildings_count] = currentUnit
set targetable_buildings_count = targetable_buildings_count + 1
endif
endif
call GroupRemoveUnit(targetsGroup, currentUnit)
endloop
if targetable_buildings_count > 0 then
return true
endif
return false
endfunction
//============================================================================
function AdvAssignTargetableUnits takes player targetPlayer, boolean preferredLocations returns boolean
local unit currentUnit = null
local group targetsGroup = CreateGroup()
local group tempGroup = CreateGroup()
local integer index = 0
set targetable_units_count = 0
if preferredLocations then
loop
call GroupEnumUnitsInRangeOfLoc(tempGroup, preferred_locations_current[index], preferred_locations_radius, null)
call BlzGroupAddGroupFast(tempGroup, targetsGroup)
set index = index + 1
call GroupClear(tempGroup)
exitwhen index >= preferred_locations_count_current
endloop
else
call GroupEnumUnitsOfPlayer(targetsGroup, targetPlayer, null)
endif
if BlzGroupGetSize(targetsGroup) <= 0 then
return false
endif
loop
set currentUnit = FirstOfGroup(targetsGroup)
exitwhen currentUnit == null
if GetOwningPlayer(currentUnit) == targetPlayer and AdvIsUnitAlive(currentUnit) then
if not can_search_invulnerable then
if not BlzIsUnitInvulnerable(currentUnit) then
set targetable_units[targetable_units_count] = currentUnit
set targetable_units_count = targetable_units_count + 1
endif
else
set targetable_units[targetable_units_count] = currentUnit
set targetable_units_count = targetable_units_count + 1
endif
endif
call GroupRemoveUnit(targetsGroup, currentUnit)
endloop
if targetable_units_count > 0 then
return true
endif
return false
endfunction
//============================================================================
function AdvSuicideWaveLaunch takes player targetPlayer, real startX, real startY, boolean searchPreferredLocations, boolean searchEverywhere, boolean canSearchUnits, boolean prioritizeTownHalls, boolean prioritizeNearest, boolean useLastTarget returns boolean
local unit target = null
local integer index = 0
local real targetX
local real targetY
local integer searchTargetCycle = 0
// Set the start coordinates
if useLastTarget then
set startX = last_target_x
set startY = last_target_y
endif
call AdvDebugDisplayToPlayer("AI Info: searching for a target...")
// --- LOOP UNTIL TARGET IS FOUND OR TIMEOUT IS REACHED ---
loop
exitwhen suicide
// --- CACHE PREFERRED LOCATIONS ---
set preferred_locations_count_current = 0
if searchPreferredLocations and preferred_locations_count > 0 then
set preferred_locations_count_current = preferred_locations_count
loop
set preferred_locations_current[index] = preferred_locations[index]
set index = index + 1
exitwhen index >= preferred_locations_count_current
endloop
endif
// --- TOWN HALLS PREFERRED LOCATIONS ---
if searchPreferredLocations and prioritizeTownHalls and preferred_locations_count_current > 0 then
call AdvDebugDisplayToPlayer("AI Info: looking for target town halls at preferred locations...")
if AdvAssignTargetableTownHalls(targetPlayer, true) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable town hall found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest town hall...")
// Nearest town hall
set target = AdvGetNearestTargetableTownHall(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random town hall target...")
// Random town hall
set target = AdvGetRandomTargetableTownHall()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: town hall target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: town hall target found!")
endif
endif
exitwhen target != null
// --- BUILDINGS PREFERRED LOCATIONS ---
if searchPreferredLocations and preferred_locations_count_current > 0 then
call AdvDebugDisplayToPlayer("AI Info: looking for target buildings at preferred locations...")
if AdvAssignTargetableBuildings(targetPlayer, true) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable building found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest building...")
// Nearest building
set target = AdvGetNearestTargetableBuilding(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random building...")
// Random building
set target = AdvGetRandomTargetableBuilding()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: building target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: building target found!")
endif
endif
exitwhen target != null
// --- TOWN HALLS EVERYWHERE ----
if searchEverywhere and prioritizeTownHalls then
call AdvDebugDisplayToPlayer("AI Info: looking for target town halls anywhere on the map...")
if AdvAssignTargetableTownHalls(targetPlayer, false) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable town hall found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest town hall...")
// Nearest town hall
set target = AdvGetNearestTargetableTownHall(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random town hall target...")
// Random town hall
set target = AdvGetRandomTargetableTownHall()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: town hall target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: town hall target found!")
endif
endif
exitwhen target != null
// --- BUILDINGS EVERYWHERE ---
if searchEverywhere then
call AdvDebugDisplayToPlayer("AI Info: looking for target buildings anywhere on the map...")
if AdvAssignTargetableBuildings(targetPlayer, false) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable building found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest building...")
// Nearest building
set target = AdvGetNearestTargetableBuilding(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random building...")
// Random building
set target = AdvGetRandomTargetableBuilding()
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: building target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: building target found!")
endif
endif
endif
exitwhen target != null
// --- UNITS PREFERRED LOCATIONS ---
if canSearchUnits and searchPreferredLocations and preferred_locations_count_current > 0 then
call AdvDebugDisplayToPlayer("AI Info: looking for target units at preferred locations...")
if AdvAssignTargetableUnits(targetPlayer, true) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable unit found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest unit...")
// Nearest unit
set target = AdvGetNearestTargetableUnit(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random unit...")
// Random unit
set target = AdvGetRandomTargetableUnit()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: unit target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: unit target found!")
endif
endif
exitwhen target != null
// --- UNITS EVERYWHERE ---
if canSearchUnits and searchEverywhere then
call AdvDebugDisplayToPlayer("AI Info: looking for target units anywhere on the map...")
if AdvAssignTargetableUnits(targetPlayer, false) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable unit found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest unit...")
// Nearest unit
set target = AdvGetNearestTargetableUnit(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random unit...")
// Random unit
set target = AdvGetRandomTargetableUnit()
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: unit target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: unit target found!")
endif
endif
endif
exitwhen target != null
call Sleep(5)
set searchTargetCycle = searchTargetCycle + 1
// When using the last target as start point, we are continuing the attack, so cycle less times (a negative value means to cycle forever)
if search_target_max_cycles_last_target > -1 then
exitwhen useLastTarget and searchTargetCycle > search_target_max_cycles_last_target
endif
// Cycles timeout (a negative value means to cycle forever)
if search_target_max_cycles_default > -1 then
exitwhen searchTargetCycle > search_target_max_cycles_default
endif
call AdvDebugDisplayToPlayer("AI Info: no target found, trying again...")
endloop
// --- LAUNCH ATTACK IF THERE IS A TARGET---
if target != null then
call AdvDebugDisplayToPlayer("AI Info: assigning target coordinates and launching attack!")
set targetX = GetUnitX(target)
set targetY = GetUnitY(target)
// Send the captain to attack and store last targets
set last_target_x = targetX
set last_target_y = targetY
call SetCaptainHome(ATTACK_CAPTAIN, targetX, targetY)
call CaptainAttack(targetX, targetY)
if not suicide then
call Sleep(5)
endif
// Target found
return true
endif
call AdvDebugDisplayToPlayer("AI Info: no target found, done trying!")
// No target found
return false
endfunction
//============================================================================
// ADVANCED SUICIDE WAVE ROUTINE
//============================================================================
function AdvWaveReachGoalSleep takes integer seconds returns nothing
// Decrease counter seconds to reach the goal
set wave_reach_goal_sleep_seconds = wave_reach_goal_sleep_seconds - seconds
// Wait with precision
loop
exitwhen seconds <= 0
exitwhen suicide
if seconds >= 5 then
call Sleep(5)
set seconds = seconds - 5
else
call Sleep(seconds)
set seconds = 0
endif
endloop
endfunction
function AdvWaveEnterCombatSleep takes integer seconds returns nothing
// Decrease counter seconds to enter combat
set wave_enter_combat_sleep_seconds = wave_enter_combat_sleep_seconds - seconds
// Wait with precision
loop
exitwhen seconds <= 0
exitwhen suicide
if seconds >= 5 then
call Sleep(5)
set seconds = seconds - 5
else
call Sleep(seconds)
set seconds = 0
endif
endloop
endfunction
function AdvWaveClearGoalSleep takes integer seconds returns nothing
// Decrease counter seconds to die
set wave_clear_goal_sleep_seconds = wave_clear_goal_sleep_seconds - seconds
// Wait with precision
loop
exitwhen seconds <= 0
exitwhen suicide
if seconds >= 5 then
call Sleep(5)
set seconds = seconds - 5
else
call Sleep(seconds)
set seconds = 0
endif
endloop
endfunction
//============================================================================
function AdvSuicideWaveRoutineReachGoal takes boolean timeout returns integer
local boolean goalReached = false
local boolean combatEntered = false
local boolean allDead = false
local boolean outOfTime = false
loop
exitwhen suicide
// Goal reached
if CaptainAtGoal() then
set goalReached = true
endif
exitwhen CaptainAtGoal()
// Entered combat before reaching goal
if CaptainInCombat(true) then
set combatEntered = true
endif
// All dead
if CaptainIsEmpty() then
set allDead = true
endif
exitwhen CaptainIsEmpty()
call AdvWaveReachGoalSleep(2)
// Out of time
if timeout then
if wave_reach_goal_sleep_seconds < -wave_reach_goal_timeout_seconds then
set outOfTime = true
endif
exitwhen wave_reach_goal_sleep_seconds < -wave_reach_goal_timeout_seconds
endif
endloop
// Goal is reached and fought before
if goalReached and combatEntered then
return 1
// Goal is reached and not fought before
elseif goalReached and not combatEntered then
return 2
// Attack wave is dead (no matter having fought)
elseif allDead then
return -1
// Timeout expired
elseif outOfTime then
return 0
endif
endfunction
function AdvSuicideWaveRoutineEnterCombat takes boolean timeout returns integer
local boolean combatEntered = false
local boolean allDead = false
local boolean outOfTime = false
loop
exitwhen suicide
// Combat
if CaptainInCombat(true) then
set combatEntered = true
endif
exitwhen CaptainInCombat(true)
// All dead
if CaptainIsEmpty() then
set allDead = true
endif
exitwhen CaptainIsEmpty()
call AdvWaveEnterCombatSleep(1)
// Out of time
if timeout then
if wave_reach_goal_sleep_seconds < -wave_reach_goal_timeout_seconds then
set outOfTime = true
endif
exitwhen wave_enter_combat_sleep_seconds < -wave_enter_combat_timeout_seconds
endif
endloop
// Combat entered
if combatEntered then
return 1
// Attack wave is dead
elseif allDead then
return -1
// Timeout expired
elseif outOfTime then
return 0
endif
endfunction
function AdvSuicideWaveRoutineClearGoal takes boolean timeout returns integer
local boolean goalCleared = false
local boolean allDead = false
local boolean outOfTime = false
loop
exitwhen suicide
// Goal cleared
if not CaptainInCombat(true) then
set goalCleared = true
endif
exitwhen not CaptainInCombat(true)
// All dead
if CaptainIsEmpty() then
set allDead = true
endif
exitwhen CaptainIsEmpty()
call AdvWaveClearGoalSleep(2)
// Out of time
if timeout then
if wave_reach_goal_sleep_seconds < -wave_reach_goal_timeout_seconds then
set outOfTime = true
endif
exitwhen wave_clear_goal_sleep_seconds < -wave_clear_goal_timeout_seconds
endif
endloop
// Goal cleared
if goalCleared then
return 1
// Attack wave is dead
elseif allDead then
return -1
// Timeout expired
elseif outOfTime then
return 0
endif
endfunction
//============================================================================
function AdvSuicideWaveRoutine takes player targetPlayer, real startX, real startY, boolean reachGoalTimeout, boolean enterCombatTimeout, boolean clearGoalTimeout, integer continuePercentage returns nothing
local integer continuePercentageNew = continuePercentage
local real previousTargetX = last_target_x
local real previousTargetY = last_target_y
local integer state = 0
local boolean combatEnteredBeforeGoal = false
local boolean goalReached = false
local boolean combatEntered = false
local boolean goalCleared = false
local boolean continueAttackReducePercentageIfFarAway = continue_attack_reduce_percentage_if_far_away
local boolean newTargetFound = false
// Reset all timeout seconds
set wave_reach_goal_sleep_seconds = 0
set wave_enter_combat_sleep_seconds = 0
set wave_clear_goal_sleep_seconds = 0
call AdvDebugDisplayToPlayer("AI Info: waiting for attack wave to reach goal...")
// --- WAIT TO REACH GOAL (OR DIE) ---
set state = AdvSuicideWaveRoutineReachGoal(reachGoalTimeout)
// Timeout
if state == 0 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal reached timeout!")
set goalReached = false
set combatEnteredBeforeGoal = false
// Dead
elseif state == -1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead!")
set goalReached = false
set combatEnteredBeforeGoal = false
// Goal reached after figthing
elseif state == 1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal reached after figthing!")
set goalReached = true
set combatEnteredBeforeGoal = true
// Goal reached before figthing
elseif state == 2 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal reached before figthing!")
set goalReached = true
set combatEnteredBeforeGoal = false
endif
call AdvDebugDisplayToPlayer("AI Info: waiting for attack wave to enter combat...")
// --- WAIT TO ENTER COMBAT (OR DIE) ---
set state = AdvSuicideWaveRoutineEnterCombat(enterCombatTimeout)
// Timeout
if state == 0 then
call AdvDebugDisplayToPlayer("AI Info: attack wave enter combat reached timeout!")
set combatEntered = false
// Dead
elseif state == -1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead!")
set combatEntered = true
// Combat entered
elseif state == 1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave combat entered!")
set combatEntered = true
endif
call AdvDebugDisplayToPlayer("AI Info: waiting for attack wave to clear goal...")
// --- WAIT TO CLEAR GOAL (OR DIE) ---
set state = AdvSuicideWaveRoutineClearGoal(clearGoalTimeout)
// Timeout
if state == 0 then
call AdvDebugDisplayToPlayer("AI Info: attack wave clear goal reached timeout!")
set combatEntered = false
// Dead
elseif state == -1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead!")
set combatEntered = true
// Goal cleared
elseif state == 1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal cleared!")
set goalCleared = true
endif
// --- GROUP DEAD ---
if CaptainIsEmpty() then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead, going back home...")
call SetCaptainHome(ATTACK_CAPTAIN, startX, startY)
call CaptainAttack(startX, startY)
if not suicide then
call Sleep(1)
endif
call TeleportCaptain(startX, startY)
// Wait for the captain to go back home
loop
call Sleep(1)
exitwhen suicide
exitwhen CaptainAtGoal()
endloop
call AdvDebugDisplayToPlayer("AI Info: returned home!")
call ClearCaptainTargets()
call ResetCaptainLocs()
call AdvSetCaptainHome(BOTH_CAPTAINS)
if not suicide then
call Sleep(2)
endif
return
endif
// --- ATTACK FINISHED CONTINUE (IF REQUIRED) ---
call AdvDebugDisplayToPlayer("AI Info: attack wave finished duty, checking if should continue...")
// If attack wave has not fought before reaching goal and then never fought again, it must continue
if goalReached and not combatEnteredBeforeGoal and not combatEntered then
call AdvDebugDisplayToPlayer("AI Info: attack wave never fought at goal nor to reach it, forcing continue...")
set continuePercentage = 100
set continueAttackReducePercentageIfFarAway = false
endif
if not suicide and not CaptainIsEmpty() and GetRandomInt(0, 100) <= continuePercentage then
call AdvDebugDisplayToPlayer("AI Info: attack wave continues to another target!")
set newTargetFound = AdvSuicideWaveLaunch(targetPlayer, 0.0, 0.0, false, true, can_search_units, false, true, true)
if newTargetFound then
// If target is not close to previous one, decrease the continue percentage (if required)
if continueAttackReducePercentageIfFarAway and AdvDistance(previousTargetX, previousTargetY, last_target_x, last_target_y) > continue_far_away_radius then
call AdvDebugDisplayToPlayer("AI Info: reducing percentage of continuing again...")
set continuePercentageNew = continuePercentageNew - continue_attack_reduce_percentage
endif
call AdvSuicideWaveRoutine(targetPlayer, startX, startY, reach_goal_timeout, enter_combat_timeout, clear_goal_timeout, continuePercentageNew)
return
endif
endif
// --- ATTACK FINISHED GO HOME ---
call AdvDebugDisplayToPlayer("AI Info: attack wave does not continue, going back home...")
call SetCaptainHome(ATTACK_CAPTAIN, startX, startY)
call CaptainAttack(startX, startY)
// Wait for the captain to go back home
loop
call Sleep(1)
exitwhen suicide
exitwhen CaptainAtGoal()
endloop
call AdvDebugDisplayToPlayer("AI Info: returned home!")
call ClearCaptainTargets()
call ResetCaptainLocs()
call AdvSetCaptainHome(BOTH_CAPTAINS)
if not suicide then
call Sleep(2)
endif
endfunction
//============================================================================
// ADVANCED SUICIDE ON PLAYER
//============================================================================
function AdvSuicideOnPlayer takes integer seconds, player targetPlayer, real startX, real startY returns nothing
local integer woodPeons
local boolean targetFound = false
call AdvDebugDisplayToPlayer("AI Info: waiting to attack...")
// Make sure the captain are at right spots
call ResetCaptainLocs()
call AdvSetCaptainHome(BOTH_CAPTAINS)
if not suicide then
call Sleep(2)
endif
// Prepare attack for given seconds
if not PrepSuicideOnPlayer(seconds) then
return
endif
call AdvDebugDisplayToPlayer("AI Info: wait is over!")
// If necessary (e.g. undead race), save the wood peons before attack group is prepared
if wood_peons_warriors then
set woodPeons = campaign_wood_peons
set campaign_wood_peons = 0
endif
call AdvDebugDisplayToPlayer("AI Info: forming group...")
loop
exitwhen suicide
call FormGroup(5, true)
exitwhen sleep_seconds <= 0
endloop
call AdvDebugDisplayToPlayer("AI Info: group formed!")
// If necessary (e.g. undead race), reset the wood peons after attack group is ready
if wood_peons_warriors then
set campaign_wood_peons = woodPeons
endif
// Travel to start position
call AdvDebugDisplayToPlayer("AI Info: traveling to start position...")
call SetCaptainHome(ATTACK_CAPTAIN, startX, startY)
call CaptainAttack(startX, startY)
loop
call Sleep(1)
exitwhen suicide
exitwhen CaptainAtGoal()
exitwhen CaptainIsEmpty()
endloop
// Stop here if captain is empty
if CaptainIsEmpty() then
return
endif
call AdvDebugDisplayToPlayer("AI Info: start position reached!")
// Launch the wave against a suitable target
set targetFound = AdvSuicideWaveLaunch(targetPlayer, startX, startY, search_preferred_locations, search_everywhere, can_search_units, prioritize_town_halls, prioritize_nearest, false)
set harass_length = 0
// Start the wave routine if there is a target
if targetFound then
call AdvSuicideWaveRoutine(targetPlayer, startX, startY, reach_goal_timeout, enter_combat_timeout, clear_goal_timeout, continue_attack_percentage)
endif
endfunction
//============================================================================
function AdvSuicideOnPlayerEx takes integer easy, integer med, integer hard, player targetPlayer, real startX, real startY returns nothing
if difficulty == EASY then
call AdvSuicideOnPlayer(easy, targetPlayer, startX, startY)
elseif difficulty == NORMAL then
call AdvSuicideOnPlayer(med, targetPlayer, startX, startY)
else
call AdvSuicideOnPlayer(hard, targetPlayer, startX, startY)
endif
endfunction
KNOWLEDGE REQUIRED
- Advanced knowledge of JASS, with a focus on AI (recommended readings: Creating AI workflow, Intermediate AI concepts)
- Basic knowledge of the Warcraft 3 World Editor
- How to import external files into a map
- Warcraft 3 World Editor
- Notepad++ (highly recommended) or any other text editor
- ADVANCED SUICIDE ON PLAYER
- ADVANCED ATTACK WAVE ROUTINE
- USAGE AND CUSTOMIZATION
- HOW TO IMPORT AND USAGE EXAMPLE
ADVANCED SUICIDE ON PLAYER
This section quickly describes how the new SuicideOnPlayer functions, AdvSuicideOnPlayer, are structured and why they are designed this way. Please don't expect a complete description of each and every line of code, for it's too complex for this tutorial.
The core of the new attack wave routine is the so called AdvSuicideOnPlayer and the associated function AdvSuicideOnPlayerEx, where Adv stands for Advanced. These two functions mimic and reimagine the behaviour of the original SuicideOnPlayer and SuicideOnPlayerEx functions, which you can find in the common.ai file of the game. The two functions are shown below:
JASS:
//============================================================================
// ADVANCED SUICIDE ON PLAYER
//============================================================================
function AdvSuicideOnPlayer takes integer seconds, player targetPlayer, real startX, real startY returns nothing
local integer woodPeons
local boolean targetFound = false
call AdvDebugDisplayToPlayer("AI Info: waiting to attack...")
// Make sure the captain are at right spots
call ResetCaptainLocs()
call AdvSetCaptainHome(BOTH_CAPTAINS)
if not suicide then
call Sleep(2)
endif
// Prepare attack for given seconds
if not PrepSuicideOnPlayer(seconds) then
return
endif
call AdvDebugDisplayToPlayer("AI Info: wait is over!")
// If necessary (e.g. undead race), save the wood peons before attack group is prepared
if wood_peons_warriors then
set woodPeons = campaign_wood_peons
set campaign_wood_peons = 0
endif
call AdvDebugDisplayToPlayer("AI Info: forming group...")
loop
exitwhen suicide
call FormGroup(5, true)
exitwhen sleep_seconds <= 0
endloop
call AdvDebugDisplayToPlayer("AI Info: group formed!")
// If necessary (e.g. undead race), reset the wood peons after attack group is ready
if wood_peons_warriors then
set campaign_wood_peons = woodPeons
endif
// Travel to start position
call AdvDebugDisplayToPlayer("AI Info: traveling to start position...")
call SetCaptainHome(ATTACK_CAPTAIN, startX, startY)
call CaptainAttack(startX, startY)
loop
call Sleep(1)
exitwhen suicide
exitwhen CaptainAtGoal()
exitwhen CaptainIsEmpty()
endloop
// Stop here if captain is empty
if CaptainIsEmpty() then
return
endif
call AdvDebugDisplayToPlayer("AI Info: start position reached!")
// Launch the wave against a suitable target
set targetFound = AdvSuicideWaveLaunch(targetPlayer, startX, startY, search_preferred_locations, search_everywhere, can_search_units, prioritize_town_halls, prioritize_nearest, false)
set harass_length = 0
// Start the wave routine if there is a target
if targetFound then
call AdvSuicideWaveRoutine(targetPlayer, startX, startY, reach_goal_timeout, enter_combat_timeout, clear_goal_timeout, continue_attack_percentage)
endif
endfunction
//============================================================================
function AdvSuicideOnPlayerEx takes integer easy, integer med, integer hard, player targetPlayer, real startX, real startY returns nothing
if difficulty == EASY then
call AdvSuicideOnPlayer(easy, targetPlayer, startX, startY)
elseif difficulty == NORMAL then
call AdvSuicideOnPlayer(med, targetPlayer, startX, startY)
else
call AdvSuicideOnPlayer(hard, targetPlayer, startX, startY)
endif
endfunction
Starting from the bottom, AdvSuicideOnPlayerEx just calls the AdvSuicideOnPlayer with different timings, depending on the difficulty. This is exactly the same of the original function SuicideOnPlayerEx. You can see there are two additional parameters, startX and startY. Those two parameters are the gathering position of the attack wave once it's formed. Basically, this attack wave, once formed in the base as usual, will travel to the position (startX, startY) and launch the attack from there. It will also use that position to compute the nearest target, if required.
Now, AdvSuicideOnPlayer is the main function, whose parameters are basically shared with AdvSuicideOnPlayerEx. However, the function itself is quite more complex, lets see it in the details:
Now, AdvSuicideOnPlayer is the main function, whose parameters are basically shared with AdvSuicideOnPlayerEx. However, the function itself is quite more complex, lets see it in the details:
- It prepares the attack as usual, then it checks if the peons are possible warriors for the attack wave (a configurable property) and, if not, it will avoid that strange behaviour of default AI players' peons stop chopping while the attack wave is gathering
- It moves the attack wave to the start position and wait till it's reached or the attack wave is destroyed
- If the attack wave is not destroyed, it launches the attack wave against a suitable target and then enables the attack wave routine, which is a blocking function (this allows the designer to write attack loops exactly the same way the default routines do)
What about the target assignment and the main attack wave routine? We'll discuss them in the next section.
ADVANCED ATTACK WAVE ROUTINE
This section quickly describes how the new attack wave routine is structured, with a brief explanation of the two main functions composing it: AdvSuicideWaveLaunch and AdvSuicideWaveRoutine. Please don't expect a complete description of each and every line of code, for it's too complex for this tutorial.
The SuicideOnPlayer functions are just the "interface" the designer calls when developing its AI scripts, but there quite some stuff going underneath.
By looking at AdvSuicideOnPlayer function in previous section, it's possible to see two functions called at the end, respectively AdvSuicideWaveLaunch, to block execution until a target is chosen, and AdvSuicideWaveRoutine, to block execution until the attack wave is successful or is defeated. This is a very rough description, because a lot of customization is possible, but we'll discuss customization in another section of this tutorial. Likewise, we will see a lot of references to preferred locations. Such locations are customizable both in their amount, (x, y) positions and radius.
The first function, AdvSuicideWaveLaunch, looks like that:
By looking at AdvSuicideOnPlayer function in previous section, it's possible to see two functions called at the end, respectively AdvSuicideWaveLaunch, to block execution until a target is chosen, and AdvSuicideWaveRoutine, to block execution until the attack wave is successful or is defeated. This is a very rough description, because a lot of customization is possible, but we'll discuss customization in another section of this tutorial. Likewise, we will see a lot of references to preferred locations. Such locations are customizable both in their amount, (x, y) positions and radius.
The first function, AdvSuicideWaveLaunch, looks like that:
JASS:
//============================================================================
function AdvSuicideWaveLaunch takes player targetPlayer, real startX, real startY, boolean searchPreferredLocations, boolean searchEverywhere, boolean canSearchUnits, boolean prioritizeTownHalls, boolean prioritizeNearest, boolean useLastTarget returns boolean
local unit target = null
local integer index = 0
local real targetX
local real targetY
local integer searchTargetCycle = 0
// Set the start coordinates
if useLastTarget then
set startX = last_target_x
set startY = last_target_y
endif
call AdvDebugDisplayToPlayer("AI Info: searching for a target...")
// --- LOOP UNTIL TARGET IS FOUND OR TIMEOUT IS REACHED ---
loop
exitwhen suicide
// --- CACHE PREFERRED LOCATIONS ---
set preferred_locations_count_current = 0
if searchPreferredLocations and preferred_locations_count > 0 then
set preferred_locations_count_current = preferred_locations_count
loop
set preferred_locations_current[index] = preferred_locations[index]
set index = index + 1
exitwhen index >= preferred_locations_count_current
endloop
endif
// --- TOWN HALLS PREFERRED LOCATIONS ---
if searchPreferredLocations and prioritizeTownHalls and preferred_locations_count_current > 0 then
call AdvDebugDisplayToPlayer("AI Info: looking for target town halls at preferred locations...")
if AdvAssignTargetableTownHalls(targetPlayer, true) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable town hall found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest town hall...")
// Nearest town hall
set target = AdvGetNearestTargetableTownHall(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random town hall target...")
// Random town hall
set target = AdvGetRandomTargetableTownHall()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: town hall target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: town hall target found!")
endif
endif
exitwhen target != null
// --- BUILDINGS PREFERRED LOCATIONS ---
if searchPreferredLocations and preferred_locations_count_current > 0 then
call AdvDebugDisplayToPlayer("AI Info: looking for target buildings at preferred locations...")
if AdvAssignTargetableBuildings(targetPlayer, true) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable building found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest building...")
// Nearest building
set target = AdvGetNearestTargetableBuilding(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random building...")
// Random building
set target = AdvGetRandomTargetableBuilding()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: building target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: building target found!")
endif
endif
exitwhen target != null
// --- TOWN HALLS EVERYWHERE ----
if searchEverywhere and prioritizeTownHalls then
call AdvDebugDisplayToPlayer("AI Info: looking for target town halls anywhere on the map...")
if AdvAssignTargetableTownHalls(targetPlayer, false) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable town hall found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest town hall...")
// Nearest town hall
set target = AdvGetNearestTargetableTownHall(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random town hall target...")
// Random town hall
set target = AdvGetRandomTargetableTownHall()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: town hall target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: town hall target found!")
endif
endif
exitwhen target != null
// --- BUILDINGS EVERYWHERE ---
if searchEverywhere then
call AdvDebugDisplayToPlayer("AI Info: looking for target buildings anywhere on the map...")
if AdvAssignTargetableBuildings(targetPlayer, false) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable building found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest building...")
// Nearest building
set target = AdvGetNearestTargetableBuilding(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random building...")
// Random building
set target = AdvGetRandomTargetableBuilding()
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: building target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: building target found!")
endif
endif
endif
exitwhen target != null
// --- UNITS PREFERRED LOCATIONS ---
if canSearchUnits and searchPreferredLocations and preferred_locations_count_current > 0 then
call AdvDebugDisplayToPlayer("AI Info: looking for target units at preferred locations...")
if AdvAssignTargetableUnits(targetPlayer, true) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable unit found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest unit...")
// Nearest unit
set target = AdvGetNearestTargetableUnit(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random unit...")
// Random unit
set target = AdvGetRandomTargetableUnit()
endif
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: unit target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: unit target found!")
endif
endif
exitwhen target != null
// --- UNITS EVERYWHERE ---
if canSearchUnits and searchEverywhere then
call AdvDebugDisplayToPlayer("AI Info: looking for target units anywhere on the map...")
if AdvAssignTargetableUnits(targetPlayer, false) then
call AdvDebugDisplayToPlayer("AI Info: at least one targetable unit found!")
if prioritizeNearest then
call AdvDebugDisplayToPlayer("AI Info: looking for the nearest unit...")
// Nearest unit
set target = AdvGetNearestTargetableUnit(startX, startY)
else
call AdvDebugDisplayToPlayer("AI Info: looking for a random unit...")
// Random unit
set target = AdvGetRandomTargetableUnit()
endif
if target == null then
call AdvDebugDisplayToPlayer("AI Info: unit target not found!")
else
call AdvDebugDisplayToPlayer("AI Info: unit target found!")
endif
endif
endif
exitwhen target != null
call Sleep(5)
set searchTargetCycle = searchTargetCycle + 1
// When using the last target as start point, we are continuing the attack, so cycle less times (a negative value means to cycle forever)
if search_target_max_cycles_last_target > -1 then
exitwhen useLastTarget and searchTargetCycle > search_target_max_cycles_last_target
endif
// Cycles timeout (a negative value means to cycle forever)
if search_target_max_cycles_default > -1 then
exitwhen searchTargetCycle > search_target_max_cycles_default
endif
call AdvDebugDisplayToPlayer("AI Info: no target found, trying again...")
endloop
// --- LAUNCH ATTACK IF THERE IS A TARGET---
if target != null then
call AdvDebugDisplayToPlayer("AI Info: assigning target coordinates and launching attack!")
set targetX = GetUnitX(target)
set targetY = GetUnitY(target)
// Send the captain to attack and store last targets
set last_target_x = targetX
set last_target_y = targetY
call SetCaptainHome(ATTACK_CAPTAIN, targetX, targetY)
call CaptainAttack(targetX, targetY)
if not suicide then
call Sleep(5)
endif
// Target found
return true
endif
call AdvDebugDisplayToPlayer("AI Info: no target found, done trying!")
// No target found
return false
endfunction
This function is very straightforward in its design. Basically, it does the following, in order:
- It searches for target unit up until a certain amount of times (possibly indefinitely) or when suicide flag is true
- It launches attack to location of chosen target unit, saving last target location to global variables
While the second part is quite easy to understand, the first one is composed of multiple steps in a loop, in a specific order. Please note that loop ends whenever a target is found or when the maximum amount (configurable) of possible iterations of the loop itself are reached:
- If there are preferred locations, cache them to avoid any kind of multithreaded trouble (AI functions can and usually do run on multiple threads).
- If there are preferred locations, preferred locations are search target and town halls targets are to prioritize, search for town halls at preferred locations. Choose the nearest one among all of those at any preferred location if required, or a random one (this automatically increase the chance of attacking a preferred location with more town halls).
- If there are preferred locations and preferred locations are search target, search for buildings at preferred locations. Choose the nearest one among all of those at any preferred location if required, or a random one (this automatically increase the chance of attacking a preferred location with more buildings).
- If there are no proferred locations or preferred locations are not a search target, but town halls targets are to prioritize and it's possible to search everywhere on the map, search for any town hall. Choose the nearest one if required, or a random one.
- If there are no proferred locations or preferred locations are not a search target, but it's possible to search everywhere on the map, search for any building. Choose the nearest one if required, or a random one.
- If there are preferred locations, preferred locations are search target and any units target is allowed, search for units at preferred locations. Choose the nearest one among all of those at any preferred location if required, or a random one (this automatically increase the chance of attacking a preferred location with more units).
- If there are no preferred locations or preferred locations are not a search target, but any units target is allowed and it's possible to search everywhere on the map, search for any unit. Choose the nearest one if required, or a random one.
All the parameters to rule this search system are cached as arguments for the function. When the function is called, global values are passed in function AdvSuicideOnPlayer as parameters so they are immutable through all the execution of the function. This way, when an attack wave is started (when the attack party reaches the start location) with certain global parameters, it retains the values of these parameters until the wave terminates (success or defeat), but the designer is free to change the values in the meantime. Please note that this doesn't hold true while the attack wave is gathering. In addition, some values are not cached, using directly the global variables, allowing the designer to change some specific behaviours of an ongoing wave. Check out these values by reading the source code.
The second function AdvSuicideWaveRoutine is the actual core routine of the attack wave and it decides whether or not the wave should continue or stop (considering the attack a success or a defeat). The function is as follows:
The second function AdvSuicideWaveRoutine is the actual core routine of the attack wave and it decides whether or not the wave should continue or stop (considering the attack a success or a defeat). The function is as follows:
JASS:
//============================================================================
function AdvSuicideWaveRoutine takes player targetPlayer, real startX, real startY, boolean reachGoalTimeout, boolean enterCombatTimeout, boolean clearGoalTimeout, integer continuePercentage returns nothing
local integer continuePercentageNew = continuePercentage
local real previousTargetX = last_target_x
local real previousTargetY = last_target_y
local integer state = 0
local boolean combatEnteredBeforeGoal = false
local boolean goalReached = false
local boolean combatEntered = false
local boolean goalCleared = false
local boolean continueAttackReducePercentageIfFarAway = continue_attack_reduce_percentage_if_far_away
local boolean newTargetFound = false
// Reset all timeout seconds
set wave_reach_goal_sleep_seconds = 0
set wave_enter_combat_sleep_seconds = 0
set wave_clear_goal_sleep_seconds = 0
call AdvDebugDisplayToPlayer("AI Info: waiting for attack wave to reach goal...")
// --- WAIT TO REACH GOAL (OR DIE) ---
set state = AdvSuicideWaveRoutineReachGoal(reachGoalTimeout)
// Timeout
if state == 0 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal reached timeout!")
set goalReached = false
set combatEnteredBeforeGoal = false
// Dead
elseif state == -1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead!")
set goalReached = false
set combatEnteredBeforeGoal = false
// Goal reached after figthing
elseif state == 1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal reached after figthing!")
set goalReached = true
set combatEnteredBeforeGoal = true
// Goal reached before figthing
elseif state == 2 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal reached before figthing!")
set goalReached = true
set combatEnteredBeforeGoal = false
endif
call AdvDebugDisplayToPlayer("AI Info: waiting for attack wave to enter combat...")
// --- WAIT TO ENTER COMBAT (OR DIE) ---
set state = AdvSuicideWaveRoutineEnterCombat(enterCombatTimeout)
// Timeout
if state == 0 then
call AdvDebugDisplayToPlayer("AI Info: attack wave enter combat reached timeout!")
set combatEntered = false
// Dead
elseif state == -1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead!")
set combatEntered = true
// Combat entered
elseif state == 1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave combat entered!")
set combatEntered = true
endif
call AdvDebugDisplayToPlayer("AI Info: waiting for attack wave to clear goal...")
// --- WAIT TO CLEAR GOAL (OR DIE) ---
set state = AdvSuicideWaveRoutineClearGoal(clearGoalTimeout)
// Timeout
if state == 0 then
call AdvDebugDisplayToPlayer("AI Info: attack wave clear goal reached timeout!")
set combatEntered = false
// Dead
elseif state == -1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead!")
set combatEntered = true
// Goal cleared
elseif state == 1 then
call AdvDebugDisplayToPlayer("AI Info: attack wave goal cleared!")
set goalCleared = true
endif
// --- GROUP DEAD ---
if CaptainIsEmpty() then
call AdvDebugDisplayToPlayer("AI Info: attack wave dead, going back home...")
call SetCaptainHome(ATTACK_CAPTAIN, startX, startY)
call CaptainAttack(startX, startY)
if not suicide then
call Sleep(1)
endif
call TeleportCaptain(startX, startY)
// Wait for the captain to go back home
loop
call Sleep(1)
exitwhen suicide
exitwhen CaptainAtGoal()
endloop
call AdvDebugDisplayToPlayer("AI Info: returned home!")
call ClearCaptainTargets()
call ResetCaptainLocs()
call AdvSetCaptainHome(BOTH_CAPTAINS)
if not suicide then
call Sleep(2)
endif
return
endif
// --- ATTACK FINISHED CONTINUE (IF REQUIRED) ---
call AdvDebugDisplayToPlayer("AI Info: attack wave finished duty, checking if should continue...")
// If attack wave has not fought before reaching goal and then never fought again, it must continue
if goalReached and not combatEnteredBeforeGoal and not combatEntered then
call AdvDebugDisplayToPlayer("AI Info: attack wave never fought at goal nor to reach it, forcing continue...")
set continuePercentage = 100
set continueAttackReducePercentageIfFarAway = false
endif
if not suicide and not CaptainIsEmpty() and GetRandomInt(0, 100) <= continuePercentage then
call AdvDebugDisplayToPlayer("AI Info: attack wave continues to another target!")
set newTargetFound = AdvSuicideWaveLaunch(targetPlayer, 0.0, 0.0, false, true, can_search_units, false, true, true)
if newTargetFound then
// If target is not close to previous one, decrease the continue percentage (if required)
if continueAttackReducePercentageIfFarAway and AdvDistance(previousTargetX, previousTargetY, last_target_x, last_target_y) > continue_far_away_radius then
call AdvDebugDisplayToPlayer("AI Info: reducing percentage of continuing again...")
set continuePercentageNew = continuePercentageNew - continue_attack_reduce_percentage
endif
call AdvSuicideWaveRoutine(targetPlayer, startX, startY, reach_goal_timeout, enter_combat_timeout, clear_goal_timeout, continuePercentageNew)
return
endif
endif
// --- ATTACK FINISHED GO HOME ---
call AdvDebugDisplayToPlayer("AI Info: attack wave does not continue, going back home...")
call SetCaptainHome(ATTACK_CAPTAIN, startX, startY)
call CaptainAttack(startX, startY)
// Wait for the captain to go back home
loop
call Sleep(1)
exitwhen suicide
exitwhen CaptainAtGoal()
endloop
call AdvDebugDisplayToPlayer("AI Info: returned home!")
call ClearCaptainTargets()
call ResetCaptainLocs()
call AdvSetCaptainHome(BOTH_CAPTAINS)
if not suicide then
call Sleep(2)
endif
endfunction
This function executes a set of checks contained in multiple auxiliary functions. Each one of those consists in a loop, with varying exiting conditions.
When a certain function exits, the flow returns to the AdvSuicideWaveRoutine function, which proceeds with following stages. While the description of each one of the auxiliary functions is outside of the scope of this tutorial, the general flow is as follows:
When a certain function exits, the flow returns to the AdvSuicideWaveRoutine function, which proceeds with following stages. While the description of each one of the auxiliary functions is outside of the scope of this tutorial, the general flow is as follows:
- Wait for the attack wave to reach goal or die trying. A timeout can be used and it's configurable as part of each script's possible customization.
- Wait for the attack wave to enter combat or die trying. A timeout can be used and it's configurable as part of each script's possible customization.
- Wait for the attack wave to clear the goal or die trying. A timeout can be used and it's configurable as part of each script's possible customization.
- Check if the attack wave party is dead. If it is dead, the wave is considered defeated and eventual following waves can now be prepared, exiting the wave routine.
- If the attack wave party is not dead, the wave is considered successful and further decision should be taken.
- If the wave is successful but the group never entered combat, it means enemies have been avoided or the pathfinding algorithm bugged itself. In this case, the attack wave needs to continue to another target. Such target is chosen as closest to the last target and it retains some of original configuration (e.g. if to prioritize town halls, etc.).
- If the attack wave is successful and the group entered combat, it means the attack wave fought to defeat the enemies. In this case, the attack wave select a new closest target with random chance, using a cached configurable parameter. This new attack wave retains some of original configuration (e.g. if to prioritize town halls, etc.) but uses last target as starting point to compute what is considered closest. If the closest target is above a configurable distance, the system can reduce the chance of that happening again in the future. Both the reduction amount and if to reduce are configurable.
- If no new target is available or the attack wave should not continue, the attack party goes home, exiting the wave routine.
- If a new target is available and the attack wave should continue, a new attack wave with such target is launched, calling recursively a new wave routine.
It is possible to note a funny behaviour, which is what I was referring to when I was talking about the pathfinding algorithm bugging itself. Basically, the native to set a target for the attack party could not work properly, considering the goal reached just after a few seconds, no matter where the attack wave party actually is on the map. This is probably why the natives used by Blizzard to choose a target of a certain player, in function CommonSuicideOnPlayer, are called inside a loop.
In the new system, there is a bit of additional delay if the pathfiding algorithm bugs out. Specifically, you will be able to see the attack party start moving towards its target and then stopping. It could do so multiple times, before actually deciding to attack. There is no way to fix this, because I would need to fix the natives, but you should not be worried. Beside the small delay, the attack party will for sure attack the destination as designed thanks to the sequence of checks described above.
In the new system, there is a bit of additional delay if the pathfiding algorithm bugs out. Specifically, you will be able to see the attack party start moving towards its target and then stopping. It could do so multiple times, before actually deciding to attack. There is no way to fix this, because I would need to fix the natives, but you should not be worried. Beside the small delay, the attack party will for sure attack the destination as designed thanks to the sequence of checks described above.
USAGE AND CUSTOMIZATION
This section describes how to use and customize the new routines in the common AI 2.0, by explaining each configuration function and the relative effect on the overall AI player's behaviour.
To use the advanced routines, it's necessary first to add them into your game. To do so, see the next and final section of this tutorial, or the first page of the system. To use them in your AI script, you just need to change each SuicideOnPlayerEx function with AdvSuicideOnPlayerEx function.
It's possible to mix the two as you see fit, using default routines for certain attacks and the advanced routines for others, seamlessy.
The new function AdvSuicideOnPlayerEx has two additional parameters, specifically the (x, y) of the start position. The start position is a specific position the attack wave must reach before actually starting the attack.
One of the features of the advanced routines are preferred locations, areas the AI can prioritize when choosing targets. It's a perfect way to define multiple target areas for the AI to choose from, allowing the game designer to inject a much more configurable attack behaviour into the AI scripts. To add preferred locations, check out the following functions:
It's possible to mix the two as you see fit, using default routines for certain attacks and the advanced routines for others, seamlessy.
The new function AdvSuicideOnPlayerEx has two additional parameters, specifically the (x, y) of the start position. The start position is a specific position the attack wave must reach before actually starting the attack.
One of the features of the advanced routines are preferred locations, areas the AI can prioritize when choosing targets. It's a perfect way to define multiple target areas for the AI to choose from, allowing the game designer to inject a much more configurable attack behaviour into the AI scripts. To add preferred locations, check out the following functions:
JASS:
//============================================================================
function AdvAddPreferredLocation takes real x, real y returns integer
set preferred_locations[preferred_locations_count] = Location(x, y)
set preferred_locations_count = preferred_locations_count + 1
return preferred_locations_count
endfunction
function AdvSetPreferredLocationXY takes integer index, real x, real y returns nothing
if index < preferred_locations_count then
set preferred_locations[index] = Location(x, y)
endif
endfunction
It's possible to use the first function to both add a new preferred location and to get the amount of preferred locations, which can be used to compute the index, parameter of the second function. The second function is used to change an existing preferred location.
One of the stronger advantages of the new routines is they are easy to customize. Indeed, the customization allows the designer to make the AI follows specific behaviours, like, but not limited to, the following:
One of the stronger advantages of the new routines is they are easy to customize. Indeed, the customization allows the designer to make the AI follows specific behaviours, like, but not limited to, the following:
- Attack at certain locations or not
- Attack buildings (possibly prioritizing town halls) or also other units
- The time (in seconds) to wait for each stage (more info about that in previous sections) of the attack wave to complete
- If to continue attacking (with a percentage) and what to consider far away enough to reduce such percentage
- Easier to use debug prints
This small list does not make the configuration, or settings, functions justice. For a complete list, see what follows. You are free to experiment with them as you see fit:
JASS:
//============================================================================
// ADVANCED SETTINGS
//============================================================================
function AdvSetDebug takes player debugPlayer, boolean flag returns nothing
set debug_player = debugPlayer
set debug = flag
endfunction
//============================================================================
function AdvSetWaveReachGoalTimeoutSeconds takes integer seconds returns nothing
set wave_reach_goal_timeout_seconds = seconds
endfunction
function AdvSetWaveEnterCombatTimeoutSeconds takes integer seconds returns nothing
set wave_enter_combat_timeout_seconds = seconds
endfunction
function AdvSetWaveClearGoalTimeoutSeconds takes integer seconds returns nothing
set wave_clear_goal_timeout_seconds = seconds
endfunction
//============================================================================
function AdvSetContinueFarAwayRadius takes real radius returns nothing
set continue_far_away_radius = radius
endfunction
function AdvSetPreferredLocationsRadius takes real radius returns nothing
set preferred_locations_radius = radius
endfunction
//============================================================================
function AdvSetWoodPeonsWarriors takes boolean flag returns nothing
set wood_peons_warriors = flag
endfunction
//============================================================================
function AdvSetSearchPreferredLocations takes boolean flag returns nothing
set search_preferred_locations = flag
endfunction
function AdvSetSearchEverywhere takes boolean flag returns nothing
set search_everywhere = flag
endfunction
function AdvSetCanSearchUnit takes boolean flag returns nothing
set can_search_units = flag
endfunction
function AdvSetCanSearchInvulnerable takes boolean flag returns nothing
set can_search_invulnerable = flag
endfunction
//============================================================================
function AdvSetPrioritizeTownHalls takes boolean flag returns nothing
set prioritize_town_halls = flag
endfunction
function AdvSetPrioritizeNearest takes boolean flag returns nothing
set prioritize_nearest = flag
endfunction
function AdvSetSearchTargetMaxCyclesDefault takes integer value returns nothing
set search_target_max_cycles_default = value
endfunction
function AdvSetSearchTargetMaxCyclesLastTarget takes integer value returns nothing
set search_target_max_cycles_last_target = value
endfunction
//============================================================================
function AdvSetUseReachGoalTimeout takes boolean flag returns nothing
set reach_goal_timeout = flag
endfunction
function AdvSetUseEnterCombatTimeout takes boolean flag returns nothing
set reach_goal_timeout = flag
endfunction
function AdvSetUseClearGoalTimeout takes boolean flag returns nothing
set enter_combat_timeout = flag
endfunction
//============================================================================
function AdvSetContinueAttackPercentage takes integer percentage returns nothing
set continue_attack_percentage = percentage
endfunction
function AdvSetContinueAttackReducePercentageIfFarAway takes boolean flag returns nothing
set continue_attack_reduce_percentage_if_far_away = flag
endfunction
function AdvSetContinueAttackReducePercentage takes integer percentage returns nothing
set continue_attack_reduce_percentage = percentage
endfunction
//============================================================================
function AdvSetAttackWaveGatherReturnXY takes real x, real y returns nothing
set attack_wave_gather_return_x = x
set attack_wave_gather_return_y = y
set attack_wave_gather_return_set = true
endfunction
function AdvSetDefenseCaptainHomeXY takes real x, real y returns nothing
set defense_captain_home_x = x
set defense_captain_home_y = y
set defense_captain_home_set = true
endfunction
Now, lets explain what each one of these functions does:
- AdvSetDebug defines if to show debug info and to which player.
- AdvSetWaveReachGoalTimeoutSeconds defines the maximum allowed time (in seconds) to reach the goal for the attack wave party
- AdvSetWaveEnterCombatTimeoutSecond defines the maximum allowed time (in seconds) to enter combat after having reached the goal
- AdvSetWaveClearGoalTimeoutSeconds defines the maximum allowed time (in seconds) to clear the goal area from enemies after having entered combat
- AdvSetContinueFarAwayRadius defines the radius outside which a new target (during wave continuation) is considered far away, so percentage of continuing the attack wave can be reduced
- AdvSetPreferredLocationsRadius defines the radius of preferred locations where to search for targets
- AdvSetWoodPeonsWarriors defines is the peons are considered warriors. This should be true for undead AI players, for example
- AdvSetSearchPreferredLocations defines whether or not to search at defined preferred locations for targets
- AdvSetSearchEverywhere defines whether or not to search everywhere for targets
- AdvSetCanSearchUnit defines whether or not regular units (not building) can be used as targets
- AdvSetCanSearchInvulnerable defines whether or not invulnerable units can be used as targets
- AdvSetPrioritizeTownHalls defines whether or not town halls should be prioritized when searching for targets
- AdvSetPrioritizeNearest defines whether or not targets are to be chosen at random or by looking for the nearest one to the attack wave starting position
- AdvSetSearchTargetMaxCyclesDefault defines the amount of target search cycles before exiting the routine, if no target is found. A negative value make the search go on forever, until a target is found
- AdvSetSearchTargetMaxCyclesLastTarget defines the amount of target search cycles before exiting the routine, if no target is found. A negative value make the search go on forever, until a target is found. This is the same as before, but it's used when the attack wave is continuing its attack
- AdvSetUseReachGoalTimeout defines whether or not the reach goal timeout defined above should be used or not, waiting possibly indefinitely for the wave to reach the goal
- AdvSetUseEnterCombatTimeout defines whether or not the enter combat timeout defined above should be used or not, waiting possibly indefinitely for the wave to enter combat
- AdvSetUseClearGoalTimeout defines whether or not the clear goal timeout defined above should be used or not, waiting possibly indefinitely for the wave to clear the goal
- AdvSetContinueAttackPercentage defines the base percentage of continuing an attack, after the attack wave ended with a success. This is the value any new attack wave starts with
- AdvSetContinueAttackReducePercentageIfFarAway defines whether or not to reduce percentage if the attack continuation chosen target is far away, according to the radius defined above
- AdvSetContinueAttackReducePercentage defines the reduction amount of the continue attack percentage when the new target is far away (it is additive for multiple waves)
- AdvSetAttackWaveGatherReturnXY defines the location where the attack wave gathers or returns after a successful attack
- AdvSetDefenseCaptainHomeXY defines where the defenders group will stay while idle
It should be possible to call these functions anywhere at any time without breaking anything. I usually call them in two ways:
- Before a certain attack wave (for example, if I want to attack nearest targets or town halls or preferred locations and so forth), resetting them afterwards
- In the main function of the AI script, to define global properties that are default for all attack waves (or should be general for the AI behaviour, like the defense captain home)
All variables configurable through these functions have also default values, as follows:
JASS:
//--------------------------------------------------------------------
player debug_player
boolean debug = false
boolean suicide = false
integer wave_reach_goal_timeout_seconds = 300
integer wave_enter_combat_timeout_seconds = 3
integer wave_clear_goal_timeout_seconds = 60
real continue_far_away_radius = 960.0
real preferred_locations_radius = 640.0
integer wave_reach_goal_sleep_seconds
integer wave_enter_combat_sleep_seconds
integer wave_clear_goal_sleep_seconds
real defense_captain_home_x
real defense_captain_home_y
boolean defense_captain_home_set = false
real attack_wave_gather_return_x
real attack_wave_gather_return_y
boolean attack_wave_gather_return_set = false
location array preferred_locations
integer preferred_locations_count = 0
location array preferred_locations_current
integer preferred_locations_count_current = 0
boolean wood_peons_warriors = false
boolean search_preferred_locations = true
boolean search_everywhere = true
boolean can_search_units = true
boolean can_search_invulnerable = false
boolean prioritize_town_halls = true
boolean prioritize_nearest = true
boolean reach_goal_timeout = true
boolean enter_combat_timeout = true
boolean clear_goal_timeout = true
integer continue_attack_percentage = 100
boolean continue_attack_reduce_percentage_if_far_away = true
integer continue_attack_reduce_percentage = 25
unit array targetable_town_halls
integer targetable_town_halls_count = 0
unit array targetable_buildings
integer targetable_buildings_count = 0
unit array targetable_units
integer targetable_units_count = 0
real last_target_x
real last_target_y
integer search_target_max_cycles_default = 100
integer search_target_max_cycles_last_target = 10
HOW TO IMPORT AND USAGE EXAMPLE
This section describes the steps required to import common AI 2.0 to your map and provides an useful usage example with real, fully functional, AI scripts.
To import common_v2.ai file inside your map, you need to overwrite the default common.ai file the game provides. To do so, you just need to do what follows:
- Download the common_v2.ai file attached to this system (or just make your own .ai using the script in the code section, it's just a text file!), which contains all the advanced routines
- Import this file into your map using the Asset Manager
- Change the file path to scripts\common.ai
By doing so, each .ai script will have the new routines included, so you can use the new routines in all AI scripts for that specific map.
The scripts below provide some useful usage example, using the Kul Tiras AI of Warcraft 3 Re-Reforged: Exodus of the Horde, chapter three, and one of the Underworld Minions AI of Warcraft 3 Re-Reforged: Exodus of the Horde, chapter five. For additional AI scripts (both with and without common_v2.ai), check out this thread.
The scripts below provide some useful usage example, using the Kul Tiras AI of Warcraft 3 Re-Reforged: Exodus of the Horde, chapter three, and one of the Underworld Minions AI of Warcraft 3 Re-Reforged: Exodus of the Horde, chapter five. For additional AI scripts (both with and without common_v2.ai), check out this thread.
JASS:
//============================================================================
// RR Prologue 03 -- Kul Tiras Dark Green Human -- AI Script
// by InsaneMonster (Luca Pasqualini)
// Requires common_v2.ai
//============================================================================
// GLOBALS
//------------------------------------------------
globals
// Tech tree
constant integer KELEN = 'Haah'
constant integer HYDROMANCER = 'h000'
constant integer UPG_HYDROMANCY = 'R000'
// Players
player User = PlayerEx(1)
player Trolls = PlayerEx(16)
// Behaviour
constant real ATTACK_WAVE_START_X = -3800.0
constant real ATTACK_WAVE_START_Y = 3500.0
boolean AttackUser = false
boolean AttackTrolls = false
// Commands
integer COMMAND_ATTACK_TROLLS = 2
integer DATA_ATTACK_TROLLS_ENABLE = 1
integer DATA_ATTACK_TROLLS_DISABLE = 0
integer COMMAND_ATTACK_USER = 1
integer DATA_ATTACK_USER_ENABLE = 1
integer DATA_ATTACK_USER_DISABLE = 0
endglobals
// BUILD ORDER
//------------------------------------------------
function SetBuildOrder takes nothing returns nothing
call SetBuildUnitEx(1, 1, 1, PEASANT)
call SetBuildUnitEx(1, 1, 1, TOWN_HALL)
call SetBuildUnitEx(8, 8, 8, PEASANT)
call SetBuildUnitEx(1, 1, 1, HUMAN_ALTAR)
call SetBuildUnitEx(2, 2, 2, HOUSE)
call SetBuildUnitEx(1, 1, 1, BARRACKS)
call SetBuildUnitEx(4, 4, 4, HOUSE)
call SetBuildUnitEx(0, 1, 1, LUMBER_MILL)
call SetBuildUnitEx(0, 1, 1, BLACKSMITH)
call SetBuildUnitEx(0, 2, 3, HOUSE)
call SetBuildUnitEx(0, 0, 1, KEEP)
call SetBuildUnitEx(0, 0, 1, ARCANE_SANCTUM)
call SetBuildUnitEx(0, 0, 2, HOUSE)
call SetBuildUnitEx(0, 0, 1, ARCANE_VAULT)
endfunction
// DEFENDERS
//------------------------------------------------
function SetDefenders takes nothing returns nothing
call CampaignDefenderEx(1, 2, 2, FOOTMAN)
call CampaignDefenderEx(1, 1, 2, RIFLEMAN)
call CampaignDefenderEx(0, 0, 1, HYDROMANCER)
call CampaignDefenderEx(1, 1, 1, KELEN)
endfunction
// RESEARCH UPGRADES
//------------------------------------------------
function ResearchUpgrades takes nothing returns nothing
call Sleep(M5)
call SetBuildUpgrEx(1, 1, 1, UPG_DEFEND)
call Sleep(M4)
call SetBuildUpgrEx(1, 1, 1, UPG_MELEE)
call Sleep(M4)
call SetBuildUpgrEx(1, 1, 1, UPG_ARMOR)
call SetBuildUpgrEx(0, 1, 1, UPG_HYDROMANCY)
call Sleep(M4)
call SetBuildUpgrEx(1, 1, 1, UPG_RANGED)
call Sleep(M4)
call SetBuildUpgrEx(1, 1, 1, UPG_LEATHER)
call SetBuildUpgrEx(0, 1, 1, UPG_GUN_RANGE)
call Sleep(M3)
call SetBuildUpgrEx(0, 0, 1, UPG_MASONRY)
call Sleep(M4)
call SetBuildUpgrEx(1, 2, 2, UPG_MELEE)
call Sleep(M5)
call SetBuildUpgrEx(1, 1, 2, UPG_ARMOR)
call Sleep(M4)
call SetBuildUpgrEx(1, 2, 2, UPG_RANGED)
call Sleep(M5)
call SetBuildUpgrEx(1, 1, 2, UPG_LEATHER)
endfunction
// ATTACK LOOP
//------------------------------------------------
function AttackWavesAttackUser takes nothing returns nothing
local boolean AttackWaveAttackUser = AttackUser
local boolean AttackWaveAttackTrolls = AttackTrolls
call AdvDebugDisplayToPlayer("AI Info: attacking User...")
call AdvSetPrioritizeTownHalls(false)
call AdvSetContinueAttackReducePercentage(10)
loop
// *** WAVE 1 ***
call AdvSetPrioritizeTownHalls(true)
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call AdvSuicideOnPlayerEx(M4, M3, M2, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 2 ***
call AdvSetPrioritizeTownHalls(true)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 2, FOOTMAN)
call CampaignAttackerEx(1, 2, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 2, HYDROMANCER)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 3 ***
call AdvSetPrioritizeTownHalls(false)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, FOOTMAN)
call CampaignAttackerEx(0, 1, 1, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, HYDROMANCER)
call AdvSuicideOnPlayerEx(M4, M3, M2, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
loop
// *** WAVE 4 ***
call AdvSetPrioritizeTownHalls(true)
call InitAssaultGroup()
call CampaignAttackerEx(3, 4, 5, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 5 ***
call AdvSetPrioritizeTownHalls(false)
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(2, 2, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 2, HYDROMANCER)
call AdvSuicideOnPlayerEx(M4, M3, M2, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 6 ***
call AdvSetPrioritizeTownHalls(true)
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, HYDROMANCER)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
endloop
// Make sure to leave both loops
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
endloop
call AdvDebugDisplayToPlayer("AI Info: finished attacking User!")
endfunction
function AttackWavesAttackTrolls takes nothing returns nothing
local boolean AttackWaveAttackUser = AttackUser
local boolean AttackWaveAttackTrolls = AttackTrolls
call AdvDebugDisplayToPlayer("AI Info: attacking Trolls...")
call AdvSetPrioritizeTownHalls(false)
call AdvSetContinueAttackReducePercentageIfFarAway(false)
loop
// *** WAVE 1 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call AdvSuicideOnPlayerEx(M4, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 2 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 2, FOOTMAN)
call CampaignAttackerEx(1, 2, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 2, HYDROMANCER)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 3 ***)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, FOOTMAN)
call CampaignAttackerEx(0, 1, 1, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, HYDROMANCER)
call AdvSuicideOnPlayerEx(M4, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
loop
// *** WAVE 4 ***
call InitAssaultGroup()
call CampaignAttackerEx(3, 4, 5, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 5 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(2, 2, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 2, HYDROMANCER)
call AdvSuicideOnPlayerEx(M4, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 6 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, HYDROMANCER)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
endloop
// Make sure to leave both loops
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
endloop
call AdvDebugDisplayToPlayer("AI Info: finished attacking Trolls!")
endfunction
function AttackWavesAttackAll takes nothing returns nothing
local boolean AttackWaveAttackUser = AttackUser
local boolean AttackWaveAttackTrolls = AttackTrolls
call AdvDebugDisplayToPlayer("AI Info: attacking Trolls and User...")
loop
// *** WAVE 1 ***
call AdvSetPrioritizeTownHalls(false)
call AdvSetContinueAttackReducePercentageIfFarAway(false)
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call AdvSuicideOnPlayerEx(M4, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 2 ***
call AdvSetPrioritizeTownHalls(true)
call AdvSetContinueAttackReducePercentageIfFarAway(true)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 2, FOOTMAN)
call CampaignAttackerEx(1, 2, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 2, HYDROMANCER)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 3 ***
call AdvSetPrioritizeTownHalls(false)
call AdvSetContinueAttackReducePercentageIfFarAway(false)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, FOOTMAN)
call CampaignAttackerEx(0, 1, 1, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, HYDROMANCER)
call AdvSuicideOnPlayerEx(M4, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
loop
// *** WAVE 4 ***
call AdvSetPrioritizeTownHalls(true)
call AdvSetContinueAttackReducePercentageIfFarAway(true)
call InitAssaultGroup()
call CampaignAttackerEx(3, 4, 5, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 5 ***
call AdvSetPrioritizeTownHalls(false)
call AdvSetContinueAttackReducePercentageIfFarAway(false)
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(2, 2, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 2, HYDROMANCER)
call AdvSuicideOnPlayerEx(M4, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
// *** WAVE 6 ***
call AdvSetPrioritizeTownHalls(true)
call AdvSetContinueAttackReducePercentageIfFarAway(true)
call InitAssaultGroup()
call CampaignAttackerEx(2, 3, 3, FOOTMAN)
call CampaignAttackerEx(1, 1, 2, RIFLEMAN)
call CampaignAttackerEx(1, 1, 1, HYDROMANCER)
call CampaignAttackerEx(1, 1, 1, KELEN)
call AdvSuicideOnPlayerEx(M5, M4, M3, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
endloop
// Make sure to leave both loops
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser
endloop
call AdvDebugDisplayToPlayer("AI Info: finished attacking Trolls and User!")
endfunction
function AttackLoop takes nothing returns nothing
loop
// Attack the user and/or the trolls depending on the condition
if AttackUser and AttackTrolls then
call AttackWavesAttackAll()
else
if AttackUser then
call AttackWavesAttackUser()
endif
if AttackTrolls then
call AttackWavesAttackTrolls()
endif
endif
// Wait a little time before looping again
call Sleep(10)
endloop
endfunction
// COMMAND LOOP
//------------------------------------------------
function CommandFetch takes nothing returns nothing
local integer Command
local integer Data
// Check if there is any command waiting
loop
exitwhen CommandsWaiting() <= 0
// Get command and data
set Command = GetLastCommand()
set Data = GetLastData()
call PopLastCommand()
// Update attack trolls behaviour command (to start/stop attack against the trolls)
if Command == COMMAND_ATTACK_TROLLS then
if Data == DATA_ATTACK_TROLLS_ENABLE then
set AttackTrolls = true
elseif Data == DATA_ATTACK_TROLLS_DISABLE then
set AttackTrolls = false
else
call AdvDebugDisplayToPlayer("AI Warning: Invalid attack trolls command data received!")
endif
// Update attack user behaviour command (to start/stop attack against the user)
elseif Command == COMMAND_ATTACK_USER then
if Data == DATA_ATTACK_USER_ENABLE then
set AttackUser = true
elseif Data == DATA_ATTACK_USER_DISABLE then
set AttackUser = false
else
call AdvDebugDisplayToPlayer("AI Warning: Invalid attack user command data received!")
endif
else
call AdvDebugDisplayToPlayer("AI Warning: Unknown command received!")
endif
endloop
endfunction
function CommandLoop takes nothing returns nothing
// Keep fetching commands while the AI is active
call CommandFetch()
call StaggerSleep(1, 5)
loop
call CommandFetch()
call Sleep(2)
endloop
endfunction
// MAIN
//------------------------------------------------
function main takes nothing returns nothing
call CampaignAI(HOUSE, null)
// Define AI properties
call DoCampaignFarms(false)
call SetReplacements(1, 1, 1)
call GroupTimedLife(true)
call AdvSetSearchPreferredLocations(false)
// Define build order
call SetBuildOrder()
// Start to research upgrades at timed intervals
call StartThread(function ResearchUpgrades)
// Define defenders
call SetDefenders()
// Start command loop for external signals
call StartThread(function CommandLoop)
// Launch attack waves as soon as possible (it depends on the commands received)
call AttackLoop()
endfunction
JASS:
//============================================================================
// RR Prologue 05 -- Underworld Minions Peach "Nagas" -- AI Script
// by InsaneMonster (Luca Pasqualini)
// Requires common_v2.ai
//============================================================================
// GLOBALS
//------------------------------------------------
globals
// Tech tree
constant integer MG_CLIFFRUNNER = 'n009'
constant integer MG_BLOODGILL = 'n00H'
constant integer MG_TIDEWARRIOR = 'n00I'
constant integer MC_TIDERUNNER = 'n00G'
constant integer MC_HUNTSMAN = 'n007'
constant integer MC_NIGHTCRAWLER = 'n008'
// Players
player User = PlayerEx(1)
player Trolls = PlayerEx(16)
// Behaviour
constant real ATTACK_WAVE_START_X = -10000.0
constant real ATTACK_WAVE_START_Y = -4700.0
boolean AttackUser = false
boolean AttackTrolls = false
boolean AttackSuicide = false
// Commands
integer COMMAND_ATTACK_TROLLS = 2
integer DATA_ATTACK_TROLLS_ENABLE = 1
integer DATA_ATTACK_TROLLS_DISABLE = 0
integer COMMAND_ATTACK_USER = 1
integer DATA_ATTACK_USER_ENABLE = 1
integer DATA_ATTACK_USER_DISABLE = 0
integer COMMAND_ATTACK_SUICIDE = 3
integer DATA_ATTACK_SUICIDE_ENABLE = 1
integer DATA_ATTACK_SUICIDE_DISABLE = 0
endglobals
// ATTACK LOOP
//------------------------------------------------
function AttackWavesAttackUser takes nothing returns nothing
local boolean AttackWaveAttackUser = AttackUser
local boolean AttackWaveAttackTrolls = AttackTrolls
local boolean AttackWaveAttackSuicide = AttackSuicide
call AdvDebugDisplayToPlayer("AI Info: attacking User...")
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
loop
// *** WAVE 1 ***
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MC_TIDERUNNER)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 2 ***
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MC_TIDERUNNER)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 1, MG_CLIFFRUNNER)
call CampaignAttackerEx(1, 1, 1, MG_BLOODGILL)
call AdvSuicideOnPlayerEx(M1, M1, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 3 ***
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MG_CLIFFRUNNER)
call CampaignAttackerEx(1, 1, 2, MG_TIDEWARRIOR)
call CampaignAttackerEx(1, 1, 1, MG_BLOODGILL)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
loop
// *** WAVE 4 ***
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MC_TIDERUNNER)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 1, MC_NIGHTCRAWLER)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 5 ***
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MG_TIDEWARRIOR)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 2, MG_BLOODGILL)
call CampaignAttackerEx(1, 1, 1, MC_NIGHTCRAWLER)
call AdvSuicideOnPlayerEx(M1, M1, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 6 ***
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MG_TIDEWARRIOR)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 1, MG_BLOODGILL)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
endloop
// Make sure to leave both loops
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
endloop
call AdvDebugDisplayToPlayer("AI Info: finished attacking User!")
endfunction
function AttackWavesAttackTrolls takes nothing returns nothing
local boolean AttackWaveAttackUser = AttackUser
local boolean AttackWaveAttackTrolls = AttackTrolls
local boolean AttackWaveAttackSuicide = AttackSuicide
call AdvDebugDisplayToPlayer("AI Info: attacking Trolls...")
call AdvSetPrioritizeNearest(true)
call AdvSetSearchPreferredLocations(false)
call AdvSetContinueAttackPercentage(0)
loop
// *** WAVE 1 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, MC_TIDERUNNER)
call CampaignAttackerEx(1, 2, 2, MC_HUNTSMAN)
call AdvSuicideOnPlayerEx(M3, M2, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
loop
// *** WAVE 2 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, MG_CLIFFRUNNER)
call CampaignAttackerEx(1, 2, 2, MG_TIDEWARRIOR)
call AdvSuicideOnPlayerEx(M3, M3, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 3 ***
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, MC_TIDERUNNER)
call CampaignAttackerEx(1, 2, 2, MC_HUNTSMAN)
call AdvSuicideOnPlayerEx(M3, M2, M2, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
endloop
// Make sure to leave both loops
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
endloop
call AdvDebugDisplayToPlayer("AI Info: finished attacking Trolls!")
endfunction
function AttackWavesAttackAll takes nothing returns nothing
local boolean AttackWaveAttackUser = AttackUser
local boolean AttackWaveAttackTrolls = AttackTrolls
local boolean AttackWaveAttackSuicide = AttackSuicide
call AdvDebugDisplayToPlayer("AI Info: attacking Trolls and User...")
loop
// *** WAVE 1 ***
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MC_TIDERUNNER)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 2 ***
call AdvSetPrioritizeNearest(true)
call AdvSetSearchPreferredLocations(false)
call AdvSetContinueAttackPercentage(0)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, MC_TIDERUNNER)
call CampaignAttackerEx(1, 2, 2, MC_HUNTSMAN)
call AdvSuicideOnPlayerEx(M1, M1, 30, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 3 ***
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MC_TIDERUNNER)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 2, MG_CLIFFRUNNER)
call CampaignAttackerEx(1, 1, 1, MG_BLOODGILL)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 4 ***
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MG_CLIFFRUNNER)
call CampaignAttackerEx(1, 1, 2, MG_TIDEWARRIOR)
call CampaignAttackerEx(1, 1, 1, MG_BLOODGILL)
call AdvSuicideOnPlayerEx(M1, M1, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
loop
// *** WAVE 5 ***
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MC_TIDERUNNER)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 1, MC_NIGHTCRAWLER)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 6 ***
call AdvSetPrioritizeNearest(true)
call AdvSetSearchPreferredLocations(false)
call AdvSetContinueAttackPercentage(0)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, MG_CLIFFRUNNER)
call CampaignAttackerEx(1, 2, 2, MG_TIDEWARRIOR)
call AdvSuicideOnPlayerEx(M1, M1, 30, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 7 ***
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MG_TIDEWARRIOR)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 2, MG_BLOODGILL)
call CampaignAttackerEx(1, 1, 1, MC_NIGHTCRAWLER)
call AdvSuicideOnPlayerEx(M1, 45, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 8 ***
call AdvSetPrioritizeNearest(false)
call AdvSetSearchPreferredLocations(true)
call AdvSetContinueAttackPercentage(100)
call InitAssaultGroup()
call CampaignAttackerEx(1, 2, 2, MG_TIDEWARRIOR)
call CampaignAttackerEx(1, 1, 2, MC_HUNTSMAN)
call CampaignAttackerEx(1, 1, 1, MG_BLOODGILL)
call AdvSuicideOnPlayerEx(M1, M1, 45, User, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
// *** WAVE 9 ***
call AdvSetPrioritizeNearest(true)
call AdvSetSearchPreferredLocations(false)
call AdvSetContinueAttackPercentage(0)
call InitAssaultGroup()
call CampaignAttackerEx(2, 2, 3, MC_TIDERUNNER)
call CampaignAttackerEx(1, 2, 2, MC_HUNTSMAN)
call AdvSuicideOnPlayerEx(M1, 30, 30, Trolls, ATTACK_WAVE_START_X, ATTACK_WAVE_START_Y)
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
endloop
// Make sure to leave both loops
exitwhen AttackWaveAttackTrolls != AttackTrolls or AttackWaveAttackUser != AttackUser or AttackSuicide != AttackWaveAttackSuicide
endloop
call AdvDebugDisplayToPlayer("AI Info: finished attacking Trolls and User!")
endfunction
// Note: the suicide attack is always only on the user
function AttackWavesAttackSuicide takes nothing returns nothing
local boolean AttackWaveAttackSuicide = AttackSuicide
local integer UserId = GetPlayerId(User)
call AdvDebugDisplayToPlayer("AI Info: starting suicide...")
// Reset the assault group
call InitAssaultGroup()
// Launch attack continuously
loop
call SuicideUnitEx(1, MC_TIDERUNNER, UserId)
call SuicideUnitEx(1, MC_HUNTSMAN, UserId)
call SuicideUnitEx(1, MC_NIGHTCRAWLER, UserId)
call SuicideUnitEx(1, MG_CLIFFRUNNER, UserId)
call SuicideUnitEx(1, MG_TIDEWARRIOR, UserId)
call SuicideUnitEx(1, MG_BLOODGILL, UserId)
call Sleep(2)
exitwhen AttackSuicide != AttackWaveAttackSuicide
endloop
call AdvDebugDisplayToPlayer("AI Info: finished suicide!")
endfunction
function AttackLoop takes nothing returns nothing
loop
// If suicide is set, execute the attack suicide loop
if AttackSuicide then
call AttackWavesAttackSuicide()
else
// Attack the user and/or the trolls depending on the condition
if AttackUser and AttackTrolls then
call AttackWavesAttackAll()
else
if AttackUser then
call AttackWavesAttackUser()
endif
if AttackTrolls then
call AttackWavesAttackTrolls()
endif
endif
endif
// Wait a little time before looping again
call Sleep(10)
endloop
endfunction
// COMMAND LOOP
//------------------------------------------------
function CommandFetch takes nothing returns nothing
local integer Command
local integer Data
// Check if there is any command waiting
loop
exitwhen CommandsWaiting() <= 0
// Get command and data
set Command = GetLastCommand()
set Data = GetLastData()
call PopLastCommand()
// Update attack suicide behaviour command (to start/stop suicide, always against the user)
if Command == COMMAND_ATTACK_SUICIDE then
if Data == DATA_ATTACK_SUICIDE_ENABLE then
set AttackSuicide = true
call AdvSetSuicide(true)
elseif Data == DATA_ATTACK_SUICIDE_DISABLE then
set AttackSuicide = false
call AdvSetSuicide(false)
else
call AdvDebugDisplayToPlayer("AI Warning: Invalid attack suicide command data received!")
endif
// Update attack trolls behaviour command (to start/stop attack against the trolls)
elseif Command == COMMAND_ATTACK_TROLLS then
if Data == DATA_ATTACK_TROLLS_ENABLE then
set AttackTrolls = true
elseif Data == DATA_ATTACK_TROLLS_DISABLE then
set AttackTrolls = false
else
call AdvDebugDisplayToPlayer("AI Warning: Invalid attack trolls command data received!")
endif
// Update attack user behaviour command (to start/stop attack against the user)
elseif Command == COMMAND_ATTACK_USER then
if Data == DATA_ATTACK_USER_ENABLE then
set AttackUser = true
elseif Data == DATA_ATTACK_USER_DISABLE then
set AttackUser = false
else
call AdvDebugDisplayToPlayer("AI Warning: Invalid attack user command data received!")
endif
else
call AdvDebugDisplayToPlayer("AI Warning: Unknown command received!")
endif
endloop
endfunction
function CommandLoop takes nothing returns nothing
// Keep fetching commands while the AI is active
call CommandFetch()
call StaggerSleep(1, 5)
loop
call CommandFetch()
call Sleep(2)
endloop
endfunction
// MAIN
//------------------------------------------------
function main takes nothing returns nothing
call CampaignAI(NAGA_CORAL, null)
// Define AI properties
call DoCampaignFarms(false)
call GroupTimedLife(true)
call SetAmphibious()
call AdvSetPrioritizeTownHalls(false)
call AdvSetContinueAttackReducePercentageIfFarAway(false)
call AdvSetAttackWaveGatherReturnXY(-9800.0, -3800.0)
// MAIN USER BASE TOP LEFT ENTRANCE
call AdvAddPreferredLocation(-6300, -7000.0)
// FOUNTAIN
call AdvAddPreferredLocation(-11300.0, -9500.0)
// TOWER OUTPOST
call AdvAddPreferredLocation(-8500, -9600.0)
// Start command loop for external signals
call StartThread(function CommandLoop)
// Launch attack waves as soon as possible (it depends on the commands received)
call AttackLoop()
endfunction
CONCLUSIONS
And... this is it people!
You can now properly design and customize your JASS AIs without all the drawbacks and the nasty bugs of the default routines. In addition, you can use them combined with default routines without bugs nor side effect.
Have fun, and make the AI players great again!
You can now properly design and customize your JASS AIs without all the drawbacks and the nasty bugs of the default routines. In addition, you can use them combined with default routines without bugs nor side effect.
Have fun, and make the AI players great again!
None so far!
Attachments
Last edited: