Magsbot Class, Session 9
Saturday May 10, 2003 2pm VRT

Magine: hi all
ZebLith: ^^
ZebLith: Good morning Magine :)
Anubis GK: Heya there, Magine!
Anubis GK: I suppose these are the right cords!
Magine: yep
Magine: so, zeb, did you get the database stuff working with Win ME?
ZebLith: Okay, I didn't try win me, I'm on win xp right now.  I was just about to try it again.  I should have ust copied my bot over from my main computer, but I woke up late.  So...that's my goal for this morning. :)
Magine: ok
Magine: well, can you try it out now and see how it goes?
ZebLith: yes, I just need to type everything back in.  I'm checking with the class log as well.
Magine: our scheduled topic for this last class is anything leftover that you want to know more about...
Magine: so, anubis, any topics you'd like me to discuss today?
Anubis GK: Aye, if/when statements, and how to make it so a user can specify a variable.
Anubis GK: *Laughs* It's an easy topic, I know....but I spent all last night trying to figure it out.
Magine: how do you mean, specify a variable?
Anubis GK: Let me telegram you the code I have so far:

Telegram from Anubis GK, sent just now:
SAY "All player's to there marks.  The race will start in 10 seconds!";
MBWAIT 10000;
@i=10;
WHILE @i>0 {
 MBWAIT 1000;
 SAY $str[@i];
 @i=@i-1 };
MBWAIT 1000;
SAY "GO GO GO!"
Anubis GK: Now the problem is, that I want the users, to type a responce in the chat dialog, and from that responce, the program will take one of 2 different paths. ^_^'
Anubis GK: But I don't know how to make mb recognize the input from the text.
Magine: ok....well, it looks like a lot of waiting is involved, heheh
Anubis GK: *Laughs* I know, I know, evil code.
Magine: it looks like a 21 second wait, not ten second
Anubis GK: Whoops.....still getting the hang of mili seconds.
Magine: no, i mean,
Magine: first you have mbwait 10000, that's ten seconds
Magine: then you have the while loop, another 10 seconds
Magine: then an extra second after the while loop
Anubis GK: Oh, *Laughs* I suppose I should word that diferently. 10 seconds untill the count down starts.
Magine: ok
Magine: even so, it will be 11 seconds before the first "say", since you put the mbwait 1000 before the say instead of after it
Anubis GK: that's right......so I would want to change the 10k into 9k.
Magine: lol, or just move the say
Anubis GK: *Does it the lazy was and changes it to 9k*
SAY "All player's to there marks. The race will start in 10 seconds!";
@i=10;
WHILE @i>0 {
 SAY $str[@i];
 MBWAIT 1000;
 @i=@i-1 };
SAY "GO GO GO!"
Magine: so, how do you want the players to respond, to what?
Anubis GK: Hm...*blinks and thinks*
Anubis GK: For example:
[Kusanagi]: Are all player's ready?  Respond YES if ready!
Anubis GK: And from there, that will execute the rest of the commands.
Magine: i see....
ZebLith: (Update:  I actually have a database opened.  Now trying to do the code we did last time. *Crosses fingers*)
ZebLith: especially when you have a version of windows that isn't older than the computer its in. >_<
Magine: cool, zeb
Magine: Anubis, you can tell if a player says YES by just having a behavior table row with Event:
HEAR @eq[$chat,YES]
Anubis GK: Aye, I thought of that too.  Isn't there a command to only make it exicute if another event has occured?
Anubis GK: *Laughs* I read that log.  Looks like Databasing is pretty hard. =D
Magine: it's tricky, yes
Magine: but once you get it going it's not so hard
Magine: regarding your question though...you would create a global variable to show that the previous event had occurred
Magine: when the preliminary event occurs, just do @@blah=1
Magine: then in the HEAR event you could put HEAR @eq[$chat,yes]&&@blah
Anubis GK: And if that global variables 1, we're in business.  And at the end of the HEAR yes command, reset the gv to 0?
Magine: yes.
Anubis GK: There doesn't need to be a @blah=1?
Magine: not necessary, no
Magine: because expressions are evaluated numerically
Magine: and 1 (or any non-zero number) = true [so @blah is the same as @blah=1]
Anubis GK: Ah.
Magine: however, you might want to put @gv_[blah] instead of @blah,
Magine: in case the variable doesn't exist at first
Anubis GK: Aye aye.
Magine: or, you could just create a row like Event: STARTUP  Action:  @@blah=0
Anubis GK: Ah.  To start up the variable and make it exsist.
Magine: right
Magine: because if it doesn't exist when the @eq[$chat,yes]||@blah  executes,
Magine: there will be an error message
Magine: but using @gv_[blah] instead of just @blah would prevent the error message...@gv_[blah] will just return 0 if @blah doesn't exist
Anubis GK: *Nods* Aye aye!

