 |
WC3Jass.com "The Jass Vault"
|
Affiliates
|
 |
|
 |
|
|
| Message |
Posted:
Tue Nov 01, 2005 1:30 pm Post subject:
Creating simple AIs for Hero Arena-type maps |
|
|
This tutorial will help you add a simple, but cool, AI to hero arena type maps.
The AI you’ll learn how to make here will not be perfect. The one we will create here will attack other heroes, pick up items, learn and use spells, but it won’t be as effective as a human player.
However, when you’ve learned the basics you should be able to improve it yourself.
Requires
JASS Knowledge – This tutorial uses JASS examples, and JASS-only features, so you will need to know JASS to follow this tutorial and make the AI. Technically it can be done in the GUI, but I won’t recommend that due to memory leaks, tons of unneeded code, and because using return bug and game cache based systems isn’t possible in the GUI. If you don’t know JASS, check the JASS tutorials here, at The JASS Vault and Wc3Campaigns.
You also need to know what a rawcode are, if you don’t, just search the different Warcraft 3 sites to find out.
A game cache and return bug based system – This can be KaTTaNa’s Local Handle Variables, Vexorian’s CSCache module (a part of the Caster System) or any other similar system.
In this tutorial I’ll use the CSCache module.
This map – A small map I’ve created that shows a simple AI like the one we’ll make in action. It is important that you have this map, as the tutorial often refers to it.
Notes
- The AI we’ll make here is not as good as a human player, but better than nothing. And when you’ve learned the basics, you should be able to improve it yourself.
- A lot of numbers (player numbers, for example) starts from 1 in the GUI, but while they starts from 0 in JASS. As this is a JASS tutorial, they start from 0 here.
- You don’t have to do the things exactly like I do them; I do it in my way, but if your way is better or you just feel more comfortable with it, do it your own way. I’m not perfect, and this tutorial isn’t perfect either, but hopefully it will help somebody.
- You could use the AI from my demo map without creating your own (if you do so, give me credit), but I suggest making your own, as maps can be very different, and because you should be able to learn more from making it yourself.
- The demo map is probably not perfectly unbugged, and it isn’t the funniest map either. Remember that it was just a map I quickly created to show a simple AI, if you want to see a better map with a good AI, take a look at Azeroth’s Arcane Arena.
- I would like to specially thank Vexorian for encouraging me to make my first AI for my map, giving me tips on how to do it, and for showing me his AI, which helped me improve mine. Thanks!
Initialization
First create a new trigger with the “Player - Player 1 (Red) leaves the game” event. Convert it to JASS. We need that trigger to register when a player leaves the game, so we can start the AI for that player. At the moment it will only register when player 0 leaves the game, so we’ll use a loop to make it register when any player from 0-11 leaves instead.
We want the AI to use abilities. This may sound hard, but it isn’t. We just have to make the Heroes learn the abilities, and they’ll cast them themselves.
NOTE: The situation where a computer-controlled hero will cast a spell is always the same situation as where it would cast the spell it’s based on. So if you have a custom spell based on Silence, it will cast it in situations where it would cast it in melee games. NEVER base your spells of the “Channel” ability, as the AI never will use them. Changing the OrderString field on a spell in the Object Editor does nothing, it will still be the same as on the base spell.
To know which spells the heroes have, we create a game cache to store it in.
In the example map my trigger creates a gamecache at map initialization and saves it in the udg_GameCache global variable. Note that the cache HAS to be initialized before we starts using it, so I will do that in the first InitTrig function of my map.
In my map I create a function called “SetupSkills”. In the AI trigger’s InitTrig function I use the ExecuteFunc native (read more about that native here) to execute that function in another thread. This is to prevent the map initialization thread from getting too long, and crash.
My SetupSkills function looks like this:
| Code: |
function SetupSkills takes nothing returns nothing
local string h // Create a local string variable
// Paladin // Here we’ll initialise the Paladin’s skills, repeat this for all other heroes
set h = UnitId2String('Hpal') // Store the returned value of UnitId2String(‘Hpal’) in the local
call StoreInteger(udg_GameCache, h, "BaseSkill1", 'AHhb') // One of his base skills is Holy Light, store it as “BaseSkill1”
call StoreInteger(udg_GameCache, h, "BaseSkill2", 'AHds') // Store Divine Shield as “BaseSkill2”
call StoreInteger(udg_GameCache, h, "BaseSkill3", 'AHad') // Store Devotion Aura as “BaseSkill3”
call StoreInteger(udg_GameCache, h, "UltimateSkill", 'AHre') // Store Resurrection as his “UltimateSkill”
… // Repeat for each Hero.
endfunction
|
Here’s my InitTrig_AI function:
| Code: |
function InitTrig_AI takes nothing returns nothing
local integer i = 0
set gg_trg_AI = CreateTrigger( )
loop
exitwhen i > 11
call TriggerRegisterPlayerEventLeave( gg_trg_AI, Player(i) )
set i = i + 1
endloop
call TriggerAddAction( gg_trg_AI, function PlayerLeaves )
call ExecuteFunc("SetupSkills")
endfunction
|
Starting the AI for a hero
To control the AI I will use a timer. I create a function called “StartAI” that takes a single unit argument: the hero (check the function in the example map). The function just creates a timer, "attaches" the hero to it, and starts it (just make the expiration function now, we will out some actions into it later, but you need the function and endfunction lines to prevent getting compile errors).
This is the empty AILoop function and the StartAI function from the example map:
| Code: |
function AILoop takes nothing returns nothing
endfunction
function StartAI takes unit hero returns nothing
local timer m = CreateTimer()
call AttachObject(m, "hero", hero)
call TimerStart(m, 0, false, function AILoop)
set m = null
endfunction
|
Note that I’m starting it as a “one-shot” timer, by using false as the 'periodic' boolean value (we’ll get back to that later).
Now just make your hero selection system call that function when a computer controlled player chooses a hero, and go to the function that is executed when a player leaves the game. Check if the player has a hero, if he/she has one, call the function that starts the AI on that hero. Example:
| Code: |
function PlayerLeaves takes nothing returns nothing
local player p = GetTriggerPlayer()
call DisplayTextToForce(bj_FORCE_ALL_PLAYERS, GetPlayerName(p)+" has left the game.")
if udg_Hero[GetPlayerId(p)] != null then
call StartAI(udg_Hero[GetPlayerId(p)])
endif
set p = null
endfunction
|
NOTE: This will make the AI take control of a leaving player's hero, this is not needed, if you want to do something else when a player leaves.
Making the AI do something
Whenever the timer expires there are some things we want it to do:
- If the hero is dead, wait until he/she/it is revived.
- If the hero is about to die, order him/her/it to move to the fountain at the map center.
- If the hero has a fine amount of health, check if an enemy is close. If true, order the Hero to attack it, else check for items close to the hero, if any, issue a smart order so the Hero will pick the up. if there isn’t any, just order the hero to patrol to a random point in the arena.
- If the hero is alive and has any unused skill points, learn a skill.
We’ll start with declaring all the variables. Notice the real variable 'e' in my function, it defines how long time will elapse before the timer expires again, so we can wait shorter time if the hero is dead, or longer time if he/she/it is attacking. That variable is initialized with the value 5.
Declare the local variables:
| Code: |
function AILoop takes nothing returns nothing
local string a = GetAttachmentTable(GetExpiredTimer())
local unit h = GetTableUnit(a, "hero")
local rect i
local location r
local real x = GetUnitX(h)
local real y = GetUnitY(h)
local group g
local boolexpr b
local boolexpr be
local unit f
local string o = OrderId2String(GetUnitCurrentOrder(h))
local real l = GetUnitState(h, UNIT_STATE_LIFE)
local real e = 5
…
|
We start with checking if the hero is dead, if he/she/it is, set the real variable to 1.5 (because waiting 5 seconds after revival is too long time, we don’t want that).
The hero’s life ('l' is 0, just set e to 1.5 to make the timer check more frequently for the hero’s revival.
| Code: |
…
if l <= 0 then
set e = 1.5
endif
…
|
Next I check if the hero’s life is below 20% of it's max life. If it is low, order the hero to move to fountain and set the variable 'e' to 3.
The hero’s life is less than 20% of max life, so order the hero to move to the position of the fountain:
| Code: |
…
if l < GetUnitState(h, UNIT_STATE_MAX_LIFE)/5 then
call IssuePointOrder(h, "move", GetUnitX(gg_unit_nfoh_0001), GetUnitY(gg_unit_nfoh_0001))
set e = 3
…
|
If the hero isn’t weak, check if he/she/it has a common order (to prevent it from interrupting channel spells). If it is a standard order, we check if any enemies are within a radius of 500. If true, simply issue an attack order (don’t change the 'e' variable, 5 seconds is fine in this situation).
| Code: |
function AIFilterEnemyConditions takes nothing returns boolean
return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 and IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(GetAttachedUnit(GetExpiredTimer(), "hero")))
endfunction
…
else
if ((o == "smart") or (o == "attack") or (o == "patrol") or (o == "move") or (o == "stop") or (o == "hold") or (o == null)) then
set g = CreateGroup()
set b = Condition(function AIFilterEnemyConditions)
call GroupEnumUnitsInRange(g, x, y, 500, b)
set f = FirstOfGroup(g)
if f == null then
…
else
call IssueTargetOrder(h, "attack", f)
endif
call DestroyGroup(g)
call DestroyBoolExpr(b)
endif
…
|
If no enemies are found, check for items. If an item is found, check if it’s a powerup. If it isn’t, check if the hero has any empty inventory slots, and order the hero to pick it up.
| Code: |
function AISetItem takes nothing returns nothing
set bj_lastRemovedItem=GetEnumItem()
endfunction
function AIItemFilter takes nothing returns boolean
return IsItemVisible(GetFilterItem()) and GetWidgetLife(GetFilterItem()) > 0
endfunction
function AIHasEmptyInventorySlot takes unit u returns boolean
return UnitItemInSlot(u, 0) == null or UnitItemInSlot(u, 1) == null or UnitItemInSlot(u, 2) == null or UnitItemInSlot(u, 3) == null or UnitItemInSlot(u, 4) == null or UnitItemInSlot(u, 5) == null
endfunction
…
if f == null then
set i = Rect(x-800, y-800, x+800, y+800)
set be = Condition(function AIItemFilter)
set bj_lastRemovedItem=null
call EnumItemsInRect(i, be, function AISetItem)
if bj_lastRemovedItem != null and (GetItemType(bj_lastRemovedItem) == ITEM_TYPE_POWERUP or AIHasEmptyInventorySlot(h)) then
call IssueTargetOrder(h, "smart", bj_lastRemovedItem)
else
…
endif
call RemoveRect(i)
call DestroyBoolExpr(be)
…
|
If the hero has items in all slots, or no items existed, order him/her/it to patrol to a random location in the map, to find new targets.
| Code: |
…
else
set r = GetRandomLocInRect(bj_mapInitialPlayableArea)
call IssuePointOrderLoc(h, "patrol", r)
call RemoveLocation(r)
…
|
Now let’s check if the hero has any unused skill points (keep this separated from the attack/item pickup/patrol block).
If he/she/it has, call a function that learns a skill to the hero. In my example I’ve used a function that stores the number it has taught the hero an ability, to keep a special pattern in the ability learning:
| Code: |
function AILearnSkill takes unit h, string a returns nothing
local integer i = GetTableInt(a, "LearnSkillOrder")+1
if i == 1 or i == 4 or i == 8 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill1"))
elseif i == 2 or i == 5 or i == 9 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill2"))
elseif i == 3 or i == 7 or i == 10 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill3"))
elseif i == 6 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "UltimateSkill"))
endif
call SetTableInt(a, "LearnSkillOrder", i)
endfunction
…
if GetHeroSkillPoints(h) > 0 and l > 0 then
call AILearnSkill(h, a)
endif
…
|
Now simply make the timer expire again after 'e' seconds:
| Code: |
…
call TimerStart(GetExpiredTimer(), e, true, function AILoop)
…
|
Last we need to set the local variables to null:
| Code: |
…
set h = null
set i = null
set r = null
set g = null
set b = null
set f = null
set be = null
…
|
Final notes
This is the basics of it, it can be way better, but this should help you get started. Feel free to ask questions here or pm me.
It shouldn’t be complicated at all, but if you have just checked the tutorial it can be so. The map was made to give a better demonstration, so please check it.
When you have finished making your simple AI, try to add one or more of the following things to imrove it:
- Try to make it find the weakest enemy close.
- Try to make different AI players work together on killing a specific unit.
- When most battles becomes centered about the fountain, make heroes run away from it when they’re fleeing.
- Make the AI post text messages that varies depending on the situation (for example, an AI player can say “Die, sucker!” before killing you).
I hope this will help somebody!
Blade.dk _________________ #wc3dev
Last edited by Blade.dk on Tue Jan 24, 2006 1:52 pm; edited 5 times in total |
|
|
|
 |
