Chapter 7

From SphereWiki
Revision as of 23:10, 1 June 2009 by MrSugarCube (talk | contribs)
Jump to: navigation, search

(WIP)

Recursive Functions

I discovered this very seldom explored extension of SPHERE scripting while reading messages on the boards. Someone was trying to create a function that counted the number of items in a container using this sort of thing, and it worked for the most part. I was very amazed, because before that, no one had even thought of using functions that looped back upon themselves.


Which is what a recursive function is. I'll say it one more time.


A recursive function is one that calls itself, or recurs. Surprise!


So how do we do this? It's as simple as calling a function:


[FUNCTION recursive_test]
recursive_test


This very small piece of code in fact IS a recursive function. As you can see, the function will call itself and start over from the beginning, which will proceed to call the function again, and again, and again, and on and on. In this case, we don't have any way to stop it. This is called an infinite loop, one that will continue forever without stopping. Your server will die a flaming death.


Lesson 1: How to NOT create an infinite loop

Let me tell you right now. You will write a script that implements an infinite loop. You will test it. Your server will die. It's guaranteed. No programmer can say that they have never accidentally written an infinite loop. (RANDOM NOTE: All windows programs are in fact infinite loops. Your SPHERE server is an infinite loop.) In a SPHERE script, however, here's what happens:

  1. The function is called.
  2. Some stuff takes place
  3. The function is called from within itself. Being a good scripting language, it records where it left off so it can go back later. This is called the stack.
  4. Go back to number 1.

This "stack" builds up very very quickly, and soon the server cannot allocate any more memory for it, and will crash when it tries. Fun stuff, I tell you.


Anyways, here is one way to avoid creating an infinite loop. Let's say we want to make a function that executes SRC.SYSMESSAGE Hello World 35 times. Here would be an example of how one could do this:


[FUNCTION recurse_hello]
IF (<ARGN1> < 1)
    RETURN 1
ENDIF
SYSMESSAGE Hello World
RECURSE_HELLO <EVAL <ARGN1> - 1>
RETURN 1


Then, in another script, we would execute this command: SRC.RECURSE_HELLO 35


Remember what ARGN is from the previous chapter? It's the argument to the function stored as a number. Initially, as you can see, it's 25 because we made it be that way. However, every time the function calls itself, or "recurses", it sets ARGN1 to be one less than itself. Here's the step-by-step analysis of this:

  1. The function is called. ARGN1 is 35 because we said so.
  2. It checks to see if ARGN1 is less than one. If it is, we immediately RETURN 1 and set off the chain reaction that stops the recursive function.
  3. The next part should be fairly obvious. We're sending a SYSMESSAGE to the default object. Because we used SRC when we initially called the function, the default object is SRC.
  4. This is where the recursion takes place. The function calls itself with an argument ONE LESS than the current one. This starts the whole thing over at step 1. This is a NEW FUNCTION CALL, remember. The original function call STILL EXISTS and the program will "rewind" back down the stack to that location later. That is why I have a RETURN 1 after the function call.


That's your example of a recursive function. It isn't very practical. Let's look at a more practical example. See if you can figure it out for yourself. This is courtesy of Belgar, for the most part:


[FUNCTION pack_to_bank]
IF (<FINDLAYER.21.FINDCONT.0.UID> == 0)
    RETURN 1
ENDIF
FINDLAYER.21.FINDCONT.0.CONT = <FINDLAYER.layer_bankbox.UID>
PACK_TO_BANK
RETURN 1