Magine: how's it coming zeblith?
ZebLith: oh no way, it worked.
Magine: :D
ZebLith: well, sorta.
ZebLith: lol
Anubis GK: =D
Magine: sorta? what didn't work?
ZebLith: it didn't add a line to the database, but it didn't give an error.  so...>_<
ZebLith: let's try restarting this again. ^^
Magine: it didn't add a line though...when someone arrived?
ZebLith: yes, it didn't add a row to the database for a new person.  but I skipped a restart as I did it, so let's try this again. ^^
Magine: ok...

ZebLith: lol, there was the problem.  I was reading a different database.  it DID work. :D
Magine: ok, cool
Magine: well now, to kill two questions with one answer, we could do an example in which the bot asks someone a question and uses the answer to add to the database
ZebLith: great :D
Anubis GK: Aye!
Anubis GK: ^_^
Magine: just a moment while i grab some code from the awrpg bot...it has a nice question-and-answer system
ZebLith: okay :)
Anubis GK: Aye, that's for sure. ^_^
Anubis GK: Oh, the new coding works like a charm Magine! =D
Magine: :)
Magine: ok....let me see here....

Magine: ok...let's have the bot ask a person what their home world is, when they first arrive
Magine: first let's make some changes to the AVATARADD event that we did last time

IF @dbfindfirst[$fmt["Name='%s'",$avname]]=0 { DBAPPEND };
Magine: change that, to this:
IF @dbfindfirst[$fmt["Name='%s'",$avname]]=0 {
  @new=1;
  DBAPPEND }
ELSE {
  @new=0 };
Magine: then, at the end of the existing code, add
 IF @new {
  SAY "What is the name of your home world?";
  SETITEM_ "q_"+$botname @avsession 1 }
Magine: so that the whole thing should be
 DATASET @MyDatabase {
  DBTABLE MyTable;
  DBACTIVE 1;
  IF @dbfindfirst[$fmt["Name='%s'",$avname]]=0 {
   @new=1;
   DBAPPEND }
  ELSE {
   @new=0 };
  DBEDIT;
  DBFIELD Name=$avname;
  DBFIELD_ ID=@atr[avatar_citizen];
  DBFIELD_ LastVisit=@now;
  DBPOST };
 IF @new {
  SAY "What is the name of your home world?";
  SETITEM_ "q_"+$botname @avsession 1 }
Magine: there, that's right :D
ZebLith: lol okay got it :D
Magine: so, if the arriving av doesn't have a record in the database already,
Magine: the bot asks them for their home world,
Magine: and then it adds an item to a numeric list
Magine: where the item number is the person's session number, and the value of the item is 1
Magine: and the name of the list is based on the bot's name, like q_Mags

