[Plugin] Python
#1

Introduction

I started to work on this plugin a few weeks ago, as I thought it would be much easier and faster to write scripts in Python than in PAWN.
Now every SAMP native is added and can be used from within Python scripts, though most of them are untested and might not work properly, so this is not a final/stable release yet, but probably most of them will work anyway.
Of course anyone can test some functions or give me some suggestions on how to improve the plugin. Any untested SAMP functions are commented with a TODO in nativefunctions.cpp.


What is it?

With this plugin you can use Python scripts for writing a gamemode and use any functions and callbacks available in SAMP.
Most functions can be used exactly the same way as in PAWN, but some of them are somewhat different (see below).

A simple testing script in Python looks like this:
http://pastebin.com/HM5TAani


Requirements

Before using this plugin you will need Python installed, you can get it from the Python homepage. If you use a Linux server, you should already have it installed.
Both newest packages were built with Python 2.6; you will need that version, if you don't want to build it for yourself.

For compiling on Linux you also need the package python-dev, on Windows everything should have already been installed with the Python installer.
On Linux you can just type "make" in the source directory to compile the plugin, it will use the paths returned by python-config.
On Windows you have to manually add the include directory (usually "C:\Program Files (x86)\Python\include") and the library directory (usually "C:\Program Files (x86)\Python\libs").
When running a 64-bit Linux version, you will need the 32-bit version of Python.

Of course, you will also need some knowledge about Python.


How to use

