Difference between revisions of "Writing Modules In Python"

From ASSS Wiki
Jump to: navigation, search
Line 1: Line 1:
== Basic python module ==
+
apple cows
I have tried to comment what is going on in the source. This module demonstrates callbacks, commands and using interfaces.
 
<pre>
 
# demo asss python module
 
# dec 28 2004 smong
 
 
 
# nearly always use this
 
from asss import *
 
 
 
 
 
# get some interfaces
 
# see chat.h for where I_CHAT comes from, see other .h files for more (fx:
 
#  game.h)
 
chat = get_interface(I_CHAT)
 
 
 
 
 
# a callback
 
# this function is called when a player enters/leaves, see core.h for PA_???
 
#  constants
 
def paction(p, action, arena):
 
    # start indenting
 
    if action == PA_ENTERARENA:
 
        # see chat.h for the names of more functions like SendMessage
 
        chat.SendMessage(p, "hello " + p.name)
 
 
 
# tell asss to call 'paction' when CB_PLAYERACTION is signalled
 
# see .h files for CB_??? names
 
cb1 = reg_callback(CB_PLAYERACTION, paction)
 
 
 
 
 
# a command
 
# see cmdman.h for what each parameter does
 
def c_moo(cmd, params, p, targ):
 
# help text (?help moo)
 
    """\
 
Module: <py> demo
 
Targets: none
 
a sample command.
 
"""
 
    chat.SendMessage(p, "moo cows")
 
 
 
# tell asss to call 'c_moo' when a player types ?moo
 
# note: add cmd_moo to conf/groupdef.dir/default so players have permission to
 
#  use this command.
 
cmd1 = add_command("moo", c_moo)
 
 
 
# setting chat (or other interfaces), cb* or cmd* to None is equivalent to
 
#  unregistering that item.
 
</pre>
 
Save this in bin/demo.py. Then ingame make sure pymod is loaded by using ?lsmod and ?insmod. Then add this module with the following command: ?insmod <py> demo. Re-entering the arena and typing ?moo should do some stuff.
 
 
 
== Code snippets ==
 
The bread and butter of most custom modules.
 
=== Callbacks ===
 
<pre>
 
from asss import *
 
 
 
chat = get_interface(I_CHAT)
 
 
 
def goal(arena, p, bid, x, y):
 
    chat.SendArenaMessage(arena, "goal.")
 
 
 
cb1 = reg_callback(CB_GOAL, goal)
 
</pre>
 
 
 
=== Commands ===
 
Useful for controlling events, fx: ?elim start. In this case the command is ?t1.
 
<pre>
 
from asss import *
 
 
 
chat = get_interface(I_CHAT)
 
 
 
def c_mycmd(cmd, params, p, targ):
 
    """\
 
some help text
 
"""
 
    chat.SendMessage(p, "moo.")
 
 
 
cmd1 = add_command("t1", c_mycmd)
 
#where the "t1" is located would be the name of the command, ex: ?t1
 
</pre>
 
 
 
You can parse integers from the params using the following code:
 
<pre>
 
try:
 
    val = int(params)
 
except ValueError:
 
    # here the conversion of params from a string to an int failed.
 
    # change the next line to 'pass' to silently ignore the conversion error,
 
    # 'return' to exit the function, or 'val = 0' to set a default value to val.
 
    chat.SendMessage(p, "Integer parameter required.")
 
</pre>
 
 
 
=== Per-player/arena data ===
 
Use this to store game state, player score, etc.
 
<pre>
 
def shipchange(p, newship, newfreq):
 
    # prefix mymod_ an abbreviation of your module name to the variable
 
    #  so that it doesn't clash with other modules. per arena data works
 
    #  in exactly the same way.
 
    p.mymod_lastship = p.ship
 
 
 
cb1 = reg_callback(CB_SHIPCHANGE, shipchange)
 
</pre>
 
 
 
=== Persistent per-player data ===
 
ASSS has built in mechanisms for saving data per player and per arena across sessions (users logging off or server restarting).
 
 
 
You need to provide three functions that will be called by ASSS to save, restore and clear the data. The example below is a complete module.
 
<pre>
 
# each player can save a note that only they can see
 
 
 
from asss import *
 
 
 
chat = get_interface(I_CHAT)
 
 
 
# show and store a note
 
def c_note(cmd, params, p, targ):
 
    if params:
 
        p.note = params
 
    if p.note:
 
        chat.SendMessage(p, "note: %s" % p.note)
 
    else:
 
        chat.SendMessage(p, "no note set")
 
 
 
cmd1 = add_command("note", c_note)
 
 
 
# return the data to save for player p
 
# returning None means "don't store a record in the database,
 
# and delete any record that's there already".
 
def getpd(p):
 
    return p.note
 
 
 
# restore the data for player p
 
def setpd(p, d):
 
    p.note = d
 
 
 
# reset/clear the data for player p
 
def clearpd(p):
 
    p.note = None
 
 
 
mypd = reg_player_persistent(
 
7890, INTERVAL_FOREVER, PERSIST_GLOBAL,
 
getpd, setpd, clearpd)
 