(As you can see, we don't always need an ARGS to make a function loop. In this case, we use a backpack with an unknown number of items inside and only stop when the pack no longer contains items.)


Recursive functions are very useful. Be sure you don't overuse them, though! Remember, while a script is running, YOUR SERVER IS FROZEN. If a recursive function takes too long to complete, your server will lag. A good method is to make sure that no function should be looping more than about 500 times. (Actually other server emulators such as POL have a mechanism to catch "runaway scripts" like this and halt them in their tracks.)


FOR

FOR

FOR is a powerful way to create a recursive function, and it allows a simpler level of control over your recursions.

Usage:

[FUNCTION for_display]
FOR X 1 20
    SYSMESSAGE <LOCAL.X> //Will sysmessage the current for count.
ENDFOR


The loop will loop through 20 times, starting at 1 and ending at 20. X is the variable containing the current FOR count. If no variable is declared, the count can be accessed using <LOCAL._FOR>


Changing LOCAL._FOR or whatever you declared as count, will not change the loop's behaviour. But be aware that if you "stack" FOR loops without giving them different loop variables, the innermost loop will overwrite the loop variables of its successors, usually leading towards a completely messup of the whole loop stuff.


FORCHARLAYER

FORCHARLAYER is another type of FOR loop. Basically it allows you to loop through each item that is stored on the specified layer of a character. This can be useful for when you want to manipulate all of the spell runes or memory items on a character as an alternative to using FINDLAYER.x in a loop.


Something to be aware of here is that whilst inside the loop, the default object will be temporarily changed to the item in the loop. As you can see in the following example, we must store a reference to the original default object (the character) so that we can still reference it from within the loop:


[FUNCTION get_mitems_names]
REF1 = <UID> // store the default object in REF1
FORCHARLAYER 30
    REF1.SYSMESSAGE <NAME> is a memory item in layer <LAYER>
ENDFOR


FORCHARMEMORYTYPE

FORCHARMEMORYTYPE is a very useful type of FOR loop. You may want to use it for experience systems, and some player and NPC killing systems. It loops through every memory item on a character that has a specified flag.


For example, a character has 4 memory items with the following names and flags:

Ellessar 02000
Sorea 022bc
Introvert 0740d
Enrath 0c40d

Script:

[FUNCTION get_war_targ_mems]
REF1 = <UID> // As with FORCHARLAYER, the default object changes within the loop
FORCHARMEMORYTYPE memory_war_targ // Loop through memory items with flag 02000=memory_war_targ
    REF1.SYSMESSAGE There is a memory item with name <NAME>, uid <UID>, flags <COLOR> and one of its flag is also 02000.
ENDFOR


Result:
On your screen you would see:

There is a memory item with name Ellessar, uid 04f000001, flags 02000 and one of its flag is also 02000.
There is a memory item with name Sorea, uid 04f000002, flags 022bc and one of its flag is also 02000.
There is a memory item with name Introvert, uid 04f000003, flags 0740d and one of its flag is also 02000.
</tt>


Note that there are only three messages, because the memory item "Enrath" does not have the flag 02000.


FORCHARS

FORCHARS is a FOR loop that you can use to check all mobiles (player and NPC) within a set radius of an object.


The correct syntax being FORCHARS x where x is the radius in tiles the loop will cover.

  • FORCHARS 2 would check any mobile within a 2 tile radius
  • FORCHARS 18 would check the area inside your screen
  • FORCHARS 6144 would check the entire world map


One example of a function using FORCHARS

[FUNCTION kill_vendors]
FORCHARS 6144 // checks entire map
    IF (<BRAIN> == brain_vendor) //argument for what will be acted upon within this function
        KILL // action
    ENDIF
ENDFOR


As with all FOR loops you have to stipulate inside the loop what it is to act upon, if you are restricting it to certian players/npcs (or in the case of FORITEMS, items) otherwise it will perform the action upon all players/npcs within the radius of the loop.


In this case the loop checks for any Vendor npc's and kills them.


FORCLIENTS, FORPLAYERS

FORCLIENTS and FORPLAYERS are FOR loops, both are used to affect a clients/players in certain radius. If you do not set the radius, radius 18 is used as default. While FORCLIENTS only acts on player characters who are logged in, FORPLAYERS acts on each and every player character, even if logged off.


Usage:

[FUNCTION radius_players]
FORCLIENTS 25
    IF (<ACCOUNT.PLEVEL> <= 1) // Affects only logged in players, not staff
        SAY I am here!
    ENDIF
ENDFOR


FORCONT

FORCONT is a type of FOR loop. It loops through every item in a container. The default object inside the loop will be the item currently being looped over.


Usage:

[FUNCTION rem_spellbooks]
FORCONT <FINDLAYER.21.UID> 10 // <FINDLAYER.21.UID> - UID of a container, 10 - how many subcontainers the function goes through, if set 0, it affects only items in container with UID
    IF (<BASEID> == i_spellbook)
        REMOVE
    ENDIF
ENDFOR


FORCONTID

FORCONTID is a FOR loop that works in a similar way to FORCONT, except that it will only cycle through items that have a specific BASEID. You can set the amount of subcontainers to loop through, like the FORCONT example.


Usage:

[FUNCTION rem_spellbooks2]
FORCONTID i_spellbook 10
    REMOVE
ENDFOR


FORCONTTYPE

This is another FOR loop that is almost identical to FORCONTYPE. The only difference is that instead of looping through items with a specific BASEID, it will loop through items with a specific TYPE. Following the spellbooks remover example:


Usage:

[FUNCTION rem_spellbook3]
FORCONTTYPE t_spellbook
    REMOVE
ENDFOR


FORITEMS

FORITEMS works in much the same way that FORCHARS does, except it checks for ITEMS within the set radius as opposed to characters. Default obejct is set to the item which can be affected.


A basic example of a function using FORITEMS:


[FUNCTION Spawn_remover]
FORITEMS 6144 //once again it checks the entire map
    IF (<TYPE> == t_spawn_char) //if this arguement is met
        REMOVE //remove it
    ENDIF //end the IF arguement
ENDFOR //end the FOR loop


FOROBJS

FOROBJS works in the same way that FORITEMS and FORCHARS does with the exception that this loop will find both characters AND items within the specified radius.