(During the class I actually used $cat["q_",$botname] instead of "q_"+$botname.  Either way would do the same thing; the $cat function just combines two strings, in this case q_ and the the bot's name. I changed it here to make it a bit simpler to understand.)

Magine: the reason for basing the list name on the bot's name is so that if you are running other bot sessions at the same time,
Magine: each one can ask questions independently
Magine: so to put it another way, SETITEM_ $cat["q_",$botname] @avsession 1  will create a global variable like   @q_Mags:1234 with a value of 1
Magine: where 1234 would be the session number of the avatar being asked the question.

Magine: now, to handle replies,
Magine: you would add a HEAR event, following any other HEAR events,
Magine: with this as the Event:

HEAR @reacted=0 && @asn[n,@litem[$cat["q_",$botname],@chatsession]]

(Note in this case, you must use $cat["q_",$botname] instead of "q_"+$botname, because the + operator for combining strings won't work when it's a function parameter.)

Magine: and this as the Action:
 REM handle player reply to bot's question;
 @av=@chatsession;
 $av=$avname;
 REM REPORT $fmt["chatsession=%s pending question=%s",@av,@n];
 IF @n=0 {
  REM do nothing if question number + player's session number not found in list q;
  BREAK };
 REM otherwise free item from pending question list and call the userevent for the question;
 FREEITEM_ $cat[q_,$botname] @av;
 $reply=$triml[$tail[$chat,2]];
 USEREVENT @n
Anubis GK: You weren't kidding when you said it was tricky.
Magine: well i am going to explain it all :)
Magine: but of course i'm assuming you've read the previous class logs...
Magine: ok, so now let's look at all that a bit at a time
Magine: first the event:
HEAR @reacted=0 && @asn[n,@litem[$cat["q_",$botname],@chatsession]]
Anubis GK: *Nods* Aye.
Magine: the @reacted=0 part just means "if no other HEAR events caused a response, in previous behavior table rows"
Magine: @reacted is a function that is true if any previous rows caused a response.
Magine: that is, if any previous row in the table had event conditions that were true, for that same event type (HEAR in this case)
Magine: so, the row we just added will only be triggered if no other HEAR events above it in the table were triggered

Magine: next, the && is just logical "and"
Magine: so both @reacted=0  AND the rest of the condition must be true, for the whole condition to be considered true
Magine: so let's look at the rest:

@asn[n,@litem[$cat["q_",$botname],@chatsession]]
Magine: for the moment we can forget about the @asn and just look at the inner part:
@litem[$cat["q_",$botname],@chatsession]
Magine: the $cat[q_,$botname] part is simple, it's just creating a string from the bot name, for example "q_Mags".
Magine: (the $cat function just combines two or more strings)
Magine: so you could look at it as being: @litem["q_Mags",@chatsession]
Magine: so now it should start to become understandable :)
Anubis GK: That definatly simplifies it. =D
ZebLith: very much so :)
Magine: what we are doing is looking in the list q_botname
Magine: for an item with the item number = the session of the av who is speaking.
Magine: the point is, if there is an item in list @q_botname whose item number is the session of the av speaking, then @litem will return some non-0 number, the value of the list item.
Magine: in this case the value will be 1, since that's what we put in the list earlier
Magine: so @litem[$cat["q_",$botname],@chatsession] will evaluate to 1 (which is the same as "true"),
Magine: IF there is an item in the @q_botname list that corresponds to the person speaking.
Magine: now the outer part of the whole expression, the @asn part,
Magine: just assigns the result of the whole @litem[$cat["q_",$botname],@chatsession] part, to a local variable @n
Magine: the purpose of that is because we are going to want to use that result in the action column,
Magine: so assigning it in the event column just saves a few milliseconds.
Magine: otherwise we would have to do @n=@litem[$cat["q_",$botname],@chatsession] in the action column
Magine: to search the q_botname list a second time.
Magine: does that all make sense so far?
ZebLith: think so. ^^
Anubis GK: Aye, I'm re-reading it, but slowly getting it. ^_^'
Magine: the meaning of the whole expression is, therefore,
HEAR @reacted=0 && @asn[n,@litem[$cat["q_",$botname],@chatsession]]
Magine: "if none of the previous rows of the table with Event=HEAR were triggered, AND if there is an item in q_botname with item number = the speaker's session number"  (and incidentally assign the item value to @n)
Magine: then the action is triggered...
Anubis GK: No duplicate entries?
Magine: no, because the list item numbers are the session numbers of the speakers,
Magine: and no two list items can have the same item number.

(For example if the session of the person speaking is 1234, then the SETITEM_ command would have assigned a number to global variable @q_botname:1234 = item 1234 of list @q_botname.)

Anubis GK: Oops.
Magine: so there can only be one entry in q_Mags for each person
Magine: thus, only one pending question for each person.