</pre>
 
Ideally you should load the module when the server starts. Alternatively if you load it dynamically you can kick everyone (not desirable) or alter the code to catch AttributeError when it attempts to read from the per player data ''.note''.
 
 
 
Taking a look at ''reg_player_persistent'' the number ''7890'' is arbitrary, but must be unique for every module using persistent data. Low numbers are reserved for the core modules. ''INTERVAL_FOREVER'' is how long this data will be kept for. Interestingly the INTERVAL_* constants are defined in statcodes.h, not persist.h. ''PERSIST_GLOBAL'' is defined in persist.h. Here are the respective code extracts (as of Feb 7 2005).
 
<pre>
 
/* these are the possible intervals */
 
enum interval_t
 
{
 
/* pyconst: enum, "INTERVAL_*" */
 
 
 
/* these are shared between arenas with the same arenagrp */
 
INTERVAL_FOREVER = 0,
 
INTERVAL_RESET,
 
INTERVAL_MAPROTATION,
 
/* these are not shared between arenas */
 
INTERVAL_GAME = 5,
 
INTERVAL_FOREVER_NONSHARED
 
};
 
</pre>
 
<pre>
 
typedef enum persist_scope_t
 
{
 
/* pyconst: enum, "PERSIST_*" */
 
 
 
PERSIST_ALLARENAS,
 
/* using this for scope means per-player data in every arena */
 
/* using this for scope means per-arena data will be stored
 
* per-arena */
 
 
 
PERSIST_GLOBAL
 
/* using this for scope means per-player data shared among all arenas */
 
/* using this for scope means per-arena data will be shared among
 
* all arenas (so it will effectively be global data). */
 
} persist_scope_t;
 
</pre>
 
Note: ASSS must be compiled with the berkeleydb option for persistent data to be available.
 
 
 
=== Attach/Detach ===
 
Attaching and detaching is similar to load/unload in a C module except it is arena specific. So you can use it to initialise per-arena data.
 
<pre>
 
def mm_attach(arena):
 
    # do stuff with arena
 
def mm_detach(arena):
 
    # undo stuff
 
</pre>
 
 
 
=== Looping over all players ===
 
This example counts the number of players in an arena.
 
<pre>
 
def count_players(arena):
 
    # a list must be used as all other variables are immutable to
 
    #  nested functions.
 
    players = [0]
 
    def cb_count(p):
 
        if p.arena == arena:
 
            players[0] = players[0] + 1
 
    for_each_player(cb_count)
 
    return players[0]
 
</pre>
 
 
 
=== Timers ===
 
Good for checking if a game is over yet. '''A reference to the timer is returned and must be retained''' (you can use per-arena data to store it). Losing the reference will cancel the timer.
 
 
 
''initial'' is the time in 1/100th's of a second before the nested function timer() will be called, you can cancel the timer before it is called. ''interval'' is the time gap, again in 1/100th's of a second between all future calls of timer(). So make_hello_timer(100, 200, arena) will make it send the arena message "hello" every 2 seconds starting from 1 second after make_hello_timer() was called.
 
 
 
The third argument to set_timer (''interval'') can be omitted and it will be assumed to be the same as ''initial''.
 
 
 
The parameter ''arena'' is needed in this case because SendArenaMessage() requires an arena parameter.
 
<pre>
 
def make_hello_timer(initial, interval, arena):
 
    def timer():
 
        # announce
 
        chat.SendArenaMessage(arena, "hello")
 
        # non-repeating timer. return 1 for it to be called after the next interval
 
        return 0
 
    return set_timer(timer, initial, interval)
 
 
 
def somefunc(arena):
 
    # create a hello timer that will execute after 1 second, and then every
 
    #  2 seconds until cancelled. timers can cancel themselves, see above.
 
    myref = make_hello_timer(100, 200, arena)
 
 
 
    # cancel the timer by losing the reference to it
 
    myref = None
 
</pre>
 
 
 
=== Regions ===
 
This is untested but it should go something like this:
 
<pre>
 
mapdata = get_interface(I_MAPDATA)
 
 
 
# regionname is a string, x and y are map tile coords.
 
def region_contains(arena, regionname, x, y):
 
    success = 0
 
 
 
    rgn = mapdata.FindRegionByName(arena, regionname)
 
 
 
    if rgn != None and mapdata.Contains(rgn, x, y):
 
        success = 1
 
 
 
    return success
 
</pre>
 
It is a good idea to cache ''rgn'' within [[#Per-player.2Farena_data|per-arena data]] so you don't add unecessary load to the server looking it up everytime.
 
 
 
== Trouble shooting ==
 
Look at the asss console for execution errors (at the time of writing not all errors are relayed to logged in staff), and if that doesn't help, add some chat.SendArenaMessage(ALLARENAS, "i'm at line ...") type messages to locate the buggy piece of code.
 
 
 
[[Category:Module]]
 
[[Category:Guides]]
 

Revision as of 03:32, 30 November 2005

apple cows