Silva's latest holy blessing gives us the power to write our own special moves AI (and with SomeoneElse on boat even the basic AI). I will try to give an overview here as to what this means exactly and how to do it. And I hope to update this as Silva progresses with adding features and I learn more tricks to abuse this (from you?). So please comment to make this a good reference for anyone to create some AI.

The whole thing works without editing the lf2.exe and will instead read your AI scripts at start up (and auto reload them if you are using the debug version, so there is no need to restart for changes to apply).To just play with custom AI from others,download the release version here. Drop the dll into your lf2 directory and also create a folder called AI to put the scripts into.To get started with creating your own AI,download the debug version here. Paste that dll into your lf2 directory. Starting your lf2.exe now will open up a console as well, giving you information about the running AI scripts. Now you can also create a folder called AI inside your lf2 directory which will contain your AI scripts. The scripts are written in Angel Script, which uses basic C syntax. All parts we need from it here are really easy. Also angel scripts are basically text files except their file ending is ".as", so you can edit and create them with any text editor you want. They should be named with the number of the id they shall be used for.

Every AI script will need to contain at least one of these functions:
The id() function - this replaces all the characters AI and gives you control over everything it does.
The ego() function - this function will be called by the original basic AI in case you do not use an id() function.

Obviously if you just want to make a melee character that simply uses certain special move inputs in a smart way you will only use the ego() function. Here is an example of a really simple AI script:

AI-Code:

intego(){if(self.hp<400){DdJ();}return0;}

This code could be useful for John as he will try to heal himself whenever his hp is below 400. But the AI for real attacks will require a little more complex conditions to be executed at the right time. Note that the ego() function is not the main AI function of your character and thus needs to 'report back' to the function it has been called from. This is done with return 0;. The 0 will give the control back to the basic AI (the id() function). If you write return 1; you can retain control in the ego() function. If you create your own functions later on they may return other values depending on their purpose.

Silva has given us access to a lot things from both the character (self) and the target and will hopefully even add more. All following variables will need to be prefixed with either self. or target. to work for your script:

AI-Code:

id//useful to determine what character (or other object) your target isnum//object number (something from 0 to 399, 0 to 7 for human players)type//object typeweapon_type//type of the held weapon (there are special numbers for knife and boomerang)weapon_held//object number of the held weapon, -1 if none is heldframe//useful for combos (in a frame to perform a leap attack?)state//useful for a general look at an unknown opponent (is the target falling/defending/DoP?)team//useful to interact with teammates (fusion/healing/team combos)clone//-1 for a normal character, 3 for a clone (possibly other values)fall//when hit the fall value rises and slowly drops back to 0; >0: DoP, >70: knocked outbdefend//when hit in state 7 this value rises and slowly drops back to 0; >70(/100 for armors): broken defensearest//attack rest - time until the attacker can do a single hit againvrest//victim rest - time until a character can be hit again (multi hit)blink//after state 14 returns a number that runs down from 15 to 0 while blinkingshake//negative number for a hit character, positive for an attackerctimer//runs down from 300 to 0 when catching someonemp//rises to 500; if below 0 only starts rising again if attack is pressed (reset to 0)hp//rises/heals up to dark_hpdark_hp//effective maximum health during a fightmax_hp//can be above 500 for enemies in stage modefacing//false when facing right; true when facing leftx_velocity//absolute velocityy_velocity//..z_velocity//..x//absolute positions on the backgroundz//..y//..D//0, 1 if respective key is pressedJ//..A//..left//..up//..right//..down//..DlA//0, these all return 1 as long as the last pressed key is D,DuA//2 if the second key (direction or J) was last pressedDrA//and 3 if the full sequence has been pressedDdA//(returns to 0 once the input has been accepted for a moveDlJ//or been voided by pressing a different key)DuJ//l, u, r, d stand for left up right downDrJ//..DdJ//..DJA//..

Most important of all these for your usual moves are the position of the character and the target, which determines whether the target is within range for a certain move. There are also some variables that do not belong to self. or target. and will allow you to know what kind of a fight is being played:

The simplest way of checking a range is to subtract character and target position to get the distance and then compare it with the range in which the move hits the target:

AI-Code:

if(((self.x-target.x)=>0)&&((self.x-target.x)=<80))