Magine: what i need to explain to clarify this is,
Magine: that q_botname is a list of pending question numbers
Magine: that 1 we assigned to @q_botname:1234  stands for question #1.
Magine: and each possible question will get it's own row in the behavior table.
Magine: that is, each question will get a row in the behavior table to handle the replies to that question
Anubis GK: Ahh!
ZebLith: oooh :D
Magine: but first there is a row in the table (the row we're looking at now) that routes player chat to the correct row for the particular question.
Anubis GK: Ohhhh!  *Light bulb* Makes sence now!
Magine: so the Event condition we've looked at so far,
Magine: checks to see if the bot is expecting a reply
Magine: from the particular speaker
Magine: and if a reply is being expected, then the action takes place,
Magine: which routes the player's chat to the correct row for the question whose reply is being waited for.
Anubis GK: Hey, that's handy for more then one thing! ^_^
Magine: and that action [to route replies to different table rows] is this:

 REM handle player reply to bot's question;
 @av=@chatsession;
 $av=$avname;
 REM REPORT $fmt["chatsession=%s pending question=%s",@av,@n];
 IF @n=0 {
  REM do nothing if question number + player's session number not found in list q_botname;
  BREAK };
 REM otherwise free item from pending question list and call the userevent for that question number;
 FREEITEM_ $cat[q_,$botname] @av;
 $reply=$trim[$chat];
 USEREVENT @n
ZebLith: nifty ^^
Magine: looking at that one line at a time:   the @av= and $av= just assigns the session number and av name to some local variables
Magine: and you recall that in the event, we assigned the question number to @n,
Magine: so here in the action we first check to see if in fact a question number was found,
Magine: and if not (if @n=0)  then do nothing
Magine: or to put it another way,
Magine: if no reply was expected from the speaker, do nothing.
Magine: otherwise,
Magine: we free the item from the list (because we have now received a reply)
Magine: and we take the player's chat and send it to the row of the behavior table that matches the question
number @n
 
In the GM bot in AWRPG, there is an additional complication: the person speaking has to prefix a colon : to their reply, so the bot will know they're speaking to it. To add this feature, you would make these changes:
 REM handle player reply to bot's question;
 @av=@chatsession;
 $av=$avname;
 REM REPORT $fmt["chatsession=%s pending question=%s",@av,@n];
 IF @n=0 {
  REM do nothing if question number + player's session number not found in list q_botname;
  BREAK };
 IF @neq[$left[$chat,1],":"] {
  WHISPER_ @chatsession "(Please begin replies with a colon, like "":yes"" or "":no"".)";
  BREAK };
 REM otherwise free item from pending question list and call the userevent for that question number;
 FREEITEM_ $cat[q_,$botname] @av;
 $reply=$trim[$tail[$chat,2]];
 USEREVENT @n

Magine: ok, so far so good....now we need to add a USEREVENT row to handle that particular question.

[Chilaou]: What is the name of your homeworld?
Magine: heheh
ZebLith: okay, It's taking everything.  bleh. >_<
Anubis GK: *does a thumbs up*
Anubis GK: Well, it's a step in the right direction?

Magine: so you would add a behavior table row with event:

USEREVENT @userevent=1
Magine: and in the action for that row,
Magine: the string $reply would already contain the user's reply [without the colon, if using the AWRPG bot version discussed in the box above]
Anubis GK: Ahhh.
Magine: normally you would do some error checking to make sure the player's reply made sense,
Magine: but since this is just an example, we can keep it simple and pretend that the player (oops, the speaker...i'm still thinking in AWRPG terms, heheh) will always make a sensible reply,
Magine: so we can just assign whatever they say to the database.
Anubis GK: So, in essance, the first stuff we put, would assign the responce a number, and depending on that responce, the input would be directed to different behavior rows?
Magine: not quite
Magine: it would be directed at different rows depending on which question is being handled
Anubis GK: Ah
Anubis GK: I was closeish. =P
Magine: we only have 1 question so far, but this system lets you add any number of possible questions
Magine: when you do SETITEM_ $cat["q_",$botname] @av 1 earlier
Magine: you would replace the 1 with whatever question number the bot was asking
Anubis GK: Ohhhh.
Magine: if you only have one bot, you could replace the $cat["q_",$botname] with just "q_"
Magine: but this system also allows for different bots asking different questions of different people, all at the same time.
 
A final summary:

1. The bot asks the question, and uses SETITEM_ to create an entry in the @q_botname list, using the session number of the person being asked as the list item number, and the question number as the value being assigned.

2. In the HEAR event, if no previous HEAR events caused a response and if the speaker's session number is found as an item number in the @q_botname list, then the question number is retrieved from the list, and the item is freed. Then a USEREVENT is generated, using the question number as the userevent number; the speaker's reply is passed to the USEREVENT in $reply, and the session and name of the speaker are passed in @av and $av.

3. In the USEREVENT where @userevent = the question number, the bot responds appropriately according to the speaker's reply.

Magine: so...(getting back to database stuff now, heheh)....to put the person's reply into the database
Magine: for the action in the USEREVENT row (Event: USEREVENT @userevent=1), you could put:

 REM player replies to "Home world?";
 DATASET @MyDatabase {
  DBTABLE MyTable;
  DBACTIVE 1;
  IF @dbfindfirst[$fmt["Name='%s'",$avname]]=0 {
   SAY "I can't find your data, sorry.";
   BREAK };
  DBEDIT;
  DBFIELD "Home world"=$reply;
  DBPOST }
Magine: note that @av, $av and $reply are already assigned in the action that called the USEREVENT.

[in the example, if the record for the arriving avatar isn't found in the database, then @dbfindfirst will return 0 and the BREAK will cause execution to leave the DATASET clause, thus ending the response. more generally, the BREAK command causes execution to leave the current context, whether it's a FOR or WHILE loop, DATASET clause, etc, or the action itself; but notice that BREAK within an IF clause doesn't just leave the IF clause, it leaves whatever is outside the IF clause. that's unique to the IF command, as far as BREAK is concerned.]

Magine: any other questions about all that?
Anubis GK: None here.
ZebLith: Ditto.
Magine: well, only a short time left for today's lession....i want to briefly mention SQL statements, or if there are any other topics at all you want to talk about?
ZebLith: No other topics from me; you already covered all my questions. :)
Anubis GK: Let's talk about SQL, I'm curious what that is.
Magine: ok :)
Magine: well, SQL stands for "structured query language", and it's a language for manipulating databases
Magine: intended to be universal, since most all serious databases will handle SQL to some extent.
Anubis GK: It sounds familar for some reason.
Magine: it ought to be :)
Magine: you can find a lot more information about SQL syntax on the web...for instance http://users.pandora.be/pecop/sqlr/ is a good source.
ZebLith: that looks familiar. ;)
Magine: that page just covers the simple commands though...SQL is very large and complex, and i'm no expert
Magine: but the cool thing is....
Magine: that since most database drivers support it,
Magine: and magsbot can connect to most databases using ADO,
Magine: it was really easy for me to add SQL functionality to magsbot
Magine: even though i'm only a beginner at SQL myself :D
Anubis GK: lol, that's neat. ^_^
ZebLith: very :D
Anubis GK: Well, it certainly is an expancive resouce.
Magine: so...you can use SQL in magsbot with the DBSQLSET, DBSQLEXEC and DBSQL commands.
Magine: using DBSQLSET you can use the various SELECT commands in SQL to filter a database
Magine: for example