First, you have to download the plugin appropriate to your operating system from below (or download the source and compile the plugin for yourself).
Then put it in the "plugins" directory of SAMP and copy the file python.amx to the gamemode directory. Now add the plugin to your server.cfg ("plugins pythonplugin.so" if you haven't changed the file name) and set the gamemode to "python" (this is required for loading a Python script, redirecting callbacks and calling SAMP natives).


Sometimes you might see a few errors like this one when you start the server:
Код:
PYTHON: Could not find native IsPlayerHoldingObject
If you are using another SAMP server version than 0.3d or 0.3c (for which there are pre-compiled python.amx are available), you have to manually edit the python.pwn and add/remove/edit the functions different in your version.
When calling a function from Python, that wasn't found, it will crash the server.
Of course it's also possible to use Python from a filterscript. For this you just have to change the OnGameModeInit/-Exit to OnFilterScriptInit/-Exit and recompile it. This can be useful if you want to have a gamemode in PAWN but extend functionality with Python scripts just like with normal filterscripts.

After that, create a file called gamemode.py and a file __init__.py in the gamemodes directory and you can start to write a script in Python.

You can also create a file python.cfg in the scriptfiles directory and add every Python module to be loaded by the plugin, for example:
Код:
gamemode
test
The modules specified here are relative to the gamemodes directory (on Windows with Python 2.6 also in the SAMP server root due to a bug in Python).
Every loaded script can contain SAMP callbacks, which are then being called. If one function returns 0 (1 for OnPlayerCommandText), that value is being returned, the default value otherwise.


Functions / Callbacks

As mentioned above, almost every SAMP function can be called exactly the same as in PAWN.
Differences are listed here:

Callbacks
OnPyInit(): This function will be called when the script is being loaded.
OnPyExit(): This function will be called when the script is being unloaded.

Functions
SetTimer(func, interval, repeating[, params ]): The parameter "func" needs to be a callable object and no string.
With the last parameter (optional) you can pass a tuple. Its contents are passed as parameters to the function specified with funcname.
InvokeFunction(func[, params ]): This function can be used from another thread to invoke a function into PAWN's thread (e.g. outputting a result from a background thread to a player). The second optional parameter contains a tuple like in SetTimer.

Functions with hexadecimal colors (SendClientMessage, TextDrawColor, ...): The colors can also be specified as a tuple (see example script above) with four elements (RGBA).
Functions with reference parameters: Any parameters that are references are removed. The function will then return a dictionary containing every reference parameter (if there is only one reference parameter and no return value (GetPlayerName, GetPlayerHealth, ...), it is returned directly; function return values will be returned as "return" in the dictionary).


Threads

Since the version from Jul. 18, 2011 it is possible to use multiple threads in the Python script. Threads can be started with Python's own modules 'thread' or 'threading'.
This can be useful if you for example want to make a HTTP request. When the background thread is done with the request, it can call InvokeFunction to get the result sent to a player.

Inside other threads you may not call any functions in the SAMP module except for SetTimer, KillTimer and InvokeFunction, otherwise it will crash your server.
If you want to restart the gamemode using the RCON commands changemode or gmx, you need to make sure, that any thread has exited before OnPyExit has returned.

In the Windows and the Linux package there is an example on using threads (python/httpserver.py), where a very simple HTTP server is used to make a simple web interface for showing online player information.

Command processing

Of course you can just split the cmdtext and use if/elif for processing commands, but that is not really fast and would require processing seperately in every Python module, where you want to have commands.
Another, much better and faster way is to use the Python module "cmdparser.py", which is available in the download section below. This module provides a function decorator with which you can decorate any function in any module which is then added as a command.
Before using it, you have to import it and add the following to ONE OnPlayerCommandText (adding it to more than one will cause any command to be executed multiple times):
Код:
from cmdparser import cmd, handle_command
def OnPlayerCommandText(playerid, cmdtext):
	handle_command(playerid, cmdtext)
The simplest way of using it is the following:
Код:
@cmd
def mycmd(playerid, prm1=None, prm2=None):
	print('player %d: %s %s' % (playerid, prm1, prm2))
This defines a new command with the name "mycmd" with two parameters prm1 and prm2. You should always define parameters with default values, because otherwise it will throw an exception if a player does not specify all needed parameters.
When just using the decorator like this, you don't have to specify a command name, it is taken from the function name. But if you want to use some more advanced features, you have to use it like that and then you have to specify the command name:
Код:
@cmd("mycmd", requires=samp.IsPlayerAdmin, convert=dict(prm1=int))
def cmd_mycmd(playerid, prm1=None):
	if prm1 == None:
		samp.SendClientMessage(playerid, 0xFFFFFF00, "Missing parameter")
		return
	print('prm1 is now an integer! %d' % prm1)
This again defines the command "mycmd", but this time there are some parameters passed to the decorator.
The first one is the name of the command (or a list of multiple aliases for the command), which is required.
The second one can be either one single callable object or a list/tuple (or any other iterable object which gives callable objects) with multiple callable objects, which expect exactly one argument, a playerid, and returns True or False. These functions are called when a player uses the command and if one of these returns False, a text message with the text unauthorized_text and the color unauthd_color (both parameters of cmd) is sent to the player and the command function is not executed.
The last parameter, convert, is a dictionary with function parameter's names as the key and a function as the value. This function has to expect one string and return any other type. The return value of that function is then passed to the command function as that parameter.

Also, you can tell the command processor to pass the raw cmdtext to the function with the parameter "raw" (raw=True in the decorator). This is for example useful if you need consecutive whitespace characters in a string.
Every parameter is also documented in the docstring of the cmd function.

Speed

Python is slightly slower when executing SAMP natives and is even slower with functions with string parameters.
However this is not because Python is slower, but because every parameter from SAMP native functions need to be parsed and the right PAWN function needs to be called.
Except for that Python scripts should not be any slower than PAWN scripts, as Python scripts are also compiled to byte code (.pyc), which is done automatically by the interpreter.

Download
****** Code project page: Here

Python modules:
Command parser (cmdparser.py): Pastebin

Old version (Dec. 02, 2011):
Source: Download (Visual Studio 2010 solution, makefile and project files)
Windows: Download (compiled with Visual Studio 2010 and Python 2.6)
Linux: Download (compiled on Debian 6 with Python 2.6)

Old version (Jul. 18, 2011):
Source: Download (Visual Studio 2010 solution, makefile and project files)
Windows: Download (compiled with Visual Studio 2010 and Python 2.6)
Linux: Download (compiled on Ubuntu 11.04; requires GLIBC >= 2.4 and Python 2.7)

Updates
Dec. 27, 2010: Added the last few functions, fixed a few bugs and added constants and OnPyExit callback.
Jan. 3, 2011: Fixed timers (they didn't work at all, but they should now) and added another simple example script. Also, the PAWN include file, example scripts and the python.pwn for SAMP 0.3c is now in the Windows and Linux archives..
Feb. 9, 2011: Added support for multiple scripts.
Jul. 18, 2011:
  • completely reimplemented timers
  • added possibility to use threads (and other things using threads in Python like Python's own timer class); see section "Threads" before using
  • added OnRconCommand
  • added InvokeFunction to invoke a function from another thread into PAWN's thread
  • changed SetTimer to accept a callable object instead of a string containing a function name
  • fixed sys.path being overwritten after start (mainly paths added by site.py)
  • fixed crash on Linux when using SAMP natives with string parameters
  • fixed wrong handling of True and False in callback return values
  • removed dictionary return value from GetPlayerIp and directly return the ip now
Nov. 24, 2011: added a command parser (see "Command processing"); no changes to the plugin itself
Dec. 02, 2011:
  • added new functions and callbacks from SAMP 0.3c R3/R4 and 0.3d
  • updated example gamemode.py to use the new cmdparser module
  • disabled warnings about missing natives for IsPlayerHoldingObject, SetPlayerHoldingObject and StopPlayerHoldingObject
  • instead of the SAMP directory now add the gamemodes directory to sys.path (this needs updating imports of modules in the SAMP directory)
  • fixed deadlock when exiting the gamemode if there is no OnPyExit function in a Python module
Jan. 21, 2012:
  • fixed (or worked around) undefined symbol issues with Python C modules
  • added to ****** Code and now published under the GPL
Reply
#2

Nice, ima give it a test
Reply
#3

Good job.
Reply
#4

Nice plugin gonna give it a try.
Reply
#5

Good job, I'm gonna check it out.
Reply
#6

I'd like to see the speed differences
Reply
#7

Looks nice, can't wait to test it.
Reply
#8

If you're using this on Windows you'll need the Visual Studio 2010 Distributable Package. The x86 one is here:

http://www.microsoft.com/downloads/e...displaylang=en
Reply
#9

Can it run multiple scripts at once?
Reply
#10

Quote:
Originally Posted by Retardedwolf
Посмотреть сообщение
I'd like to see the speed differences
Python is about 1.5-6 times slower than PAWN when calling SAMP functions. A function with three integer values takes about 510ms in Python and 294ms in PAWN (repeated 1,000,000 times), while a function with an integer and a string takes about 569ms in Python and 100ms in PAWN. That shouldn't matter in most cases anyway.

Quote:
Originally Posted by Wyu
Посмотреть сообщение
Can it run multiple scripts at once?
No, not yet, but maybe sometime if it completely works with one script.
Reply
#11

Код:
----------
Loaded log file: "server_log.txt".
----------

SA-MP Dedicated Server
----------------------
v0.3c, ©2005-2010 SA-MP Team

[16:45:16] filterscripts = ""  (string)
[16:45:16] 
[16:45:16] Server Plugins
[16:45:16] --------------
[16:45:16]  Loading plugin: PythonPlugin
[16:45:16] 	Python plugin got loaded.
[16:45:16]   Loaded.
[16:45:16]  Loaded 1 plugins.

[16:45:16] 
[16:45:16] Filter Scripts
[16:45:16] ---------------
[16:45:16]   Loaded 0 filter scripts.

[16:45:16] Script[gamemodes/python.amx]: Run time error 19: "File or function is not found"
[16:45:16] Number of vehicle models: 0
[16:45:18] --- Server Shutting Down.
[16:45:18] 	Python plugin got unloaded.
Why it doesn't work?
And also when I try to compile python.pwn with pawno it can't find the include file in pawno/include

Also there is some problems compiling with Vistual C++ 2010

One more thing. The error above is only with 0.3c, I tried it with 0.3b and it did excute with errors and warnings
Reply
#12

Nice, this could actually come in handy for me.

+kudos (lmao)
Reply
#13

Looks nice, imma test it too!
Reply
#14

Looks nice, can't wait to test it.
Reply
#15

You should list the advantages comparing Python to Pawn.
Reply
#16

Quote:
Originally Posted by Retardedwolf
Посмотреть сообщение
You should list the advantages comparing Python to Pawn.
The number of built-in data structures Python has is a big advantage. Python not only offers more of them, but they're more lightweight to use in code since you don't have to pre-declare or initialize variables. I did a bunch of code conversion for another Python plugin, and the number of types available to you just make implementing common SA-MP stuff more elegant.

From adminspec.pwn:

Код:
new gSpectateID[MAX_PLAYERS];
new gSpectateType[MAX_PLAYERS];

*snip*

public OnPlayerInteriorChange(playerid, newinteriorid, oldinteriorid)
{
	// IF ANYONE IS SPECTATING THIS PLAYER, WE'LL ALSO HAVE
	// TO CHANGE THEIR INTERIOR ID TO MATCH
	new x = 0;
	while(x!=MAX_PLAYERS) {
	    if( IsPlayerConnected(x) &&	GetPlayerState(x) == PLAYER_STATE_SPECTATING &&
			gSpectateID[x] == playerid && gSpectateType[x] == ADMIN_SPEC_TYPE_PLAYER )
   		{
   		    SetPlayerInterior(x,newinteriorid);
		}
		x++;
	}
}

*snip*

                // end assignments from /specplayer

		gSpectateID[playerid] = specplayerid;
		gSpectateType[playerid] = ADMIN_SPEC_TYPE_PLAYER;

*snip*

                // bit from /specoff where both arrays are set to null values
		gSpectateID[playerid] = INVALID_PLAYER_ID;
		gSpectateType[playerid] = ADMIN_SPEC_TYPE_NONE;
By comparison here are the equivalent bits from my adminspec.py:

Код:
# the original gSpectateID and gSpectateType arrays have been combined
# into one dictionary of 2-tuples, (id,type)

gSpectate = {}

def OnPlayerInteriorChange(playerid, newinteriorid, oldinteriorid):
    # IF ANYONE IS SPECTATING THIS PLAYER, WE'LL ALSO HAVE
    # TO CHANGE THEIR INTERIOR ID TO MATCH
    for x in range(0,MAX_PLAYERS):
        if IsPlayerConnected(x) and GetPlayerState(x) == PLAYER_STATE_SPECTATING and\
           gSpectate.has_key(x) and gSpectate[x] == (playerid,ADMIN_SPEC_TYPE_PLAYER):
            SetPlayerInterior(x,newinteriorid)

*snip*

        # same bit from /specplayer - now it's just one line
        gSpectate[playerid] = (id,ADMIN_SPEC_TYPE_PLAYER)

*snip*

        # same bit from /specoff - instead of setting items in two arrays to null values
        # we just remove the entry entirely
        gSpectate.pop(playerid)
Instead of using two MAX_PLAYERS length arrays, the Python version combines them into one dictionary that holds another data structure called a tuple. A tuple is pretty much an immutable array. The nice thing about tuples is that you can create them on the fly and try to evaluate them in-line. So instead of

Код:
	    if( IsPlayerConnected(x) &&	GetPlayerState(x) == PLAYER_STATE_SPECTATING &&
			gSpectateID[x] == playerid && gSpectateType[x] == ADMIN_SPEC_TYPE_PLAYER )
we can use

Код:
        if IsPlayerConnected(x) and GetPlayerState(x) == PLAYER_STATE_SPECTATING and\
           gSpectate.has_key(x) and gSpectate[x] == (playerid,ADMIN_SPEC_TYPE_PLAYER):
            SetPlayerInterior(x,newinteriorid)
The underlined part in the Python version is the in-line tuple comparison. (playerid,ADMIN_SPEC_TYPE_PLAYER) is a tuple.

I don't know if that does a good job selling just how much simpler and cleaner Python scripts are, especially compared to Pawn, but once you're familiar with the language and know how to use the built-ins you're provided with, you can do more in Python with less time and less code than you can in Pawn.
Reply
#17

Quote:
Originally Posted by yass0016
Посмотреть сообщение
Why it doesn't work?
And also when I try to compile python.pwn with pawno it can't find the include file in pawno/include

Also there is some problems compiling with Vistual C++ 2010

One more thing. The error above is only with 0.3c, I tried it with 0.3b and it did excute with errors and warnings
I haven't tested it with 0.3c yet, but I think some functions were removed in 0.3c (SetPlayerHoldingObject, ...) and that's the problem. You need to remove (or comment out) these old functions from python.pwn and recompile it. I forgot to include the include file, I'll include it in the next packages. For now, I put it on pastebin: http://pastebin.com/xW3SqrXp
You can ignore the warnings I mentioned in the first post with 0.3b (see that post, if you use 0.3c).

Which errors exactly do you get (or what problems do you have) when trying to compile the plugin? Have you set the Python include and library directories correctly?


Quote:
Originally Posted by Retardedwolf
You should list the advantages comparing Python to Pawn.
Some big advantages of Python are, as already mentioned by mkr, dynamic variables (no predefined type of variables, you can assign anything to any variable; and lists/arrays/dictionaries with variable sizes), and its huge standards library (for many (most) things there are already functions in a Python module). Of course object orientation also is a big advantage.
For example, you can write a player class, which contains any player-specific information and add one global dictionary. Then you can create a new instance of that class whenever a player connects, and add it to the dictionary. When a player disconnects, you can then remove his entry.
Код:
from samp import *

class player(object):
	def __init__(self, playerid):
		self.playerid = playerid
		self.senselessInfo = 123
		# any other needed player variables

players = {}
def OnPlayerConnect(playerid):
	# add a new instance of the player class to the global player dictionary
	players[playerid] = player(playerid)

def OnPlayerDisconnect(playerid, reason):
	# delete that player from the dictionary
	del players[playerid]
Then you can also simply loop through each connected player without checking, if a player is connected 500 times even if there is only 1 player online:
Код:
for pl in players:
	printf("Player %d: %d" % (players[pl].playerid, players[pl].senselessInfo))
And to check, if a playerid is online, you can use:
Код:
if playerid in players:
Reply
#18

Thanks for listing them, but it would be hard for me to learn Python anyways : [
Reply
#19

I think color tuple order is backwards: i.e. (A,G,B,R) instead of (R,G,B,A). My color values other than white weren't displaying correctly, so I wrote a command to test stuff easily.

Код:
def OnPlayerCommandText(playerid, cmdtext):
    cmd = cmdtext.split(' ')
    if cmd[0] == "/colortest":
        if len(cmd) == 5:
            color = (int(cmd[1]),int(cmd[2]),int(cmd[3]),int(cmd[4]))
            SendClientMessage(playerid,color,"** This is color 0x%02x%02x%02x%02x - this is its Python tuple form: %s" % (color[0],color[1],color[2],color[3],str(color)))
        return True
I got these results:



EDIT: A simple fix was editing _getColor in nativefunctions.h and recompiling, though the DLL it spat out was more than twice the size as the provided one. You just reverse the order of the array elements, from

Код:
	PyArg_ParseTuple(pyobj, "bbbb", &col[0], &col[1], &col[2], &col[3]); \
to

Код:
	PyArg_ParseTuple(pyobj, "bbbb", &col[3], &col[2], &col[1], &col[0]); \
I'm not sure how big a hack this is.
Reply
#20

Well, apparently I only tested that with colors like (255, 0, 0, 255) or (255, 255, 255, 255), so I didn't realize that and I'm not that familiar with the byte orders.

I'm not sure if it would be better to use ntohl to convert it from big-endian to the host byte order, but I think most servers running SAMP should be using little-endian (see here), so thanks, I'll change it.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)