2274 lines
71 KiB
C++
2274 lines
71 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2009 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
or at your option any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/**
|
|
* @file ai_python.cpp
|
|
* Interface to python for ai-scripts.
|
|
*
|
|
* Note this file isn't C-style cast safe:
|
|
* PyObject_NEW this macro contains 2 casts
|
|
* Py_DECREF this macro contains 3 casts
|
|
* Py_InitModule3 this macro contains 1 cast
|
|
* Py_XDECREF this macro contains 3 casts
|
|
*/
|
|
|
|
/* 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"
|
|
|
|
#include "ai.hpp"
|
|
#include "ai_python.hpp"
|
|
#include "attack_prediction.hpp"
|
|
#include "gamestatus.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "random.hpp"
|
|
#include "log.hpp"
|
|
#include "map.hpp"
|
|
#include "game_end_exceptions.hpp"
|
|
#include "game_events.hpp"
|
|
#include "game_config.hpp"
|
|
#include "settings.hpp"
|
|
#include "game_preferences.hpp"
|
|
|
|
#include <cassert>
|
|
#include <fstream>
|
|
#include <marshal.h>
|
|
|
|
#define LOG_AI LOG_STREAM(info, ai)
|
|
|
|
#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION == 4
|
|
#define Py_ssize_t int
|
|
#endif
|
|
|
|
static python_ai* running_instance;
|
|
bool python_ai::init_ = false;
|
|
|
|
// Appeace gcc 2.4 which doesn't want us pass inline strings to the Python API.
|
|
#define CC(x) const_cast<char *>(x)
|
|
#define STRINGVALUE CC("s")
|
|
#define INTVALUE CC("i")
|
|
#define DOUBLEVALUE CC("d")
|
|
#define NOVALUE CC("")
|
|
#define OBVALUE CC("O!")
|
|
|
|
#define GSDEF(x, func, doc) \
|
|
{CC(x), reinterpret_cast<getter>(func), NULL,\
|
|
CC(doc), NULL },
|
|
|
|
static PyObject* wrap_unittype(const unit_type& type);
|
|
static PyObject* wrap_attacktype(const attack_type& type);
|
|
static PyObject* wrapper_unittype_resistance_against(wesnoth_unittype* type, PyObject* args);
|
|
|
|
static PyObject* wrapper_unittype_get_name(wesnoth_unittype* unit, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(STRINGVALUE, unit->unit_type_->id().c_str());
|
|
}
|
|
|
|
#define ut_get_general( t, n, x ) \
|
|
static PyObject* wrapper_unittype_get_##n( wesnoth_unittype* type, void* /*closure*/ ) \
|
|
{ \
|
|
return Py_BuildValue(t,type->unit_type_->x); \
|
|
}
|
|
|
|
#define ut_get( x ) \
|
|
ut_get_general(INTVALUE, x, x() )
|
|
|
|
#define ut_get_ability( x ) \
|
|
ut_get_general(INTVALUE, x, has_ability(#x))
|
|
|
|
#define ut_get_ability_by_id( x ) \
|
|
ut_get_general(INTVALUE, x, has_ability_by_id(#x))
|
|
|
|
ut_get_ability( heals )
|
|
ut_get_ability( regenerate )
|
|
ut_get_ability( leadership )
|
|
ut_get_ability( illuminates )
|
|
ut_get_ability( skirmisher )
|
|
ut_get_ability( teleport )
|
|
ut_get_ability_by_id( curing )
|
|
ut_get_ability_by_id( steadfast )
|
|
ut_get( not_living )
|
|
ut_get( can_advance )
|
|
ut_get( has_zoc )
|
|
ut_get( level )
|
|
ut_get( movement )
|
|
ut_get( cost )
|
|
ut_get( alignment )
|
|
ut_get( hitpoints )
|
|
static PyObject* wrapper_unittype_get_usage( wesnoth_unittype* type, void* /*closure*/ )
|
|
{
|
|
return Py_BuildValue(STRINGVALUE, type->unit_type_->usage().c_str());
|
|
}
|
|
|
|
#define ut_gs( x, doc ) \
|
|
{CC(#x), reinterpret_cast<getter>(wrapper_unittype_get_##x), NULL, CC(doc), NULL},
|
|
|
|
static PyGetSetDef unittype_getseters[] = {
|
|
ut_gs(name, "Name of the unit type." )
|
|
ut_gs(heals, "If type can heal others (either healing or curing ability)." )
|
|
ut_gs(curing, "If type has curing ability (remove poison from others)." )
|
|
ut_gs(regenerate, "If type has regenerate ability." )
|
|
ut_gs(leadership, "If type has leadership ability." )
|
|
ut_gs(illuminates, "If type has illuminates ability." )
|
|
ut_gs(skirmisher, "If type has skirmisher ability." )
|
|
ut_gs(teleport, "If type has teleport ability." )
|
|
ut_gs(steadfast, "If type has steadfast ability." )
|
|
ut_gs(not_living, "If type has not-living ability." )
|
|
ut_gs(can_advance, "If type can advance." )
|
|
ut_gs(has_zoc, "If type has a ZOC." )
|
|
ut_gs(level, "Level of the type." )
|
|
ut_gs(hitpoints, "Hitpoints of the type." )
|
|
ut_gs(usage, "AI's usage hint of the type, one of: 'archer', 'fighter', "
|
|
"'healer', 'mixed fighter', 'scout'." )
|
|
ut_gs(movement, "Movement points of the type." )
|
|
ut_gs(cost, "Cost of the type." )
|
|
ut_gs(alignment, "Alignment of the type: 0=lawful, 1=neutral, 2=chaotic." )
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
PyObject* python_ai::unittype_advances_to( wesnoth_unittype* type, PyObject* args )
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
return NULL;
|
|
|
|
PyObject* list = PyList_New(type->unit_type_->advances_to().size());
|
|
int r;
|
|
for (size_t advance = 0; advance < type->unit_type_->advances_to().size(); advance++)
|
|
{
|
|
std::map<std::string,unit_type>::const_iterator t =
|
|
unit_type_data::types().find_unit_type(type->unit_type_->advances_to()[advance]);
|
|
|
|
assert(t != unit_type_data::types().end());
|
|
r = PyList_SetItem(list,advance,wrap_unittype(t->second));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
static PyObject* wrapper_unittype_attacks( wesnoth_unittype* type, PyObject* args )
|
|
{
|
|
if ( !PyArg_ParseTuple( args, NOVALUE ) )
|
|
return NULL;
|
|
|
|
const std::vector<attack_type>& attacks = type->unit_type_->attacks();
|
|
PyObject* list = PyList_New(attacks.size());
|
|
for ( size_t attack = 0; attack < attacks.size(); attack++)
|
|
PyList_SetItem(list,attack,wrap_attacktype(attacks[attack]));
|
|
return list;
|
|
}
|
|
|
|
#define MDEF(name, func, docs) { CC(name), reinterpret_cast<PyCFunction>(func),\
|
|
METH_VARARGS, CC(docs) },
|
|
static PyMethodDef unittype_methods[] = {
|
|
MDEF("advances_to",
|
|
python_ai::unittype_advances_to,
|
|
"Returns: unittype[]\n"
|
|
"Returns a list of wesnoth.unittype of possible advancements.")
|
|
MDEF("attacks",
|
|
wrapper_unittype_attacks,
|
|
"Returns: attacktype[]\n"
|
|
"Returns list of possible attack types.\n")
|
|
MDEF( "movement_cost",
|
|
python_ai::wrapper_unittype_movement_cost,
|
|
"Parameters: location\n"
|
|
"Returns: cost\n"
|
|
"Returns the cost of moving over the given location.")
|
|
MDEF( "defense_modifier",
|
|
python_ai::wrapper_unittype_defense_modifier,
|
|
"Parameters: location\n"
|
|
"Returns: percent\n"
|
|
"Returns the defense modifier in % (probability the unit will be hit)"
|
|
" on the given location.")
|
|
MDEF("damage_from",
|
|
wrapper_unittype_resistance_against,
|
|
"Parameters: attacktype\n"
|
|
"Returns: percent\n"
|
|
"Returns the damage in percent a unit of this type receives when "
|
|
"attacked with the given attack type. (0 means no damage at all, 100 "
|
|
"means full damage, 200 means double damage.)")
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static int unittype_internal_compare(wesnoth_unittype* left,
|
|
wesnoth_unittype* right)
|
|
{
|
|
return reinterpret_cast<long>(left->unit_type_) -
|
|
reinterpret_cast<long>(right->unit_type_);
|
|
}
|
|
|
|
static PyTypeObject wesnoth_unittype_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.unittype"), /* tp_name*/
|
|
sizeof(wesnoth_unittype), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
0, /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
reinterpret_cast<cmpfunc>
|
|
(unittype_internal_compare), /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
0, /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("Describes a unit type."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
unittype_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
unittype_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
static PyObject* wrap_unittype(const unit_type& type)
|
|
{
|
|
wesnoth_unittype* wrap = reinterpret_cast<wesnoth_unittype*>(PyObject_NEW(wesnoth_unittype, &wesnoth_unittype_type));
|
|
if (wrap)
|
|
wrap->unit_type_ = &type;
|
|
return reinterpret_cast<PyObject*>(wrap);
|
|
}
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
const attack_type* attack_type_;
|
|
} wesnoth_attacktype;
|
|
|
|
#define at_get_general( t, n, x ) \
|
|
static PyObject* attacktype_get_##n(wesnoth_attacktype* type, void* /*closure*/) \
|
|
{ \
|
|
return Py_BuildValue(t,type->attack_type_->x); \
|
|
}
|
|
|
|
#define at_get( t, x ) \
|
|
at_get_general( t, x, x() )
|
|
|
|
#define at_get_ability_by_id(x) \
|
|
at_get_general(INTVALUE, x, has_special_by_id(#x))
|
|
|
|
#define at_get_string_prop( x ) \
|
|
at_get_general(STRINGVALUE, x, x().c_str() )
|
|
|
|
at_get(INTVALUE, damage )
|
|
at_get(INTVALUE, num_attacks )
|
|
at_get(DOUBLEVALUE, attack_weight )
|
|
at_get(DOUBLEVALUE, defense_weight )
|
|
|
|
at_get_ability_by_id(backstab )
|
|
at_get_ability_by_id(slow )
|
|
at_get_ability_by_id(berserk )
|
|
at_get_ability_by_id(stones )
|
|
at_get_ability_by_id(plague )
|
|
at_get_ability_by_id(marksman )
|
|
at_get_ability_by_id(magical )
|
|
at_get_ability_by_id(charge )
|
|
at_get_ability_by_id(drains )
|
|
at_get_ability_by_id(poison )
|
|
|
|
at_get_string_prop( name )
|
|
at_get_string_prop( range )
|
|
|
|
static void wesnoth_attacktype_dealloc(wesnoth_attacktype* self)
|
|
{
|
|
delete self->attack_type_;
|
|
self->ob_type->tp_free(reinterpret_cast<PyObject*>(self));
|
|
}
|
|
|
|
#define at_gs( x, doc ) \
|
|
{CC(#x), reinterpret_cast<getter>(attacktype_get_##x), NULL, CC(doc), NULL},
|
|
|
|
static PyGetSetDef attacktype_getseters[] = {
|
|
at_gs( name, "Name of the attack." )
|
|
at_gs( damage, "Attack damage." )
|
|
at_gs( num_attacks, "Number of hits." )
|
|
at_gs( attack_weight, "AI setting, floating point number." )
|
|
at_gs( defense_weight, "AI setting, floating point number." )
|
|
at_gs( backstab, "This attack has backstab." )
|
|
at_gs( slow, "This attack causes slow." )
|
|
at_gs( berserk, "This attack uses berserk." )
|
|
at_gs( stones, "This attack has 'stones' special." )
|
|
at_gs( plague, "This attack has 'plague' special." )
|
|
at_gs( marksman, "This attack has 'marksman' special." )
|
|
at_gs( magical, "This attack is magical." )
|
|
at_gs( charge, "This attack has the 'charge' special." )
|
|
at_gs( drains, "This attack has the 'drains' special." )
|
|
at_gs( poison, "This attack has the 'poison' special." )
|
|
at_gs( range, "String with the name of the attack range." )
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
static PyMethodDef attacktype_methods[] = {
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static PyTypeObject wesnoth_attacktype_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.attacktype"), /* tp_name*/
|
|
sizeof(wesnoth_attacktype), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
reinterpret_cast<destructor>
|
|
(wesnoth_attacktype_dealloc), /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
0, /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, //UniConvert, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
0, /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("Describes an attack type."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
attacktype_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
attacktype_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
static PyObject* wrap_attacktype(const attack_type& type)
|
|
{
|
|
wesnoth_attacktype* attack;
|
|
attack = static_cast<wesnoth_attacktype*>(PyObject_NEW(wesnoth_attacktype, &wesnoth_attacktype_type));
|
|
|
|
attack->attack_type_ = new attack_type(type);
|
|
return reinterpret_cast<PyObject*>(attack);
|
|
}
|
|
|
|
#define u_check if (!running_instance->is_unit_valid(unit->unit_)) Py_RETURN_NONE
|
|
|
|
bool python_ai::is_unit_valid(const unit* unit)
|
|
{
|
|
bool ret = false ;
|
|
if (unit) {
|
|
Py_BEGIN_ALLOW_THREADS
|
|
for(unit_map::const_iterator i = running_instance->get_info().units.begin(); \
|
|
i != running_instance->get_info().units.end(); ++i) {
|
|
if (unit == &i->second) {
|
|
ret = true ;
|
|
break ;
|
|
}
|
|
}
|
|
Py_END_ALLOW_THREADS
|
|
}
|
|
|
|
return ret ;
|
|
}
|
|
|
|
static PyObject* unit_get_name(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(STRINGVALUE, lexical_cast<std::string>(unit->unit_->underlying_id()).c_str());
|
|
}
|
|
|
|
static PyObject* unit_is_enemy(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
bool enemy ;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
enemy = running_instance->current_team().is_enemy( unit->unit_->side() ) ;
|
|
Py_END_ALLOW_THREADS
|
|
|
|
return PyBool_FromLong( enemy ) ;
|
|
}
|
|
|
|
static PyObject* unit_can_recruit(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return PyBool_FromLong( unit->unit_->can_recruit() ) ;
|
|
}
|
|
|
|
static PyObject* unit_side(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, unit->unit_->side());
|
|
}
|
|
|
|
static PyObject* unit_movement_left(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, unit->unit_->movement_left());
|
|
}
|
|
|
|
static PyObject* unit_max_movement(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, unit->unit_->total_movement());
|
|
}
|
|
|
|
static PyObject* unit_can_attack(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
bool bValue ;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
bValue = unit->unit_->attacks_left() ;
|
|
Py_END_ALLOW_THREADS
|
|
|
|
return PyBool_FromLong( bValue ) ;
|
|
}
|
|
|
|
static PyObject* unit_hitpoints(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, static_cast<int>(unit->unit_->hitpoints()));
|
|
}
|
|
|
|
static PyObject* unit_max_hitpoints(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, static_cast<int>(unit->unit_->max_hitpoints()));
|
|
}
|
|
|
|
static PyObject* unit_experience(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, static_cast<int>(unit->unit_->experience()));
|
|
}
|
|
|
|
static PyObject* unit_max_experience(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, static_cast<int>(unit->unit_->max_experience()));
|
|
}
|
|
|
|
static PyObject* unit_poisoned(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return Py_BuildValue(INTVALUE, utils::string_bool(unit->unit_->get_state("poisoned")));
|
|
}
|
|
|
|
static PyObject* unit_slowed(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return PyBool_FromLong( utils::string_bool( unit->unit_->get_state("poisoned") ) ) ;
|
|
}
|
|
|
|
static PyObject* unit_stoned(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check;
|
|
return PyBool_FromLong( utils::string_bool( unit->unit_->get_state("stoned") ) ) ;
|
|
}
|
|
|
|
// @todo: Make Py_RETURN_TRUE and Py_RETURN_FALSE work properly with
|
|
// gcc 4.3.x series compilers. Latest compilers are much more strict. This
|
|
// will likely require a change from the python folks. Until then, use the
|
|
// version below.
|
|
static PyObject* unit_query_valid(wesnoth_unit* unit, void* /*closure*/)
|
|
{
|
|
u_check ;
|
|
// if( running_instance->is_unit_valid(unit->unit_) )
|
|
// Py_RETURN_TRUE ;
|
|
|
|
// Py_RETURN_FALSE ;
|
|
|
|
return PyBool_FromLong( running_instance->is_unit_valid(unit->unit_) == true ? 1 : 0 ) ;
|
|
}
|
|
|
|
static PyGetSetDef unit_getseters[] = {
|
|
GSDEF("name", unit_get_name,
|
|
"Name of the unit (''description'' from WML).")
|
|
GSDEF("is_enemy",unit_is_enemy,
|
|
"True if this is an enemy unit, False if it is allied.")
|
|
GSDEF("can_recruit", unit_can_recruit,
|
|
"If the unit can recruit.")
|
|
GSDEF("hitpoints", unit_hitpoints,
|
|
"Current hitpoints of the unit.")
|
|
GSDEF("max_hitpoints", unit_max_hitpoints,
|
|
"Maximum hitpoints of the unit.")
|
|
GSDEF("experience", unit_experience,
|
|
"Current experience of the unit.")
|
|
GSDEF("max_experience", unit_max_experience,
|
|
"Maximum experience of the unit.")
|
|
GSDEF("is_valid",unit_query_valid,
|
|
"Indicates if the unit is still valid in the game. This is the only accessible "
|
|
"field of an invalid unit, all others trigger an exception.")
|
|
GSDEF("side", unit_side,
|
|
"The side of the unit, starting with 1.")
|
|
GSDEF("movement_left", unit_movement_left,
|
|
"How many movement points the unit has left.")
|
|
GSDEF("max_movement", unit_max_movement,
|
|
"Maximum movement points of the unit.")
|
|
GSDEF("can_attack", unit_can_attack,
|
|
"If the unit can still attack.")
|
|
GSDEF("poisoned", unit_poisoned,
|
|
"If the unit is poisoned.")
|
|
GSDEF("slowed", unit_slowed,
|
|
"If the unit is slowed.")
|
|
GSDEF("stoned", unit_stoned,
|
|
"If the unit is stoned.")
|
|
{NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
static PyObject* wrapper_unit_type( wesnoth_unit* unit, PyObject* args )
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
return NULL;
|
|
u_check;
|
|
assert(unit->unit_->type());
|
|
return wrap_unittype(*unit->unit_->type());
|
|
}
|
|
|
|
static PyObject* wrapper_unit_attacks( wesnoth_unit* unit, PyObject* args )
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
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]));
|
|
return list;
|
|
}
|
|
|
|
static PyObject* wrapper_unit_damage_from( wesnoth_unit* unit, PyObject* args )
|
|
{
|
|
wesnoth_attacktype* attack;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_attacktype_type, &attack ) )
|
|
return NULL;
|
|
u_check;
|
|
static map_location no_loc;
|
|
return Py_BuildValue(INTVALUE,unit->unit_->damage_from(*attack->attack_type_,true,no_loc));
|
|
}
|
|
|
|
static PyObject* wrapper_unittype_resistance_against( wesnoth_unittype* type, PyObject* args )
|
|
{
|
|
wesnoth_attacktype* attack;
|
|
if (!PyArg_ParseTuple(args, OBVALUE, &wesnoth_attacktype_type, &attack))
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE,type->unit_type_->movement_type().resistance_against(*attack->attack_type_));
|
|
}
|
|
|
|
|
|
static PyMethodDef unit_methods[] = {
|
|
MDEF("type", wrapper_unit_type,
|
|
"Returns: unittype\n"
|
|
"Returns the type of the unit.")
|
|
MDEF("attacks", wrapper_unit_attacks,
|
|
"Returns: attacktype[]\n"
|
|
"Returns list of possible attack types.\n")
|
|
MDEF("movement_cost", python_ai::wrapper_unit_movement_cost,
|
|
"Parameters: location\n"
|
|
"Returns: cost\n"
|
|
"Returns the cost of moving over the given location.")
|
|
MDEF("defense_modifier", python_ai::wrapper_unit_defense_modifier,
|
|
"Parameters: location\n"
|
|
"Returns: percent\n"
|
|
"Returns the defense modifier in % (probability the unit will be hit) on the given location.")
|
|
MDEF("damage_from", wrapper_unit_damage_from,
|
|
"Parameters: attacktype\n"
|
|
"Returns: percent\n"
|
|
"Returns the damage in percent the unit receives when attacked with "
|
|
"the given attack type. (0 means no damage at all, 100 means full "
|
|
"damage, 200 means double damage.)")
|
|
MDEF("find_path", python_ai::wrapper_unit_find_path,
|
|
"Parameters: location from, location to, float max_cost = unit.movement_left\n"
|
|
"Returns: location[] path\n"
|
|
"Finds a path from 'from' to 'to' costing less than 'max_cost' "
|
|
"movement points to reach and returns it as a list of locations. "
|
|
"path[0] will be 'from', path[-1] will be 'to'. "
|
|
"If no path can be found (for example, if the target is not reachable, "
|
|
"or it would cost more than max_cost), an empty list is returned.")
|
|
MDEF("attack_statistics", python_ai::wrapper_unit_attack_statistics,
|
|
"Parameters: location from, location to, int attack = -1\n"
|
|
"Returns: own_hp, enemy_hp\n"
|
|
"Returns two dictionaries with the expected battle results when the "
|
|
"unit attacks from 'from' to the unit at 'to', optionally using the "
|
|
"attack with index 'attack', or if no attack is given the attack which "
|
|
"would be presented to the player in the attack dialog. The "
|
|
"dictionaries contain the expected hitpoints after "
|
|
"the fight, as a mapping from hitpoints to percent, where percent are "
|
|
"specified as floating point value from 0 to 1. For example, a return of: "
|
|
"{0:1}, {50:0.5, 40:0.5} would mean, the attacking unit "
|
|
"is certain to die (probability for 0 hitpoints is 1), and the enemy "
|
|
"unit will either remain at 50 or 40 HP after the fight, with equal "
|
|
"probability of 0.5.")
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
|
|
static int unit_internal_compare(wesnoth_unit* left, wesnoth_unit* right)
|
|
{
|
|
return reinterpret_cast<long>(left->unit_) - reinterpret_cast<long>(right->unit_);
|
|
}
|
|
|
|
static PyTypeObject wesnoth_unit_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.unit"), /* tp_name*/
|
|
sizeof(wesnoth_unit), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
0, /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
reinterpret_cast<cmpfunc>(unit_internal_compare), /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, //UniConvert, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
0, /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("Represents a single unit. Trying to use a method or access a property, "
|
|
"with the exception of is_valid, will result in an exception if the unit "
|
|
"is invalid (was destroyed last move, and so on)."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
unit_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
unit_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
map_location* location_;
|
|
} wesnoth_location;
|
|
|
|
static void wesnoth_location_dealloc(wesnoth_location* self)
|
|
{
|
|
delete self->location_;
|
|
self->location_ = NULL;
|
|
PyObject_Del(reinterpret_cast<PyObject*>(self));
|
|
}
|
|
|
|
static PyObject* location_get_x(wesnoth_location* location, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, location->location_->x);
|
|
}
|
|
static PyObject* location_get_y(wesnoth_location* location, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, location->location_->y);
|
|
}
|
|
|
|
static int location_internal_compare(wesnoth_location* left, wesnoth_location* right)
|
|
{
|
|
if (*left->location_ == *right->location_)
|
|
return 0;
|
|
return *left->location_ < *right->location_ ? -1 : 1;
|
|
}
|
|
|
|
static long location_internal_hash(wesnoth_location* obj)
|
|
{
|
|
// Never return -1, which is reserved for raising an exception.
|
|
// Note that both x and y can get values < 0,
|
|
// e.g. when checking all positions in a certain radius at the map border.
|
|
unsigned char x = static_cast<unsigned>(obj->location_->x);
|
|
unsigned char y = static_cast<unsigned>(obj->location_->y);
|
|
return (x << 8) + y;
|
|
}
|
|
|
|
static PyGetSetDef location_getseters[] = {
|
|
GSDEF("x", location_get_x,
|
|
"X position, starting with 0 for leftmost column.")
|
|
GSDEF("y", location_get_y,
|
|
"Y position, starting with 0 for topmost row.")
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
static PyObject* wrapper_location_adjacent_to( wesnoth_location* left, PyObject* args );
|
|
static PyObject* wrapper_location_distance_to( wesnoth_location* left, PyObject* args );
|
|
static PyMethodDef location_methods[] = {
|
|
MDEF("adjacent_to", wrapper_location_adjacent_to,
|
|
"Parameters: location\n"
|
|
"Returns: result\n"
|
|
"Returns True if the location is adjacent to this one, False otherwise.")
|
|
MDEF("distance_to", wrapper_location_distance_to,
|
|
"Parameters: location\n"
|
|
"Returns: int distance\n"
|
|
"Returns the distance in hexes to the other location.")
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static PyTypeObject wesnoth_location_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.location"), /* tp_name*/
|
|
sizeof(wesnoth_location), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
reinterpret_cast<destructor>(wesnoth_location_dealloc), /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
reinterpret_cast<cmpfunc>(location_internal_compare), /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, //UniConvert, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
reinterpret_cast<hashfunc>(location_internal_hash), /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("Represents a single location on the map."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
location_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
location_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
static PyObject *wrap_location(const map_location& loc)
|
|
{
|
|
wesnoth_location* location;
|
|
location = reinterpret_cast<wesnoth_location*>(PyObject_NEW(wesnoth_location, &wesnoth_location_type));
|
|
location->location_ = new map_location(loc.x, loc.y);
|
|
return reinterpret_cast<PyObject*>(location);
|
|
}
|
|
|
|
void recalculate_movemaps()
|
|
{
|
|
python_ai *ai = running_instance;
|
|
ai->src_dst_.clear();
|
|
ai->dst_src_.clear();
|
|
ai->possible_moves_.clear();
|
|
ai->calculate_possible_moves(ai->possible_moves_,
|
|
ai->src_dst_, ai->dst_src_, false);
|
|
|
|
ai->enemy_src_dst_.clear();
|
|
ai->enemy_dst_src_.clear();
|
|
ai->enemy_possible_moves_.clear();
|
|
ai->calculate_possible_moves(ai->enemy_possible_moves_,
|
|
ai->enemy_src_dst_, ai->enemy_dst_src_, true);
|
|
}
|
|
|
|
static PyObject* wrapper_location_adjacent_to( wesnoth_location* left, PyObject* args )
|
|
{
|
|
wesnoth_location* right;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &right ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE, tiles_adjacent(*left->location_,*right->location_) ? 1 : 0);
|
|
}
|
|
|
|
static PyObject* wrapper_location_distance_to( wesnoth_location* left, PyObject* args )
|
|
{
|
|
wesnoth_location* right;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &right ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE, distance_between(*left->location_,*right->location_));
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
const gamemap* map_;
|
|
} wesnoth_gamemap;
|
|
|
|
static PyObject* gamemap_get_x(wesnoth_gamemap* map, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, map->map_->w());
|
|
}
|
|
static PyObject* gamemap_get_y(wesnoth_gamemap* map, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, map->map_->h());
|
|
}
|
|
|
|
static PyGetSetDef gamemap_getseters[] = {
|
|
GSDEF("x", gamemap_get_x, "Width of the map in hexes.")
|
|
GSDEF("y", gamemap_get_y, "Height of the map in hexes.")
|
|
{ NULL, NULL, NULL, NULL, NULL },
|
|
};
|
|
|
|
static PyObject* wrapper_getmap_is_village( wesnoth_gamemap* map, PyObject* args )
|
|
{
|
|
wesnoth_location* location;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE, map->map_->is_village(*location->location_) ? 1 : 0);
|
|
}
|
|
|
|
static PyObject* wrapper_getmap_is_keep( wesnoth_gamemap* map, PyObject* args )
|
|
{
|
|
wesnoth_location* location;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE, map->map_->is_keep(*location->location_) ? 1 : 0);
|
|
}
|
|
|
|
static PyObject* wrapper_getmap_is_castle( wesnoth_gamemap* map, PyObject* args )
|
|
{
|
|
wesnoth_location* location;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE, map->map_->is_castle(*location->location_) ? 1 : 0);
|
|
}
|
|
|
|
static PyObject* wrapper_getmap_is_border( wesnoth_gamemap* map, PyObject* args )
|
|
{
|
|
wesnoth_location* location;
|
|
bool on_board, on_board_without_borders;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) )
|
|
return NULL;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
on_board = map->map_->on_board_with_border(*location->location_);
|
|
on_board_without_borders = map->map_->on_board(*location->location_);
|
|
Py_END_ALLOW_THREADS
|
|
|
|
return Py_BuildValue(INTVALUE, on_board && !on_board_without_borders ? 1 : 0);
|
|
}
|
|
|
|
static PyMethodDef gamemap_methods[] = {
|
|
MDEF("is_village", wrapper_getmap_is_village,
|
|
"Parameters: location\n"
|
|
"Returns: result\n"
|
|
"Returns True if a village is at the given location, False otherwise.")
|
|
MDEF("is_keep", wrapper_getmap_is_keep,
|
|
"Parameters: location\n"
|
|
"Returns: result\n"
|
|
"Returns True if a keep (where a leader must stand to recruit) is at "
|
|
"the given location, False otherwise.")
|
|
MDEF("is_castle", wrapper_getmap_is_castle,
|
|
"Parameters: location\n"
|
|
"Returns: result\n"
|
|
"Returns True if the given location is a castle tile (where units are "
|
|
"recruited to), False otherwise.")
|
|
MDEF("is_border", wrapper_getmap_is_border,
|
|
"Parameters: location\n"
|
|
"Returns: result\n"
|
|
"Returns True if the given location is a border tile, False otherwise.")
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static PyTypeObject wesnoth_gamemap_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.gamemap"), /* tp_name*/
|
|
sizeof(wesnoth_gamemap), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
0, /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
0, /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, //UniConvert, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
0, /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("Represents the current map."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
gamemap_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
gamemap_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
static PyObject* wrapper_team_name(wesnoth_team* team, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(STRINGVALUE, team->team_->team_name().c_str());
|
|
}
|
|
|
|
static PyObject* wrapper_team_income(wesnoth_team* team, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, team->team_->income());
|
|
}
|
|
|
|
static PyObject* wrapper_team_gold(wesnoth_team* team, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, team->team_->gold());
|
|
}
|
|
|
|
static PyObject* wrapper_team_is_enemy(wesnoth_team* team, void* /*closure*/)
|
|
{
|
|
int result;
|
|
|
|
// Find side number of team
|
|
int side = 0;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
for (size_t t = 0; t < running_instance->get_teams().size(); t++) {
|
|
if (team->team_ == &running_instance->get_teams()[t]) {
|
|
side = 1 + t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
result = running_instance->current_team().is_enemy(side) == true ? 1 : 0;
|
|
Py_END_ALLOW_THREADS
|
|
|
|
return PyBool_FromLong( result ) ;
|
|
}
|
|
|
|
static PyObject* wrapper_team_side(wesnoth_team* team, void* /*closure*/)
|
|
{
|
|
int side = 0;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
for (size_t t = 0; t < running_instance->get_teams().size(); t++) {
|
|
if (team->team_ == &running_instance->get_teams()[t]) {
|
|
side = 1 + t;
|
|
break;
|
|
}
|
|
}
|
|
Py_END_ALLOW_THREADS
|
|
|
|
return Py_BuildValue(INTVALUE, side);
|
|
}
|
|
|
|
static int wrapper_team_internal_compare(wesnoth_team* left, wesnoth_team* right)
|
|
{
|
|
return reinterpret_cast<long>(left->team_) - reinterpret_cast<long>(right->team_);
|
|
}
|
|
|
|
static PyObject* wrapper_team_owns_village( wesnoth_team* team, PyObject* args )
|
|
{
|
|
wesnoth_location* location;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) )
|
|
return NULL;
|
|
return PyBool_FromLong( team->team_->owns_village(*location->location_) ? 1 : 0) ;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_team_recruits( wesnoth_team* team, PyObject* args )
|
|
{
|
|
if ( !PyArg_ParseTuple( args, NOVALUE) )
|
|
return NULL;
|
|
|
|
PyObject* list = PyList_New(team->team_->recruits().size());
|
|
int r;
|
|
int idx = 0;
|
|
for (std::set<std::string>::const_iterator recruit = team->team_->recruits().begin(); recruit != team->team_->recruits().end(); ++recruit)
|
|
{
|
|
std::map<std::string,unit_type>::const_iterator t = unit_type_data::types().find_unit_type(*recruit);
|
|
assert(t != unit_type_data::types().end());
|
|
r = PyList_SetItem(list,idx++,wrap_unittype(t->second));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject *python_ai::wrapper_team_targets(PyObject *, PyObject *args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
return NULL;
|
|
|
|
PyObject* dict = PyDict_New();
|
|
|
|
/**
|
|
* @todo FIXME: There should be a C++ method to return all targets instead,
|
|
* for now it's just copy&pasted.
|
|
*/
|
|
std::vector<team::target>& team_targets =
|
|
running_instance->current_team().targets();
|
|
|
|
unit_map &units = running_instance->get_info().units;
|
|
|
|
for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
|
|
// explicit targets for this team
|
|
for(std::vector<team::target>::iterator j = team_targets.begin();
|
|
j != team_targets.end(); ++j) {
|
|
if(u->second.matches_filter(&(j->criteria), u->first)) {
|
|
PyDict_SetItem(dict, wrap_location(u->first),
|
|
PyLong_FromLong(static_cast<int>(j->value)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return dict;
|
|
}
|
|
|
|
static PyMethodDef team_methods[] = {
|
|
MDEF("owns_village", wrapper_team_owns_village,
|
|
"Parameters: location\n"
|
|
"Returns: result\n"
|
|
"True if the team owns a village at the given location.")
|
|
MDEF("recruits", python_ai::wrapper_team_recruits,
|
|
"Returns: recruits\n"
|
|
"Returns a list of wesnoth.unittype objects"
|
|
" of all possible recruits for this team.")
|
|
MDEF("targets", python_ai::wrapper_team_targets,
|
|
"Returns: targets\n"
|
|
"Returns a dictionary containing all WML targets for the "
|
|
"team, mapping their locations to the scores in WML.")
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static PyGetSetDef team_getseters[] = {
|
|
GSDEF("name", wrapper_team_name, "The name of this team.")
|
|
GSDEF("gold", wrapper_team_gold, "The current amount of gold this team has.")
|
|
GSDEF("income", wrapper_team_income, "The current per-turn income if this team.")
|
|
GSDEF("side", wrapper_team_side, "Side number of this team, starting with 1.")
|
|
GSDEF("is_enemy", wrapper_team_is_enemy, "Whether this team is an enemy.")
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
static PyTypeObject wesnoth_team_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.team"), /* tp_name*/
|
|
sizeof(wesnoth_team), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
0, /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
reinterpret_cast<cmpfunc>(wrapper_team_internal_compare), /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, //UniConvert, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
0, /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("Represents one team/player/side."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
team_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
team_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
PyObject* python_ai::wrapper_unit_movement_cost( wesnoth_unit* unit, PyObject* args )
|
|
{
|
|
gamemap const &map = running_instance->get_info().map;
|
|
wesnoth_location* loc;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) )
|
|
return NULL;
|
|
u_check ;
|
|
return Py_BuildValue(INTVALUE,unit->unit_->movement_cost(map.get_terrain(*loc->location_)));
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_unit_defense_modifier( wesnoth_unit* unit, PyObject* args )
|
|
{
|
|
gamemap const &map = running_instance->get_info().map;
|
|
wesnoth_location* loc;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) )
|
|
return NULL;
|
|
u_check ;
|
|
return Py_BuildValue(INTVALUE,unit->unit_->defense_modifier(map.get_terrain(*loc->location_)));
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_unittype_movement_cost( wesnoth_unittype* type, PyObject* args )
|
|
{
|
|
gamemap const &map = running_instance->get_info().map;
|
|
wesnoth_location* loc;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE,type->unit_type_->movement_type().movement_cost(
|
|
map, map.get_terrain(*loc->location_)));
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_unittype_defense_modifier( wesnoth_unittype* type, PyObject* args )
|
|
{
|
|
gamemap const &map = running_instance->get_info().map;
|
|
wesnoth_location* loc;
|
|
if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) )
|
|
return NULL;
|
|
return Py_BuildValue(INTVALUE,type->unit_type_->movement_type().defense_modifier(
|
|
map, map.get_terrain(*loc->location_)));
|
|
}
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
const gamestatus* status_;
|
|
} wesnoth_gamestatus;
|
|
|
|
static PyMethodDef gamestatus_methods[] = {
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static PyObject* wrapper_gamestatus_turn(wesnoth_gamestatus* status, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, status->status_->turn());
|
|
}
|
|
|
|
static PyObject* wrapper_gamestatus_number_of_turns(wesnoth_gamestatus* status, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, status->status_->number_of_turns());
|
|
}
|
|
|
|
static PyObject* wrapper_gamestatus_lawful_bonus(wesnoth_gamestatus* status, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, status->status_->get_time_of_day().lawful_bonus);
|
|
}
|
|
|
|
static PyObject* wrapper_gamestatus_previous_lawful_bonus(wesnoth_gamestatus* status, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, status->status_->get_previous_time_of_day().lawful_bonus);
|
|
}
|
|
|
|
static PyObject* wrapper_gamestatus_gold_per_village(wesnoth_gamestatus* /*status*/, void* /*closure*/)
|
|
{
|
|
return Py_BuildValue(INTVALUE, settings::get_village_gold(""));
|
|
}
|
|
|
|
static PyGetSetDef gamestatus_getseters[] = {
|
|
GSDEF("turn", wrapper_gamestatus_turn, "The current turn.")
|
|
GSDEF("number_of_turns", wrapper_gamestatus_number_of_turns,
|
|
"The maximum number of turns of the whole game.")
|
|
GSDEF("lawful_bonus", wrapper_gamestatus_lawful_bonus,
|
|
"The bonus for lawful units in the current turn. This is the percentage to add to "
|
|
"the attack damage of lawful units (alignment = 0), and to subtract from chaotic "
|
|
"units (alignment = 2). Neutral units (alignment = 1) are not affected.")
|
|
GSDEF("previous_lawful_bonus", wrapper_gamestatus_previous_lawful_bonus,
|
|
"The value of lawful_bonus in the previous turn.")
|
|
GSDEF("gold_per_village", wrapper_gamestatus_gold_per_village,
|
|
"The gold a village generates each turn.")
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
static PyTypeObject wesnoth_gamestatus_type = {
|
|
PyObject_HEAD_INIT(NULL)
|
|
0, /* ob_size*/
|
|
CC("ai.gamestatus"), /* tp_name*/
|
|
sizeof(wesnoth_gamestatus), /* tp_basicsize*/
|
|
0, /* tp_itemsize*/
|
|
0, /* tp_dealloc*/
|
|
0, /* tp_print*/
|
|
0, /* tp_getattr*/
|
|
0, /* tp_setattr*/
|
|
0, /* tp_compare*/
|
|
0, /* tp_repr*/
|
|
0, //UniConvert, /* tp_as_number*/
|
|
0, /* tp_as_sequence*/
|
|
0, /* tp_as_mapping*/
|
|
0, /* tp_hash */
|
|
0, /* tp_call*/
|
|
0, /* tp_str*/
|
|
0, /* tp_getattro*/
|
|
0, /* tp_setattro*/
|
|
0, /* tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags*/
|
|
CC("This class has information about the game status."), /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
gamestatus_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
gamestatus_getseters, /* tp_getset */
|
|
NULL, /* tp_base */
|
|
NULL, /* tp_dict */
|
|
NULL, /* tp_descr_get */
|
|
NULL, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
NULL, /* tp_init */
|
|
NULL, /* tp_alloc */
|
|
NULL, /* tp_new */
|
|
NULL, /* tp_free */
|
|
NULL, /* tp_is_gc */
|
|
NULL, /* tp_bases */
|
|
NULL, /* tp_mro */
|
|
NULL, /* tp_cache */
|
|
NULL, /* tp_subclasses */
|
|
NULL, /* tp_weaklist */
|
|
NULL, /* tp_del */
|
|
#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6
|
|
0, /* tp_version_tag */
|
|
#endif
|
|
};
|
|
|
|
static PyObject* wrap_move_map(const ai_interface::move_map& wrap)
|
|
{
|
|
PyObject* dict = PyDict_New();
|
|
PyObject* list;
|
|
PyObject *loc, *loc2;
|
|
ai_interface::move_map::const_iterator pos;
|
|
for (pos = wrap.begin(); pos != wrap.end(); pos++)
|
|
{
|
|
loc = wrap_location(pos->first);
|
|
list = PyDict_GetItem(dict,loc);
|
|
if (!list)
|
|
{
|
|
list = PyList_New(0);
|
|
PyDict_SetItem(dict, loc, list);
|
|
Py_DECREF(list);
|
|
}
|
|
loc2 = wrap_location(pos->second);
|
|
PyList_Append(list, loc2);
|
|
Py_DECREF(loc2);
|
|
Py_DECREF(loc);
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_log_message(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
const char* msg;
|
|
if ( !PyArg_ParseTuple( args, STRINGVALUE, &msg ) )
|
|
return NULL;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
running_instance->log_message(msg);
|
|
Py_END_ALLOW_THREADS
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_log(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
const char *msg;
|
|
if (!PyArg_ParseTuple( args, STRINGVALUE, &msg))
|
|
return NULL;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
LOG_AI << msg;
|
|
Py_END_ALLOW_THREADS
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_units(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if ( !PyArg_ParseTuple( args, NOVALUE) )
|
|
return NULL;
|
|
|
|
PyObject* dict = PyDict_New();
|
|
if ( !dict )
|
|
{
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* key;
|
|
wesnoth_unit* unit;
|
|
int ret;
|
|
|
|
for(unit_map::const_iterator i = running_instance->get_info().units.begin(); i != running_instance->get_info().units.end(); ++i) {
|
|
key = wrap_location(i->first);
|
|
unit = static_cast<wesnoth_unit*>(PyObject_NEW(wesnoth_unit, &wesnoth_unit_type));
|
|
unit->unit_ = &i->second;
|
|
ret = PyDict_SetItem(dict,reinterpret_cast<PyObject*>(key),reinterpret_cast<PyObject*>(unit));
|
|
Py_DECREF(unit);
|
|
Py_DECREF(key);
|
|
}
|
|
|
|
return dict;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_location(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
int x, y;
|
|
if (!PyArg_ParseTuple( args, CC("ii"), &x, &y ))
|
|
return NULL;
|
|
if (x < 0 || x >= running_instance->get_info().map.w()) Py_RETURN_NONE;
|
|
if (y < 0 || y >= running_instance->get_info().map.h()) Py_RETURN_NONE;
|
|
|
|
map_location loc(x,y);
|
|
return wrap_location(loc);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_map(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE)) return NULL;
|
|
|
|
wesnoth_gamemap* map = static_cast<wesnoth_gamemap*>(PyObject_NEW(wesnoth_gamemap, &wesnoth_gamemap_type));
|
|
map->map_ = &running_instance->get_info().map;
|
|
return reinterpret_cast<PyObject*>(map);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_teams(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE)) return NULL;
|
|
|
|
PyObject* list = PyList_New(running_instance->get_info().teams.size());
|
|
wesnoth_team* the_team;
|
|
|
|
for (size_t team = 0; team < running_instance->get_info().teams.size(); team++)
|
|
{
|
|
the_team = static_cast<wesnoth_team*>(PyObject_NEW(wesnoth_team, &wesnoth_team_type));
|
|
the_team->team_ = &running_instance->get_info().teams[team];
|
|
PyList_SetItem(list,team,reinterpret_cast<PyObject*>(the_team));
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_current_team(PyObject* /*self*/, PyObject* /*args*/)
|
|
{
|
|
wesnoth_team* the_team;
|
|
the_team = static_cast<wesnoth_team*>(PyObject_NEW(wesnoth_team, &wesnoth_team_type));
|
|
the_team->team_ = &running_instance->current_team();
|
|
return reinterpret_cast<PyObject*>(the_team);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_src_dst(PyObject* /*self*/, PyObject* /*args*/)
|
|
{
|
|
return wrap_move_map(running_instance->src_dst_);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_dst_src(PyObject* /*self*/, PyObject* /*args*/)
|
|
{
|
|
return wrap_move_map(running_instance->dst_src_);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_enemy_src_dst(PyObject* /*self*/, PyObject* /*args*/)
|
|
{
|
|
return wrap_move_map(running_instance->enemy_src_dst_);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_enemy_dst_src(PyObject* /*self*/, PyObject* /*args*/)
|
|
{
|
|
return wrap_move_map(running_instance->enemy_dst_src_);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_move_unit(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
wesnoth_location* from;
|
|
wesnoth_location* to;
|
|
if (!PyArg_ParseTuple( args, CC("O!O!"), &wesnoth_location_type, &from,
|
|
&wesnoth_location_type, &to ) )
|
|
return NULL;
|
|
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS ;
|
|
|
|
bool valid = false;
|
|
ai_interface::move_map::const_iterator pos;
|
|
for (pos = running_instance->src_dst_.begin(); pos != running_instance->src_dst_.end(); pos++)
|
|
{
|
|
if ( pos->first == ( *from->location_ ) )
|
|
{
|
|
valid = true;
|
|
if ( pos->second == ( *to->location_ ) )
|
|
break;
|
|
}
|
|
}
|
|
if (!valid || pos == running_instance->src_dst_.end() )
|
|
{
|
|
Py_BLOCK_THREADS ;
|
|
Py_RETURN_NONE ;
|
|
}
|
|
|
|
location location;
|
|
try
|
|
{
|
|
location = running_instance->move_unit_partial( *from->location_,
|
|
*to->location_,
|
|
running_instance->possible_moves_ ) ;
|
|
}
|
|
catch( end_level_exception& e )
|
|
{
|
|
running_instance->exception = e ;
|
|
Py_BLOCK_THREADS ;
|
|
PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
recalculate_movemaps();
|
|
Py_BLOCK_THREADS ;
|
|
PyObject* loc = wrap_location(location);
|
|
|
|
return loc;
|
|
}
|
|
|
|
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, CC("O!O!|i"), &wesnoth_location_type, &from,
|
|
&wesnoth_location_type, &to, &weapon ) )
|
|
return NULL;
|
|
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS ;
|
|
|
|
/**
|
|
* @todo 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 code below will horribly fail).
|
|
*/
|
|
if (!tiles_adjacent(*from->location_, *to->location_))
|
|
{
|
|
Py_BLOCK_THREADS ;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// check if units actually exist
|
|
bool fromexists = false;
|
|
bool toexists = false;
|
|
for(unit_map::const_iterator i = running_instance->get_info().units.begin();
|
|
i != running_instance->get_info().units.end(); ++i) {
|
|
if (i->first == *from->location_) {
|
|
fromexists = true;
|
|
}
|
|
else if (i->first == *to->location_) {
|
|
toexists = true;
|
|
}
|
|
if( fromexists && toexists )
|
|
break ;
|
|
}
|
|
|
|
if (!fromexists || !toexists)
|
|
{
|
|
Py_BLOCK_THREADS ;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
info& inf = running_instance->get_info();
|
|
|
|
battle_context bc(
|
|
inf.map,
|
|
inf.teams,
|
|
inf.units,
|
|
inf.state,
|
|
*from->location_,
|
|
*to->location_,
|
|
weapon);
|
|
|
|
try
|
|
{
|
|
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 ;
|
|
Py_BLOCK_THREADS ;
|
|
PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
recalculate_movemaps();
|
|
Py_BLOCK_THREADS ;
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_adjacent_tiles(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
wesnoth_location* where;
|
|
if (!PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &where))
|
|
return NULL;
|
|
|
|
gamemap const &map = running_instance->get_info().map;
|
|
PyObject* list = PyList_New(0);
|
|
map_location loc[6];
|
|
get_adjacent_tiles(*where->location_,loc);
|
|
for ( int tile = 0; tile < 6; tile++ )
|
|
if (loc[tile].valid(map.w(), map.h()))
|
|
{
|
|
PyObject *o = wrap_location(loc[tile]);
|
|
PyList_Append(list, o);
|
|
Py_DECREF(o);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_recruit_unit(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
wesnoth_location* where;
|
|
const char* name;
|
|
if (!PyArg_ParseTuple( args, CC("sO!"), &name, &wesnoth_location_type, &where))
|
|
return NULL;
|
|
|
|
int r ;
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS ;
|
|
|
|
try
|
|
{
|
|
r = running_instance->recruit(name,*where->location_) == true ? 1 : 0 ;
|
|
}
|
|
catch( end_level_exception& e )
|
|
{
|
|
running_instance->exception = e ;
|
|
Py_BLOCK_THREADS ;
|
|
PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
recalculate_movemaps();
|
|
Py_BLOCK_THREADS ;
|
|
|
|
return Py_BuildValue(INTVALUE, r);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_gamestatus(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
return NULL;
|
|
wesnoth_gamestatus* status;
|
|
status = static_cast<wesnoth_gamestatus*>(PyObject_NEW(wesnoth_gamestatus, &wesnoth_gamestatus_type));
|
|
status->status_ = &running_instance->get_info().state;
|
|
return reinterpret_cast<PyObject*>(status);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_unit_find_path(wesnoth_unit* self, PyObject* args)
|
|
{
|
|
wesnoth_location* from;
|
|
wesnoth_location* to;
|
|
if( !running_instance->is_unit_valid(self->unit_) )
|
|
Py_RETURN_NONE ;
|
|
double max_cost = self->unit_->movement_left();
|
|
if ( !PyArg_ParseTuple( args, CC("O!O!|d"), &wesnoth_location_type, &from,
|
|
&wesnoth_location_type, &to, &max_cost ) )
|
|
return NULL;
|
|
|
|
// Save thread state and release GIL
|
|
PyThreadState *_save;
|
|
Py_UNBLOCK_THREADS
|
|
|
|
info& inf = running_instance->get_info();
|
|
|
|
max_cost += 1; // should be at least 1
|
|
|
|
const shortest_path_calculator calc(*self->unit_,
|
|
running_instance->current_team(),
|
|
inf.units, inf.teams, inf.map);
|
|
const paths::route& route = a_star_search(*from->location_, *to->location_,
|
|
max_cost, &calc, inf.map.w(), inf.map.h());
|
|
|
|
// Lock the GIL again
|
|
Py_BLOCK_THREADS
|
|
|
|
PyObject* steps = PyList_New(route.steps.size());
|
|
for (size_t step = 0; step < route.steps.size(); step++)
|
|
PyList_SetItem(steps,step,wrap_location(route.steps[step]));
|
|
|
|
|
|
return steps;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_unit_attack_statistics(wesnoth_unit* self, PyObject* args)
|
|
{
|
|
wesnoth_location* from;
|
|
wesnoth_location* to;
|
|
int weapon = -1;
|
|
|
|
if (!PyArg_ParseTuple(args, CC("O!O!|i"), &wesnoth_location_type, &from,
|
|
&wesnoth_location_type, &to, &weapon))
|
|
return NULL;
|
|
if (!running_instance->is_unit_valid(self->unit_))
|
|
Py_RETURN_NONE;
|
|
if (weapon < -1 || weapon >= static_cast<int>(self->unit_->attacks().size())){
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
PyThreadState *_save;
|
|
Py_UNBLOCK_THREADS
|
|
|
|
info& inf = running_instance->get_info();
|
|
|
|
// We need to temporarily move our unit to where the attack calculation
|
|
// is supposed to take place.
|
|
std::pair<map_location,unit> *temp = inf.units.extract(*from->location_);
|
|
std::pair<map_location,unit> *backup = temp;
|
|
std::pair<map_location,unit> replace(*from->location_,*self->unit_);
|
|
inf.units.add(&replace);
|
|
|
|
battle_context bc(
|
|
inf.map,
|
|
inf.teams,
|
|
inf.units,
|
|
inf.state,
|
|
*from->location_,
|
|
*to->location_,
|
|
weapon);
|
|
|
|
unsigned int i;
|
|
std::vector<double>attacker = bc.get_attacker_combatant().hp_dist;
|
|
Py_BLOCK_THREADS
|
|
|
|
PyObject* adict = PyDict_New();
|
|
for (i = 0; i < attacker.size(); i++) {
|
|
if (attacker[i] > 0)
|
|
PyDict_SetItem(adict, PyInt_FromLong(i), PyFloat_FromDouble(attacker[i]));
|
|
}
|
|
|
|
std::vector<double>defender = bc.get_defender_combatant().hp_dist;
|
|
PyObject* ddict = PyDict_New();
|
|
for (i = 0; i < defender.size(); i++) {
|
|
if (defender[i] > 0)
|
|
PyDict_SetItem(ddict, PyInt_FromLong(i), PyFloat_FromDouble(defender[i]));
|
|
}
|
|
|
|
// Restore old position again
|
|
Py_UNBLOCK_THREADS
|
|
temp = inf.units.extract(*from->location_);
|
|
if (backup)
|
|
inf.units.add(backup);
|
|
Py_BLOCK_THREADS
|
|
|
|
return Py_BuildValue(CC("(OO)"), adict, ddict);
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_set_variable(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
char const *variable;
|
|
PyObject *value;
|
|
if (!PyArg_ParseTuple(args, CC("sO"), &variable, &value))
|
|
return NULL;
|
|
|
|
PyObject *so = PyMarshal_WriteObjectToString(value, Py_MARSHAL_VERSION);
|
|
if (so)
|
|
{
|
|
char *cs;
|
|
Py_ssize_t len;
|
|
PyString_AsStringAndSize(so, &cs, &len);
|
|
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS ;
|
|
|
|
std::string s;
|
|
char const *digits[] = {
|
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
|
"a", "b", "c", "d", "e", "f"};
|
|
int i;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
unsigned char x = cs[i];
|
|
s += std::string(digits[x >> 4]) + std::string(digits[x & 15]);
|
|
}
|
|
|
|
config const &old_memory = running_instance->current_team().ai_memory();
|
|
config new_memory(old_memory);
|
|
new_memory[variable] = s;
|
|
running_instance->current_team().set_ai_memory(new_memory);
|
|
Py_BLOCK_THREADS ;
|
|
|
|
Py_DECREF(so);
|
|
} else {
|
|
// If the marshal request generated an exception, we need to
|
|
// return NULL and not None or the exception stack gets hosed!
|
|
if( PyErr_Occurred() ) {
|
|
return NULL ;
|
|
}
|
|
}
|
|
|
|
Py_INCREF(Py_None) ;
|
|
return Py_None ;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_variable(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
char const *variable;
|
|
PyObject *default_value = Py_BuildValue( STRINGVALUE, "" ) ;
|
|
|
|
// If a default value was not provided, see if we were called with
|
|
// just the value to find.
|
|
if (!PyArg_ParseTuple(args, CC("s|O"), &variable, &default_value))
|
|
return NULL;
|
|
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS
|
|
|
|
config const &memory = running_instance->current_team().ai_memory();
|
|
char const *s = memory[variable].c_str();
|
|
|
|
if (s && s[0])
|
|
{
|
|
int i;
|
|
int len = strlen(s);
|
|
char *data = new char[len / 2];
|
|
for (i = 0; i < len; i += 2)
|
|
{
|
|
int v1 = s[i] - '0';
|
|
int v2 = s[i + 1] - '0';
|
|
if (v1 > 9) v1 += 10 + '0' - 'a';
|
|
if (v2 > 9) v2 += 10 + '0' - 'a';
|
|
char c = v1 * 16 + v2;
|
|
data[i / 2] = c;
|
|
}
|
|
Py_BLOCK_THREADS ;
|
|
|
|
Py_DECREF( default_value ) ;
|
|
PyObject *ret = PyMarshal_ReadObjectFromString(data, len / 2);
|
|
delete[] data;
|
|
return ret ;
|
|
} else if( default_value ) {
|
|
// Value did not exist - do return default value
|
|
Py_BLOCK_THREADS ;
|
|
return default_value ;
|
|
}
|
|
|
|
Py_BLOCK_THREADS ;
|
|
Py_DECREF( default_value ) ;
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_version(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
return NULL;
|
|
return Py_BuildValue(CC("s#"), game_config::version.data(), game_config::version.size());
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_raise_user_interact(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, NOVALUE))
|
|
return NULL;
|
|
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS ;
|
|
|
|
try
|
|
{
|
|
running_instance->raise_user_interact() ;
|
|
}
|
|
catch( end_level_exception& e )
|
|
{
|
|
running_instance->exception = e ;
|
|
Py_BLOCK_THREADS ;
|
|
PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ;
|
|
return NULL ;
|
|
}
|
|
|
|
Py_BLOCK_THREADS ;
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_test_move(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
wesnoth_location* from;
|
|
// loc initialises to x=-1000, y=-1000. i.e. temporarily moves the unit off the map
|
|
map_location loc;
|
|
wesnoth_location* to = reinterpret_cast<wesnoth_location*>(wrap_location(loc));
|
|
|
|
if (!PyArg_ParseTuple(args, CC("O!|O!"), &wesnoth_location_type, &from,
|
|
&wesnoth_location_type, &to))
|
|
return NULL;
|
|
|
|
// Trivial case, just return the current move_maps
|
|
if (*from->location_ == *to->location_) {
|
|
// PyTuple_New creates a new reference (which we will return)
|
|
PyObject* ret = PyTuple_New(2);
|
|
|
|
/* wrap_move_map creates a new reference, PyTuple_SetItem takes away
|
|
* a reference - so this should cancel out and we don't need to
|
|
* change reference counts ourselves.
|
|
*/
|
|
PyTuple_SetItem(ret, 0, wrap_move_map(running_instance->enemy_src_dst_));
|
|
PyTuple_SetItem(ret, 1, wrap_move_map(running_instance->enemy_dst_src_));
|
|
|
|
return ret;
|
|
}
|
|
|
|
PyThreadState *_save ;
|
|
Py_UNBLOCK_THREADS ;
|
|
|
|
info& inf = running_instance->get_info();
|
|
|
|
unit_map::iterator u_it = inf.units.find(*from->location_);
|
|
if (u_it == inf.units.end())
|
|
{
|
|
Py_BLOCK_THREADS ;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// Temporarily move our unit to the specified location, storing any
|
|
// unit that might happen to be there already.
|
|
std::pair<map_location,unit> *temp = inf.units.extract(*to->location_);
|
|
std::pair<map_location,unit> *backup = temp;
|
|
std::pair<map_location,unit> *original = inf.units.extract(*from->location_);
|
|
std::pair<map_location,unit> test(*to->location_, u_it->second);
|
|
inf.units.add(&test);
|
|
|
|
ai_interface::move_map test_src_dst;
|
|
ai_interface::move_map test_dst_src;
|
|
std::map<map_location, paths> possible_moves;
|
|
|
|
running_instance->calculate_moves(inf.units, possible_moves, test_src_dst, test_dst_src, true);
|
|
|
|
// Restore old positions again
|
|
temp = inf.units.extract(*to->location_);
|
|
inf.units.add(original);
|
|
if (backup)
|
|
inf.units.add(backup);
|
|
Py_BLOCK_THREADS ;
|
|
|
|
PyObject* srcdst = wrap_move_map(test_src_dst);
|
|
PyObject* dstsrc = wrap_move_map(test_dst_src);
|
|
// possible_moves not used
|
|
|
|
PyObject* ret = PyTuple_New(2);
|
|
PyTuple_SetItem(ret, 0, srcdst);
|
|
PyTuple_SetItem(ret, 1, dstsrc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
PyObject* python_ai::wrapper_get_random(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
int a, b, r;
|
|
if (!PyArg_ParseTuple(args, CC("ii"), &a, &b))
|
|
return NULL;
|
|
r = get_random() % (b-a+1) + a;
|
|
return Py_BuildValue(INTVALUE, r);
|
|
}
|
|
|
|
static PyMethodDef wesnoth_python_methods[] = {
|
|
MDEF("log_message", python_ai::wrapper_log_message,
|
|
"Parameters: string\n"
|
|
"Logs a message, displayed as a chat message, if debug is enabled." )
|
|
MDEF("log", python_ai::wrapper_log,
|
|
"Parameters: string\n"
|
|
"Writes a debug message to the AI log. This should be used instead of "
|
|
"print to not clutter stdout if AI logging is disabled.")
|
|
MDEF("get_random", python_ai::wrapper_get_random,
|
|
"Parameters: a, b\n"
|
|
"Returns: random_number\n"
|
|
"Get random number in the range [a, b].")
|
|
MDEF("get_units", python_ai::wrapper_get_units,
|
|
"Returns: units\n"
|
|
"Returns a dictionary containing (location, unit) pairs.")
|
|
MDEF("get_location", python_ai::wrapper_get_location,
|
|
"Parameters: x, y\n"
|
|
"Returns: location\n"
|
|
"Returns a wesnoth.location object pointing to position (x, y) on the map.")
|
|
MDEF("get_map", python_ai::wrapper_get_map,
|
|
"Returns: map\n"
|
|
"Returns a wesnoth.gamemap object representing the current map.")
|
|
MDEF("get_teams", python_ai::wrapper_get_teams,
|
|
"Returns: teams\n"
|
|
"Returns a list containing wesnoth.team objects, representing the current teams in the game.")
|
|
MDEF("get_current_team", python_ai::wrapper_get_current_team,
|
|
"Returns: team\n"
|
|
"Returns a wesnoth.team object representing the current team.")
|
|
MDEF("get_destinations_by_unit", python_ai::wrapper_get_src_dst,
|
|
"Returns: moves\n"
|
|
"Returns a dictionary. Keys are wesnoth.location objects pointing to player's units, "
|
|
"values are lists of possible destinations for this unit.")
|
|
MDEF("get_units_by_destination", python_ai::wrapper_get_dst_src,
|
|
"Returns: moves\n"
|
|
"Returns a dictionary. Keys are wesnoth.location objects pointing to positions the "
|
|
"player's units can reach, values are lists of locations where units that can reach "
|
|
"this position are.")
|
|
MDEF("get_enemy_destinations_by_unit", python_ai::wrapper_get_enemy_src_dst,
|
|
"Returns: moves\n"
|
|
"Returns a dictionary. Keys are wesnoth.location objects pointing to player's units, "
|
|
"values are lists of possible destinations for this unit.")
|
|
MDEF("get_enemy_units_by_destination", python_ai::wrapper_get_enemy_dst_src,
|
|
"Returns: moves\n"
|
|
"Returns a dictionary. Keys are wesnoth.location objects pointing to positions the "
|
|
"enemie's units can reach, values are lists of locations where units that can reach "
|
|
"this position are.")
|
|
MDEF("move_unit", python_ai::wrapper_move_unit,
|
|
"Parameters: location from, location to\n"
|
|
"Returns: new_position\n"
|
|
"Moves the unit on 'from' to the location specified by 'to', and "
|
|
"returns a wesnoth.location object representing the position the unit was moved to.")
|
|
MDEF("attack_unit", python_ai::wrapper_attack_unit,
|
|
"Parameters: location attacker, location defender, int weapon = -1\n"
|
|
"Unit at position 'attacker' attacks unit at position 'defender' with weapon 'weapon'. "
|
|
"The weapon parameter is optional, and the same weapon which would be "
|
|
"highlighted for a human player is used if it is omitted.")
|
|
MDEF("get_adjacent_tiles", python_ai::wrapper_get_adjacent_tiles,
|
|
"Parameters: location\n"
|
|
"Returns: positions\n"
|
|
"Returns a list of wesnoth.location representing tiles adjacent to the specified location.")
|
|
MDEF("recruit_unit", python_ai::wrapper_recruit_unit,
|
|
"Parameters: string name, location where\n"
|
|
"Returns: result\n"
|
|
"Recruits the unit of type 'name', at the location 'where'. Returns 1 on success, 0 on failure.")
|
|
MDEF("get_gamestatus", python_ai::wrapper_get_gamestatus,
|
|
"Returns: status\n"
|
|
"Returns a wesnoth.gamestatus object representing the current game status.")
|
|
MDEF("set_variable", python_ai::wrapper_set_variable,
|
|
"Parameters: variable, value\n"
|
|
"Sets a persistent variable 'variable' to 'value'. This can be "
|
|
"used to make the AI save strings (and other python values which can "
|
|
"be marshalled) over multiple turns.")
|
|
MDEF("get_variable", python_ai::wrapper_get_variable,
|
|
"Parameters: variable, default value\n"
|
|
"Returns: value or default value\n"
|
|
"Retrieves a persistent variable 'variable' from the AI, which has "
|
|
"previously been set with set_variable - or None if it can't be found and\n"
|
|
"default value has not been set. If a default value is set and the value"
|
|
"can not be found, the default value is returned.")
|
|
MDEF("get_version", python_ai::wrapper_get_version,
|
|
"Returns a string containing current Wesnoth version")
|
|
MDEF("raise_user_interact", python_ai::wrapper_raise_user_interact,
|
|
"Function which should be called frequently to allow the user to interact with the interface.\n"
|
|
"This function will make sure that interaction doesn't occur too often, "
|
|
"so there is no problem with calling it very regularly.")
|
|
MDEF("test_move", python_ai::wrapper_test_move,
|
|
"Parameters: from location, to location (optional)\n"
|
|
"Returns: (enemy_destinations_by_unit, enemy_units_by_destination) or None\n"
|
|
"Returns two dictionaries, representing the possible enemy moves if "
|
|
"the unit were to move from 'from location' to 'test location'. "
|
|
"If 'to location' is not given, it returns the possible enemy moves ignoring this unit. "
|
|
"The results will be the same as if wesnoth.get_enemy_destinations_by_unit() "
|
|
"and wesnoth.get_enemy_units_by_destination() were called after actually "
|
|
"performing the move.\n"
|
|
"Returns None if there is no unit at 'from location'.")
|
|
{ NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
|
|
#define Py_Register( x, n ) { \
|
|
PyType_Ready(&x); \
|
|
Py_INCREF(&x); \
|
|
PyTypeObject *type = &x; \
|
|
PyObject* pyob = reinterpret_cast<PyObject *>(type); \
|
|
PyModule_AddObject(module, CC(n), pyob); }
|
|
|
|
void python_ai::initialize_python()
|
|
{
|
|
if (init_) return;
|
|
init_ = true;
|
|
|
|
Py_Initialize( );
|
|
PyObject* module = Py_InitModule3(CC("ai"), wesnoth_python_methods,
|
|
CC("This is the wesnoth AI module. "
|
|
"The python script will be executed once for each turn of the side with the "
|
|
"python AI using the script."));
|
|
Py_Register(wesnoth_unit_type, "unit");
|
|
Py_Register(wesnoth_location_type, "location");
|
|
Py_Register(wesnoth_gamemap_type, "gamemap");
|
|
Py_Register(wesnoth_unittype_type, "unittype");
|
|
Py_Register(wesnoth_team_type, "team");
|
|
Py_Register(wesnoth_attacktype_type, "attacktype");
|
|
Py_Register(wesnoth_gamestatus_type, "gamestatus");
|
|
}
|
|
|
|
/***
|
|
* Invoke the named python script using Wesnoth's builtin Python interpreter.
|
|
*/
|
|
void python_ai::invoke(std::string name)
|
|
{
|
|
initialize_python();
|
|
PyErr_Clear();
|
|
PyObject* globals = PyDict_New();
|
|
PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
|
|
std::string python_code;
|
|
python_code +=
|
|
"import sys\n"
|
|
"backup = sys.path[:]\n"
|
|
"sys.path.append(\"" + game_config::path + "/data/ai/python\")\n"
|
|
"sys.path.append(\"" + game_config::path + "/data/tools/wesnoth\")\n"
|
|
"try:\n"
|
|
"\timport " + name + "\n"
|
|
"finally:\n"
|
|
"\tsys.path = backup\n";
|
|
PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input, globals,
|
|
globals);
|
|
Py_XDECREF(ret);
|
|
Py_DECREF(globals);
|
|
}
|
|
|
|
/***
|
|
* Invoke the Wesnoth's builtin interactive Python interpreter.
|
|
*/
|
|
int python_ai::run_shell()
|
|
{
|
|
initialize_python();
|
|
PyErr_Clear();
|
|
PyObject* globals = PyDict_New();
|
|
PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
|
|
std::string python_code;
|
|
// Inspired by Django shell command implementation.
|
|
python_code +=
|
|
"import sys\n"
|
|
"import code\n"
|
|
"import ai\n"
|
|
"sys.path.append(\"" + game_config::path + "/data/ai/python\")\n"
|
|
"sys.path.append(\"" + game_config::path + "/data/tools/wesnoth\")\n"
|
|
"imported_objects = {}\n"
|
|
"try: # Try activating rlcompleter, because it's handy.\n"
|
|
"\timport readline\n"
|
|
"except ImportError:\n"
|
|
"\tpass\n"
|
|
"else:\n"
|
|
"\timport rlcompleter\n"
|
|
"\treadline.set_completer(rlcompleter.Completer(imported_objects).complete)\n"
|
|
"\treadline.parse_and_bind('tab:complete')\n"
|
|
"banner= 'Wesnoth %s\\nPython %s' % (ai.get_version(), sys.version)\n"
|
|
"code.interact(banner, local=imported_objects)\n"
|
|
;
|
|
PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input, globals,
|
|
globals);
|
|
if (PyErr_Occurred())
|
|
PyErr_Print();
|
|
|
|
int cret = (int)PyInt_AsLong(ret);
|
|
Py_XDECREF(ret);
|
|
Py_DECREF(globals);
|
|
return cret; //Py_Main(argc, argv);
|
|
}
|
|
|
|
python_ai::python_ai(ai_interface::info& info) :
|
|
ai_interface(info),
|
|
exception(QUIT),
|
|
src_dst_(),
|
|
dst_src_(),
|
|
possible_moves_(),
|
|
enemy_src_dst_(),
|
|
enemy_dst_src_(),
|
|
enemy_possible_moves_()
|
|
{
|
|
LOG_AI << "Running Python instance.\n";
|
|
running_instance = this;
|
|
initialize_python();
|
|
calculate_possible_moves(possible_moves_,src_dst_,dst_src_,false);
|
|
calculate_possible_moves(enemy_possible_moves_,enemy_src_dst_,enemy_dst_src_,true);
|
|
}
|
|
|
|
python_ai::~python_ai()
|
|
{
|
|
// This is called whenever the AI is destroyed after its turn -
|
|
// the Python interpreter itself will be auto cleaned up at program exit.
|
|
LOG_AI << "Closing Python instance.\n";
|
|
running_instance = NULL;
|
|
}
|
|
|
|
void python_ai::play_turn()
|
|
{
|
|
game_events::fire("ai turn");
|
|
|
|
std::string script_name = current_team().ai_parameters()["python_script"];
|
|
if (script_name.substr(script_name.length() - 3) != ".py") {
|
|
// Make sure the script ends in .py here -
|
|
// Wesnoth will not execute any other files.
|
|
std::cerr << "\"" << script_name << "\" is not a valid script name.\n";
|
|
return;
|
|
}
|
|
std::string script = get_binary_file_location("data", "ai/python" + script_name);
|
|
|
|
PyErr_Clear();
|
|
|
|
PyObject* globals = PyDict_New();
|
|
PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
|
|
|
|
std::string path(".");
|
|
if (!game_config::path.empty()) path = game_config::path;
|
|
LOG_AI << "Executing Python script \"" << script << "\".\n";
|
|
LOG_AI << "Python path: \"" << path << "/data/ai/python" << "\"\n";
|
|
// Run the python script. We actually execute a short inline python script,
|
|
// which sets up the module search path to the data path,
|
|
// runs the script, and then resets the path.
|
|
std::string runSafe = preferences::run_safe_python()?"True":"False" ;
|
|
std::string python_code;
|
|
python_code +=
|
|
"err = \"unknown error\"\n"
|
|
"try:\n"
|
|
"\timport sys, traceback\n"
|
|
"\ttry:\n"
|
|
"\t\tsys.stderr = file(\"pyerr.txt\", \"wb\")\n"
|
|
"\texcept IOError:\n"
|
|
"\t\tsys.stderr.write(\"Python: Could not create pyerr.txt in current directory.\\n\")\n"
|
|
"\tbackup = sys.path[:]\n"
|
|
"\tsys.path.append(\"" + path + "/data/ai/python\")\n"
|
|
"\tsys.path.append(\"data/ais\")\n"
|
|
"\ttry:\n"
|
|
"\t\timport parse, safe\n"
|
|
"\t\tparse.pathes = [\"" + path + "\"]\n"
|
|
"\t\tcode, context = parse.parse(\"" + script + "\")\n"
|
|
"\t\tsafe.safe_exec(code, context, " + runSafe + ")\n"
|
|
"\texcept:\n"
|
|
"\t\terr = str(traceback.format_exc())\n"
|
|
"\t\traise\n"
|
|
"finally:\n"
|
|
"\tsys.path = backup\n" ;
|
|
|
|
PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input,
|
|
globals, globals);
|
|
|
|
if (PyErr_Occurred()) {
|
|
// 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";
|
|
std::cerr << "Python version: " << Py_GetVersion() << "\n";
|
|
PyObject *err = PyDict_GetItemString(globals, "err");
|
|
std::cerr << (err ? PyString_AsString(err) :
|
|
std::string("internal error (wrong python version?)")) <<
|
|
std::endl;
|
|
}
|
|
// Otherwise, re-throw the exception here,
|
|
// so it will get handled properly further up.
|
|
else {
|
|
LOG_AI << "Python script has been interrupted.\n";
|
|
Py_XDECREF(ret);
|
|
Py_DECREF(globals);
|
|
throw exception;
|
|
}
|
|
}
|
|
Py_XDECREF(ret);
|
|
Py_DECREF(globals);
|
|
}
|
|
|
|
#endif // HAVE_PYTHON
|