diff --git a/src/audio/Makefile.am b/src/audio/Makefile.am index 95453f5..f7e2909 100644 --- a/src/audio/Makefile.am +++ b/src/audio/Makefile.am @@ -51,21 +51,21 @@ _sdl_la_DEPENDENCIES = libadonthell_audio.la ## Unit tests test_CXXFLAGS = $(libgmock_CFLAGS) $(libgtest_CFLAGS) -test_LDADD = $(libgmock_LIBS) $(libgtest_LIBS) +test_LDADD = $(libgmock_LIBS) $(libgtest_LIBS) $(top_builddir)/src/audio/libadonthell_audio.la -test_audio_SOURCES = $(libadonthell_audio_la_SOURCES) test_audio.cc +test_audio_SOURCES = test_audio.cc test_audio_CXXFLAGS = $(libadonthell_audio_la_CXXFLAGS) $(test_CXXFLAGS) test_audio_LDADD = $(libadonthell_audio_la_LIBADD) $(test_LDADD) -test_audio_event_SOURCES = $(libadonthell_audio_la_SOURCES) test_audio_event.cc +test_audio_event_SOURCES = test_audio_event.cc test_audio_event_CXXFLAGS = $(libadonthell_audio_la_CXXFLAGS) $(test_CXXFLAGS) test_audio_event_LDADD = $(libadonthell_audio_la_LIBADD) $(test_LDADD) -test_audio_manager_SOURCES = $(libadonthell_audio_la_SOURCES) test_audio_manager.cc +test_audio_manager_SOURCES = test_audio_manager.cc test_audio_manager_CXXFLAGS = $(libadonthell_audio_la_CXXFLAGS) $(test_CXXFLAGS) test_audio_manager_LDADD = $(libadonthell_audio_la_LIBADD) $(test_LDADD) -test_sound_SOURCES = $(libadonthell_audio_la_SOURCES) test_sound.cc +test_sound_SOURCES = test_sound.cc test_sound_CXXFLAGS = $(libadonthell_audio_la_CXXFLAGS) $(test_CXXFLAGS) test_sound_LDADD = $(libadonthell_audio_la_LIBADD) $(test_LDADD) diff --git a/src/audio/audio_manager.cc b/src/audio/audio_manager.cc index 20cd4d3..60b0a5d 100644 --- a/src/audio/audio_manager.cc +++ b/src/audio/audio_manager.cc @@ -90,13 +90,4 @@ namespace audio return true; } - bool audio_manager::set_sound_dir(const std::string sound_dir) { - LOG(INFO) << "set_sound_dir(" << sound_dir << "); was: " - << sound_dir_; - - sound_dir_ = sound_dir; - - return true; - } - } // namespace{} diff --git a/src/audio/audio_manager.h b/src/audio/audio_manager.h index 2268c62..48f8121 100644 --- a/src/audio/audio_manager.h +++ b/src/audio/audio_manager.h @@ -85,10 +85,6 @@ namespace audio */ static const int get_audio_rate() { return audio_rate_; } - /** @return sound_dir directory in which sound files reside - */ - static const std::string get_sound_dir() { return sound_dir_; } - /** Sets the audio buffer size * * @param audio_buffers size of audio buffer, in bytes @@ -125,13 +121,6 @@ namespace audio */ static bool set_audio_rate(const int audio_rate = DEFAULT_AUDIO_RATE); - /** Sets the directory in which sound files reside - * - * @param sound_dir directory in which sound files reside - * @return true on success, false otherwise - */ - static bool set_sound_dir(const std::string sound_dir = DEFAULT_SOUND_DIR); - protected: static int audio_buffers_; static int audio_channels_; diff --git a/src/audio/sound.cc b/src/audio/sound.cc index e7072ff..f92d430 100644 --- a/src/audio/sound.cc +++ b/src/audio/sound.cc @@ -28,6 +28,7 @@ #include +#include "base/base.h" #include "audio/audio.h" #include "audio/audio_manager.h" #include "audio/sound.h" @@ -49,12 +50,17 @@ sound::sound (const std::string &filename) LOG(INFO) << logging::indent() << "sound::sound(" << filename << ") called"; logging::increment_log_indent_level(); - std::string sound_dir = audio_manager::get_sound_dir(); - LOG(INFO) << logging::indent() << "sound_dir: '" << sound_dir << "'"; - - m_filename = sound_dir + filename; - open_file(); - + m_filename = "audio/" + filename; + if (base::Paths.find_in_path (m_filename)) + { + open_file(); + } + else + { + m_channel = -1; + m_sample = NULL; + } + logging::decrement_log_indent_level(); } diff --git a/src/audio/test_audio_event.cc b/src/audio/test_audio_event.cc index 2239d6a..affbe56 100644 --- a/src/audio/test_audio_event.cc +++ b/src/audio/test_audio_event.cc @@ -67,11 +67,11 @@ namespace audio }; // class{} TEST_F(audio_event_Test, constructor_Default) { - EXPECT_EQ("foo.ogg", ev->sample().getfilename()); + EXPECT_EQ("audio/foo.ogg", ev->sample().getfilename()); } TEST_F(audio_event_Test, put_state_Filename) { - EXPECT_EQ("foo.ogg", ev->sample().getfilename()); + EXPECT_EQ("audio/foo.ogg", ev->sample().getfilename()); base::flat f; ev->put_state(f); @@ -79,11 +79,11 @@ namespace audio sound_noop *s2 = new sound_noop("bar.ogg"); audio_event *ev2 = new audio_event(s2); - EXPECT_EQ("bar.ogg", ev2->sample().getfilename()); + EXPECT_EQ("audio/bar.ogg", ev2->sample().getfilename()); ev2->get_state(f); - EXPECT_EQ("foo.ogg", ev2->sample().getfilename()); + EXPECT_EQ("audio/foo.ogg", ev2->sample().getfilename()); delete ev2; delete s2; diff --git a/src/audio/test_audio_manager.cc b/src/audio/test_audio_manager.cc index cc7adfa..f0cb295 100644 --- a/src/audio/test_audio_manager.cc +++ b/src/audio/test_audio_manager.cc @@ -86,12 +86,6 @@ namespace audio EXPECT_EQ(DEFAULT_AUDIO_RATE, audio_manager::get_audio_rate()); } - TEST_F(audio_manager_Test, set_sound_dir_Default) { - audio_manager::set_sound_dir(); - - EXPECT_EQ(DEFAULT_SOUND_DIR, audio_manager::get_sound_dir()); - } - TEST_F(audio_manager_Test, set_audio_buffers) { audio_manager::set_audio_buffers(27); @@ -122,12 +116,6 @@ namespace audio EXPECT_EQ(27, audio_manager::get_audio_rate()); } - TEST_F(audio_manager_Test, set_sound_dir) { - audio_manager::set_sound_dir("27"); - - EXPECT_EQ("27", audio_manager::get_sound_dir()); - } - } // namespace{} diff --git a/src/audio/test_sound.cc b/src/audio/test_sound.cc index d0771ef..cba21c5 100644 --- a/src/audio/test_sound.cc +++ b/src/audio/test_sound.cc @@ -64,11 +64,11 @@ namespace audio }; // class{} TEST_F(sound_Test, constructor_Default) { - EXPECT_EQ("foo.ogg", s->getfilename()); + EXPECT_EQ("audio/foo.ogg", s->getfilename()); } TEST_F(sound_Test, put_state_Filename) { - EXPECT_EQ("foo.ogg", s->getfilename()); + EXPECT_EQ("audio/foo.ogg", s->getfilename()); base::flat f; s->put_state(f); @@ -76,7 +76,7 @@ namespace audio sound_noop *s2 = new sound_noop(); s2->get_state(f); - EXPECT_EQ("foo.ogg", s2->getfilename()); + EXPECT_EQ("audio/foo.ogg", s2->getfilename()); EXPECT_EQ(-1, s2->get_channel()); EXPECT_FALSE(s2->get_forcedhalt()); diff --git a/src/main/adonthell.cc b/src/main/adonthell.cc index 4493f6b..28022d4 100644 --- a/src/main/adonthell.cc +++ b/src/main/adonthell.cc @@ -118,14 +118,6 @@ bool app::init_modules (const u_int16 & modules) if (m & AUDIO) { audio::setup (Cfg); - audio::audio_manager::set_sound_dir(Userdatadir + "/" + Game + "/audio/"); - - LOG(INFO) << logging::indent() - << "audio::audio_manager::sound_dir: '" - << audio::audio_manager::get_sound_dir() - << "'" - ; - if (!audio::init (Backend)) { LOG(ERROR) << logging::indent() << "audio::init() failed"; diff --git a/src/py-runtime/Makefile.am b/src/py-runtime/Makefile.am index 1e17d8f..b15e9fe 100644 --- a/src/py-runtime/Makefile.am +++ b/src/py-runtime/Makefile.am @@ -1,6 +1,6 @@ AM_CXXFLAGS = -I$(top_srcdir)/src EXTRA_DIST = py_runtime.i CMakeLists.txt -CLEANFILES = $(top_srcdir)/src/py-wrappers/runtime/py_*_wrap.cc +CLEANFILES = $(top_srcdir)/src/py-wrappers/runtime/py_runtime.cc ## SWIG runtime support lib_LTLIBRARIES = libadonthell_py_runtime.la diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 96280ab..f2e3ca5 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -26,6 +26,15 @@ target_link_libraries(adonthell_python ${PYTHON_LIBRARIES} adonthell_base) +################################ +# Unit tests +IF(DEVBUILD) + add_executable(test_python test_python.cc) + target_link_libraries(test_python ${TEST_LIBRARIES} adonthell_python) + add_test(NAME Python COMMAND test_python) +ENDIF(DEVBUILD) + +################################ # install adonthell_install_lib (adonthell_python) adonthell_install_include(python "${adonthell_python_HEADERS}") \ No newline at end of file diff --git a/src/python/Makefile.am b/src/python/Makefile.am index 6122f9e..d231090 100644 --- a/src/python/Makefile.am +++ b/src/python/Makefile.am @@ -27,3 +27,17 @@ libadonthell_python_la_CXXFLAGS = $(PY_CFLAGS) $(AM_CXXFLAGS) libadonthell_python_la_LIBADD = $(PY_LIBS) \ $(top_builddir)/src/base/libadonthell_base.la \ $(SWIGRUNTIME_LIBS) -lstdc++ + + +## Unit tests +test_CXXFLAGS = $(libgmock_CFLAGS) $(libgtest_CFLAGS) +test_LDADD = $(libgmock_LIBS) $(libgtest_LIBS) $(top_builddir)/src/python/libadonthell_python.la + +test_python_SOURCES = test_python.cc +test_python_CXXFLAGS = $(libadonthell_python_la_CXXFLAGS) $(test_CXXFLAGS) +test_python_LDADD = $(libadonthell_python_la_LIBADD) $(test_LDADD) + +TESTS = \ + test_python + +check_PROGRAMS = $(TESTS) diff --git a/src/python/method.h b/src/python/method.h index b56ddd6..52b490e 100644 --- a/src/python/method.h +++ b/src/python/method.h @@ -67,6 +67,12 @@ namespace python */ std::string name () const; + /** + * Return name of enclosing script. + * @return script name. + */ + std::string script () const { return Script->class_name(); } + /** * Execute the connected %method with the given arguments. * @param args a python tuple to be passed to the %method. diff --git a/src/python/python.cc b/src/python/python.cc index 66bb343..9bbfbb0 100644 --- a/src/python/python.cc +++ b/src/python/python.cc @@ -102,17 +102,55 @@ namespace python return ret; } + // pad tuple + PyObject *pad_tuple (PyObject *tuple, const u_int16 & len) + { + // make sure the given arguments are a tuple + if (tuple && !PyTuple_Check (tuple)) + { + fprintf (stderr, "*** error: python::pad_tuple: argument must be a tuple!\n"); + return NULL; + } + + // calculate size of argument tuple required + u_int16 size = tuple ? PyTuple_GET_SIZE (tuple) + len : len; + + // prepare callback arguments + PyObject *new_tuple = PyTuple_New (size); + + // pad with none object + for (u_int16 i = 0; i < len; i++) + { + PyTuple_SET_ITEM (new_tuple, i, Py_None); + } + + // copy remaining objects, if any + for (u_int16 i = len; i < size; i++) + { + PyObject *o = PyTuple_GET_ITEM (tuple, i - len); + Py_XINCREF (o); + PyTuple_SET_ITEM (new_tuple, i, o); + } + + return new_tuple; + } + // unflatten the contents of a tuple PyObject *get_tuple (base::flat & in, const u_int16 & start) { u_int16 len = in.get_uint16 ("pln") + start; PyObject *tuple = PyTuple_New (len); void *value; - + for (u_int16 i = start; i < len; i++) { switch (int type = in.next (&value)) { + case base::flat::T_CHAR: + { + PyTuple_SetItem (tuple, i, Py_None); + break; + } case base::flat::T_STRING: { // Stolen reference @@ -147,10 +185,12 @@ namespace python { // Borrowed reference PyObject *item = PyTuple_GetItem (tuple, i); + if (item == NULL) + out.put_char ("n", ' '); // Check for the type of this object // String? - if (PyString_Check (item)) + else if (PyString_Check (item)) out.put_string ("s", PyString_AsString (item)); // Integer? diff --git a/src/python/python.h b/src/python/python.h index adc2e40..691ef1c 100644 --- a/src/python/python.h +++ b/src/python/python.h @@ -316,9 +316,19 @@ namespace python //@} /** - * @name Loading / Saving + * @name Convenience functions. */ //@{ + /** + * Pads the front of the given tuple, moving the existing + * entries to the end of the tuple. If the given tuple is + * NULL, returns a new tuple of size len. Reference count + * of the contents of the given tuple is increased by one. + * + * @return a new tuple or NULL on error. + */ + PyObject *pad_tuple (PyObject *tuple, const u_int16 & len); + /** * Read the contents of a tuple from given stream. * @param in flattener to read the tuple from. diff --git a/src/python/test_python.cc b/src/python/test_python.cc new file mode 100644 index 0000000..31ffacc --- /dev/null +++ b/src/python/test_python.cc @@ -0,0 +1,92 @@ +/* + Copyright (C) 2010 Kai Sterker + Part of the Adonthell Project http://adonthell.linuxgames.com + + Adonthell is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Adonthell is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Adonthell; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "base/logging.h" +#include + +#include "python/python.h" + +namespace python +{ + class python_Test : public ::testing::Test + { + protected: + python_Test() + { + python::init(); + } + + virtual ~python_Test() + { + python::cleanup(); + } + + // If the constructor and destructor are not enough for setting up + // and cleaning up each test, you can define the following methods: + + virtual void SetUp() + { + } + + virtual void TearDown() + { + } + }; // class{} + + TEST_F(python_Test, pad_tuple_1) + { + PyObject *no_tuple = PyList_New(1); + EXPECT_EQ(NULL, python::pad_tuple (no_tuple, 0)); + } + + TEST_F(python_Test, pad_tuple_2) + { + PyObject *null_tuple = python::pad_tuple (NULL, 1); + EXPECT_EQ(true, PyTuple_Check(null_tuple)); + EXPECT_EQ(1, PyTuple_GET_SIZE(null_tuple)); + EXPECT_EQ(Py_None, PyTuple_GET_ITEM(null_tuple, 0)); + } + + TEST_F(python_Test, pad_tuple_3) + { + PyObject *arg1 = PyString_FromString ("a"); + PyObject *arg2 = PyInt_FromLong (1); + + PyObject *a_tuple = PyTuple_New (2); + PyTuple_SetItem (a_tuple, 0, arg1); + PyTuple_SetItem (a_tuple, 1, arg2); + + PyObject *res = python::pad_tuple (a_tuple, 2); + EXPECT_EQ(true, PyTuple_Check(res)); + EXPECT_EQ(4, PyTuple_GET_SIZE(res)); + EXPECT_EQ(Py_None, PyTuple_GET_ITEM(res, 0)); + EXPECT_EQ(Py_None, PyTuple_GET_ITEM(res, 1)); + EXPECT_EQ(arg1, PyTuple_GET_ITEM(res, 2)); + EXPECT_EQ(arg2, PyTuple_GET_ITEM(res, 3)); + } + +} // namespace{} + + +int main(int argc, char **argv) +{ + ::google::InitGoogleLogging(argv[0]); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/world/CMakeLists.txt b/src/world/CMakeLists.txt index 707adbe..6d6ffc5 100644 --- a/src/world/CMakeLists.txt +++ b/src/world/CMakeLists.txt @@ -1,5 +1,6 @@ # Define the adonthell_world_SRCS variable containing all required files. set(adonthell_world_SRCS + action.cc area.cc area_manager.cc character.cc @@ -24,6 +25,7 @@ set(adonthell_world_SRCS ) set(adonthell_world_HEADERS + action.h area.h area_manager.h character.h diff --git a/src/world/Makefile.am b/src/world/Makefile.am index a6be100..a790c67 100644 --- a/src/world/Makefile.am +++ b/src/world/Makefile.am @@ -4,6 +4,7 @@ EXTRA_DIST = CMakeLists.txt ## Our header files pkgincludeworlddir = $(pkgincludedir)/world pkgincludeworld_HEADERS = \ + action.h \ area.h \ area_manager.h \ character.h \ @@ -43,6 +44,7 @@ lib_LTLIBRARIES = libadonthell_world.la ## Rules to build libworld libadonthell_world_la_SOURCES = \ + action.cc \ area.cc \ area_manager.cc \ character.cc \ @@ -73,3 +75,17 @@ libadonthell_world_la_LIBADD = $(PY_LIBS) $(libglog_LIBS) \ $(top_builddir)/src/gfx/libadonthell_gfx.la \ $(top_builddir)/src/rpg/libadonthell_rpg.la \ $(top_builddir)/src/py-runtime/libadonthell_py_runtime.la -lstdc++ + + +## Unit tests +test_CXXFLAGS = $(libgmock_CFLAGS) $(libgtest_CFLAGS) +test_LDADD = $(libgmock_LIBS) $(libgtest_LIBS) $(top_builddir)/src/world/libadonthell_world.la + +test_renderer_SOURCES = test_renderer.cc +test_renderer_CXXFLAGS = $(libadonthell_world_la_CXXFLAGS) $(test_CXXFLAGS) +test_renderer_LDADD = $(libadonthell_world_la_LIBADD) $(test_LDADD) + +TESTS = \ + test_renderer + +check_PROGRAMS = $(TESTS) diff --git a/src/world/action.cc b/src/world/action.cc new file mode 100644 index 0000000..6e8e4f3 --- /dev/null +++ b/src/world/action.cc @@ -0,0 +1,123 @@ +/* + Copyright (C) 2010 Kai Sterker + Part of the Adonthell Project http://adonthell.linuxgames.com + + Adonthell is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Adonthell is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Adonthell; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @file world/action.cc + * + * @author Kai Sterker + * @brief Implements the object interaction class. + */ + + +#include "world/action.h" +#include "python/pool.h" +#include "world/character.h" +#include "world/object.h" + + +using world::action; + +// ctor +action::action () +{ + Action = NULL; + Args = NULL; +} + +// dtor +action::~action() +{ + clear (); +} + +// reset +void action::clear () +{ + delete Action; + Py_XDECREF(Args); + + Action = NULL; + Args = NULL; +} + +// init +bool action::init (const std::string & script, const std::string & method, PyObject *args) +{ + clear (); + + Action = python::pool::connect (ACTION_DIR + script, script, method); + if (Action == NULL) return false; + + Args = python::pad_tuple (args, 2); + if (Args == NULL) + { + delete Action; + Action = NULL; + + return false; + } + + return true; +} + +// execute action +void action::execute (world::character *actor, world::object *target) +{ + execute (python::pass_instance (actor), python::pass_instance (target)); +} + +// execute action +void action::execute (PyObject *actor, PyObject *target) +{ + if (Action != NULL) + { + // prepare arguments + PyTuple_SET_ITEM (Args, 0, actor); + PyTuple_SET_ITEM (Args, 1, target); + + Action->execute (Args); + + // reset + PyTuple_SET_ITEM (Args, 0, Py_None); + PyTuple_SET_ITEM (Args, 1, Py_None); + } +} + +// save +void action::put_state (base::flat& file) const +{ + if (Action != NULL) + { + Action->put_state (file); + python::put_tuple (Args, file, 2); + } +} + +// load +bool action::get_state (base::flat& file) +{ + Action = new python::method(); + if (Action->get_state (file)) + { + Args = python::get_tuple (file, 2); + return true; + } + + return false; +} diff --git a/src/world/action.h b/src/world/action.h new file mode 100644 index 0000000..6052c01 --- /dev/null +++ b/src/world/action.h @@ -0,0 +1,143 @@ +/* + Copyright (C) 2010 Kai Sterker + Part of the Adonthell Project http://adonthell.linuxgames.com + + Adonthell is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Adonthell is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Adonthell; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * @file world/action.h + * + * @author Kai Sterker + * @brief Declares the object interaction class. + */ + +#ifndef WORLD_ACTION_H +#define WORLD_ACTION_H + +#include "python/method.h" + +/** + * Path to the object interaction scripts. + */ +#define ACTION_DIR "schedules.obj." + +namespace world +{ + class character; + class object; + + /** + * This class is used to associate an action with a scenery + * object. The action itself is implemented as the method of + * a python script. Since the method is loaded from the python::pool, + * it should be stateless as it is probably shared by more than + * one object. + */ + class action + { + public: + /** + * Create a new, empty action. + */ + action (); + + /** + * Destructor. + */ + ~action (); + + /** + * Initialize the action. + * @param script name of file inside the action directory. + * @param method name of the method implementing the action. + * @param args optional arguments when calling the method. + * @return true on success, false otherwise. + */ + bool init (const std::string & script, const std::string & method, PyObject *args = NULL); + +#ifndef SWIG + /** + * Execute the action with the given character and object. + * @param actor the character triggering the action. + * @param target the object acted on. + */ + void execute (world::character *actor, world::object *target); + + /** + * Get the method that implements the action. Might be + * NULL if the action is not initialized yet. + * + * @return the method that implements the action. + */ + python::method *get_method () const { return Action; } + + /** + * Get the arguments used to execute the action. + * @return the method arguments. + */ + PyObject *get_args () const { return Args; } +#endif + + /** + * Execute the action with the given character and object. + * + * This method is optimized for being called from a Python + * script, as it avoids boxing and unboxing of its arguments. + * + * @param actor the character triggering the action. + * @param target the object acted on. + */ + void execute (PyObject *actor, PyObject *target); + + /** + * @name Loading / Saving + */ + //@{ + /** + * Save the %action to a stream. + * @param file stream where to save the %action. + */ + void put_state (base::flat& file) const; + + /** + * Loads the %action from a stream. + * @param file stream to load the %action from. + * @return \e true if the %schedule was loaded successfully, \e false otherwise. + */ + bool get_state (base::flat& file); + //@} + +#ifndef SWIG + /// allow %action to be passed through SWIG + GET_TYPE_NAME(world::action); +#endif // SWIG + + private: + /** + * Reset the action. + */ + void clear (); + + /// Forbid copy constructor + action (const action & a); + /// the action implementation + python::method *Action; + /// additional arguments for the action + PyObject *Args; + }; +} + +#endif diff --git a/src/world/area.cc b/src/world/area.cc index 70079f6..3e736b0 100644 --- a/src/world/area.cc +++ b/src/world/area.cc @@ -65,16 +65,15 @@ void area::clear() } // convenience method for adding object at a known index -bool area::place_entity (const s_int32 & index, coordinates & pos) +world::chunk_info *area::place_entity (const s_int32 & index, coordinates & pos) { if (index >= 0 && index < Entities.size()) { - chunk::add(Entities[index], pos); - return true; + return chunk::add(Entities[index], pos); } fprintf (stderr, "*** area::place_entity: no entity at index %i!\n", index); - return false; + return NULL; } // update state of map @@ -216,10 +215,13 @@ std::vector area::find_zones (const world::vector3 & poin // save to stream bool area::put_state (base::flat & file) const { - u_int32 index = 0; + u_int32 index = 0, act_idx = 0; char buffer[128]; base::flat record; + // list of map(interactions) + base::flat action_list; + // gather all different placeables and their locations on the map collector objects; chunk::put_state (objects); @@ -240,7 +242,7 @@ bool area::put_state (base::flat & file) const index = 0; record.clear(); - // second pass: save entities and their positions + // second pass: save entities and their positions and actions for (collector::const_iterator i = objects.begin(); i != objects.end(); i++) { base::flat entity; @@ -253,6 +255,16 @@ bool area::put_state (base::flat & file) const base::flat anonym; for (j = data.Anonymous.begin(); j != data.Anonymous.end(); j++) { + // save location action + if ((*j)->has_action ()) + { + sprintf (buffer, "%d", act_idx++); + + base::flat actn_data; + (*j)->get_action()->put_state (actn_data); + action_list.put_flat (buffer, actn_data); + anonym.put_string ("action", buffer); + } ((*j)->Min - (*j)->get_object()->entire_min()).put_state (anonym); } @@ -275,6 +287,7 @@ bool area::put_state (base::flat & file) const sprintf (buffer, "%d", index++); record.put_flat (buffer, entity); } + file.put_flat ("actions", action_list); file.put_flat ("entities", record); // reset @@ -293,7 +306,7 @@ bool area::put_state (base::flat & file) const } file.put_flat ("states", record); - + // save the zones std::list ::const_iterator zone_i = Zones.begin(); base::flat zone_list; @@ -319,6 +332,9 @@ bool area::get_state (base::flat & file) void *value; char *id; + // map (inter)actions + std::string actn_id = ""; + // load placeable models std::vector tmp_objects; base::flat record = file.get_flat ("objects"); @@ -362,6 +378,9 @@ bool area::get_state (base::flat & file) tmp_objects.push_back (object); } + // load actions, if any + base::flat action_list = file.get_flat ("actions"); + // load entities record = file.get_flat ("entities"); while (record.next (&value, &size, &id) == base::flat::T_FLAT) @@ -374,10 +393,20 @@ bool area::get_state (base::flat & file) // try loading anonymous entities base::flat entity_data = entity.get_flat ("anonym", true); - + // iterate over entity positions while (entity_data.next (&value, &size, &id) != base::flat::T_UNKNOWN) { + // load action associated to location, if any + if ("action" == id) + { + // get action id + actn_id = *((const char*) value); + + // read next value + entity_data.next (&value, &size, &id); + } + // get coordinate pos.set_str (std::string ((const char*) value, size)); @@ -390,7 +419,16 @@ bool area::get_state (base::flat & file) } // place entity at current index at given coordinate - place_entity (ety_idx, pos); + chunk_info *ci = place_entity (ety_idx, pos); + + // location has an action assigned + if (actn_id != "") + { + base::flat actn_data = action_list.get_flat (actn_id); + world::action *actn = ci->set_action (); + actn->get_state (actn_data); + actn_id = ""; + } } // try loading named entities @@ -398,6 +436,16 @@ bool area::get_state (base::flat & file) // iterate over object positions while (entity_data.next (&value, &size, &id) != base::flat::T_UNKNOWN) { + // load action associated to location, if any + if ("action" == id) + { + // get action id + actn_id = *((const char*) value); + + // read next value + entity_data.next (&value, &size, &id); + } + // first get id of entity std::string entity_name ((const char*) value); @@ -408,7 +456,16 @@ bool area::get_state (base::flat & file) // create a named instance (that will be unique if it is the first) ... world::entity *ety = new world::named_entity (object, entity_name, ety_idx == -1); // ... and place it on the map - place_entity (add_entity (ety), pos); + chunk_info *ci = place_entity (add_entity (ety), pos); + + // location has an action assigned + if (actn_id != "") + { + base::flat actn_data = action_list.get_flat (actn_id); + world::action *actn = ci->set_action (); + actn->get_state (actn_data); + actn_id = ""; + } // associate world object with its rpg representation switch (object->type()) @@ -421,6 +478,11 @@ bool area::get_state (base::flat & file) { fprintf (stderr, "*** area::get_state: cannot find rpg instance for '%s'.\n", entity_name.c_str()); } + break; + } + default: + { + break; } } } diff --git a/src/world/area.h b/src/world/area.h index 8a10b57..7995b5b 100644 --- a/src/world/area.h +++ b/src/world/area.h @@ -90,9 +90,9 @@ namespace world * Place object at a given index at the given location. * @param index index of object in the list of objects. * @param pos position to place object at. - * @return true on success, false otherwise. + * @return newly placed chunk or NULL on failure. */ - bool place_entity (const s_int32& index, coordinates & pos); + chunk_info * place_entity (const s_int32& index, coordinates & pos); /** * Get entity at given index diff --git a/src/world/chunk.cc b/src/world/chunk.cc index 774f254..9491132 100644 --- a/src/world/chunk.cc +++ b/src/world/chunk.cc @@ -47,7 +47,7 @@ typedef enum #define MAX_OBJECTS 16 /// minimum node size (in all 3 dimensions) #define MIN_SIZE 240 - + bool chunk_info::operator == (const chunk_info & ci) const { if (Entity->get_object() == ci.Entity->get_object()) @@ -60,6 +60,16 @@ bool chunk_info::operator == (const chunk_info & ci) const return false; } +struct ci_ptr_equal +{ + const chunk_info *a; + + bool operator()(const chunk_info * b) const + { + return *a == *b; + } +}; + // ctor chunk::chunk () : Min(), Max(), Split() { @@ -72,6 +82,11 @@ chunk::chunk () : Min(), Max(), Split() // dtor chunk::~chunk() { + std::list::const_iterator i; + for (i = Objects.begin (); i != Objects.end(); i++) + { + delete *i; + } Objects.clear(); for (u_int8 i = 0; i < 8; i++) { @@ -80,14 +95,16 @@ chunk::~chunk() } // add an object to chunk -void chunk::add (entity * object, const coordinates & pos) +chunk_info * chunk::add (entity * object, const coordinates & pos) { // calculate axis-aligned bbox for object const placeable *p = object->get_object(); vector3 min = pos + p->entire_min(); vector3 max = min + p->entire_max(); - add (chunk_info (object, min, max)); + chunk_info *ci = new chunk_info (object, min, max); + add (ci); + return ci; } // check if object exists at given position @@ -98,7 +115,8 @@ bool chunk::exists (entity *object, const coordinates & pos) vector3 min = pos + p->entire_min(); vector3 max = min + p->entire_max(); - return exists (chunk_info (object, min, max)); + chunk_info ci(object, min, max); + return exists (ci); } // remove object from chunk @@ -108,21 +126,22 @@ world::entity * chunk::remove (entity * object, const coordinates & pos) const placeable *p = object->get_object(); vector3 min = pos + p->entire_min(); vector3 max = min + p->entire_max(); - - return remove (chunk_info (object, min, max)); + + chunk_info ci(object, min, max); + return remove (ci); } // add an object to chunk -void chunk::add (const chunk_info & ci) +void chunk::add (chunk_info * ci) { // update bounding box of chunk - Min.set_x (std::min (Min.x(), ci.Min.x())); - Min.set_y (std::min (Min.y(), ci.Min.y())); - Min.set_z (std::min (Min.z(), ci.Min.z())); + Min.set_x (std::min (Min.x(), ci->Min.x())); + Min.set_y (std::min (Min.y(), ci->Min.y())); + Min.set_z (std::min (Min.z(), ci->Min.z())); - Max.set_x (std::max (Max.x(), ci.Max.x())); - Max.set_y (std::max (Max.y(), ci.Max.y())); - Max.set_z (std::max (Max.z(), ci.Max.z())); + Max.set_x (std::max (Max.x(), ci->Max.x())); + Max.set_y (std::max (Max.y(), ci->Max.y())); + Max.set_z (std::max (Max.z(), ci->Max.z())); // we're in a leaf ... if (is_leaf()) @@ -142,10 +161,10 @@ void chunk::add (const chunk_info & ci) split (); s_int8 chunks[8]; - std::list::iterator i = Objects.begin(); + std::list::iterator i = Objects.begin(); while (i != Objects.end ()) { - const u_int8 num = find_chunks (chunks, (*i).Min, (*i).Max); + const u_int8 num = find_chunks (chunks, (*i)->Min, (*i)->Max); // if objects would be split between children, we have to keep them // in the current node @@ -157,7 +176,7 @@ void chunk::add (const chunk_info & ci) if (c == NULL) { c = new chunk; - c->Min = (*i).Min; + c->Min = (*i)->Min; Children[chunks[0]] = c; } @@ -179,7 +198,7 @@ void chunk::add (const chunk_info & ci) // we're not (or no longer) in a leaf, so we need to find the chunk // to which we add the object to s_int8 chunks[8]; - const u_int8 num = find_chunks (chunks, ci.Min, ci.Max); + const u_int8 num = find_chunks (chunks, ci->Min, ci->Max); if (num == 1) { chunk *c = Children[chunks[0]]; @@ -188,7 +207,7 @@ void chunk::add (const chunk_info & ci) if (c == NULL) { c = new chunk; - c->Min = ci.Min; + c->Min = ci->Min; Children[chunks[0]] = c; } @@ -218,7 +237,8 @@ bool chunk::exists (const chunk_info & ci) } } - std::list::iterator it = find (Objects.begin(), Objects.end(), ci); + ci_ptr_equal eq = { &ci }; + std::list::iterator it = std::find_if (Objects.begin(), Objects.end(), eq); if (it != Objects.end()) { return true; @@ -254,14 +274,15 @@ world::entity * chunk::remove (const chunk_info & ci) } } - std::list::iterator it = find (Objects.begin(), Objects.end(), ci); + ci_ptr_equal eq = { &ci }; + std::list::iterator it = std::find_if (Objects.begin(), Objects.end(), eq); if (it != Objects.end()) { - removed = it->get_entity(); + removed = (*it)->get_entity(); if (!Resize || - Min.x() == (*it).Min.x() || Min.y() == (*it).Min.y() || Min.z() == (*it).Min.z() || - Max.x() == (*it).Max.x() || Max.y() == (*it).Max.y() || Max.z() == (*it).Max.z()) + Min.x() == (*it)->Min.x() || Min.y() == (*it)->Min.y() || Min.z() == (*it)->Min.z() || + Max.x() == (*it)->Max.x() || Max.y() == (*it)->Max.y() || Max.z() == (*it)->Max.z()) { Resize = true; } @@ -295,12 +316,12 @@ void chunk::objects_in_view (const s_int32 & min_x, const s_int32 & max_x, const } // process contained map objects - std::list::const_iterator i; + std::list::const_iterator i; for (i = Objects.begin (); i != Objects.end(); i++) { - if (in_view (min_x, max_x, min_yz, max_yz, (*i).Min, (*i).Max)) + if (in_view (min_x, max_x, min_yz, max_yz, (*i)->Min, (*i)->Max)) { - result.push_back ((chunk_info*) &(*i)); + result.push_back (*i); } } @@ -363,12 +384,12 @@ void chunk::objects_in_bbox (const vector3 & min, const vector3::const_iterator i; + std::list::const_iterator i; for (i = Objects.begin (); i != Objects.end(); i++) { - if (type & i->get_object()->type() && in_bbox (min, max, i->solid_min(), i->solid_max())) + if (type & (*i)->get_object()->type() && in_bbox (min, max, (*i)->solid_min(), (*i)->solid_max())) { - result.push_back ((chunk_info*) &(*i)); + result.push_back (*i); } } } @@ -499,21 +520,21 @@ void chunk::put_state (collector & objects) const } // save objects contained in this chunk - std::list::const_iterator i; + std::list::const_iterator i; for (i = Objects.begin (); i != Objects.end(); i++) { - const entity *e = i->get_entity(); + const entity *e = (*i)->get_entity(); collector_data & data = objects[e->get_object()]; if (!e->has_name()) { // anonymous objects - data.Anonymous.push_back ((chunk_info*) &(*i)); + data.Anonymous.push_back (*i); } else { // named entities - data.Named.push_back ((chunk_info*) &(*i)); + data.Named.push_back (*i); } } } diff --git a/src/world/chunk.h b/src/world/chunk.h index 7a9f231..62f9dfd 100644 --- a/src/world/chunk.h +++ b/src/world/chunk.h @@ -68,14 +68,15 @@ namespace world * Add object at given coordinates. * @param object entity to add to the world. * @param coordinates location of the entity. + * @returns a pointer to the newly added chunk. */ - void add (entity * object, const coordinates & pos); + chunk_info * add (entity * object, const coordinates & pos); /** * Add object at given coordinates. * @param ci entity to add to the world. */ - void add (const chunk_info & ci); + void add (chunk_info * ci); /** * Check if given object is present at given position. @@ -91,7 +92,10 @@ namespace world bool exists (const chunk_info & ci); /** - * Remove object at given coordinates. + * Remove object at given coordinates. Returns a pointer + * to the removed object, which must be deleted if it is + * no longer used. + * * @param object entity to remove from the world. * @param coordinates location of the entity. * @return object that was removed, or NULL if no @@ -100,7 +104,10 @@ namespace world entity * remove (entity * object, const coordinates & pos); /** - * Remove object from world. + * Remove object from world. Returns a pointer to + * the removed object, which must be deleted if it is + * no longer used. + * * @param ci entity to remove from world. * @return object that was removed, or NULL if no * such object existed. @@ -297,7 +304,7 @@ namespace world /// the children of the chunk chunk* Children[8]; /// the objects contained in the chunk - std::list Objects; + std::list Objects; /// the minimum of the chunks AABB vector3 Min; diff --git a/src/world/chunk_info.h b/src/world/chunk_info.h index 88fefd6..9fd3ccd 100644 --- a/src/world/chunk_info.h +++ b/src/world/chunk_info.h @@ -51,21 +51,19 @@ namespace world * @param max the extend of the object in world space */ chunk_info (entity *e, const vector3 & min, const vector3 & max) - : Min (min), Max (max), Entity (e) + : Min (min), Max (max), Entity (e), Action(NULL) { calc_solid_max(); } /** - * Create a copy of another chunk_info. - * @param ci object to copy. + * Cleanup. */ - chunk_info (const chunk_info & ci) - : Min (ci.Min), Max (ci.Max), Entity (ci.Entity), Shadow (ci.Shadow) + ~chunk_info () { - calc_solid_max(); + delete Action; } - + /** * Compare if two chunk infos are the same. * @return True if both position and object contained are equal. @@ -90,6 +88,54 @@ namespace world return Entity; } + /** + * @name Object interaction + */ + //@{ + /** + * Assign an action to that location. + * @return an empty action that must be initialized. + */ + world::action *set_action() + { + if (Action == NULL) + { + Action = new world::action(); + } + return Action; + } + + /** + * Get pointer to action associated with + * the specific location or entity. If an action + * is set for the location, that is returned. + * Otherwise, the entity-specific action is returned. + * May return NULL if there is neither. + * + * @return an action or NULL, if none is assigned. + */ + world::action *get_action() const + { + if (Action) return Action; + else return Entity->get_action(); + } + + /** + * Check whether this location has a specific + * action assigned. Allows to distinguish between + * location and entity based action. + * @return true if this location has an action, false otherwise. + */ + bool has_action () const + { + return Action != NULL; + } + //@} + + /** + * @name Position information + */ + //@{ /** * Return "real" position, taking only solid placeable shape offset into account. * @return lower coordinate of bounding box @@ -121,6 +167,7 @@ namespace world const placeable *object = Entity->get_object(); return Min - object->entire_min(); } + //@} /** * @name Object shadow @@ -178,19 +225,24 @@ namespace world #endif private: + /// Forbid copy construction + chunk_info (const chunk_info & ci); + /** * Calculate constant SolidMax */ - void calc_solid_max() - { + void calc_solid_max() + { const placeable *object = Entity->get_object(); SolidMax = vector3(Min.x() + object->solid_max_length(), Min.y() + object->solid_max_width(), Min.z() + object->solid_max_height()); - } + } /// pointer to map object entity * Entity; + /// location specific action + action * Action; /// shadow cast on this object std::vector Shadow; /// extend of the solid portion of the object diff --git a/src/world/entity.h b/src/world/entity.h index 31d00c9..9525ec5 100644 --- a/src/world/entity.h +++ b/src/world/entity.h @@ -27,6 +27,7 @@ */ #include "world/placeable.h" +#include "world/action.h" #ifndef WORLD_ENTITY_H #define WORLD_ENTITY_H @@ -47,7 +48,7 @@ class entity * Create an anonymous entity. * @param object representation of the entity. */ - entity (placeable *object) : Object (object) { } + entity (placeable *object) : Object (object), Action (NULL) { } /** * Delete entity and its associated object. @@ -55,6 +56,7 @@ class entity virtual ~entity () { delete Object; + delete Action; } /** @@ -66,6 +68,29 @@ class entity return Object; } + /** + * Attach an action to the entity. + * @return the newly attached action. + */ + action *set_action () + { + if (Action == NULL) + { + Action = new world::action(); + } + + return Action; + } + + /** + * Return associated action. + * @return associated action. + */ + action *get_action () const + { + return Action; + } + /** * Check whether this is a named entity. * @return true if this is the case, false otherwise. @@ -87,6 +112,8 @@ class entity protected: /// an entity on a map placeable *Object; + /// action associated to the entity + action *Action; }; /** diff --git a/src/world/mapview.cc b/src/world/mapview.cc index a89c249..52d9b2c 100644 --- a/src/world/mapview.cc +++ b/src/world/mapview.cc @@ -249,12 +249,12 @@ void mapview::draw (const s_int16 & x, const s_int16 & y, const gfx::drawing_are if ((*i)->Min.z() > zn->max().z()) { // object inside zone boundaries? --> discard - if (!((*i)->Max.x() < zn->min().x() || (*i)->Min.x() > zn->max().x() || - (*i)->Max.y() < zn->min().y() || (*i)->Min.y() > zn->max().y())) - { + // if (!((*i)->Max.x() < zn->min().x() || (*i)->Min.x() > zn->max().x() || + // (*i)->Max.y() < zn->min().y() || (*i)->Min.y() > zn->max().y())) + // { i = objectlist.erase (i); continue; - } + // } } i++; } @@ -272,11 +272,11 @@ void mapview::draw (const s_int16 & x, const s_int16 & y, const gfx::drawing_are if ((*i)->Min.z() > (*zn)->max().z()) { // object inside zone boundaries? --> discard - if (!((*i)->Max.x() < (*zn)->min().x() || (*i)->Min.x() > (*zn)->max().x() || - (*i)->Max.y() < (*zn)->min().y() || (*i)->Min.y() > (*zn)->max().y())) - { + // if (!((*i)->Max.x() < (*zn)->min().x() || (*i)->Min.x() > (*zn)->max().x() || + // (*i)->Max.y() < (*zn)->min().y() || (*i)->Min.y() > (*zn)->max().y())) + // { continue; - } + // } } discard = false; diff --git a/src/world/moving.cc b/src/world/moving.cc index ef6ee69..f052c1e 100644 --- a/src/world/moving.cc +++ b/src/world/moving.cc @@ -330,7 +330,7 @@ void moving::calculate_ground_pos () // bbox of everything below our character const vector3 min (x(), y(), Mymap.min().z()); const vector3 max (min.x() + placeable::length(), min.y() + placeable::width(), z() - 1); - + // get objects below us std::list ground_tiles = Mymap.objects_in_bbox (min, max, OBJECT); @@ -353,15 +353,35 @@ void moving::calculate_ground_pos () // sort according to their z-Order ground_tiles.sort (z_order()); - // the topmost object will be our ground pos - ci = ground_tiles.begin(); - GroundPos = (*ci)->center_min().z() + (*ci)->get_object()->get_surface_pos (); + // center of character + s_int32 cx = x() + placeable::length()/2; + s_int32 cy = y() + placeable::width()/2; - // get the terrain, if any - Terrain = (*ci)->get_object()->get_terrain(); + // find object that will be our ground pos + for (ci = ground_tiles.begin(); ci != ground_tiles.end(); ci++) + { + // apply shadow + MyShadow->cast_on (*ci); + + // position of character relative to tile + s_int32 px = cx - (*ci)->center_min().x(); + s_int32 py = cy - (*ci)->center_min().y(); + + // is this really the object below character? + if (px >= 0 && px <= (*ci)->get_object()->length() && + py >= 0 && py <= (*ci)->get_object()->width()) + { + // get ground pos + GroundPos = (*ci)->center_min().z() + (*ci)->get_object()->get_surface_pos (px, py); + + // get the terrain, if any + Terrain = (*ci)->get_object()->get_terrain(); + break; + } + } - // apply shadow - for (; ci != ground_tiles.end(); ci++) + // apply remainder of shadow + for (ci++; ci != ground_tiles.end(); ci++) { MyShadow->cast_on (*ci); } diff --git a/src/world/pathfinding.cc b/src/world/pathfinding.cc index 460acdc..d0efbb8 100644 --- a/src/world/pathfinding.cc +++ b/src/world/pathfinding.cc @@ -298,8 +298,8 @@ bool pathfinding::find_path(const character * chr, const vector3 & goal } // Check if there is an obstacle in this node - vector3 cmin(temp_node->pos.x() * 20, temp_node->pos.y() * 20, 20); - vector3 cmax(temp_node->pos.x() * 20 , temp_node->pos.y() * 20 , chr->placeable::height()); + vector3 cmin(temp_node->pos.x() * 20, temp_node->pos.y() * 20, chr->z() + 1); + vector3 cmax(temp_node->pos.x() * 20 + chr->placeable::length(), temp_node->pos.y() * 20 + chr->placeable::width() , chr->z() + chr->height() - 1); std::list collisions = chr->map().objects_in_bbox(cmin, cmax); diff --git a/src/world/pathfinding_manager.cc b/src/world/pathfinding_manager.cc index 24134a9..fb8e9cb 100644 --- a/src/world/pathfinding_manager.cc +++ b/src/world/pathfinding_manager.cc @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Adonthell; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02120-1301, USA */ /** @@ -37,11 +37,6 @@ using world::character; // there's going to be a problem static const s_int16 MAX_TASKS = 55; -// Max number of times that a character can be stuck during a task, -// it's used to prevent the stupid behaviour of keeping hitting always -// the same obstacle. When it is exceeded the character will stop and the task deleted -static const u_int8 MAX_TIMES_STUCK = 5; - // The various phases a task can have static const u_int8 PHASE_PATHFINDING = 1; static const u_int8 PHASE_MOVING = 2; @@ -103,8 +98,7 @@ s_int16 pathfinding_manager::add_task_sec(const character *chr) bool pathfinding_manager::add_task_ll(const s_int16 id, character * chr, const world::vector3 & target, const world::vector3 & target2, const u_int8 phase, - const u_int8 actualNode, const u_int8 actualDir, - const u_int8 pixMoved, const u_int8 pixToMove) + const u_int8 actualNode, const u_int8 actualDir) { // delete any previously set callback delete m_task[id].callback; @@ -117,12 +111,8 @@ bool pathfinding_manager::add_task_ll(const s_int16 id, character * chr, m_task[id].actualNode = actualNode; m_task[id].actualDir = static_cast(actualDir); m_task[id].path->clear(); - - m_task[id].pixelsMoved = pixMoved; - m_task[id].pixelsToMove = pixToMove; - m_task[id].startPos.set_x(m_task[id].chr->x()); - m_task[id].startPos.set_y(m_task[id].chr->y()); - m_task[id].timesStuck = 0; + m_task[id].lastPos.set_x(m_task[id].chr->x()); + m_task[id].lastPos.set_y(m_task[id].chr->y()); // Verify if we can indeed add this task, or if the character is already performing another task // This should be in add_task_sec, however due to a bug it has to stay here @@ -335,140 +325,87 @@ u_int8 pathfinding_manager::calc_distance(const world::coordinates & node, const bool pathfinding_manager::move_chr(const s_int16 id) { + s_int32 grid_x = m_task[id].chr->x() / 20; + s_int32 grid_y = m_task[id].chr->y() / 20; + s_int32 target_grid_x = m_task[id].path->at(m_task[id].actualNode).x(); + s_int32 target_grid_y = m_task[id].path->at(m_task[id].actualNode).y(); + // Check if the direction has already been computed if (m_task[id].actualDir != character::NONE) { - // Verify if we've moved enough pixels - if (m_task[id].pixelsMoved >= m_task[id].pixelsToMove) + + // Verify if we're on the intended grid + if ((grid_x == target_grid_x) && (grid_y == target_grid_y)) { + // Check if we've finished the path if (m_task[id].path->size() - 1 == m_task[id].actualNode) { - // Verify if we're on the intended grid - if ((m_task[id].chr->x() / 20) == (m_task[id].path->at(m_task[id].actualNode).x()) && - (m_task[id].chr->y() / 20) == (m_task[id].path->at(m_task[id].actualNode).y())) { - - return true; - - } + return true; } else { - // We're not on the final node - // Verify if we've really reached the intended grid or their closest neigbhours - if ((m_task[id].chr->x() / 20) >= (m_task[id].path->at(m_task[id].actualNode).x() - 1) && - (m_task[id].chr->x() / 20) <= (m_task[id].path->at(m_task[id].actualNode).x() + 1) && - (m_task[id].chr->y() / 20) >= (m_task[id].path->at(m_task[id].actualNode).y() - 1) && - (m_task[id].chr->y() / 20) <= (m_task[id].path->at(m_task[id].actualNode).y() + 1)) { - ++m_task[id].actualNode; - } - + ++m_task[id].actualNode; + m_task[id].actualDir = character::NONE; } - - m_task[id].actualDir = character::NONE; - } else { - - m_task[id].pixelsMovedLst = m_task[id].pixelsMoved; - m_task[id].pixelsMoved = calc_distance(m_task[id].startPos, m_task[id].chr); - - // Verify if we're stuck somewhere - if (m_task[id].pixelsMoved == m_task[id].pixelsMovedLst) + + // Verify if we are stuck + if ((m_task[id].lastPos.x() == m_task[id].chr->x()) && (m_task[id].lastPos.y() == m_task[id].chr->y())) { - ++m_task[id].framesStuck; - - if (m_task[id].framesStuck > 30) - { - ++m_task[id].timesStuck; - - if (m_task[id].timesStuck > MAX_TIMES_STUCK) - m_task[id].phase = PHASE_FINISHED; - - m_task[id].framesStuck = 0; - - // We can either recalculate all the path from actualNode-1 to the target - // or calculate a way arround from actualNode-1 to actualNode - // The first will take longer to calculate, however the path will be - // smaller and more pleasing to the eye. - // The last will be much quicker to calculate but will unnecessarily increase - // the path size and return a rather strange path - // We could decide beetween the both depending on the actual path size - // If it was big then we would use the quicker way - // Otherwise we would opt by the slower but nicer "calculate the whole path" way - - // Stops the character - m_task[id].chr->stop(); - m_task[id].chr->update_state(); - - // Don't forget to clear the path - m_task[id].path->clear(); - - if (m_pathfinding.find_path(m_task[id].chr, m_task[id].target, m_task[id].target2, m_task[id].path) == false) - m_task[id].phase = PHASE_FINISHED; - - if ((m_task[id].path->empty()) || (m_task[id].path->size() == 1)) - m_task[id].phase = PHASE_FINISHED; - - m_task[id].actualNode = 0; - m_task[id].pixelsMoved = 0; - m_task[id].pixelsMovedLst = 0; - m_task[id].actualDir = character::NONE; - } + // We are stuck, let's find another path + + // Stop the character + m_task[id].chr->stop(); + m_task[id].chr->update_state(); + + // Don't forget to clear the path + m_task[id].path->clear(); + + if (m_pathfinding.find_path(m_task[id].chr, m_task[id].target, m_task[id].target2, m_task[id].path) == false) + m_task[id].phase = PHASE_FINISHED; + + if ((m_task[id].path->empty()) || (m_task[id].path->size() == 1)) + m_task[id].phase = PHASE_FINISHED; + + m_task[id].actualNode = 0; + m_task[id].actualDir = character::NONE; } - - } + + // Update lastPos with our actual position + m_task[id].lastPos.set_x(m_task[id].chr->x()); + m_task[id].lastPos.set_y(m_task[id].chr->y()); + + + } } else { - s_int32 grid_x = m_task[id].chr->x() / 20; - s_int32 grid_y = m_task[id].chr->y() / 20; - s_int32 target_grid_x = m_task[id].path->at(m_task[id].actualNode).x(); - s_int32 target_grid_y = m_task[id].path->at(m_task[id].actualNode).y(); - - if ((grid_x == target_grid_x) && (grid_y == target_grid_y)) - { - if ((m_task[id].actualNode == m_task[id].path->size() - 1) || (m_task[id].path->empty())) - return true; - - ++m_task[id].actualNode; - return false; - } - - m_task[id].actualDir = character::NONE; - + // Calculate the next node + if (grid_y > target_grid_y) { // We have to move up m_task[id].actualDir |= character::NORTH; + m_task[id].chr->set_direction(m_task[id].actualDir); } else if (grid_y < target_grid_y) { // We have to move down m_task[id].actualDir |= character::SOUTH; + m_task[id].chr->set_direction(m_task[id].actualDir); } - + if (grid_x > target_grid_x) { - // We have to move to the left + // We have to move left m_task[id].actualDir |= character::WEST; + m_task[id].chr->set_direction(m_task[id].actualDir); } else if (grid_x < target_grid_x) { - // We have to move to the right + // We have to move right m_task[id].actualDir |= character::EAST; + m_task[id].chr->set_direction(m_task[id].actualDir); } - // Updates the direction - m_task[id].chr->set_direction(m_task[id].actualDir); - - world::coordinates temp_coor(target_grid_x * 20, target_grid_y * 20, 0); - - // Calc the distance from the actual node to the target node - m_task[id].pixelsToMove = calc_distance(temp_coor, m_task[id].chr); - - // Update the startPos with our actual pos - m_task[id].startPos.set_x(m_task[id].chr->x()); - m_task[id].startPos.set_y(m_task[id].chr->y()); - - m_task[id].pixelsMoved = 0; - } - + return false; } @@ -491,8 +428,6 @@ void pathfinding_manager::put_state(base::flat & file) taskBlock.put_sint32("target2_x", m_task[i].target2.x()); taskBlock.put_sint32("target2_y", m_task[i].target2.y()); taskBlock.put_uint8("phase", m_task[i].phase); - taskBlock.put_uint8("pixToMove", m_task[i].pixelsToMove); - taskBlock.put_uint8("pixMoved", m_task[i].pixelsMoved); taskBlock.put_uint8("aNode", m_task[i].actualNode); taskBlock.put_uint8("aDir", m_task[i].actualDir); taskBlock.put_uint8("finalDir", m_task[i].finalDir); @@ -556,8 +491,6 @@ void pathfinding_manager::get_state(base::flat & file) s_int32 tX2 = taskBlock.get_sint32("target2_x"); s_int32 tY2 = taskBlock.get_sint32("target2_y"); u_int8 phase = taskBlock.get_uint8("phase"); - u_int8 pixToMove = taskBlock.get_uint8("pixToMove"); - u_int8 pixMoved = taskBlock.get_uint8("pixMoved"); u_int8 aNode = taskBlock.get_uint8("aNode"); u_int8 aDir = taskBlock.get_uint8("aDir"); u_int8 finalDir = taskBlock.get_uint8("finalDir"); @@ -566,7 +499,7 @@ void pathfinding_manager::get_state(base::flat & file) world::vector3 tempTarget2(tX2, tY2, 0); // Creates a new task with all the info - add_task_ll(i, tChr, tempTarget, tempTarget2, phase, aNode, aDir, pixMoved, pixToMove); + add_task_ll(i, tChr, tempTarget, tempTarget2, phase, aNode, aDir); set_final_direction(i, static_cast(finalDir)); // Now let's load the path diff --git a/src/world/pathfinding_manager.h b/src/world/pathfinding_manager.h index 1dad411..81f42d6 100644 --- a/src/world/pathfinding_manager.h +++ b/src/world/pathfinding_manager.h @@ -200,8 +200,7 @@ namespace world */ bool add_task_ll(const s_int16 id, character * chr, const world::vector3 & target, const world::vector3 & target2, - const u_int8 phase, const u_int8 actualNode, const u_int8 actualDir, - const u_int8 pixMoved = 0, const u_int8 pixToMove = 0); + const u_int8 phase, const u_int8 actualNode, const u_int8 actualDir); /** * Verify if we can add the task and in which slot diff --git a/src/world/pathfinding_task.h b/src/world/pathfinding_task.h index f5cc1d0..286186a 100644 --- a/src/world/pathfinding_task.h +++ b/src/world/pathfinding_task.h @@ -50,24 +50,14 @@ namespace world /// The path to the target, as a group of nodes std::vector * path; - /// Used to calc the distance walked - world::coordinates startPos; + /// The character's position in the last frame + world::coordinates lastPos; /// The phase where this task is u_int8 phase; /// The actual node being moved to u_int8 actualNode; /// Actual Direction u_int8 actualDir; - /// Number of pixels moved beetween two nodes - u_int8 pixelsMoved; - /// Number of pixels moved in the last iteration. Used for dynamic collision detection - u_int8 pixelsMovedLst; - /// Number of pixels that have to be moved in order to reach the next node - u_int8 pixelsToMove; - /// Number of frames stuck - u_int8 framesStuck; - /// Number of consecutive times we've been stuck - u_int8 timesStuck; /// Final Direction to where a character points after finishing moving u_int8 finalDir; }; diff --git a/src/world/placeable.cc b/src/world/placeable.cc index 7598dd2..1eb3d41 100644 --- a/src/world/placeable.cc +++ b/src/world/placeable.cc @@ -131,6 +131,31 @@ s_int32 placeable::get_surface_pos () const return SolidCurSize.z() + SolidCurPos.z(); } +// get z position at the given coordinate +s_int32 placeable::get_surface_pos(const s_int32 & x, const s_int32 & y) const +{ + for (std::vector::const_iterator i = Model.begin(); i != Model.end(); i++) + { + const placeable_shape *shape = (*i)->current_shape (); + if (shape != NULL && shape->is_solid()) + { + if (x >= shape->x() && x <= shape->length() + shape->x() && + y >= shape->y() && y <= shape->width() + shape->y()) + { + return shape->height() + shape->z(); + } + } + } + + // Fallback. We might end up here if the coordinates fall + // onto a non-solid part of the placeable. We should return + // a value indicating failure in that case (e.g. MAX_INT) + // and the caller would have to probe another placeable + // in turn. But we can probably live with this minor + // inaccuracy for now. + return get_surface_pos(); +} + // get terrain type of placeable const std::string* placeable::get_terrain () const { diff --git a/src/world/placeable.h b/src/world/placeable.h index b5ffdf0..c7d9785 100644 --- a/src/world/placeable.h +++ b/src/world/placeable.h @@ -274,6 +274,13 @@ namespace world * @return the z-position of the object's solid components. */ s_int32 get_surface_pos() const; + + /** + * Return the surface of the object at the given coordinate. + * + * @return the z-position of the object at the given coordinate. + */ + s_int32 get_surface_pos(const s_int32 & x, const s_int32 & y) const; /** * Return the terrain type of this object. diff --git a/src/world/render_info.h b/src/world/render_info.h index 41370e5..bd9672e 100644 --- a/src/world/render_info.h +++ b/src/world/render_info.h @@ -55,10 +55,10 @@ class render_info render_info (const placeable_shape *shape, const gfx::sprite *sprite, const vector3 & pos, const std::vector *shdw) : Pos (pos), Shape (shape), Sprite (sprite), Shadow (shdw) { - Projection[0] = x() + shape->ox(); - Projection[1] = y() + shape->oy() - z() - shape->height(); - Projection[2] = x() + shape->ox() + shape->length(); - Projection[3] = y() + shape->oy() - z() + shape->width(); + Projection[0] = x() /*+ shape->ox()*/; + Projection[1] = y() /*+ shape->oy()*/ - z() - shape->height(); + Projection[2] = x() /*+ shape->ox()*/ + shape->length(); + Projection[3] = y() /*+ shape->oy()*/ - z() + shape->width(); } /** @@ -82,8 +82,8 @@ class render_info */ ///@{ /** - * Return real world space position of sprite. - * @return sprite x-coordinate. + * Return real world space position of object. + * @return object x-coordinate. */ s_int32 x () const { @@ -91,8 +91,8 @@ class render_info } /** - * Return real world space position of sprite. - * @return sprite y-coordinate. + * Return real world space position of object. + * @return object y-coordinate. */ s_int32 y () const { @@ -100,8 +100,8 @@ class render_info } /** - * Return real world space position of sprite. - * @return sprite z-coordinate. + * Return real world space position of object. + * @return object z-coordinate. */ s_int32 z () const { @@ -114,7 +114,7 @@ class render_info */ s_int32 screen_x () const { - return x() + Shape->ox(); + return Projection[0] + Shape->ox(); } /** @@ -123,7 +123,7 @@ class render_info */ s_int32 screen_y () const { - return y() + Shape->oy() - z() - Shape->height(); + return Projection[1] + Shape->oy(); } ///@} @@ -181,7 +181,7 @@ class render_info const std::vector *Shadow; private: - /// the 2D projection of the object + /// the 2D projection of the object onto the drawing surface s_int32 Projection[4]; }; diff --git a/src/world/renderer.cc b/src/world/renderer.cc index 509ec64..0c40839 100644 --- a/src/world/renderer.cc +++ b/src/world/renderer.cc @@ -1,7 +1,7 @@ /* $Id: renderer.cc,v 1.9 2009/03/21 11:59:47 ksterker Exp $ - Copyright (C) 2008/2009 Kai Sterker + Copyright (C) 2008/2009/2010 Kai Sterker Part of the Adonthell Project http://adonthell.linuxgames.com Adonthell is free software; you can redistribute it and/or modify @@ -113,6 +113,9 @@ void default_renderer::render (const s_int16 & x, const s_int16 & y, std::list < for (iterator it = render_queue.begin(); it != render_queue.end(); it++) fprintf (stderr, " - (%i, %i, %i) - (%i, %i, %i)\n", it->x(), it->y(), it->z(), it->x() + it->Shape->length(), it->y() + it->Shape->width(), it->z() + it->Shape->height()); + //visualize_deadlock (render_queue); + //exit(1); + draw (x, y, render_queue.front(), da, target); render_queue.pop_front(); } @@ -145,50 +148,72 @@ bool default_renderer::can_draw_object (render_info & obj, const_iterator & begi return true; } -// check object order +#define YT 1 +#define YB 2 +#define ZT 4 +#define ZB 8 + +// check object order (is obj2 below obj?) bool default_renderer::is_object_below (const render_info & obj, const render_info & obj2) const { - s_int32 min_x = obj2.x() + obj2.Shape->ox(); - s_int32 min_y = obj2.y() + obj2.Shape->oy(); + s_int32 min_x = obj2.x(); + s_int32 min_y = obj2.y(); s_int32 min_z = obj2.z(); s_int32 max_x = min_x + obj2.Shape->length(); s_int32 max_y = min_y + obj2.Shape->width(); s_int32 max_z = min_z + obj2.Shape->height(); - // 2 | 3 | 4 y x | F | F - // --+---+-- ^ --+---+-- - // 1 | 8 | 5 | T | o | F - // --+---+-- | --+---+-- - // 0 | 7 | 6 +-----> z T | T | x - - if (obj.y() + obj.Shape->oy() + obj.Shape->width() <= min_y) + // only comparing two objects by their y and z coordinate, we + // want to figure out if an object is in front of the other or + // not. For that purpose, we divide space into 9 segments, with + // object two as the center segement #8. We then check in which + // segment the first object is located. + // + // segments coordinates is below? + // 2 | 3 | 4 y x | F | F + // --+---+-- ^ --+---+-- max_y + // 1 | 8 | 5 | T | o | F + // --+---+-- | --+---+-- min_y + // 0 | 7 | 6 +-----> z T | T | x + // \ \-- max_z + // \----- min_z + // + // If the first object falls completely into segment 0, 1 or 7 + // it is below object two. In segment 3, 4 or 5 it is above. + // In segments 2 and 6, the two objects do not overlap, but + // since that is tested in advance, this case should not occur. + // Finally, if object one falls into segment 8, the two objects + // intersect and there is no easy answer which object is in front + // and which behind. + + if (obj.y() + obj.Shape->width() <= min_y) { - if (obj.z() + obj.Shape->height() <= min_z) // 0 + if (obj.z() + obj.Shape->height() <= min_z) // segment 0 { return true; } - else if (obj.z() >= max_z) // 6 + else if (obj.z() >= max_z) // segment 6 { fprintf (stderr, "*** default_renderer::is_object_below: objects do not overlap!\n"); fprintf (stderr, " [%i, %i, %i] - [%i, %i, %i]\n", min_x, min_y, min_z, max_x, max_y, max_z); fprintf (stderr, " (%i, %i, %i) - (%i, %i, %i)\n", obj.x(), obj.y(), obj.z(), obj.x() + obj.Shape->length(), obj.y() + obj.Shape->width(), obj.z() + obj.Shape->height()); return false; } - else // 7 + else // segment 7 { return true; } } - else if (obj.y() + obj.Shape->oy() >= max_y) + else if (obj.y() >= max_y) { - if(obj.z() + obj.Shape->height() <= min_z) // 2 + if(obj.z() + obj.Shape->height() <= min_z) // segment 2 { fprintf (stderr, "*** default_renderer::is_object_below: objects do not overlap!\n"); fprintf (stderr, " [%i, %i, %i] - [%i, %i, %i]\n", min_x, min_y, min_z, max_x, max_y, max_z); fprintf (stderr, " (%i, %i, %i) - (%i, %i, %i)\n", obj.x(), obj.y(), obj.z(), obj.x() + obj.Shape->length(), obj.y() + obj.Shape->width(), obj.z() + obj.Shape->height()); return false; } - else if(obj.z() >= max_z) // 4 + else if(obj.z() >= max_z) // segment 4 { return false; } @@ -197,66 +222,124 @@ bool default_renderer::is_object_below (const render_info & obj, const render_in return false; } } - else if (obj.z() >= max_z) // 5 + else if (obj.z() >= max_z) // segment 5 { return false; } - else if (obj.z() + obj.Shape->height() <= min_z) // 1 + else if (obj.z() + obj.Shape->height() <= min_z) // segment 1 { return true; } - else // 8 + else // segment 8 { + // TODO: here we'd need to cut non-solid flat objects that intersect with + // solid non-flat objects + // this will be allowed for certain cases and needs code to split objects for correct rendering // in other cases, it cannot be avoided due to map structure or differences between collision // detection and the code utilized here. - - // fprintf (stderr, "*** default_renderer::is_object_below: object intersection!\n"); - // fprintf (stderr, "\n [%i, %i, %i] - [%i, %i, %i]\n", min_x, min_y, min_z, max_x, max_y, max_z); - // fprintf (stderr, " (%i, %i, %i) - (%i, %i, %i)\n", obj.x(), obj.y(), obj.z(), obj.x() + obj.Shape->length(), obj.y() + obj.Shape->width(), obj.z() + obj.Shape->height()); if (obj.Shape->is_flat() == obj2.Shape->is_flat()) { if (obj.Shape->is_flat()) { - // both objects are floor tiles --> draw the one first that's below - return obj.z() + obj.Shape->height() < max_z; + // both objects are floor tiles + if (obj.z() + obj.Shape->height() != max_z) + { + // draw the one first that's below + return obj.z() + obj.Shape->height() < max_z; + } + else + { + // equal position --> draw the thinner first + return obj.Shape->height() > obj2.Shape->height(); + } } else { - // both objects are walls --> draw the one first that's behind - return obj.y() + obj.Shape->oy() < min_y; + // both objects are walls + if (obj.y() + obj.Shape->width() != max_y) + { + // draw the one first that's behind + return obj.y() + obj.Shape->width() < max_y; + } + else + { + // equal position --> draw the thinner first + return obj.Shape->width() > obj2.Shape->width(); + } } } - - // TODO: here we'd need to cut non-solid flat objects that intersect with - // solid non-flat objects - return obj2.Shape->is_flat(); - /* - s_int32 oy = 0, oz = 0; - // figure out area of least overlap - if (obj.y() + obj.Shape->oy() + obj.Shape->width() >= min_y && obj.y() + obj.Shape->oy() + obj.Shape->width() <= max_y) - oy = obj.y() + obj.Shape->oy() + obj.Shape->width() - min_y; - else if (max_y >= obj.y() + obj.Shape->oy() && max_y <= obj.y() + obj.Shape->oy() + obj.Shape->width()) - oy = obj.y() + obj.Shape->oy() - max_y; - - if (obj.z() + obj.Shape->height() >= min_z && obj.z() + obj.Shape->height() <= max_z) - oz = obj.z() + obj.Shape->height() - min_z; - else if (max_z >= obj.z() && max_z <= obj.z() + obj.Shape->height()) - oz = obj.z() - max_z; + // ZT YB YT ZB 0 + // _______ + // | | ---|--- | | | | + // |-|--- | ___|___ ---|-| ---|--- + // | | | ---'--- | | | + + int res = 0; + if (obj.y() >= min_y && obj.y() <= max_y && obj.y() + obj.Shape->width() >= max_y) + { + res += YT; // obj in front of obj2 + } + else if (obj.y() + obj.Shape->width() <= max_y && obj.y() + obj.Shape->width() >= min_y && obj.y() <= min_y) + { + res += YB; // obj behind obj2 + } - if (oy != 0 && abs(oy) < abs(oz)) + if (obj.z() >= min_z && obj.z() <= max_z && obj.z() + obj.Shape->height() >= max_z) { - // fprintf (stderr, " [%i, %i, %i] - [%i, %i, %i]\n", min_x, min_y + (oy > 0 ? oy : 0), min_z, max_x, max_y + (oy < 0 ? oy : 0), max_z); - return is_object_below (obj, min_x, min_y + (oy > 0 ? oy : 0), min_z, max_x, max_y + (oy < 0 ? oy : 0), max_z); + res += ZT; // obj on top obj2 } - else + else if (obj.z() + obj.Shape->height() <= max_z && obj.z() + obj.Shape->height() >= min_z && obj.z() <= min_z) { - // fprintf (stderr, " [%i, %i, %i] - [%i, %i, %i]\n", min_x, min_y, min_z + (oz > 0 ? oz : 0), max_x, max_y, max_z + (oz < 0 ? oz : 0)); - return is_object_below (obj, min_x, min_y, min_z + (oz > 0 ? oz : 0), max_x, max_y, max_z + (oz < 0 ? oz : 0)); + res += ZB; // obj below obj2 + } + + switch (res) + { + case 0: + { + fprintf (stderr, "*** default_renderer::is_object_below: total intersection of objects!\n"); // E + fprintf (stderr, " [%i, %i, %i] - [%i, %i, %i]\n", min_x, min_y, min_z, max_x, max_y, max_z); + fprintf (stderr, " (%i, %i, %i) - (%i, %i, %i)\n", obj.x(), obj.y(), obj.z(), obj.x() + obj.Shape->length(), obj.y() + obj.Shape->width(), obj.z() + obj.Shape->height()); + + // FIXME: this is a bad guess + return obj2.Shape->is_flat(); + } + case YT: + { + return false; + } + case YB: + { + return true; + } + case ZT: + { + return false; + } + case ZB: + { + return true; + } + case YT|ZT: + { + return false; + } + case YT|ZB: + { + return true; + } + case YB|ZT: + { + return false; + } + case YB|ZB: + { + return true; + } } - */ } } @@ -267,8 +350,15 @@ void debug_renderer::draw (const s_int16 & x, const s_int16 & y, const render_in if (DrawBBox) { - cube3 bbox (obj.Shape->length(), obj.Shape->width(), obj.Shape->height()); - bbox.draw (x + obj.x(), y + obj.y() - obj.z(), &da, target); + // draw detailed collision information + for (std::vector::const_iterator i = obj.Shape->begin(); i != obj.Shape->end(); i++) + { + (*i)->draw (x + obj.Pos.x(), y + obj.Pos.y() - obj.Pos.z(), &da, target); + } + + // draw complete bbox around placeable + // cube3 bbox (obj.Shape->length(), obj.Shape->width(), obj.Shape->height()); + // bbox.draw (x + obj.x(), y + obj.y() - obj.z(), &da, target); } if (Print) @@ -287,4 +377,53 @@ void debug_renderer::draw (const s_int16 & x, const s_int16 & y, const render_in } } +void default_renderer::visualize_deadlock (std::list & render_queue) const +{ + std::vector queue (render_queue.begin(), render_queue.end()); + + std::ofstream graph ("deadlock.dot"); + graph << "digraph deadlock {" << std::endl; + + // print all nodes + for (int i = 0; i < render_queue.size(); i++) + { + const render_info & ri = queue[i]; + graph << "n" << i << " [label=\"" << ri.Pos << "\\n" << ri.Shape->get_min() << " - " << ri.Shape->get_max() << "\"];" << std::endl; + } + + graph << std::endl; + + // check each object if it can be drawn + for (int j = 0; j < render_queue.size(); j++) + { + for (int i = 0; i < render_queue.size(); i++) + { + // ... but not with itself + if (i == j) continue; + + const render_info & ri = queue[i]; + const render_info & rj = queue[j]; + + // if objects don't overlap, we're still good + if (rj.min_x() >= ri.max_x() || + rj.min_yz() >= ri.max_yz() || + ri.min_x() >= rj.max_x() || + ri.min_yz() >= rj.max_yz()) + continue; + + // objects do overlap, so we need to figure out position of objects + // relative to each other + if (is_object_below (ri, rj)) + { + // draw and remove from queue + graph << " n" << j << " -> n" << i << ";" << std::endl; + break; + } + } + } + + graph << "}" << std::endl; + graph.close(); +} + } diff --git a/src/world/renderer.h b/src/world/renderer.h index 4cbd7be..6ad5ff8 100644 --- a/src/world/renderer.h +++ b/src/world/renderer.h @@ -139,7 +139,17 @@ class default_renderer : public renderer_base * @return true if overlap occurs, false otherwise. */ bool can_draw_object (render_info & obj, const_iterator & begin, const_iterator & end) const; + + /** + * Check if obj1 is below obj2 in the view. + * @param obj1 an object + * @param obj2 another object + * @return true if obj1 is located beneath obj2 in the view, false otherwise. + */ bool is_object_below (const render_info & obj1, const render_info & obj2) const; + +private: + void visualize_deadlock (std::list & render_queue) const; }; /** diff --git a/src/world/schedule.cc b/src/world/schedule.cc index 5fa4b8b..e965068 100644 --- a/src/world/schedule.cc +++ b/src/world/schedule.cc @@ -1,7 +1,5 @@ /* - $Id: schedule.cc,v 1.2 2009/05/03 16:26:00 ksterker Exp $ - - Copyright (C) 2004/2005/2006 Kai Sterker + Copyright (C) 2004/2005/2006/2010 Kai Sterker Part of the Adonthell Project http://adonthell.linuxgames.com Adonthell is free software; you can redistribute it and/or modify @@ -202,31 +200,16 @@ void schedule::queue_alarm (const string & time, const bool & absolute) // add the schedule object to the python argument tuple PyObject *schedule::add_schedule (PyObject* args) const { - // make sure the given arguments are a tuple - if (args && !PyTuple_Check (args)) + // prepare callback arguments + PyObject *new_args = python::pad_tuple (args, 1); + if (new_args == NULL) { - fprintf (stderr, "*** warning: schedule::add_schedule: args must be a tuple!\n"); return args; } - // calculate size of argument tuple required - u_int16 size = args ? PyTuple_GET_SIZE (args) + 1 : 1; - - // prepare callback arguments - PyObject *new_args = PyTuple_New (size); - // first argument is the schedule itself PyTuple_SET_ITEM (new_args, 0, python::pass_instance (this)); - // prepare arguments - for (u_int16 i = 1; i < size; i++) - { - // copy remaining arguments, if any - PyObject *arg = PyTuple_GET_ITEM (args, i - 1); - Py_INCREF (arg); - PyTuple_SET_ITEM (new_args, i, arg); - } - return new_args; } diff --git a/src/world/test_renderer.cc b/src/world/test_renderer.cc new file mode 100644 index 0000000..df20cbe --- /dev/null +++ b/src/world/test_renderer.cc @@ -0,0 +1,131 @@ +/* + Copyright (C) 2010 Kai Sterker + Part of the Adonthell Project http://adonthell.linuxgames.com + + Adonthell is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Adonthell is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Adonthell; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "base/logging.h" +#include + +#include "world/renderer.h" + +namespace world +{ + class renderer_Test : public ::testing::Test, public default_renderer + { + protected: + renderer_Test() + { + } + + virtual ~renderer_Test() + { + } + + // If the constructor and destructor are not enough for setting up + // and cleaning up each test, you can define the following methods: + + virtual void SetUp() + { + cube3 *c1 = new cube3 (vector3(0, 0, -5), vector3(128, 96, 0)); + cube3 *c2 = new cube3 (vector3(0, 0, 0), vector3(128, 12, 100)); + + s1.add_part (c1); + s2.add_part (c2); + } + + virtual void TearDown() + { + // Code here will be called immediately after each test (right + // before the destructor). + } + + placeable_shape s1; + placeable_shape s2; + }; // class{} + + TEST_F(renderer_Test, is_object_below_1) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 224, -100), NULL); + + EXPECT_EQ(false, is_object_below (obj1, obj2)); + EXPECT_EQ(true, is_object_below (obj2, obj1)); + } + + TEST_F(renderer_Test, is_object_below_2) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 280, -100), NULL); + + EXPECT_EQ(false, is_object_below (obj1, obj2)); + EXPECT_EQ(true, is_object_below (obj2, obj1)); + } + + TEST_F(renderer_Test, is_object_below_3) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 196, -100), NULL); + + EXPECT_EQ(false, is_object_below (obj1, obj2)); + EXPECT_EQ(true, is_object_below (obj2, obj1)); + } + + TEST_F(renderer_Test, is_object_below_4) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 182, -5), NULL); + + EXPECT_EQ(true, is_object_below (obj1, obj2)); + EXPECT_EQ(false, is_object_below (obj2, obj1)); + } + + TEST_F(renderer_Test, is_object_below_5) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 280, -5), NULL); + + EXPECT_EQ(true, is_object_below (obj1, obj2)); + EXPECT_EQ(false, is_object_below (obj2, obj1)); + } + + TEST_F(renderer_Test, is_object_below_6) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 224, -5), NULL); + + EXPECT_EQ(true, is_object_below (obj1, obj2)); + EXPECT_EQ(false, is_object_below (obj2, obj1)); + } +/* + TEST_F(renderer_Test, is_object_below_7) + { + render_info obj1 (&s1, NULL, vector3(-256, 192, 0), NULL); + render_info obj2 (&s2, NULL, vector3(-256, 224, -100), NULL); + + EXPECT_EQ(true, is_object_below (obj1, obj2)); + EXPECT_EQ(false, is_object_below (obj2, obj1)); + } +*/ +} // namespace{} + + +int main(int argc, char **argv) +{ + ::google::InitGoogleLogging(argv[0]); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}