@c -*-texinfo-*- @node Python Behaviour Layer, , Addons, Using CEL @section The Python Behaviour Layer @cindex @sc{blpython} @cindex behaviour layer One of the predefined behaviour layers in Crystal Entity Layer is the @sc{blpython} behaviour layer. In this behaviour layer @sc{python} is used as simple scripting language. This allows one to create game logic using @sc{python}, and thus creating full scripting games without need of recompilation. @subheading Cs and cel python bindings There are several python modules for crystalspace and cel: @itemize @bullet @item @samp{cspace} This is the main crystalspace python module which provides most of the functionality in its api. @item @samp{blcelc} This is the main cel python module which provides most of the functionality in its api. @item @samp{pycel} This is a high level layer that is accessible when running scripts under the python behaviourlayer. It provides easy access to some of the common functionality in cel, and usually this is what you'll import into your scripts. Note pycel includes all functionality from both of the above. @end itemize Also it has to be noted pycel is automatically imported into each behaviour namespace, so you need not import it unless to import * from it. @subheading Behaviours When using the @sc{blpython} behaviour layer you basically create @dfn{scripts}. Every script corresponds to a behaviour for an entity (multiple entities can use it of course). Every script must include a behaviour of the same name. A python behaviour has the following structure: @example # file fovcontrol.py from pycel import * class fovcontrol: def __init__(self,celEntity): print "Initializing fovcontrol...",celEntity.Name # get camera from main actor (called camera in this case) self.camera = celGetDefaultCamera(Entities["camera"]).Camera self.initfov = self.camera.GetFOV () # get/create a pccommandinput self.input = celCommandInput(celEntity) self.input.Bind("z","zoomin") self.input.Bind("x","zoomout") self.input.Bind("c","zoom0") # get/create a timer self.timer = celTimer(celEntity) self.timer.WakeUpFrame(CEL_EVENT_PRE) # define some initial values self.zoomin = False self.zoomout = False # get initial time self.time = Clock.GetCurrentTicks() # property class message callbacks def pctimer_wakeupframe(self,celEntity,args): self.time = Clock.GetCurrentTicks() - self.time if self.zoomin: self.camera.SetFOV(int(self.camera.GetFOV()+(5*(self.time/1000.0))),Graphics2D.GetWidth()) if self.zoomout: self.camera.SetFOV(int(self.camera.GetFOV()-(5*(self.time/1000.0))),Graphics2D.GetWidth()) def pccommandinput_zoomin1(self,celEntity,args): self.zoomin = True def pccommandinput_zoomout1(self,celEntity,args): self.zoomout = True def pccommandinput_zoomin0(self,celEntity,args): self.zoomin = False def pccommandinput_zoomout0(self,celEntity,args): self.zoomout = False def pccommandinput_zoom01(self,celEntity,args): self.camera.SetFOV(self.initfov,Graphics2D.GetWidth()) @end example As can be seen we have a class with several methods. First we have the constructor (__init__ function) that gets a pointer to the entity it's being attached to (celEntity). As usual in python all methods in the class get a first parameter that points to the own python class instance (self) of the behaviour, we can use it to save variables for the instance. After this we have some callback functions, in many situations cel will call certain functions in our python behaviours so we may react to events. For example @samp{pctimer_wakeup} is got when the entity receives a wakeup event from a timer, @samp{pctrigger_entertrigger} is got when the entity enters some trigger's area. @subsubheading Python behaviours at xml worldfiles Python behaviours can be set directly in cel xml format as seen elsewhere in this manual. Here is an example entity with some property classes and a python behaviour set to it: @example @end example Note the python behaviour will be loaded from the actor.py file that must lie somewhere in PYTHONPATH (note celstart will add the main folder of your zip file to the PYTHONPATH if you're using it). Usually the celentity addon is placed in a crystalspace sector inside the world files. @subheading pycel pycel is a high level layer which defines some aliases for some of the most accessed things: @subsubheading The PhysicalLayer The physical layer is your main pointer to cel functionality, and you'll require this to create entities, destroy them, get string ids... It can be accessed from python at @samp{pycel.PhysicalLayer}. @subsubheading PhysicalLayer dictionaries All the physical layer dictionaries can be accessed directly from pycel: @itemize @item @samp{pycel.Entities} Used to get entity objects by name. @item @samp{pycel.EntityTemplates} Used to get entity templates by name. @item @samp{pycel.PcFactories} Used to get or register property class factories by id (like cel.pcfactory.timer). @item @samp{pycel.BehaviourLayers} Used to get or register behaviour layers with cel. @end itemize @subsubheading CrystalSpace plugins Some often used plugins in cs are ready to use (if present). These are: @itemize @item @samp{pycel.Engine} The engine plugin where you can get access to most crystal space objects in your map (iEngine interface) @item @samp{pycel.Vfs} The virtual file system plugin that you can use to move around the file system (iVFS interface). @item @samp{pycel.Clock} The virtual clock plugin, used to get precise times to calculate motion (iVirtualClock interface). @item @samp{pycel.Graphics2D} The graphics2d plugin presently in use, used to draw 2d things on the screen (iGraphics2D interface). @item @samp{pycel.Graphics3D} The graphics3d plugin presently in use, used to draw 3d things on the screen (iGraphics3D interface). @item @samp{pycel.Config} The configuration manager plugin, used to query or load crystalspace config files (iConfigManager interface). @item @samp{pycel.Loader} The loader plugin from crystalspace, used to load world or library files (iLoader interface). @item @samp{pycel.Stringset} (iStringSet). The string set plugin, used to transform from cs string ids to regular strings (iStringSet interface). @end itemize @subsubheading getid/fromid Functions to transform from string to stringid. @itemize @item @samp{pycel.getid(string)} returns a string id from the given string. @item @samp{pycel.fromid(stringid)} returns a string from the given stringid. @end itemize @subsubheading parblock Function to create a celParameterBlock from a python dict or list. This is described in detail later. @subsubheading CreateEntity/RemoveEntity These can be accessed directly from pycel to create and destroy entities. Use them in the same way as the physical layer functions of the same name. @subsubheading Property Class accessors Some functions are defined to easily create cel property classes for entities, or access the pre existant ones. This will be described in the next section of this manual. @subheading Accessing entity property classes Property classes are objects we can create attached to an entity, and which augment the entity with some functionality (@pxref{Property Classes}). First it must be noted to use each property class it must be ensured it is registered at the physical layer, to do this in python simply add the property class factory full name (cel.pcfactory.pcclass) to the @samp{PcFactories} dict (which is a member of the physicallayer but has global access due to pycel layer). An example of this follows: @example pcclasses = ["region","tooltip","mesh","solid","meshselect","zonemanager","trigger", "quest","light","inventory","defaultcamera","gravity","movable", "pccommandinput","linmove","actormove","colldet","timer","soundlistener", "soundsource","billboard","properties"] for pcclass in pcclasses: PcFactories.append("cel.pcfactory."+pcclass) @end example For each property class there are several functions in pycel module that allow easily fetching or creating of the appropiate property class from an entity object: @itemize @bullet @item @samp{celPropertyClass(celEntity)} Get the property class if it exists or create a new one. @item @samp{celAddPropertyClass(celEntity)} Add the property class to the entity. @item @samp{celGetPropertyClass(celEntity)} Get the property class if it exists. @end itemize Where @samp{PropertyClass} would be substituted for the appropiate name, like in @samp{celGetTimer}, @samp{celAddCommandInput}... As an example at the initialization section we could do: @example def __init__(self,celEntity): self.input = celCommandInput(celEntity) self.timer = celAddTimer(celEntity) self.timer.WakeUpFrame (CEL_EVENT_POST) self.select = celMeshSelect(celEntity) @end example @subheading Messages Each property class in cel can generate a number of messages. A python behaviour receives this messages as python function calls with the same name as the message. We can use this calls to respond to events from the different property classes appropiately. In the first example we're receiving a @samp{pctimer_wakeupframe} function call each frame, due to the activated @samp{pctimer}. Also there are some @samp{pccommandinput_*} functions, that are triggered by the @samp{pccommandinput} binds. Notice for each input bind we make through pccommandinput the behaviour will receive three different events called @samp{pccommandinput_bind1}, @samp{pccommandinput_bind0} and @samp{pccommandinput_bind_}; for the press, release and maintain events for the key respectively. Other property classes like @samp{pcmechanicsobject}, @samp{pcmeshselect}, @samp{pcbillboard}, @samp{pcdamage}, @samp{pclinmove} also generate their own special messages. Message function calls get three parameters. The first is the behaviour class instance, the second the entity receiving the message, and the last a celParameterBlock that holds all message specific values. You can access the values from the parameter block as a python dict with cel StringIDs for index instead of normal strings. @example # a pcbillboard select message def pcbillboard_select(self,celEntity,args): bx = args[getid("cel.parameter.x")] by = args[getid("cel.parameter.y")] button= args[getid("cel.parameter.button")] @end example It is possible to test for existance of a certain parameter, or iterate over the values. Also note for efficiency you would save the StringIDs for later use instead of querying them each time. A final snippet for printing all parameters to a message: @example def pcbillboard_select(self,celEntity,Args): for strid in args.keys(): print fromid(strid),args[strid] @end example @subheading Sending Messages Some messages (like @samp{__init__} and messages from property classes) are automatically called but you can also define your own messages and call them using the @samp{iCelBehaviour.SendMessage} function. To send a message/event to another entity we must create the same kind of parameter list. There exists a helper function in the physical layer to do this called @samp{CreateParameterBlock}, and also the @samp{pycel} alias @samp{parblock}. It accepts either a dict, list or tuple as arguments. If its a dict will initialize the ids and values in the parameter block, if we provide a list or tuple it will only fill the ids and will require filling the values later: @example #Creating a parameter block from a list. This is more similar to the c++ method pars = parblock(["control","x","y"]) pars[getid ("cel.parameter.control")] = "specular" pars[getid ("cel.parameter.x")] = 15 pars[getid ("cel.parameter.y")] = 200 #Creating a parameter block from a dictionary. pars2 = parblock(@{"control":"shininess","x":30,"y":200@}) @end example The difference is in speed, usually if you'll be sending the same kind of parameter block many times you'll want to build it in the constructor from the behaviour, and just fill values before sending. In other situations it can be appropiate to create the entire parameter block from a dict when needed. After this the message is sent using SendMessage in the target entity behaviour: @example Entities["player"].Behaviour.SendMessage("control_variable", None, pars) @end example @subheading A python world for celstart The best way to run python (or xml and python) only cel worlds is to use the celstart cel application (@pxref{AppCelstart}). For this you will need to load the python behaviourlayer from the celstart configuration file. Another common procedure is to define some starting entity to be created with an app behaviour. From here it is possible to load the map entirely from python although note other setups are possible. In order to do do this, we would add something like the following to the config file: @example CelStart.BehaviourLayer.blpython = cel.behaviourlayer.python CelStart.Entity.bootstrap = bootstrap CelStart.EntityBehaviour.bootstrap = appinit CelStart.EntityBehaviourLayer.bootstrap = blpython @end example This would create an starting entity and load the python behaviour appinit, which would be loaded from the file appinit.py. From this behaviour you can load a map using either cs or cel api, an usual way of doing this is through a cel ZoneManager property class at the initial entity: @example def __init__(self,celEntity): zoneMgr = celZoneManager(celEntity) Vfs.ChDirAuto("/tmp/celstart/") zoneMgr.Load("/tmp/celstart","level.xml") @end example Note /tmp/celstart/ is the vfs path where celstart maps the zip file initially, so this doesnt change depending on operating system or real file location.