But this way the character will only perform the move when the target is within 80px to the right of our character. To make this work to the left as well and also only when the character is facing his target we will include the self.facing into the distance calculation like this:

AI-Code:

(self.x-target.x)*(2*(self.facing?1:0)-1)

The additional calculations turn the self.facing from 0 and 1 into -1 and 1. Now the character should also not perform the move when the target is too far away on the z- axis. This is a little easier because it doesn't matter whether the target is above or below our character and Silva has implemented an absolute function to turn any value into a positive one:

AI-Code:

if(abs(self.z-target.z)=<8)

With this condition the move will only be executed when the target is within 17px on the z-axis The y-axis usually isn't quite as interesting as the other two, but if you've got a melee move that cannot hit falling characters you might want to check whether the target is on the ground:

AI-Code:

if(target.y==0)

Now with ranged moves the whole thing gets more complex as the z-range increases with projectiles that can be aimed up and down as they move away. With Clide I have found that you can simply do a division of x and z distance to get an angle you can compare:

AI-Code:

if(abs(100*(self.z-target.z)/(self.x-target.x))=<15)

Now that the conditions are set you can perform your move by putting one or several of the available commands.

D(x,y);//x=1,y=0: the key is pressedJ(x,y);//x=1,y=1: the key is pressed and/or held (useful for walking)A(x,y);//x=0,y=0: the key is released (required if you are using the void id() function)left(x,y);//..up(x,y);//..right(x,y);//..down(x,y);//..DlA();DuA();DrA();DdA();DlJ();DuJ();DrJ();DdJ();DJA();

This is as far as you will need to read for creating simple AI scripts, if you already have something working and want to create more complex things: read on. If you have trouble with your script: read the error and debugging section.

abs(i);//returns the absolute value of i (negative numbers will be turned positive)rand(i);//randomly returns a number from 0 to iloadTarget(i);//this allows you to change your target, while i is a number from 0 to 399//the function will return the object type or -1 if the object does not exist//also the objects i=0,...,7 are always the human characters

But you can also create your own functions:

AI-Code:

int sum(intx, inty){intz=x+y;returnz;}

The first line defines what variable type the function will return and what variable types it will be using. Here are two examples of how this one could be called:

AI-Code:

int variable = sum(self.x, self.y);if(sum(target.z, target.x)<0){}

As you can see a function can be used within calculations and conditions, making it do complex calculations with given variables and letting you use the returned value like a variable. In the first sample the function is used to write the sum of the characters x and y coordinates to a variable and the second one is used to have the sum of the targets z and x coordinates as a condition. So if there is a calculation you need to do very often but with differing variables just create a function for it.

If you ever need a function that does not return anything you can just call it "void functionname()", just like the void id() function.

If you just don't know what you are dealing with and need to see some of those values you are basing your conditions on you can use the function print(), and if you don't like the console running full also clr(). Here is a sample:

AI-Code:

print("text");//prints text in the consoleprint(variable);//prints the value of the variable in the consoleprint("\n");//adds a new line in the consoleprint(variable+"text\n");//prints a value and text with a new lineclr()//clears the console

If you have made a syntax error within your script the console will spurt out really helpful error messages. Here are a few examples on how to interpret and treat them:

AI-Code:

ai\0.as(8, 10): ERROR : Expected ';'

This error simply means that within the file "0.as" you forgot to put a ";" in line 8 position 10. Simply go there and add it, problem fixed.

AI-Code:

ai\0.as(11, 2): ERROR : Unexpected end of file

This error is a little less helpful than the previous one, but it simply means you forgot to close a bracket somewhere. You can just add one at the end, but that may not be the right position so better fully check your script. Maybe you have also opened one too many?

This means you are trying to use a boolean variable there (like self.facing, it can only be true or false) in a mathematic calculation. To convert the variable to the respective mathematic values simply add "?1:0" at its end. (eg.: self.facing?1:0; 1 is true, 0 is false)

If you are running into error messages you do not understand just post them together with your script and I shall add an explanation here.

So you are getting more advanced with your AI and want to know more about other game objects not selected as a target by the basic AI. loadTarget(i) will do this for you. It loads the target variables of object number i and returns the type of it or -1 if it does not exist. There are 400 object slots in the game (0 to 399) and the first 8 are reserved for the human players. So if you want to pick one of these without knowing it's number you will need a for loop to check out all 400 of them:

AI-Code:

for(int i =0; i <400;++i){if(loadTarget(i)==0&&target.team!=self.team){print("opponent "+i+" has "+target.hp+"hp\n");}}

This example will print out the hp from all opponents. But it will only have object number 399 selected afterwards. We will need to add a few variables and another condition to select a certain one of them. I will go with the opponent that has the least health:

You can of course also try to get the opponent closest to you, do a combination of both, look for weapons within reach or check for hostile projectiles. However there is one problem: the basic AI will always reselect the target by it's own conditions if you are only using the ego() function. You should better use this to base only special actions on a self selected target. The most useful area for this is probably anything team related such as healing moves or the fusion. John for example could check all his teammates, whether they are close enough to him and in need of health. And Jan could simply check all teammates to perform her healing angels.

Well, pretty good tutorial. Though a better way I used to debug with was putting print functions inside every if block. When the com try to use the move in the if block, it prints out the string I assigned (which are... difference between both x pixels and both y pixels, the move combination and the mp points.). And there is something I have been trying to do but failed, make the com stand (doin nothing) when the opponent is on ground(laying). And is there any way we can make things go random. For instance, the com randomize between either using this move or that, or maybe this AI or that!!.

Thanx again YinYin, and of course lets not forget Silva for this awesome system.

@A-MAN: well you can use the print function in any way you like
the new return function should allow you to stop a character from moving around - but the safety distance from lying enemies cannot be overridden yet
also i dont know about randomizing yet - usually there are random functions based on system time or something - need to look up whether chaiscript has something of that kind but i doubt it

@Alblaka: the self_weaponid i understand, the other two i don't - maybe elaborate those in the actual hex project thread from silva
also looking forward to more AI

UPDATE: added a new section about target loading and changed all code to C syntax
next i will add a little more about how i debug and test
and after that a new section with samples

in functions, you cannot access global variables, so anything within a function has to be passed in as a parameter (maybe you can define new variables in functions, untested). so if you want to manipulate globals, you have to do it in the global code space (i.e. not in a function).

Gave it a try and ran into a slight issue with my character Striker. Using following AI:

Code:

{
if (self.hp < 400){
DdA()
}
return 1
}

DvA is a transformation move, turning the character into a different ID one. The second form again has a DvA transforming him back.
Using this script, the AI spammed DvA as soon as the condition triggered and as well performed DvA in the frame right after the transformation.
I fixed this issue by adding a self.frame check to the condition, but this bug sort of means the AI script of ID #1 runs for 1 frame of ID #2 (or is the DvA input simply being carried over for a single frame by engine?)

Nothing serious, just dunno'ing.

On a sidenote, kudos for the flawless execution and easyly installation. Took me 5 minutes to get the setup running... Now I can turn my chars into actual companions/opponents :3

edit:

Quote:Also when writing your AI, keep in mind that you do not need to restart LF2 for changes to apply.

Damnit, couldn't you say that earlier?! XD Or rather, in a more visible spot.

(06-19-2012, 02:59 PM)Alblaka Wrote: I fixed this issue by adding a self.frame check to the condition, but this bug sort of means the AI script of ID #1 runs for 1 frame of ID #2 (or is the DvA input simply being carried over for a single frame by engine?)

i think its the engine
check it by printing the DdA
print(self.DdA)
0(blank) is nothing
1(smiley) is defend pressed
2(filled smiley) is defend down pressed
3(heart) is defend down attack pressed

the value should go back to 0 once the input has been accepted (by a hit_Da)
or voided by pressing a different key
for some cases this doesnt seem to happen (think woody: D>A+J will do both blasts and tiger dash)

(06-19-2012, 02:59 PM)Alblaka Wrote: On a sidenote, kudos for the flawless execution and easyly installation. Took me 5 minutes to get the setup running... Now I can turn my chars into actual companions/opponents :3

hell yes!

edit:

Quote:Damnit, couldn't you say that earlier?! XD

reward for reading that far

edit: nope, loadTarget is currently only returning -1 - but the function itself works - so you can check for something else than the type (state/team may work)