DBSQLSET "select * from Users where HP_current>10"
Magine: that would be something i could use with the AWRPG database to find players whose current HP's are above 10.  [you can also use the SQL select command in the SQL edit box on the magsbot database window]
Magine: you can also use the "select" in place of the @dbfindfirst function
Magine: so instead of
@dbfindfirst[$fmt["Name='%s'",$avname]]
Magine: you could use
DBSQLSET $fmt["select * from MyTable where Name='%s'",$avname]
Magine: then you would use @dbreccount to see if any records with that name were found
Magine: (if @dbreccount=0 then no records were found).
Magine: also, if you want to update records instead of examining them,
Magine: you can use DBSQL
Magine: like
DBSQL "update Users set HP_current=1000 where Name='Magine'"
Magine: to give myself 1000 HP's, heheh
ZebLith: :D
Anubis GK: Complicated, but it I still have a 1/2 watt light bulb on!
Magine: it will become more clear with use :)
Magine: as for magsbot, be sure to read the help for more info about databases, at http://www.turtleflight.com/mbh/mh_actions.htm#database
Anubis GK: Aye.  Seem's like the SQL commands are in plain english?
Magine: well, the SQL commands seem like English but they are very specific
Magine: as you can see at http://users.pandora.be/pecop/sqlr/
Magine: you can do all kinds of things with SQL, i've barely used the simplist functionality so far, in my scripts
Anubis GK: No kidding?  Wow! =D
Magine: well, we are about at the end of our time. the question is....should we have another class next week?
Magine: there are no scheduled topics, but if there's something you want to discuss?
ZebLith: well... do you have anything in mind to teach next week?
ZebLith: hmm...
Magine: nothing in mind at the moment
Anubis GK: *Shrugs* All my questions are just basic ones, that I can figure out with time, so I dun have any ideas. ^_^'
Magine: we could do a workshop kind of thing perhaps, but you will have to come up with a project idea
ZebLith: I must say I've grown accustomed to waking at 8:30 every saturday...
Magine: heheh
Anubis GK: *Chuckles*
Magine: i was going to suggest moving the class an hour later, heheh
[Kusanagi]: *Raises her hand* Anubis would like that!
ZebLith: any specific reason?  I rather like it because as soon as it's done I get lunch. ;)  but whatever works, the most work ridden part of the year is over.
Magine: if it's easier for you, we can keep it at the same time as now
Magine: well, you don't need to decide a topic right now....just email me before next saturday (well before friday let's say)
Magine: magine@turtleflight.com
Anubis GK: Ether way, I'm fine with the time. ^_^
Magine: ok
ZebLith: ditto.  :)
Magine: so, you can email me with workshop project ideas, or any questions
Magine: i'm mean about telegrams but i have no problem with email at all :)
Anubis GK: Oh, can I just have a quick question?
Magine: sure, shoot :)
Anubis GK: Can these little puppies do avatar to object detection?  Like for instance, a avatar colides with another avatar, or object.
Magine: no...i wish it were possible :(
Magine: but the world server has no notion of collision detection
Magine: it's all done within the awb
Magine: i think they might add that to the SDK someday
Anubis GK: Awwwwww *Laughs* No wonder why I can't get that to work.
Magine: heheh
ZebLith: it's a suggestion we should push anyways. :)
Magine: [to do collision detection with a bot program] you'd have to have the bot do a survey and determine the bounding box for every object etc, the same as the awb does internally
Magine: so i guess it could be done, but it would be a big project
Anubis GK: Eeesh, sounds laggy?
ZebLith: that it does >_<
Magine: it could be laggy doing it for a lot of people
Magine: the awb only has to do it for one person after all :)
[also i suspect that the AWB does collision detection using the renderware package]
Anubis GK: lol, no kidding. ^_^
Anubis GK: Well, I suppose I'll find an alternate route to my problem then!
Magine: it might not be too laggy if i built it into magsbot instead of doing it as a script
Magine: but i have no plans to do that right now....huge project