|
 |
|
 |
 |
|
 |
|
|
| Message |
Posted:
Tue Nov 01, 2005 1:35 pm Post subject:
|
|
|
I made this because some guys was asking me how I've made the AI in my map.
Suggestions and comments are very welcome. _________________ #wc3dev |
|
|
|
 |
|
|
|
| Message |
Posted:
Tue Nov 29, 2005 5:36 am Post subject:
ok |
|
|
That was good, finally some AI script that is decent, btw, can you help out on the equipment system? Thank you alot btw. _________________ There's always a price to pay for everything you get... That's the fundamentals of life... |
|
|
|
 |
|
|
|
| Message |
Posted:
Thu Dec 01, 2005 6:44 am Post subject:
|
|
|
Thanks. I'll turn my inventory system into a demo map then, maybe a tutorial if I have the time, I just forgot about it. I'll release my inventory system as soon as I have the time  _________________ #wc3dev |
|
|
|
 |
|
 |
|
 |
 |
|
 |
|
|
| Message |
Posted:
Sat Jan 14, 2006 9:58 am Post subject:
|
|
|
A few suggestions:
1. Maybe the hero has more than 1 empty inventoryslot, so let him pick ALL items till his inventory is full and the hero should probably replace or instantly use weak or charged items and pick up better ones if there are any.
2. Use a loop instead of a cycling timer(No big deal). It would prevent you from creating and removing te item pickup rect.
3. ALWAYS use orderIDs. They are a lil faster(No big deal too).
4.a. Make a diffrence between good human players who left the game and bad ones, so the AI goes on to make the same amount of points. (I think this can be done by counting the player's kills/points and compare it to the others and then sort him in one of maybe 3 categories)
4.b.Make a diffrence between insane and newbie comps.
All in all this is great tutorial for everybody and sayed just some suggesteions which everbody could do.
It's not htat easy to use every point I mentioned, but ..
 _________________ ► BertTheJasser |
|
|
|
 |
|
 |
|
 |
 |
|
 |
|
|
| Message |
Posted:
Mon Jan 23, 2006 8:23 pm Post subject:
|
|
|
Remember, this was made to be easy, advanced features can be added later, this is to explain the very basics, so it will stay with that. Thanks for the suggestions though.
By the way, I have made some minor updates to this tutorial. _________________ #wc3dev |
|
|
|
 |
|
|
|
| Message |
Posted:
Tue Jan 24, 2006 1:11 pm Post subject:
|
|
|
For a sample to learn it's perfect, I just wanted to give you some improvement possibilities. _________________ ► BertTheJasser |
|
|
|
 |
|
|
|
| Message |
Posted:
Tue Jul 10, 2007 11:10 am Post subject:
help |
|
|
I'm wondering how to issue a player to select a hero from a tavern... I did this
call IssueNeutralImmediateOrderById(GetEnumPlayer(), GetEnumUnit(), 'Hpal')
Doesn't work, instead of just leaving players I want to make them select a hero in the beginning from my allstars tavern instead of just selecting a hero like ur map. Please help |
|
|
|
 |
|
|
|
| Message |
Posted:
Wed Jul 11, 2007 8:57 am Post subject:
|
|
|
| Code: |
set u = udg_Merchant // Just an example
set Uid = 'Hpal' // The Id for the target unit
call IssueTrainOrderByIdBJ(U,Uid)
|
You might want to remove the BJ though...
- Hope this helps... _________________
 |
|
|
|
 |
|
 |
|
 |
 |
|
 |
|
|
| Message |
Posted:
Thu Nov 29, 2007 10:29 am Post subject:
|
|
|
| kixer wrote: |
| Code: |
set u = udg_Merchant // Just an example
set Uid = 'Hpal' // The Id for the target unit
call IssueTrainOrderByIdBJ(U,Uid)
|
You might want to remove the BJ though...
- Hope this helps... |
Talking about DotA AllStars, how do you do the same thing for AI players?
To be more detailed, you create a game as the first player, with 4 computer players as allies and 5 computer players as enemies. When the game starts, you want all the computer players to select a hero.
I haven't tried the codes above. But I think it won't work because there is nowhere to assign which player to issue the order. IssueNeutralImmediateOrderById doesn't work for me, either. I don't know why.
Could anybody help? |
|
|
|
 |
|
 |
|
 |
 |
|
 |
|
|
| Message |
Posted:
Thu Nov 29, 2007 10:38 am Post subject:
|
|
|
<continuing last post...>
Actually, IssueNeutralImmediateOrderById works on human-controlled slot, i.e., the first player, i.e., Player(1). The code select a hero for me.
But with the same code (same shop, same hero, only the player slot is different), it doesn't work. |
|
|
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|