First step for restricted (and therefore safe) Python in trunk.
For script authors this means: - no more exceptions (try..except) - no more unsafe builtins (eval, file, type, ...) - no more import (some whitelisted modules are auto-imported currently) - no more access to internals (e.g. __dict__, __name__, ...) The campaign server for now still doesn't allow .py scripts, but likely this restriction can be removed.
This commit is contained in:
parent
fe3ee09b27
commit
a0bf9a7209
4 changed files with 476 additions and 166 deletions
359
data/ais/safe.py
Normal file
359
data/ais/safe.py
Normal file
|
@ -0,0 +1,359 @@
|
|||
"""An attempt at creating a safe_exec for python.
|
||||
|
||||
This file is public domain and is not suited for any serious purpose.
|
||||
This code is not guaranteed to work. Use at your own risk!
|
||||
Beware! Trust no one!
|
||||
|
||||
Please e-mail philhassey@yahoo.com if you find any security holes.
|
||||
|
||||
Known limitations:
|
||||
- Safe doesn't have any testing for timeouts/DoS. One-liners
|
||||
like these will lock up the system: "while 1: pass", "234234**234234"
|
||||
- Lots of (likely) safe builtins and safe AST Nodes are not allowed.
|
||||
I suppose you can add them to the whitelist if you want them. I
|
||||
trimmed it down as much as I thought I could get away with and still
|
||||
have useful python code.
|
||||
- Might not work with future versions of python - this is made with
|
||||
python 2.4 in mind. _STR_NOT_BEGIN might have to be extended
|
||||
in the future with more magic variable prefixes. Or you can
|
||||
switch to conservative mode, but then even variables like "my_var"
|
||||
won't work, which is sort of a nuisance.
|
||||
- If you get data back from a safe_exec, don't call any functions
|
||||
or methods - they might not be safe with __builtin__ restored
|
||||
to its normal state. Work with them again via an additional safe_exec.
|
||||
- The "context" sent to the functions is not tested at all. If you
|
||||
pass in a dangerous function {'myfile':file} the code will be able
|
||||
to call it.
|
||||
"""
|
||||
|
||||
# Built-in Objects
|
||||
# http://docs.python.org/lib/builtin.html
|
||||
|
||||
# AST Nodes - compiler
|
||||
# http://docs.python.org/lib/module-compiler.ast.html
|
||||
|
||||
# Types and members - inspection
|
||||
# http://docs.python.org/lib/inspect-types.html
|
||||
# The standard type heirarchy
|
||||
# http://docs.python.org/ref/types.html
|
||||
|
||||
# Based loosely on - Restricted "safe" eval - by Babar K. Zafar
|
||||
# (it isn't very safe, but it got me started)
|
||||
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496746
|
||||
|
||||
# Securing Python: Controlling the abilities of the interpreter
|
||||
# (or - why even trying this is likely to end in tears)
|
||||
# http://us.pycon.org/common/talkdata/PyCon2007/062/PyCon_2007.pdf
|
||||
|
||||
# Changes
|
||||
# 2007-03-13: added test for unicode strings that contain __, etc
|
||||
# 2007-03-09: renamed safe_eval to safe_exec, since that's what it is.
|
||||
# 2007-03-09: use "exec code in context" , because of test_misc_recursive_fnc
|
||||
# 2007-03-09: Removed 'type' from _BUILTIN_OK - see test_misc_type_escape
|
||||
# 2007-03-08: Cleaned up the destroy / restore mechanism, added more tests
|
||||
# 2007-03-08: Fixed how contexts work.
|
||||
# 2007-03-07: Added test for global node
|
||||
# 2007-03-07: Added test for SyntaxError
|
||||
# 2007-03-07: Fixed an issue where the context wasn't being reset (added test)
|
||||
# 2007-03-07: Added unittest for dir()
|
||||
# 2007-03-07: Removed 'isinstance', 'issubclass' from builtins whitelist
|
||||
# 2007-03-07: Removed 'EmptyNode', 'Global' from AST whitelist
|
||||
# 2007-03-07: Added import __builtin__; s/__builtins__/__builtin__
|
||||
|
||||
import compiler
|
||||
import __builtin__
|
||||
|
||||
class SafeException(Exception):
|
||||
"""Base class for Safe Exceptions"""
|
||||
def __init__(self,*value):
|
||||
self.value = str(value)
|
||||
def __str__(self):
|
||||
return self.value
|
||||
class CheckNodeException(SafeException):
|
||||
"""AST Node class is not in the whitelist."""
|
||||
pass
|
||||
class CheckStrException(SafeException):
|
||||
"""A string in the AST looks insecure."""
|
||||
pass
|
||||
class RunBuiltinException(SafeException):
|
||||
"""During the run a non-whitelisted builtin was called."""
|
||||
pass
|
||||
|
||||
_NODE_CLASS_OK = [
|
||||
'Add', 'And', 'AssAttr', 'AssList', 'AssName', 'AssTuple',
|
||||
'Assert', 'Assign','AugAssign', 'Bitand', 'Bitor', 'Bitxor', 'Break',
|
||||
'CallFunc', 'Class', 'Compare', 'Const', 'Continue',
|
||||
'Dict', 'Discard', 'Div', 'Ellipsis', 'Expression', 'FloorDiv',
|
||||
'For', 'Function', 'Getattr', 'If', 'Keyword',
|
||||
'LeftShift', 'List', 'ListComp', 'ListCompFor', 'ListCompIf', 'Mod',
|
||||
'Module', 'Mul', 'Name', 'Node', 'Not', 'Or', 'Pass', 'Power',
|
||||
'Print', 'Printnl', 'Return', 'RightShift', 'Slice', 'Sliceobj',
|
||||
'Stmt', 'Sub', 'Subscript', 'Tuple', 'UnaryAdd', 'UnarySub', 'While',
|
||||
]
|
||||
_NODE_ATTR_OK = []
|
||||
_STR_OK = ['__init__']
|
||||
_STR_NOT_CONTAIN = ['__']
|
||||
_STR_NOT_BEGIN = ['im_','func_','tb_','f_','co_',]
|
||||
|
||||
## conservative settings
|
||||
#_NODE_ATTR_OK = ['flags']
|
||||
#_STR_NOT_CONTAIN = ['_']
|
||||
#_STR_NOT_BEGIN = []
|
||||
|
||||
def _check_node(node):
|
||||
if node.__class__.__name__ not in _NODE_CLASS_OK:
|
||||
raise CheckNodeException(node.lineno,node.__class__.__name__)
|
||||
for k,v in node.__dict__.items():
|
||||
if k in _NODE_ATTR_OK: continue
|
||||
if v in _STR_OK: continue
|
||||
if type(v) not in [str,unicode]: continue
|
||||
for s in _STR_NOT_CONTAIN:
|
||||
if s in v: raise CheckStrException(node.lineno,k,v)
|
||||
for s in _STR_NOT_BEGIN:
|
||||
if v[:len(s)] == s: raise CheckStrException(node.lineno,k,v)
|
||||
for child in node.getChildNodes():
|
||||
_check_node(child)
|
||||
|
||||
def _check_ast(code):
|
||||
ast = compiler.parse(code)
|
||||
_check_node(ast)
|
||||
|
||||
# r = [v for v in dir(__builtin__) if v[0] != '_' and v[0] == v[0].upper()] ; r.sort() ; print r
|
||||
_BUILTIN_OK = [
|
||||
'__debug__','quit','exit',
|
||||
|
||||
'ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'IOError', 'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError',
|
||||
|
||||
'abs', 'bool', 'cmp', 'complex', 'dict', 'divmod', 'filter', 'float', 'frozenset', 'hex', 'int', 'len', 'list', 'long', 'map', 'max', 'min', 'object', 'oct', 'pow', 'range', 'reduce', 'repr', 'round', 'set', 'slice', 'str', 'sum', 'tuple', 'xrange', 'zip',
|
||||
'isinstance', 'issubclass']
|
||||
|
||||
#this is zope's list...
|
||||
#in ['False', 'None', 'True', 'abs', 'basestring', 'bool', 'callable',
|
||||
#'chr', 'cmp', 'complex', 'divmod', 'float', 'hash',
|
||||
#'hex', 'id', 'int', 'isinstance', 'issubclass', 'len',
|
||||
#'long', 'oct', 'ord', 'pow', 'range', 'repr', 'round',
|
||||
#'str', 'tuple', 'unichr', 'unicode', 'xrange', 'zip']:
|
||||
|
||||
|
||||
_BUILTIN_STR = [
|
||||
'copyright','credits','license','__name__','__doc__',
|
||||
]
|
||||
|
||||
def _builtin_fnc(k):
|
||||
def fnc(*vargs,**kargs):
|
||||
raise RunBuiltinException(k)
|
||||
return fnc
|
||||
_builtin_globals = None
|
||||
_builtin_globals_r = None
|
||||
def _builtin_init():
|
||||
global _builtin_globals, _builtin_globals_r
|
||||
if _builtin_globals != None: return
|
||||
_builtin_globals_r = __builtin__.__dict__.copy()
|
||||
r = _builtin_globals = {}
|
||||
for k in __builtin__.__dict__.keys():
|
||||
v = None
|
||||
if k in _BUILTIN_OK: v = __builtin__.__dict__[k]
|
||||
elif k in _BUILTIN_STR: v = ''
|
||||
else: v = _builtin_fnc(k)
|
||||
r[k] = v
|
||||
def _builtin_destroy():
|
||||
_builtin_init()
|
||||
for k,v in _builtin_globals.items():
|
||||
__builtin__.__dict__[k] = v
|
||||
def _builtin_restore():
|
||||
for k,v in _builtin_globals_r.items():
|
||||
__builtin__.__dict__[k] = v
|
||||
|
||||
def safe_check(code):
|
||||
"""Check the code to be safe."""
|
||||
return _check_ast(code)
|
||||
|
||||
def safe_run(code,context=None):
|
||||
"""Exec code with only safe builtins on."""
|
||||
if context == None: context = {}
|
||||
|
||||
_builtin_destroy()
|
||||
try:
|
||||
#exec code in _builtin_globals,context
|
||||
context['__builtins__'] = _builtin_globals
|
||||
exec code in context
|
||||
_builtin_restore()
|
||||
except:
|
||||
_builtin_restore()
|
||||
raise
|
||||
|
||||
def safe_exec(code,context = None):
|
||||
"""Check the code to be safe, then run it with only safe builtins on."""
|
||||
safe_check(code)
|
||||
safe_run(code,context)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import unittest
|
||||
|
||||
class TestSafe(unittest.TestCase):
|
||||
def test_check_node_import(self):
|
||||
self.assertRaises(CheckNodeException,safe_exec,"import os")
|
||||
def test_check_node_from(self):
|
||||
self.assertRaises(CheckNodeException,safe_exec,"from os import *")
|
||||
def test_check_node_exec(self):
|
||||
self.assertRaises(CheckNodeException,safe_exec,"exec 'None'")
|
||||
def test_check_node_raise(self):
|
||||
self.assertRaises(CheckNodeException,safe_exec,"raise Exception")
|
||||
def test_check_node_global(self):
|
||||
self.assertRaises(CheckNodeException,safe_exec,"global abs")
|
||||
|
||||
def test_check_str_x(self):
|
||||
self.assertRaises(CheckStrException,safe_exec,"x__ = 1")
|
||||
def test_check_str_str(self):
|
||||
self.assertRaises(CheckStrException,safe_exec,"x = '__'")
|
||||
def test_check_str_class(self):
|
||||
self.assertRaises(CheckStrException,safe_exec,"None.__class__")
|
||||
def test_check_str_func_globals(self):
|
||||
self.assertRaises(CheckStrException,safe_exec,"def x(): pass; x.func_globals")
|
||||
def test_check_str_init(self):
|
||||
safe_exec("def __init__(self): pass")
|
||||
def test_check_str_subclasses(self):
|
||||
self.assertRaises(CheckStrException,safe_exec,"object.__subclasses__")
|
||||
def test_check_str_properties(self):
|
||||
code = """
|
||||
class X(object):
|
||||
def __get__(self,k,t=None):
|
||||
1/0
|
||||
"""
|
||||
self.assertRaises(CheckStrException,safe_exec,code)
|
||||
def test_check_str_unicode(self):
|
||||
self.assertRaises(CheckStrException,safe_exec,"u'__'")
|
||||
|
||||
def test_run_builtin_open(self):
|
||||
self.assertRaises(RunBuiltinException,safe_exec,"open('test.txt','w')")
|
||||
def test_run_builtin_getattr(self):
|
||||
self.assertRaises(RunBuiltinException,safe_exec,"getattr(None,'x')")
|
||||
def test_run_builtin_abs(self):
|
||||
safe_exec("abs(-1)")
|
||||
def test_run_builtin_open_fnc(self):
|
||||
def test():
|
||||
f = open('test.txt','w')
|
||||
self.assertRaises(RunBuiltinException,safe_exec,"test()",{'test':test})
|
||||
def test_run_builtin_open_context(self):
|
||||
#this demonstrates how python jumps into some mystical
|
||||
#restricted mode at this point .. causing this to throw
|
||||
#an IOError. a bit strange, if you ask me.
|
||||
self.assertRaises(IOError,safe_exec,"test('test.txt','w')",{'test':open})
|
||||
def test_run_builtin_type_context(self):
|
||||
#however, even though this is also a very dangerous function
|
||||
#python's mystical restricted mode doesn't throw anything.
|
||||
safe_exec("test(1)",{'test':type})
|
||||
def test_run_builtin_dir(self):
|
||||
self.assertRaises(RunBuiltinException,safe_exec,"dir(None)")
|
||||
|
||||
def test_run_exeception_div(self):
|
||||
self.assertRaises(ZeroDivisionError,safe_exec,"1/0")
|
||||
def test_run_exeception_i(self):
|
||||
self.assertRaises(ValueError,safe_exec,"(-1)**0.5")
|
||||
|
||||
def test_misc_callback(self):
|
||||
self.value = None
|
||||
def test(): self.value = 1
|
||||
safe_exec("test()", {'test':test})
|
||||
self.assertEqual(self.value, 1)
|
||||
def test_misc_safe(self):
|
||||
self.value = None
|
||||
def test(v): self.value = v
|
||||
code = """
|
||||
class Test:
|
||||
def __init__(self,value):
|
||||
self.x = value
|
||||
self.y = 4
|
||||
def run(self):
|
||||
for n in xrange(0,34):
|
||||
self.x += n
|
||||
self.y *= n
|
||||
return self.x+self.y
|
||||
b = Test(value)
|
||||
r = b.run()
|
||||
test(r)
|
||||
"""
|
||||
safe_exec(code,{'value':3,'test':test})
|
||||
self.assertEqual(self.value, 564)
|
||||
|
||||
def test_misc_context_reset(self):
|
||||
#test that local contact is reset
|
||||
safe_exec("abs = None")
|
||||
safe_exec("abs(-1)")
|
||||
safe_run("abs = None")
|
||||
safe_run("abs(-1)")
|
||||
|
||||
def test_misc_syntax_error(self):
|
||||
self.assertRaises(SyntaxError,safe_exec,"/")
|
||||
|
||||
def test_misc_context_switch(self):
|
||||
self.value = None
|
||||
def test(v): self.value = v
|
||||
safe_exec("""
|
||||
def test2():
|
||||
open('test.txt','w')
|
||||
test(test2)
|
||||
""",{'test':test})
|
||||
self.assertRaises(RunBuiltinException,safe_exec,"test()",{'test':self.value})
|
||||
|
||||
def test_misc_context_junk(self):
|
||||
#test that stuff isn't being added into *my* context
|
||||
#except what i want in it..
|
||||
c = {}
|
||||
safe_exec("b=1",c)
|
||||
self.assertEqual(c['b'],1)
|
||||
|
||||
def test_misc_context_later(self):
|
||||
#honestly, i'd rec that people don't do this, but
|
||||
#at least we've got it covered ...
|
||||
c = {}
|
||||
safe_exec("def test(): open('test.txt','w')",c)
|
||||
self.assertRaises(RunBuiltinException,c['test'])
|
||||
|
||||
#def test_misc_test(self):
|
||||
#code = "".join(open('test.py').readlines())
|
||||
#safe_check(code)
|
||||
|
||||
def test_misc_builtin_globals_write(self):
|
||||
#check that a user can't modify the special _builtin_globals stuff
|
||||
safe_exec("abs = None")
|
||||
self.assertNotEqual(_builtin_globals['abs'],None)
|
||||
|
||||
#def test_misc_builtin_globals_used(self):
|
||||
##check that the same builtin globals are always used
|
||||
#c1,c2 = {},{}
|
||||
#safe_exec("def test(): pass",c1)
|
||||
#safe_exec("def test(): pass",c2)
|
||||
#self.assertEqual(c1['test'].func_globals,c2['test'].func_globals)
|
||||
#self.assertEqual(c1['test'].func_globals,_builtin_globals)
|
||||
|
||||
def test_misc_builtin_globals_used(self):
|
||||
#check that the same builtin globals are always used
|
||||
c = {}
|
||||
safe_exec("def test1(): pass",c)
|
||||
safe_exec("def test2(): pass",c)
|
||||
self.assertEqual(c['test1'].func_globals,c['test2'].func_globals)
|
||||
self.assertEqual(c['test1'].func_globals['__builtins__'],_builtin_globals)
|
||||
self.assertEqual(c['__builtins__'],_builtin_globals)
|
||||
|
||||
def test_misc_type_escape(self):
|
||||
#tests that 'type' isn't allowed anymore
|
||||
#with type defined, you could create magical classes like this:
|
||||
code = """
|
||||
def delmethod(self): 1/0
|
||||
foo=type('Foo', (object,), {'_' + '_del_' + '_':delmethod})()
|
||||
foo.error
|
||||
"""
|
||||
try:
|
||||
self.assertRaises(RunBuiltinException,safe_exec,code)
|
||||
finally:
|
||||
pass
|
||||
|
||||
def test_misc_recursive_fnc(self):
|
||||
code = "def test():test()\ntest()"
|
||||
self.assertRaises(RuntimeError,safe_exec,code)
|
||||
|
||||
|
||||
unittest.main()
|
||||
|
||||
#safe_exec('print locals()')
|
|
@ -1,5 +1,4 @@
|
|||
#!WPY
|
||||
import wesnoth, heapq, random
|
||||
|
||||
"""This is a rather simple minded example of a python AI."""
|
||||
|
||||
|
@ -162,10 +161,10 @@ class AI:
|
|||
score = -score
|
||||
|
||||
if unit in units and enemy in enemies:
|
||||
try:
|
||||
loc = wesnoth.move_unit(unit, tile)
|
||||
except ValueError:
|
||||
loc = None
|
||||
#try:
|
||||
loc = wesnoth.move_unit(unit, tile)
|
||||
#except ValueError:
|
||||
# loc = None
|
||||
if loc == tile:
|
||||
e = wesnoth.get_units()[enemy]
|
||||
wesnoth.attack_unit(tile, enemy)
|
||||
|
|
|
@ -11,6 +11,22 @@
|
|||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
/* Note about Python exceptions:
|
||||
|
||||
Originally, runtime exceptions were used for things like using out of bounds
|
||||
coordinates or moving a unit which already got killed, and were supposed to
|
||||
be catched by scripts. The subset of the Python language allowed in
|
||||
scripts now has no exceptions anymore, to keep things simple. Therefore the
|
||||
API was changed so scripts are not supposed to catch exceptions anymore.
|
||||
|
||||
This means, invalid positions or units will now only be indicated by things
|
||||
like return values, but are ignored otherwise.
|
||||
|
||||
Most API functions still will cause exceptions (like if the game ends, or
|
||||
the wrong number of parameters is passed to a function) - but those are not
|
||||
supposed to be catched by user scripts.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "global.hpp"
|
||||
|
@ -33,18 +49,15 @@ static python_ai* running_instance;
|
|||
bool python_ai::init_ = false;
|
||||
PyObject* python_ai::python_error_ = NULL;
|
||||
|
||||
#define MSG_UNIT "Invalid unit"
|
||||
void python_ai::set_error(const char *fmt, ...)
|
||||
{
|
||||
char buf[1024];
|
||||
va_list arg;
|
||||
#define return_none do {Py_INCREF(Py_None); return Py_None;} while(false)
|
||||
|
||||
va_start(arg, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
PyErr_SetString(PyExc_ValueError, buf);
|
||||
}
|
||||
#define wrap(code) \
|
||||
try { code } \
|
||||
catch(end_level_exception& e) { \
|
||||
running_instance->exception = e; \
|
||||
PyErr_SetString(PyExc_RuntimeError, "C++ exception!"); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
static PyObject* wrap_unittype(const unit_type& type);
|
||||
static PyObject* wrap_attacktype(const attack_type& type);
|
||||
|
@ -121,7 +134,7 @@ static PyGetSetDef unittype_getseters[] = {
|
|||
|
||||
PyObject* python_ai::unittype_advances_to( wesnoth_unittype* type, PyObject* args )
|
||||
{
|
||||
if ( !PyArg_ParseTuple( args, "" ) )
|
||||
if (!PyArg_ParseTuple(args, "" ))
|
||||
return NULL;
|
||||
|
||||
PyObject* list = PyList_New(type->unit_type_->advances_to().size());
|
||||
|
@ -416,135 +429,96 @@ static PyObject* wrap_attacktype(const attack_type& type)
|
|||
return (PyObject*)attack;
|
||||
}
|
||||
|
||||
#define u_check if (!running_instance->is_unit_valid(unit->unit_)) return_none
|
||||
|
||||
bool python_ai::is_unit_valid(const unit* unit, bool do_set_error)
|
||||
bool python_ai::is_unit_valid(const unit* unit)
|
||||
{
|
||||
if (!unit)
|
||||
{
|
||||
if (do_set_error)
|
||||
set_error("Undefined unit.");
|
||||
return false;
|
||||
}
|
||||
for(unit_map::const_iterator i = running_instance->get_info().units.begin(); i != running_instance->get_info().units.end(); ++i) {
|
||||
if (unit == &i->second)
|
||||
return true;
|
||||
}
|
||||
if (do_set_error)
|
||||
set_error("Unit was destroyed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
static PyObject* unit_get_name(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("s",( const char* )unit->unit_->name().c_str());
|
||||
u_check;
|
||||
return Py_BuildValue("s",( const char* )unit->unit_->name().c_str());
|
||||
}
|
||||
|
||||
static PyObject* unit_is_enemy(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",running_instance->current_team().is_enemy(unit->unit_->side()) == true ? 1 : 0);
|
||||
u_check;
|
||||
return Py_BuildValue("i", running_instance->current_team().is_enemy(unit->unit_->side()) == true ? 1 : 0);
|
||||
}
|
||||
|
||||
static PyObject* unit_can_recruit(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",unit->unit_->can_recruit() == true ? 1 : 0);
|
||||
u_check;
|
||||
return Py_BuildValue("i", unit->unit_->can_recruit() == true ? 1 : 0);
|
||||
}
|
||||
|
||||
static PyObject* unit_side(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",unit->unit_->side());
|
||||
u_check;
|
||||
return Py_BuildValue("i", unit->unit_->side());
|
||||
}
|
||||
|
||||
static PyObject* unit_movement_left(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",unit->unit_->movement_left());
|
||||
u_check;
|
||||
return Py_BuildValue("i", unit->unit_->movement_left());
|
||||
}
|
||||
|
||||
static PyObject* unit_can_attack(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",unit->unit_->attacks_left());
|
||||
u_check;
|
||||
return Py_BuildValue("i", unit->unit_->attacks_left());
|
||||
}
|
||||
|
||||
static PyObject* unit_hitpoints(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",(int)unit->unit_->hitpoints());
|
||||
u_check;
|
||||
return Py_BuildValue("i", (int)unit->unit_->hitpoints());
|
||||
}
|
||||
|
||||
static PyObject* unit_max_hitpoints(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",(int)unit->unit_->max_hitpoints());
|
||||
u_check;
|
||||
return Py_BuildValue("i", (int)unit->unit_->max_hitpoints());
|
||||
}
|
||||
|
||||
static PyObject* unit_experience(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",(int)unit->unit_->experience());
|
||||
u_check;
|
||||
return Py_BuildValue("i", (int)unit->unit_->experience());
|
||||
}
|
||||
|
||||
static PyObject* unit_max_experience(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",(int)unit->unit_->max_experience());
|
||||
u_check;
|
||||
return Py_BuildValue("i", (int)unit->unit_->max_experience());
|
||||
}
|
||||
|
||||
static PyObject* unit_poisoned(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",utils::string_bool(unit->unit_->get_state("poisoned")));
|
||||
u_check;
|
||||
return Py_BuildValue("i", utils::string_bool(unit->unit_->get_state("poisoned")));
|
||||
}
|
||||
|
||||
static PyObject* unit_stoned(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("i",utils::string_bool(unit->unit_->get_state("stoned")));
|
||||
u_check;
|
||||
return Py_BuildValue("i", utils::string_bool(unit->unit_->get_state("stoned")));
|
||||
}
|
||||
|
||||
static PyObject* unit_query_valid(wesnoth_unit* unit, void* /*closure*/)
|
||||
{
|
||||
return Py_BuildValue("i",running_instance->is_unit_valid(unit->unit_, false) == true ? 1 : 0);
|
||||
return Py_BuildValue("i", running_instance->is_unit_valid(unit->unit_) == true ? 1 : 0);
|
||||
}
|
||||
|
||||
static PyGetSetDef unit_getseters[] = {
|
||||
|
@ -580,24 +554,18 @@ static PyGetSetDef unit_getseters[] = {
|
|||
|
||||
static PyObject* wrapper_unit_type( wesnoth_unit* unit, PyObject* args )
|
||||
{
|
||||
if ( !PyArg_ParseTuple( args, "" ) )
|
||||
return NULL;
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
if (!PyArg_ParseTuple(args, ""))
|
||||
return NULL;
|
||||
u_check;
|
||||
wassert(unit->unit_->type());
|
||||
return wrap_unittype(*unit->unit_->type());
|
||||
}
|
||||
|
||||
static PyObject* wrapper_unit_attacks( wesnoth_unit* unit, PyObject* args )
|
||||
{
|
||||
if ( !PyArg_ParseTuple( args, "" ) )
|
||||
return NULL;
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
if (!PyArg_ParseTuple(args, ""))
|
||||
return NULL;
|
||||
u_check;
|
||||
PyObject* list = PyList_New(unit->unit_->attacks().size());
|
||||
for ( size_t attack = 0; attack < unit->unit_->attacks().size(); attack++)
|
||||
PyList_SetItem(list,attack,wrap_attacktype(unit->unit_->attacks()[attack]));
|
||||
|
@ -609,10 +577,7 @@ static PyObject* wrapper_unit_damage_from( wesnoth_unit* unit, PyObject* args )
|
|||
wesnoth_attacktype* attack;
|
||||
if ( !PyArg_ParseTuple( args, "O!", &wesnoth_attacktype_type, &attack ) )
|
||||
return NULL;
|
||||
if (!running_instance->is_unit_valid(unit->unit_)){
|
||||
running_instance->set_error(MSG_UNIT);
|
||||
return NULL;
|
||||
}
|
||||
u_check;
|
||||
static gamemap::location no_loc;
|
||||
return Py_BuildValue("i",unit->unit_->damage_from(*attack->attack_type_,true,no_loc));
|
||||
}
|
||||
|
@ -620,7 +585,7 @@ static PyObject* wrapper_unit_damage_from( wesnoth_unit* unit, PyObject* args )
|
|||
static PyObject* wrapper_unittype_resistance_against( wesnoth_unittype* type, PyObject* args )
|
||||
{
|
||||
wesnoth_attacktype* attack;
|
||||
if ( !PyArg_ParseTuple( args, "O!", &wesnoth_attacktype_type, &attack ) )
|
||||
if (!PyArg_ParseTuple(args, "O!", &wesnoth_attacktype_type, &attack))
|
||||
return NULL;
|
||||
return Py_BuildValue("i",type->unit_type_->movement_type().resistance_against(*attack->attack_type_));
|
||||
}
|
||||
|
@ -1311,26 +1276,20 @@ PyObject* python_ai::wrapper_get_units(PyObject* /*self*/, PyObject* args)
|
|||
|
||||
PyObject* python_ai::wrapper_get_location(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
int x, y;
|
||||
if ( !PyArg_ParseTuple( args, "ii", &x, &y ) )
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple( args, "ii", &x, &y ))
|
||||
return NULL;
|
||||
if(x < 0 || x >= running_instance->get_info().map.x()){
|
||||
running_instance->set_error("Invalid x value %d", x);
|
||||
return NULL;
|
||||
}
|
||||
if(y < 0 || y >= running_instance->get_info().map.y()){
|
||||
running_instance->set_error("Invalid y value %d", y);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= running_instance->get_info().map.x()) return_none;
|
||||
if (y < 0 || y >= running_instance->get_info().map.y()) return_none;
|
||||
|
||||
gamemap::location loc(x,y);
|
||||
return wrap_location(loc);
|
||||
return wrap_location(loc);
|
||||
}
|
||||
|
||||
PyObject* python_ai::wrapper_get_map(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
if ( !PyArg_ParseTuple( args, "" ) )
|
||||
return NULL;
|
||||
if (!PyArg_ParseTuple(args, "" )) return NULL;
|
||||
|
||||
wesnoth_gamemap* map = (wesnoth_gamemap*)PyObject_NEW(wesnoth_gamemap, &wesnoth_gamemap_type);
|
||||
map->map_ = &running_instance->get_info().map;
|
||||
return (PyObject*)map;
|
||||
|
@ -1338,8 +1297,7 @@ PyObject* python_ai::wrapper_get_map(PyObject* /*self*/, PyObject* args)
|
|||
|
||||
PyObject* python_ai::wrapper_get_teams(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
if ( !PyArg_ParseTuple( args, "" ) )
|
||||
return NULL;
|
||||
if (!PyArg_ParseTuple(args, "" )) return NULL;
|
||||
|
||||
PyObject* list = PyList_New(running_instance->get_info().teams.size());
|
||||
wesnoth_team* the_team;
|
||||
|
@ -1386,8 +1344,9 @@ PyObject* python_ai::wrapper_move_unit(PyObject* /*self*/, PyObject* args)
|
|||
{
|
||||
wesnoth_location* from;
|
||||
wesnoth_location* to;
|
||||
if ( !PyArg_ParseTuple( args, "O!O!", &wesnoth_location_type, &from, &wesnoth_location_type, &to ) )
|
||||
return NULL;
|
||||
if (!PyArg_ParseTuple( args, "O!O!", &wesnoth_location_type, &from,
|
||||
&wesnoth_location_type, &to ) )
|
||||
return NULL;
|
||||
|
||||
bool valid = false;
|
||||
ai_interface::move_map::const_iterator pos;
|
||||
|
@ -1400,36 +1359,30 @@ PyObject* python_ai::wrapper_move_unit(PyObject* /*self*/, PyObject* args)
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!valid)
|
||||
{
|
||||
set_error("No unit at specified position.");
|
||||
return NULL;
|
||||
}
|
||||
if (pos == running_instance->src_dst_.end())
|
||||
{
|
||||
set_error("This unit can't go to specified destination.");
|
||||
return NULL;
|
||||
}
|
||||
if (!valid) return_none;
|
||||
if (pos == running_instance->src_dst_.end()) return_none;
|
||||
|
||||
location location;
|
||||
try {
|
||||
wrap(
|
||||
location = running_instance->move_unit_partial(
|
||||
*from->location_, *to->location_, running_instance->possible_moves_);
|
||||
}
|
||||
catch(end_level_exception& e) {
|
||||
running_instance->exception = e;
|
||||
PyErr_SetString(PyExc_RuntimeError, "Game aborted!");
|
||||
return NULL;
|
||||
}
|
||||
)
|
||||
|
||||
PyObject* loc = wrap_location(location);
|
||||
running_instance->src_dst_.clear();
|
||||
running_instance->dst_src_.clear();
|
||||
running_instance->possible_moves_.clear();
|
||||
running_instance->calculate_possible_moves(running_instance->possible_moves_,running_instance->src_dst_,running_instance->dst_src_,false);
|
||||
running_instance->calculate_possible_moves(
|
||||
running_instance->possible_moves_,
|
||||
running_instance->src_dst_,
|
||||
running_instance->dst_src_,false);
|
||||
running_instance->enemy_src_dst_.clear();
|
||||
running_instance->enemy_dst_src_.clear();
|
||||
running_instance->enemy_possible_moves_.clear();
|
||||
running_instance->calculate_possible_moves(running_instance->enemy_possible_moves_,running_instance->enemy_src_dst_,running_instance->enemy_dst_src_,true);
|
||||
running_instance->calculate_possible_moves(
|
||||
running_instance->enemy_possible_moves_,
|
||||
running_instance->enemy_src_dst_,
|
||||
running_instance->enemy_dst_src_,true);
|
||||
return loc;
|
||||
}
|
||||
|
||||
|
@ -1438,16 +1391,15 @@ PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args)
|
|||
wesnoth_location* from;
|
||||
wesnoth_location* to;
|
||||
int weapon = -1; // auto-choose
|
||||
if ( !PyArg_ParseTuple( args, "O!O!|i", &wesnoth_location_type, &from, &wesnoth_location_type, &to, &weapon ) )
|
||||
return NULL;
|
||||
if (!PyArg_ParseTuple( args, "O!O!|i", &wesnoth_location_type, &from,
|
||||
&wesnoth_location_type, &to, &weapon ) )
|
||||
return NULL;
|
||||
|
||||
// FIXME: Remove this check and let the C++ code do the check if the attack
|
||||
// is valid at all (there may be ranged attacks or similar later, and then
|
||||
// the below will horribly fail).
|
||||
if (!tiles_adjacent(*from->location_, *to->location_)) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
if (!tiles_adjacent(*from->location_, *to->location_))
|
||||
return_none;
|
||||
|
||||
info& inf = running_instance->get_info();
|
||||
|
||||
|
@ -1461,16 +1413,12 @@ PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args)
|
|||
*to->location_,
|
||||
weapon);
|
||||
|
||||
try {
|
||||
wrap(
|
||||
running_instance->attack_enemy(*from->location_,*to->location_,
|
||||
bc.get_attacker_stats().attack_num,
|
||||
bc.get_defender_stats().attack_num);
|
||||
}
|
||||
catch(end_level_exception& e) {
|
||||
running_instance->exception = e;
|
||||
PyErr_SetString(PyExc_RuntimeError, "Game is won!");
|
||||
return NULL;
|
||||
}
|
||||
)
|
||||
|
||||
running_instance->src_dst_.clear();
|
||||
running_instance->dst_src_.clear();
|
||||
running_instance->possible_moves_.clear();
|
||||
|
@ -1487,8 +1435,8 @@ PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args)
|
|||
PyObject* python_ai::wrapper_get_adjacent_tiles(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
wesnoth_location* where;
|
||||
if ( !PyArg_ParseTuple( args, "O!", &wesnoth_location_type, &where ) )
|
||||
return NULL;
|
||||
if (!PyArg_ParseTuple( args, "O!", &wesnoth_location_type, &where))
|
||||
return NULL;
|
||||
|
||||
gamemap const &map = running_instance->get_info().map;
|
||||
PyObject* list = PyList_New(0);
|
||||
|
@ -1504,14 +1452,16 @@ PyObject* python_ai::wrapper_recruit_unit(PyObject* /*self*/, PyObject* args)
|
|||
{
|
||||
wesnoth_location* where;
|
||||
const char* name;
|
||||
if ( !PyArg_ParseTuple( args, "sO!", &name, &wesnoth_location_type, &where ) )
|
||||
return NULL;
|
||||
return Py_BuildValue("i", running_instance->recruit(name,*where->location_) == true ? 1 : 0);
|
||||
if (!PyArg_ParseTuple( args, "sO!", &name, &wesnoth_location_type, &where))
|
||||
return NULL;
|
||||
wrap(
|
||||
return Py_BuildValue("i", running_instance->recruit(name,*where->location_) == true ? 1 : 0);
|
||||
)
|
||||
}
|
||||
|
||||
PyObject* python_ai::wrapper_get_gamestatus(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
if ( !PyArg_ParseTuple( args, "" ) )
|
||||
if (!PyArg_ParseTuple(args, ""))
|
||||
return NULL;
|
||||
wesnoth_gamestatus* status;
|
||||
status = (wesnoth_gamestatus*)PyObject_NEW(wesnoth_gamestatus, &wesnoth_gamestatus_type);
|
||||
|
@ -1553,14 +1503,13 @@ PyObject* python_ai::wrapper_unit_attack_statistics(wesnoth_unit* self, PyObject
|
|||
wesnoth_location* to;
|
||||
int weapon = -1;
|
||||
|
||||
if ( !PyArg_ParseTuple( args, "O!O!|i", &wesnoth_location_type, &from,
|
||||
&wesnoth_location_type, &to, &weapon) )
|
||||
return NULL;
|
||||
if (!running_instance->is_unit_valid(self->unit_))
|
||||
return NULL;
|
||||
if (!PyArg_ParseTuple(args, "O!O!|i", &wesnoth_location_type, &from,
|
||||
&wesnoth_location_type, &to, &weapon))
|
||||
return NULL;
|
||||
if (!running_instance->is_unit_valid(self->unit_))
|
||||
return_none;
|
||||
if (weapon < -1 || weapon >= (int) self->unit_->attacks().size()){
|
||||
set_error("Invalid weapon %d", weapon);
|
||||
return NULL;
|
||||
return_none;
|
||||
}
|
||||
|
||||
info& inf = running_instance->get_info();
|
||||
|
@ -1783,8 +1732,10 @@ void python_ai::play_turn()
|
|||
python_code += "'" + *i + "ais', ";
|
||||
}
|
||||
python_code += "])\n"
|
||||
"import wesnoth, safe, heapq, random\n"
|
||||
"try:\n"
|
||||
" execfile(\"" + script + "\")\n"
|
||||
" code = file(\"" + script + "\").read()\n"
|
||||
" safe.safe_exec(code, {\"wesnoth\" : wesnoth, \"heapq\" : heapq, \"random\" : random})\n"
|
||||
"finally:\n"
|
||||
" sys.path = backup\n";
|
||||
PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input,
|
||||
|
@ -1794,7 +1745,8 @@ void python_ai::play_turn()
|
|||
Py_DECREF(globals);
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
// RuntimeError is the game-won exception, no need to print it
|
||||
// RuntimeError is the game-won exception, no need to print it.
|
||||
// Anything else likely is a mistake by the script author.
|
||||
if (!PyErr_ExceptionMatches(PyExc_RuntimeError)) {
|
||||
LOG_AI << "Python script has crashed.\n";
|
||||
PyErr_Print();
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
|
||||
static void set_error(const char *fmt, ...);
|
||||
|
||||
static bool is_unit_valid(const unit* unit, bool do_set_error = true);
|
||||
static bool is_unit_valid(const unit* unit);
|
||||
std::vector<team>& get_teams() { return get_info().teams; }
|
||||
static std::vector<std::string> get_available_scripts();
|
||||
protected:
|
||||
|
|
Loading…
Add table
Reference in a new issue