Difference between revisions of "Chapter 9"

From SphereWiki
Jump to: navigation, search
Line 179: Line 179:
  
 
Here's a little secret I bet you've already guessed: ALL ITEM EVENTS (except @Timer) HAVE AN @item COUNTERPART. This means that @itemPickUp_Pack, @itemTargOn_Char, @itemTargOn_Item, and all others should all (in theory) work.
 
Here's a little secret I bet you've already guessed: ALL ITEM EVENTS (except @Timer) HAVE AN @item COUNTERPART. This means that @itemPickUp_Pack, @itemTargOn_Char, @itemTargOn_Item, and all others should all (in theory) work.
 +
 +
 +
==Environmental Events==
 +
This chapter serves to explain only one event, because we need to introduce some concepts beforehand. The first concept we're going to introduce is that of the '''SECTOR'''. In the game, the map is divided not only into regions, but into sectors as well. Sectors represent portions of the map that by default are 64 tiles wide and 64 tiles high. They can overlap between regions (for example, one sector is the upper corner of Britain and most of the Britain graveyard). They hold information such as light level, weather, and complexity information.
 +
 +
 +
Also, the world is divided vertically into bands, which we shall call LOCAL areas here. This is used for the purpose of realistic fading of light during sunsets and a real "revolving" world effect.
 +
 +
 +
How do we manipulate these sectors? Well, there is a SECTOR object that is available to you in any script, and it refers to the sector in which the specific object sits. This SECTOR object has a VERY limited number of variables and functions:
 +
 +
 +
'''COMPLEXITY'''<br />
 +
Returns the number of characters (both PC and NPC) in the sector. This is useful if you have a huge effect-based script and you don't want to lag up everyone in a sector. It's also useful so NPCs don't spam huge portions of speech in crowded areas. If you look in the speech files provided with the game, you'll see this in use.
 +
 +
 +
'''SEASON &lt;integer&gt;'''<br />
 +
One of the most interesting features of the SECTOR, this controls which "seasonal" graphics the client displays. The values you can pass to this function are:
 +
 +
 +
{| border="1"
 +
| '''Value''' || '''Description'''
 +
|-
 +
| 0 || Spring time season (lots of flowers)
 +
|-
 +
| 1 || Summer time season (this is the default)
 +
|-
 +
| 2 || Fall season (all trees have "fall" leaves and there are a lot of mushrooms)
 +
|-
 +
| 3 || Winter season (the ground is covered in snow and the trees have no leaves)
 +
|-
 +
| 4 || Dead season (no leaves on trees, gravestones and gore everywhere)
 +
|}
 +
 +
 +
'''LIGHT &lt;level&gt;'''<br />
 +
Holds a value between 0 and 30 that represents the light level, with 0 being full daylight and 30 being pitch black. On pre-T2A clients, 18 is the darkest LIGHT value. If you give it a value, it will set the light to that level. If not, it will set the light level to whatever its default value happens to be at that time. (See LOCALLIGHT.)
 +
 +
 +
'''LOW'''<br />
 +
Actually used - COMPLEXITY.LOW. Returns 1 if the complexity is within the "low" level.
 +
 +
 +
'''MEDIUM'''<br />
 +
Actually used - COMPLEXITY.MEDIUM. Returns 1 if the complexity is within the "medium" level.
 +
 +
 +
'''HIGH'''<br />
 +
Actually used - COMPLEXITY.HIGH. Returns 1 if the complexity is within the "high" level.
 +
 +
 +
'''ALLCLIENTS &lt;function call&gt;'''<br />
 +
This can be used just like SERV.ALLCLIENTS or REGION.ALLCLIENTS. Any function given here will be executed on any player in the SECTOR. This is not a wise one to use, because the player could in fact be in a different sector than you expect.
 +
 +
 +
'''RAIN'''<br />
 +
This makes it rain for all players in the sector.
 +
 +
 +
'''SNOW'''<br />
 +
Makes it snow for all players in the sector. (Doing SECTOR.RAIN, then SECTOR.SNOW is interesting. You get this weird rain/snow mix.)
 +
 +
 +
'''DRY'''<br />
 +
Turns off both rain and snow.
 +
 +
 +
'''RESTOCK'''<br />
 +
Causes all spawnpoints to double-click themselves and reset any spawns in the area. Useful if you just changed the loot on something.
 +
 +
 +
'''ISDARK'''<br />
 +
Tests the light level of the sector to see if it is below a "dark" threshold.
 +
 +
 +
'''ISNIGHTTIME'''<br />
 +
There is a clock that is kept in LOCAL areas, like timezones in real life. This checks the clock to see if it is "night" in the area. It does not necessarily have to be dark.
 +
 +
 +
'''LOCALTOD'''<br />
 +
Returns the LOCAL time of day for this "time zone".
 +
 +
 +
'''LOCALTIME'''<br />
 +
Returns the LOCAL time in a format like this: "quarter past seven in the evening"
 +
 +
 +
'''LOCALLIGHT'''<br />
 +
The light level in unaltered sectors in this LOCAL area. Used by LIGHT when you call that function without parameters.
 +
 +
 +
 +
Now, back to what we were originally discussing.
 +
 +
 +
The event associated with the SECTOR is '''@EnvironChange'''. This is a surprisingly useful event because it fires every time ANY of those sector variables are changed. That includes LOCALTIME, meaning that @EnvironChange will fire approximately once every ten seconds. (It depends on the speed of the game clock on your server.)
 +
 +
 +
This makes @EnvironChange the only repeating event for characters. (Actually there is another, but you haven't learned about REGION functions yet.) This may not seem very useful, but let's take a look at a script from the sphere_events_human.scp file.
 +
 +
 +
<spherescript>[EVENTS e_Human_Environ]
 +
ON=@EnvironChange // 1
 +
IF (<FLAGS> & statf_war) // 2
 +
RETURN 0
 +
ENDIF
 +
IF !(<SECTOR.ISDARK>) || (<FLAGS> & statf_nightsight) // 3
 +
IF (<FINDLAYER.layer_hand2>) // 4
 +
IF (<FINDLAYER.layer_hand2.TYPE> == t_light_lit)
 +
FINDLAYER.layer_hand2.BOUNCE
 +
ENDIF
 +
ENDIF
 +
RETURN 0
 +
ENDIF
 +
// already have a lit light ? (5)
 +
IF (<FINDLAYER.layer_hand2>) // 6
 +
IF (<FINDLAYER.layer_hand2.TYPE> == t_light_lit)
 +
RETURN 0
 +
ENDIF
 +
ENDIF
 +
// type to use a torch or light source if I have one. (and it's dark) (7)
 +
IF (<FINDTYPE.t_light_out>) // 8
 +
FINDTYPE.t_light_out.EQUIP
 +
FINDTYPE.t_light_out.USE
 +
ENDIF
 +
RETURN 0</spherescript>
 +
 +
 +
Well, this is different. This is our first rather complicated example script, so let's take it slowly. First of all, you can see it's in an [EVENTS] section, with the defname of e_Human_Environ. (If you look through spherechar_human.scp, you'll see this event installed a lot on the NPCs.) This script makes the character equip a torch, if he has one, based on the light level. It's a cool event when it becomes night and all your NPCs are walking around town with torches to light their way. Basically, this script has the following steps:
 +
 +
# When the SECTOR changes, the event fires
 +
# We check to see if the character is in war mode by checking his FLAGS using the &amp; operator. If he is, we don't want him to do anything, so we RETURN from the script immediately.
 +
# Next, we check the character's sector to see if it's dark. In the same IF statement we check to see if the NPC has the Nightsight FLAGS set. If he does, he can't tell that it's dark anyway, and so it doesn't matter if he has a torch or not. According to the structure of our IF statement, it becomes true if SECTOR.ISDARK is NOT true (!) OR (||) <tt>&lt;FLAGS&gt; &amp; statf_nightsight</tt> evaluates to true.
 +
# If it is NOT dark outside, OR the character has nightsight on, we don't want him to have a torch equipped, so we check his hand (layer_hand2) for a torch. IF he has one, we use the BOUNCE function on the torch to place it in the character's backpack. The script then RETURNs.
 +
# The next section of the script is only reached if it is BOTH dark outside and the character does not have nightsight on.
 +
# It checks to see if there is already a light in the character's hand (layer_hand2). If there is, we don't need to equip another one so the script exits. You must always do a check like this in these kinds of scripts. Remember, @EnvironChange will take effect almost once every ten seconds. We don't want him equipping a new torch every ten seconds.
 +
# The only possible way to reach this point in the script is if the following conditions have been met: it is dark outside, the character does NOT have nightsight, and there is no torch equipped on the character.
 +
# The very last section checks to see if the character has any torches equipped in his backpack. Remember, FINDTYPE will return the first instance of an object of a given type (in this case t_light_out) in the character or any sub-containers (his backpack).
 +
 +
 +
So what's a REAL use for this event that doesn't involve torches? Well, if you look through sphereevents.scp, you'll find another [EVENTS] section called e_undead, which makes dead creatures very very weak when it's light outside, and their normal strength at night. It uses a TAG to temporarily store their normal strength.
 +
 +
 +
You'll see another use for the @EnvironChange event at the end of the chapter. I take advantage of the fact that it's a periodic event (that is, it repeats a lot).
 +
 +
 +
And a final piece of advise for you to think about:
 +
 +
''Be careful when calling functions that change the environment (i.e. changing season, weather, light), from within the @EnvironChange trigger''.
 +
 +
Remember that the @EnvironChange triggers fires when SECTOR properties change? What do you think might happen if you if used the following script?
 +
 +
 +
<spherescript>// don't run this!
 +
ON=@EnvironChange
 +
SECTOR.LIGHT += 1
 +
RETURN 0</spherescript>
 +
 +
 +
Do you see what is going to happen? The sector's light is going to be incremented by 1 when the trigger fires, but when this happens the trigger will fire again because the environment has been changed, and this will cause the light to be incremented again, which will cause the trigger to fire again, which will cause the light to be incremented again... Quite clearly, this is not a good thing and your server will crash or freeze within seconds of this script running. If you must insist on changing the environment under the @EnvironChange trigger then you must put in a conditional statement which will prevent the server from entering an infinite loop, for example:
 +
 +
<spherescript>
 +
ON=@EnvironChange
 +
IF (<SECTOR.LIGHT> != 5)
 +
SECTOR.LIGHT = 5
 +
ENDIF
 +
RETURN 0</spherescript>
 +
 +
 +
This will set the light level to 5, but only if it is not 5 already (Sphere is already intelligent enough to not fire the trigger again if you set the LIGHT to the same value it already had, but the idea should be clear)
 +
 +
 +
===Information for Advanced Scripters===
 +
There is a way to refer to all of the sectors within a region. It works a little like ALLCLIENTS, in that it will run a function on all sectors. This is a good way to change the season for an entire region. If a region is contained within another (i.e. Britain contained within Britain Territory), it will be affected if you use this command on the surrounding region.
 +
 +
<spherescript>REGION.SECTORS</spherescript>
 +
 +
For example:
 +
 +
<spherescript>REGION.SECTORS SEASON 3</spherescript>
 +
 +
This will set all sectors in the region to the "winter" season. Keep in mind that sectors can "overlap" regions, meaning that you may get some winter in other surrounding regions as well.

Revision as of 15:29, 2 June 2009

(WIP)

How to "install" [EVENTS]

A basic event looks like the following:

[EVENTS defname]
code


Looks just like every other thing we've done so far doesn't it? Well events work the same way. They can be installed either in-game our through a script using the following:


EVENTS +defname
EVENTS=defname
EVENTS -defname


These are three very different commands. Some people tend to confuse them. Here is what they do:


EVENTS +DEFNAME
Adds the event to the character's list of events. Yes, a character can have more than one event. Both of the following will respond to triggers called on the character:


SRC.EVENTS +e_death_event
SRC.EVENTS +e_ctf_event


EVENTS=DEFNAME
This method is should not be used. It will erase all other events on the character in place of this one.


EVENTS -DEFNAME
This will remove the specified event from the character.


THE DEFAULT OBJECT OF AN [EVENTS] SCRIPT IS THE CHARACTER ON WHICH THE SCRIPT IS INSTALLED.


The order of event handling

There are quite a few ways to add events to items or characters. These are:


EVENTS +DEFNAME
Can be set by a function, another event, a player action, or inside a trigger (for example, the @Create trigger).


TEVENTS=DEFNAME
In the body of the ITEMDEF or CHARDEF. There may be as many of TEVENTS lines as you wish.


TYPE=DEFNAME
(Items only) In the body of the ITEMDEF. There can only be ONE base type definition. TYPEs are not only events, they also determine some basic behaviour of the item, and it's capabilities (for example, if an item is not of an equippable type, it cannot be equipped).


DIRECT TRIGGERS
Poor man's events: As events are generally assortments of triggers what can be added to an item or character. You may also "hardcode" some trigger actions in the ITEMDEF/CHARDEF itself.


ORDER OF FIRING
Look at these simple code examples:


[ITEMDEF i_triggertester]
ID=i_floor_wood
TYPE = t_testtype
NAME=TriggerTester
TEVENTS=e_tev_test

ON=@Create
    EVENTS +e_ev_test
    
ON=@DClick
    SERV.LOG item dclick from direct trigger
    
[TYPEDEF e_ev_test]
ON=@DClick
    SERV.LOG dclick from event
    
[TYPEDEF e_tev_test]
ON=@DClick
    SERV.LOG item dclick from tevent
    
[TYPEDEF t_testtype]
ON=@DClick
    SERV.LOG item dclick from base typedef


This gives the following results:

item dclick from event
item dclick from tevent
item dclick from base typedef
item dclick from direct trigger


The same for characters:


[CHARDEF c_testorc]
ID=c_orc
NAME=TestOrc
TEVENTS=e_tev_character

ON=@Create
    STR = 100
    
ON=@NPCRestock
    EVENTS +e_ev_character
    
ON=@DClick
    SERV.LOG char dclick from direct trigger
    
[EVENTS e_tev_character]
ON=@DClick
    SERV.LOG char dclick from tevent
    
[EVENTS e_ev_character]
ON=@DClick
    SERV.LOG char dclick from event


The results:

char dclick from event
char dclick from tevent
char dclick from direct trigger


Item-based events

There are two main types of events:


ITEM EVENT
Triggered when any player interacts in a certain way with a given item.


PLAYER EVENT
Triggered when a given player interacts in a certain way with ANY item or performs certain tasks, such as moving, casting a spell, crafting an item, etc.


In the item script, the item is the default object. In the player script, the player is the default object.


So what are these events? Well here's a short list of some of them:


@itemDClick - Triggered when the player double-clicks any item. The reference object for the item is ACT.
@itemEquip - Triggered when the player equips any item. The reference object for the item is ACT.
@itemUnEquip - Triggered when the player unequips any item. The reference object for the item is ACT.
@itemClick - Triggered when the player double-clicks any item. The reference object for the item is ACT.
@itemStep - Triggered when the player steps on any item. The reference object for the item is ACT.


Notice anything similar between those descriptions? If you don't, I think you need to go back and take reading lessons. That's right. In ANY script beginning with @item, ACT is the item acted upon. How about that. Why do you suppose they called it ACT?


In any case, for @item scripts, the following is true:


ACT = the item acted upon
SRC = the character doing the acting (the player with the event)
[] = the character doing the acting (the player with the event)


Notice that in this case, SRC and the default object both refer to the character. THIS IS NOT ALWAYS THE CASE. If you want to refer to the character, get in the habit of using the default object rather than SRC in an event. Now, let's take a look at a rather useless event:


[EVENTS e_test_events]
ON=@itemStep
    SYSMESSAGE You have stepped on <ACT.NAME>!
    RETURN 0


Put this script in one of your files and resync your server. Now, go in-game and type: .xevents +e_test_events Target yourself. Congratulations, you just installed an event on yourself. From this point forward, until you remove the event, any time you walk on an item, you'll get an irritating message. Try it. Go around walking on things! Make an item called "a bug" and walk around stepping on them.


Ok stop walking on things and continue reading now. :)


Here's a little secret I bet you've already guessed: ALL ITEM EVENTS (except @Timer) HAVE AN @item COUNTERPART. This means that @itemPickUp_Pack, @itemTargOn_Char, @itemTargOn_Item, and all others should all (in theory) work.


Environmental Events

This chapter serves to explain only one event, because we need to introduce some concepts beforehand. The first concept we're going to introduce is that of the SECTOR. In the game, the map is divided not only into regions, but into sectors as well. Sectors represent portions of the map that by default are 64 tiles wide and 64 tiles high. They can overlap between regions (for example, one sector is the upper corner of Britain and most of the Britain graveyard). They hold information such as light level, weather, and complexity information.


Also, the world is divided vertically into bands, which we shall call LOCAL areas here. This is used for the purpose of realistic fading of light during sunsets and a real "revolving" world effect.


How do we manipulate these sectors? Well, there is a SECTOR object that is available to you in any script, and it refers to the sector in which the specific object sits. This SECTOR object has a VERY limited number of variables and functions:


COMPLEXITY
Returns the number of characters (both PC and NPC) in the sector. This is useful if you have a huge effect-based script and you don't want to lag up everyone in a sector. It's also useful so NPCs don't spam huge portions of speech in crowded areas. If you look in the speech files provided with the game, you'll see this in use.


SEASON <integer>
One of the most interesting features of the SECTOR, this controls which "seasonal" graphics the client displays. The values you can pass to this function are:


Value Description
0 Spring time season (lots of flowers)
1 Summer time season (this is the default)
2 Fall season (all trees have "fall" leaves and there are a lot of mushrooms)
3 Winter season (the ground is covered in snow and the trees have no leaves)
4 Dead season (no leaves on trees, gravestones and gore everywhere)


LIGHT <level>
Holds a value between 0 and 30 that represents the light level, with 0 being full daylight and 30 being pitch black. On pre-T2A clients, 18 is the darkest LIGHT value. If you give it a value, it will set the light to that level. If not, it will set the light level to whatever its default value happens to be at that time. (See LOCALLIGHT.)


LOW
Actually used - COMPLEXITY.LOW. Returns 1 if the complexity is within the "low" level.


MEDIUM
Actually used - COMPLEXITY.MEDIUM. Returns 1 if the complexity is within the "medium" level.


HIGH
Actually used - COMPLEXITY.HIGH. Returns 1 if the complexity is within the "high" level.


ALLCLIENTS <function call>
This can be used just like SERV.ALLCLIENTS or REGION.ALLCLIENTS. Any function given here will be executed on any player in the SECTOR. This is not a wise one to use, because the player could in fact be in a different sector than you expect.


RAIN
This makes it rain for all players in the sector.


SNOW
Makes it snow for all players in the sector. (Doing SECTOR.RAIN, then SECTOR.SNOW is interesting. You get this weird rain/snow mix.)


DRY
Turns off both rain and snow.


RESTOCK
Causes all spawnpoints to double-click themselves and reset any spawns in the area. Useful if you just changed the loot on something.


ISDARK
Tests the light level of the sector to see if it is below a "dark" threshold.


ISNIGHTTIME
There is a clock that is kept in LOCAL areas, like timezones in real life. This checks the clock to see if it is "night" in the area. It does not necessarily have to be dark.


LOCALTOD
Returns the LOCAL time of day for this "time zone".


LOCALTIME
Returns the LOCAL time in a format like this: "quarter past seven in the evening"


LOCALLIGHT
The light level in unaltered sectors in this LOCAL area. Used by LIGHT when you call that function without parameters.


Now, back to what we were originally discussing.


The event associated with the SECTOR is @EnvironChange. This is a surprisingly useful event because it fires every time ANY of those sector variables are changed. That includes LOCALTIME, meaning that @EnvironChange will fire approximately once every ten seconds. (It depends on the speed of the game clock on your server.)


This makes @EnvironChange the only repeating event for characters. (Actually there is another, but you haven't learned about REGION functions yet.) This may not seem very useful, but let's take a look at a script from the sphere_events_human.scp file.


[EVENTS e_Human_Environ]
ON=@EnvironChange	// 1
    IF (<FLAGS> & statf_war)	// 2
        RETURN 0
    ENDIF
    IF !(<SECTOR.ISDARK>) || (<FLAGS> & statf_nightsight)	// 3
        IF (<FINDLAYER.layer_hand2>)	// 4
            IF (<FINDLAYER.layer_hand2.TYPE> == t_light_lit)
                FINDLAYER.layer_hand2.BOUNCE
            ENDIF
        ENDIF
        RETURN 0
    ENDIF
    // already have a lit light ? (5)
    IF (<FINDLAYER.layer_hand2>)	// 6
        IF (<FINDLAYER.layer_hand2.TYPE> == t_light_lit)
            RETURN 0
        ENDIF
    ENDIF
    // type to use a torch or light source if I have one. (and it's dark) (7)
    IF (<FINDTYPE.t_light_out>)	// 8
        FINDTYPE.t_light_out.EQUIP
        FINDTYPE.t_light_out.USE
    ENDIF
    RETURN 0


Well, this is different. This is our first rather complicated example script, so let's take it slowly. First of all, you can see it's in an [EVENTS] section, with the defname of e_Human_Environ. (If you look through spherechar_human.scp, you'll see this event installed a lot on the NPCs.) This script makes the character equip a torch, if he has one, based on the light level. It's a cool event when it becomes night and all your NPCs are walking around town with torches to light their way. Basically, this script has the following steps:

  1. When the SECTOR changes, the event fires
  2. We check to see if the character is in war mode by checking his FLAGS using the & operator. If he is, we don't want him to do anything, so we RETURN from the script immediately.
  3. Next, we check the character's sector to see if it's dark. In the same IF statement we check to see if the NPC has the Nightsight FLAGS set. If he does, he can't tell that it's dark anyway, and so it doesn't matter if he has a torch or not. According to the structure of our IF statement, it becomes true if SECTOR.ISDARK is NOT true (!) OR (||) <FLAGS> & statf_nightsight evaluates to true.
  4. If it is NOT dark outside, OR the character has nightsight on, we don't want him to have a torch equipped, so we check his hand (layer_hand2) for a torch. IF he has one, we use the BOUNCE function on the torch to place it in the character's backpack. The script then RETURNs.
  5. The next section of the script is only reached if it is BOTH dark outside and the character does not have nightsight on.
  6. It checks to see if there is already a light in the character's hand (layer_hand2). If there is, we don't need to equip another one so the script exits. You must always do a check like this in these kinds of scripts. Remember, @EnvironChange will take effect almost once every ten seconds. We don't want him equipping a new torch every ten seconds.
  7. The only possible way to reach this point in the script is if the following conditions have been met: it is dark outside, the character does NOT have nightsight, and there is no torch equipped on the character.
  8. The very last section checks to see if the character has any torches equipped in his backpack. Remember, FINDTYPE will return the first instance of an object of a given type (in this case t_light_out) in the character or any sub-containers (his backpack).


So what's a REAL use for this event that doesn't involve torches? Well, if you look through sphereevents.scp, you'll find another [EVENTS] section called e_undead, which makes dead creatures very very weak when it's light outside, and their normal strength at night. It uses a TAG to temporarily store their normal strength.


You'll see another use for the @EnvironChange event at the end of the chapter. I take advantage of the fact that it's a periodic event (that is, it repeats a lot).


And a final piece of advise for you to think about:

Be careful when calling functions that change the environment (i.e. changing season, weather, light), from within the @EnvironChange trigger.

Remember that the @EnvironChange triggers fires when SECTOR properties change? What do you think might happen if you if used the following script?


// don't run this!
ON=@EnvironChange
    SECTOR.LIGHT += 1
    RETURN 0


Do you see what is going to happen? The sector's light is going to be incremented by 1 when the trigger fires, but when this happens the trigger will fire again because the environment has been changed, and this will cause the light to be incremented again, which will cause the trigger to fire again, which will cause the light to be incremented again... Quite clearly, this is not a good thing and your server will crash or freeze within seconds of this script running. If you must insist on changing the environment under the @EnvironChange trigger then you must put in a conditional statement which will prevent the server from entering an infinite loop, for example:

ON=@EnvironChange
    IF (<SECTOR.LIGHT> != 5)
        SECTOR.LIGHT = 5
    ENDIF
    RETURN 0


This will set the light level to 5, but only if it is not 5 already (Sphere is already intelligent enough to not fire the trigger again if you set the LIGHT to the same value it already had, but the idea should be clear)


Information for Advanced Scripters

There is a way to refer to all of the sectors within a region. It works a little like ALLCLIENTS, in that it will run a function on all sectors. This is a good way to change the season for an entire region. If a region is contained within another (i.e. Britain contained within Britain Territory), it will be affected if you use this command on the surrounding region.

REGION.SECTORS

For example:

REGION.SECTORS SEASON 3

This will set all sectors in the region to the "winter" season. Keep in mind that sectors can "overlap" regions, meaning that you may get some winter in other surrounding regions as well.