Seraph arrived and we continued to discuss bots' ability to detect collision.

Anubis GK: Hmmm, and so my original problem of how to get the bot to recognize when someone passes a finish line remains...hmmm.
...
Magine: anubis, you could determine when someone passes a line in other ways that collision detection
...
Magine: there's a system in magsbot for detecting if a person is within an area [using the SetArea buttons]
Magine: to detect if an avatar is within an area, you can define the area using the SetAreaN,S,W,E buttons on the . tab
Anubis GK: Hmmm, aye!
Magine: then in your program, use the @safe_ function
Magine: like IF @safe_["MyArea",@avsession]

We were also playing around with having the bots detect object and avatar clicks and object selection. For that you would simply use the AVATARCLICK, OBJECTCLICK or OBJECTSELECT events. Within the action for AVATARCLICK you would use $atr[clicked_name] or @atr[clicked_session] for the name or session of the avatar being clicked.

And we talked about getting bots to follow you:

Anubis GK: Because I wish to one day have pets that follow around in Morroc.
Magine: well there is a follow command in the standard table
Magine: mags follow
[Mags]: After you...
Magine: mags stay
[Mags]: Okay, staying put.
Magine: so you can look at the code to figure out how to make yours follow
Magine: oh there's also a follow button on the movement tab, even simpler example



I hope you've all found these classes useful. We may continue these sessions as a bot workshop...email me if you're interested.